@contentful/field-editor-number 1.1.5 → 1.1.7-beta.1

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.
@@ -0,0 +1,6 @@
1
+ export declare const styles: {
2
+ container: string;
3
+ controlsWrapper: string;
4
+ control: string;
5
+ input: string;
6
+ };
@@ -5,64 +5,94 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
6
 
7
7
  var React = require('react');
8
- var fieldEditorShared = require('@contentful/field-editor-shared');
9
- var isEmpty = _interopDefault(require('lodash/isEmpty'));
10
8
  var f36Components = require('@contentful/f36-components');
9
+ var f36Icons = require('@contentful/f36-icons');
10
+ var fieldEditorShared = require('@contentful/field-editor-shared');
11
+ var tokens = _interopDefault(require('@contentful/f36-tokens'));
12
+ var emotion = require('emotion');
11
13
 
12
- function parseNumber(value, type) {
13
- // This has saner semantics than parseFloat.
14
- // For values with chars in them, it gives
15
- // us NaN unlike parseFloat
16
- var floatVal = +value;
17
- var hasDot = value.includes('.');
18
- var hasFractional = /^(?:\+|-)?\d+\.\d+$/.test(value);
19
-
20
- if (isEmpty(value)) {
21
- return {
22
- isValid: true,
23
- value: undefined
24
- };
25
- }
26
-
27
- if (isNaN(floatVal)) {
28
- return {
29
- isValid: false,
30
- value: undefined
31
- };
32
- }
33
-
34
- if (type === 'Integer' && hasDot) {
35
- var intVal = parseInt(value, 10);
36
- return {
37
- isValid: false,
38
- value: intVal
39
- };
40
- }
14
+ var styles = {
15
+ container: /*#__PURE__*/emotion.css({
16
+ position: 'relative'
17
+ }),
18
+ controlsWrapper: /*#__PURE__*/emotion.css({
19
+ position: 'absolute',
20
+ top: '1px',
21
+ right: '1px',
22
+ width: tokens.spacingL,
23
+ height: 'calc(100% - 2px)',
24
+ display: 'flex',
25
+ flexDirection: 'column'
26
+ }),
27
+ control: /*#__PURE__*/emotion.css({
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ justifyContent: 'center',
31
+ minHeight: 0,
32
+ cursor: 'pointer',
33
+ padding: 0,
34
+ margin: 0,
35
+ outline: 'none',
36
+ border: "0 solid " + tokens.gray300,
37
+ background: 'none',
38
+ borderLeftWidth: '1px',
39
+ '&:first-of-type': {
40
+ borderTopRightRadius: tokens.borderRadiusMedium
41
+ },
42
+ '&:last-of-type': {
43
+ borderTopWidth: '1px',
44
+ borderBottomRightRadius: tokens.borderRadiusMedium
45
+ },
46
+ svg: {
47
+ fill: tokens.gray600
48
+ },
49
+ '&:active': {
50
+ background: tokens.gray300
51
+ }
52
+ }),
53
+ input: /*#__PURE__*/emotion.css({
54
+ paddingRight: tokens.spacingXl
55
+ })
56
+ };
41
57
 
42
- if (hasDot && !hasFractional) {
43
- return {
44
- isValid: false,
45
- value: floatVal
46
- };
58
+ function parseNumber(value, type) {
59
+ if (Number.isNaN(+value)) {
60
+ return;
47
61
  }
48
62
 
49
- return {
50
- isValid: true,
51
- value: floatVal
52
- };
63
+ return type === 'Integer' ? parseInt(value, 10) : parseFloat(value);
64
+ }
65
+ var FLOAT_REGEX = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/;
66
+ var INT_REGEX = /^[+-]?([0-9]*)$/;
67
+ function isNumberInputValueValid(value, type) {
68
+ var regex = type === 'Integer' ? INT_REGEX : FLOAT_REGEX;
69
+ return regex.test(value);
53
70
  }
54
71
 
55
- function getRangeFromField(field) {
72
+ var getRangeFromField = function getRangeFromField(field) {
56
73
  var validations = field.validations || [];
57
74
  var result = validations.find(function (validation) {
58
75
  return validation.range;
59
76
  });
60
77
  return result ? result.range : {};
61
- }
62
-
63
- function valueToString(value) {
78
+ };
79
+ var valueToString = function valueToString(value) {
64
80
  return value === undefined ? '' : String(value);
65
- }
81
+ };
82
+ var countDecimals = function countDecimals(number) {
83
+ var _number$toString$spli, _number$toString$spli2;
84
+
85
+ return (_number$toString$spli = (_number$toString$spli2 = number.toString().split('.')[1]) == null ? void 0 : _number$toString$spli2.length) != null ? _number$toString$spli : 0;
86
+ };
87
+
88
+ var StepChangeType;
89
+
90
+ (function (StepChangeType) {
91
+ StepChangeType["Increment"] = "increment";
92
+ StepChangeType["Decrement"] = "decrement";
93
+ })(StepChangeType || (StepChangeType = {}));
94
+
95
+ var NUMBER_STEP = 1;
66
96
 
67
97
  function InnerNumberEditor(_ref) {
68
98
  var disabled = _ref.disabled,
@@ -70,46 +100,138 @@ function InnerNumberEditor(_ref) {
70
100
  field = _ref.field,
71
101
  setValue = _ref.setValue,
72
102
  sdkValue = _ref.value;
73
- var previousValue = React.useRef(valueToString(sdkValue));
74
103
 
75
104
  var _React$useState = React.useState(valueToString(sdkValue)),
76
105
  inputValue = _React$useState[0],
77
106
  setInputValue = _React$useState[1];
78
107
 
79
108
  var range = getRangeFromField(field);
109
+ var inputRef = React.useRef(null);
80
110
  React.useEffect(function () {
81
- previousValue.current = valueToString(sdkValue);
111
+ var stringSdkValue = valueToString(sdkValue); // Update the input value if the SDK value (numeric) changes
112
+
113
+ if (stringSdkValue !== inputValue) {
114
+ setInputValue(stringSdkValue);
115
+ } // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to trigger it only when sdkValue has changed
116
+
82
117
  }, [sdkValue]);
83
- React.useEffect(function () {
84
- var stringifiedSdkValue = valueToString(sdkValue); // Update the input value (string) if the SDK value (numeric) changes
85
118
 
86
- if (stringifiedSdkValue !== previousValue.current && stringifiedSdkValue !== inputValue) {
87
- setInputValue(stringifiedSdkValue);
119
+ var updateExternalValue = function updateExternalValue(value) {
120
+ if (sdkValue !== value) {
121
+ setValue(value);
122
+ }
123
+ };
124
+
125
+ var changeValueByStep = function changeValueByStep(type) {
126
+ var currentValue = Number.isNaN(+inputValue) ? 0 : +inputValue;
127
+ var nextValue = type === StepChangeType.Increment ? currentValue + NUMBER_STEP : currentValue - NUMBER_STEP; // Floating point numbers cannot represent all decimals precisely in binary.
128
+ // This can lead to unexpected results, such as 0.1 + 0.2 = 0.30000000000000004.
129
+ // See more details: https://floating-point-gui.de/
130
+
131
+ nextValue = +nextValue.toFixed(countDecimals(currentValue));
132
+ setInputValue(valueToString(nextValue));
133
+ setValue(nextValue);
134
+ }; // Keeps focus on the input
135
+
136
+
137
+ var handleControlPointerDown = function handleControlPointerDown(event) {
138
+ var _inputRef$current;
139
+
140
+ event.preventDefault();
141
+ (_inputRef$current = inputRef.current) == null ? void 0 : _inputRef$current.focus();
142
+ };
143
+
144
+ var handleKeyDown = function handleKeyDown(event) {
145
+ var keyToFnMap = {
146
+ ArrowUp: function ArrowUp() {
147
+ return changeValueByStep(StepChangeType.Increment);
148
+ },
149
+ ArrowDown: function ArrowDown() {
150
+ return changeValueByStep(StepChangeType.Decrement);
151
+ }
152
+ };
153
+ var fn = keyToFnMap[event.key];
154
+
155
+ if (fn) {
156
+ event.preventDefault();
157
+ fn();
158
+ }
159
+ };
160
+
161
+ var handleInputChange = function handleInputChange(e) {
162
+ var value = e.target.value;
163
+
164
+ if (!value) {
165
+ setInputValue(value);
166
+ updateExternalValue(undefined);
167
+ return;
168
+ }
169
+
170
+ if (!isNumberInputValueValid(value, field.type)) {
171
+ return;
172
+ }
173
+
174
+ setInputValue(value);
175
+ var parsedNumber = parseNumber(value, field.type);
176
+ field.setInvalid(parsedNumber === undefined);
177
+
178
+ if (parsedNumber !== undefined) {
179
+ updateExternalValue(parsedNumber);
88
180
  }
89
- }, [inputValue, sdkValue]);
181
+ };
182
+
90
183
  return React.createElement("div", {
91
- "data-test-id": "number-editor"
92
- }, React.createElement(f36Components.TextInput, {
184
+ "data-test-id": "number-editor",
185
+ className: styles.container
186
+ }, React.createElement(f36Components.TextInput // With type="number" react doesn't call onChange for certain inputs, for example if you type `e`
187
+ // so we use "text" instead and fully rely on our own validation.
188
+ // See more details: https://github.com/facebook/react/issues/6556
189
+ , {
190
+ // With type="number" react doesn't call onChange for certain inputs, for example if you type `e`
191
+ // so we use "text" instead and fully rely on our own validation.
192
+ // See more details: https://github.com/facebook/react/issues/6556
193
+ type: "text",
93
194
  testId: "number-editor-input",
94
- min: range.min !== undefined ? String(range.min) : '',
95
- max: range.max !== undefined ? String(range.max) : '',
96
- step: field.type === 'Integer' ? '1' : '',
195
+ className: styles.input,
196
+ min: range.min,
197
+ max: range.max,
198
+ step: NUMBER_STEP,
97
199
  isRequired: field.required,
98
200
  isInvalid: errors.length > 0,
99
201
  isDisabled: disabled,
100
202
  value: inputValue,
101
- type: "number",
102
- onChange: function onChange(e) {
103
- var parseResult = parseNumber(e.target.value, field.type);
104
- field.setInvalid(!parseResult.isValid);
105
-
106
- if (parseResult.isValid) {
107
- setValue(parseResult.value);
108
- }
109
-
110
- setInputValue(e.target.value);
111
- }
112
- }));
203
+ ref: inputRef,
204
+ onChange: handleInputChange,
205
+ onKeyDown: handleKeyDown,
206
+ // The same role that input type="number" has
207
+ // See more details: https://www.digitala11y.com/spinbutton-role/
208
+ role: "spinbutton",
209
+ "aria-valuenow": sdkValue != null ? sdkValue : 0,
210
+ "aria-valuetext": inputValue,
211
+ "aria-valuemin": range.min,
212
+ "aria-valuemax": range.max
213
+ }), !disabled && React.createElement("div", {
214
+ className: styles.controlsWrapper,
215
+ "aria-hidden": "true"
216
+ }, React.createElement("button", {
217
+ tabIndex: -1,
218
+ className: styles.control,
219
+ onClick: function onClick() {
220
+ return changeValueByStep(StepChangeType.Increment);
221
+ },
222
+ onPointerDown: handleControlPointerDown
223
+ }, React.createElement(f36Icons.ArrowUpTrimmedIcon, {
224
+ size: "medium"
225
+ })), React.createElement("button", {
226
+ tabIndex: -1,
227
+ className: styles.control,
228
+ onClick: function onClick() {
229
+ return changeValueByStep(StepChangeType.Decrement);
230
+ },
231
+ onPointerDown: handleControlPointerDown
232
+ }, React.createElement(f36Icons.ArrowDownTrimmedIcon, {
233
+ size: "medium"
234
+ }))));
113
235
  }
114
236
 
115
237
  function NumberEditor(props) {
@@ -1 +1 @@
1
- {"version":3,"file":"field-editor-number.cjs.development.js","sources":["../src/parseNumber.ts","../src/NumberEditor.tsx"],"sourcesContent":["import isEmpty from 'lodash/isEmpty';\n\nexport function parseNumber(\n value: string,\n type: string\n): {\n isValid: boolean;\n value: number | undefined;\n} {\n // This has saner semantics than parseFloat.\n // For values with chars in them, it gives\n // us NaN unlike parseFloat\n const floatVal = +value;\n const hasDot = value.includes('.');\n const hasFractional = /^(?:\\+|-)?\\d+\\.\\d+$/.test(value);\n\n if (isEmpty(value)) {\n return {\n isValid: true,\n value: undefined,\n };\n }\n\n if (isNaN(floatVal)) {\n return {\n isValid: false,\n value: undefined,\n };\n }\n\n if (type === 'Integer' && hasDot) {\n const intVal = parseInt(value, 10);\n\n return {\n isValid: false,\n value: intVal,\n };\n }\n\n if (hasDot && !hasFractional) {\n return {\n isValid: false,\n value: floatVal,\n };\n }\n\n return {\n isValid: true,\n value: floatVal,\n };\n}\n","import * as React from 'react';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\nimport { parseNumber } from './parseNumber';\n\nimport { TextInput } from '@contentful/f36-components';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype RangeValidation = { min?: number; max?: number };\n\nfunction getRangeFromField(field: FieldAPI): RangeValidation {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n}\n\nfunction valueToString(value: InnerNumberEditorProps['value']) {\n return value === undefined ? '' : String(value);\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const previousValue = React.useRef(valueToString(sdkValue));\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n\n React.useEffect(() => {\n previousValue.current = valueToString(sdkValue);\n }, [sdkValue]);\n\n React.useEffect(() => {\n const stringifiedSdkValue = valueToString(sdkValue);\n // Update the input value (string) if the SDK value (numeric) changes\n if (stringifiedSdkValue !== previousValue.current && stringifiedSdkValue !== inputValue) {\n setInputValue(stringifiedSdkValue);\n }\n }, [inputValue, sdkValue]);\n\n return (\n <div data-test-id=\"number-editor\">\n <TextInput\n testId=\"number-editor-input\"\n min={range.min !== undefined ? String(range.min) : ''}\n max={range.max !== undefined ? String(range.max) : ''}\n step={field.type === 'Integer' ? '1' : ''}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n type=\"number\"\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n const parseResult = parseNumber(e.target.value, field.type);\n field.setInvalid(!parseResult.isValid);\n\n if (parseResult.isValid) {\n setValue(parseResult.value);\n }\n\n setInputValue(e.target.value);\n }}\n />\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n"],"names":["parseNumber","value","type","floatVal","hasDot","includes","hasFractional","test","isEmpty","isValid","undefined","isNaN","intVal","parseInt","getRangeFromField","field","validations","result","find","validation","range","valueToString","String","InnerNumberEditor","disabled","errors","setValue","sdkValue","previousValue","React","inputValue","setInputValue","current","stringifiedSdkValue","TextInput","testId","min","max","step","isRequired","required","isInvalid","length","isDisabled","onChange","e","parseResult","target","setInvalid","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":"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"}
1
+ {"version":3,"file":"field-editor-number.cjs.development.js","sources":["../src/NumberEditor.styles.ts","../src/parseNumber.ts","../src/utils.ts","../src/NumberEditor.tsx"],"sourcesContent":["import tokens from '@contentful/f36-tokens';\nimport { css } from 'emotion';\n\nexport const styles = {\n container: css({\n position: 'relative',\n }),\n controlsWrapper: css({\n position: 'absolute',\n top: '1px',\n right: '1px',\n width: tokens.spacingL,\n height: 'calc(100% - 2px)',\n display: 'flex',\n flexDirection: 'column',\n }),\n control: css({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: 0,\n cursor: 'pointer',\n padding: 0,\n margin: 0,\n outline: 'none',\n border: `0 solid ${tokens.gray300}`,\n background: 'none',\n borderLeftWidth: '1px',\n\n '&:first-of-type': {\n borderTopRightRadius: tokens.borderRadiusMedium,\n },\n\n '&:last-of-type': {\n borderTopWidth: '1px',\n borderBottomRightRadius: tokens.borderRadiusMedium,\n },\n\n svg: {\n fill: tokens.gray600,\n },\n\n '&:active': {\n background: tokens.gray300,\n },\n }),\n input: css({\n paddingRight: tokens.spacingXl,\n }),\n};\n","export function parseNumber(value: string, type: string) {\n if (Number.isNaN(+value)) {\n return;\n }\n\n return type === 'Integer' ? parseInt(value, 10) : parseFloat(value);\n}\n\nconst FLOAT_REGEX = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/;\nconst INT_REGEX = /^[+-]?([0-9]*)$/;\n\nexport function isNumberInputValueValid(value: string, type: string) {\n const regex = type === 'Integer' ? INT_REGEX : FLOAT_REGEX;\n\n return regex.test(value);\n}\n","import { FieldAPI } from '@contentful/field-editor-shared';\n\ntype RangeValidation = { min?: number; max?: number };\n\nexport const getRangeFromField = (field: FieldAPI): RangeValidation => {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n};\n\nexport const valueToString = (value: number | null | undefined) => {\n return value === undefined ? '' : String(value);\n};\n\nexport const countDecimals = (number: number) => {\n return number.toString().split('.')[1]?.length ?? 0;\n};\n","import * as React from 'react';\n\nimport { TextInput } from '@contentful/f36-components';\nimport { ArrowUpTrimmedIcon, ArrowDownTrimmedIcon } from '@contentful/f36-icons';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\n\nimport { styles } from './NumberEditor.styles';\nimport { isNumberInputValueValid, parseNumber } from './parseNumber';\nimport { getRangeFromField, valueToString, countDecimals } from './utils';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nenum StepChangeType {\n Increment = 'increment',\n Decrement = 'decrement',\n}\n\nconst NUMBER_STEP = 1;\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n const stringSdkValue = valueToString(sdkValue);\n // Update the input value if the SDK value (numeric) changes\n if (stringSdkValue !== inputValue) {\n setInputValue(stringSdkValue);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to trigger it only when sdkValue has changed\n }, [sdkValue]);\n\n const updateExternalValue = (value: number | undefined) => {\n if (sdkValue !== value) {\n setValue(value);\n }\n };\n\n const changeValueByStep = (type: StepChangeType) => {\n const currentValue = Number.isNaN(+inputValue) ? 0 : +inputValue;\n let nextValue =\n type === StepChangeType.Increment ? currentValue + NUMBER_STEP : currentValue - NUMBER_STEP;\n // Floating point numbers cannot represent all decimals precisely in binary.\n // This can lead to unexpected results, such as 0.1 + 0.2 = 0.30000000000000004.\n // See more details: https://floating-point-gui.de/\n nextValue = +nextValue.toFixed(countDecimals(currentValue));\n\n setInputValue(valueToString(nextValue));\n setValue(nextValue);\n };\n\n // Keeps focus on the input\n const handleControlPointerDown: React.PointerEventHandler<HTMLButtonElement> = (event) => {\n event.preventDefault();\n inputRef.current?.focus();\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<any>) => {\n const keyToFnMap: {\n [key: string]: () => void;\n } = {\n ArrowUp: () => changeValueByStep(StepChangeType.Increment),\n ArrowDown: () => changeValueByStep(StepChangeType.Decrement),\n };\n\n const fn = keyToFnMap[event.key];\n if (fn) {\n event.preventDefault();\n fn();\n }\n };\n\n const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {\n const value = e.target.value;\n if (!value) {\n setInputValue(value);\n updateExternalValue(undefined);\n return;\n }\n\n if (!isNumberInputValueValid(value, field.type)) {\n return;\n }\n\n setInputValue(value);\n\n const parsedNumber = parseNumber(value, field.type);\n field.setInvalid(parsedNumber === undefined);\n if (parsedNumber !== undefined) {\n updateExternalValue(parsedNumber);\n }\n };\n\n return (\n <div data-test-id=\"number-editor\" className={styles.container}>\n <TextInput\n // With type=\"number\" react doesn't call onChange for certain inputs, for example if you type `e`\n // so we use \"text\" instead and fully rely on our own validation.\n // See more details: https://github.com/facebook/react/issues/6556\n type=\"text\"\n testId=\"number-editor-input\"\n className={styles.input}\n min={range.min}\n max={range.max}\n step={NUMBER_STEP}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n ref={inputRef}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n // The same role that input type=\"number\" has\n // See more details: https://www.digitala11y.com/spinbutton-role/\n role=\"spinbutton\"\n aria-valuenow={sdkValue ?? 0}\n aria-valuetext={inputValue}\n aria-valuemin={range.min}\n aria-valuemax={range.max}\n />\n {/**\n * We hide this controls from screen readers and keyboard focus.\n * For those purposes we have a keyboard handler. The same way native input number works.\n */}\n {!disabled && (\n <div className={styles.controlsWrapper} aria-hidden=\"true\">\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Increment)}\n onPointerDown={handleControlPointerDown}>\n <ArrowUpTrimmedIcon size=\"medium\" />\n </button>\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Decrement)}\n onPointerDown={handleControlPointerDown}>\n <ArrowDownTrimmedIcon size=\"medium\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n"],"names":["styles","container","css","position","controlsWrapper","top","right","width","tokens","spacingL","height","display","flexDirection","control","alignItems","justifyContent","minHeight","cursor","padding","margin","outline","border","gray300","background","borderLeftWidth","borderTopRightRadius","borderRadiusMedium","borderTopWidth","borderBottomRightRadius","svg","fill","gray600","input","paddingRight","spacingXl","parseNumber","value","type","Number","isNaN","parseInt","parseFloat","FLOAT_REGEX","INT_REGEX","isNumberInputValueValid","regex","test","getRangeFromField","field","validations","result","find","validation","range","valueToString","undefined","String","countDecimals","number","toString","split","length","StepChangeType","NUMBER_STEP","InnerNumberEditor","disabled","errors","setValue","sdkValue","React","inputValue","setInputValue","inputRef","stringSdkValue","updateExternalValue","changeValueByStep","currentValue","nextValue","Increment","toFixed","handleControlPointerDown","event","preventDefault","current","focus","handleKeyDown","keyToFnMap","ArrowUp","ArrowDown","Decrement","fn","key","handleInputChange","e","target","parsedNumber","setInvalid","className","TextInput","testId","min","max","step","isRequired","required","isInvalid","isDisabled","ref","onChange","onKeyDown","role","tabIndex","onClick","onPointerDown","ArrowUpTrimmedIcon","size","ArrowDownTrimmedIcon","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t=require("react"),i=require("@contentful/field-editor-shared"),r=(e=require("lodash/isEmpty"))&&"object"==typeof e&&"default"in e?e.default:e,n=require("@contentful/f36-components");function a(e){return void 0===e?"":String(e)}function l(e){var i=e.disabled,l=e.errors,u=e.field,d=e.setValue,s=e.value,o=t.useRef(a(s)),f=t.useState(a(s)),v=f[0],c=f[1],m=function(e){var t=(e.validations||[]).find((function(e){return e.range}));return t?t.range:{}}(u);return t.useEffect((function(){o.current=a(s)}),[s]),t.useEffect((function(){var e=a(s);e!==o.current&&e!==v&&c(e)}),[v,s]),t.createElement("div",{"data-test-id":"number-editor"},t.createElement(n.TextInput,{testId:"number-editor-input",min:void 0!==m.min?String(m.min):"",max:void 0!==m.max?String(m.max):"",step:"Integer"===u.type?"1":"",isRequired:u.required,isInvalid:l.length>0,isDisabled:i,value:v,type:"number",onChange:function(e){var t,i,n,a,l,s=(i=u.type,n=+(t=e.target.value),a=t.includes("."),l=/^(?:\+|-)?\d+\.\d+$/.test(t),r(t)?{isValid:!0,value:void 0}:isNaN(n)?{isValid:!1,value:void 0}:"Integer"===i&&a?{isValid:!1,value:parseInt(t,10)}:a&&!l?{isValid:!1,value:n}:{isValid:!0,value:n});u.setInvalid(!s.isValid),s.isValid&&d(s.value),c(e.target.value)}}))}function u(e){var r=e.field;return t.createElement(i.FieldConnector,{field:r,isInitiallyDisabled:e.isInitiallyDisabled},(function(e){return t.createElement(l,{disabled:e.disabled,errors:e.errors,field:r,setValue:e.setValue,value:e.value})}))}u.defaultProps={isInitiallyDisabled:!0},exports.NumberEditor=u;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t,n=require("react"),r=require("@contentful/f36-components"),i=require("@contentful/f36-icons"),o=require("@contentful/field-editor-shared"),a=(e=require("@contentful/f36-tokens"))&&"object"==typeof e&&"default"in e?e.default:e,u=require("emotion"),l={container:u.css({position:"relative"}),controlsWrapper:u.css({position:"absolute",top:"1px",right:"1px",width:a.spacingL,height:"calc(100% - 2px)",display:"flex",flexDirection:"column"}),control:u.css({display:"flex",alignItems:"center",justifyContent:"center",minHeight:0,cursor:"pointer",padding:0,margin:0,outline:"none",border:"0 solid "+a.gray300,background:"none",borderLeftWidth:"1px","&:first-of-type":{borderTopRightRadius:a.borderRadiusMedium},"&:last-of-type":{borderTopWidth:"1px",borderBottomRightRadius:a.borderRadiusMedium},svg:{fill:a.gray600},"&:active":{background:a.gray300}}),input:u.css({paddingRight:a.spacingXl})},s=/^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/,c=/^[+-]?([0-9]*)$/,d=function(e){return void 0===e?"":String(e)};function f(e){var o=e.disabled,a=e.errors,u=e.field,f=e.setValue,m=e.value,p=n.useState(d(m)),v=p[0],b=p[1],g=function(e){var t=(e.validations||[]).find((function(e){return e.range}));return t?t.range:{}}(u),x=n.useRef(null);n.useEffect((function(){var e=d(m);e!==v&&b(e)}),[m]);var I=function(e){m!==e&&f(e)},y=function(e){var n,r,i,o=Number.isNaN(+v)?0:+v,a=e===t.Increment?o+1:o-1;a=+a.toFixed((n=o,null!=(r=null==(i=n.toString().split(".")[1])?void 0:i.length)?r:0)),b(d(a)),f(a)},h=function(e){var t;e.preventDefault(),null==(t=x.current)||t.focus()};return n.createElement("div",{"data-test-id":"number-editor",className:l.container},n.createElement(r.TextInput,{type:"text",testId:"number-editor-input",className:l.input,min:g.min,max:g.max,step:1,isRequired:u.required,isInvalid:a.length>0,isDisabled:o,value:v,ref:x,onChange:function(e){var t=e.target.value;if(!t)return b(t),void I(void 0);if(function(e,t){return("Integer"===t?c:s).test(e)}(t,u.type)){b(t);var n=function(e,t){if(!Number.isNaN(+e))return"Integer"===t?parseInt(e,10):parseFloat(e)}(t,u.type);u.setInvalid(void 0===n),void 0!==n&&I(n)}},onKeyDown:function(e){var n={ArrowUp:function(){return y(t.Increment)},ArrowDown:function(){return y(t.Decrement)}}[e.key];n&&(e.preventDefault(),n())},role:"spinbutton","aria-valuenow":null!=m?m:0,"aria-valuetext":v,"aria-valuemin":g.min,"aria-valuemax":g.max}),!o&&n.createElement("div",{className:l.controlsWrapper,"aria-hidden":"true"},n.createElement("button",{tabIndex:-1,className:l.control,onClick:function(){return y(t.Increment)},onPointerDown:h},n.createElement(i.ArrowUpTrimmedIcon,{size:"medium"})),n.createElement("button",{tabIndex:-1,className:l.control,onClick:function(){return y(t.Decrement)},onPointerDown:h},n.createElement(i.ArrowDownTrimmedIcon,{size:"medium"}))))}function m(e){var t=e.field;return n.createElement(o.FieldConnector,{field:t,isInitiallyDisabled:e.isInitiallyDisabled},(function(e){return n.createElement(f,{disabled:e.disabled,errors:e.errors,field:t,setValue:e.setValue,value:e.value})}))}!function(e){e.Increment="increment",e.Decrement="decrement"}(t||(t={})),m.defaultProps={isInitiallyDisabled:!0},exports.NumberEditor=m;
2
2
  //# sourceMappingURL=field-editor-number.cjs.production.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"field-editor-number.cjs.production.min.js","sources":["../src/NumberEditor.tsx","../src/parseNumber.ts"],"sourcesContent":["import * as React from 'react';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\nimport { parseNumber } from './parseNumber';\n\nimport { TextInput } from '@contentful/f36-components';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype RangeValidation = { min?: number; max?: number };\n\nfunction getRangeFromField(field: FieldAPI): RangeValidation {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n}\n\nfunction valueToString(value: InnerNumberEditorProps['value']) {\n return value === undefined ? '' : String(value);\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const previousValue = React.useRef(valueToString(sdkValue));\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n\n React.useEffect(() => {\n previousValue.current = valueToString(sdkValue);\n }, [sdkValue]);\n\n React.useEffect(() => {\n const stringifiedSdkValue = valueToString(sdkValue);\n // Update the input value (string) if the SDK value (numeric) changes\n if (stringifiedSdkValue !== previousValue.current && stringifiedSdkValue !== inputValue) {\n setInputValue(stringifiedSdkValue);\n }\n }, [inputValue, sdkValue]);\n\n return (\n <div data-test-id=\"number-editor\">\n <TextInput\n testId=\"number-editor-input\"\n min={range.min !== undefined ? String(range.min) : ''}\n max={range.max !== undefined ? String(range.max) : ''}\n step={field.type === 'Integer' ? '1' : ''}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n type=\"number\"\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n const parseResult = parseNumber(e.target.value, field.type);\n field.setInvalid(!parseResult.isValid);\n\n if (parseResult.isValid) {\n setValue(parseResult.value);\n }\n\n setInputValue(e.target.value);\n }}\n />\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n","import isEmpty from 'lodash/isEmpty';\n\nexport function parseNumber(\n value: string,\n type: string\n): {\n isValid: boolean;\n value: number | undefined;\n} {\n // This has saner semantics than parseFloat.\n // For values with chars in them, it gives\n // us NaN unlike parseFloat\n const floatVal = +value;\n const hasDot = value.includes('.');\n const hasFractional = /^(?:\\+|-)?\\d+\\.\\d+$/.test(value);\n\n if (isEmpty(value)) {\n return {\n isValid: true,\n value: undefined,\n };\n }\n\n if (isNaN(floatVal)) {\n return {\n isValid: false,\n value: undefined,\n };\n }\n\n if (type === 'Integer' && hasDot) {\n const intVal = parseInt(value, 10);\n\n return {\n isValid: false,\n value: intVal,\n };\n }\n\n if (hasDot && !hasFractional) {\n return {\n isValid: false,\n value: floatVal,\n };\n }\n\n return {\n isValid: true,\n value: floatVal,\n };\n}\n"],"names":["valueToString","value","undefined","String","InnerNumberEditor","disabled","errors","field","setValue","sdkValue","previousValue","React","inputValue","setInputValue","range","result","validations","find","validation","getRangeFromField","current","stringifiedSdkValue","TextInput","testId","min","max","step","type","isRequired","required","isInvalid","length","isDisabled","onChange","e","floatVal","hasDot","hasFractional","parseResult","target","includes","test","isEmpty","isValid","isNaN","parseInt","setInvalid","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":"iQAgCA,SAASA,EAAcC,eACJC,IAAVD,EAAsB,GAAKE,OAAOF,GAU3C,SAASG,SACPC,IAAAA,SACAC,IAAAA,OACAC,IAAAA,MACAC,IAAAA,SACOC,IAAPR,MAEMS,EAAgBC,SAAaX,EAAcS,MACbE,WAAeX,EAAcS,IAA1DG,OAAYC,OACbC,EA5BR,SAA2BP,OAEnBQ,GADcR,EAAMS,aAAe,IACdC,MAAK,SAACC,UAAgBA,EAAmBJ,gBAG7DC,EAASA,EAAOD,MAAQ,GAuBjBK,CAAkBZ,UAEhCI,aAAgB,WACdD,EAAcU,QAAUpB,EAAcS,KACrC,CAACA,IAEJE,aAAgB,eACRU,EAAsBrB,EAAcS,GAEtCY,IAAwBX,EAAcU,SAAWC,IAAwBT,GAC3EC,EAAcQ,KAEf,CAACT,EAAYH,IAGdE,sCAAkB,iBAChBA,gBAACW,aACCC,OAAO,sBACPC,SAAmBtB,IAAdY,EAAMU,IAAoBrB,OAAOW,EAAMU,KAAO,GACnDC,SAAmBvB,IAAdY,EAAMW,IAAoBtB,OAAOW,EAAMW,KAAO,GACnDC,KAAqB,YAAfnB,EAAMoB,KAAqB,IAAM,GACvCC,WAAYrB,EAAMsB,SAClBC,UAAWxB,EAAOyB,OAAS,EAC3BC,WAAY3B,EACZJ,MAAOW,EACPe,KAAK,SACLM,SAAU,SAACC,OC3EjBjC,EACA0B,EAQMQ,EACAC,EACAC,EDiEQC,GC3EdX,ED2EwDpB,EAAMoB,KCnExDQ,IATNlC,ED4EwCiC,EAAEK,OAAOtC,OClE3CmC,EAASnC,EAAMuC,SAAS,KACxBH,EAAgB,sBAAsBI,KAAKxC,GAE7CyC,EAAQzC,GACH,CACL0C,SAAS,EACT1C,WAAOC,GAIP0C,MAAMT,GACD,CACLQ,SAAS,EACT1C,WAAOC,GAIE,YAATyB,GAAsBS,EAGjB,CACLO,SAAS,EACT1C,MAJa4C,SAAS5C,EAAO,KAQ7BmC,IAAWC,EACN,CACLM,SAAS,EACT1C,MAAOkC,GAIJ,CACLQ,SAAS,EACT1C,MAAOkC,IDgCD5B,EAAMuC,YAAYR,EAAYK,SAE1BL,EAAYK,SACdnC,EAAS8B,EAAYrC,OAGvBY,EAAcqB,EAAEK,OAAOtC,oBAOjB8C,EAAaC,OACnBzC,EAAUyC,EAAVzC,aAGNI,gBAACsC,kBAAuB1C,MAAOA,EAAO2C,oBAAqBF,EAAME,sBAC9D,mBAMCvC,gBAACP,GACCC,WAJFA,SAKEC,SANFA,OAOEC,MAAOA,EACPC,WANFA,SAOEP,QAVFA,WAiBR8C,EAAaI,aAAe,CAC1BD,qBAAqB"}
1
+ {"version":3,"file":"field-editor-number.cjs.production.min.js","sources":["../src/NumberEditor.tsx","../src/NumberEditor.styles.ts","../src/parseNumber.ts","../src/utils.ts"],"sourcesContent":["import * as React from 'react';\n\nimport { TextInput } from '@contentful/f36-components';\nimport { ArrowUpTrimmedIcon, ArrowDownTrimmedIcon } from '@contentful/f36-icons';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\n\nimport { styles } from './NumberEditor.styles';\nimport { isNumberInputValueValid, parseNumber } from './parseNumber';\nimport { getRangeFromField, valueToString, countDecimals } from './utils';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nenum StepChangeType {\n Increment = 'increment',\n Decrement = 'decrement',\n}\n\nconst NUMBER_STEP = 1;\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n const stringSdkValue = valueToString(sdkValue);\n // Update the input value if the SDK value (numeric) changes\n if (stringSdkValue !== inputValue) {\n setInputValue(stringSdkValue);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to trigger it only when sdkValue has changed\n }, [sdkValue]);\n\n const updateExternalValue = (value: number | undefined) => {\n if (sdkValue !== value) {\n setValue(value);\n }\n };\n\n const changeValueByStep = (type: StepChangeType) => {\n const currentValue = Number.isNaN(+inputValue) ? 0 : +inputValue;\n let nextValue =\n type === StepChangeType.Increment ? currentValue + NUMBER_STEP : currentValue - NUMBER_STEP;\n // Floating point numbers cannot represent all decimals precisely in binary.\n // This can lead to unexpected results, such as 0.1 + 0.2 = 0.30000000000000004.\n // See more details: https://floating-point-gui.de/\n nextValue = +nextValue.toFixed(countDecimals(currentValue));\n\n setInputValue(valueToString(nextValue));\n setValue(nextValue);\n };\n\n // Keeps focus on the input\n const handleControlPointerDown: React.PointerEventHandler<HTMLButtonElement> = (event) => {\n event.preventDefault();\n inputRef.current?.focus();\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<any>) => {\n const keyToFnMap: {\n [key: string]: () => void;\n } = {\n ArrowUp: () => changeValueByStep(StepChangeType.Increment),\n ArrowDown: () => changeValueByStep(StepChangeType.Decrement),\n };\n\n const fn = keyToFnMap[event.key];\n if (fn) {\n event.preventDefault();\n fn();\n }\n };\n\n const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {\n const value = e.target.value;\n if (!value) {\n setInputValue(value);\n updateExternalValue(undefined);\n return;\n }\n\n if (!isNumberInputValueValid(value, field.type)) {\n return;\n }\n\n setInputValue(value);\n\n const parsedNumber = parseNumber(value, field.type);\n field.setInvalid(parsedNumber === undefined);\n if (parsedNumber !== undefined) {\n updateExternalValue(parsedNumber);\n }\n };\n\n return (\n <div data-test-id=\"number-editor\" className={styles.container}>\n <TextInput\n // With type=\"number\" react doesn't call onChange for certain inputs, for example if you type `e`\n // so we use \"text\" instead and fully rely on our own validation.\n // See more details: https://github.com/facebook/react/issues/6556\n type=\"text\"\n testId=\"number-editor-input\"\n className={styles.input}\n min={range.min}\n max={range.max}\n step={NUMBER_STEP}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n ref={inputRef}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n // The same role that input type=\"number\" has\n // See more details: https://www.digitala11y.com/spinbutton-role/\n role=\"spinbutton\"\n aria-valuenow={sdkValue ?? 0}\n aria-valuetext={inputValue}\n aria-valuemin={range.min}\n aria-valuemax={range.max}\n />\n {/**\n * We hide this controls from screen readers and keyboard focus.\n * For those purposes we have a keyboard handler. The same way native input number works.\n */}\n {!disabled && (\n <div className={styles.controlsWrapper} aria-hidden=\"true\">\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Increment)}\n onPointerDown={handleControlPointerDown}>\n <ArrowUpTrimmedIcon size=\"medium\" />\n </button>\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Decrement)}\n onPointerDown={handleControlPointerDown}>\n <ArrowDownTrimmedIcon size=\"medium\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n","import tokens from '@contentful/f36-tokens';\nimport { css } from 'emotion';\n\nexport const styles = {\n container: css({\n position: 'relative',\n }),\n controlsWrapper: css({\n position: 'absolute',\n top: '1px',\n right: '1px',\n width: tokens.spacingL,\n height: 'calc(100% - 2px)',\n display: 'flex',\n flexDirection: 'column',\n }),\n control: css({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: 0,\n cursor: 'pointer',\n padding: 0,\n margin: 0,\n outline: 'none',\n border: `0 solid ${tokens.gray300}`,\n background: 'none',\n borderLeftWidth: '1px',\n\n '&:first-of-type': {\n borderTopRightRadius: tokens.borderRadiusMedium,\n },\n\n '&:last-of-type': {\n borderTopWidth: '1px',\n borderBottomRightRadius: tokens.borderRadiusMedium,\n },\n\n svg: {\n fill: tokens.gray600,\n },\n\n '&:active': {\n background: tokens.gray300,\n },\n }),\n input: css({\n paddingRight: tokens.spacingXl,\n }),\n};\n","export function parseNumber(value: string, type: string) {\n if (Number.isNaN(+value)) {\n return;\n }\n\n return type === 'Integer' ? parseInt(value, 10) : parseFloat(value);\n}\n\nconst FLOAT_REGEX = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/;\nconst INT_REGEX = /^[+-]?([0-9]*)$/;\n\nexport function isNumberInputValueValid(value: string, type: string) {\n const regex = type === 'Integer' ? INT_REGEX : FLOAT_REGEX;\n\n return regex.test(value);\n}\n","import { FieldAPI } from '@contentful/field-editor-shared';\n\ntype RangeValidation = { min?: number; max?: number };\n\nexport const getRangeFromField = (field: FieldAPI): RangeValidation => {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n};\n\nexport const valueToString = (value: number | null | undefined) => {\n return value === undefined ? '' : String(value);\n};\n\nexport const countDecimals = (number: number) => {\n return number.toString().split('.')[1]?.length ?? 0;\n};\n"],"names":["StepChangeType","styles","container","css","position","controlsWrapper","top","right","width","tokens","spacingL","height","display","flexDirection","control","alignItems","justifyContent","minHeight","cursor","padding","margin","outline","border","gray300","background","borderLeftWidth","borderTopRightRadius","borderRadiusMedium","borderTopWidth","borderBottomRightRadius","svg","fill","gray600","input","paddingRight","spacingXl","FLOAT_REGEX","INT_REGEX","valueToString","value","undefined","String","InnerNumberEditor","disabled","errors","field","setValue","sdkValue","React","inputValue","setInputValue","range","result","validations","find","validation","getRangeFromField","inputRef","stringSdkValue","updateExternalValue","changeValueByStep","type","number","currentValue","Number","isNaN","nextValue","Increment","toFixed","toString","split","_number$toString$spli2","length","handleControlPointerDown","event","preventDefault","current","focus","className","TextInput","testId","min","max","step","isRequired","required","isInvalid","isDisabled","ref","onChange","e","target","test","isNumberInputValueValid","parsedNumber","parseInt","parseFloat","parseNumber","setInvalid","onKeyDown","fn","ArrowUp","ArrowDown","Decrement","key","role","tabIndex","onClick","onPointerDown","ArrowUpTrimmedIcon","size","ArrowDownTrimmedIcon","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":"0EAiCKA,yPC9BQC,EAAS,CACpBC,UAAWC,MAAI,CACbC,SAAU,aAEZC,gBAAiBF,MAAI,CACnBC,SAAU,WACVE,IAAK,MACLC,MAAO,MACPC,MAAOC,EAAOC,SACdC,OAAQ,mBACRC,QAAS,OACTC,cAAe,WAEjBC,QAASX,MAAI,CACXS,QAAS,OACTG,WAAY,SACZC,eAAgB,SAChBC,UAAW,EACXC,OAAQ,UACRC,QAAS,EACTC,OAAQ,EACRC,QAAS,OACTC,kBAAmBb,EAAOc,QAC1BC,WAAY,OACZC,gBAAiB,wBAEE,CACjBC,qBAAsBjB,EAAOkB,qCAGb,CAChBC,eAAgB,MAChBC,wBAAyBpB,EAAOkB,oBAGlCG,IAAK,CACHC,KAAMtB,EAAOuB,oBAGH,CACVR,WAAYf,EAAOc,WAGvBU,MAAO9B,MAAI,CACT+B,aAAczB,EAAO0B,aCvCnBC,EAAc,yCACdC,EAAY,kBCGLC,EAAgB,SAACC,eACXC,IAAVD,EAAsB,GAAKE,OAAOF,IH2B3C,SAASG,SACPC,IAAAA,SACAC,IAAAA,OACAC,IAAAA,MACAC,IAAAA,SACOC,IAAPR,QAEoCS,WAAeV,EAAcS,IAA1DE,OAAYC,OACbC,EG5CyB,SAACN,OAE1BO,GADcP,EAAMQ,aAAe,IACdC,MAAK,SAACC,UAAgBA,EAAmBJ,gBAG7DC,EAASA,EAAOD,MAAQ,GHuCjBK,CAAkBX,GAC1BY,EAAWT,SAA+B,MAEhDA,aAAgB,eACRU,EAAiBpB,EAAcS,GAEjCW,IAAmBT,GACrBC,EAAcQ,KAGf,CAACX,QAEEY,EAAsB,SAACpB,GACvBQ,IAAaR,GACfO,EAASP,IAIPqB,EAAoB,SAACC,OGlDCC,MHmDpBC,EAAeC,OAAOC,OAAOhB,GAAc,GAAKA,EAClDiB,EACFL,IAAS7D,EAAemE,UAAYJ,EA/BtB,EA+BmDA,EA/BnD,EAmChBG,GAAaA,EAAUE,SGzDGN,EHyDmBC,oBGxDxCD,EAAOO,WAAWC,MAAM,KAAK,WAA7BC,EAAiCC,UAAU,IH0DhDtB,EAAcZ,EAAc4B,IAC5BpB,EAASoB,IAILO,EAAyE,SAACC,SAC9EA,EAAMC,0BACNlB,EAASmB,YAASC,gBAwClB7B,sCAAkB,gBAAgB8B,UAAW7E,EAAOC,WAClD8C,gBAAC+B,aAIClB,KAAK,OACLmB,OAAO,sBACPF,UAAW7E,EAAOgC,MAClBgD,IAAK9B,EAAM8B,IACXC,IAAK/B,EAAM+B,IACXC,KA9FY,EA+FZC,WAAYvC,EAAMwC,SAClBC,UAAW1C,EAAO4B,OAAS,EAC3Be,WAAY5C,EACZJ,MAAOU,EACPuC,IAAK/B,EACLgC,SAtCgE,SAACC,OAC/DnD,EAAQmD,EAAEC,OAAOpD,UAClBA,SACHW,EAAcX,QACdoB,OAAoBnB,eE7FcD,EAAesB,UAC9B,YAATA,EAAqBxB,EAAYD,GAElCwD,KAAKrD,GF8FXsD,CAAwBtD,EAAOM,EAAMgB,OAI1CX,EAAcX,OAERuD,WElHkBvD,EAAesB,OACrCG,OAAOC,OAAO1B,SAIF,YAATsB,EAAqBkC,SAASxD,EAAO,IAAMyD,WAAWzD,GF6GtC0D,CAAY1D,EAAOM,EAAMgB,MAC9ChB,EAAMqD,gBAA4B1D,IAAjBsD,QACItD,IAAjBsD,GACFnC,EAAoBmC,KAsBlBK,UAtDgB,SAACzB,OAQf0B,EALF,CACFC,QAAS,kBAAMzC,EAAkB5D,EAAemE,YAChDmC,UAAW,kBAAM1C,EAAkB5D,EAAeuG,aAG9B7B,EAAM8B,KACxBJ,IACF1B,EAAMC,iBACNyB,MA8CEK,KAAK,mCACU1D,EAAAA,EAAY,mBACXE,kBACDE,EAAM8B,oBACN9B,EAAM+B,OAMrBvC,GACAK,uBAAK8B,UAAW7E,EAAOI,8BAA6B,QAClD2C,0BACE0D,UAAW,EACX5B,UAAW7E,EAAOa,QAClB6F,QAAS,kBAAM/C,EAAkB5D,EAAemE,YAChDyC,cAAenC,GACfzB,gBAAC6D,sBAAmBC,KAAK,YAE3B9D,0BACE0D,UAAW,EACX5B,UAAW7E,EAAOa,QAClB6F,QAAS,kBAAM/C,EAAkB5D,EAAeuG,YAChDK,cAAenC,GACfzB,gBAAC+D,wBAAqBD,KAAK,uBAQvBE,EAAaC,OACnBpE,EAAUoE,EAAVpE,aAGNG,gBAACkE,kBAAuBrE,MAAOA,EAAOsE,oBAAqBF,EAAME,sBAC9D,mBAMCnE,gBAACN,GACCC,WAJFA,SAKEC,SANFA,OAOEC,MAAOA,EACPC,WANFA,SAOEP,QAVFA,YAnJR,SAAKvC,GACHA,wBACAA,wBAFF,CAAKA,IAAAA,OAoKLgH,EAAaI,aAAe,CAC1BD,qBAAqB"}
@@ -1,62 +1,92 @@
1
- import { createElement, useRef, useState, useEffect } from 'react';
2
- import { FieldConnector } from '@contentful/field-editor-shared';
3
- import isEmpty from 'lodash-es/isEmpty';
1
+ import { createElement, useState, useRef, useEffect } from 'react';
4
2
  import { TextInput } from '@contentful/f36-components';
3
+ import { ArrowUpTrimmedIcon, ArrowDownTrimmedIcon } from '@contentful/f36-icons';
4
+ import { FieldConnector } from '@contentful/field-editor-shared';
5
+ import tokens from '@contentful/f36-tokens';
6
+ import { css } from 'emotion';
7
+
8
+ var styles = {
9
+ container: /*#__PURE__*/css({
10
+ position: 'relative'
11
+ }),
12
+ controlsWrapper: /*#__PURE__*/css({
13
+ position: 'absolute',
14
+ top: '1px',
15
+ right: '1px',
16
+ width: tokens.spacingL,
17
+ height: 'calc(100% - 2px)',
18
+ display: 'flex',
19
+ flexDirection: 'column'
20
+ }),
21
+ control: /*#__PURE__*/css({
22
+ display: 'flex',
23
+ alignItems: 'center',
24
+ justifyContent: 'center',
25
+ minHeight: 0,
26
+ cursor: 'pointer',
27
+ padding: 0,
28
+ margin: 0,
29
+ outline: 'none',
30
+ border: "0 solid " + tokens.gray300,
31
+ background: 'none',
32
+ borderLeftWidth: '1px',
33
+ '&:first-of-type': {
34
+ borderTopRightRadius: tokens.borderRadiusMedium
35
+ },
36
+ '&:last-of-type': {
37
+ borderTopWidth: '1px',
38
+ borderBottomRightRadius: tokens.borderRadiusMedium
39
+ },
40
+ svg: {
41
+ fill: tokens.gray600
42
+ },
43
+ '&:active': {
44
+ background: tokens.gray300
45
+ }
46
+ }),
47
+ input: /*#__PURE__*/css({
48
+ paddingRight: tokens.spacingXl
49
+ })
50
+ };
5
51
 
6
52
  function parseNumber(value, type) {
7
- // This has saner semantics than parseFloat.
8
- // For values with chars in them, it gives
9
- // us NaN unlike parseFloat
10
- var floatVal = +value;
11
- var hasDot = value.includes('.');
12
- var hasFractional = /^(?:\+|-)?\d+\.\d+$/.test(value);
13
-
14
- if (isEmpty(value)) {
15
- return {
16
- isValid: true,
17
- value: undefined
18
- };
19
- }
20
-
21
- if (isNaN(floatVal)) {
22
- return {
23
- isValid: false,
24
- value: undefined
25
- };
26
- }
27
-
28
- if (type === 'Integer' && hasDot) {
29
- var intVal = parseInt(value, 10);
30
- return {
31
- isValid: false,
32
- value: intVal
33
- };
34
- }
35
-
36
- if (hasDot && !hasFractional) {
37
- return {
38
- isValid: false,
39
- value: floatVal
40
- };
53
+ if (Number.isNaN(+value)) {
54
+ return;
41
55
  }
42
56
 
43
- return {
44
- isValid: true,
45
- value: floatVal
46
- };
57
+ return type === 'Integer' ? parseInt(value, 10) : parseFloat(value);
58
+ }
59
+ var FLOAT_REGEX = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/;
60
+ var INT_REGEX = /^[+-]?([0-9]*)$/;
61
+ function isNumberInputValueValid(value, type) {
62
+ var regex = type === 'Integer' ? INT_REGEX : FLOAT_REGEX;
63
+ return regex.test(value);
47
64
  }
48
65
 
49
- function getRangeFromField(field) {
66
+ var getRangeFromField = function getRangeFromField(field) {
50
67
  var validations = field.validations || [];
51
68
  var result = validations.find(function (validation) {
52
69
  return validation.range;
53
70
  });
54
71
  return result ? result.range : {};
55
- }
56
-
57
- function valueToString(value) {
72
+ };
73
+ var valueToString = function valueToString(value) {
58
74
  return value === undefined ? '' : String(value);
59
- }
75
+ };
76
+ var countDecimals = function countDecimals(number) {
77
+ var _number$toString$spli, _number$toString$spli2;
78
+
79
+ return (_number$toString$spli = (_number$toString$spli2 = number.toString().split('.')[1]) == null ? void 0 : _number$toString$spli2.length) != null ? _number$toString$spli : 0;
80
+ };
81
+
82
+ var StepChangeType;
83
+
84
+ (function (StepChangeType) {
85
+ StepChangeType["Increment"] = "increment";
86
+ StepChangeType["Decrement"] = "decrement";
87
+ })(StepChangeType || (StepChangeType = {}));
88
+
89
+ var NUMBER_STEP = 1;
60
90
 
61
91
  function InnerNumberEditor(_ref) {
62
92
  var disabled = _ref.disabled,
@@ -64,46 +94,138 @@ function InnerNumberEditor(_ref) {
64
94
  field = _ref.field,
65
95
  setValue = _ref.setValue,
66
96
  sdkValue = _ref.value;
67
- var previousValue = useRef(valueToString(sdkValue));
68
97
 
69
98
  var _React$useState = useState(valueToString(sdkValue)),
70
99
  inputValue = _React$useState[0],
71
100
  setInputValue = _React$useState[1];
72
101
 
73
102
  var range = getRangeFromField(field);
103
+ var inputRef = useRef(null);
74
104
  useEffect(function () {
75
- previousValue.current = valueToString(sdkValue);
105
+ var stringSdkValue = valueToString(sdkValue); // Update the input value if the SDK value (numeric) changes
106
+
107
+ if (stringSdkValue !== inputValue) {
108
+ setInputValue(stringSdkValue);
109
+ } // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to trigger it only when sdkValue has changed
110
+
76
111
  }, [sdkValue]);
77
- useEffect(function () {
78
- var stringifiedSdkValue = valueToString(sdkValue); // Update the input value (string) if the SDK value (numeric) changes
79
112
 
80
- if (stringifiedSdkValue !== previousValue.current && stringifiedSdkValue !== inputValue) {
81
- setInputValue(stringifiedSdkValue);
113
+ var updateExternalValue = function updateExternalValue(value) {
114
+ if (sdkValue !== value) {
115
+ setValue(value);
116
+ }
117
+ };
118
+
119
+ var changeValueByStep = function changeValueByStep(type) {
120
+ var currentValue = Number.isNaN(+inputValue) ? 0 : +inputValue;
121
+ var nextValue = type === StepChangeType.Increment ? currentValue + NUMBER_STEP : currentValue - NUMBER_STEP; // Floating point numbers cannot represent all decimals precisely in binary.
122
+ // This can lead to unexpected results, such as 0.1 + 0.2 = 0.30000000000000004.
123
+ // See more details: https://floating-point-gui.de/
124
+
125
+ nextValue = +nextValue.toFixed(countDecimals(currentValue));
126
+ setInputValue(valueToString(nextValue));
127
+ setValue(nextValue);
128
+ }; // Keeps focus on the input
129
+
130
+
131
+ var handleControlPointerDown = function handleControlPointerDown(event) {
132
+ var _inputRef$current;
133
+
134
+ event.preventDefault();
135
+ (_inputRef$current = inputRef.current) == null ? void 0 : _inputRef$current.focus();
136
+ };
137
+
138
+ var handleKeyDown = function handleKeyDown(event) {
139
+ var keyToFnMap = {
140
+ ArrowUp: function ArrowUp() {
141
+ return changeValueByStep(StepChangeType.Increment);
142
+ },
143
+ ArrowDown: function ArrowDown() {
144
+ return changeValueByStep(StepChangeType.Decrement);
145
+ }
146
+ };
147
+ var fn = keyToFnMap[event.key];
148
+
149
+ if (fn) {
150
+ event.preventDefault();
151
+ fn();
152
+ }
153
+ };
154
+
155
+ var handleInputChange = function handleInputChange(e) {
156
+ var value = e.target.value;
157
+
158
+ if (!value) {
159
+ setInputValue(value);
160
+ updateExternalValue(undefined);
161
+ return;
162
+ }
163
+
164
+ if (!isNumberInputValueValid(value, field.type)) {
165
+ return;
166
+ }
167
+
168
+ setInputValue(value);
169
+ var parsedNumber = parseNumber(value, field.type);
170
+ field.setInvalid(parsedNumber === undefined);
171
+
172
+ if (parsedNumber !== undefined) {
173
+ updateExternalValue(parsedNumber);
82
174
  }
83
- }, [inputValue, sdkValue]);
175
+ };
176
+
84
177
  return createElement("div", {
85
- "data-test-id": "number-editor"
86
- }, createElement(TextInput, {
178
+ "data-test-id": "number-editor",
179
+ className: styles.container
180
+ }, createElement(TextInput // With type="number" react doesn't call onChange for certain inputs, for example if you type `e`
181
+ // so we use "text" instead and fully rely on our own validation.
182
+ // See more details: https://github.com/facebook/react/issues/6556
183
+ , {
184
+ // With type="number" react doesn't call onChange for certain inputs, for example if you type `e`
185
+ // so we use "text" instead and fully rely on our own validation.
186
+ // See more details: https://github.com/facebook/react/issues/6556
187
+ type: "text",
87
188
  testId: "number-editor-input",
88
- min: range.min !== undefined ? String(range.min) : '',
89
- max: range.max !== undefined ? String(range.max) : '',
90
- step: field.type === 'Integer' ? '1' : '',
189
+ className: styles.input,
190
+ min: range.min,
191
+ max: range.max,
192
+ step: NUMBER_STEP,
91
193
  isRequired: field.required,
92
194
  isInvalid: errors.length > 0,
93
195
  isDisabled: disabled,
94
196
  value: inputValue,
95
- type: "number",
96
- onChange: function onChange(e) {
97
- var parseResult = parseNumber(e.target.value, field.type);
98
- field.setInvalid(!parseResult.isValid);
99
-
100
- if (parseResult.isValid) {
101
- setValue(parseResult.value);
102
- }
103
-
104
- setInputValue(e.target.value);
105
- }
106
- }));
197
+ ref: inputRef,
198
+ onChange: handleInputChange,
199
+ onKeyDown: handleKeyDown,
200
+ // The same role that input type="number" has
201
+ // See more details: https://www.digitala11y.com/spinbutton-role/
202
+ role: "spinbutton",
203
+ "aria-valuenow": sdkValue != null ? sdkValue : 0,
204
+ "aria-valuetext": inputValue,
205
+ "aria-valuemin": range.min,
206
+ "aria-valuemax": range.max
207
+ }), !disabled && createElement("div", {
208
+ className: styles.controlsWrapper,
209
+ "aria-hidden": "true"
210
+ }, createElement("button", {
211
+ tabIndex: -1,
212
+ className: styles.control,
213
+ onClick: function onClick() {
214
+ return changeValueByStep(StepChangeType.Increment);
215
+ },
216
+ onPointerDown: handleControlPointerDown
217
+ }, createElement(ArrowUpTrimmedIcon, {
218
+ size: "medium"
219
+ })), createElement("button", {
220
+ tabIndex: -1,
221
+ className: styles.control,
222
+ onClick: function onClick() {
223
+ return changeValueByStep(StepChangeType.Decrement);
224
+ },
225
+ onPointerDown: handleControlPointerDown
226
+ }, createElement(ArrowDownTrimmedIcon, {
227
+ size: "medium"
228
+ }))));
107
229
  }
108
230
 
109
231
  function NumberEditor(props) {
@@ -1 +1 @@
1
- {"version":3,"file":"field-editor-number.esm.js","sources":["../src/parseNumber.ts","../src/NumberEditor.tsx"],"sourcesContent":["import isEmpty from 'lodash/isEmpty';\n\nexport function parseNumber(\n value: string,\n type: string\n): {\n isValid: boolean;\n value: number | undefined;\n} {\n // This has saner semantics than parseFloat.\n // For values with chars in them, it gives\n // us NaN unlike parseFloat\n const floatVal = +value;\n const hasDot = value.includes('.');\n const hasFractional = /^(?:\\+|-)?\\d+\\.\\d+$/.test(value);\n\n if (isEmpty(value)) {\n return {\n isValid: true,\n value: undefined,\n };\n }\n\n if (isNaN(floatVal)) {\n return {\n isValid: false,\n value: undefined,\n };\n }\n\n if (type === 'Integer' && hasDot) {\n const intVal = parseInt(value, 10);\n\n return {\n isValid: false,\n value: intVal,\n };\n }\n\n if (hasDot && !hasFractional) {\n return {\n isValid: false,\n value: floatVal,\n };\n }\n\n return {\n isValid: true,\n value: floatVal,\n };\n}\n","import * as React from 'react';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\nimport { parseNumber } from './parseNumber';\n\nimport { TextInput } from '@contentful/f36-components';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype RangeValidation = { min?: number; max?: number };\n\nfunction getRangeFromField(field: FieldAPI): RangeValidation {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n}\n\nfunction valueToString(value: InnerNumberEditorProps['value']) {\n return value === undefined ? '' : String(value);\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const previousValue = React.useRef(valueToString(sdkValue));\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n\n React.useEffect(() => {\n previousValue.current = valueToString(sdkValue);\n }, [sdkValue]);\n\n React.useEffect(() => {\n const stringifiedSdkValue = valueToString(sdkValue);\n // Update the input value (string) if the SDK value (numeric) changes\n if (stringifiedSdkValue !== previousValue.current && stringifiedSdkValue !== inputValue) {\n setInputValue(stringifiedSdkValue);\n }\n }, [inputValue, sdkValue]);\n\n return (\n <div data-test-id=\"number-editor\">\n <TextInput\n testId=\"number-editor-input\"\n min={range.min !== undefined ? String(range.min) : ''}\n max={range.max !== undefined ? String(range.max) : ''}\n step={field.type === 'Integer' ? '1' : ''}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n type=\"number\"\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n const parseResult = parseNumber(e.target.value, field.type);\n field.setInvalid(!parseResult.isValid);\n\n if (parseResult.isValid) {\n setValue(parseResult.value);\n }\n\n setInputValue(e.target.value);\n }}\n />\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n"],"names":["parseNumber","value","type","floatVal","hasDot","includes","hasFractional","test","isEmpty","isValid","undefined","isNaN","intVal","parseInt","getRangeFromField","field","validations","result","find","validation","range","valueToString","String","InnerNumberEditor","disabled","errors","setValue","sdkValue","previousValue","React","inputValue","setInputValue","current","stringifiedSdkValue","TextInput","testId","min","max","step","isRequired","required","isInvalid","length","isDisabled","onChange","e","parseResult","target","setInvalid","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":"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"}
1
+ {"version":3,"file":"field-editor-number.esm.js","sources":["../src/NumberEditor.styles.ts","../src/parseNumber.ts","../src/utils.ts","../src/NumberEditor.tsx"],"sourcesContent":["import tokens from '@contentful/f36-tokens';\nimport { css } from 'emotion';\n\nexport const styles = {\n container: css({\n position: 'relative',\n }),\n controlsWrapper: css({\n position: 'absolute',\n top: '1px',\n right: '1px',\n width: tokens.spacingL,\n height: 'calc(100% - 2px)',\n display: 'flex',\n flexDirection: 'column',\n }),\n control: css({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: 0,\n cursor: 'pointer',\n padding: 0,\n margin: 0,\n outline: 'none',\n border: `0 solid ${tokens.gray300}`,\n background: 'none',\n borderLeftWidth: '1px',\n\n '&:first-of-type': {\n borderTopRightRadius: tokens.borderRadiusMedium,\n },\n\n '&:last-of-type': {\n borderTopWidth: '1px',\n borderBottomRightRadius: tokens.borderRadiusMedium,\n },\n\n svg: {\n fill: tokens.gray600,\n },\n\n '&:active': {\n background: tokens.gray300,\n },\n }),\n input: css({\n paddingRight: tokens.spacingXl,\n }),\n};\n","export function parseNumber(value: string, type: string) {\n if (Number.isNaN(+value)) {\n return;\n }\n\n return type === 'Integer' ? parseInt(value, 10) : parseFloat(value);\n}\n\nconst FLOAT_REGEX = /^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]*)?$/;\nconst INT_REGEX = /^[+-]?([0-9]*)$/;\n\nexport function isNumberInputValueValid(value: string, type: string) {\n const regex = type === 'Integer' ? INT_REGEX : FLOAT_REGEX;\n\n return regex.test(value);\n}\n","import { FieldAPI } from '@contentful/field-editor-shared';\n\ntype RangeValidation = { min?: number; max?: number };\n\nexport const getRangeFromField = (field: FieldAPI): RangeValidation => {\n const validations = field.validations || [];\n const result = validations.find((validation) => (validation as any).range) as\n | { range: RangeValidation }\n | undefined;\n return result ? result.range : {};\n};\n\nexport const valueToString = (value: number | null | undefined) => {\n return value === undefined ? '' : String(value);\n};\n\nexport const countDecimals = (number: number) => {\n return number.toString().split('.')[1]?.length ?? 0;\n};\n","import * as React from 'react';\n\nimport { TextInput } from '@contentful/f36-components';\nimport { ArrowUpTrimmedIcon, ArrowDownTrimmedIcon } from '@contentful/f36-icons';\nimport {\n FieldAPI,\n FieldConnector,\n FieldConnectorChildProps,\n} from '@contentful/field-editor-shared';\n\nimport { styles } from './NumberEditor.styles';\nimport { isNumberInputValueValid, parseNumber } from './parseNumber';\nimport { getRangeFromField, valueToString, countDecimals } from './utils';\n\nexport interface NumberEditorProps {\n /**\n * is the field disabled initially\n */\n isInitiallyDisabled: boolean;\n\n /**\n * sdk.field\n */\n field: FieldAPI;\n}\n\ntype InnerNumberEditorProps = Pick<\n FieldConnectorChildProps<number>,\n 'disabled' | 'errors' | 'setValue' | 'value'\n> & {\n field: NumberEditorProps['field'];\n};\n\nenum StepChangeType {\n Increment = 'increment',\n Decrement = 'decrement',\n}\n\nconst NUMBER_STEP = 1;\n\nfunction InnerNumberEditor({\n disabled,\n errors,\n field,\n setValue,\n value: sdkValue,\n}: InnerNumberEditorProps) {\n const [inputValue, setInputValue] = React.useState(valueToString(sdkValue));\n const range = getRangeFromField(field);\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n const stringSdkValue = valueToString(sdkValue);\n // Update the input value if the SDK value (numeric) changes\n if (stringSdkValue !== inputValue) {\n setInputValue(stringSdkValue);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- we want to trigger it only when sdkValue has changed\n }, [sdkValue]);\n\n const updateExternalValue = (value: number | undefined) => {\n if (sdkValue !== value) {\n setValue(value);\n }\n };\n\n const changeValueByStep = (type: StepChangeType) => {\n const currentValue = Number.isNaN(+inputValue) ? 0 : +inputValue;\n let nextValue =\n type === StepChangeType.Increment ? currentValue + NUMBER_STEP : currentValue - NUMBER_STEP;\n // Floating point numbers cannot represent all decimals precisely in binary.\n // This can lead to unexpected results, such as 0.1 + 0.2 = 0.30000000000000004.\n // See more details: https://floating-point-gui.de/\n nextValue = +nextValue.toFixed(countDecimals(currentValue));\n\n setInputValue(valueToString(nextValue));\n setValue(nextValue);\n };\n\n // Keeps focus on the input\n const handleControlPointerDown: React.PointerEventHandler<HTMLButtonElement> = (event) => {\n event.preventDefault();\n inputRef.current?.focus();\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<any>) => {\n const keyToFnMap: {\n [key: string]: () => void;\n } = {\n ArrowUp: () => changeValueByStep(StepChangeType.Increment),\n ArrowDown: () => changeValueByStep(StepChangeType.Decrement),\n };\n\n const fn = keyToFnMap[event.key];\n if (fn) {\n event.preventDefault();\n fn();\n }\n };\n\n const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {\n const value = e.target.value;\n if (!value) {\n setInputValue(value);\n updateExternalValue(undefined);\n return;\n }\n\n if (!isNumberInputValueValid(value, field.type)) {\n return;\n }\n\n setInputValue(value);\n\n const parsedNumber = parseNumber(value, field.type);\n field.setInvalid(parsedNumber === undefined);\n if (parsedNumber !== undefined) {\n updateExternalValue(parsedNumber);\n }\n };\n\n return (\n <div data-test-id=\"number-editor\" className={styles.container}>\n <TextInput\n // With type=\"number\" react doesn't call onChange for certain inputs, for example if you type `e`\n // so we use \"text\" instead and fully rely on our own validation.\n // See more details: https://github.com/facebook/react/issues/6556\n type=\"text\"\n testId=\"number-editor-input\"\n className={styles.input}\n min={range.min}\n max={range.max}\n step={NUMBER_STEP}\n isRequired={field.required}\n isInvalid={errors.length > 0}\n isDisabled={disabled}\n value={inputValue}\n ref={inputRef}\n onChange={handleInputChange}\n onKeyDown={handleKeyDown}\n // The same role that input type=\"number\" has\n // See more details: https://www.digitala11y.com/spinbutton-role/\n role=\"spinbutton\"\n aria-valuenow={sdkValue ?? 0}\n aria-valuetext={inputValue}\n aria-valuemin={range.min}\n aria-valuemax={range.max}\n />\n {/**\n * We hide this controls from screen readers and keyboard focus.\n * For those purposes we have a keyboard handler. The same way native input number works.\n */}\n {!disabled && (\n <div className={styles.controlsWrapper} aria-hidden=\"true\">\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Increment)}\n onPointerDown={handleControlPointerDown}>\n <ArrowUpTrimmedIcon size=\"medium\" />\n </button>\n <button\n tabIndex={-1}\n className={styles.control}\n onClick={() => changeValueByStep(StepChangeType.Decrement)}\n onPointerDown={handleControlPointerDown}>\n <ArrowDownTrimmedIcon size=\"medium\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n\nexport function NumberEditor(props: NumberEditorProps) {\n const { field } = props;\n\n return (\n <FieldConnector<number> field={field} isInitiallyDisabled={props.isInitiallyDisabled}>\n {({\n value,\n errors,\n disabled,\n setValue,\n }: Pick<FieldConnectorChildProps<number>, 'disabled' | 'errors' | 'setValue' | 'value'>) => (\n <InnerNumberEditor\n disabled={disabled}\n errors={errors}\n field={field}\n setValue={setValue}\n value={value}\n />\n )}\n </FieldConnector>\n );\n}\n\nNumberEditor.defaultProps = {\n isInitiallyDisabled: true,\n};\n"],"names":["styles","container","css","position","controlsWrapper","top","right","width","tokens","spacingL","height","display","flexDirection","control","alignItems","justifyContent","minHeight","cursor","padding","margin","outline","border","gray300","background","borderLeftWidth","borderTopRightRadius","borderRadiusMedium","borderTopWidth","borderBottomRightRadius","svg","fill","gray600","input","paddingRight","spacingXl","parseNumber","value","type","Number","isNaN","parseInt","parseFloat","FLOAT_REGEX","INT_REGEX","isNumberInputValueValid","regex","test","getRangeFromField","field","validations","result","find","validation","range","valueToString","undefined","String","countDecimals","number","toString","split","length","StepChangeType","NUMBER_STEP","InnerNumberEditor","disabled","errors","setValue","sdkValue","React","inputValue","setInputValue","inputRef","stringSdkValue","updateExternalValue","changeValueByStep","currentValue","nextValue","Increment","toFixed","handleControlPointerDown","event","preventDefault","current","focus","handleKeyDown","keyToFnMap","ArrowUp","ArrowDown","Decrement","fn","key","handleInputChange","e","target","parsedNumber","setInvalid","className","TextInput","testId","min","max","step","isRequired","required","isInvalid","isDisabled","ref","onChange","onKeyDown","role","tabIndex","onClick","onPointerDown","ArrowUpTrimmedIcon","size","ArrowDownTrimmedIcon","NumberEditor","props","FieldConnector","isInitiallyDisabled","defaultProps"],"mappings":";;;;;;;AAGO,IAAMA,MAAM,GAAG;AACpBC,EAAAA,SAAS,eAAEC,GAAG,CAAC;AACbC,IAAAA,QAAQ,EAAE;AADG,GAAD,CADM;AAIpBC,EAAAA,eAAe,eAAEF,GAAG,CAAC;AACnBC,IAAAA,QAAQ,EAAE,UADS;AAEnBE,IAAAA,GAAG,EAAE,KAFc;AAGnBC,IAAAA,KAAK,EAAE,KAHY;AAInBC,IAAAA,KAAK,EAAEC,MAAM,CAACC,QAJK;AAKnBC,IAAAA,MAAM,EAAE,kBALW;AAMnBC,IAAAA,OAAO,EAAE,MANU;AAOnBC,IAAAA,aAAa,EAAE;AAPI,GAAD,CAJA;AAapBC,EAAAA,OAAO,eAAEX,GAAG,CAAC;AACXS,IAAAA,OAAO,EAAE,MADE;AAEXG,IAAAA,UAAU,EAAE,QAFD;AAGXC,IAAAA,cAAc,EAAE,QAHL;AAIXC,IAAAA,SAAS,EAAE,CAJA;AAKXC,IAAAA,MAAM,EAAE,SALG;AAMXC,IAAAA,OAAO,EAAE,CANE;AAOXC,IAAAA,MAAM,EAAE,CAPG;AAQXC,IAAAA,OAAO,EAAE,MARE;AASXC,IAAAA,MAAM,eAAab,MAAM,CAACc,OATf;AAUXC,IAAAA,UAAU,EAAE,MAVD;AAWXC,IAAAA,eAAe,EAAE,KAXN;AAaX,uBAAmB;AACjBC,MAAAA,oBAAoB,EAAEjB,MAAM,CAACkB;AADZ,KAbR;AAiBX,sBAAkB;AAChBC,MAAAA,cAAc,EAAE,KADA;AAEhBC,MAAAA,uBAAuB,EAAEpB,MAAM,CAACkB;AAFhB,KAjBP;AAsBXG,IAAAA,GAAG,EAAE;AACHC,MAAAA,IAAI,EAAEtB,MAAM,CAACuB;AADV,KAtBM;AA0BX,gBAAY;AACVR,MAAAA,UAAU,EAAEf,MAAM,CAACc;AADT;AA1BD,GAAD,CAbQ;AA2CpBU,EAAAA,KAAK,eAAE9B,GAAG,CAAC;AACT+B,IAAAA,YAAY,EAAEzB,MAAM,CAAC0B;AADZ,GAAD;AA3CU,CAAf;;SCHSC,YAAYC,OAAeC;AACzC,MAAIC,MAAM,CAACC,KAAP,CAAa,CAACH,KAAd,CAAJ,EAA0B;AACxB;AACD;;AAED,SAAOC,IAAI,KAAK,SAAT,GAAqBG,QAAQ,CAACJ,KAAD,EAAQ,EAAR,CAA7B,GAA2CK,UAAU,CAACL,KAAD,CAA5D;AACD;AAED,IAAMM,WAAW,GAAG,wCAApB;AACA,IAAMC,SAAS,GAAG,iBAAlB;AAEA,SAAgBC,wBAAwBR,OAAeC;AACrD,MAAMQ,KAAK,GAAGR,IAAI,KAAK,SAAT,GAAqBM,SAArB,GAAiCD,WAA/C;AAEA,SAAOG,KAAK,CAACC,IAAN,CAAWV,KAAX,CAAP;AACD;;ACXM,IAAMW,iBAAiB,GAAG,SAApBA,iBAAoB,CAACC,KAAD;AAC/B,MAAMC,WAAW,GAAGD,KAAK,CAACC,WAAN,IAAqB,EAAzC;AACA,MAAMC,MAAM,GAAGD,WAAW,CAACE,IAAZ,CAAiB,UAACC,UAAD;AAAA,WAAiBA,UAAkB,CAACC,KAApC;AAAA,GAAjB,CAAf;AAGA,SAAOH,MAAM,GAAGA,MAAM,CAACG,KAAV,GAAkB,EAA/B;AACD,CANM;AAQP,AAAO,IAAMC,aAAa,GAAG,SAAhBA,aAAgB,CAAClB,KAAD;AAC3B,SAAOA,KAAK,KAAKmB,SAAV,GAAsB,EAAtB,GAA2BC,MAAM,CAACpB,KAAD,CAAxC;AACD,CAFM;AAIP,AAAO,IAAMqB,aAAa,GAAG,SAAhBA,aAAgB,CAACC,MAAD;;;AAC3B,4DAAOA,MAAM,CAACC,QAAP,GAAkBC,KAAlB,CAAwB,GAAxB,EAA6B,CAA7B,CAAP,qBAAO,uBAAiCC,MAAxC,oCAAkD,CAAlD;AACD,CAFM;;ACiBP,IAAKC,cAAL;;AAAA,WAAKA;AACHA,EAAAA,2BAAA,cAAA;AACAA,EAAAA,2BAAA,cAAA;AACD,CAHD,EAAKA,cAAc,KAAdA,cAAc,KAAA,CAAnB;;AAKA,IAAMC,WAAW,GAAG,CAApB;;AAEA,SAASC,iBAAT;MACEC,gBAAAA;MACAC,cAAAA;MACAlB,aAAAA;MACAmB,gBAAAA;MACOC,gBAAPhC;;AAEA,wBAAoCiC,QAAA,CAAef,aAAa,CAACc,QAAD,CAA5B,CAApC;AAAA,MAAOE,UAAP;AAAA,MAAmBC,aAAnB;;AACA,MAAMlB,KAAK,GAAGN,iBAAiB,CAACC,KAAD,CAA/B;AACA,MAAMwB,QAAQ,GAAGH,MAAA,CAA+B,IAA/B,CAAjB;AAEAA,EAAAA,SAAA,CAAgB;AACd,QAAMI,cAAc,GAAGnB,aAAa,CAACc,QAAD,CAApC;;AAEA,QAAIK,cAAc,KAAKH,UAAvB,EAAmC;AACjCC,MAAAA,aAAa,CAACE,cAAD,CAAb;AACD;;AAEF,GAPD,EAOG,CAACL,QAAD,CAPH;;AASA,MAAMM,mBAAmB,GAAG,SAAtBA,mBAAsB,CAACtC,KAAD;AAC1B,QAAIgC,QAAQ,KAAKhC,KAAjB,EAAwB;AACtB+B,MAAAA,QAAQ,CAAC/B,KAAD,CAAR;AACD;AACF,GAJD;;AAMA,MAAMuC,iBAAiB,GAAG,SAApBA,iBAAoB,CAACtC,IAAD;AACxB,QAAMuC,YAAY,GAAGtC,MAAM,CAACC,KAAP,CAAa,CAAC+B,UAAd,IAA4B,CAA5B,GAAgC,CAACA,UAAtD;AACA,QAAIO,SAAS,GACXxC,IAAI,KAAKyB,cAAc,CAACgB,SAAxB,GAAoCF,YAAY,GAAGb,WAAnD,GAAiEa,YAAY,GAAGb,WADlF;AAGA;AACA;;AACAc,IAAAA,SAAS,GAAG,CAACA,SAAS,CAACE,OAAV,CAAkBtB,aAAa,CAACmB,YAAD,CAA/B,CAAb;AAEAL,IAAAA,aAAa,CAACjB,aAAa,CAACuB,SAAD,CAAd,CAAb;AACAV,IAAAA,QAAQ,CAACU,SAAD,CAAR;AACD,GAXD;;;AAcA,MAAMG,wBAAwB,GAAiD,SAAzEA,wBAAyE,CAACC,KAAD;;;AAC7EA,IAAAA,KAAK,CAACC,cAAN;AACA,yBAAAV,QAAQ,CAACW,OAAT,uCAAkBC,KAAlB;AACD,GAHD;;AAKA,MAAMC,aAAa,GAAG,SAAhBA,aAAgB,CAACJ,KAAD;AACpB,QAAMK,UAAU,GAEZ;AACFC,MAAAA,OAAO,EAAE;AAAA,eAAMZ,iBAAiB,CAACb,cAAc,CAACgB,SAAhB,CAAvB;AAAA,OADP;AAEFU,MAAAA,SAAS,EAAE;AAAA,eAAMb,iBAAiB,CAACb,cAAc,CAAC2B,SAAhB,CAAvB;AAAA;AAFT,KAFJ;AAOA,QAAMC,EAAE,GAAGJ,UAAU,CAACL,KAAK,CAACU,GAAP,CAArB;;AACA,QAAID,EAAJ,EAAQ;AACNT,MAAAA,KAAK,CAACC,cAAN;AACAQ,MAAAA,EAAE;AACH;AACF,GAbD;;AAeA,MAAME,iBAAiB,GAA+C,SAAhEA,iBAAgE,CAACC,CAAD;AACpE,QAAMzD,KAAK,GAAGyD,CAAC,CAACC,MAAF,CAAS1D,KAAvB;;AACA,QAAI,CAACA,KAAL,EAAY;AACVmC,MAAAA,aAAa,CAACnC,KAAD,CAAb;AACAsC,MAAAA,mBAAmB,CAACnB,SAAD,CAAnB;AACA;AACD;;AAED,QAAI,CAACX,uBAAuB,CAACR,KAAD,EAAQY,KAAK,CAACX,IAAd,CAA5B,EAAiD;AAC/C;AACD;;AAEDkC,IAAAA,aAAa,CAACnC,KAAD,CAAb;AAEA,QAAM2D,YAAY,GAAG5D,WAAW,CAACC,KAAD,EAAQY,KAAK,CAACX,IAAd,CAAhC;AACAW,IAAAA,KAAK,CAACgD,UAAN,CAAiBD,YAAY,KAAKxC,SAAlC;;AACA,QAAIwC,YAAY,KAAKxC,SAArB,EAAgC;AAC9BmB,MAAAA,mBAAmB,CAACqB,YAAD,CAAnB;AACD;AACF,GAnBD;;AAqBA,SACE1B,aAAA,MAAA;oBAAkB;AAAgB4B,IAAAA,SAAS,EAAEjG,MAAM,CAACC;GAApD,EACEoE,aAAA,CAAC6B,SAAD;AAEE;AACA;AAHF;AACE;AACA;AACA;AACA7D,IAAAA,IAAI,EAAC;AACL8D,IAAAA,MAAM,EAAC;AACPF,IAAAA,SAAS,EAAEjG,MAAM,CAACgC;AAClBoE,IAAAA,GAAG,EAAE/C,KAAK,CAAC+C;AACXC,IAAAA,GAAG,EAAEhD,KAAK,CAACgD;AACXC,IAAAA,IAAI,EAAEvC;AACNwC,IAAAA,UAAU,EAAEvD,KAAK,CAACwD;AAClBC,IAAAA,SAAS,EAAEvC,MAAM,CAACL,MAAP,GAAgB;AAC3B6C,IAAAA,UAAU,EAAEzC;AACZ7B,IAAAA,KAAK,EAAEkC;AACPqC,IAAAA,GAAG,EAAEnC;AACLoC,IAAAA,QAAQ,EAAEhB;AACViB,IAAAA,SAAS,EAAExB;AACX;AACA;AACAyB,IAAAA,IAAI,EAAC;qBACU1C,mBAAAA,WAAY;sBACXE;qBACDjB,KAAK,CAAC+C;qBACN/C,KAAK,CAACgD;GAvBvB,CADF,EA8BG,CAACpC,QAAD,IACCI,aAAA,MAAA;AAAK4B,IAAAA,SAAS,EAAEjG,MAAM,CAACI;mBAA6B;GAApD,EACEiE,aAAA,SAAA;AACE0C,IAAAA,QAAQ,EAAE,CAAC;AACXd,IAAAA,SAAS,EAAEjG,MAAM,CAACa;AAClBmG,IAAAA,OAAO,EAAE;AAAA,aAAMrC,iBAAiB,CAACb,cAAc,CAACgB,SAAhB,CAAvB;AAAA;AACTmC,IAAAA,aAAa,EAAEjC;GAJjB,EAKEX,aAAA,CAAC6C,kBAAD;AAAoBC,IAAAA,IAAI,EAAC;GAAzB,CALF,CADF,EAQE9C,aAAA,SAAA;AACE0C,IAAAA,QAAQ,EAAE,CAAC;AACXd,IAAAA,SAAS,EAAEjG,MAAM,CAACa;AAClBmG,IAAAA,OAAO,EAAE;AAAA,aAAMrC,iBAAiB,CAACb,cAAc,CAAC2B,SAAhB,CAAvB;AAAA;AACTwB,IAAAA,aAAa,EAAEjC;GAJjB,EAKEX,aAAA,CAAC+C,oBAAD;AAAsBD,IAAAA,IAAI,EAAC;GAA3B,CALF,CARF,CA/BJ,CADF;AAmDD;;AAED,SAAgBE,aAAaC;AAC3B,MAAQtE,KAAR,GAAkBsE,KAAlB,CAAQtE,KAAR;AAEA,SACEqB,aAAA,CAACkD,cAAD;AAAwBvE,IAAAA,KAAK,EAAEA;AAAOwE,IAAAA,mBAAmB,EAAEF,KAAK,CAACE;GAAjE,EACG;AAAA,QACCpF,KADD,SACCA,KADD;AAAA,QAEC8B,MAFD,SAECA,MAFD;AAAA,QAGCD,QAHD,SAGCA,QAHD;AAAA,QAICE,QAJD,SAICA,QAJD;AAAA,WAMCE,aAAA,CAACL,iBAAD;AACEC,MAAAA,QAAQ,EAAEA;AACVC,MAAAA,MAAM,EAAEA;AACRlB,MAAAA,KAAK,EAAEA;AACPmB,MAAAA,QAAQ,EAAEA;AACV/B,MAAAA,KAAK,EAAEA;KALT,CAND;AAAA,GADH,CADF;AAkBD;AAEDiF,YAAY,CAACI,YAAb,GAA4B;AAC1BD,EAAAA,mBAAmB,EAAE;AADK,CAA5B;;;;"}
@@ -1,4 +1,2 @@
1
- export declare function parseNumber(value: string, type: string): {
2
- isValid: boolean;
3
- value: number | undefined;
4
- };
1
+ export declare function parseNumber(value: string, type: string): number | undefined;
2
+ export declare function isNumberInputValueValid(value: string, type: string): boolean;
@@ -0,0 +1,9 @@
1
+ import { FieldAPI } from '@contentful/field-editor-shared';
2
+ declare type RangeValidation = {
3
+ min?: number;
4
+ max?: number;
5
+ };
6
+ export declare const getRangeFromField: (field: FieldAPI) => RangeValidation;
7
+ export declare const valueToString: (value: number | null | undefined) => string;
8
+ export declare const countDecimals: (number: number) => number;
9
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/field-editor-number",
3
- "version": "1.1.5",
3
+ "version": "1.1.7-beta.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/field-editor-number.esm.js",
6
6
  "typings": "dist/index.d.ts",
@@ -24,12 +24,10 @@
24
24
  "@contentful/f36-components": "^4.0.27",
25
25
  "@contentful/f36-tokens": "^4.0.0",
26
26
  "@contentful/field-editor-shared": "^1.1.3",
27
- "emotion": "^10.0.17",
28
- "lodash": "^4.17.15",
29
- "lodash-es": "^4.17.15"
27
+ "emotion": "^10.0.17"
30
28
  },
31
29
  "devDependencies": {
32
- "@contentful/field-editor-test-utils": "^1.2.0",
30
+ "@contentful/field-editor-test-utils": "^1.2.2",
33
31
  "@testing-library/jest-dom": "^5.12.0",
34
32
  "@testing-library/react": "^11.2.6",
35
33
  "@testing-library/user-event": "^13.1.9"
@@ -46,6 +44,5 @@
46
44
  "diagnostics": false
47
45
  }
48
46
  }
49
- },
50
- "gitHead": "6cee7fea6ac7d0f8cf1c6689ad8c95fc5814d85e"
47
+ }
51
48
  }
package/CHANGELOG.md DELETED
@@ -1,166 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
-
6
- ## [1.1.5](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.1.4...@contentful/field-editor-number@1.1.5) (2022-07-11)
7
-
8
- **Note:** Version bump only for package @contentful/field-editor-number
9
-
10
- ## [1.1.4](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.1.3...@contentful/field-editor-number@1.1.4) (2022-06-22)
11
-
12
- **Note:** Version bump only for package @contentful/field-editor-number
13
-
14
- ## [1.1.3](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.1.2...@contentful/field-editor-number@1.1.3) (2022-04-20)
15
-
16
- ### Bug Fixes
17
-
18
- - change input type to number ([#1112](https://github.com/contentful/field-editors/issues/1112)) ([e759e6d](https://github.com/contentful/field-editors/commit/e759e6d939a22cdabbd21e283e364c490a87d94f))
19
-
20
- ## [1.1.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.1.1...@contentful/field-editor-number@1.1.2) (2022-02-15)
21
-
22
- ### Bug Fixes
23
-
24
- - bump f36 packages ([#1025](https://github.com/contentful/field-editors/issues/1025)) ([ec37a40](https://github.com/contentful/field-editors/commit/ec37a4000db7cd75c66dd9621136b2272c9feeea))
25
-
26
- ## [1.1.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.1.0...@contentful/field-editor-number@1.1.1) (2022-02-14)
27
-
28
- ### Bug Fixes
29
-
30
- - **number-editor:** use "text" input that syncs numeric value when valid ([#1020](https://github.com/contentful/field-editors/issues/1020)) ([0f38462](https://github.com/contentful/field-editors/commit/0f384623f969284d122255191397402bbb3e7763))
31
-
32
- # [1.1.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.0.3...@contentful/field-editor-number@1.1.0) (2022-01-11)
33
-
34
- ### Features
35
-
36
- - bump f36 packages to stable v4 [BAU-521] ([#988](https://github.com/contentful/field-editors/issues/988)) ([419cf56](https://github.com/contentful/field-editors/commit/419cf56692179b074fcfa2743469d5265ed98429))
37
-
38
- ## [1.0.3](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.0.2...@contentful/field-editor-number@1.0.3) (2021-12-23)
39
-
40
- ### Bug Fixes
41
-
42
- - markdown buttons ([#968](https://github.com/contentful/field-editors/issues/968)) ([9803b98](https://github.com/contentful/field-editors/commit/9803b98c25d92df6148686ffe2749a77f7efdbb9))
43
-
44
- ## [1.0.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.0.1...@contentful/field-editor-number@1.0.2) (2021-12-20)
45
-
46
- **Note:** Version bump only for package @contentful/field-editor-number
47
-
48
- ## [1.0.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@1.0.0...@contentful/field-editor-number@1.0.1) (2021-11-17)
49
-
50
- ### Bug Fixes
51
-
52
- - **card-actions:** update forma 36 to fix card actions click issue ([#927](https://github.com/contentful/field-editors/issues/927)) ([3dfdef2](https://github.com/contentful/field-editors/commit/3dfdef2c2b0045f12ea94ddafca89a8e9f25e7d0))
53
-
54
- # [1.0.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.14.0...@contentful/field-editor-number@1.0.0) (2021-11-04)
55
-
56
- **Note:** Version bump only for package @contentful/field-editor-number
57
-
58
- # [0.14.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.6...@contentful/field-editor-number@0.14.0) (2021-11-04)
59
-
60
- ### Features
61
-
62
- - Forma v4 components adoption ([#805](https://github.com/contentful/field-editors/issues/805)) ([526bde6](https://github.com/contentful/field-editors/commit/526bde6e10e0ee3789705ec10fb31489af7ca59e))
63
-
64
- ### BREAKING CHANGES
65
-
66
- - adopts a new Forma v4 beta
67
-
68
- ## [0.13.6](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.5...@contentful/field-editor-number@0.13.6) (2021-10-14)
69
-
70
- **Note:** Version bump only for package @contentful/field-editor-number
71
-
72
- ## [0.13.5](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.4...@contentful/field-editor-number@0.13.5) (2021-10-06)
73
-
74
- **Note:** Version bump only for package @contentful/field-editor-number
75
-
76
- ## [0.13.4](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.3...@contentful/field-editor-number@0.13.4) (2021-09-17)
77
-
78
- **Note:** Version bump only for package @contentful/field-editor-number
79
-
80
- ## [0.13.3](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.2...@contentful/field-editor-number@0.13.3) (2021-09-16)
81
-
82
- **Note:** Version bump only for package @contentful/field-editor-number
83
-
84
- ## [0.13.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.1...@contentful/field-editor-number@0.13.2) (2021-08-19)
85
-
86
- **Note:** Version bump only for package @contentful/field-editor-number
87
-
88
- ## [0.13.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.13.0...@contentful/field-editor-number@0.13.1) (2021-07-29)
89
-
90
- **Note:** Version bump only for package @contentful/field-editor-number
91
-
92
- # [0.13.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.6...@contentful/field-editor-number@0.13.0) (2021-07-23)
93
-
94
- ### Features
95
-
96
- - 💡 new color tokens ([#778](https://github.com/contentful/field-editors/issues/778)) ([fba548d](https://github.com/contentful/field-editors/commit/fba548de32305016df7f2685634eefb14294828f))
97
-
98
- ## [0.12.6](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.3...@contentful/field-editor-number@0.12.6) (2021-07-06)
99
-
100
- **Note:** Version bump only for package @contentful/field-editor-number
101
-
102
- ## [0.12.5](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.3...@contentful/field-editor-number@0.12.5) (2021-07-06)
103
-
104
- **Note:** Version bump only for package @contentful/field-editor-number
105
-
106
- ## [0.12.4](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.3...@contentful/field-editor-number@0.12.4) (2021-06-23)
107
-
108
- **Note:** Version bump only for package @contentful/field-editor-number
109
-
110
- ## [0.12.3](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.2...@contentful/field-editor-number@0.12.3) (2021-06-23)
111
-
112
- **Note:** Version bump only for package @contentful/field-editor-number
113
-
114
- ## [0.12.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.1...@contentful/field-editor-number@0.12.2) (2021-06-22)
115
-
116
- **Note:** Version bump only for package @contentful/field-editor-number
117
-
118
- ## [0.12.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.12.0...@contentful/field-editor-number@0.12.1) (2021-03-05)
119
-
120
- **Note:** Version bump only for package @contentful/field-editor-number
121
-
122
- # [0.12.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.11.2...@contentful/field-editor-number@0.12.0) (2021-02-19)
123
-
124
- ### Features
125
-
126
- - bump min version of forma-36 ([#606](https://github.com/contentful/field-editors/issues/606)) ([fd57c7a](https://github.com/contentful/field-editors/commit/fd57c7a4312766af38c01507f17706ab22992617))
127
-
128
- ## [0.11.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.11.1...@contentful/field-editor-number@0.11.2) (2021-02-09)
129
-
130
- **Note:** Version bump only for package @contentful/field-editor-number
131
-
132
- ## [0.11.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.11.0...@contentful/field-editor-number@0.11.1) (2021-02-01)
133
-
134
- **Note:** Version bump only for package @contentful/field-editor-number
135
-
136
- # [0.11.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.10.0...@contentful/field-editor-number@0.11.0) (2021-01-20)
137
-
138
- ### Features
139
-
140
- - update minimal forma-36 versions to use updated design ([#565](https://github.com/contentful/field-editors/issues/565)) ([332c734](https://github.com/contentful/field-editors/commit/332c734bfaf54f0e9773fcbb460d743b1f5459ec))
141
-
142
- # [0.10.0](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.5...@contentful/field-editor-number@0.10.0) (2021-01-12)
143
-
144
- ### Features
145
-
146
- - update minimal required Forma version to the 3.73.12 ([#552](https://github.com/contentful/field-editors/issues/552)) ([2816fd9](https://github.com/contentful/field-editors/commit/2816fd960c28815faebf49a9ef8f4c4c0d91fc36))
147
-
148
- ## [0.9.5](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.4...@contentful/field-editor-number@0.9.5) (2020-12-16)
149
-
150
- **Note:** Version bump only for package @contentful/field-editor-number
151
-
152
- ## [0.9.4](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.3...@contentful/field-editor-number@0.9.4) (2020-11-06)
153
-
154
- **Note:** Version bump only for package @contentful/field-editor-number
155
-
156
- ## [0.9.3](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.2...@contentful/field-editor-number@0.9.3) (2020-11-06)
157
-
158
- **Note:** Version bump only for package @contentful/field-editor-number
159
-
160
- ## [0.9.2](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.1...@contentful/field-editor-number@0.9.2) (2020-10-28)
161
-
162
- **Note:** Version bump only for package @contentful/field-editor-number
163
-
164
- ## [0.9.1](https://github.com/contentful/field-editors/compare/@contentful/field-editor-number@0.9.0...@contentful/field-editor-number@0.9.1) (2020-08-24)
165
-
166
- **Note:** Version bump only for package @contentful/field-editor-number
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2020 Contentful
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.