@wordpress/commands 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/components/command-menu.js +67 -46
  3. package/build/components/command-menu.js.map +1 -1
  4. package/build/hooks/use-command-context.js +3 -7
  5. package/build/hooks/use-command-context.js.map +1 -1
  6. package/build/hooks/use-command-loader.js +0 -4
  7. package/build/hooks/use-command-loader.js.map +1 -1
  8. package/build/hooks/use-command.js +0 -4
  9. package/build/hooks/use-command.js.map +1 -1
  10. package/build/index.js +0 -6
  11. package/build/index.js.map +1 -1
  12. package/build/lock-unlock.js +1 -2
  13. package/build/lock-unlock.js.map +1 -1
  14. package/build/private-apis.js +1 -4
  15. package/build/private-apis.js.map +1 -1
  16. package/build/store/actions.js +5 -11
  17. package/build/store/actions.js.map +1 -1
  18. package/build/store/index.js +2 -11
  19. package/build/store/index.js.map +1 -1
  20. package/build/store/private-actions.js +0 -1
  21. package/build/store/private-actions.js.map +1 -1
  22. package/build/store/reducer.js +7 -18
  23. package/build/store/reducer.js.map +1 -1
  24. package/build/store/selectors.js +33 -4
  25. package/build/store/selectors.js.map +1 -1
  26. package/build-module/components/command-menu.js +70 -42
  27. package/build-module/components/command-menu.js.map +1 -1
  28. package/build-module/hooks/use-command-context.js +5 -4
  29. package/build-module/hooks/use-command-context.js.map +1 -1
  30. package/build-module/hooks/use-command-loader.js +2 -2
  31. package/build-module/hooks/use-command-loader.js.map +1 -1
  32. package/build-module/hooks/use-command.js +2 -2
  33. package/build-module/hooks/use-command.js.map +1 -1
  34. package/build-module/index.js.map +1 -1
  35. package/build-module/lock-unlock.js.map +1 -1
  36. package/build-module/private-apis.js.map +1 -1
  37. package/build-module/store/actions.js +5 -5
  38. package/build-module/store/actions.js.map +1 -1
  39. package/build-module/store/index.js +2 -2
  40. package/build-module/store/index.js.map +1 -1
  41. package/build-module/store/private-actions.js.map +1 -1
  42. package/build-module/store/reducer.js +8 -17
  43. package/build-module/store/reducer.js.map +1 -1
  44. package/build-module/store/selectors.js +34 -0
  45. package/build-module/store/selectors.js.map +1 -1
  46. package/build-style/style-rtl.css +36 -18
  47. package/build-style/style.css +36 -18
  48. package/package.json +12 -10
  49. package/src/components/command-menu.js +78 -22
  50. package/src/components/style.scss +38 -19
  51. package/src/store/selectors.js +30 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/commands",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Handles the commands menu.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -27,21 +27,23 @@
27
27
  "react-native": "src/index",
28
28
  "dependencies": {
29
29
  "@babel/runtime": "^7.16.0",
30
- "@wordpress/components": "^25.4.0",
31
- "@wordpress/data": "^9.8.0",
32
- "@wordpress/element": "^5.15.0",
33
- "@wordpress/i18n": "^4.38.0",
34
- "@wordpress/icons": "^9.29.0",
35
- "@wordpress/keyboard-shortcuts": "^4.15.0",
36
- "@wordpress/private-apis": "^0.20.0",
30
+ "@wordpress/components": "^25.6.0",
31
+ "@wordpress/data": "^9.10.0",
32
+ "@wordpress/element": "^5.17.0",
33
+ "@wordpress/i18n": "^4.40.0",
34
+ "@wordpress/icons": "^9.31.0",
35
+ "@wordpress/keyboard-shortcuts": "^4.17.0",
36
+ "@wordpress/private-apis": "^0.22.0",
37
+ "classnames": "^2.3.1",
37
38
  "cmdk": "^0.2.0",
38
39
  "rememo": "^4.0.2"
39
40
  },
40
41
  "peerDependencies": {
41
- "react": "^18.0.0"
42
+ "react": "^18.0.0",
43
+ "react-dom": "^18.0.0"
42
44
  },
43
45
  "publishConfig": {
44
46
  "access": "public"
45
47
  },
46
- "gitHead": "6f14d11ed4cb59df110a28ebaa23ecba95eb673a"
48
+ "gitHead": "78a288d55b83a713b2f7d98d5a855c0771a2afc6"
47
49
  }
@@ -1,13 +1,20 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { Command } from 'cmdk';
4
+ import { Command, useCommandState } from 'cmdk';
5
+ import classnames from 'classnames';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
8
9
  */
9
10
  import { useSelect, useDispatch } from '@wordpress/data';
10
- import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
11
+ import {
12
+ useState,
13
+ useEffect,
14
+ useRef,
15
+ useCallback,
16
+ useMemo,
17
+ } from '@wordpress/element';
11
18
  import { __ } from '@wordpress/i18n';
12
19
  import {
13
20
  Modal,
@@ -18,7 +25,7 @@ import {
18
25
  store as keyboardShortcutsStore,
19
26
  useShortcut,
20
27
  } from '@wordpress/keyboard-shortcuts';
21
- import { Icon } from '@wordpress/icons';
28
+ import { Icon, search as inputIcon } from '@wordpress/icons';
22
29
 
23
30
  /**
24
31
  * Internal dependencies
@@ -43,12 +50,18 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) {
43
50
  key={ command.name }
44
51
  value={ command.searchLabel ?? command.label }
45
52
  onSelect={ () => command.callback( { close } ) }
53
+ id={ command.name }
46
54
  >
47
55
  <HStack
48
56
  alignment="left"
49
- className="commands-command-menu__item"
57
+ className={ classnames(
58
+ 'commands-command-menu__item',
59
+ {
60
+ 'has-icon': command.icon,
61
+ }
62
+ ) }
50
63
  >
51
- <Icon icon={ command.icon } />
64
+ { command.icon && <Icon icon={ command.icon } /> }
52
65
  <span>
53
66
  <TextHighlight
54
67
  text={ command.label }
@@ -112,12 +125,15 @@ export function CommandMenuGroup( { isContextual, search, setLoader, close } ) {
112
125
  key={ command.name }
113
126
  value={ command.searchLabel ?? command.label }
114
127
  onSelect={ () => command.callback( { close } ) }
128
+ id={ command.name }
115
129
  >
116
130
  <HStack
117
131
  alignment="left"
118
- className="commands-command-menu__item"
132
+ className={ classnames( 'commands-command-menu__item', {
133
+ 'has-icon': command.icon,
134
+ } ) }
119
135
  >
120
- <Icon icon={ command.icon } />
136
+ { command.icon && <Icon icon={ command.icon } /> }
121
137
  <span>
122
138
  <TextHighlight
123
139
  text={ command.label }
@@ -140,6 +156,33 @@ export function CommandMenuGroup( { isContextual, search, setLoader, close } ) {
140
156
  );
141
157
  }
142
158
 
159
+ function CommandInput( { isOpen, search, setSearch } ) {
160
+ const commandMenuInput = useRef();
161
+ const _value = useCommandState( ( state ) => state.value );
162
+ const selectedItemId = useMemo( () => {
163
+ const item = document.querySelector(
164
+ `[cmdk-item=""][data-value="${ _value }"]`
165
+ );
166
+ return item?.getAttribute( 'id' );
167
+ }, [ _value ] );
168
+ useEffect( () => {
169
+ // Focus the command palette input when mounting the modal.
170
+ if ( isOpen ) {
171
+ commandMenuInput.current.focus();
172
+ }
173
+ }, [ isOpen ] );
174
+ return (
175
+ <Command.Input
176
+ ref={ commandMenuInput }
177
+ value={ search }
178
+ onValueChange={ setSearch }
179
+ placeholder={ __( 'Search for commands' ) }
180
+ aria-activedescendant={ selectedItemId }
181
+ icon={ search }
182
+ />
183
+ );
184
+ }
185
+
143
186
  export function CommandMenu() {
144
187
  const { registerShortcut } = useDispatch( keyboardShortcutsStore );
145
188
  const [ search, setSearch ] = useState( '' );
@@ -149,13 +192,12 @@ export function CommandMenu() {
149
192
  );
150
193
  const { open, close } = useDispatch( commandsStore );
151
194
  const [ loaders, setLoaders ] = useState( {} );
152
- const commandMenuInput = useRef();
153
195
 
154
196
  useEffect( () => {
155
197
  registerShortcut( {
156
198
  name: 'core/commands',
157
199
  category: 'global',
158
- description: __( 'Open the command palette' ),
200
+ description: __( 'Open the command palette.' ),
159
201
  keyCombination: {
160
202
  modifier: 'primary',
161
203
  character: 'k',
@@ -165,7 +207,11 @@ export function CommandMenu() {
165
207
 
166
208
  useShortcut(
167
209
  'core/commands',
210
+ /** @type {import('react').KeyboardEventHandler} */
168
211
  ( event ) => {
212
+ // Bails to avoid obscuring the effect of the preceding handler(s).
213
+ if ( event.defaultPrevented ) return;
214
+
169
215
  event.preventDefault();
170
216
  if ( isOpen ) {
171
217
  close();
@@ -191,16 +237,23 @@ export function CommandMenu() {
191
237
  close();
192
238
  };
193
239
 
194
- useEffect( () => {
195
- // Focus the command palette input when mounting the modal.
196
- if ( isOpen ) {
197
- commandMenuInput.current.focus();
198
- }
199
- }, [ isOpen ] );
200
-
201
240
  if ( ! isOpen ) {
202
241
  return false;
203
242
  }
243
+
244
+ const onKeyDown = ( event ) => {
245
+ if (
246
+ // Ignore keydowns from IMEs
247
+ event.nativeEvent.isComposing ||
248
+ // Workaround for Mac Safari where the final Enter/Backspace of an IME composition
249
+ // is `isComposing=false`, even though it's technically still part of the composition.
250
+ // These can only be detected by keyCode.
251
+ event.keyCode === 229
252
+ ) {
253
+ event.preventDefault();
254
+ }
255
+ };
256
+
204
257
  const isLoading = Object.values( loaders ).some( Boolean );
205
258
 
206
259
  return (
@@ -211,13 +264,16 @@ export function CommandMenu() {
211
264
  __experimentalHideHeader
212
265
  >
213
266
  <div className="commands-command-menu__container">
214
- <Command label={ __( 'Command palette' ) }>
267
+ <Command
268
+ label={ __( 'Command palette' ) }
269
+ onKeyDown={ onKeyDown }
270
+ >
215
271
  <div className="commands-command-menu__header">
216
- <Command.Input
217
- ref={ commandMenuInput }
218
- value={ search }
219
- onValueChange={ setSearch }
220
- placeholder={ __( 'Type a command or search' ) }
272
+ <Icon icon={ inputIcon } />
273
+ <CommandInput
274
+ search={ search }
275
+ setSearch={ setSearch }
276
+ isOpen={ isOpen }
221
277
  />
222
278
  </div>
223
279
  <Command.List>
@@ -1,9 +1,15 @@
1
1
  // dirty hack to clean up modal
2
2
  .commands-command-menu {
3
- width: 100%;
4
- max-width: 480px;
3
+ border-radius: $grid-unit-05;
4
+ width: calc(100% - #{$grid-unit-40});
5
+ margin: auto;
6
+ max-width: 420px;
5
7
  position: relative;
6
- top: 15%;
8
+ top: calc(15% + #{$header-height});
9
+
10
+ @include break-small() {
11
+ top: 15%;
12
+ }
7
13
 
8
14
  .components-modal__content {
9
15
  margin: 0;
@@ -19,6 +25,7 @@
19
25
  .commands-command-menu__header {
20
26
  display: flex;
21
27
  align-items: center;
28
+ padding-left: $grid-unit-20;
22
29
 
23
30
  .components-button {
24
31
  height: $grid-unit-70;
@@ -42,33 +49,32 @@
42
49
  [cmdk-input] {
43
50
  border: none;
44
51
  width: 100%;
45
- padding: $grid-unit-20;
52
+ padding: $grid-unit-20 $grid-unit-20 $grid-unit-20 $grid-unit-10;
46
53
  outline: none;
47
54
  color: $gray-900;
48
55
  margin: 0;
49
- font-size: 20px;
56
+ font-size: 16px;
50
57
  line-height: 28px;
51
- border-bottom: 1px solid $gray-200;
52
58
  border-radius: 0;
53
59
 
54
60
  &::placeholder {
55
- color: $gray-600;
61
+ color: $gray-700;
56
62
  }
57
63
 
58
64
  &:focus {
59
65
  box-shadow: none;
60
66
  outline: none;
61
- border-bottom: 1px solid $gray-200;
62
67
  }
63
68
  }
64
69
 
65
70
  [cmdk-item] {
66
- border-radius: $grid-unit-05;
71
+ border-radius: $radius-block-ui;
67
72
  cursor: pointer;
68
73
  display: flex;
69
74
  align-items: center;
70
- padding: $grid-unit;
71
75
  color: $gray-900;
76
+ font-size: $default-font-size;
77
+ min-height: $button-size-next-default-40px;
72
78
 
73
79
  &[aria-selected="true"],
74
80
  &:active {
@@ -86,16 +92,25 @@
86
92
  }
87
93
 
88
94
  svg {
89
- fill: $gray-600;
95
+ fill: $gray-900;
96
+ }
97
+
98
+ > div {
99
+ padding: $grid-unit;
100
+ padding-left: $grid-unit-50; // Account for commands without icons.
101
+ }
102
+
103
+ > .has-icon {
104
+ padding-left: $grid-unit;
90
105
  }
91
106
  }
92
107
 
93
108
  [cmdk-root] > [cmdk-list] {
94
- max-height: 400px;
109
+ max-height: 368px; // Specific to not have commands overflow oddly.
95
110
  overflow: auto;
96
111
 
97
- & > [cmdk-list-sizer] :has([cmdk-group-items]:not(:empty)) {
98
- padding: $grid-unit;
112
+ & [cmdk-list-sizer] > [cmdk-group] > [cmdk-group-items]:not(:empty) {
113
+ padding: 0 $grid-unit $grid-unit;
99
114
  }
100
115
  }
101
116
 
@@ -103,9 +118,9 @@
103
118
  display: flex;
104
119
  align-items: center;
105
120
  justify-content: center;
106
- height: $grid-unit-80;
107
121
  white-space: pre-wrap;
108
- color: $gray-800;
122
+ color: $gray-900;
123
+ padding: $grid-unit-10 0 $grid-unit-40;
109
124
  }
110
125
 
111
126
  [cmdk-loading] {
@@ -115,10 +130,14 @@
115
130
  [cmdk-list-sizer] {
116
131
  position: relative;
117
132
  }
133
+ }
118
134
 
119
- [cmdk-group]:has([cmdk-group-items]:not(:empty)) + [cmdk-group]:has([cmdk-group-items]:not(:empty)) {
120
- border-top: 1px solid $gray-200;
121
- }
135
+ .commands-command-menu__item span {
136
+ // Ensure commands do not run off the edge (great for post titles).
137
+ display: inline-block;
138
+ overflow: hidden;
139
+ text-overflow: ellipsis;
140
+ white-space: nowrap;
122
141
  }
123
142
 
124
143
  .commands-command-menu__item mark {
@@ -3,6 +3,14 @@
3
3
  */
4
4
  import createSelector from 'rememo';
5
5
 
6
+ /**
7
+ * Returns the registered static commands.
8
+ *
9
+ * @param {Object} state State tree.
10
+ * @param {boolean} contextual Whether to return only contextual commands.
11
+ *
12
+ * @return {import('./actions').WPCommandConfig[]} The list of registered commands.
13
+ */
6
14
  export const getCommands = createSelector(
7
15
  ( state, contextual = false ) =>
8
16
  Object.values( state.commands ).filter( ( command ) => {
@@ -13,6 +21,14 @@ export const getCommands = createSelector(
13
21
  ( state ) => [ state.commands, state.context ]
14
22
  );
15
23
 
24
+ /**
25
+ * Returns the registered command loaders.
26
+ *
27
+ * @param {Object} state State tree.
28
+ * @param {boolean} contextual Whether to return only contextual command loaders.
29
+ *
30
+ * @return {import('./actions').WPCommandLoaderConfig[]} The list of registered command loaders.
31
+ */
16
32
  export const getCommandLoaders = createSelector(
17
33
  ( state, contextual = false ) =>
18
34
  Object.values( state.commandLoaders ).filter( ( loader ) => {
@@ -23,10 +39,24 @@ export const getCommandLoaders = createSelector(
23
39
  ( state ) => [ state.commandLoaders, state.context ]
24
40
  );
25
41
 
42
+ /**
43
+ * Returns whether the command palette is open.
44
+ *
45
+ * @param {Object} state State tree.
46
+ *
47
+ * @return {boolean} Returns whether the command palette is open.
48
+ */
26
49
  export function isOpen( state ) {
27
50
  return state.isOpen;
28
51
  }
29
52
 
53
+ /**
54
+ * Returns whether the active context.
55
+ *
56
+ * @param {Object} state State tree.
57
+ *
58
+ * @return {string} Context.
59
+ */
30
60
  export function getContext( state ) {
31
61
  return state.context;
32
62
  }