@fluentui/react-spinbutton 9.0.0-beta.9 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. package/CHANGELOG.json +493 -1
  2. package/CHANGELOG.md +148 -2
  3. package/README.md +4 -6
  4. package/dist/index.d.ts +49 -60
  5. package/{lib → dist}/tsdoc-metadata.json +0 -0
  6. package/lib/components/SpinButton/SpinButton.js.map +1 -1
  7. package/lib/components/SpinButton/SpinButton.types.js.map +1 -1
  8. package/lib/components/SpinButton/renderSpinButton.js.map +1 -1
  9. package/lib/components/SpinButton/useSpinButton.js +137 -100
  10. package/lib/components/SpinButton/useSpinButton.js.map +1 -1
  11. package/lib/components/SpinButton/useSpinButtonStyles.js +22 -16
  12. package/lib/components/SpinButton/useSpinButtonStyles.js.map +1 -1
  13. package/lib/index.js.map +1 -1
  14. package/lib/utils/clamp.js +6 -12
  15. package/lib/utils/clamp.js.map +1 -1
  16. package/lib/utils/getBound.js.map +1 -1
  17. package/lib/utils/precision.js.map +1 -1
  18. package/lib-commonjs/components/SpinButton/SpinButton.js.map +1 -1
  19. package/lib-commonjs/components/SpinButton/renderSpinButton.js.map +1 -1
  20. package/lib-commonjs/components/SpinButton/useSpinButton.js +136 -100
  21. package/lib-commonjs/components/SpinButton/useSpinButton.js.map +1 -1
  22. package/lib-commonjs/components/SpinButton/useSpinButtonStyles.js +22 -16
  23. package/lib-commonjs/components/SpinButton/useSpinButtonStyles.js.map +1 -1
  24. package/lib-commonjs/index.js.map +1 -1
  25. package/lib-commonjs/utils/clamp.js +10 -16
  26. package/lib-commonjs/utils/clamp.js.map +1 -1
  27. package/lib-commonjs/utils/getBound.js.map +1 -1
  28. package/lib-commonjs/utils/precision.js.map +1 -1
  29. package/package.json +11 -12
  30. package/lib/SpinButton.d.ts +0 -1
  31. package/lib/components/SpinButton/SpinButton.d.ts +0 -6
  32. package/lib/components/SpinButton/SpinButton.strings.d.ts +0 -2
  33. package/lib/components/SpinButton/SpinButton.strings.js +0 -5
  34. package/lib/components/SpinButton/SpinButton.strings.js.map +0 -1
  35. package/lib/components/SpinButton/SpinButton.types.d.ts +0 -141
  36. package/lib/components/SpinButton/index.d.ts +0 -5
  37. package/lib/components/SpinButton/renderSpinButton.d.ts +0 -5
  38. package/lib/components/SpinButton/useSpinButton.d.ts +0 -12
  39. package/lib/components/SpinButton/useSpinButtonStyles.d.ts +0 -7
  40. package/lib/index.d.ts +0 -2
  41. package/lib/utils/clamp.d.ts +0 -1
  42. package/lib/utils/getBound.d.ts +0 -2
  43. package/lib/utils/index.d.ts +0 -3
  44. package/lib/utils/precision.d.ts +0 -14
  45. package/lib-commonjs/SpinButton.d.ts +0 -1
  46. package/lib-commonjs/components/SpinButton/SpinButton.d.ts +0 -6
  47. package/lib-commonjs/components/SpinButton/SpinButton.strings.d.ts +0 -2
  48. package/lib-commonjs/components/SpinButton/SpinButton.strings.js +0 -11
  49. package/lib-commonjs/components/SpinButton/SpinButton.strings.js.map +0 -1
  50. package/lib-commonjs/components/SpinButton/SpinButton.types.d.ts +0 -141
  51. package/lib-commonjs/components/SpinButton/index.d.ts +0 -5
  52. package/lib-commonjs/components/SpinButton/renderSpinButton.d.ts +0 -5
  53. package/lib-commonjs/components/SpinButton/useSpinButton.d.ts +0 -12
  54. package/lib-commonjs/components/SpinButton/useSpinButtonStyles.d.ts +0 -7
  55. package/lib-commonjs/index.d.ts +0 -2
  56. package/lib-commonjs/utils/clamp.d.ts +0 -1
  57. package/lib-commonjs/utils/getBound.d.ts +0 -2
  58. package/lib-commonjs/utils/index.d.ts +0 -3
  59. package/lib-commonjs/utils/precision.d.ts +0 -14
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @fluentui/react-spinbutton
2
2
 
3
- **SpinButton component for [Fluent UI React](https://aka.ms/fluentui-storybook)**
3
+ **SpinButton component for [Fluent UI React](https://react.fluentui.dev/)**
4
4
 
5
- These are not production-ready components and **should never be used in product**. This space is useful for testing new components whose APIs might change before final release.
6
-
7
- SpinButtons are used to allow numeric input bounded between minimum and maximum values with button controls to increment and decrement the input value by some step amount. Values can also be manipulated via the keyboard.
5
+ SpinButtons are used to allow numeric input bounded between minimum and maximum values with buttons to increment and decrement the input value. Values can also be manipulated via the keyboard.
8
6
 
9
7
  ### Usage
10
8
 
@@ -21,7 +19,7 @@ import { SpinButton } from '@fluentui/react-spinbutton';
21
19
  <SpinButton value={value} onChange={onSpinButtonChange}/>
22
20
  ```
23
21
 
24
- See [Fluent UI Storybook](https://aka.ms/fluentui-storybook) for more detailed usage examples.
22
+ See [Fluent UI Storybook](https://react.fluentui.dev/) for more detailed usage examples.
25
23
 
26
24
  Alternatively, run Storybook locally with:
27
25
 
@@ -34,4 +32,4 @@ See [Spec.md](./Spec.md).
34
32
 
35
33
  ### Migration Guide
36
34
 
37
- If you're upgrading to Fluent UI v9 see [MIGRATION.md](./MIGRATION.md) for guidance on updating to the latest SpinButton implementation.
35
+ When upgrading to Fluent UI v9 see the [upgrade guide](https://react.fluentui.dev/?path=/docs/concepts-upgrading-from-v8-components-spinbutton-upgrade--page) for guidance on updating to the latest SpinButton implementation.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /// <reference types="react" />
2
+
1
3
  import type { ComponentProps } from '@fluentui/react-utilities';
2
4
  import type { ComponentState } from '@fluentui/react-utilities';
3
5
  import type { ForwardRefComponent } from '@fluentui/react-utilities';
@@ -21,22 +23,29 @@ export declare type SpinButtonChangeEvent = React_2.MouseEvent<HTMLButtonElement
21
23
 
22
24
  export declare const spinButtonClassNames: SlotClassNames<SpinButtonSlots>;
23
25
 
24
- export declare type SpinButtonCommons = {
26
+ export declare type SpinButtonOnChangeData = {
27
+ value?: number | null;
28
+ displayValue?: string;
29
+ };
30
+
31
+ /**
32
+ * SpinButton Props
33
+ */
34
+ export declare type SpinButtonProps = Omit<ComponentProps<Partial<SpinButtonSlots>, 'input'>, 'defaultValue' | 'onChange' | 'size' | 'value'> & {
35
+ /**
36
+ * Controls the colors and borders of the input.
37
+ * @default 'outline'
38
+ */
39
+ appearance?: 'outline' | 'underline' | 'filled-darker' | 'filled-lighter';
25
40
  /**
26
41
  * Initial value of the control (assumed to be valid). Updates to this prop will not be respected.
27
42
  *
28
43
  * Use this if you intend for the SpinButton to be an uncontrolled component which maintains its
29
44
  * own value. For a controlled component, use `value` instead. (Mutually exclusive with `value`.)
30
- */
31
- defaultValue: number;
32
- /**
33
- * Current value of the control (assumed to be valid).
34
45
  *
35
- * Only provide this if the SpinButton is a controlled component where you are maintaining its
36
- * current state and passing updates based on change events; otherwise, use the `defaultValue`
37
- * property. (Mutually exclusive with `defaultValue`.)
46
+ * Use `null` to indicate the control has no value.
38
47
  */
39
- value: number;
48
+ defaultValue?: number | null;
40
49
  /**
41
50
  * String representation of `value`.
42
51
  *
@@ -47,28 +56,15 @@ export declare type SpinButtonCommons = {
47
56
  * current state and passing updates based on change events. When SpinButton is used as an
48
57
  * uncontrolled component this prop is ignored.
49
58
  */
50
- displayValue: string;
51
- /**
52
- * Min value of the control. If not provided, the control has no minimum value.
53
- */
54
- min: number;
59
+ displayValue?: string;
55
60
  /**
56
61
  * Max value of the control. If not provided, the control has no maximum value.
57
62
  */
58
- max: number;
59
- /**
60
- * Difference between two adjacent values of the control.
61
- * This value is used to calculate the precision of the input if no `precision` is given.
62
- * The precision calculated this way will always be greater than or equal 0.
63
- * @default 1
64
- */
65
- step: number;
63
+ max?: number;
66
64
  /**
67
- * Large difference between two values. This should be greater than `step` and is used
68
- * when users hit the Page Up or Page Down keys.
69
- * @default 1
65
+ * Min value of the control. If not provided, the control has no minimum value.
70
66
  */
71
- stepPage: number;
67
+ min?: number;
72
68
  /**
73
69
  * Callback for when the committed value changes.
74
70
  * - User presses the up/down buttons (on single press or every spin)
@@ -76,40 +72,46 @@ export declare type SpinButtonCommons = {
76
72
  * - User *commits* edits to the input text by focusing away (blurring) or pressing enter.
77
73
  * Note that this is NOT called for every key press while the user is editing.
78
74
  */
79
- onChange: (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => void;
75
+ onChange?: (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => void;
80
76
  /**
81
77
  * How many decimal places the value should be rounded to.
82
78
  *
83
79
  * The default is calculated based on the precision of `step`: i.e. if step = 1, precision = 0.
84
80
  * step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2.
85
81
  */
86
- precision: number;
87
- /**
88
- * Controls the colors and borders of the input.
89
- * @default 'outline'
90
- */
91
- appearance: 'outline' | 'underline' | 'filledDarker' | 'filledLighter';
82
+ precision?: number;
92
83
  /**
93
84
  * Size of the input.
94
85
  * @default 'medium'
95
86
  */
96
- size: 'small' | 'medium';
87
+ size?: 'small' | 'medium';
97
88
  /**
98
- * Strings for localizing text in the control.
89
+ * Difference between two adjacent values of the control.
90
+ * This value is used to calculate the precision of the input if no `precision` is given.
91
+ * The precision calculated this way will always be greater than or equal 0.
92
+ * @default 1
99
93
  */
100
- strings?: SpinButtonStrings;
101
- };
102
-
103
- export declare type SpinButtonOnChangeData = {
104
- value?: number;
105
- displayValue?: string;
94
+ step?: number;
95
+ /**
96
+ * Large difference between two values. This should be greater than `step` and is used
97
+ * when users hit the Page Up or Page Down keys.
98
+ * @default 1
99
+ */
100
+ stepPage?: number;
101
+ /**
102
+ * Current value of the control (assumed to be valid).
103
+ *
104
+ * Only provide this if the SpinButton is a controlled component where you are maintaining its
105
+ * current state and passing updates based on change events; otherwise, use the `defaultValue`
106
+ * property.
107
+ *
108
+ * Use `null` to indicate the control has no value.
109
+ *
110
+ * Mutually exclusive with `defaultValue`.
111
+ */
112
+ value?: number | null;
106
113
  };
107
114
 
108
- /**
109
- * SpinButton Props
110
- */
111
- export declare type SpinButtonProps = Omit<ComponentProps<Partial<SpinButtonSlots>, 'input'>, 'onChange' | 'size'> & Partial<SpinButtonCommons>;
112
-
113
115
  export declare type SpinButtonSlots = {
114
116
  /**
115
117
  * The root element of SpinButton is a container `<div>`.
@@ -139,7 +141,7 @@ export declare type SpinButtonSpinState = 'rest' | 'up' | 'down';
139
141
  /**
140
142
  * State used in rendering SpinButton
141
143
  */
142
- export declare type SpinButtonState = ComponentState<SpinButtonSlots> & Partial<SpinButtonCommons> & Pick<SpinButtonCommons, 'appearance' | 'size'> & {
144
+ export declare type SpinButtonState = ComponentState<SpinButtonSlots> & Required<Pick<SpinButtonProps, 'appearance' | 'size'>> & {
143
145
  /**
144
146
  * State used to track which direction, if any, SpinButton is currently spinning.
145
147
  * @default 'rest'
@@ -152,19 +154,6 @@ export declare type SpinButtonState = ComponentState<SpinButtonSlots> & Partial<
152
154
  atBound: SpinButtonBounds;
153
155
  };
154
156
 
155
- export declare type SpinButtonStrings = {
156
- /**
157
- * Label applied to the increment button.
158
- * Can include the token "\{step\}" which will be replaced with the value of the `step` prop.
159
- */
160
- incrementButtonLabel: string;
161
- /**
162
- * Label applied to the decrement button.
163
- * Can include the token "\{step\}" which will be replaced with the value of the `step` prop.
164
- */
165
- decrementButtonLabel: string;
166
- };
167
-
168
157
  /**
169
158
  * Create the state required to render SpinButton.
170
159
  *
File without changes
@@ -1 +1 @@
1
- {"version":3,"sources":["components/SpinButton/SpinButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAZ,MAAuB,OAAvB;AACA,SAAS,sBAAT,QAAuC,iBAAvC;AACA,SAAS,yBAAT,QAA0C,oBAA1C;AACA,SAAS,4BAAT,QAA6C,uBAA7C;AAIA;;AAEG;;AACH,OAAO,MAAM,UAAU,gBAAyC,KAAK,CAAC,UAAN,CAAiB,CAAC,KAAD,EAAQ,GAAR,KAAe;AAC9F,QAAM,KAAK,GAAG,sBAAsB,CAAC,KAAD,EAAQ,GAAR,CAApC;AAEA,EAAA,4BAA4B,CAAC,KAAD,CAA5B;AACA,SAAO,yBAAyB,CAAC,KAAD,CAAhC;AACD,CAL+D,CAAzD;AAOP,UAAU,CAAC,WAAX,GAAyB,YAAzB","sourcesContent":["import * as React from 'react';\nimport { useSpinButton_unstable } from './useSpinButton';\nimport { renderSpinButton_unstable } from './renderSpinButton';\nimport { useSpinButtonStyles_unstable } from './useSpinButtonStyles';\nimport type { SpinButtonProps } from './SpinButton.types';\nimport type { ForwardRefComponent } from '@fluentui/react-utilities';\n\n/**\n * A SpinButton allows someone to incrementally adjust a value in small steps.\n */\nexport const SpinButton: ForwardRefComponent<SpinButtonProps> = React.forwardRef((props, ref) => {\n const state = useSpinButton_unstable(props, ref);\n\n useSpinButtonStyles_unstable(state);\n return renderSpinButton_unstable(state);\n});\n\nSpinButton.displayName = 'SpinButton';\n"],"sourceRoot":"../src/"}
1
+ {"version":3,"sources":["components/SpinButton/SpinButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAZ,MAAuB,OAAvB;AACA,SAAS,sBAAT,QAAuC,iBAAvC;AACA,SAAS,yBAAT,QAA0C,oBAA1C;AACA,SAAS,4BAAT,QAA6C,uBAA7C;AAIA;;AAEG;;AACH,OAAO,MAAM,UAAU,gBAAyC,KAAK,CAAC,UAAN,CAAiB,CAAC,KAAD,EAAQ,GAAR,KAAe;EAC9F,MAAM,KAAK,GAAG,sBAAsB,CAAC,KAAD,EAAQ,GAAR,CAApC;EAEA,4BAA4B,CAAC,KAAD,CAA5B;EACA,OAAO,yBAAyB,CAAC,KAAD,CAAhC;AACD,CAL+D,CAAzD;AAOP,UAAU,CAAC,WAAX,GAAyB,YAAzB","sourcesContent":["import * as React from 'react';\nimport { useSpinButton_unstable } from './useSpinButton';\nimport { renderSpinButton_unstable } from './renderSpinButton';\nimport { useSpinButtonStyles_unstable } from './useSpinButtonStyles';\nimport type { SpinButtonProps } from './SpinButton.types';\nimport type { ForwardRefComponent } from '@fluentui/react-utilities';\n\n/**\n * A SpinButton allows someone to incrementally adjust a value in small steps.\n */\nexport const SpinButton: ForwardRefComponent<SpinButtonProps> = React.forwardRef((props, ref) => {\n const state = useSpinButton_unstable(props, ref);\n\n useSpinButtonStyles_unstable(state);\n return renderSpinButton_unstable(state);\n});\n\nSpinButton.displayName = 'SpinButton';\n"],"sourceRoot":"../src/"}
@@ -1 +1 @@
1
- {"version":3,"file":"SpinButton.types.js","sourceRoot":"../src/","sources":["components/SpinButton/SpinButton.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';\n// import { Input } from '@fluentui/react-input';\nimport * as React from 'react';\n\nexport type SpinButtonSlots = {\n /**\n * The root element of SpinButton is a container `<div>`.\n * The root slot receives the `className` and `style` specified on the `<SpinButton>`.\n * All other native props are applied to the primary slot: `input`.\n */\n root: NonNullable<Slot<'span'>>;\n\n /**\n * Input that displays the current value and accepts direct input from the user.\n * Displayed value is formatted.\n *\n * This is the primary slot.\n */\n input: NonNullable<Slot<'input'>>;\n\n /**\n * Renders the increment control.\n */\n incrementButton: NonNullable<Slot<'button'>>;\n\n /**\n * Renders the decrement control.\n */\n decrementButton: NonNullable<Slot<'button'>>;\n};\n\nexport type SpinButtonCommons = {\n /**\n * Initial value of the control (assumed to be valid). Updates to this prop will not be respected.\n *\n * Use this if you intend for the SpinButton to be an uncontrolled component which maintains its\n * own value. For a controlled component, use `value` instead. (Mutually exclusive with `value`.)\n */\n defaultValue: number;\n\n /**\n * Current value of the control (assumed to be valid).\n *\n * Only provide this if the SpinButton is a controlled component where you are maintaining its\n * current state and passing updates based on change events; otherwise, use the `defaultValue`\n * property. (Mutually exclusive with `defaultValue`.)\n */\n value: number;\n\n /**\n * String representation of `value`.\n *\n * Use this when displaying the value to users as something other than a plain number.\n * For example, when displaying currency values this might be \"$1.00\" when value is `1`.\n *\n * Only provide this if the SpinButton is a controlled component where you are maintaining its\n * current state and passing updates based on change events. When SpinButton is used as an\n * uncontrolled component this prop is ignored.\n */\n displayValue: string;\n\n /**\n * Min value of the control. If not provided, the control has no minimum value.\n */\n min: number;\n\n /**\n * Max value of the control. If not provided, the control has no maximum value.\n */\n max: number;\n\n /**\n * Difference between two adjacent values of the control.\n * This value is used to calculate the precision of the input if no `precision` is given.\n * The precision calculated this way will always be greater than or equal 0.\n * @default 1\n */\n step: number;\n\n /**\n * Large difference between two values. This should be greater than `step` and is used\n * when users hit the Page Up or Page Down keys.\n * @default 1\n */\n stepPage: number;\n\n /**\n * Callback for when the committed value changes.\n * - User presses the up/down buttons (on single press or every spin)\n * - User presses the up/down arrow keys (on single press or every spin)\n * - User *commits* edits to the input text by focusing away (blurring) or pressing enter.\n * Note that this is NOT called for every key press while the user is editing.\n */\n onChange: (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => void;\n\n /**\n * How many decimal places the value should be rounded to.\n *\n * The default is calculated based on the precision of `step`: i.e. if step = 1, precision = 0.\n * step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2.\n */\n precision: number;\n\n /**\n * Controls the colors and borders of the input.\n * @default 'outline'\n */\n appearance: 'outline' | 'underline' | 'filledDarker' | 'filledLighter';\n\n /**\n * Size of the input.\n * @default 'medium'\n */\n size: 'small' | 'medium';\n\n /**\n * Strings for localizing text in the control.\n */\n strings?: SpinButtonStrings;\n};\n\n/**\n * SpinButton Props\n */\nexport type SpinButtonProps = Omit<ComponentProps<Partial<SpinButtonSlots>, 'input'>, 'onChange' | 'size'> &\n Partial<SpinButtonCommons>;\n\n/**\n * State used in rendering SpinButton\n */\nexport type SpinButtonState = ComponentState<SpinButtonSlots> &\n Partial<SpinButtonCommons> &\n Pick<SpinButtonCommons, 'appearance' | 'size'> & {\n /**\n * State used to track which direction, if any, SpinButton is currently spinning.\n * @default 'rest'\n */\n spinState: SpinButtonSpinState;\n\n /**\n * State used to track if the value is at the range bounds of [min-max].\n * @default 'none'\n */\n atBound: SpinButtonBounds;\n };\n\nexport type SpinButtonChangeEvent =\n | React.MouseEvent<HTMLButtonElement>\n | React.ChangeEvent<HTMLElement>\n | React.FocusEvent<HTMLInputElement>\n | React.KeyboardEvent<HTMLInputElement>;\n\nexport type SpinButtonOnChangeData = {\n value?: number;\n displayValue?: string;\n};\n\nexport type SpinButtonSpinState = 'rest' | 'up' | 'down';\nexport type SpinButtonBounds = 'none' | 'min' | 'max' | 'both';\n\nexport type SpinButtonStrings = {\n /**\n * Label applied to the increment button.\n * Can include the token \"\\{step\\}\" which will be replaced with the value of the `step` prop.\n */\n incrementButtonLabel: string;\n\n /**\n * Label applied to the decrement button.\n * Can include the token \"\\{step\\}\" which will be replaced with the value of the `step` prop.\n */\n decrementButtonLabel: string;\n};\n"]}
1
+ {"version":3,"file":"SpinButton.types.js","sourceRoot":"../src/","sources":["components/SpinButton/SpinButton.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';\n// import { Input } from '@fluentui/react-input';\nimport * as React from 'react';\n\nexport type SpinButtonSlots = {\n /**\n * The root element of SpinButton is a container `<div>`.\n * The root slot receives the `className` and `style` specified on the `<SpinButton>`.\n * All other native props are applied to the primary slot: `input`.\n */\n root: NonNullable<Slot<'span'>>;\n\n /**\n * Input that displays the current value and accepts direct input from the user.\n * Displayed value is formatted.\n *\n * This is the primary slot.\n */\n input: NonNullable<Slot<'input'>>;\n\n /**\n * Renders the increment control.\n */\n incrementButton: NonNullable<Slot<'button'>>;\n\n /**\n * Renders the decrement control.\n */\n decrementButton: NonNullable<Slot<'button'>>;\n};\n\n/**\n * SpinButton Props\n */\nexport type SpinButtonProps = Omit<\n ComponentProps<Partial<SpinButtonSlots>, 'input'>,\n 'defaultValue' | 'onChange' | 'size' | 'value'\n> & {\n /**\n * Controls the colors and borders of the input.\n * @default 'outline'\n */\n appearance?: 'outline' | 'underline' | 'filled-darker' | 'filled-lighter';\n\n /**\n * Initial value of the control (assumed to be valid). Updates to this prop will not be respected.\n *\n * Use this if you intend for the SpinButton to be an uncontrolled component which maintains its\n * own value. For a controlled component, use `value` instead. (Mutually exclusive with `value`.)\n *\n * Use `null` to indicate the control has no value.\n */\n defaultValue?: number | null;\n\n /**\n * String representation of `value`.\n *\n * Use this when displaying the value to users as something other than a plain number.\n * For example, when displaying currency values this might be \"$1.00\" when value is `1`.\n *\n * Only provide this if the SpinButton is a controlled component where you are maintaining its\n * current state and passing updates based on change events. When SpinButton is used as an\n * uncontrolled component this prop is ignored.\n */\n displayValue?: string;\n\n /**\n * Max value of the control. If not provided, the control has no maximum value.\n */\n max?: number;\n\n /**\n * Min value of the control. If not provided, the control has no minimum value.\n */\n min?: number;\n\n /**\n * Callback for when the committed value changes.\n * - User presses the up/down buttons (on single press or every spin)\n * - User presses the up/down arrow keys (on single press or every spin)\n * - User *commits* edits to the input text by focusing away (blurring) or pressing enter.\n * Note that this is NOT called for every key press while the user is editing.\n */\n onChange?: (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => void;\n\n /**\n * How many decimal places the value should be rounded to.\n *\n * The default is calculated based on the precision of `step`: i.e. if step = 1, precision = 0.\n * step = 0.0089, precision = 4. step = 300, precision = 2. step = 23.00, precision = 2.\n */\n precision?: number;\n\n /**\n * Size of the input.\n * @default 'medium'\n */\n size?: 'small' | 'medium';\n\n /**\n * Difference between two adjacent values of the control.\n * This value is used to calculate the precision of the input if no `precision` is given.\n * The precision calculated this way will always be greater than or equal 0.\n * @default 1\n */\n step?: number;\n\n /**\n * Large difference between two values. This should be greater than `step` and is used\n * when users hit the Page Up or Page Down keys.\n * @default 1\n */\n stepPage?: number;\n\n /**\n * Current value of the control (assumed to be valid).\n *\n * Only provide this if the SpinButton is a controlled component where you are maintaining its\n * current state and passing updates based on change events; otherwise, use the `defaultValue`\n * property.\n *\n * Use `null` to indicate the control has no value.\n *\n * Mutually exclusive with `defaultValue`.\n */\n value?: number | null;\n};\n\n/**\n * State used in rendering SpinButton\n */\nexport type SpinButtonState = ComponentState<SpinButtonSlots> &\n Required<Pick<SpinButtonProps, 'appearance' | 'size'>> & {\n /**\n * State used to track which direction, if any, SpinButton is currently spinning.\n * @default 'rest'\n */\n spinState: SpinButtonSpinState;\n\n /**\n * State used to track if the value is at the range bounds of [min-max].\n * @default 'none'\n */\n atBound: SpinButtonBounds;\n };\n\nexport type SpinButtonChangeEvent =\n | React.MouseEvent<HTMLButtonElement>\n | React.ChangeEvent<HTMLElement>\n | React.FocusEvent<HTMLInputElement>\n | React.KeyboardEvent<HTMLInputElement>;\n\nexport type SpinButtonOnChangeData = {\n value?: number | null;\n displayValue?: string;\n};\n\nexport type SpinButtonSpinState = 'rest' | 'up' | 'down';\nexport type SpinButtonBounds = 'none' | 'min' | 'max' | 'both';\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["components/SpinButton/renderSpinButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAZ,MAAuB,OAAvB;AACA,SAAS,QAAT,QAAyB,2BAAzB;AAGA;;AAEG;;AACH,OAAO,MAAM,yBAAyB,GAAI,KAAD,IAA2B;AAClE;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA,QAAM;AAAE,IAAA,KAAF;AAAS,IAAA;AAAT,MAAuB,QAAQ,CAAkB,KAAlB,CAArC;AAEA,sBACE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,IAAP,EAAW,EAAA,GAAK,SAAS,CAAC;AAAf,GAAX,eACE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,KAAP,EAAY,EAAA,GAAK,SAAS,CAAC;AAAf,GAAZ,CADF,eAEE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,eAAP,EAAsB,EAAA,GAAK,SAAS,CAAC;AAAf,GAAtB,CAFF,eAGE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,eAAP,EAAsB,EAAA,GAAK,SAAS,CAAC;AAAf,GAAtB,CAHF,CADF;AAOD,CAhCM","sourcesContent":["import * as React from 'react';\nimport { getSlots } from '@fluentui/react-utilities';\nimport type { SpinButtonState, SpinButtonSlots } from './SpinButton.types';\n\n/**\n * Render the final JSX of SpinButton\n */\nexport const renderSpinButton_unstable = (state: SpinButtonState) => {\n // Leaving this here for now.\n // This is the approach using react-input's Input component.\n // It has some Typescript problems and feels hacky.\n // const { slots, slotProps } = getSlots<SpinButtonSlots>(state);\n\n // const { contentAfter, ...otherInputSlotProps } = slotProps.input as SpinButtonSlots['input'];\n // const inputContentAfter = {\n // ...contentAfter,\n // children: (\n // <>\n // <slots.incrementButton {...slotProps.incrementButton} />\n // <slots.decrementButton {...slotProps.decrementButton} />\n // </>\n // ),\n // };\n\n // return (\n // <slots.root {...slotProps.root}>\n // <slots.input {...otherInputSlotProps} contentAfter={inputContentAfter}/>\n // </slots.root>\n // );\n\n const { slots, slotProps } = getSlots<SpinButtonSlots>(state);\n\n return (\n <slots.root {...slotProps.root}>\n <slots.input {...slotProps.input} />\n <slots.incrementButton {...slotProps.incrementButton} />\n <slots.decrementButton {...slotProps.decrementButton} />\n </slots.root>\n );\n};\n"],"sourceRoot":"../src/"}
1
+ {"version":3,"sources":["components/SpinButton/renderSpinButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAZ,MAAuB,OAAvB;AACA,SAAS,QAAT,QAAyB,2BAAzB;AAGA;;AAEG;;AACH,OAAO,MAAM,yBAAyB,GAAI,KAAD,IAA2B;EAClE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA,MAAM;IAAE,KAAF;IAAS;EAAT,IAAuB,QAAQ,CAAkB,KAAlB,CAArC;EAEA,oBACE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,IAAP,EAAW,EAAA,GAAK,SAAS,CAAC;EAAf,CAAX,eACE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,KAAP,EAAY,EAAA,GAAK,SAAS,CAAC;EAAf,CAAZ,CADF,eAEE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,eAAP,EAAsB,EAAA,GAAK,SAAS,CAAC;EAAf,CAAtB,CAFF,eAGE,KAAA,CAAA,aAAA,CAAC,KAAK,CAAC,eAAP,EAAsB,EAAA,GAAK,SAAS,CAAC;EAAf,CAAtB,CAHF,CADF;AAOD,CAhCM","sourcesContent":["import * as React from 'react';\nimport { getSlots } from '@fluentui/react-utilities';\nimport type { SpinButtonState, SpinButtonSlots } from './SpinButton.types';\n\n/**\n * Render the final JSX of SpinButton\n */\nexport const renderSpinButton_unstable = (state: SpinButtonState) => {\n // Leaving this here for now.\n // This is the approach using react-input's Input component.\n // It has some Typescript problems and feels hacky.\n // const { slots, slotProps } = getSlots<SpinButtonSlots>(state);\n\n // const { contentAfter, ...otherInputSlotProps } = slotProps.input as SpinButtonSlots['input'];\n // const inputContentAfter = {\n // ...contentAfter,\n // children: (\n // <>\n // <slots.incrementButton {...slotProps.incrementButton} />\n // <slots.decrementButton {...slotProps.decrementButton} />\n // </>\n // ),\n // };\n\n // return (\n // <slots.root {...slotProps.root}>\n // <slots.input {...otherInputSlotProps} contentAfter={inputContentAfter}/>\n // </slots.root>\n // );\n\n const { slots, slotProps } = getSlots<SpinButtonSlots>(state);\n\n return (\n <slots.root {...slotProps.root}>\n <slots.input {...slotProps.input} />\n <slots.incrementButton {...slotProps.incrementButton} />\n <slots.decrementButton {...slotProps.decrementButton} />\n </slots.root>\n );\n};\n"],"sourceRoot":"../src/"}
@@ -1,8 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { getPartitionedNativeProps, resolveShorthand, useControllableState, useMergedEventCallbacks, useTimeout } from '@fluentui/react-utilities';
3
3
  import * as Keys from '@fluentui/keyboard-keys';
4
- import { spinButtonDefaultStrings } from './SpinButton.strings';
5
- import { calculatePrecision, precisionRound, getBound, clampWhenInRange } from '../../utils/index';
4
+ import { calculatePrecision, precisionRound, getBound, clamp } from '../../utils/index';
6
5
  import { ChevronUp16Regular, ChevronDown16Regular } from '@fluentui/react-icons';
7
6
  const DEFAULT_SPIN_DELAY_MS = 150;
8
7
  const MIN_SPIN_DELAY_MS = 80;
@@ -23,10 +22,12 @@ const lerp = (start, end, percent) => start + (end - start) * percent;
23
22
 
24
23
 
25
24
  export const useSpinButton_unstable = (props, ref) => {
25
+ var _a;
26
+
26
27
  const nativeProps = getPartitionedNativeProps({
27
28
  props,
28
29
  primarySlotTagName: 'input',
29
- excludedPropNames: ['onChange', 'size', 'min', 'max']
30
+ excludedPropNames: ['defaultValue', 'max', 'min', 'onChange', 'size', 'value']
30
31
  });
31
32
  const {
32
33
  value,
@@ -43,8 +44,7 @@ export const useSpinButton_unstable = (props, ref) => {
43
44
  root,
44
45
  input,
45
46
  incrementButton,
46
- decrementButton,
47
- strings = spinButtonDefaultStrings
47
+ decrementButton
48
48
  } = props;
49
49
  const precision = React.useMemo(() => {
50
50
  return precisionFromProps !== null && precisionFromProps !== void 0 ? precisionFromProps : Math.max(calculatePrecision(step), 0);
@@ -54,94 +54,44 @@ export const useSpinButton_unstable = (props, ref) => {
54
54
  defaultState: defaultValue,
55
55
  initialState: 0
56
56
  });
57
- const [textValue, setTextValue] = React.useState(value !== undefined && displayValue !== undefined ? displayValue : String(currentValue));
58
- const [spinState, setSpinState] = React.useState('rest');
59
- const [atBound, setAtBound] = React.useState('none');
57
+ const isControlled = value !== undefined;
58
+ const [textValue, setTextValue] = React.useState(undefined);
59
+ const [keyboardSpinState, setKeyboardSpinState] = React.useState('rest');
60
60
  const internalState = React.useRef({
61
61
  value: currentValue,
62
- spinState,
62
+ spinState: 'rest',
63
63
  spinTime: 0,
64
- spinDelay: DEFAULT_SPIN_DELAY_MS
64
+ spinDelay: DEFAULT_SPIN_DELAY_MS,
65
+ atBound: currentValue !== null ? getBound(precisionRound(currentValue, precision), min, max) : 'none'
65
66
  });
66
- const state = {
67
- size,
68
- appearance,
69
- spinState,
70
- atBound,
71
- components: {
72
- root: 'span',
73
- input: 'input',
74
- incrementButton: 'button',
75
- decrementButton: 'button'
76
- },
77
- root: resolveShorthand(root, {
78
- required: true,
79
- defaultProps: nativeProps.root
80
- }),
81
- input: resolveShorthand(input, {
82
- required: true,
83
- defaultProps: {
84
- ref,
85
- autoComplete: 'off',
86
- role: 'spinbutton',
87
- appearance: appearance,
88
- type: 'text',
89
- ...nativeProps.primary
90
- }
91
- }),
92
- incrementButton: resolveShorthand(incrementButton, {
93
- required: true,
94
- defaultProps: {
95
- tabIndex: -1,
96
- children: /*#__PURE__*/React.createElement(ChevronUp16Regular, null),
97
- disabled: nativeProps.primary.disabled,
98
- 'aria-label': strings.incrementButtonLabel.replace('{step}', step.toString())
99
- }
100
- }),
101
- decrementButton: resolveShorthand(decrementButton, {
102
- required: true,
103
- defaultProps: {
104
- tabIndex: -1,
105
- children: /*#__PURE__*/React.createElement(ChevronDown16Regular, null),
106
- disabled: nativeProps.primary.disabled,
107
- 'aria-label': strings.decrementButtonLabel.replace('{step}', step.toString())
108
- }
109
- })
110
- };
111
67
  const [setStepTimeout, clearStepTimeout] = useTimeout();
112
- React.useEffect(() => {
113
- let newTextValue;
114
-
115
- if (value !== undefined) {
116
- const roundedValue = precisionRound(value, precision);
117
- newTextValue = displayValue !== null && displayValue !== void 0 ? displayValue : String(roundedValue);
118
- internalState.current.value = roundedValue;
119
- setAtBound(getBound(roundedValue, min, max));
120
- } else {
121
- newTextValue = String(precisionRound(currentValue, precision));
122
- internalState.current.value = currentValue;
123
- }
124
68
 
125
- setTextValue(newTextValue);
126
- }, [value, displayValue, currentValue, precision, setAtBound, min, max]);
69
+ const stepValue = (e, direction, startFrom) => {
70
+ let startValue = internalState.current.value;
127
71
 
128
- const handleInputChange = e => {
129
- if (!internalState.current.previousTextValue) {
130
- internalState.current.previousTextValue = textValue;
131
- }
72
+ if (startFrom) {
73
+ const num = parseFloat(startFrom);
132
74
 
133
- const newValue = e.target.value;
134
- setTextValue(newValue);
135
- };
75
+ if (!isNaN(num)) {
76
+ startValue = num;
77
+ }
78
+ }
136
79
 
137
- const stepValue = (e, direction) => {
80
+ const val = startValue;
138
81
  const dir = direction === 'up' || direction === 'upPage' ? 1 : -1;
139
82
  const stepSize = direction === 'upPage' || direction === 'downPage' ? stepPage : step;
140
- const val = internalState.current.value;
83
+
84
+ if (val === null) {
85
+ const stepStart = min === undefined ? 0 : min;
86
+ const nullStep = clamp(stepStart + stepSize * dir, min, max);
87
+ commit(e, nullStep);
88
+ return;
89
+ }
90
+
141
91
  let newValue = val + stepSize * dir;
142
92
 
143
93
  if (!Number.isNaN(newValue)) {
144
- newValue = clampWhenInRange(val, newValue, min, max);
94
+ newValue = clamp(newValue, min, max);
145
95
  }
146
96
 
147
97
  commit(e, newValue);
@@ -156,6 +106,15 @@ export const useSpinButton_unstable = (props, ref) => {
156
106
  }
157
107
  };
158
108
 
109
+ const handleInputChange = e => {
110
+ if (!internalState.current.previousTextValue) {
111
+ internalState.current.previousTextValue = textValue;
112
+ }
113
+
114
+ const newValue = e.target.value;
115
+ setTextValue(newValue);
116
+ };
117
+
159
118
  const handleIncrementMouseDown = e => {
160
119
  internalState.current.spinState = 'up';
161
120
  stepValue(e, 'up');
@@ -179,43 +138,48 @@ export const useSpinButton_unstable = (props, ref) => {
179
138
  };
180
139
 
181
140
  const handleKeyDown = e => {
141
+ let nextKeyboardSpinState = 'rest';
142
+
182
143
  if (e.key === Keys.ArrowUp) {
183
- stepValue(e, 'up');
184
- setSpinState('up');
144
+ stepValue(e, 'up', textValue);
145
+ nextKeyboardSpinState = 'up';
185
146
  } else if (e.key === Keys.ArrowDown) {
186
- stepValue(e, 'down');
187
- setSpinState('down');
147
+ stepValue(e, 'down', textValue);
148
+ nextKeyboardSpinState = 'down';
188
149
  } else if (e.key === Keys.PageUp) {
189
150
  e.preventDefault();
190
- stepValue(e, 'upPage');
191
- setSpinState('up');
151
+ stepValue(e, 'upPage', textValue);
152
+ nextKeyboardSpinState = 'up';
192
153
  } else if (e.key === Keys.PageDown) {
193
154
  e.preventDefault();
194
- stepValue(e, 'downPage');
195
- setSpinState('down');
155
+ stepValue(e, 'downPage', textValue);
156
+ nextKeyboardSpinState = 'down';
196
157
  } else if (!e.shiftKey && e.key === Keys.Home && min !== undefined) {
197
158
  commit(e, min);
198
- setSpinState('down');
159
+ nextKeyboardSpinState = 'down';
199
160
  } else if (!e.shiftKey && e.key === Keys.End && max !== undefined) {
200
161
  commit(e, max);
201
- setSpinState('up');
162
+ nextKeyboardSpinState = 'up';
202
163
  } else if (e.key === Keys.Enter) {
203
164
  commit(e, currentValue, textValue);
204
- setSpinState('rest');
165
+ internalState.current.previousTextValue = undefined;
205
166
  } else if (e.key === Keys.Escape) {
206
167
  if (internalState.current.previousTextValue) {
207
- setTextValue(internalState.current.previousTextValue);
168
+ setTextValue(undefined);
208
169
  internalState.current.previousTextValue = undefined;
209
170
  }
171
+ }
210
172
 
211
- setSpinState('rest');
212
- } else {
213
- setSpinState('rest');
173
+ if (keyboardSpinState !== nextKeyboardSpinState) {
174
+ setKeyboardSpinState(nextKeyboardSpinState);
214
175
  }
215
176
  };
216
177
 
217
178
  const handleKeyUp = e => {
218
- setSpinState('rest');
179
+ if (keyboardSpinState !== 'rest') {
180
+ setKeyboardSpinState('rest');
181
+ internalState.current.spinState = 'rest';
182
+ }
219
183
  };
220
184
 
221
185
  const commit = (e, newValue, newDisplayValue) => {
@@ -226,8 +190,12 @@ export const useSpinButton_unstable = (props, ref) => {
226
190
  if (valueChanged) {
227
191
  roundedValue = precisionRound(newValue, precision);
228
192
  setCurrentValue(roundedValue);
229
- internalState.current.value = roundedValue;
230
- setAtBound(getBound(roundedValue, min, max));
193
+ } else if (displayValueChanged && !isControlled) {
194
+ const nextValue = parseFloat(newDisplayValue);
195
+
196
+ if (!isNaN(nextValue)) {
197
+ setCurrentValue(precisionRound(nextValue, precision));
198
+ }
231
199
  }
232
200
 
233
201
  if (valueChanged || displayValueChanged) {
@@ -236,13 +204,82 @@ export const useSpinButton_unstable = (props, ref) => {
236
204
  displayValue: newDisplayValue
237
205
  });
238
206
  }
207
+
208
+ setTextValue(undefined);
239
209
  };
240
210
 
241
- state.input.value = textValue;
211
+ const state = {
212
+ size,
213
+ appearance,
214
+ spinState: keyboardSpinState,
215
+ atBound: internalState.current.atBound,
216
+ components: {
217
+ root: 'span',
218
+ input: 'input',
219
+ incrementButton: 'button',
220
+ decrementButton: 'button'
221
+ },
222
+ root: resolveShorthand(root, {
223
+ required: true,
224
+ defaultProps: nativeProps.root
225
+ }),
226
+ input: resolveShorthand(input, {
227
+ required: true,
228
+ defaultProps: {
229
+ ref,
230
+ autoComplete: 'off',
231
+ role: 'spinbutton',
232
+ appearance: appearance,
233
+ type: 'text',
234
+ ...nativeProps.primary
235
+ }
236
+ }),
237
+ incrementButton: resolveShorthand(incrementButton, {
238
+ required: true,
239
+ defaultProps: {
240
+ tabIndex: -1,
241
+ children: /*#__PURE__*/React.createElement(ChevronUp16Regular, null),
242
+ disabled: nativeProps.primary.disabled,
243
+ 'aria-label': 'Increment value',
244
+ type: 'button'
245
+ }
246
+ }),
247
+ decrementButton: resolveShorthand(decrementButton, {
248
+ required: true,
249
+ defaultProps: {
250
+ tabIndex: -1,
251
+ children: /*#__PURE__*/React.createElement(ChevronDown16Regular, null),
252
+ disabled: nativeProps.primary.disabled,
253
+ 'aria-label': 'Decrement value',
254
+ type: 'button'
255
+ }
256
+ })
257
+ };
258
+ let valueToDisplay;
259
+
260
+ if (textValue !== undefined) {
261
+ valueToDisplay = textValue;
262
+ } else if (value === null || currentValue === null) {
263
+ valueToDisplay = displayValue !== null && displayValue !== void 0 ? displayValue : '';
264
+ internalState.current.value = null;
265
+ internalState.current.atBound = 'none';
266
+ } else {
267
+ const roundedValue = precisionRound(currentValue, precision);
268
+ internalState.current.value = roundedValue;
269
+ internalState.current.atBound = getBound(roundedValue, min, max);
270
+
271
+ if (isControlled) {
272
+ valueToDisplay = displayValue !== null && displayValue !== void 0 ? displayValue : String(roundedValue);
273
+ } else {
274
+ valueToDisplay = String(roundedValue);
275
+ }
276
+ }
277
+
278
+ state.input.value = valueToDisplay;
242
279
  state.input['aria-valuemin'] = min;
243
280
  state.input['aria-valuemax'] = max;
244
- state.input['aria-valuenow'] = currentValue;
245
- state.input['aria-valuetext'] = value !== undefined && displayValue || undefined;
281
+ state.input['aria-valuenow'] = currentValue !== null && currentValue !== void 0 ? currentValue : undefined;
282
+ state.input['aria-valuetext'] = (_a = state.input['aria-valuetext']) !== null && _a !== void 0 ? _a : value !== undefined && displayValue || undefined;
246
283
  state.input.onChange = useMergedEventCallbacks(state.input.onChange, handleInputChange);
247
284
  state.input.onBlur = useMergedEventCallbacks(state.input.onBlur, handleBlur);
248
285
  state.input.onKeyDown = useMergedEventCallbacks(state.input.onKeyDown, handleKeyDown);