@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
@@ -92,7 +92,7 @@ describe( 'ColorPalette', () => {
92
92
  const isOpen = true;
93
93
  const onToggle = jest.fn();
94
94
 
95
- const renderedToggleButton = shallow(
95
+ const renderedToggleButton = mount(
96
96
  dropdown.props().renderToggle( { isOpen, onToggle } )
97
97
  );
98
98
 
@@ -56,6 +56,8 @@ const stateReducer = (
56
56
  }
57
57
  };
58
58
  export default function CustomSelectControl( {
59
+ /** Start opting into the larger default height that will become the default size in a future version. */
60
+ __next36pxDefaultSize = false,
59
61
  className,
60
62
  hideLabelFromVision,
61
63
  label,
@@ -141,8 +143,11 @@ export default function CustomSelectControl( {
141
143
  // This is needed because some speech recognition software don't support `aria-labelledby`.
142
144
  'aria-label': label,
143
145
  'aria-labelledby': undefined,
144
- className: 'components-custom-select-control__button',
145
- isSmall: true,
146
+ className: classnames(
147
+ 'components-custom-select-control__button',
148
+ { 'is-next-36px-default-size': __next36pxDefaultSize }
149
+ ),
150
+ isSmall: ! __next36pxDefaultSize,
146
151
  describedBy: getDescribedBy(),
147
152
  } ) }
148
153
  >
@@ -169,6 +174,7 @@ export default function CustomSelectControl( {
169
174
  'is-highlighted':
170
175
  index === highlightedIndex,
171
176
  'has-hint': !! item.__experimentalHint,
177
+ 'is-next-36px-default-size': __next36pxDefaultSize,
172
178
  }
173
179
  ),
174
180
  style: item.style,
@@ -6,81 +6,84 @@ import CustomSelectControl from '../';
6
6
  export default {
7
7
  title: 'Components/CustomSelectControl',
8
8
  component: CustomSelectControl,
9
- };
10
-
11
- const defaultOptions = [
12
- {
13
- key: 'small',
14
- name: 'Small',
15
- style: { fontSize: '50%' },
16
- },
17
- {
18
- key: 'normal',
19
- name: 'Normal',
20
- style: { fontSize: '100%' },
21
- className: 'can-apply-custom-class-to-option',
22
- },
23
- {
24
- key: 'large',
25
- name: 'Large',
26
- style: { fontSize: '200%' },
27
- },
28
- {
29
- key: 'huge',
30
- name: 'Huge',
31
- style: { fontSize: '300%' },
32
- },
33
- ];
34
- export const _default = () => (
35
- <CustomSelectControl label="Font size" options={ defaultOptions } />
36
- );
37
-
38
- const longLabelOptions = [
39
- {
40
- key: 'reallylonglabel1',
41
- name: 'Really long labels are good for stress testing',
42
- },
43
- {
44
- key: 'reallylonglabel2',
45
- name: 'But they can take a long time to type.',
46
- },
47
- {
48
- key: 'reallylonglabel3',
49
- name:
50
- 'That really is ok though because you should stress test your UIs.',
9
+ argTypes: {
10
+ __next36pxDefaultSize: {
11
+ control: { type: 'boolean' },
12
+ },
51
13
  },
52
- ];
14
+ };
53
15
 
54
- export const longLabels = () => (
55
- <CustomSelectControl
56
- label="Testing long labels"
57
- options={ longLabelOptions }
58
- />
59
- );
16
+ export const Default = CustomSelectControl.bind( {} );
17
+ Default.args = {
18
+ label: 'Label',
19
+ options: [
20
+ {
21
+ key: 'small',
22
+ name: 'Small',
23
+ style: { fontSize: '50%' },
24
+ },
25
+ {
26
+ key: 'normal',
27
+ name: 'Normal',
28
+ style: { fontSize: '100%' },
29
+ className: 'can-apply-custom-class-to-option',
30
+ },
31
+ {
32
+ key: 'large',
33
+ name: 'Large',
34
+ style: { fontSize: '200%' },
35
+ },
36
+ {
37
+ key: 'huge',
38
+ name: 'Huge',
39
+ style: { fontSize: '300%' },
40
+ },
41
+ ],
42
+ };
60
43
 
61
- const withHintsOptions = [
62
- {
63
- key: 'thumbnail',
64
- name: 'Thumbnail',
65
- __experimentalHint: '150x150',
66
- },
67
- {
68
- key: 'medium',
69
- name: 'Medium',
70
- __experimentalHint: '250x250',
71
- },
72
- {
73
- key: 'large',
74
- name: 'Large',
75
- __experimentalHint: '1024x1024',
76
- },
77
- {
78
- key: 'full',
79
- name: 'Full Size',
80
- __experimentalHint: '1600x1600',
81
- },
82
- ];
44
+ export const WithLongLabels = CustomSelectControl.bind( {} );
45
+ WithLongLabels.args = {
46
+ ...Default.args,
47
+ options: [
48
+ {
49
+ key: 'reallylonglabel1',
50
+ name: 'Really long labels are good for stress testing',
51
+ },
52
+ {
53
+ key: 'reallylonglabel2',
54
+ name: 'But they can take a long time to type.',
55
+ },
56
+ {
57
+ key: 'reallylonglabel3',
58
+ name:
59
+ 'That really is ok though because you should stress test your UIs.',
60
+ },
61
+ ],
62
+ };
83
63
 
84
- export const hints = () => (
85
- <CustomSelectControl label="Testing hints" options={ withHintsOptions } />
86
- );
64
+ export const WithHints = CustomSelectControl.bind( {} );
65
+ WithHints.args = {
66
+ ...Default.args,
67
+ options: [
68
+ {
69
+ key: 'thumbnail',
70
+ name: 'Thumbnail',
71
+ __experimentalHint: '150x150',
72
+ },
73
+ {
74
+ key: 'medium',
75
+ name: 'Medium',
76
+ __experimentalHint: '250x250',
77
+ },
78
+ {
79
+ key: 'large',
80
+ name: 'Large',
81
+ __experimentalHint: '1024x1024',
82
+ },
83
+ {
84
+ key: 'full',
85
+ name: 'Full Size',
86
+ __experimentalHint: '1600x1600',
87
+ },
88
+ ],
89
+ };
@@ -10,15 +10,26 @@
10
10
  .components-custom-select-control__button {
11
11
  border: 1px solid $gray-700;
12
12
  border-radius: $radius-block-ui;
13
- min-height: 30px;
14
13
  min-width: 130px;
15
14
  position: relative;
16
15
  text-align: left;
17
16
 
17
+ &:not(.is-next-36px-default-size) {
18
+ min-height: 30px;
19
+ }
20
+
18
21
  // For all button sizes allow sufficient space for the
19
22
  // dropdown "arrow" icon to display.
20
23
  &.components-custom-select-control__button {
21
- padding-right: $icon-size;
24
+ // TODO: Some of these can be removed after some internal inconsistencies are addressed, such as the chevron
25
+ // (https://github.com/WordPress/gutenberg/issues/39400) and Button padding (https://github.com/WordPress/gutenberg/issues/39431)
26
+ padding-left: $grid-unit-20;
27
+ padding-right: $icon-size + $grid-unit-10;
28
+
29
+ &:not(.is-next-36px-default-size) {
30
+ padding-left: $grid-unit-10;
31
+ padding-right: $icon-size;
32
+ }
22
33
  }
23
34
 
24
35
  &:focus:not(:disabled) {
@@ -61,10 +72,14 @@
61
72
  display: grid;
62
73
  grid-template-columns: auto auto;
63
74
  list-style-type: none;
64
- padding: $grid-unit-10;
75
+ padding: $grid-unit-10 $grid-unit-20;
65
76
  cursor: default;
66
77
  line-height: $icon-size + $grid-unit-05;
67
78
 
79
+ &:not(.is-next-36px-default-size) {
80
+ padding: $grid-unit-10;
81
+ }
82
+
68
83
  &.has-hint {
69
84
  grid-template-columns: auto auto 30px;
70
85
  }
@@ -8,6 +8,7 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react';
8
8
  */
9
9
  import { Text } from '../../text';
10
10
  import { Divider } from '..';
11
+ import { Flex } from '../../flex';
11
12
 
12
13
  const meta: ComponentMeta< typeof Divider > = {
13
14
  component: Divider,
@@ -30,7 +31,7 @@ const meta: ComponentMeta< typeof Divider > = {
30
31
  };
31
32
  export default meta;
32
33
 
33
- const HorizontalTemplate: ComponentStory< typeof Divider > = ( args ) => (
34
+ const Template: ComponentStory< typeof Divider > = ( args ) => (
34
35
  <div>
35
36
  <Text>Some text before the divider</Text>
36
37
  <Divider { ...args } />
@@ -38,33 +39,35 @@ const HorizontalTemplate: ComponentStory< typeof Divider > = ( args ) => (
38
39
  </div>
39
40
  );
40
41
 
41
- const VerticalTemplate: ComponentStory< typeof Divider > = ( args ) => {
42
- const styles = {
43
- display: 'flex',
44
- alignItems: 'stretch',
45
- justifyContent: 'start',
46
- };
47
-
48
- return (
49
- <div style={ styles }>
50
- <Text>Some text before the divider</Text>
51
- <Divider { ...args } />
52
- <Text>Some text after the divider</Text>
53
- </div>
54
- );
55
- };
56
-
57
- export const Horizontal: ComponentStory<
58
- typeof Divider
59
- > = HorizontalTemplate.bind( {} );
42
+ export const Horizontal: ComponentStory< typeof Divider > = Template.bind( {} );
60
43
  Horizontal.args = {
61
44
  margin: 2,
62
45
  };
63
46
 
64
- export const Vertical: ComponentStory< typeof Divider > = VerticalTemplate.bind(
65
- {}
66
- );
47
+ export const Vertical: ComponentStory< typeof Divider > = Template.bind( {} );
67
48
  Vertical.args = {
68
49
  ...Horizontal.args,
69
50
  orientation: 'vertical',
70
51
  };
52
+
53
+ // Inside a `flex` container, the divider will need to be `stretch` aligned in order to be visible.
54
+ export const InFlexContainer: ComponentStory< typeof Divider > = ( args ) => {
55
+ return (
56
+ <Flex align="stretch">
57
+ <Text>
58
+ Some text before the divider Some text before the divider Some
59
+ text before the divider Some text before the divider Some text
60
+ before the divider Some text before the divider Some text before
61
+ the divider
62
+ </Text>
63
+ <Divider { ...args } />
64
+ <Text>
65
+ Some text after the divider Some text after the divider Some
66
+ text after the divider
67
+ </Text>
68
+ </Flex>
69
+ );
70
+ };
71
+ InFlexContainer.args = {
72
+ ...Vertical.args,
73
+ };
@@ -45,6 +45,14 @@ const renderMargin = ( {
45
45
  } )()
46
46
  );
47
47
 
48
+ const renderDisplay = ( {
49
+ 'aria-orientation': orientation = 'horizontal',
50
+ }: Props ) => {
51
+ return orientation === 'vertical'
52
+ ? css( { display: 'inline' } )
53
+ : undefined;
54
+ };
55
+
48
56
  const renderBorder = ( {
49
57
  'aria-orientation': orientation = 'horizontal',
50
58
  }: Props ) => {
@@ -67,6 +75,7 @@ export const DividerView = styled.hr< Props >`
67
75
  border: 0;
68
76
  margin: 0;
69
77
 
78
+ ${ renderDisplay }
70
79
  ${ renderBorder }
71
80
  ${ renderSize }
72
81
  ${ renderMargin }
@@ -22,8 +22,18 @@ export interface OwnProps {
22
22
  * Adjusts the inline-end margin.
23
23
  */
24
24
  marginEnd?: SpaceInput;
25
+ /**
26
+ * Divider's orientation. When using inside a flex container, you may need to make sure the divider is `stretch` aligned
27
+ * in order for it to be visible.
28
+ *
29
+ * @default 'horizontal'
30
+ */
31
+ orientation?: SeparatorProps[ 'orientation' ];
25
32
  }
26
33
 
27
34
  export interface Props
28
- extends Omit< SeparatorProps, 'children' | 'unstable_system' >,
35
+ extends Omit<
36
+ SeparatorProps,
37
+ 'children' | 'unstable_system' | 'orientation'
38
+ >,
29
39
  OwnProps {}
@@ -42,13 +42,13 @@ export default function FocalPointPickerControls( {
42
42
  <ControlWrapper className="focal-point-picker__controls">
43
43
  <UnitControl
44
44
  label={ __( 'Left' ) }
45
- value={ valueX }
45
+ value={ [ valueX, '%' ].join( '' ) }
46
46
  onChange={ ( next ) => handleChange( next, 'x' ) }
47
47
  dragDirection="e"
48
48
  />
49
49
  <UnitControl
50
50
  label={ __( 'Top' ) }
51
- value={ valueY }
51
+ value={ [ valueY, '%' ].join( '' ) }
52
52
  onChange={ ( next ) => handleChange( next, 'y' ) }
53
53
  dragDirection="s"
54
54
  />
@@ -63,7 +63,6 @@ function UnitControl( props ) {
63
63
  labelPosition="top"
64
64
  max={ TEXTCONTROL_MAX }
65
65
  min={ TEXTCONTROL_MIN }
66
- unit="%"
67
66
  units={ [ { value: '%', label: '%' } ] }
68
67
  { ...props }
69
68
  />
@@ -203,7 +203,6 @@ describe( 'FontSizePicker', () => {
203
203
  );
204
204
  const element = screen.getByLabelText( 'Large' );
205
205
  expect( element ).toBeInTheDocument();
206
- expect( element.children ).toHaveLength( 2 );
207
206
  expect( element.children[ 0 ].textContent ).toBe( '1.7' );
208
207
  } );
209
208
  it( 'should use incremental sequence of numbers as labels if we have complex css', () => {
@@ -223,7 +222,6 @@ describe( 'FontSizePicker', () => {
223
222
  );
224
223
  const element = screen.getByLabelText( 'Large' );
225
224
  expect( element ).toBeInTheDocument();
226
- expect( element.children ).toHaveLength( 2 );
227
225
  expect( element.children[ 0 ].textContent ).toBe( '3' );
228
226
  } );
229
227
  } );
@@ -57,6 +57,24 @@ Callback function passed directly to the `input` file element.
57
57
  - Type: `Function`
58
58
  - Required: Yes
59
59
 
60
+ ### onClick
61
+
62
+ Callback function passed directly to the `input` file element.
63
+
64
+ This can be useful when you want to force a `change` event to fire when the user chooses the same file again. To do this, set the target value to an empty string in the `onClick` function.
65
+
66
+ ```jsx
67
+ <FormFileUpload
68
+ onClick={ ( event ) => ( event.target.value = '' ) }
69
+ onChange={ onChange }
70
+ >
71
+ Upload
72
+ </FormFileUpload>
73
+ ```
74
+
75
+ - Type: `Function`
76
+ - Required: No
77
+
60
78
  ### render
61
79
 
62
80
  Optional callback function used to render the UI. If passed the component does not render any UI and calls this function to render it.
@@ -13,6 +13,7 @@ function FormFileUpload( {
13
13
  children,
14
14
  multiple = false,
15
15
  onChange,
16
+ onClick,
16
17
  render,
17
18
  ...props
18
19
  } ) {
@@ -38,6 +39,8 @@ function FormFileUpload( {
38
39
  style={ { display: 'none' } }
39
40
  accept={ accept }
40
41
  onChange={ onChange }
42
+ onClick={ onClick }
43
+ data-testid="form-file-upload-input"
41
44
  />
42
45
  </div>
43
46
  );
@@ -1,30 +1,92 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { shallow } from 'enzyme';
5
- import { noop } from 'lodash';
4
+ import { render as RTLrender, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
9
9
  */
10
10
  import FormFileUpload from '../';
11
11
 
12
+ /**
13
+ * Browser dependencies
14
+ */
15
+ const { File } = window;
16
+
17
+ function render( jsx ) {
18
+ return {
19
+ user: userEvent.setup( {
20
+ // Avoids timeout errors (https://github.com/testing-library/user-event/issues/565#issuecomment-1064579531).
21
+ delay: null,
22
+ } ),
23
+ ...RTLrender( jsx ),
24
+ };
25
+ }
26
+
27
+ // @testing-library/user-event considers changing <input type="file"> to a string as a change, but it do not occur on real browsers, so the comparisons will be against this result
28
+ const fakePath = expect.objectContaining( {
29
+ target: expect.objectContaining( {
30
+ value: 'C:\\fakepath\\hello.png',
31
+ } ),
32
+ } );
33
+
12
34
  describe( 'FormFileUpload', () => {
13
35
  it( 'should show an Icon Button and a hidden input', () => {
14
- const wrapper = shallow(
36
+ render( <FormFileUpload>My Upload Button</FormFileUpload> );
37
+
38
+ const button = screen.getByText( 'My Upload Button' );
39
+ const input = screen.getByTestId( 'form-file-upload-input' );
40
+ expect( button ).toBeInTheDocument();
41
+ expect( input.style.display ).toBe( 'none' );
42
+ } );
43
+
44
+ it( 'should not fire a change event after selecting the same file', async () => {
45
+ const onChange = jest.fn();
46
+
47
+ const { user } = render(
48
+ <FormFileUpload onChange={ onChange }>
49
+ My Upload Button
50
+ </FormFileUpload>
51
+ );
52
+
53
+ const file = new File( [ 'hello' ], 'hello.png', {
54
+ type: 'image/png',
55
+ } );
56
+
57
+ const input = screen.getByTestId( 'form-file-upload-input' );
58
+
59
+ await user.upload( input, file );
60
+
61
+ await user.upload( input, file );
62
+
63
+ expect( onChange ).toHaveBeenCalledTimes( 1 );
64
+ expect( onChange ).toHaveBeenCalledWith( fakePath );
65
+ } );
66
+
67
+ it( 'should fire a change event after selecting the same file if the value was reset in between', async () => {
68
+ const onChange = jest.fn();
69
+
70
+ const { user } = render(
15
71
  <FormFileUpload
16
- instanceId={ 1 }
17
- blocks={ [] }
18
- recentlyUsedBlocks={ [] }
19
- debouncedSpeak={ noop }
72
+ onClick={ jest.fn( ( e ) => ( e.target.value = '' ) ) }
73
+ onChange={ onChange }
20
74
  >
21
75
  My Upload Button
22
76
  </FormFileUpload>
23
77
  );
24
78
 
25
- const button = wrapper.find( 'ForwardRef(Button)' );
26
- const input = wrapper.find( 'input' );
27
- expect( button.prop( 'children' ) ).toBe( 'My Upload Button' );
28
- expect( input.prop( 'style' ).display ).toBe( 'none' );
79
+ const file = new File( [ 'hello' ], 'hello.png', {
80
+ type: 'image/png',
81
+ } );
82
+
83
+ const input = screen.getByTestId( 'form-file-upload-input' );
84
+ await user.upload( input, file );
85
+
86
+ expect( onChange ).toHaveBeenNthCalledWith( 1, fakePath );
87
+
88
+ await user.upload( input, file );
89
+
90
+ expect( onChange ).toHaveBeenNthCalledWith( 2, fakePath );
29
91
  } );
30
92
  } );
@@ -17,7 +17,6 @@ import type {
17
17
  * WordPress dependencies
18
18
  */
19
19
  import { forwardRef, useRef } from '@wordpress/element';
20
- import { UP, DOWN, ENTER, ESCAPE } from '@wordpress/keycodes';
21
20
  /**
22
21
  * Internal dependencies
23
22
  */
@@ -68,7 +67,6 @@ function InputField(
68
67
  pressEnter,
69
68
  pressUp,
70
69
  reset,
71
- update,
72
70
  } = useInputControlStateReducer( stateReducer, {
73
71
  isDragEnabled,
74
72
  value: valueProp,
@@ -92,10 +90,12 @@ function InputField(
92
90
  return;
93
91
  }
94
92
  if ( ! isFocused && ! wasDirtyOnBlur.current ) {
95
- update( valueProp, _event as SyntheticEvent );
93
+ commit( valueProp, _event as SyntheticEvent );
96
94
  } else if ( ! isDirty ) {
97
95
  onChange( value, {
98
- event: _event as ChangeEvent< HTMLInputElement >,
96
+ event: _event as
97
+ | ChangeEvent< HTMLInputElement >
98
+ | PointerEvent< HTMLInputElement >,
99
99
  } );
100
100
  wasDirtyOnBlur.current = false;
101
101
  }
@@ -109,7 +109,7 @@ function InputField(
109
109
  * If isPressEnterToChange is set, this commits the value to
110
110
  * the onChange callback.
111
111
  */
112
- if ( isPressEnterToChange && isDirty ) {
112
+ if ( isDirty || ! event.target.validity.valid ) {
113
113
  wasDirtyOnBlur.current = true;
114
114
  handleOnCommit( event );
115
115
  }
@@ -137,19 +137,19 @@ function InputField(
137
137
  };
138
138
 
139
139
  const handleOnKeyDown = ( event: KeyboardEvent< HTMLInputElement > ) => {
140
- const { keyCode } = event;
140
+ const { key } = event;
141
141
  onKeyDown( event );
142
142
 
143
- switch ( keyCode ) {
144
- case UP:
143
+ switch ( key ) {
144
+ case 'ArrowUp':
145
145
  pressUp( event );
146
146
  break;
147
147
 
148
- case DOWN:
148
+ case 'ArrowDown':
149
149
  pressDown( event );
150
150
  break;
151
151
 
152
- case ENTER:
152
+ case 'Enter':
153
153
  pressEnter( event );
154
154
 
155
155
  if ( isPressEnterToChange ) {
@@ -158,7 +158,7 @@ function InputField(
158
158
  }
159
159
  break;
160
160
 
161
- case ESCAPE:
161
+ case 'Escape':
162
162
  if ( isPressEnterToChange && isDirty ) {
163
163
  event.preventDefault();
164
164
  reset( valueProp, event );
@@ -169,7 +169,18 @@ function InputField(
169
169
 
170
170
  const dragGestureProps = useDrag< PointerEvent< HTMLInputElement > >(
171
171
  ( dragProps ) => {
172
- const { distance, dragging, event } = dragProps;
172
+ const { distance, dragging, event, target } = dragProps;
173
+
174
+ // The `target` prop always references the `input` element while, by
175
+ // default, the `dragProps.event.target` property would reference the real
176
+ // event target (i.e. any DOM element that the pointer is hovering while
177
+ // dragging). Ensuring that the `target` is always the `input` element
178
+ // allows consumers of `InputControl` (or any higher-level control) to
179
+ // check the input's validity by accessing `event.target.validity.valid`.
180
+ dragProps.event = {
181
+ ...dragProps.event,
182
+ target,
183
+ };
173
184
 
174
185
  if ( ! distance ) return;
175
186
  event.stopPropagation();
@@ -18,7 +18,6 @@ export const PRESS_DOWN = 'PRESS_DOWN';
18
18
  export const PRESS_ENTER = 'PRESS_ENTER';
19
19
  export const PRESS_UP = 'PRESS_UP';
20
20
  export const RESET = 'RESET';
21
- export const UPDATE = 'UPDATE';
22
21
 
23
22
  interface EventPayload {
24
23
  event?: SyntheticEvent;
@@ -42,14 +41,9 @@ export type DragStartAction = Action< typeof DRAG_START, DragProps >;
42
41
  export type DragEndAction = Action< typeof DRAG_END, DragProps >;
43
42
  export type DragAction = Action< typeof DRAG, DragProps >;
44
43
  export type ResetAction = Action< typeof RESET, Partial< ValuePayload > >;
45
- export type UpdateAction = Action< typeof UPDATE, ValuePayload >;
46
44
  export type InvalidateAction = Action< typeof INVALIDATE, { error: unknown } >;
47
45
 
48
- export type ChangeEventAction =
49
- | ChangeAction
50
- | ResetAction
51
- | CommitAction
52
- | UpdateAction;
46
+ export type ChangeEventAction = ChangeAction | ResetAction | CommitAction;
53
47
 
54
48
  export type DragEventAction = DragStartAction | DragEndAction | DragAction;
55
49