@wordpress/components 19.6.1-next.a55ed9455a.0 → 19.7.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 (179) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/build/base-control/index.js +19 -14
  3. package/build/base-control/index.js.map +1 -1
  4. package/build/base-control/styles/base-control-styles.js +33 -12
  5. package/build/base-control/styles/base-control-styles.js.map +1 -1
  6. package/build/box-control/all-input-control.js +3 -7
  7. package/build/box-control/all-input-control.js.map +1 -1
  8. package/build/box-control/axial-input-controls.js +20 -15
  9. package/build/box-control/axial-input-controls.js.map +1 -1
  10. package/build/box-control/input-controls.js +21 -16
  11. package/build/box-control/input-controls.js.map +1 -1
  12. package/build/box-control/utils.js +25 -11
  13. package/build/box-control/utils.js.map +1 -1
  14. package/build/checkbox-control/index.js +21 -1
  15. package/build/checkbox-control/index.js.map +1 -1
  16. package/build/color-palette/index.js +53 -4
  17. package/build/color-palette/index.js.map +1 -1
  18. package/build/custom-select-control/index.js +8 -3
  19. package/build/custom-select-control/index.js.map +1 -1
  20. package/build/divider/styles.js +28 -16
  21. package/build/divider/styles.js.map +1 -1
  22. package/build/focal-point-picker/controls.js +2 -3
  23. package/build/focal-point-picker/controls.js.map +1 -1
  24. package/build/form-file-upload/index.js +4 -1
  25. package/build/form-file-upload/index.js.map +1 -1
  26. package/build/input-control/input-field.js +21 -14
  27. package/build/input-control/input-field.js.map +1 -1
  28. package/build/input-control/reducer/actions.js +1 -3
  29. package/build/input-control/reducer/actions.js.map +1 -1
  30. package/build/input-control/reducer/reducer.js +1 -43
  31. package/build/input-control/reducer/reducer.js.map +1 -1
  32. package/build/number-control/index.js +15 -10
  33. package/build/number-control/index.js.map +1 -1
  34. package/build/resizable-box/resize-tooltip/styles/resize-tooltip.styles.js +4 -4
  35. package/build/resizable-box/resize-tooltip/styles/resize-tooltip.styles.js.map +1 -1
  36. package/build/toggle-group-control/toggle-group-control-option/component.js +1 -4
  37. package/build/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
  38. package/build/toggle-group-control/toggle-group-control-option/styles.js +12 -19
  39. package/build/toggle-group-control/toggle-group-control-option/styles.js.map +1 -1
  40. package/build/tree-grid/index.js +4 -1
  41. package/build/tree-grid/index.js.map +1 -1
  42. package/build/unit-control/index.js +49 -27
  43. package/build/unit-control/index.js.map +1 -1
  44. package/build/unit-control/unit-select-control.js +2 -4
  45. package/build/unit-control/unit-select-control.js.map +1 -1
  46. package/build-module/base-control/index.js +19 -14
  47. package/build-module/base-control/index.js.map +1 -1
  48. package/build-module/base-control/styles/base-control-styles.js +34 -6
  49. package/build-module/base-control/styles/base-control-styles.js.map +1 -1
  50. package/build-module/box-control/all-input-control.js +4 -8
  51. package/build-module/box-control/all-input-control.js.map +1 -1
  52. package/build-module/box-control/axial-input-controls.js +18 -14
  53. package/build-module/box-control/axial-input-controls.js.map +1 -1
  54. package/build-module/box-control/input-controls.js +18 -14
  55. package/build-module/box-control/input-controls.js.map +1 -1
  56. package/build-module/box-control/utils.js +25 -11
  57. package/build-module/box-control/utils.js.map +1 -1
  58. package/build-module/checkbox-control/index.js +24 -3
  59. package/build-module/checkbox-control/index.js.map +1 -1
  60. package/build-module/color-palette/index.js +52 -4
  61. package/build-module/color-palette/index.js.map +1 -1
  62. package/build-module/custom-select-control/index.js +8 -3
  63. package/build-module/custom-select-control/index.js.map +1 -1
  64. package/build-module/divider/styles.js +29 -10
  65. package/build-module/divider/styles.js.map +1 -1
  66. package/build-module/focal-point-picker/controls.js +2 -3
  67. package/build-module/focal-point-picker/controls.js.map +1 -1
  68. package/build-module/form-file-upload/index.js +4 -1
  69. package/build-module/form-file-upload/index.js.map +1 -1
  70. package/build-module/input-control/input-field.js +21 -13
  71. package/build-module/input-control/input-field.js.map +1 -1
  72. package/build-module/input-control/reducer/actions.js +0 -1
  73. package/build-module/input-control/reducer/actions.js.map +1 -1
  74. package/build-module/input-control/reducer/reducer.js +2 -39
  75. package/build-module/input-control/reducer/reducer.js.map +1 -1
  76. package/build-module/number-control/index.js +15 -9
  77. package/build-module/number-control/index.js.map +1 -1
  78. package/build-module/resizable-box/resize-tooltip/styles/resize-tooltip.styles.js +4 -4
  79. package/build-module/resizable-box/resize-tooltip/styles/resize-tooltip.styles.js.map +1 -1
  80. package/build-module/toggle-group-control/toggle-group-control-option/component.js +1 -4
  81. package/build-module/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
  82. package/build-module/toggle-group-control/toggle-group-control-option/styles.js +11 -17
  83. package/build-module/toggle-group-control/toggle-group-control-option/styles.js.map +1 -1
  84. package/build-module/tree-grid/index.js +4 -1
  85. package/build-module/tree-grid/index.js.map +1 -1
  86. package/build-module/unit-control/index.js +47 -25
  87. package/build-module/unit-control/index.js.map +1 -1
  88. package/build-module/unit-control/unit-select-control.js +2 -3
  89. package/build-module/unit-control/unit-select-control.js.map +1 -1
  90. package/build-style/style-rtl.css +29 -181
  91. package/build-style/style.css +29 -181
  92. package/build-types/base-control/index.d.ts +23 -18
  93. package/build-types/base-control/index.d.ts.map +1 -1
  94. package/build-types/base-control/styles/base-control-styles.d.ts +4 -0
  95. package/build-types/base-control/styles/base-control-styles.d.ts.map +1 -1
  96. package/build-types/card/card-divider/hook.d.ts +1 -1
  97. package/build-types/color-palette/index.d.ts.map +1 -1
  98. package/build-types/color-picker/styles.d.ts +1 -1
  99. package/build-types/divider/stories/index.d.ts +1 -0
  100. package/build-types/divider/stories/index.d.ts.map +1 -1
  101. package/build-types/divider/styles.d.ts.map +1 -1
  102. package/build-types/divider/types.d.ts +8 -1
  103. package/build-types/divider/types.d.ts.map +1 -1
  104. package/build-types/input-control/input-field.d.ts.map +1 -1
  105. package/build-types/input-control/reducer/actions.d.ts +1 -3
  106. package/build-types/input-control/reducer/actions.d.ts.map +1 -1
  107. package/build-types/input-control/reducer/reducer.d.ts +3 -9
  108. package/build-types/input-control/reducer/reducer.d.ts.map +1 -1
  109. package/build-types/input-control/types.d.ts +2 -2
  110. package/build-types/input-control/types.d.ts.map +1 -1
  111. package/build-types/number-control/index.d.ts +3 -3
  112. package/build-types/number-control/index.d.ts.map +1 -1
  113. package/build-types/range-control/styles/range-control-styles.d.ts +1 -1
  114. package/build-types/resizable-box/resize-tooltip/styles/resize-tooltip.styles.d.ts.map +1 -1
  115. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts.map +1 -1
  116. package/build-types/toggle-group-control/toggle-group-control-option/styles.d.ts +0 -4
  117. package/build-types/toggle-group-control/toggle-group-control-option/styles.d.ts.map +1 -1
  118. package/build-types/unit-control/index.d.ts +7 -4
  119. package/build-types/unit-control/index.d.ts.map +1 -1
  120. package/build-types/unit-control/stories/index.d.ts +33 -0
  121. package/build-types/unit-control/stories/index.d.ts.map +1 -0
  122. package/build-types/unit-control/styles/unit-control-styles.d.ts +1 -1
  123. package/build-types/unit-control/types.d.ts +23 -6
  124. package/build-types/unit-control/types.d.ts.map +1 -1
  125. package/build-types/unit-control/unit-select-control.d.ts.map +1 -1
  126. package/package.json +17 -17
  127. package/src/base-control/README.md +9 -1
  128. package/src/base-control/index.js +20 -13
  129. package/src/base-control/stories/index.js +2 -2
  130. package/src/base-control/styles/base-control-styles.js +23 -1
  131. package/src/box-control/all-input-control.js +2 -10
  132. package/src/box-control/axial-input-controls.js +32 -21
  133. package/src/box-control/input-controls.js +30 -19
  134. package/src/box-control/utils.js +29 -12
  135. package/src/checkbox-control/index.js +34 -3
  136. package/src/checkbox-control/stories/index.js +44 -0
  137. package/src/checkbox-control/style.scss +4 -2
  138. package/src/color-palette/index.js +73 -8
  139. package/src/color-palette/stories/index.js +62 -26
  140. package/src/color-palette/style.scss +11 -3
  141. package/src/color-palette/test/__snapshots__/index.js.snap +662 -12
  142. package/src/color-palette/test/index.js +1 -1
  143. package/src/custom-select-control/index.js +8 -2
  144. package/src/custom-select-control/stories/index.js +77 -74
  145. package/src/custom-select-control/style.scss +18 -3
  146. package/src/divider/stories/index.tsx +26 -23
  147. package/src/divider/styles.ts +9 -0
  148. package/src/divider/types.ts +11 -1
  149. package/src/focal-point-picker/controls.js +2 -3
  150. package/src/font-size-picker/test/index.js +0 -2
  151. package/src/form-file-upload/README.md +18 -0
  152. package/src/form-file-upload/index.js +3 -0
  153. package/src/form-file-upload/test/index.js +73 -11
  154. package/src/input-control/input-field.tsx +23 -12
  155. package/src/input-control/reducer/actions.ts +1 -7
  156. package/src/input-control/reducer/reducer.ts +0 -29
  157. package/src/input-control/types.ts +2 -1
  158. package/src/mobile/image/style.native.scss +1 -0
  159. package/src/number-control/README.md +14 -0
  160. package/src/number-control/index.js +13 -12
  161. package/src/number-control/stories/index.js +14 -7
  162. package/src/number-control/test/index.js +79 -1
  163. package/src/range-control/stories/index.js +91 -119
  164. package/src/resizable-box/resize-tooltip/styles/resize-tooltip.styles.js +1 -0
  165. package/src/toggle-group-control/test/__snapshots__/index.js.snap +0 -27
  166. package/src/toggle-group-control/toggle-group-control-option/component.tsx +1 -4
  167. package/src/toggle-group-control/toggle-group-control-option/styles.ts +0 -12
  168. package/src/toolbar-group/style.scss +0 -73
  169. package/src/tree-grid/README.md +1 -1
  170. package/src/tree-grid/index.js +4 -0
  171. package/src/tree-grid/test/index.js +61 -17
  172. package/src/unit-control/README.md +1 -3
  173. package/src/unit-control/index.tsx +59 -30
  174. package/src/unit-control/stories/index.tsx +170 -0
  175. package/src/unit-control/test/index.js +143 -100
  176. package/src/unit-control/types.ts +60 -41
  177. package/src/unit-control/unit-select-control.tsx +2 -3
  178. package/tsconfig.tsbuildinfo +1 -1
  179. package/src/unit-control/stories/index.js +0 -127
@@ -61,24 +61,12 @@ export const buttonActive = css`
61
61
  export const ButtonContentView = styled.div`
62
62
  font-size: ${ CONFIG.fontSize };
63
63
  line-height: 1;
64
- position: absolute;
65
- top: 50%;
66
- left: 50%;
67
- transform: translate( -50%, -50% );
68
64
  `;
69
65
 
70
66
  export const separatorActive = css`
71
67
  background: transparent;
72
68
  `;
73
69
 
74
- export const LabelPlaceholderView = styled.div`
75
- font-size: ${ CONFIG.fontSize };
76
- font-weight: bold;
77
- height: 0;
78
- overflow: hidden;
79
- visibility: hidden;
80
- `;
81
-
82
70
  export const medium = css`
83
71
  min-height: ${ CONFIG.controlHeight };
84
72
  `;
@@ -64,76 +64,3 @@ div.components-toolbar {
64
64
  }
65
65
  }
66
66
  }
67
-
68
- // Size multiple sequential buttons to be optically balanced.
69
- // Icons are 36px, as set by a 24px icon and 12px padding.
70
- .block-editor-block-toolbar > .components-toolbar > .block-editor-block-toolbar__slot, // When a plugin adds to a slot, the segment has a `components-toolbar` class.
71
- .block-editor-block-toolbar > .components-toolbar-group > .block-editor-block-toolbar__slot, // When no plugin adds to slots, the segment has a `components-toolbar-group` class.
72
- .block-editor-block-toolbar > .block-editor-block-toolbar__slot > .components-toolbar, // The nesting order is sometimes reversed.
73
- .block-editor-block-toolbar > .block-editor-block-toolbar__slot > .components-dropdown, // Targets unique markup for the "Replace" button.
74
- .block-editor-block-toolbar .block-editor-block-toolbar__slot .components-toolbar-group { // Inline formatting tools use this class.
75
-
76
- // Segments with just a single button.
77
- > .components-button:first-child:last-child,
78
- > .components-dropdown:first-child:last-child .components-button,
79
- &.components-dropdown > .components-button.components-button, // Single segments where the dropdown is also the toolbar group (such as text align).
80
- &.components-dropdown > * .components-button {
81
- min-width: $block-toolbar-height;
82
- padding-left: $grid-unit-15;
83
- padding-right: $grid-unit-15;
84
-
85
- &::before {
86
- left: $grid-unit-10;
87
- right: $grid-unit-10;
88
- }
89
- }
90
-
91
- // First.
92
- // @todo, this unnamed div only shows up when plugins add to slots. We should remove the fragment.
93
- > .components-button:first-child,
94
- > div:first-child > .components-button,
95
- > .components-dropdown:first-child .components-button {
96
- min-width: $block-toolbar-height - $grid-unit-15 * 0.5;
97
- padding-left: $grid-unit-15 - $border-width;
98
- padding-right: $grid-unit-15 * 0.5;
99
-
100
- &::before {
101
- left: $grid-unit-10;
102
- right: 2px;
103
- }
104
- }
105
-
106
- // Middle.
107
- // @todo, this unnamed div only shows up when plugins add to slots. We should remove the fragment.
108
- > .components-button,
109
- > div > .components-button,
110
- > .components-dropdown .components-button {
111
- min-width: $block-toolbar-height - $grid-unit-15;
112
- padding-left: $grid-unit-15 * 0.5; // 6px.
113
- padding-right: $grid-unit-15 * 0.5;
114
-
115
- svg {
116
- min-width: $button-size-small; // This is the optimal icon size, and we size the whole button after this.
117
- }
118
-
119
- &::before {
120
- left: 2px;
121
- right: 2px;
122
- }
123
- }
124
-
125
- // Last.
126
- // @todo, this unnamed div only shows up when plugins add to slots. We should remove the fragment.
127
- > .components-button:last-child,
128
- > div:last-child > .components-button,
129
- > .components-dropdown:last-child .components-button {
130
- min-width: $block-toolbar-height - $grid-unit-15 * 0.5;
131
- padding-left: $grid-unit-15 * 0.5;
132
- padding-right: $grid-unit-15 - $border-width;
133
-
134
- &::before {
135
- left: 2px;
136
- right: $grid-unit-10;
137
- }
138
- }
139
- }
@@ -114,7 +114,7 @@ Aside from the documented callback functions, any props specified will be passed
114
114
 
115
115
  ###### onFocusRow( event: Event, startRow: HTMLElement, destinationRow: HTMLElement )
116
116
 
117
- Callback that fires when focus is shifted from one row to another via the UP and DOWN keys.
117
+ Callback that fires when focus is shifted from one row to another via the Up and Down keys. Callback is also fired on Home and End keys which move focus from the beginning row to the end row.
118
118
  The callback is passed the event, the start row element that the focus was on originally, and
119
119
  the destination row element after the focus has moved.
120
120
 
@@ -275,6 +275,10 @@ function TreeGrid(
275
275
  );
276
276
  focusablesInNextRow[ nextIndex ].focus();
277
277
 
278
+ // Let consumers know the row that was originally focused,
279
+ // and the row that is now in focus.
280
+ onFocusRow( event, activeRow, rows[ nextRowIndex ] );
281
+
278
282
  // Prevent key use for anything else. This ensures Voiceover
279
283
  // doesn't try to handle key navigation.
280
284
  event.preventDefault();
@@ -6,7 +6,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { LEFT, RIGHT, UP, DOWN } from '@wordpress/keycodes';
9
+ import { LEFT, RIGHT, UP, DOWN, HOME, END } from '@wordpress/keycodes';
10
10
  import { forwardRef } from '@wordpress/element';
11
11
 
12
12
  /**
@@ -68,10 +68,10 @@ describe( 'TreeGrid', () => {
68
68
  level={ 1 }
69
69
  positionInSet={ 1 }
70
70
  setSize={ 3 }
71
- isExpanded={ true }
71
+ isExpanded={ false }
72
72
  >
73
73
  <TreeGridCell withoutGridItem>
74
- <TestButton>Row 1</TestButton>
74
+ <TestButton aria-expanded="false">Row 1</TestButton>
75
75
  </TreeGridCell>
76
76
  </TreeGridRow>
77
77
  <TreeGridRow
@@ -88,7 +88,7 @@ describe( 'TreeGrid', () => {
88
88
  level={ 1 }
89
89
  positionInSet={ 3 }
90
90
  setSize={ 3 }
91
- isExpanded={ true }
91
+ isExpanded={ false }
92
92
  >
93
93
  <TreeGridCell withoutGridItem>
94
94
  <TestButton>Row 3</TestButton>
@@ -97,16 +97,16 @@ describe( 'TreeGrid', () => {
97
97
  </TreeGrid>
98
98
  );
99
99
 
100
- screen.getByText( 'Row 2' ).focus();
101
- const row2Element = screen.getByText( 'Row 2' ).closest( 'tr' );
100
+ screen.getByText( 'Row 1' ).focus();
101
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
102
102
 
103
- fireEvent.keyDown( screen.getByText( 'Row 2' ), {
103
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
104
104
  key: 'ArrowRight',
105
105
  keyCode: RIGHT,
106
- currentTarget: row2Element,
106
+ currentTarget: row1Element,
107
107
  } );
108
108
 
109
- expect( onExpandRow ).toHaveBeenCalledWith( row2Element );
109
+ expect( onExpandRow ).toHaveBeenCalledWith( row1Element );
110
110
  } );
111
111
  } );
112
112
 
@@ -120,17 +120,17 @@ describe( 'TreeGrid', () => {
120
120
  level={ 1 }
121
121
  positionInSet={ 1 }
122
122
  setSize={ 3 }
123
- isExpanded={ false }
123
+ isExpanded={ true }
124
124
  >
125
125
  <TreeGridCell withoutGridItem>
126
- <TestButton>Row 1</TestButton>
126
+ <TestButton aria-expanded="true">Row 1</TestButton>
127
127
  </TreeGridCell>
128
128
  </TreeGridRow>
129
129
  <TreeGridRow
130
130
  level={ 1 }
131
131
  positionInSet={ 2 }
132
132
  setSize={ 3 }
133
- isExpanded={ true }
133
+ isExpanded={ false }
134
134
  >
135
135
  <TreeGridCell withoutGridItem>
136
136
  <TestButton>Row 2</TestButton>
@@ -149,16 +149,16 @@ describe( 'TreeGrid', () => {
149
149
  </TreeGrid>
150
150
  );
151
151
 
152
- screen.getByText( 'Row 2' ).focus();
153
- const row2Element = screen.getByText( 'Row 2' ).closest( 'tr' );
152
+ screen.getByText( 'Row 1' ).focus();
153
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
154
154
 
155
- fireEvent.keyDown( screen.getByText( 'Row 2' ), {
155
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
156
156
  key: 'ArrowLeft',
157
157
  keyCode: LEFT,
158
- currentTarget: row2Element,
158
+ currentTarget: row1Element,
159
159
  } );
160
160
 
161
- expect( onCollapseRow ).toHaveBeenCalledWith( row2Element );
161
+ expect( onCollapseRow ).toHaveBeenCalledWith( row1Element );
162
162
  } );
163
163
  } );
164
164
 
@@ -205,6 +205,28 @@ describe( 'TreeGrid', () => {
205
205
  );
206
206
  } );
207
207
 
208
+ it( 'should call onFocusRow with event, start and end nodes when pressing End', () => {
209
+ const onFocusRow = jest.fn();
210
+ render( <TestTree onFocusRow={ onFocusRow } /> );
211
+
212
+ screen.getByText( 'Row 1' ).focus();
213
+
214
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
215
+ const row3Element = screen.getByText( 'Row 3' ).closest( 'tr' );
216
+
217
+ fireEvent.keyDown( screen.getByText( 'Row 1' ), {
218
+ key: 'End',
219
+ keyCode: END,
220
+ currentTarget: row1Element,
221
+ } );
222
+
223
+ expect( onFocusRow ).toHaveBeenCalledWith(
224
+ expect.objectContaining( { key: 'End', keyCode: END } ),
225
+ row1Element,
226
+ row3Element
227
+ );
228
+ } );
229
+
208
230
  it( 'should call onFocusRow with event, start and end nodes when pressing Up Arrow', () => {
209
231
  const onFocusRow = jest.fn();
210
232
  render( <TestTree onFocusRow={ onFocusRow } /> );
@@ -227,6 +249,28 @@ describe( 'TreeGrid', () => {
227
249
  );
228
250
  } );
229
251
 
252
+ it( 'should call onFocusRow with event, start and end nodes when pressing Home', () => {
253
+ const onFocusRow = jest.fn();
254
+ render( <TestTree onFocusRow={ onFocusRow } /> );
255
+
256
+ screen.getByText( 'Row 3' ).focus();
257
+
258
+ const row3Element = screen.getByText( 'Row 3' ).closest( 'tr' );
259
+ const row1Element = screen.getByText( 'Row 1' ).closest( 'tr' );
260
+
261
+ fireEvent.keyDown( screen.getByText( 'Row 3' ), {
262
+ key: 'Home',
263
+ keyCode: HOME,
264
+ currentTarget: row3Element,
265
+ } );
266
+
267
+ expect( onFocusRow ).toHaveBeenCalledWith(
268
+ expect.objectContaining( { key: 'Home', keyCode: HOME } ),
269
+ row3Element,
270
+ row1Element
271
+ );
272
+ } );
273
+
230
274
  it( 'should call onFocusRow when shift key is held', () => {
231
275
  const onFocusRow = jest.fn();
232
276
  render( <TestTree onFocusRow={ onFocusRow } /> );
@@ -4,7 +4,7 @@
4
4
  This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5
5
  </div>
6
6
 
7
- UnitControl allows the user to set a value as well as a unit (e.g. `px`).
7
+ `UnitControl` allows the user to set a numeric quantity as well as a unit (e.g. `px`).
8
8
 
9
9
  ## Usage
10
10
 
@@ -66,14 +66,12 @@ The position of the label (`top`, `side`, `bottom`, or `edge`).
66
66
  Callback when the `value` changes.
67
67
 
68
68
  - Required: No
69
- - Default: `noop`
70
69
 
71
70
  ### `onUnitChange`: `UnitControlOnChangeCallback`
72
71
 
73
72
  Callback when the `unit` changes.
74
73
 
75
74
  - Required: No
76
- - Default: `noop`
77
75
 
78
76
  ### `size`: `string`
79
77
 
@@ -7,23 +7,23 @@ import type {
7
7
  ForwardedRef,
8
8
  SyntheticEvent,
9
9
  ChangeEvent,
10
+ PointerEvent,
10
11
  } from 'react';
11
- import { noop, omit } from 'lodash';
12
+ import { omit } from 'lodash';
12
13
  import classnames from 'classnames';
13
14
 
14
15
  /**
15
16
  * WordPress dependencies
16
17
  */
18
+ import deprecated from '@wordpress/deprecated';
17
19
  import { forwardRef, useMemo, useRef, useEffect } from '@wordpress/element';
18
20
  import { __ } from '@wordpress/i18n';
19
- import { ENTER } from '@wordpress/keycodes';
20
21
 
21
22
  /**
22
23
  * Internal dependencies
23
24
  */
24
25
  import type { WordPressComponentProps } from '../ui/context';
25
26
  import * as inputControlActionTypes from '../input-control/reducer/actions';
26
- import { composeStateReducers } from '../input-control/reducer/reducer';
27
27
  import { Root, ValueInput } from './styles/unit-control-styles';
28
28
  import UnitSelectControl from './unit-select-control';
29
29
  import {
@@ -36,9 +36,16 @@ import { useControlledState } from '../utils/hooks';
36
36
  import type { UnitControlProps, UnitControlOnChangeCallback } from './types';
37
37
  import type { StateReducer } from '../input-control/reducer/state';
38
38
 
39
- function UnitControl(
40
- {
41
- __unstableStateReducer: stateReducer = ( state ) => state,
39
+ function UnforwardedUnitControl(
40
+ unitControlProps: WordPressComponentProps<
41
+ UnitControlProps,
42
+ 'input',
43
+ false
44
+ >,
45
+ forwardedRef: ForwardedRef< any >
46
+ ) {
47
+ const {
48
+ __unstableStateReducer: stateReducerProp,
42
49
  autoComplete = 'off',
43
50
  className,
44
51
  disabled = false,
@@ -47,17 +54,24 @@ function UnitControl(
47
54
  isResetValueOnUnitChange = false,
48
55
  isUnitSelectTabbable = true,
49
56
  label,
50
- onChange = noop,
51
- onUnitChange = noop,
57
+ onChange: onChangeProp,
58
+ onUnitChange,
52
59
  size = 'default',
53
60
  style,
54
61
  unit: unitProp,
55
62
  units: unitsProp = CSS_UNITS,
56
63
  value: valueProp,
57
64
  ...props
58
- }: WordPressComponentProps< UnitControlProps, 'input', false >,
59
- forwardedRef: ForwardedRef< any >
60
- ) {
65
+ } = unitControlProps;
66
+
67
+ if ( 'unit' in unitControlProps ) {
68
+ deprecated( 'UnitControl unit prop', {
69
+ since: '5.6',
70
+ hint: 'The unit should be provided within the `value` prop.',
71
+ version: '6.2',
72
+ } );
73
+ }
74
+
61
75
  // The `value` prop, in theory, should not be `null`, but the following line
62
76
  // ensures it fallback to `undefined` in case a consumer of `UnitControl`
63
77
  // still passes `null` as a `value`.
@@ -81,7 +95,9 @@ function UnitControl(
81
95
  );
82
96
 
83
97
  useEffect( () => {
84
- setUnit( parsedUnit );
98
+ if ( parsedUnit !== undefined ) {
99
+ setUnit( parsedUnit );
100
+ }
85
101
  }, [ parsedUnit ] );
86
102
 
87
103
  // Stores parsed value for hand-off in state reducer.
@@ -91,14 +107,18 @@ function UnitControl(
91
107
 
92
108
  const handleOnQuantityChange = (
93
109
  nextQuantityValue: number | string | undefined,
94
- changeProps: { event: ChangeEvent< HTMLInputElement > }
110
+ changeProps: {
111
+ event:
112
+ | ChangeEvent< HTMLInputElement >
113
+ | PointerEvent< HTMLInputElement >;
114
+ }
95
115
  ) => {
96
116
  if (
97
117
  nextQuantityValue === '' ||
98
118
  typeof nextQuantityValue === 'undefined' ||
99
119
  nextQuantityValue === null
100
120
  ) {
101
- onChange( '', changeProps );
121
+ onChangeProp?.( '', changeProps );
102
122
  return;
103
123
  }
104
124
 
@@ -113,7 +133,7 @@ function UnitControl(
113
133
  unit
114
134
  ).join( '' );
115
135
 
116
- onChange( onChangeValue, changeProps );
136
+ onChangeProp?.( onChangeValue, changeProps );
117
137
  };
118
138
 
119
139
  const handleOnUnitChange: UnitControlOnChangeCallback = (
@@ -128,8 +148,8 @@ function UnitControl(
128
148
  nextValue = `${ data.default }${ nextUnitValue }`;
129
149
  }
130
150
 
131
- onChange( nextValue, changeProps );
132
- onUnitChange( nextUnitValue, changeProps );
151
+ onChangeProp?.( nextValue, changeProps );
152
+ onUnitChange?.( nextUnitValue, changeProps );
133
153
 
134
154
  setUnit( nextUnitValue );
135
155
  };
@@ -157,11 +177,11 @@ function UnitControl(
157
177
  : undefined;
158
178
  const changeProps = { event, data };
159
179
 
160
- onChange(
180
+ onChangeProp?.(
161
181
  `${ validParsedQuantity ?? '' }${ validParsedUnit }`,
162
182
  changeProps
163
183
  );
164
- onUnitChange( validParsedUnit, changeProps );
184
+ onUnitChange?.( validParsedUnit, changeProps );
165
185
 
166
186
  setUnit( validParsedUnit );
167
187
  }
@@ -170,8 +190,8 @@ function UnitControl(
170
190
  const handleOnBlur: FocusEventHandler< HTMLInputElement > = mayUpdateUnit;
171
191
 
172
192
  const handleOnKeyDown = ( event: KeyboardEvent< HTMLInputElement > ) => {
173
- const { keyCode } = event;
174
- if ( keyCode === ENTER ) {
193
+ const { key } = event;
194
+ if ( key === 'Enter' ) {
175
195
  mayUpdateUnit( event );
176
196
  }
177
197
  };
@@ -186,6 +206,8 @@ function UnitControl(
186
206
  * @return The updated state to apply to InputControl
187
207
  */
188
208
  const unitControlStateReducer: StateReducer = ( state, action ) => {
209
+ const nextState = { ...state };
210
+
189
211
  /*
190
212
  * On commits (when pressing ENTER and on blur if
191
213
  * isPressEnterToChange is true), if a parse has been performed
@@ -193,14 +215,24 @@ function UnitControl(
193
215
  */
194
216
  if ( action.type === inputControlActionTypes.COMMIT ) {
195
217
  if ( refParsedQuantity.current !== undefined ) {
196
- state.value = ( refParsedQuantity.current ?? '' ).toString();
218
+ nextState.value = (
219
+ refParsedQuantity.current ?? ''
220
+ ).toString();
197
221
  refParsedQuantity.current = undefined;
198
222
  }
199
223
  }
200
224
 
201
- return state;
225
+ return nextState;
202
226
  };
203
227
 
228
+ let stateReducer: StateReducer = unitControlStateReducer;
229
+ if ( stateReducerProp ) {
230
+ stateReducer = ( state, action ) => {
231
+ const baseState = unitControlStateReducer( state, action );
232
+ return stateReducerProp( baseState, action );
233
+ };
234
+ }
235
+
204
236
  const inputSuffix = ! disableUnits ? (
205
237
  <UnitSelectControl
206
238
  aria-label={ __( 'Select unit' ) }
@@ -244,17 +276,14 @@ function UnitControl(
244
276
  suffix={ inputSuffix }
245
277
  value={ parsedQuantity ?? '' }
246
278
  step={ step }
247
- __unstableStateReducer={ composeStateReducers(
248
- unitControlStateReducer,
249
- stateReducer
250
- ) }
279
+ __unstableStateReducer={ stateReducer }
251
280
  />
252
281
  </Root>
253
282
  );
254
283
  }
255
284
 
256
285
  /**
257
- * `UnitControl` allows the user to set a value as well as a unit (e.g. `px`).
286
+ * `UnitControl` allows the user to set a numeric quantity as well as a unit (e.g. `px`).
258
287
  *
259
288
  *
260
289
  * @example
@@ -269,7 +298,7 @@ function UnitControl(
269
298
  * };
270
299
  * ```
271
300
  */
272
- const ForwardedUnitControl = forwardRef( UnitControl );
301
+ export const UnitControl = forwardRef( UnforwardedUnitControl );
273
302
 
274
303
  export { parseQuantityAndUnitFromRawValue, useCustomUnits } from './utils';
275
- export default ForwardedUnitControl;
304
+ export default UnitControl;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ComponentMeta, ComponentStory } from '@storybook/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useState } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { UnitControl } from '../';
15
+ import { CSS_UNITS } from '../utils';
16
+
17
+ const meta: ComponentMeta< typeof UnitControl > = {
18
+ component: UnitControl,
19
+ title: 'Components (Experimental)/UnitControl',
20
+ argTypes: {
21
+ __unstableInputWidth: {
22
+ control: { type: 'text' },
23
+ },
24
+ __unstableStateReducer: {
25
+ control: { type: null },
26
+ },
27
+ size: {
28
+ control: { type: 'select' },
29
+ },
30
+ onChange: {
31
+ action: 'onChange',
32
+ control: { type: null },
33
+ },
34
+ onUnitChange: {
35
+ control: { type: null },
36
+ },
37
+ value: {
38
+ control: { type: null },
39
+ },
40
+ },
41
+ parameters: {
42
+ controls: {
43
+ expanded: true,
44
+ },
45
+ docs: { source: { state: 'open' } },
46
+ },
47
+ };
48
+ export default meta;
49
+
50
+ const DefaultTemplate: ComponentStory< typeof UnitControl > = ( {
51
+ onChange,
52
+ ...args
53
+ } ) => {
54
+ const [ value, setValue ] = useState< string | undefined >( '10px' );
55
+
56
+ return (
57
+ <div style={ { maxWidth: '100px' } }>
58
+ <UnitControl
59
+ { ...args }
60
+ value={ value }
61
+ onChange={ ( v, extra ) => {
62
+ setValue( v );
63
+ onChange?.( v, extra );
64
+ } }
65
+ />
66
+ </div>
67
+ );
68
+ };
69
+
70
+ export const Default: ComponentStory<
71
+ typeof UnitControl
72
+ > = DefaultTemplate.bind( {} );
73
+ Default.args = {
74
+ label: 'Label',
75
+ };
76
+
77
+ /**
78
+ * If the `isPressEnterToChange` prop is set to `true`, the `onChange` callback
79
+ * will not fire while a new value is typed in the input field (you can verify this
80
+ * behavior by inspecting the console's output).
81
+ */
82
+ export const PressEnterToChange: ComponentStory<
83
+ typeof UnitControl
84
+ > = DefaultTemplate.bind( {} );
85
+ PressEnterToChange.args = {
86
+ ...Default.args,
87
+ isPressEnterToChange: true,
88
+ onChange: ( v ) => {
89
+ // eslint-disable-next-line no-console
90
+ console.log( v );
91
+ },
92
+ };
93
+
94
+ /**
95
+ * Most of `NumberControl`'s props can be passed to `UnitControl`, and they will
96
+ * affect its numeric input field.
97
+ */
98
+ export const TweakingTheNumberInput: ComponentStory<
99
+ typeof UnitControl
100
+ > = DefaultTemplate.bind( {} );
101
+ TweakingTheNumberInput.args = {
102
+ ...Default.args,
103
+ min: 0,
104
+ max: 100,
105
+ step: 'any',
106
+ label: 'Custom label',
107
+ };
108
+
109
+ /**
110
+ * When only one unit is available, the unit selection dropdown becomes static text.
111
+ */
112
+ export const WithSingleUnit: ComponentStory<
113
+ typeof UnitControl
114
+ > = DefaultTemplate.bind( {} );
115
+ WithSingleUnit.args = {
116
+ ...Default.args,
117
+ units: CSS_UNITS.slice( 0, 1 ),
118
+ };
119
+
120
+ /**
121
+ * It is possible to pass a custom list of units. Every time the unit changes,
122
+ * if the `isResetValueOnUnitChange` is set to `true`, the input's quantity is
123
+ * reset to the new unit's default value.
124
+ */
125
+ export const WithCustomUnits: ComponentStory< typeof UnitControl > = ( {
126
+ onChange,
127
+ ...args
128
+ } ) => {
129
+ const [ value, setValue ] = useState< string | undefined >( '80km' );
130
+
131
+ return (
132
+ <div style={ { maxWidth: '100px' } }>
133
+ <UnitControl
134
+ { ...args }
135
+ value={ value }
136
+ onChange={ ( v, extra ) => {
137
+ setValue( v );
138
+ onChange?.( v, extra );
139
+ } }
140
+ />
141
+ </div>
142
+ );
143
+ };
144
+ WithCustomUnits.args = {
145
+ ...Default.args,
146
+ isResetValueOnUnitChange: true,
147
+ min: 0,
148
+ units: [
149
+ {
150
+ value: 'km',
151
+ label: 'km',
152
+ default: 1,
153
+ },
154
+ {
155
+ value: 'mi',
156
+ label: 'mi',
157
+ default: 1,
158
+ },
159
+ {
160
+ value: 'm',
161
+ label: 'm',
162
+ default: 1000,
163
+ },
164
+ {
165
+ value: 'yd',
166
+ label: 'yd',
167
+ default: 1760,
168
+ },
169
+ ],
170
+ };