@wordpress/block-library 9.31.1-next.233ccab9b.0 → 9.32.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/build/accordion/edit.js +62 -12
- package/build/accordion/edit.js.map +1 -1
- package/build/accordion/index.js +15 -7
- package/build/accordion/index.js.map +1 -1
- package/build/accordion/view.js +15 -15
- package/build/accordion/view.js.map +1 -1
- package/build/accordion-heading/edit.js +68 -0
- package/build/accordion-heading/edit.js.map +1 -0
- package/build/accordion-heading/icon.js.map +1 -0
- package/build/{accordion-header → accordion-heading}/index.js +9 -12
- package/build/accordion-heading/index.js.map +1 -0
- package/build/{accordion-content → accordion-heading}/init.js.map +1 -1
- package/build/{accordion-header → accordion-heading}/save.js +6 -8
- package/build/accordion-heading/save.js.map +1 -0
- package/build/{accordion-content → accordion-item}/edit.js +8 -2
- package/build/accordion-item/edit.js.map +1 -0
- package/build/accordion-item/icon.js.map +1 -0
- package/build/{accordion-content → accordion-item}/index.js +5 -5
- package/build/accordion-item/index.js.map +1 -0
- package/build/accordion-item/init.js.map +1 -0
- package/build/accordion-item/save.js.map +1 -0
- package/build/accordion-panel/index.js +3 -2
- package/build/accordion-panel/index.js.map +1 -1
- package/build/block/index.js +1 -0
- package/build/block/index.js.map +1 -1
- package/build/index.js +24 -4
- package/build/index.js.map +1 -1
- package/build/navigation-link/edit.js +3 -140
- package/build/navigation-link/edit.js.map +1 -1
- package/build/navigation-link/shared/controls.js +171 -0
- package/build/navigation-link/shared/controls.js.map +1 -0
- package/build/navigation-link/shared/index.js +13 -0
- package/build/navigation-link/shared/index.js.map +1 -0
- package/build/navigation-submenu/edit.js +5 -110
- package/build/navigation-submenu/edit.js.map +1 -1
- package/build/pattern/index.js +1 -0
- package/build/pattern/index.js.map +1 -1
- package/build/post-time-to-read/edit.js +16 -61
- package/build/post-time-to-read/edit.js.map +1 -1
- package/build/post-time-to-read/index.js +5 -7
- package/build/post-time-to-read/index.js.map +1 -1
- package/build/post-time-to-read/variations.js +41 -0
- package/build/post-time-to-read/variations.js.map +1 -0
- package/build/query-title/edit.js +1 -1
- package/build/query-title/edit.js.map +1 -1
- package/build/template-part/index.js +1 -0
- package/build/template-part/index.js.map +1 -1
- package/build/utils/get-transformed-metadata.js +7 -0
- package/build/utils/get-transformed-metadata.js.map +1 -1
- package/build-module/accordion/edit.js +66 -16
- package/build-module/accordion/edit.js.map +1 -1
- package/build-module/accordion/index.js +15 -7
- package/build-module/accordion/index.js.map +1 -1
- package/build-module/accordion/view.js +15 -15
- package/build-module/accordion/view.js.map +1 -1
- package/build-module/accordion-heading/edit.js +61 -0
- package/build-module/accordion-heading/edit.js.map +1 -0
- package/build-module/accordion-heading/icon.js.map +1 -0
- package/build-module/{accordion-header → accordion-heading}/index.js +9 -12
- package/build-module/accordion-heading/index.js.map +1 -0
- package/build-module/{accordion-content → accordion-heading}/init.js.map +1 -1
- package/build-module/{accordion-header → accordion-heading}/save.js +6 -8
- package/build-module/accordion-heading/save.js.map +1 -0
- package/build-module/{accordion-content → accordion-item}/edit.js +8 -2
- package/build-module/accordion-item/edit.js.map +1 -0
- package/build-module/accordion-item/icon.js.map +1 -0
- package/build-module/{accordion-content → accordion-item}/index.js +5 -5
- package/build-module/accordion-item/index.js.map +1 -0
- package/build-module/accordion-item/init.js.map +1 -0
- package/build-module/accordion-item/save.js.map +1 -0
- package/build-module/accordion-panel/index.js +3 -2
- package/build-module/accordion-panel/index.js.map +1 -1
- package/build-module/block/index.js +1 -0
- package/build-module/block/index.js.map +1 -1
- package/build-module/index.js +25 -5
- package/build-module/index.js.map +1 -1
- package/build-module/navigation-link/edit.js +4 -141
- package/build-module/navigation-link/edit.js.map +1 -1
- package/build-module/navigation-link/shared/controls.js +165 -0
- package/build-module/navigation-link/shared/controls.js.map +1 -0
- package/build-module/navigation-link/shared/index.js +9 -0
- package/build-module/navigation-link/shared/index.js.map +1 -0
- package/build-module/navigation-submenu/edit.js +6 -111
- package/build-module/navigation-submenu/edit.js.map +1 -1
- package/build-module/pattern/index.js +1 -0
- package/build-module/pattern/index.js.map +1 -1
- package/build-module/post-time-to-read/edit.js +17 -62
- package/build-module/post-time-to-read/edit.js.map +1 -1
- package/build-module/post-time-to-read/index.js +5 -7
- package/build-module/post-time-to-read/index.js.map +1 -1
- package/build-module/post-time-to-read/variations.js +33 -0
- package/build-module/post-time-to-read/variations.js.map +1 -0
- package/build-module/query-title/edit.js +1 -1
- package/build-module/query-title/edit.js.map +1 -1
- package/build-module/template-part/index.js +1 -0
- package/build-module/template-part/index.js.map +1 -1
- package/build-module/utils/get-transformed-metadata.js +7 -0
- package/build-module/utils/get-transformed-metadata.js.map +1 -1
- package/build-style/{accordion-header → accordion-heading}/style-rtl.css +8 -7
- package/build-style/{accordion-header → accordion-heading}/style.css +8 -7
- package/build-style/{accordion-content → accordion-item}/style-rtl.css +5 -5
- package/build-style/{accordion-content → accordion-item}/style.css +5 -5
- package/build-style/style-rtl.css +13 -12
- package/build-style/style.css +13 -12
- package/package.json +35 -35
- package/src/accordion/block.json +11 -3
- package/src/accordion/edit.js +70 -13
- package/src/accordion/index.js +4 -4
- package/src/accordion/index.php +1 -1
- package/src/accordion/view.js +15 -15
- package/src/{accordion-header → accordion-heading}/block.json +10 -12
- package/src/accordion-heading/edit.js +70 -0
- package/src/{accordion-header → accordion-heading}/save.js +6 -8
- package/src/{accordion-header → accordion-heading}/style.scss +11 -7
- package/src/{accordion-content → accordion-item}/block.json +5 -5
- package/src/{accordion-content → accordion-item}/edit.js +12 -2
- package/src/{accordion-content → accordion-item}/index.php +11 -11
- package/src/{accordion-content → accordion-item}/style.scss +3 -3
- package/src/accordion-panel/block.json +3 -2
- package/src/block/block.json +1 -0
- package/src/index.js +23 -4
- package/src/navigation-link/edit.js +3 -142
- package/src/navigation-link/shared/README.md +47 -0
- package/src/navigation-link/shared/controls.js +167 -0
- package/src/navigation-link/shared/index.js +8 -0
- package/src/navigation-link/shared/test/controls.js +210 -0
- package/src/navigation-submenu/edit.js +7 -125
- package/src/pattern/block.json +1 -0
- package/src/post-time-to-read/block.json +3 -7
- package/src/post-time-to-read/edit.js +36 -94
- package/src/post-time-to-read/index.js +2 -0
- package/src/post-time-to-read/index.php +12 -7
- package/src/post-time-to-read/variations.js +39 -0
- package/src/query-title/edit.js +2 -1
- package/src/query-title/index.php +3 -1
- package/src/style.scss +2 -2
- package/src/template-part/block.json +1 -0
- package/src/utils/get-transformed-metadata.js +8 -0
- package/build/accordion-content/edit.js.map +0 -1
- package/build/accordion-content/icon.js.map +0 -1
- package/build/accordion-content/index.js.map +0 -1
- package/build/accordion-content/save.js.map +0 -1
- package/build/accordion-header/edit.js +0 -84
- package/build/accordion-header/edit.js.map +0 -1
- package/build/accordion-header/icon.js.map +0 -1
- package/build/accordion-header/index.js.map +0 -1
- package/build/accordion-header/init.js.map +0 -1
- package/build/accordion-header/save.js.map +0 -1
- package/build-module/accordion-content/edit.js.map +0 -1
- package/build-module/accordion-content/icon.js.map +0 -1
- package/build-module/accordion-content/index.js.map +0 -1
- package/build-module/accordion-content/save.js.map +0 -1
- package/build-module/accordion-header/edit.js +0 -77
- package/build-module/accordion-header/edit.js.map +0 -1
- package/build-module/accordion-header/icon.js.map +0 -1
- package/build-module/accordion-header/index.js.map +0 -1
- package/build-module/accordion-header/init.js.map +0 -1
- package/build-module/accordion-header/save.js.map +0 -1
- package/src/accordion-header/edit.js +0 -87
- /package/build/{accordion-header → accordion-heading}/icon.js +0 -0
- /package/build/{accordion-content → accordion-heading}/init.js +0 -0
- /package/build/{accordion-content → accordion-item}/icon.js +0 -0
- /package/build/{accordion-header → accordion-item}/init.js +0 -0
- /package/build/{accordion-content → accordion-item}/save.js +0 -0
- /package/build-module/{accordion-header → accordion-heading}/icon.js +0 -0
- /package/build-module/{accordion-content → accordion-heading}/init.js +0 -0
- /package/build-module/{accordion-content → accordion-item}/icon.js +0 -0
- /package/build-module/{accordion-header → accordion-item}/init.js +0 -0
- /package/build-module/{accordion-content → accordion-item}/save.js +0 -0
- /package/src/{accordion-header → accordion-heading}/icon.js +0 -0
- /package/src/{accordion-content → accordion-heading}/index.js +0 -0
- /package/src/{accordion-content → accordion-heading}/init.js +0 -0
- /package/src/{accordion-content → accordion-item}/icon.js +0 -0
- /package/src/{accordion-header → accordion-item}/index.js +0 -0
- /package/src/{accordion-header → accordion-item}/init.js +0 -0
- /package/src/{accordion-content → accordion-item}/save.js +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Navigation Blocks Shared Components
|
|
2
|
+
|
|
3
|
+
This directory contains shared components and utilities used by both the Navigation Link and Navigation Submenu blocks to reduce code duplication and ensure consistent behavior.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The Navigation Link and Navigation Submenu blocks share significant functionality, particularly in their inspector controls (ToolsPanel). This shared directory was created to:
|
|
8
|
+
|
|
9
|
+
- **Reduce code duplication** - Eliminate identical code between the two blocks
|
|
10
|
+
- **Ensure consistency** - Both blocks now use the same components, preventing behavioral differences
|
|
11
|
+
- **Reduce maintenance burden** - Changes to shared functionality only need to be made in one place
|
|
12
|
+
- **Minimize bugs** - Less duplicated code means fewer places for bugs to hide
|
|
13
|
+
- **Improve testability** - Shared components can be tested once and reused
|
|
14
|
+
|
|
15
|
+
## Current Shared Components
|
|
16
|
+
|
|
17
|
+
- **`Controls`** - Inspector controls component providing the ToolsPanel interface for both blocks
|
|
18
|
+
|
|
19
|
+
## Future Direction
|
|
20
|
+
|
|
21
|
+
While this shared directory provides immediate benefits for reducing duplication, the long-term vision is to refactor towards a **unified Navigation Item block** that can behave differently based on context (link vs submenu). This would:
|
|
22
|
+
|
|
23
|
+
- Eliminate the need for separate Navigation Link and Navigation Submenu blocks
|
|
24
|
+
- Provide a single, more maintainable codebase
|
|
25
|
+
- Allow for more flexible navigation item types
|
|
26
|
+
- Simplify the user experience
|
|
27
|
+
|
|
28
|
+
However, this refactoring is beyond the current scope and would require significant architectural changes. For now, this shared directory provides a practical solution that:
|
|
29
|
+
|
|
30
|
+
- Maintains backward compatibility
|
|
31
|
+
- Reduces immediate technical debt
|
|
32
|
+
- Prepares the foundation for future unification
|
|
33
|
+
- Supports the integration of new features like Dynamic URL functionality
|
|
34
|
+
|
|
35
|
+
## Testing
|
|
36
|
+
|
|
37
|
+
All shared components include comprehensive tests in the `test/` directory. The tests use pure mocking strategies to ensure isolated, reliable testing of component behavior.
|
|
38
|
+
|
|
39
|
+
## Contributing
|
|
40
|
+
|
|
41
|
+
When adding new shared functionality:
|
|
42
|
+
|
|
43
|
+
1. Place shared components in this directory
|
|
44
|
+
2. Export them from `index.js`
|
|
45
|
+
3. Add comprehensive tests
|
|
46
|
+
4. Update both Navigation Link and Navigation Submenu blocks to use the shared component
|
|
47
|
+
5. Remove any duplicated code from the individual blocks
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
__experimentalToolsPanel as ToolsPanel,
|
|
6
|
+
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
7
|
+
CheckboxControl,
|
|
8
|
+
TextControl,
|
|
9
|
+
TextareaControl,
|
|
10
|
+
} from '@wordpress/components';
|
|
11
|
+
import { __ } from '@wordpress/i18n';
|
|
12
|
+
import { useRef } from '@wordpress/element';
|
|
13
|
+
import { safeDecodeURI } from '@wordpress/url';
|
|
14
|
+
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal dependencies
|
|
18
|
+
*/
|
|
19
|
+
import { useToolsPanelDropdownMenuProps } from '../../utils/hooks';
|
|
20
|
+
import { updateAttributes } from '../update-attributes';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Shared Controls component for Navigation Link and Navigation Submenu blocks.
|
|
24
|
+
*
|
|
25
|
+
* This component provides the inspector controls (ToolsPanel) that are identical
|
|
26
|
+
* between both navigation blocks.
|
|
27
|
+
*
|
|
28
|
+
* @param {Object} props - Component props
|
|
29
|
+
* @param {Object} props.attributes - Block attributes
|
|
30
|
+
* @param {Function} props.setAttributes - Function to update block attributes
|
|
31
|
+
* @param {Function} props.setIsEditingControl - Function to set editing state (optional)
|
|
32
|
+
*/
|
|
33
|
+
export function Controls( {
|
|
34
|
+
attributes,
|
|
35
|
+
setAttributes,
|
|
36
|
+
setIsEditingControl = () => {},
|
|
37
|
+
} ) {
|
|
38
|
+
const { label, url, description, rel, opensInNewTab } = attributes;
|
|
39
|
+
const lastURLRef = useRef( url );
|
|
40
|
+
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<ToolsPanel
|
|
44
|
+
label={ __( 'Settings' ) }
|
|
45
|
+
resetAll={ () => {
|
|
46
|
+
setAttributes( {
|
|
47
|
+
label: '',
|
|
48
|
+
url: '',
|
|
49
|
+
description: '',
|
|
50
|
+
rel: '',
|
|
51
|
+
opensInNewTab: false,
|
|
52
|
+
} );
|
|
53
|
+
} }
|
|
54
|
+
dropdownMenuProps={ dropdownMenuProps }
|
|
55
|
+
>
|
|
56
|
+
<ToolsPanelItem
|
|
57
|
+
hasValue={ () => !! label }
|
|
58
|
+
label={ __( 'Text' ) }
|
|
59
|
+
onDeselect={ () => setAttributes( { label: '' } ) }
|
|
60
|
+
isShownByDefault
|
|
61
|
+
>
|
|
62
|
+
<TextControl
|
|
63
|
+
__nextHasNoMarginBottom
|
|
64
|
+
__next40pxDefaultSize
|
|
65
|
+
label={ __( 'Text' ) }
|
|
66
|
+
value={ label ? stripHTML( label ) : '' }
|
|
67
|
+
onChange={ ( labelValue ) => {
|
|
68
|
+
setAttributes( { label: labelValue } );
|
|
69
|
+
} }
|
|
70
|
+
autoComplete="off"
|
|
71
|
+
onFocus={ () => setIsEditingControl( true ) }
|
|
72
|
+
onBlur={ () => setIsEditingControl( false ) }
|
|
73
|
+
/>
|
|
74
|
+
</ToolsPanelItem>
|
|
75
|
+
|
|
76
|
+
<ToolsPanelItem
|
|
77
|
+
hasValue={ () => !! url }
|
|
78
|
+
label={ __( 'Link' ) }
|
|
79
|
+
onDeselect={ () => setAttributes( { url: '' } ) }
|
|
80
|
+
isShownByDefault
|
|
81
|
+
>
|
|
82
|
+
<TextControl
|
|
83
|
+
__nextHasNoMarginBottom
|
|
84
|
+
__next40pxDefaultSize
|
|
85
|
+
label={ __( 'Link' ) }
|
|
86
|
+
value={ url ? safeDecodeURI( url ) : '' }
|
|
87
|
+
onChange={ ( urlValue ) => {
|
|
88
|
+
setAttributes( {
|
|
89
|
+
url: encodeURI( safeDecodeURI( urlValue ) ),
|
|
90
|
+
} );
|
|
91
|
+
} }
|
|
92
|
+
autoComplete="off"
|
|
93
|
+
type="url"
|
|
94
|
+
onFocus={ () => {
|
|
95
|
+
lastURLRef.current = url;
|
|
96
|
+
setIsEditingControl( true );
|
|
97
|
+
} }
|
|
98
|
+
onBlur={ () => {
|
|
99
|
+
// Defer the updateAttributes call to ensure entity connection isn't severed by accident.
|
|
100
|
+
updateAttributes(
|
|
101
|
+
{ url: ! url ? lastURLRef.current : url },
|
|
102
|
+
setAttributes,
|
|
103
|
+
{ ...attributes, url: lastURLRef.current }
|
|
104
|
+
);
|
|
105
|
+
setIsEditingControl( false );
|
|
106
|
+
} }
|
|
107
|
+
/>
|
|
108
|
+
</ToolsPanelItem>
|
|
109
|
+
|
|
110
|
+
<ToolsPanelItem
|
|
111
|
+
hasValue={ () => !! opensInNewTab }
|
|
112
|
+
label={ __( 'Open in new tab' ) }
|
|
113
|
+
onDeselect={ () => setAttributes( { opensInNewTab: false } ) }
|
|
114
|
+
isShownByDefault
|
|
115
|
+
>
|
|
116
|
+
<CheckboxControl
|
|
117
|
+
__nextHasNoMarginBottom
|
|
118
|
+
label={ __( 'Open in new tab' ) }
|
|
119
|
+
checked={ opensInNewTab }
|
|
120
|
+
onChange={ ( value ) =>
|
|
121
|
+
setAttributes( { opensInNewTab: value } )
|
|
122
|
+
}
|
|
123
|
+
/>
|
|
124
|
+
</ToolsPanelItem>
|
|
125
|
+
|
|
126
|
+
<ToolsPanelItem
|
|
127
|
+
hasValue={ () => !! description }
|
|
128
|
+
label={ __( 'Description' ) }
|
|
129
|
+
onDeselect={ () => setAttributes( { description: '' } ) }
|
|
130
|
+
isShownByDefault
|
|
131
|
+
>
|
|
132
|
+
<TextareaControl
|
|
133
|
+
__nextHasNoMarginBottom
|
|
134
|
+
label={ __( 'Description' ) }
|
|
135
|
+
value={ description || '' }
|
|
136
|
+
onChange={ ( descriptionValue ) => {
|
|
137
|
+
setAttributes( { description: descriptionValue } );
|
|
138
|
+
} }
|
|
139
|
+
help={ __(
|
|
140
|
+
'The description will be displayed in the menu if the current theme supports it.'
|
|
141
|
+
) }
|
|
142
|
+
/>
|
|
143
|
+
</ToolsPanelItem>
|
|
144
|
+
|
|
145
|
+
<ToolsPanelItem
|
|
146
|
+
hasValue={ () => !! rel }
|
|
147
|
+
label={ __( 'Rel attribute' ) }
|
|
148
|
+
onDeselect={ () => setAttributes( { rel: '' } ) }
|
|
149
|
+
isShownByDefault
|
|
150
|
+
>
|
|
151
|
+
<TextControl
|
|
152
|
+
__nextHasNoMarginBottom
|
|
153
|
+
__next40pxDefaultSize
|
|
154
|
+
label={ __( 'Rel attribute' ) }
|
|
155
|
+
value={ rel || '' }
|
|
156
|
+
onChange={ ( relValue ) => {
|
|
157
|
+
setAttributes( { rel: relValue } );
|
|
158
|
+
} }
|
|
159
|
+
autoComplete="off"
|
|
160
|
+
help={ __(
|
|
161
|
+
'The relationship of the linked URL as space-separated link types.'
|
|
162
|
+
) }
|
|
163
|
+
/>
|
|
164
|
+
</ToolsPanelItem>
|
|
165
|
+
</ToolsPanel>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared components for Navigation Link and Navigation Submenu blocks.
|
|
3
|
+
*
|
|
4
|
+
* This module provides common functionality that can be used by both blocks
|
|
5
|
+
* to reduce code duplication and ensure consistent behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { Controls } from './controls';
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* External dependencies
|
|
7
|
+
*/
|
|
8
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal dependencies
|
|
12
|
+
*/
|
|
13
|
+
import { Controls } from '../controls';
|
|
14
|
+
|
|
15
|
+
// Mock the updateAttributes function
|
|
16
|
+
let mockUpdateAttributes;
|
|
17
|
+
jest.mock( '../../update-attributes', () => ( {
|
|
18
|
+
updateAttributes: ( ...args ) => mockUpdateAttributes( ...args ),
|
|
19
|
+
} ) );
|
|
20
|
+
|
|
21
|
+
// Mock the useToolsPanelDropdownMenuProps hook
|
|
22
|
+
jest.mock( '../../../utils/hooks', () => ( {
|
|
23
|
+
useToolsPanelDropdownMenuProps: () => ( {} ),
|
|
24
|
+
} ) );
|
|
25
|
+
|
|
26
|
+
describe( 'Controls', () => {
|
|
27
|
+
// Initialize the mock function
|
|
28
|
+
beforeAll( () => {
|
|
29
|
+
mockUpdateAttributes = jest.fn();
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
const defaultProps = {
|
|
33
|
+
attributes: {
|
|
34
|
+
label: 'Test Link',
|
|
35
|
+
url: 'https://example.com',
|
|
36
|
+
description: 'Test description',
|
|
37
|
+
rel: 'nofollow',
|
|
38
|
+
opensInNewTab: false,
|
|
39
|
+
},
|
|
40
|
+
setAttributes: jest.fn(),
|
|
41
|
+
setIsEditingControl: jest.fn(),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
beforeEach( () => {
|
|
45
|
+
jest.clearAllMocks();
|
|
46
|
+
mockUpdateAttributes.mockClear();
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
it( 'renders all form controls', () => {
|
|
50
|
+
render( <Controls { ...defaultProps } /> );
|
|
51
|
+
|
|
52
|
+
expect( screen.getByLabelText( 'Text' ) ).toBeInTheDocument();
|
|
53
|
+
expect( screen.getByLabelText( 'Link' ) ).toBeInTheDocument();
|
|
54
|
+
expect(
|
|
55
|
+
screen.getByLabelText( 'Open in new tab' )
|
|
56
|
+
).toBeInTheDocument();
|
|
57
|
+
expect( screen.getByLabelText( 'Description' ) ).toBeInTheDocument();
|
|
58
|
+
expect( screen.getByLabelText( 'Rel attribute' ) ).toBeInTheDocument();
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
it( 'strips HTML from label values', () => {
|
|
62
|
+
const propsWithHtml = {
|
|
63
|
+
...defaultProps,
|
|
64
|
+
attributes: {
|
|
65
|
+
...defaultProps.attributes,
|
|
66
|
+
label: '<strong>Bold Text</strong>',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
render( <Controls { ...propsWithHtml } /> );
|
|
70
|
+
|
|
71
|
+
const textInput = screen.getByLabelText( 'Text' );
|
|
72
|
+
expect( textInput.value ).toBe( 'Bold Text' );
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
it( 'decodes URL values for display', () => {
|
|
76
|
+
const propsWithEncodedUrl = {
|
|
77
|
+
...defaultProps,
|
|
78
|
+
attributes: {
|
|
79
|
+
...defaultProps.attributes,
|
|
80
|
+
url: 'https://example.com/test%20page',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
render( <Controls { ...propsWithEncodedUrl } /> );
|
|
84
|
+
|
|
85
|
+
const urlInput = screen.getByLabelText( 'Link' );
|
|
86
|
+
expect( urlInput.value ).toBe( 'https://example.com/test page' );
|
|
87
|
+
} );
|
|
88
|
+
|
|
89
|
+
it( 'encodes URL values when changed', () => {
|
|
90
|
+
render( <Controls { ...defaultProps } /> );
|
|
91
|
+
|
|
92
|
+
const urlInput = screen.getByLabelText( 'Link' );
|
|
93
|
+
|
|
94
|
+
fireEvent.change( urlInput, {
|
|
95
|
+
target: { value: 'https://example.com/test page' },
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
|
|
99
|
+
url: 'https://example.com/test%20page',
|
|
100
|
+
} );
|
|
101
|
+
} );
|
|
102
|
+
|
|
103
|
+
it( 'calls updateAttributes on URL blur', () => {
|
|
104
|
+
render( <Controls { ...defaultProps } /> );
|
|
105
|
+
|
|
106
|
+
const urlInput = screen.getByLabelText( 'Link' );
|
|
107
|
+
|
|
108
|
+
fireEvent.focus( urlInput );
|
|
109
|
+
fireEvent.blur( urlInput );
|
|
110
|
+
|
|
111
|
+
expect( mockUpdateAttributes ).toHaveBeenCalledWith(
|
|
112
|
+
{ url: 'https://example.com' },
|
|
113
|
+
defaultProps.setAttributes,
|
|
114
|
+
{ ...defaultProps.attributes, url: 'https://example.com' }
|
|
115
|
+
);
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
it( 'stores last URL value on focus and uses it in updateAttributes', () => {
|
|
119
|
+
const propsWithDifferentUrl = {
|
|
120
|
+
...defaultProps,
|
|
121
|
+
attributes: {
|
|
122
|
+
...defaultProps.attributes,
|
|
123
|
+
url: 'https://different.com',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
render( <Controls { ...propsWithDifferentUrl } /> );
|
|
127
|
+
|
|
128
|
+
const urlInput = screen.getByLabelText( 'Link' );
|
|
129
|
+
|
|
130
|
+
fireEvent.focus( urlInput );
|
|
131
|
+
|
|
132
|
+
// Change the URL
|
|
133
|
+
fireEvent.change( urlInput, {
|
|
134
|
+
target: { value: 'https://new.com' },
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
// Blur should call updateAttributes with the current URL (since url exists)
|
|
138
|
+
fireEvent.blur( urlInput );
|
|
139
|
+
|
|
140
|
+
expect( mockUpdateAttributes ).toHaveBeenCalledWith(
|
|
141
|
+
{ url: 'https://different.com' }, // Current URL from attributes (not input value)
|
|
142
|
+
defaultProps.setAttributes,
|
|
143
|
+
{
|
|
144
|
+
...propsWithDifferentUrl.attributes,
|
|
145
|
+
url: 'https://different.com',
|
|
146
|
+
} // lastURLRef.current
|
|
147
|
+
);
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
it( 'calls setIsEditingControl on focus and blur for all inputs', () => {
|
|
151
|
+
render( <Controls { ...defaultProps } /> );
|
|
152
|
+
|
|
153
|
+
const textInput = screen.getByLabelText( 'Text' );
|
|
154
|
+
const urlInput = screen.getByLabelText( 'Link' );
|
|
155
|
+
|
|
156
|
+
// Test text input
|
|
157
|
+
fireEvent.focus( textInput );
|
|
158
|
+
expect( defaultProps.setIsEditingControl ).toHaveBeenCalledWith( true );
|
|
159
|
+
|
|
160
|
+
fireEvent.blur( textInput );
|
|
161
|
+
expect( defaultProps.setIsEditingControl ).toHaveBeenCalledWith(
|
|
162
|
+
false
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Test URL input
|
|
166
|
+
fireEvent.focus( urlInput );
|
|
167
|
+
expect( defaultProps.setIsEditingControl ).toHaveBeenCalledWith( true );
|
|
168
|
+
|
|
169
|
+
fireEvent.blur( urlInput );
|
|
170
|
+
expect( defaultProps.setIsEditingControl ).toHaveBeenCalledWith(
|
|
171
|
+
false
|
|
172
|
+
);
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
it( 'handles all form field changes correctly', () => {
|
|
176
|
+
render( <Controls { ...defaultProps } /> );
|
|
177
|
+
|
|
178
|
+
// Test text change
|
|
179
|
+
const textInput = screen.getByLabelText( 'Text' );
|
|
180
|
+
fireEvent.change( textInput, { target: { value: 'New Label' } } );
|
|
181
|
+
expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
|
|
182
|
+
label: 'New Label',
|
|
183
|
+
} );
|
|
184
|
+
|
|
185
|
+
// Test description change
|
|
186
|
+
const descriptionInput = screen.getByLabelText( 'Description' );
|
|
187
|
+
fireEvent.change( descriptionInput, {
|
|
188
|
+
target: { value: 'New Description' },
|
|
189
|
+
} );
|
|
190
|
+
expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
|
|
191
|
+
description: 'New Description',
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
// Test rel change
|
|
195
|
+
const relInput = screen.getByLabelText( 'Rel attribute' );
|
|
196
|
+
fireEvent.change( relInput, {
|
|
197
|
+
target: { value: 'nofollow noopener' },
|
|
198
|
+
} );
|
|
199
|
+
expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
|
|
200
|
+
rel: 'nofollow noopener',
|
|
201
|
+
} );
|
|
202
|
+
|
|
203
|
+
// Test checkbox change
|
|
204
|
+
const checkbox = screen.getByLabelText( 'Open in new tab' );
|
|
205
|
+
fireEvent.click( checkbox );
|
|
206
|
+
expect( defaultProps.setAttributes ).toHaveBeenCalledWith( {
|
|
207
|
+
opensInNewTab: true,
|
|
208
|
+
} );
|
|
209
|
+
} );
|
|
210
|
+
} );
|
|
@@ -7,15 +7,7 @@ import clsx from 'clsx';
|
|
|
7
7
|
* WordPress dependencies
|
|
8
8
|
*/
|
|
9
9
|
import { useSelect, useDispatch } from '@wordpress/data';
|
|
10
|
-
import {
|
|
11
|
-
CheckboxControl,
|
|
12
|
-
TextControl,
|
|
13
|
-
TextareaControl,
|
|
14
|
-
ToolbarButton,
|
|
15
|
-
ToolbarGroup,
|
|
16
|
-
__experimentalToolsPanel as ToolsPanel,
|
|
17
|
-
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
18
|
-
} from '@wordpress/components';
|
|
10
|
+
import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
|
|
19
11
|
import { displayShortcut, isKeyboardEvent } from '@wordpress/keycodes';
|
|
20
12
|
import { __ } from '@wordpress/i18n';
|
|
21
13
|
import {
|
|
@@ -41,11 +33,11 @@ import { useMergeRefs, usePrevious } from '@wordpress/compose';
|
|
|
41
33
|
import { ItemSubmenuIcon } from './icons';
|
|
42
34
|
import { LinkUI } from '../navigation-link/link-ui';
|
|
43
35
|
import { updateAttributes } from '../navigation-link/update-attributes';
|
|
36
|
+
import { Controls } from '../navigation-link/shared';
|
|
44
37
|
import {
|
|
45
38
|
getColors,
|
|
46
39
|
getNavigationChildBlockProps,
|
|
47
40
|
} from '../navigation/edit/utils';
|
|
48
|
-
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
|
|
49
41
|
import { DEFAULT_BLOCK } from '../navigation/constants';
|
|
50
42
|
|
|
51
43
|
const ALLOWED_BLOCKS = [
|
|
@@ -132,7 +124,7 @@ export default function NavigationSubmenuEdit( {
|
|
|
132
124
|
context,
|
|
133
125
|
clientId,
|
|
134
126
|
} ) {
|
|
135
|
-
const { label, url, description
|
|
127
|
+
const { label, url, description } = attributes;
|
|
136
128
|
|
|
137
129
|
const { showSubmenuIcon, maxNestingLevel, openSubmenusOnClick } = context;
|
|
138
130
|
|
|
@@ -151,7 +143,6 @@ export default function NavigationSubmenuEdit( {
|
|
|
151
143
|
const isDraggingWithin = useIsDraggingWithin( listItemRef );
|
|
152
144
|
const itemLabelPlaceholder = __( 'Add text…' );
|
|
153
145
|
const ref = useRef();
|
|
154
|
-
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
|
|
155
146
|
|
|
156
147
|
const {
|
|
157
148
|
parentCount,
|
|
@@ -380,120 +371,11 @@ export default function NavigationSubmenuEdit( {
|
|
|
380
371
|
/>
|
|
381
372
|
</ToolbarGroup>
|
|
382
373
|
</BlockControls>
|
|
383
|
-
{ /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ }
|
|
384
374
|
<InspectorControls>
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
label: '',
|
|
390
|
-
url: '',
|
|
391
|
-
description: '',
|
|
392
|
-
rel: '',
|
|
393
|
-
opensInNewTab: false,
|
|
394
|
-
} );
|
|
395
|
-
} }
|
|
396
|
-
dropdownMenuProps={ dropdownMenuProps }
|
|
397
|
-
>
|
|
398
|
-
<ToolsPanelItem
|
|
399
|
-
label={ __( 'Text' ) }
|
|
400
|
-
isShownByDefault
|
|
401
|
-
hasValue={ () => !! label }
|
|
402
|
-
onDeselect={ () => setAttributes( { label: '' } ) }
|
|
403
|
-
>
|
|
404
|
-
<TextControl
|
|
405
|
-
__nextHasNoMarginBottom
|
|
406
|
-
__next40pxDefaultSize
|
|
407
|
-
value={ label || '' }
|
|
408
|
-
onChange={ ( labelValue ) => {
|
|
409
|
-
setAttributes( { label: labelValue } );
|
|
410
|
-
} }
|
|
411
|
-
label={ __( 'Text' ) }
|
|
412
|
-
autoComplete="off"
|
|
413
|
-
/>
|
|
414
|
-
</ToolsPanelItem>
|
|
415
|
-
|
|
416
|
-
<ToolsPanelItem
|
|
417
|
-
label={ __( 'Link' ) }
|
|
418
|
-
isShownByDefault
|
|
419
|
-
hasValue={ () => !! url }
|
|
420
|
-
onDeselect={ () => setAttributes( { url: '' } ) }
|
|
421
|
-
>
|
|
422
|
-
<TextControl
|
|
423
|
-
__nextHasNoMarginBottom
|
|
424
|
-
__next40pxDefaultSize
|
|
425
|
-
value={ url || '' }
|
|
426
|
-
onChange={ ( urlValue ) => {
|
|
427
|
-
setAttributes( { url: urlValue } );
|
|
428
|
-
} }
|
|
429
|
-
label={ __( 'Link' ) }
|
|
430
|
-
autoComplete="off"
|
|
431
|
-
type="url"
|
|
432
|
-
/>
|
|
433
|
-
</ToolsPanelItem>
|
|
434
|
-
|
|
435
|
-
<ToolsPanelItem
|
|
436
|
-
hasValue={ () => !! opensInNewTab }
|
|
437
|
-
label={ __( 'Open in new tab' ) }
|
|
438
|
-
onDeselect={ () =>
|
|
439
|
-
setAttributes( { opensInNewTab: false } )
|
|
440
|
-
}
|
|
441
|
-
isShownByDefault
|
|
442
|
-
>
|
|
443
|
-
<CheckboxControl
|
|
444
|
-
__nextHasNoMarginBottom
|
|
445
|
-
label={ __( 'Open in new tab' ) }
|
|
446
|
-
checked={ opensInNewTab }
|
|
447
|
-
onChange={ ( value ) =>
|
|
448
|
-
setAttributes( { opensInNewTab: value } )
|
|
449
|
-
}
|
|
450
|
-
/>
|
|
451
|
-
</ToolsPanelItem>
|
|
452
|
-
|
|
453
|
-
<ToolsPanelItem
|
|
454
|
-
label={ __( 'Description' ) }
|
|
455
|
-
isShownByDefault
|
|
456
|
-
hasValue={ () => !! description }
|
|
457
|
-
onDeselect={ () =>
|
|
458
|
-
setAttributes( { description: '' } )
|
|
459
|
-
}
|
|
460
|
-
>
|
|
461
|
-
<TextareaControl
|
|
462
|
-
__nextHasNoMarginBottom
|
|
463
|
-
value={ description || '' }
|
|
464
|
-
onChange={ ( descriptionValue ) => {
|
|
465
|
-
setAttributes( {
|
|
466
|
-
description: descriptionValue,
|
|
467
|
-
} );
|
|
468
|
-
} }
|
|
469
|
-
label={ __( 'Description' ) }
|
|
470
|
-
help={ __(
|
|
471
|
-
'The description will be displayed in the menu if the current theme supports it.'
|
|
472
|
-
) }
|
|
473
|
-
/>
|
|
474
|
-
</ToolsPanelItem>
|
|
475
|
-
|
|
476
|
-
<ToolsPanelItem
|
|
477
|
-
label={ __( 'Rel attribute' ) }
|
|
478
|
-
isShownByDefault
|
|
479
|
-
hasValue={ () => !! rel }
|
|
480
|
-
onDeselect={ () => setAttributes( { rel: '' } ) }
|
|
481
|
-
>
|
|
482
|
-
<TextControl
|
|
483
|
-
__nextHasNoMarginBottom
|
|
484
|
-
__next40pxDefaultSize
|
|
485
|
-
value={ rel || '' }
|
|
486
|
-
onChange={ ( relValue ) => {
|
|
487
|
-
setAttributes( { rel: relValue } );
|
|
488
|
-
} }
|
|
489
|
-
label={ __( 'Rel attribute' ) }
|
|
490
|
-
autoComplete="off"
|
|
491
|
-
help={ __(
|
|
492
|
-
'The relationship of the linked URL as space-separated link types.'
|
|
493
|
-
) }
|
|
494
|
-
/>
|
|
495
|
-
</ToolsPanelItem>
|
|
496
|
-
</ToolsPanel>
|
|
375
|
+
<Controls
|
|
376
|
+
attributes={ attributes }
|
|
377
|
+
setAttributes={ setAttributes }
|
|
378
|
+
/>
|
|
497
379
|
</InspectorControls>
|
|
498
380
|
<div { ...blockProps }>
|
|
499
381
|
{ /* eslint-disable jsx-a11y/anchor-is-valid */ }
|
package/src/pattern/block.json
CHANGED
|
@@ -16,13 +16,9 @@
|
|
|
16
16
|
"type": "boolean",
|
|
17
17
|
"default": true
|
|
18
18
|
},
|
|
19
|
-
"
|
|
20
|
-
"type": "
|
|
21
|
-
"default":
|
|
22
|
-
},
|
|
23
|
-
"showWordCount": {
|
|
24
|
-
"type": "boolean",
|
|
25
|
-
"default": false
|
|
19
|
+
"displayMode": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"default": "time"
|
|
26
22
|
},
|
|
27
23
|
"averageReadingSpeed": {
|
|
28
24
|
"type": "number",
|