@wordpress/commands 0.2.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 +7 -0
- package/LICENSE.md +788 -0
- package/README.md +35 -0
- package/build/components/command-menu.js +213 -0
- package/build/components/command-menu.js.map +1 -0
- package/build/hooks/use-command-loader.js +48 -0
- package/build/hooks/use-command-loader.js.map +1 -0
- package/build/hooks/use-command.js +48 -0
- package/build/hooks/use-command.js.map +1 -0
- package/build/index.js +22 -0
- package/build/index.js.map +1 -0
- package/build/private-apis.js +35 -0
- package/build/private-apis.js.map +1 -0
- package/build/store/actions.js +116 -0
- package/build/store/actions.js.map +1 -0
- package/build/store/index.js +45 -0
- package/build/store/index.js.map +1 -0
- package/build/store/reducer.js +99 -0
- package/build/store/reducer.js.map +1 -0
- package/build/store/selectors.js +33 -0
- package/build/store/selectors.js.map +1 -0
- package/build-module/components/command-menu.js +198 -0
- package/build-module/components/command-menu.js.map +1 -0
- package/build-module/hooks/use-command-loader.js +38 -0
- package/build-module/hooks/use-command-loader.js.map +1 -0
- package/build-module/hooks/use-command.js +38 -0
- package/build-module/hooks/use-command.js.map +1 -0
- package/build-module/index.js +3 -0
- package/build-module/index.js.map +1 -0
- package/build-module/private-apis.js +20 -0
- package/build-module/private-apis.js.map +1 -0
- package/build-module/store/actions.js +103 -0
- package/build-module/store/actions.js.map +1 -0
- package/build-module/store/index.js +27 -0
- package/build-module/store/index.js.map +1 -0
- package/build-module/store/reducer.js +90 -0
- package/build-module/store/reducer.js.map +1 -0
- package/build-module/store/selectors.js +21 -0
- package/build-module/store/selectors.js.map +1 -0
- package/build-style/style-rtl.css +264 -0
- package/build-style/style.css +264 -0
- package/package.json +47 -0
- package/src/components/command-menu.js +216 -0
- package/src/components/style.scss +179 -0
- package/src/hooks/use-command-loader.js +30 -0
- package/src/hooks/use-command.js +41 -0
- package/src/index.js +2 -0
- package/src/private-apis.js +22 -0
- package/src/store/actions.js +91 -0
- package/src/store/index.js +28 -0
- package/src/store/reducer.js +81 -0
- package/src/store/selectors.js +29 -0
- package/src/style.scss +1 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'cmdk';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { useSelect, useDispatch } from '@wordpress/data';
|
|
10
|
+
import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
|
|
11
|
+
import { __ } from '@wordpress/i18n';
|
|
12
|
+
import { Modal, TextHighlight } from '@wordpress/components';
|
|
13
|
+
import {
|
|
14
|
+
store as keyboardShortcutsStore,
|
|
15
|
+
useShortcut,
|
|
16
|
+
} from '@wordpress/keyboard-shortcuts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Internal dependencies
|
|
20
|
+
*/
|
|
21
|
+
import { store as commandsStore } from '../store';
|
|
22
|
+
|
|
23
|
+
function CommandMenuLoader( { name, search, hook, setLoader, close } ) {
|
|
24
|
+
const { isLoading, commands = [] } = hook( { search } ) ?? {};
|
|
25
|
+
useEffect( () => {
|
|
26
|
+
setLoader( name, isLoading );
|
|
27
|
+
}, [ setLoader, name, isLoading ] );
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<Command.List>
|
|
32
|
+
{ isLoading && (
|
|
33
|
+
<Command.Loading>{ __( 'Searching…' ) }</Command.Loading>
|
|
34
|
+
) }
|
|
35
|
+
|
|
36
|
+
{ commands.map( ( command ) => (
|
|
37
|
+
<Command.Item
|
|
38
|
+
key={ command.name }
|
|
39
|
+
value={ command.name }
|
|
40
|
+
onSelect={ () => command.callback( { close } ) }
|
|
41
|
+
>
|
|
42
|
+
<span className="commands-command-menu__item">
|
|
43
|
+
<TextHighlight
|
|
44
|
+
text={ command.label }
|
|
45
|
+
highlight={ search }
|
|
46
|
+
/>
|
|
47
|
+
</span>
|
|
48
|
+
</Command.Item>
|
|
49
|
+
) ) }
|
|
50
|
+
</Command.List>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) {
|
|
56
|
+
// The "hook" prop is actually a custom React hook
|
|
57
|
+
// so to avoid breaking the rules of hooks
|
|
58
|
+
// the CommandMenuLoaderWrapper component need to be
|
|
59
|
+
// remounted on each hook prop change
|
|
60
|
+
// We use the key state to make sure we do that properly.
|
|
61
|
+
const currentLoader = useRef( hook );
|
|
62
|
+
const [ key, setKey ] = useState( 0 );
|
|
63
|
+
useEffect( () => {
|
|
64
|
+
if ( currentLoader.current !== hook ) {
|
|
65
|
+
currentLoader.current = hook;
|
|
66
|
+
setKey( ( prevKey ) => prevKey + 1 );
|
|
67
|
+
}
|
|
68
|
+
}, [ hook ] );
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<CommandMenuLoader
|
|
72
|
+
key={ key }
|
|
73
|
+
hook={ currentLoader.current }
|
|
74
|
+
search={ search }
|
|
75
|
+
setLoader={ setLoader }
|
|
76
|
+
close={ close }
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function CommandMenuGroup( { group, search, setLoader, close } ) {
|
|
82
|
+
const { commands, loaders } = useSelect(
|
|
83
|
+
( select ) => {
|
|
84
|
+
const { getCommands, getCommandLoaders } = select( commandsStore );
|
|
85
|
+
return {
|
|
86
|
+
commands: getCommands( group ),
|
|
87
|
+
loaders: getCommandLoaders( group ),
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
[ group ]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Command.Group heading={ group }>
|
|
95
|
+
{ commands.map( ( command ) => (
|
|
96
|
+
<Command.Item
|
|
97
|
+
key={ command.name }
|
|
98
|
+
value={ command.name }
|
|
99
|
+
onSelect={ () => command.callback( { close } ) }
|
|
100
|
+
>
|
|
101
|
+
<span className="commands-command-menu__item">
|
|
102
|
+
<TextHighlight
|
|
103
|
+
text={ command.label }
|
|
104
|
+
highlight={ search }
|
|
105
|
+
/>
|
|
106
|
+
</span>
|
|
107
|
+
</Command.Item>
|
|
108
|
+
) ) }
|
|
109
|
+
{ loaders.map( ( loader ) => (
|
|
110
|
+
<CommandMenuLoaderWrapper
|
|
111
|
+
key={ loader.name }
|
|
112
|
+
hook={ loader.hook }
|
|
113
|
+
search={ search }
|
|
114
|
+
setLoader={ setLoader }
|
|
115
|
+
close={ close }
|
|
116
|
+
/>
|
|
117
|
+
) ) }
|
|
118
|
+
</Command.Group>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function CommandMenu() {
|
|
123
|
+
const { registerShortcut } = useDispatch( keyboardShortcutsStore );
|
|
124
|
+
const [ search, setSearch ] = useState( '' );
|
|
125
|
+
const [ open, setOpen ] = useState( false );
|
|
126
|
+
const { groups } = useSelect( ( select ) => {
|
|
127
|
+
const { getGroups } = select( commandsStore );
|
|
128
|
+
return {
|
|
129
|
+
groups: getGroups(),
|
|
130
|
+
};
|
|
131
|
+
}, [] );
|
|
132
|
+
const [ loaders, setLoaders ] = useState( {} );
|
|
133
|
+
|
|
134
|
+
useEffect( () => {
|
|
135
|
+
registerShortcut( {
|
|
136
|
+
name: 'core/commands',
|
|
137
|
+
category: 'global',
|
|
138
|
+
description: __( 'Open the global command menu' ),
|
|
139
|
+
keyCombination: {
|
|
140
|
+
modifier: 'primary',
|
|
141
|
+
character: 'k',
|
|
142
|
+
},
|
|
143
|
+
} );
|
|
144
|
+
}, [ registerShortcut ] );
|
|
145
|
+
|
|
146
|
+
useShortcut(
|
|
147
|
+
'core/commands',
|
|
148
|
+
( event ) => {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
setOpen( ( prevOpen ) => ! prevOpen );
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
bindGlobal: true,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const setLoader = useCallback(
|
|
158
|
+
( name, value ) =>
|
|
159
|
+
setLoaders( ( current ) => ( {
|
|
160
|
+
...current,
|
|
161
|
+
[ name ]: value,
|
|
162
|
+
} ) ),
|
|
163
|
+
[]
|
|
164
|
+
);
|
|
165
|
+
const close = () => {
|
|
166
|
+
setSearch( '' );
|
|
167
|
+
setOpen( false );
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if ( ! open ) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const isLoading = Object.values( loaders ).some( Boolean );
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Modal
|
|
177
|
+
className="commands-command-menu"
|
|
178
|
+
overlayClassName="commands-command-menu__overlay"
|
|
179
|
+
onRequestClose={ close }
|
|
180
|
+
__experimentalHideHeader
|
|
181
|
+
>
|
|
182
|
+
<div className="commands-command-menu__container">
|
|
183
|
+
<Command label={ __( 'Global Command Menu' ) }>
|
|
184
|
+
<div className="commands-command-menu__header">
|
|
185
|
+
<Command.Input
|
|
186
|
+
// The input should be focused when the modal is opened.
|
|
187
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
188
|
+
autoFocus
|
|
189
|
+
value={ search }
|
|
190
|
+
onValueChange={ setSearch }
|
|
191
|
+
placeholder={ __(
|
|
192
|
+
'Search for content and templates, or try commands like "Add…"'
|
|
193
|
+
) }
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
<Command.List>
|
|
197
|
+
{ ! isLoading && (
|
|
198
|
+
<Command.Empty>
|
|
199
|
+
{ __( 'No results found.' ) }
|
|
200
|
+
</Command.Empty>
|
|
201
|
+
) }
|
|
202
|
+
{ groups.map( ( group ) => (
|
|
203
|
+
<CommandMenuGroup
|
|
204
|
+
key={ group }
|
|
205
|
+
group={ group }
|
|
206
|
+
search={ search }
|
|
207
|
+
setLoader={ setLoader }
|
|
208
|
+
close={ close }
|
|
209
|
+
/>
|
|
210
|
+
) ) }
|
|
211
|
+
</Command.List>
|
|
212
|
+
</Command>
|
|
213
|
+
</div>
|
|
214
|
+
</Modal>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// dirty hack to clean up modal
|
|
2
|
+
.commands-command-menu {
|
|
3
|
+
padding: 0;
|
|
4
|
+
width: 100%;
|
|
5
|
+
max-width: 680px;
|
|
6
|
+
position: relative;
|
|
7
|
+
top: 15%;
|
|
8
|
+
|
|
9
|
+
.components-modal__content {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.commands-command-menu__overlay {
|
|
16
|
+
display: block;
|
|
17
|
+
align-items: start;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.commands-command-menu__header {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
margin: $grid-unit-15 $grid-unit-15 0 $grid-unit-15;
|
|
24
|
+
|
|
25
|
+
.components-button {
|
|
26
|
+
height: $grid-unit-70;
|
|
27
|
+
width: $grid-unit-70;
|
|
28
|
+
border: 1px solid $gray-600;
|
|
29
|
+
border-right: 0;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
border-radius: $radius-block-ui 0 0 $radius-block-ui;
|
|
32
|
+
|
|
33
|
+
& + [cmdk-input] {
|
|
34
|
+
border-top-left-radius: 0;
|
|
35
|
+
border-bottom-left-radius: 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.commands-command-menu__container {
|
|
41
|
+
[cmdk-linear-badge] {
|
|
42
|
+
height: 24px;
|
|
43
|
+
padding: 0 8px;
|
|
44
|
+
font-size: 12px;
|
|
45
|
+
color: $gray-800;
|
|
46
|
+
background: $gray-300;
|
|
47
|
+
border-radius: 4px;
|
|
48
|
+
width: fit-content;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
margin: $grid-unit-20 $grid-unit-20 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[cmdk-linear-shortcuts] {
|
|
55
|
+
display: flex;
|
|
56
|
+
margin-left: auto;
|
|
57
|
+
gap: $grid-unit-10;
|
|
58
|
+
|
|
59
|
+
kbd {
|
|
60
|
+
color: $gray-800;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[cmdk-input] {
|
|
65
|
+
border: 1px solid $gray-600;
|
|
66
|
+
width: 100%;
|
|
67
|
+
padding: $grid-unit-15 $grid-unit-20;
|
|
68
|
+
min-height: $grid-unit-70;
|
|
69
|
+
outline: none;
|
|
70
|
+
color: $gray-900;
|
|
71
|
+
margin: 0;
|
|
72
|
+
border-radius: $radius-block-ui;
|
|
73
|
+
|
|
74
|
+
&::placeholder {
|
|
75
|
+
color: $gray-600;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&:focus {
|
|
79
|
+
outline: 2px solid transparent;
|
|
80
|
+
border-color: var(--wp-admin-theme-color);
|
|
81
|
+
box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
[cmdk-item] {
|
|
86
|
+
content-visibility: auto;
|
|
87
|
+
border-radius: $radius-block-ui;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
height: $grid-unit-50;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: $grid-unit-15;
|
|
93
|
+
padding: 0 $grid-unit-20;
|
|
94
|
+
color: $gray-900;
|
|
95
|
+
user-select: none;
|
|
96
|
+
will-change: background, color;
|
|
97
|
+
transition: all 150ms ease;
|
|
98
|
+
transition-property: none;
|
|
99
|
+
position: relative;
|
|
100
|
+
|
|
101
|
+
&[aria-selected="true"] {
|
|
102
|
+
background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
|
|
103
|
+
color: var(--wp-admin-theme-color);
|
|
104
|
+
|
|
105
|
+
svg {
|
|
106
|
+
color: var(--wp-admin-theme-color);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&[aria-disabled="true"] {
|
|
111
|
+
color: $gray-600;
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
&:active {
|
|
116
|
+
transition-property: background;
|
|
117
|
+
background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
& + [cmdk-item] {
|
|
121
|
+
margin-top: $grid-unit-05;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
svg,
|
|
125
|
+
.dashicon {
|
|
126
|
+
width: $grid-unit-20;
|
|
127
|
+
height: $grid-unit-20;
|
|
128
|
+
color: $gray-800;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
[cmdk-root] > [cmdk-list] {
|
|
133
|
+
min-height: 300px;
|
|
134
|
+
max-height: 400px;
|
|
135
|
+
overflow: auto;
|
|
136
|
+
overscroll-behavior: contain;
|
|
137
|
+
transition: 100ms ease;
|
|
138
|
+
transition-property: height;
|
|
139
|
+
margin: $grid-unit-15;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
[cmdk-group-heading] {
|
|
143
|
+
user-select: none;
|
|
144
|
+
color: $gray-700;
|
|
145
|
+
padding: $grid-unit-30 $grid-unit-20 $grid-unit-20;
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
font-size: 11px;
|
|
149
|
+
text-transform: uppercase;
|
|
150
|
+
font-weight: 500;
|
|
151
|
+
position: sticky;
|
|
152
|
+
top: 0;
|
|
153
|
+
background: $white;
|
|
154
|
+
z-index: 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[cmdk-empty] {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
height: $grid-unit-80;
|
|
162
|
+
white-space: pre-wrap;
|
|
163
|
+
color: $gray-800;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
[cmdk-loading] {
|
|
167
|
+
padding: $grid-unit-20;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
[cmdk-list-sizer] {
|
|
171
|
+
position: relative;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.commands-command-menu__item mark {
|
|
176
|
+
color: inherit;
|
|
177
|
+
background: unset;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect } from '@wordpress/element';
|
|
5
|
+
import { useDispatch } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { store as commandsStore } from '../store';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Attach a command loader to the Global command menu.
|
|
14
|
+
*
|
|
15
|
+
* @param {import('../store/actions').WPCommandLoaderConfig} loader command loader config.
|
|
16
|
+
*/
|
|
17
|
+
export default function useCommandLoader( { name, group, hook } ) {
|
|
18
|
+
const { registerCommandLoader, unregisterCommandLoader } =
|
|
19
|
+
useDispatch( commandsStore );
|
|
20
|
+
useEffect( () => {
|
|
21
|
+
registerCommandLoader( {
|
|
22
|
+
name,
|
|
23
|
+
group,
|
|
24
|
+
hook,
|
|
25
|
+
} );
|
|
26
|
+
return () => {
|
|
27
|
+
unregisterCommandLoader( name, group );
|
|
28
|
+
};
|
|
29
|
+
}, [ name, group, hook, registerCommandLoader, unregisterCommandLoader ] );
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect, useRef } from '@wordpress/element';
|
|
5
|
+
import { useDispatch } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { store as commandsStore } from '../store';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Attach a command to the Global command menu.
|
|
14
|
+
*
|
|
15
|
+
* @param {import('../store/actions').WPCommandConfig} command command config.
|
|
16
|
+
*/
|
|
17
|
+
export default function useCommand( command ) {
|
|
18
|
+
const { registerCommand, unregisterCommand } = useDispatch( commandsStore );
|
|
19
|
+
const currentCallback = useRef( command.callback );
|
|
20
|
+
useEffect( () => {
|
|
21
|
+
currentCallback.current = command.callback;
|
|
22
|
+
}, [ command.callback ] );
|
|
23
|
+
|
|
24
|
+
useEffect( () => {
|
|
25
|
+
registerCommand( {
|
|
26
|
+
name: command.name,
|
|
27
|
+
group: command.group,
|
|
28
|
+
label: command.label,
|
|
29
|
+
callback: currentCallback.current,
|
|
30
|
+
} );
|
|
31
|
+
return () => {
|
|
32
|
+
unregisterCommand( command.name, command.group );
|
|
33
|
+
};
|
|
34
|
+
}, [
|
|
35
|
+
command.name,
|
|
36
|
+
command.label,
|
|
37
|
+
command.group,
|
|
38
|
+
registerCommand,
|
|
39
|
+
unregisterCommand,
|
|
40
|
+
] );
|
|
41
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { default as useCommand } from './hooks/use-command';
|
|
10
|
+
import { default as useCommandLoader } from './hooks/use-command-loader';
|
|
11
|
+
|
|
12
|
+
export const { lock, unlock } =
|
|
13
|
+
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
|
14
|
+
'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
|
|
15
|
+
'@wordpress/commands'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const privateApis = {};
|
|
19
|
+
lock( privateApis, {
|
|
20
|
+
useCommand,
|
|
21
|
+
useCommandLoader,
|
|
22
|
+
} );
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** @typedef {import('@wordpress/keycodes').WPKeycodeModifier} WPKeycodeModifier */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration of a registered keyboard shortcut.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {Object} WPCommandConfig
|
|
7
|
+
*
|
|
8
|
+
* @property {string} name Command name.
|
|
9
|
+
* @property {string} label Command label.
|
|
10
|
+
* @property {string=} group Command group.
|
|
11
|
+
* @property {Function} callback Command callback.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {(search: string) => WPCommandConfig[]} WPCommandLoaderHook hoo
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Command loader config.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {Object} WPCommandLoaderConfig
|
|
22
|
+
*
|
|
23
|
+
* @property {string} name Command loader name.
|
|
24
|
+
* @property {string=} group Command loader group.
|
|
25
|
+
* @property {WPCommandLoaderHook} hook Command loader hook.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns an action object used to register a new command.
|
|
30
|
+
*
|
|
31
|
+
* @param {WPCommandConfig} config Command config.
|
|
32
|
+
*
|
|
33
|
+
* @return {Object} action.
|
|
34
|
+
*/
|
|
35
|
+
export function registerCommand( { name, label, callback, group = '' } ) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'REGISTER_COMMAND',
|
|
38
|
+
name,
|
|
39
|
+
label,
|
|
40
|
+
callback,
|
|
41
|
+
group,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns an action object used to unregister a command.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} name Command name.
|
|
49
|
+
* @param {string} group Command group.
|
|
50
|
+
*
|
|
51
|
+
* @return {Object} action.
|
|
52
|
+
*/
|
|
53
|
+
export function unregisterCommand( name, group ) {
|
|
54
|
+
return {
|
|
55
|
+
type: 'UNREGISTER_COMMAND',
|
|
56
|
+
name,
|
|
57
|
+
group,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Register command loader.
|
|
63
|
+
*
|
|
64
|
+
* @param {WPCommandLoaderConfig} config Command loader config.
|
|
65
|
+
*
|
|
66
|
+
* @return {Object} action.
|
|
67
|
+
*/
|
|
68
|
+
export function registerCommandLoader( { name, group = '', hook } ) {
|
|
69
|
+
return {
|
|
70
|
+
type: 'REGISTER_COMMAND_LOADER',
|
|
71
|
+
name,
|
|
72
|
+
group,
|
|
73
|
+
hook,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Unregister command loader hook.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} name Command loader name.
|
|
81
|
+
* @param {string} group Command loader group.
|
|
82
|
+
*
|
|
83
|
+
* @return {Object} action.
|
|
84
|
+
*/
|
|
85
|
+
export function unregisterCommandLoader( name, group ) {
|
|
86
|
+
return {
|
|
87
|
+
type: 'UNREGISTER_COMMAND_LOADER',
|
|
88
|
+
name,
|
|
89
|
+
group,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { createReduxStore, register } from '@wordpress/data';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import reducer from './reducer';
|
|
10
|
+
import * as actions from './actions';
|
|
11
|
+
import * as selectors from './selectors';
|
|
12
|
+
|
|
13
|
+
const STORE_NAME = 'core/commands';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Store definition for the commands namespace.
|
|
17
|
+
*
|
|
18
|
+
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore
|
|
19
|
+
*
|
|
20
|
+
* @type {Object}
|
|
21
|
+
*/
|
|
22
|
+
export const store = createReduxStore( STORE_NAME, {
|
|
23
|
+
reducer,
|
|
24
|
+
actions,
|
|
25
|
+
selectors,
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
register( store );
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { combineReducers } from '@wordpress/data';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reducer returning the registered commands
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} state Current state.
|
|
10
|
+
* @param {Object} action Dispatched action.
|
|
11
|
+
*
|
|
12
|
+
* @return {Object} Updated state.
|
|
13
|
+
*/
|
|
14
|
+
function commands( state = {}, action ) {
|
|
15
|
+
switch ( action.type ) {
|
|
16
|
+
case 'REGISTER_COMMAND':
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
[ action.group ]: {
|
|
20
|
+
...state[ action.group ],
|
|
21
|
+
[ action.name ]: {
|
|
22
|
+
name: action.name,
|
|
23
|
+
label: action.label,
|
|
24
|
+
group: action.group,
|
|
25
|
+
callback: action.callback,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
case 'UNREGISTER_COMMAND': {
|
|
30
|
+
const { [ action.name ]: _, ...remainingState } =
|
|
31
|
+
state?.[ action.group ];
|
|
32
|
+
return {
|
|
33
|
+
...state,
|
|
34
|
+
[ action.group ]: remainingState,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reducer returning the command loaders
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} state Current state.
|
|
46
|
+
* @param {Object} action Dispatched action.
|
|
47
|
+
*
|
|
48
|
+
* @return {Object} Updated state.
|
|
49
|
+
*/
|
|
50
|
+
function commandLoaders( state = {}, action ) {
|
|
51
|
+
switch ( action.type ) {
|
|
52
|
+
case 'REGISTER_COMMAND_LOADER':
|
|
53
|
+
return {
|
|
54
|
+
...state,
|
|
55
|
+
[ action.group ]: {
|
|
56
|
+
...state[ action.group ],
|
|
57
|
+
[ action.name ]: {
|
|
58
|
+
name: action.name,
|
|
59
|
+
hook: action.hook,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
case 'UNREGISTER_COMMAND_LOADER': {
|
|
64
|
+
const { [ action.name ]: _, ...remainingState } =
|
|
65
|
+
state?.[ action.group ];
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
[ action.group ]: remainingState,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const reducer = combineReducers( {
|
|
77
|
+
commands,
|
|
78
|
+
commandLoaders,
|
|
79
|
+
} );
|
|
80
|
+
|
|
81
|
+
export default reducer;
|