@wordpress/block-editor 15.9.1-next.8b30e05b0.0 → 15.10.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.
- package/CHANGELOG.md +2 -0
- package/README.md +8 -0
- package/build/components/block-alignment-matrix-control/index.js +1 -8
- package/build/components/block-alignment-matrix-control/index.js.map +2 -2
- package/build/components/block-bindings/attribute-control.js +172 -0
- package/build/components/block-bindings/attribute-control.js.map +7 -0
- package/build/components/block-bindings/index.js +47 -0
- package/build/components/block-bindings/index.js.map +7 -0
- package/build/components/block-bindings/source-fields-list.js +135 -0
- package/build/components/block-bindings/source-fields-list.js.map +7 -0
- package/build/components/block-bindings/use-block-bindings-utils.js +66 -0
- package/build/components/block-bindings/use-block-bindings-utils.js.map +7 -0
- package/build/components/block-edit/edit.js +1 -3
- package/build/components/block-edit/edit.js.map +2 -2
- package/build/components/block-inspector/edit-contents.js +93 -14
- package/build/components/block-inspector/edit-contents.js.map +3 -3
- package/build/components/block-inspector/index.js +44 -28
- package/build/components/block-inspector/index.js.map +2 -2
- package/build/components/block-settings-menu-controls/edit-section-menu-item.js +39 -9
- package/build/components/block-settings-menu-controls/edit-section-menu-item.js.map +3 -3
- package/build/components/block-styles/preview-panel.js +3 -5
- package/build/components/block-styles/preview-panel.js.map +2 -2
- package/build/components/block-styles/use-styles-for-block.js +2 -2
- package/build/components/block-styles/use-styles-for-block.js.map +2 -2
- package/build/components/block-toolbar/index.js +1 -8
- package/build/components/block-toolbar/index.js.map +3 -3
- package/build/components/content-only-controls/index.js +2 -25
- package/build/components/content-only-controls/index.js.map +2 -2
- package/build/components/content-only-controls/link/index.js +3 -3
- package/build/components/content-only-controls/link/index.js.map +2 -2
- package/build/components/content-only-controls/media/index.js +3 -3
- package/build/components/content-only-controls/media/index.js.map +2 -2
- package/build/components/content-only-controls/rich-text/index.js +3 -2
- package/build/components/content-only-controls/rich-text/index.js.map +2 -2
- package/build/components/dimensions-tool/width-height-tool.js +4 -16
- package/build/components/dimensions-tool/width-height-tool.js.map +3 -3
- package/build/components/grid/grid-item-resizer.js +9 -5
- package/build/components/grid/grid-item-resizer.js.map +2 -2
- package/build/components/image-editor/cropper.js +3 -34
- package/build/components/image-editor/cropper.js.map +3 -3
- package/build/components/image-editor/index.js +9 -3
- package/build/components/image-editor/index.js.map +2 -2
- package/build/components/image-editor/use-transform-image.js +62 -32
- package/build/components/image-editor/use-transform-image.js.map +2 -2
- package/build/components/image-editor/zoom-dropdown.js +2 -2
- package/build/components/image-editor/zoom-dropdown.js.map +2 -2
- package/build/components/index.js +7 -3
- package/build/components/index.js.map +2 -2
- package/build/components/inserter/hooks/use-insertion-point.js +5 -2
- package/build/components/inserter/hooks/use-insertion-point.js.map +2 -2
- package/build/components/inserter-draggable-blocks/index.js +8 -4
- package/build/components/inserter-draggable-blocks/index.js.map +2 -2
- package/build/components/inspector-controls-tabs/content-tab.js +3 -2
- package/build/components/inspector-controls-tabs/content-tab.js.map +2 -2
- package/build/components/link-control/index.js +1 -1
- package/build/components/link-control/index.js.map +2 -2
- package/build/components/link-control/search-input.js +2 -2
- package/build/components/link-control/search-input.js.map +2 -2
- package/build/hooks/block-bindings.js +22 -260
- package/build/hooks/block-bindings.js.map +3 -3
- package/build/layouts/grid.js +23 -28
- package/build/layouts/grid.js.map +2 -2
- package/build/private-apis.js +1 -0
- package/build/private-apis.js.map +2 -2
- package/build/store/private-keys.js +3 -0
- package/build/store/private-keys.js.map +2 -2
- package/build/store/private-selectors.js +2 -1
- package/build/store/private-selectors.js.map +2 -2
- package/build/store/reducer.js +3 -2
- package/build/store/reducer.js.map +2 -2
- package/build/utils/block-bindings.js +2 -44
- package/build/utils/block-bindings.js.map +3 -3
- package/build/utils/index.js +2 -5
- package/build/utils/index.js.map +2 -2
- package/build-module/components/block-alignment-matrix-control/index.js +1 -8
- package/build-module/components/block-alignment-matrix-control/index.js.map +2 -2
- package/build-module/components/block-bindings/attribute-control.js +150 -0
- package/build-module/components/block-bindings/attribute-control.js.map +7 -0
- package/build-module/components/block-bindings/index.js +10 -0
- package/build-module/components/block-bindings/index.js.map +7 -0
- package/build-module/components/block-bindings/source-fields-list.js +104 -0
- package/build-module/components/block-bindings/source-fields-list.js.map +7 -0
- package/build-module/components/block-bindings/use-block-bindings-utils.js +45 -0
- package/build-module/components/block-bindings/use-block-bindings-utils.js.map +7 -0
- package/build-module/components/block-edit/edit.js +1 -3
- package/build-module/components/block-edit/edit.js.map +2 -2
- package/build-module/components/block-inspector/edit-contents.js +93 -14
- package/build-module/components/block-inspector/edit-contents.js.map +2 -2
- package/build-module/components/block-inspector/index.js +44 -28
- package/build-module/components/block-inspector/index.js.map +2 -2
- package/build-module/components/block-settings-menu-controls/edit-section-menu-item.js +39 -9
- package/build-module/components/block-settings-menu-controls/edit-section-menu-item.js.map +2 -2
- package/build-module/components/block-styles/preview-panel.js +3 -5
- package/build-module/components/block-styles/preview-panel.js.map +2 -2
- package/build-module/components/block-styles/use-styles-for-block.js +2 -2
- package/build-module/components/block-styles/use-styles-for-block.js.map +2 -2
- package/build-module/components/block-toolbar/index.js +1 -8
- package/build-module/components/block-toolbar/index.js.map +2 -2
- package/build-module/components/content-only-controls/index.js +2 -25
- package/build-module/components/content-only-controls/index.js.map +2 -2
- package/build-module/components/content-only-controls/link/index.js +3 -3
- package/build-module/components/content-only-controls/link/index.js.map +2 -2
- package/build-module/components/content-only-controls/media/index.js +3 -3
- package/build-module/components/content-only-controls/media/index.js.map +2 -2
- package/build-module/components/content-only-controls/rich-text/index.js +3 -2
- package/build-module/components/content-only-controls/rich-text/index.js.map +2 -2
- package/build-module/components/dimensions-tool/width-height-tool.js +4 -6
- package/build-module/components/dimensions-tool/width-height-tool.js.map +2 -2
- package/build-module/components/grid/grid-item-resizer.js +9 -5
- package/build-module/components/grid/grid-item-resizer.js.map +2 -2
- package/build-module/components/image-editor/cropper.js +3 -34
- package/build-module/components/image-editor/cropper.js.map +2 -2
- package/build-module/components/image-editor/index.js +9 -3
- package/build-module/components/image-editor/index.js.map +2 -2
- package/build-module/components/image-editor/use-transform-image.js +63 -33
- package/build-module/components/image-editor/use-transform-image.js.map +2 -2
- package/build-module/components/image-editor/zoom-dropdown.js +2 -2
- package/build-module/components/image-editor/zoom-dropdown.js.map +2 -2
- package/build-module/components/index.js +74 -68
- package/build-module/components/index.js.map +2 -2
- package/build-module/components/inserter/hooks/use-insertion-point.js +5 -2
- package/build-module/components/inserter/hooks/use-insertion-point.js.map +2 -2
- package/build-module/components/inserter-draggable-blocks/index.js +8 -4
- package/build-module/components/inserter-draggable-blocks/index.js.map +2 -2
- package/build-module/components/inspector-controls-tabs/content-tab.js +3 -2
- package/build-module/components/inspector-controls-tabs/content-tab.js.map +2 -2
- package/build-module/components/link-control/index.js +1 -1
- package/build-module/components/link-control/index.js.map +2 -2
- package/build-module/components/link-control/search-input.js +2 -2
- package/build-module/components/link-control/search-input.js.map +2 -2
- package/build-module/hooks/block-bindings.js +27 -270
- package/build-module/hooks/block-bindings.js.map +2 -2
- package/build-module/layouts/grid.js +23 -28
- package/build-module/layouts/grid.js.map +2 -2
- package/build-module/private-apis.js +3 -1
- package/build-module/private-apis.js.map +2 -2
- package/build-module/store/private-keys.js +2 -0
- package/build-module/store/private-keys.js.map +2 -2
- package/build-module/store/private-selectors.js +4 -2
- package/build-module/store/private-selectors.js.map +2 -2
- package/build-module/store/reducer.js +4 -3
- package/build-module/store/reducer.js.map +2 -2
- package/build-module/utils/block-bindings.js +1 -42
- package/build-module/utils/block-bindings.js.map +2 -2
- package/build-module/utils/index.js +1 -3
- package/build-module/utils/index.js.map +2 -2
- package/build-style/style-rtl.css +6 -6
- package/build-style/style.css +6 -6
- package/package.json +39 -40
- package/src/components/block-alignment-matrix-control/index.js +1 -5
- package/src/components/block-bindings/attribute-control.js +174 -0
- package/src/components/block-bindings/index.js +6 -0
- package/src/components/block-bindings/source-fields-list.js +130 -0
- package/src/components/block-bindings/use-block-bindings-utils.js +156 -0
- package/src/components/block-edit/edit.js +1 -3
- package/src/components/block-inspector/edit-contents.js +108 -18
- package/src/components/block-inspector/index.js +53 -30
- package/src/components/block-settings-menu-controls/edit-section-menu-item.js +50 -6
- package/src/components/block-styles/preview-panel.js +3 -5
- package/src/components/block-styles/use-styles-for-block.js +2 -2
- package/src/components/block-toolbar/index.js +1 -6
- package/src/components/block-toolbar/style.scss +6 -6
- package/src/components/content-only-controls/index.js +2 -27
- package/src/components/content-only-controls/link/index.js +3 -3
- package/src/components/content-only-controls/media/index.js +3 -3
- package/src/components/content-only-controls/rich-text/index.js +3 -2
- package/src/components/dimensions-tool/width-height-tool.js +6 -13
- package/src/components/grid/grid-item-resizer.js +18 -5
- package/src/components/image-editor/cropper.js +3 -32
- package/src/components/image-editor/index.js +34 -29
- package/src/components/image-editor/use-transform-image.js +80 -34
- package/src/components/image-editor/zoom-dropdown.js +2 -2
- package/src/components/index.js +5 -1
- package/src/components/inserter/hooks/use-insertion-point.js +3 -0
- package/src/components/inserter/style.scss +1 -1
- package/src/components/inserter-draggable-blocks/index.js +19 -8
- package/src/components/inspector-controls-tabs/content-tab.js +6 -2
- package/src/components/link-control/index.js +1 -1
- package/src/components/link-control/search-input.js +8 -2
- package/src/components/link-control/test/index.js +146 -7
- package/src/hooks/block-bindings.js +27 -347
- package/src/layouts/grid.js +40 -72
- package/src/layouts/test/grid.js +14 -0
- package/src/private-apis.js +2 -0
- package/src/store/private-keys.js +1 -0
- package/src/store/private-selectors.js +8 -1
- package/src/store/reducer.js +10 -3
- package/src/utils/block-bindings.js +0 -157
- package/src/utils/index.js +0 -1
- package/tsconfig.json +1 -0
- package/build/components/block-toolbar/block-name-context.js +0 -30
- package/build/components/block-toolbar/block-name-context.js.map +0 -7
- package/build-module/components/block-toolbar/block-name-context.js +0 -9
- package/build-module/components/block-toolbar/block-name-context.js.map +0 -7
- package/src/components/block-toolbar/block-name-context.js +0 -9
- /package/src/{utils → components/block-bindings}/test/use-block-bindings-utils.js +0 -0
|
@@ -15,14 +15,18 @@ const ContentTab = ( { rootClientId, contentClientIds } ) => {
|
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const shouldShowContentOnlyControls =
|
|
19
|
+
window?.__experimentalContentOnlyPatternInsertion &&
|
|
20
|
+
window?.__experimentalContentOnlyInspectorFields;
|
|
21
|
+
|
|
18
22
|
return (
|
|
19
23
|
<>
|
|
20
|
-
{ !
|
|
24
|
+
{ ! shouldShowContentOnlyControls && (
|
|
21
25
|
<PanelBody title={ __( 'Content' ) }>
|
|
22
26
|
<BlockQuickNavigation clientIds={ contentClientIds } />
|
|
23
27
|
</PanelBody>
|
|
24
28
|
) }
|
|
25
|
-
{
|
|
29
|
+
{ shouldShowContentOnlyControls && (
|
|
26
30
|
<ContentOnlyControls rootClientId={ rootClientId } />
|
|
27
31
|
) }
|
|
28
32
|
</>
|
|
@@ -23,6 +23,7 @@ import { isShallowEqualObjects } from '@wordpress/is-shallow-equal';
|
|
|
23
23
|
import { useSelect, useDispatch } from '@wordpress/data';
|
|
24
24
|
import { store as preferencesStore } from '@wordpress/preferences';
|
|
25
25
|
import { keyboardReturn, linkOff } from '@wordpress/icons';
|
|
26
|
+
import deprecated from '@wordpress/deprecated';
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Internal dependencies
|
|
@@ -35,7 +36,6 @@ import useCreatePage from './use-create-page';
|
|
|
35
36
|
import useInternalValue from './use-internal-value';
|
|
36
37
|
import { ViewerFill } from './viewer-slot';
|
|
37
38
|
import { DEFAULT_LINK_SETTINGS } from './constants';
|
|
38
|
-
import deprecated from '@wordpress/deprecated';
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Default properties associated with a link control value.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { forwardRef, useState } from '@wordpress/element';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
|
+
import deprecated from '@wordpress/deprecated';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Internal dependencies
|
|
@@ -11,7 +12,6 @@ import { URLInput } from '../';
|
|
|
11
12
|
import LinkControlSearchResults from './search-results';
|
|
12
13
|
import { CREATE_TYPE } from './constants';
|
|
13
14
|
import useSearchHandler from './use-search-handler';
|
|
14
|
-
import deprecated from '@wordpress/deprecated';
|
|
15
15
|
|
|
16
16
|
// Must be a function as otherwise URLInput will default
|
|
17
17
|
// to the fetchLinkSuggestions passed in block editor settings
|
|
@@ -106,7 +106,13 @@ const LinkControlSearchInput = forwardRef(
|
|
|
106
106
|
allowDirectEntry ||
|
|
107
107
|
( suggestion && Object.keys( suggestion ).length >= 1 )
|
|
108
108
|
) {
|
|
109
|
-
|
|
109
|
+
// Strip out id, url, kind, and type from the current link to prevent
|
|
110
|
+
// entity metadata from persisting when switching to a different link type.
|
|
111
|
+
// For example, when changing from an entity link (kind: 'post-type', type: 'page')
|
|
112
|
+
// to a custom URL (type: 'link', no kind), we need to ensure the old 'kind'
|
|
113
|
+
// doesn't carry over. We do want to preserve other properites like title, though.
|
|
114
|
+
const { id, url, kind, type, ...restLinkProps } =
|
|
115
|
+
currentLink ?? {};
|
|
110
116
|
onSelect(
|
|
111
117
|
// Some direct entries don't have types or IDs, and we still need to clear the previous ones.
|
|
112
118
|
{ ...restLinkProps, ...suggestion },
|
|
@@ -1621,7 +1621,7 @@ describe( 'Selecting links', () => {
|
|
|
1621
1621
|
id: '1',
|
|
1622
1622
|
title: 'https://www.wordpress.org',
|
|
1623
1623
|
url: 'https://www.wordpress.org',
|
|
1624
|
-
type: '
|
|
1624
|
+
type: 'link',
|
|
1625
1625
|
},
|
|
1626
1626
|
], // Url.
|
|
1627
1627
|
] )(
|
|
@@ -1683,7 +1683,7 @@ describe( 'Selecting links', () => {
|
|
|
1683
1683
|
id: '1',
|
|
1684
1684
|
title: 'https://www.wordpress.org',
|
|
1685
1685
|
url: 'https://www.wordpress.org',
|
|
1686
|
-
type: '
|
|
1686
|
+
type: 'link',
|
|
1687
1687
|
},
|
|
1688
1688
|
], // Url.
|
|
1689
1689
|
] )(
|
|
@@ -2135,7 +2135,7 @@ describe( 'Rich link previews', () => {
|
|
|
2135
2135
|
id: '1',
|
|
2136
2136
|
title: 'WordPress.org', // Customize this for differentiation in assertions.
|
|
2137
2137
|
url: 'https://www.wordpress.org',
|
|
2138
|
-
type: '
|
|
2138
|
+
type: 'link',
|
|
2139
2139
|
};
|
|
2140
2140
|
|
|
2141
2141
|
beforeAll( () => {
|
|
@@ -2800,14 +2800,153 @@ describe( 'Entity handling', () => {
|
|
|
2800
2800
|
} );
|
|
2801
2801
|
await user.click( applyButton );
|
|
2802
2802
|
|
|
2803
|
-
// Verify that onChange was called with
|
|
2804
|
-
//
|
|
2803
|
+
// Verify that onChange was called with entity metadata cleared.
|
|
2804
|
+
// Kind should be undefined (no longer an entity).
|
|
2805
|
+
// Note: Currently when clicking Apply (vs selecting a suggestion),
|
|
2806
|
+
// type and id are also undefined - that's a separate issue with the
|
|
2807
|
+
// TODO: Apply button handler not processing URLs through handleDirectEntry,
|
|
2808
|
+
// so the shape of the data for a custom link can be different depending on
|
|
2809
|
+
// how it was submitted.
|
|
2805
2810
|
expect( onChange ).toHaveBeenCalledWith(
|
|
2806
2811
|
expect.objectContaining( {
|
|
2807
|
-
url:
|
|
2808
|
-
id: undefined,
|
|
2812
|
+
url: 'www.wordpress.org',
|
|
2809
2813
|
kind: undefined,
|
|
2814
|
+
} )
|
|
2815
|
+
);
|
|
2816
|
+
} );
|
|
2817
|
+
|
|
2818
|
+
it( 'should clear entity metadata (type/kind) when changing from page link to custom link via suggestion', async () => {
|
|
2819
|
+
const user = userEvent.setup();
|
|
2820
|
+
|
|
2821
|
+
// Start with an entity link that has type and kind
|
|
2822
|
+
const pageLink = {
|
|
2823
|
+
id: 123,
|
|
2824
|
+
url: 'https://example.com/page',
|
|
2825
|
+
title: 'Test Page',
|
|
2826
|
+
type: 'page',
|
|
2827
|
+
kind: 'post-type',
|
|
2828
|
+
};
|
|
2829
|
+
|
|
2830
|
+
const onChange = jest.fn();
|
|
2831
|
+
|
|
2832
|
+
// Mock search suggestions to return a custom URL
|
|
2833
|
+
// URL suggestions have an id and type but no 'kind' (which indicates entity metadata)
|
|
2834
|
+
mockFetchSearchSuggestions.mockImplementation( ( searchTerm ) => {
|
|
2835
|
+
const suggestions = [
|
|
2836
|
+
{
|
|
2837
|
+
id: uniqueId(),
|
|
2838
|
+
title: searchTerm,
|
|
2839
|
+
url: searchTerm,
|
|
2840
|
+
type: 'link', // URL suggestions have type 'link'
|
|
2841
|
+
// Importantly: no 'kind' property (entities have kind)
|
|
2842
|
+
},
|
|
2843
|
+
];
|
|
2844
|
+
return Promise.resolve( suggestions );
|
|
2845
|
+
} );
|
|
2846
|
+
|
|
2847
|
+
render(
|
|
2848
|
+
<LinkControl
|
|
2849
|
+
value={ pageLink }
|
|
2850
|
+
handleEntities
|
|
2851
|
+
forceIsEditingLink
|
|
2852
|
+
onChange={ onChange }
|
|
2853
|
+
/>
|
|
2854
|
+
);
|
|
2855
|
+
|
|
2856
|
+
const searchInput = screen.getByRole( 'combobox', {
|
|
2857
|
+
name: 'Search or type URL',
|
|
2858
|
+
} );
|
|
2859
|
+
|
|
2860
|
+
// Initially should be disabled because it's an entity
|
|
2861
|
+
expect( searchInput ).toBeDisabled();
|
|
2862
|
+
|
|
2863
|
+
// Click the unsync button to enable editing
|
|
2864
|
+
const unlinkButton = screen.getByRole( 'button', {
|
|
2865
|
+
name: 'Unsync and edit',
|
|
2866
|
+
} );
|
|
2867
|
+
await user.click( unlinkButton );
|
|
2868
|
+
|
|
2869
|
+
// Input should now be enabled and value should be cleared
|
|
2870
|
+
expect( searchInput ).toBeEnabled();
|
|
2871
|
+
expect( searchInput ).toHaveValue( '' );
|
|
2872
|
+
|
|
2873
|
+
// Type a custom URL
|
|
2874
|
+
await user.type( searchInput, 'https://custom-url.com' );
|
|
2875
|
+
|
|
2876
|
+
// Wait for suggestions to appear
|
|
2877
|
+
const suggestionsList = await screen.findByRole( 'listbox' );
|
|
2878
|
+
expect( suggestionsList ).toBeVisible();
|
|
2879
|
+
|
|
2880
|
+
// Select the custom URL suggestion (not clicking Apply button)
|
|
2881
|
+
const urlSuggestion = screen.getByRole( 'option', {
|
|
2882
|
+
name: /https:\/\/custom-url\.com/,
|
|
2883
|
+
} );
|
|
2884
|
+
await user.click( urlSuggestion );
|
|
2885
|
+
|
|
2886
|
+
// Verify that onChange was called with id, type and kind explicitly set to undefined
|
|
2887
|
+
// This is the critical fix - when selecting a custom URL suggestion after unlinking,
|
|
2888
|
+
// entity metadata (type/kind) should be cleared (not just when using the Apply button)
|
|
2889
|
+
expect( onChange ).toHaveBeenCalledWith(
|
|
2890
|
+
expect.objectContaining( {
|
|
2891
|
+
url: 'https://custom-url.com',
|
|
2892
|
+
type: 'link',
|
|
2893
|
+
kind: undefined,
|
|
2894
|
+
} )
|
|
2895
|
+
);
|
|
2896
|
+
} );
|
|
2897
|
+
|
|
2898
|
+
it( 'should clear entity metadata when pressing Enter for direct entry (without clicking suggestion)', async () => {
|
|
2899
|
+
const user = userEvent.setup();
|
|
2900
|
+
const onChange = jest.fn();
|
|
2901
|
+
|
|
2902
|
+
const pageLink = {
|
|
2903
|
+
id: 123,
|
|
2904
|
+
url: 'https://example.com/page',
|
|
2905
|
+
title: 'Test Page',
|
|
2906
|
+
type: 'page',
|
|
2907
|
+
kind: 'post-type',
|
|
2908
|
+
};
|
|
2909
|
+
|
|
2910
|
+
render(
|
|
2911
|
+
<LinkControl
|
|
2912
|
+
value={ pageLink }
|
|
2913
|
+
handleEntities
|
|
2914
|
+
forceIsEditingLink
|
|
2915
|
+
onChange={ onChange }
|
|
2916
|
+
/>
|
|
2917
|
+
);
|
|
2918
|
+
|
|
2919
|
+
const searchInput = screen.getByRole( 'combobox', {
|
|
2920
|
+
name: 'Search or type URL',
|
|
2921
|
+
} );
|
|
2922
|
+
|
|
2923
|
+
// Initially should be disabled because it's an entity
|
|
2924
|
+
expect( searchInput ).toBeDisabled();
|
|
2925
|
+
|
|
2926
|
+
// Click the unsync button to enable editing
|
|
2927
|
+
const unlinkButton = screen.getByRole( 'button', {
|
|
2928
|
+
name: 'Unsync and edit',
|
|
2929
|
+
} );
|
|
2930
|
+
await user.click( unlinkButton );
|
|
2931
|
+
|
|
2932
|
+
// Input should now be enabled and value should be cleared
|
|
2933
|
+
expect( searchInput ).toBeEnabled();
|
|
2934
|
+
expect( searchInput ).toHaveValue( '' );
|
|
2935
|
+
|
|
2936
|
+
// Type a custom URL
|
|
2937
|
+
await user.type( searchInput, 'https://direct-entry.com' );
|
|
2938
|
+
|
|
2939
|
+
// Press Enter WITHOUT clicking the suggestion (direct entry path)
|
|
2940
|
+
triggerEnter( searchInput );
|
|
2941
|
+
|
|
2942
|
+
// Verify that onChange was called with type and kind explicitly set to undefined
|
|
2943
|
+
// This tests the direct entry path in onSubmit (lines 157-165 in search-input.js)
|
|
2944
|
+
// where the user types a URL and presses Enter without selecting from suggestions
|
|
2945
|
+
expect( onChange ).toHaveBeenCalledWith(
|
|
2946
|
+
expect.objectContaining( {
|
|
2947
|
+
url: 'https://direct-entry.com',
|
|
2810
2948
|
type: undefined,
|
|
2949
|
+
kind: undefined,
|
|
2811
2950
|
} )
|
|
2812
2951
|
);
|
|
2813
2952
|
} );
|
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import fastDeepEqual from 'fast-deep-equal/es6';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* WordPress dependencies
|
|
8
3
|
*/
|
|
9
4
|
import { __ } from '@wordpress/i18n';
|
|
10
|
-
import {
|
|
11
|
-
getBlockBindingsSource,
|
|
12
|
-
getBlockType,
|
|
13
|
-
store as blockStore,
|
|
14
|
-
} from '@wordpress/blocks';
|
|
5
|
+
import { store as blocksStore } from '@wordpress/blocks';
|
|
15
6
|
import {
|
|
16
7
|
__experimentalItemGroup as ItemGroup,
|
|
17
|
-
__experimentalItem as Item,
|
|
18
8
|
__experimentalText as Text,
|
|
19
9
|
__experimentalToolsPanel as ToolsPanel,
|
|
20
|
-
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
21
|
-
__experimentalVStack as VStack,
|
|
22
|
-
privateApis as componentsPrivateApis,
|
|
23
10
|
} from '@wordpress/components';
|
|
24
11
|
import { useSelect } from '@wordpress/data';
|
|
25
12
|
import { useContext } from '@wordpress/element';
|
|
@@ -28,29 +15,15 @@ import { useViewportMatch } from '@wordpress/compose';
|
|
|
28
15
|
/**
|
|
29
16
|
* Internal dependencies
|
|
30
17
|
*/
|
|
31
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
BlockBindingsAttributeControl,
|
|
20
|
+
useBlockBindingsUtils,
|
|
21
|
+
} from '../components/block-bindings';
|
|
32
22
|
import { unlock } from '../lock-unlock';
|
|
33
23
|
import InspectorControls from '../components/inspector-controls';
|
|
34
24
|
import BlockContext from '../components/block-context';
|
|
35
|
-
import { useBlockEditContext } from '../components/block-edit';
|
|
36
25
|
import { store as blockEditorStore } from '../store';
|
|
37
26
|
|
|
38
|
-
const { Menu } = unlock( componentsPrivateApis );
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get the normalized attribute type for block bindings.
|
|
42
|
-
* Converts 'rich-text' to 'string' since rich-text is stored as string.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} blockName The block name.
|
|
45
|
-
* @param {string} attribute The attribute name.
|
|
46
|
-
* @return {string} The normalized attribute type.
|
|
47
|
-
*/
|
|
48
|
-
const getAttributeType = ( blockName, attribute ) => {
|
|
49
|
-
const _attributeType =
|
|
50
|
-
getBlockType( blockName ).attributes?.[ attribute ]?.type;
|
|
51
|
-
return _attributeType === 'rich-text' ? 'string' : _attributeType;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
27
|
const useToolsPanelDropdownMenuProps = () => {
|
|
55
28
|
const isMobile = useViewportMatch( 'medium', '<' );
|
|
56
29
|
return ! isMobile
|
|
@@ -64,295 +37,35 @@ const useToolsPanelDropdownMenuProps = () => {
|
|
|
64
37
|
: {};
|
|
65
38
|
};
|
|
66
39
|
|
|
67
|
-
function BlockBindingsPanelMenuContent( { attribute, binding, sources } ) {
|
|
68
|
-
const { clientId } = useBlockEditContext();
|
|
69
|
-
const { updateBlockBindings } = useBlockBindingsUtils();
|
|
70
|
-
const isMobile = useViewportMatch( 'medium', '<' );
|
|
71
|
-
const blockContext = useContext( BlockContext );
|
|
72
|
-
const { attributeType, select } = useSelect(
|
|
73
|
-
( _select ) => {
|
|
74
|
-
const { name: blockName } =
|
|
75
|
-
_select( blockEditorStore ).getBlock( clientId );
|
|
76
|
-
return {
|
|
77
|
-
attributeType: getAttributeType( blockName, attribute ),
|
|
78
|
-
select: _select,
|
|
79
|
-
};
|
|
80
|
-
},
|
|
81
|
-
[ clientId, attribute ]
|
|
82
|
-
);
|
|
83
|
-
return (
|
|
84
|
-
<Menu placement={ isMobile ? 'bottom-start' : 'left-start' }>
|
|
85
|
-
{ Object.entries( sources ).map( ( [ sourceKey, data ] ) => {
|
|
86
|
-
// Only show sources that have compatible data for this specific attribute.
|
|
87
|
-
const sourceDataItems = data.filter(
|
|
88
|
-
( item ) => item.type === attributeType
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const noItemsAvailable =
|
|
92
|
-
! sourceDataItems || sourceDataItems.length === 0;
|
|
93
|
-
|
|
94
|
-
if ( noItemsAvailable ) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const source = getBlockBindingsSource( sourceKey );
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<Menu
|
|
102
|
-
key={ sourceKey }
|
|
103
|
-
placement={ isMobile ? 'bottom-start' : 'left-start' }
|
|
104
|
-
>
|
|
105
|
-
<Menu.SubmenuTriggerItem>
|
|
106
|
-
<Menu.ItemLabel>{ source.label }</Menu.ItemLabel>
|
|
107
|
-
</Menu.SubmenuTriggerItem>
|
|
108
|
-
<Menu.Popover gutter={ 8 }>
|
|
109
|
-
<Menu.Group>
|
|
110
|
-
{ sourceDataItems.map( ( item ) => {
|
|
111
|
-
const itemBindings = {
|
|
112
|
-
source: sourceKey,
|
|
113
|
-
args: item.args || {
|
|
114
|
-
key: item.key,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
let values = {};
|
|
118
|
-
try {
|
|
119
|
-
values = source.getValues( {
|
|
120
|
-
select,
|
|
121
|
-
context: blockContext,
|
|
122
|
-
bindings: {
|
|
123
|
-
[ attribute ]: itemBindings,
|
|
124
|
-
},
|
|
125
|
-
} );
|
|
126
|
-
} catch ( e ) {}
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<Menu.CheckboxItem
|
|
130
|
-
key={
|
|
131
|
-
sourceKey +
|
|
132
|
-
JSON.stringify(
|
|
133
|
-
item.args
|
|
134
|
-
) || item.key
|
|
135
|
-
}
|
|
136
|
-
onChange={ () => {
|
|
137
|
-
const isCurrentlySelected =
|
|
138
|
-
fastDeepEqual(
|
|
139
|
-
binding?.args,
|
|
140
|
-
item.args
|
|
141
|
-
) ??
|
|
142
|
-
// Deprecate key dependency in 7.0.
|
|
143
|
-
item.key ===
|
|
144
|
-
binding?.args?.key;
|
|
145
|
-
|
|
146
|
-
if ( isCurrentlySelected ) {
|
|
147
|
-
// Unset if the same item is selected again.
|
|
148
|
-
updateBlockBindings( {
|
|
149
|
-
[ attribute ]:
|
|
150
|
-
undefined,
|
|
151
|
-
} );
|
|
152
|
-
} else {
|
|
153
|
-
updateBlockBindings( {
|
|
154
|
-
[ attribute ]:
|
|
155
|
-
itemBindings,
|
|
156
|
-
} );
|
|
157
|
-
}
|
|
158
|
-
} }
|
|
159
|
-
name={ attribute + '-binding' }
|
|
160
|
-
value={ values[ attribute ] }
|
|
161
|
-
checked={
|
|
162
|
-
fastDeepEqual(
|
|
163
|
-
binding?.args,
|
|
164
|
-
item.args
|
|
165
|
-
) ??
|
|
166
|
-
// Deprecate key dependency in 7.0.
|
|
167
|
-
item.key === binding?.args?.key
|
|
168
|
-
}
|
|
169
|
-
>
|
|
170
|
-
<Menu.ItemLabel>
|
|
171
|
-
{ item.label }
|
|
172
|
-
</Menu.ItemLabel>
|
|
173
|
-
<Menu.ItemHelpText>
|
|
174
|
-
{ values[ attribute ] }
|
|
175
|
-
</Menu.ItemHelpText>
|
|
176
|
-
</Menu.CheckboxItem>
|
|
177
|
-
);
|
|
178
|
-
} ) }
|
|
179
|
-
</Menu.Group>
|
|
180
|
-
</Menu.Popover>
|
|
181
|
-
</Menu>
|
|
182
|
-
);
|
|
183
|
-
} ) }
|
|
184
|
-
</Menu>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function BlockBindingsAttribute( { attribute, binding, sources, blockName } ) {
|
|
189
|
-
const { source: sourceName, args } = binding || {};
|
|
190
|
-
const data = sources?.[ sourceName ];
|
|
191
|
-
const source = getBlockBindingsSource( sourceName );
|
|
192
|
-
|
|
193
|
-
let displayText;
|
|
194
|
-
let isValid = true;
|
|
195
|
-
const isNotBound = binding === undefined;
|
|
196
|
-
|
|
197
|
-
if ( isNotBound ) {
|
|
198
|
-
// Check if there are any compatible sources for this attribute type.
|
|
199
|
-
const attributeType = getAttributeType( blockName, attribute );
|
|
200
|
-
|
|
201
|
-
const hasCompatibleSources = Object.values( sources ).some( ( items ) =>
|
|
202
|
-
items.some( ( item ) => item.type === attributeType )
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
if ( ! hasCompatibleSources ) {
|
|
206
|
-
displayText = __( 'No sources available' );
|
|
207
|
-
} else {
|
|
208
|
-
displayText = __( 'Not connected' );
|
|
209
|
-
}
|
|
210
|
-
isValid = true;
|
|
211
|
-
} else if ( ! source ) {
|
|
212
|
-
// If there's a binding but the source is not found, it's invalid.
|
|
213
|
-
isValid = false;
|
|
214
|
-
displayText = __( 'Source not registered' );
|
|
215
|
-
} else {
|
|
216
|
-
displayText =
|
|
217
|
-
data?.find( ( item ) => fastDeepEqual( item.args, args ) )?.label ||
|
|
218
|
-
source?.label ||
|
|
219
|
-
sourceName;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<VStack className="block-editor-bindings__item" spacing={ 0 }>
|
|
224
|
-
<Text truncate>{ attribute }</Text>
|
|
225
|
-
<Text
|
|
226
|
-
truncate
|
|
227
|
-
variant={ isValid ? 'muted' : undefined }
|
|
228
|
-
isDestructive={ ! isValid }
|
|
229
|
-
>
|
|
230
|
-
{ displayText }
|
|
231
|
-
</Text>
|
|
232
|
-
</VStack>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function ReadOnlyBlockBindingsPanelItem( {
|
|
237
|
-
attribute,
|
|
238
|
-
binding,
|
|
239
|
-
sources,
|
|
240
|
-
blockName,
|
|
241
|
-
} ) {
|
|
242
|
-
const isMobile = useViewportMatch( 'medium', '<' );
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<ToolsPanelItem hasValue={ () => !! binding } label={ attribute }>
|
|
246
|
-
<Menu placement={ isMobile ? 'bottom-start' : 'left-start' }>
|
|
247
|
-
<Menu.TriggerButton render={ <Item /> } disabled>
|
|
248
|
-
<BlockBindingsAttribute
|
|
249
|
-
attribute={ attribute }
|
|
250
|
-
binding={ binding }
|
|
251
|
-
sources={ sources }
|
|
252
|
-
blockName={ blockName }
|
|
253
|
-
/>
|
|
254
|
-
</Menu.TriggerButton>
|
|
255
|
-
</Menu>
|
|
256
|
-
</ToolsPanelItem>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function EditableBlockBindingsPanelItem( {
|
|
261
|
-
attribute,
|
|
262
|
-
binding,
|
|
263
|
-
sources,
|
|
264
|
-
blockName,
|
|
265
|
-
} ) {
|
|
266
|
-
const { updateBlockBindings } = useBlockBindingsUtils();
|
|
267
|
-
const isMobile = useViewportMatch( 'medium', '<' );
|
|
268
|
-
|
|
269
|
-
return (
|
|
270
|
-
<ToolsPanelItem
|
|
271
|
-
hasValue={ () => !! binding }
|
|
272
|
-
label={ attribute }
|
|
273
|
-
onDeselect={ () => {
|
|
274
|
-
updateBlockBindings( {
|
|
275
|
-
[ attribute ]: undefined,
|
|
276
|
-
} );
|
|
277
|
-
} }
|
|
278
|
-
>
|
|
279
|
-
<Menu placement={ isMobile ? 'bottom-start' : 'left-start' }>
|
|
280
|
-
<Menu.TriggerButton render={ <Item /> }>
|
|
281
|
-
<BlockBindingsAttribute
|
|
282
|
-
attribute={ attribute }
|
|
283
|
-
binding={ binding }
|
|
284
|
-
sources={ sources }
|
|
285
|
-
blockName={ blockName }
|
|
286
|
-
/>
|
|
287
|
-
</Menu.TriggerButton>
|
|
288
|
-
<Menu.Popover gutter={ isMobile ? 8 : 36 }>
|
|
289
|
-
<BlockBindingsPanelMenuContent
|
|
290
|
-
attribute={ attribute }
|
|
291
|
-
binding={ binding }
|
|
292
|
-
sources={ sources }
|
|
293
|
-
/>
|
|
294
|
-
</Menu.Popover>
|
|
295
|
-
</Menu>
|
|
296
|
-
</ToolsPanelItem>
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
40
|
export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
|
|
301
41
|
const blockContext = useContext( BlockContext );
|
|
302
42
|
const { removeAllBlockBindings } = useBlockBindingsUtils();
|
|
303
43
|
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
|
|
304
44
|
|
|
305
|
-
|
|
306
|
-
// or when underlying data changes.
|
|
307
|
-
const { canUpdateBlockBindings, bindableAttributes } = useSelect(
|
|
45
|
+
const { bindableAttributes, hasCompatibleFields } = useSelect(
|
|
308
46
|
( select ) => {
|
|
309
47
|
const { __experimentalBlockBindingsSupportedAttributes } =
|
|
310
48
|
select( blockEditorStore ).getSettings();
|
|
49
|
+
const {
|
|
50
|
+
getAllBlockBindingsSources,
|
|
51
|
+
getBlockBindingsSourceFieldsList,
|
|
52
|
+
} = unlock( select( blocksStore ) );
|
|
311
53
|
|
|
312
54
|
return {
|
|
313
|
-
canUpdateBlockBindings:
|
|
314
|
-
select( blockEditorStore ).getSettings()
|
|
315
|
-
.canUpdateBlockBindings,
|
|
316
55
|
bindableAttributes:
|
|
317
56
|
__experimentalBlockBindingsSupportedAttributes?.[
|
|
318
57
|
blockName
|
|
319
58
|
],
|
|
59
|
+
hasCompatibleFields: Object.values(
|
|
60
|
+
getAllBlockBindingsSources()
|
|
61
|
+
).some(
|
|
62
|
+
( source ) =>
|
|
63
|
+
getBlockBindingsSourceFieldsList( source, blockContext )
|
|
64
|
+
?.length > 0
|
|
65
|
+
),
|
|
320
66
|
};
|
|
321
67
|
},
|
|
322
|
-
[ blockName ]
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const sources = useSelect(
|
|
326
|
-
( select ) => {
|
|
327
|
-
const { getAllBlockBindingsSources } = unlock(
|
|
328
|
-
select( blockStore )
|
|
329
|
-
);
|
|
330
|
-
const data = {};
|
|
331
|
-
Object.entries( getAllBlockBindingsSources() ).forEach(
|
|
332
|
-
( [ sourceName, source ] ) => {
|
|
333
|
-
if ( ! source.getFieldsList ) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const context = {};
|
|
338
|
-
if ( source.usesContext?.length ) {
|
|
339
|
-
for ( const key of source.usesContext ) {
|
|
340
|
-
context[ key ] = blockContext[ key ];
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const items = source.getFieldsList( {
|
|
345
|
-
select,
|
|
346
|
-
context,
|
|
347
|
-
} );
|
|
348
|
-
if ( items?.length ) {
|
|
349
|
-
data[ sourceName ] = items;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
);
|
|
353
|
-
return data;
|
|
354
|
-
},
|
|
355
|
-
[ blockContext ]
|
|
68
|
+
[ blockName, blockContext ]
|
|
356
69
|
);
|
|
357
70
|
|
|
358
71
|
// Return early if there are no bindable attributes.
|
|
@@ -362,12 +75,7 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
|
|
|
362
75
|
|
|
363
76
|
const { bindings } = metadata || {};
|
|
364
77
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// Lock the UI when the user can't update bindings or there are no fields to connect to.
|
|
368
|
-
const readOnly = ! canUpdateBlockBindings || ! hasCompatibleData;
|
|
369
|
-
|
|
370
|
-
if ( bindings === undefined && ! hasCompatibleData ) {
|
|
78
|
+
if ( bindings === undefined && ! hasCompatibleFields ) {
|
|
371
79
|
return null;
|
|
372
80
|
}
|
|
373
81
|
|
|
@@ -382,42 +90,14 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
|
|
|
382
90
|
className="block-editor-bindings__panel"
|
|
383
91
|
>
|
|
384
92
|
<ItemGroup isBordered isSeparated>
|
|
385
|
-
{ bindableAttributes.map( ( attribute ) =>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const hasCompatibleDataForAttribute = Object.values(
|
|
395
|
-
sources
|
|
396
|
-
).some( ( data ) =>
|
|
397
|
-
data.some( ( item ) => item.type === attributeType )
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
const isAttributeReadOnly =
|
|
401
|
-
readOnly || ! hasCompatibleDataForAttribute;
|
|
402
|
-
|
|
403
|
-
return isAttributeReadOnly ? (
|
|
404
|
-
<ReadOnlyBlockBindingsPanelItem
|
|
405
|
-
key={ attribute }
|
|
406
|
-
attribute={ attribute }
|
|
407
|
-
binding={ binding }
|
|
408
|
-
sources={ sources }
|
|
409
|
-
blockName={ blockName }
|
|
410
|
-
/>
|
|
411
|
-
) : (
|
|
412
|
-
<EditableBlockBindingsPanelItem
|
|
413
|
-
key={ attribute }
|
|
414
|
-
attribute={ attribute }
|
|
415
|
-
binding={ binding }
|
|
416
|
-
sources={ sources }
|
|
417
|
-
blockName={ blockName }
|
|
418
|
-
/>
|
|
419
|
-
);
|
|
420
|
-
} ) }
|
|
93
|
+
{ bindableAttributes.map( ( attribute ) => (
|
|
94
|
+
<BlockBindingsAttributeControl
|
|
95
|
+
key={ attribute }
|
|
96
|
+
attribute={ attribute }
|
|
97
|
+
blockName={ blockName }
|
|
98
|
+
binding={ bindings?.[ attribute ] }
|
|
99
|
+
/>
|
|
100
|
+
) ) }
|
|
421
101
|
</ItemGroup>
|
|
422
102
|
{ /*
|
|
423
103
|
Use a div element to make the ToolsPanelHiddenInnerWrapper
|