@synerise/ds-input-number 1.2.48 → 1.2.50

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.2.50](https://github.com/Synerise/synerise-design/compare/@synerise/ds-input-number@1.2.49...@synerise/ds-input-number@1.2.50) (2026-07-23)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-input-number
9
+
10
+ ## [1.2.49](https://github.com/Synerise/synerise-design/compare/@synerise/ds-input-number@1.2.48...@synerise/ds-input-number@1.2.49) (2026-07-09)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-input-number
13
+
6
14
  ## [1.2.48](https://github.com/Synerise/synerise-design/compare/@synerise/ds-input-number@1.2.47...@synerise/ds-input-number@1.2.48) (2026-06-17)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-input-number
package/CLAUDE.md ADDED
@@ -0,0 +1,149 @@
1
+ # InputNumber (`@synerise/ds-input-number`)
2
+
3
+ > Locale-aware numeric input, **DS-native** (no Ant Design). Renders a styled `<input>` with custom
4
+ > up/down steppers, form-field layout (label, tooltip, description, error text), autosize support,
5
+ > and locale-driven thousand/decimal formatting.
6
+
7
+ ## Package structure
8
+
9
+ ```
10
+ src/
11
+ InputNumber.tsx — main component (value flow, steppers, autosize wiring)
12
+ InputNumber.types.ts — InputNumberProps (explicit, hand-written), deprecated Props alias
13
+ InputNumber.styles.tsx — DS-native styled-components (InputNumberRoot, HandlerWrap, HandlerUp/Down, InputField, Addon, …)
14
+ index.ts — public exports
15
+ hooks/
16
+ useStepper.ts — float-safe increment/decrement, clamp, precision, press-and-hold + Shift×10
17
+ constants/
18
+ inputNumber.constants.ts — MAXIMUM_FRACTION_DIGITS (20), MAXIMUM_NUMBER_DIGITS (15), NUMBER_DELIMITER ('.'), ANGLE_UP_SVG / ANGLE_DOWN_SVG (stepper glyphs)
19
+ utils/
20
+ inputNumber.utils.ts — formatNumber, parseFormattedNumber helpers
21
+ __specs__/
22
+ InputNumber.spec.tsx — Vitest tests (component + steppers + value flow)
23
+ inputNumber.utils.spec.tsx — Vitest tests (utils)
24
+ ```
25
+
26
+ There is **no `style/` LESS** — all styling is in `InputNumber.styles.tsx`.
27
+
28
+ ## Public exports
29
+
30
+ ### `InputNumber` (default export)
31
+
32
+ Functional component. Not a forwardRef — no ref forwarding.
33
+
34
+ Props = explicit `InputNumberOwnProps` + `FormFieldCommonProps` + `PassthroughAttributes` (`data-*`/`aria-*`)
35
+ + standard input HTML attributes via `Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange' | 'onBlur' | 'size' | 'min' | 'max' | 'type'>` (antd-parity forwarding of `maxLength`, `onFocus`, `tabIndex`, `inputMode`, `onKeyDown`, …). The omitted keys are owned explicitly by the component (different types/behavior). Two have special wiring: `onKeyDown` and `onKeyPress` are composed with the internal arrow-stepping / numeric-filter handlers (consumer called first), and `inputMode` defaults to `'decimal'` but a consumer value overrides it:
36
+
37
+ | Prop | Type | Default | Description |
38
+ |------|------|---------|-------------|
39
+ | `value` | `number \| null` | `undefined` | Controlled value |
40
+ | `defaultValue` | `number \| null` | `undefined` | Uncontrolled initial value |
41
+ | `onChange` | `(value: number \| null) => void` | `undefined` | Called with parsed numeric value on every change |
42
+ | `onBlur` | `(event: FocusEvent<HTMLInputElement>) => void` | `undefined` | Native input blur, invoked **after** the on-blur min/max re-align (consumers read `event.target.value`) |
43
+ | `onKeyPress` | `(event: KeyboardEvent<HTMLInputElement>) => void` | `undefined` | Forwarded the input's `keypress` event (fired before the internal numeric-key filter) |
44
+ | `onStep` | `(value: number, info: { offset: number; type: 'up' \| 'down' }) => void` | `undefined` | Fires on a stepper interaction (up/down button or ArrowUp/ArrowDown). antd parity — fires alongside `onChange`; `offset` is the applied step magnitude (incl. Shift×10) |
45
+ | `min` / `max` | `number` | `undefined` | Stepper bounds (clamped on step) |
46
+ | `step` | `number \| string` | `1` | Stepper increment |
47
+ | `disabled` / `readOnly` | `boolean` | `undefined` | Disable input; steppers are not rendered |
48
+ | `autoFocus` | `boolean` | `undefined` | Focus the input on mount |
49
+ | `tabIndex` | `number` | `undefined` | Forwarded to the underlying `<input>` (focus order) |
50
+ | `placeholder` | `string` | `undefined` | Input placeholder |
51
+ | `size` | `'small' \| 'middle' \| 'large'` | `undefined` | `'large'` → 48px tall |
52
+ | `error` | `boolean` | `undefined` | Triggers error state (red inset border + background) |
53
+ | `errorText` | `ReactNode` | `undefined` | Error message shown below input; also triggers error state |
54
+ | `label` / `description` / `tooltip` / `tooltipConfig` | from `FormFieldCommonProps` | `undefined` | Form-field layout |
55
+ | `prefixel` / `suffixel` | `ReactNode` | `undefined` | Content in the left / right addon slot |
56
+ | `raw` | `boolean` | `undefined` | When `true`, renders bare input without FormField wrapper |
57
+ | `valueFormatOptions` | `NumberToFormatOptions` | `undefined` | Override formatting options (e.g. `{ maximumFractionDigits: 2 }`) |
58
+ | `autoResize` | `AutoResizeProp` | `undefined` | Enable autosize: `true` or `{ minWidth, maxWidth?, stretchToFit? }` |
59
+ | `autoResizeProps` | `Partial<Pick<AutosizeInputProps, 'placeholderIsMinWidth' \| 'wrapperClassName' \| 'wrapperStyle' \| 'extraWidth'>>` | `undefined` | Extra props passed to `AutosizeWrapper` |
60
+ | `style` / `className` | `CSSProperties` / `string` | `undefined` | Applied to the inner `InputNumberWrapper` |
61
+
62
+ > **Removed in the antd-removal migration (STOR-2334) — BREAKING.** The antd-only props are gone:
63
+ > `formatter`, `parser` (formatting is always locale-driven via `useDataFormat` + `valueFormatOptions`),
64
+ > `decimalSeparator` (superseded by the locale `decimalDelimiter`), `controls`, `keyboard`, `stringMode`,
65
+ > `bordered`, `status`, `prefix`, `onPressEnter`, and `addonBefore`/`addonAfter` (use `prefixel`/`suffixel`).
66
+ > Also removed: `precision` — the stepped value is now rounded to
67
+ > `max(decimals(step), valueFormatOptions?.maximumFractionDigits ?? 0)`, so express decimal precision
68
+ > via `step` and/or `valueFormatOptions` instead.
69
+
70
+ ### `InputNumberProps` (type export)
71
+
72
+ The full props type. `Props` is also exported from the types file as a `@deprecated` alias but is **not**
73
+ re-exported from `index.ts`.
74
+
75
+ ## Usage patterns
76
+
77
+ ```tsx
78
+ import InputNumber from '@synerise/ds-input-number';
79
+
80
+ // Basic with form field
81
+ <InputNumber label="Quantity" min={1} max={100} defaultValue={10} onChange={(v) => console.log(v)} />
82
+
83
+ // Error state
84
+ <InputNumber label="Price" error errorText="Must be positive" value={price} onChange={setPrice} />
85
+
86
+ // With prefix/suffix
87
+ <InputNumber prefixel="$" suffixel="USD" min={0} step={0.01} />
88
+
89
+ // Raw (no form field wrapper)
90
+ <InputNumber raw value={qty} onChange={setQty} />
91
+
92
+ // Autosize
93
+ <InputNumber autoResize={{ minWidth: '60px', maxWidth: '200px', stretchToFit: true }} value={val} onChange={setVal} />
94
+ ```
95
+
96
+ ## Styling
97
+
98
+ All styling lives in `InputNumber.styles.tsx` as DS-native styled-components driven by transient
99
+ `$`-props and `:focus-within` (no antd LESS, no `.ant-*`/`.ds-*` styling selectors). The
100
+ `ant-input-number-*` and `ds-input-number-*` class names are kept on the elements as hooks only
101
+ (ui-tests / interim external CSS). Theme tokens used: `grey-700` (text), `grey-300` (idle border),
102
+ `blue-600` (focus border), `red-600`/`red-050` (error), `grey-500` (placeholder), `grey-050` (addon).
103
+
104
+ > **Deep import**: `InputNumber.styles.tsx` imports `autoresizeConfObjToCss` from
105
+ > `@synerise/ds-input/dist/Input.styles` — a fragile internal path (pre-existing; flagged as an
106
+ > eslint warning until ds-input exposes it from the root).
107
+
108
+ ## Key dependencies
109
+
110
+ - `@synerise/ds-form-field` — `FormField` wrapper providing label, tooltip, description, error layout
111
+ - `@synerise/ds-input` — `AutosizeWrapper` + `AutoResizeProp` type
112
+ - `@synerise/ds-core` — `useDataFormat` hook (locale-aware number formatting), `NumberToFormatOptions`, `ThemeProps`
113
+ - `@synerise/ds-utils` — `useResizeObserver` (for `stretchToFit` autosize) + `PassthroughAttributes` type
114
+ - `uuid` — generates stable `id` for label `htmlFor` association
115
+
116
+ ## Implementation notes
117
+
118
+ - **Steppers (`useStepper`)**: float-safe arithmetic (integer scaling, so `0.1 + 0.2 === 0.3`), clamp to
119
+ `min`/`max`, then round to `max(decimals(step), valueFormatOptions?.maximumFractionDigits ?? 0)` — so a
120
+ fractional `step` (e.g. `0.01`) keeps its decimals without any extra config, and `maximumFractionDigits`
121
+ raises the rounding floor. Mouse press-and-hold auto-repeats (600ms delay → 200ms interval);
122
+ `Shift` multiplies the step by ten; ArrowUp/ArrowDown step from the keyboard (a consumer `onKeyDown`
123
+ is invoked first, then the stepping runs). Steppers are not rendered when `disabled` or `readOnly`.
124
+ - **Value flow**: a controlled `<input type="text">`. Typed input is parsed locale→number string first
125
+ (`parseFormattedNumber`) then re-formatted for display (`formatNumber`) — preserving a lone `-`, a
126
+ trailing decimal and trailing zeros while typing. `onChange` always emits `number | null` (emits `0`,
127
+ not `null`). `aria-valuenow` carries the raw numeric value.
128
+ - **On-blur min/max re-align (antd parity)**: typing is never clamped (rc-input-number's
129
+ `userTyping=true` path), so a value can exceed `min`/`max` mid-edit. On blur, an out-of-range value is
130
+ re-aligned to the nearest bound (`> max ⇒ max`, `< min ⇒ min`) — mirroring rc-input-number's
131
+ `flushInputValue(false)` → `triggerValueUpdate(…, userTyping=false)` → `getRangeValue`. When it clamps
132
+ it updates the display and fires `onChange(clamped)`; an in-range value is left untouched (no
133
+ `onChange`). Empty / `NaN` values are not clamped. Steppers clamp independently in `useStepper`.
134
+ - **Numeric key filter**: lives on the input's `onKeyPress` — the native `keypress` event does not fire
135
+ for Backspace / arrows / Delete, so editing keys are never blocked. A consumer-supplied `onKeyPress`
136
+ prop is invoked first (so it can inspect / `preventDefault`), then the numeric filter runs.
137
+ - **Locale-aware formatting**: `useDataFormat()` returns `formatValue`, `thousandDelimiter`,
138
+ `decimalDelimiter` based on the active `DataFormatNotationType` (`'EU'` or `'US'`).
139
+ - **Controlled/uncontrolled hybrid**: keeps `localValue`/`displayValue` state; `value` prop changes sync
140
+ via `useEffect` (only when `value !== localValue`).
141
+ - **Error state**: `error={true}` or a non-empty `errorText` adds the `error` class to the root and the
142
+ red inset border + `red-050` background via `$error`.
143
+ - **`raw` mode**: skips the `FormField` wrapper — `label`/`description`/`errorText`/`tooltip` have no effect.
144
+ - **`autoResize` + `stretchToFit`**: `useResizeObserver` on a wrapper ref dynamically sets `max-width`;
145
+ `AUTOSIZE_EXTRA_WIDTH = 45` accommodates the stepper buttons.
146
+ - **Maximum digits**: `parseFormattedNumber` truncates to `MAXIMUM_NUMBER_DIGITS = 15` to avoid
147
+ floating-point precision issues near `Number.MAX_SAFE_INTEGER`.
148
+ - **Uses Vitest** for testing.
149
+ ```
package/README.md CHANGED
@@ -24,12 +24,13 @@ Input-Number UI Component
24
24
  | label | Input label | ReactNode | - |
25
25
  | max | Max value | number | Infinity |
26
26
  | min | Min value | number | -Infinity |
27
+ | onBlur | Native input blur handler (consumers read `event.target.value`) | (e: FocusEvent<HTMLInputElement>) => void | - |
27
28
  | onChange | Called with the parsed numeric value on change. Returns `null` when input is cleared. | (value: number \| null) => void | - |
28
- | onPressEnter | Callback triggered when Enter key is pressed | (e: Event) => void | - |
29
29
  | precision | Precision of input value | number | - |
30
30
  | prefixel | ReactNode to render in the left addon slot | ReactNode | - |
31
31
  | raw | Render bare input without FormField wrapper (no label / tooltip / description / errorText) | boolean | - |
32
- | size | Height of input box | string | - |
32
+ | readOnly | Read-only input (steppers are not rendered) | boolean | `false` |
33
+ | size | Height of input box (`'large'` → 48px) | 'small' \| 'middle' \| 'large' | - |
33
34
  | step | The number by which the current value is increased or decreased | number \| string | 1 |
34
35
  | suffixel | ReactNode to render in the right addon slot | ReactNode | - |
35
36
  | tooltip | Tooltip content shown next to the label | ReactNode | - |
@@ -39,11 +40,7 @@ Input-Number UI Component
39
40
  | autoResize | Enable autosize: `true` or `{ minWidth, maxWidth?, stretchToFit? }` | boolean \| object | - |
40
41
  | autoResizeProps | Extra props for the autosize wrapper (`placeholderIsMinWidth`, `wrapperClassName`, `wrapperStyle`, `extraWidth`) | object | - |
41
42
 
42
- > **Note**: `formatter` and `parser` props from Ant Design cannot be customised they are always overridden internally by the locale-aware formatting logic.
43
-
44
- ### Methods
45
-
46
- | Name | Description |
47
- | ------- | ------------ |
48
- | blur() | remove focus |
49
- | focus() | get focus |
43
+ > **Note**: this component is DS-native (no Ant Design). The former antd-only props were removed
44
+ > `formatter`/`parser`/`decimalSeparator` (formatting is always locale-driven via `valueFormatOptions`),
45
+ > `controls`, `keyboard`, `stringMode`, `bordered`, `status`, `prefix`, `onPressEnter`, and
46
+ > `addonBefore`/`addonAfter` (use `prefixel`/`suffixel`).
@@ -1,4 +1,4 @@
1
1
  import { default as React } from 'react';
2
2
  import { InputNumberProps } from './InputNumber.types';
3
- declare const InputNumber: ({ label, description, errorText, raw, error, prefixel, suffixel, style, tooltip, tooltipConfig, value, defaultValue, valueFormatOptions, onChange, autoResize, autoResizeProps, ...antdProps }: InputNumberProps) => React.JSX.Element;
3
+ declare const InputNumber: ({ label, description, errorText, raw, error, prefixel, suffixel, style, className, tooltip, tooltipConfig, value, defaultValue, valueFormatOptions, onChange, onBlur, onKeyPress, onKeyDown, onStep, autoResize, autoResizeProps, min, max, step, disabled, readOnly, autoFocus, inputMode, placeholder, size, name, id: idProp, ...rest }: InputNumberProps) => React.JSX.Element;
4
4
  export default InputNumber;
@@ -1,14 +1,15 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useEffect, useMemo, useCallback } from "react";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useCallback, useState, useRef, useMemo, useEffect } from "react";
3
3
  import { v4 } from "uuid";
4
4
  import { useDataFormat } from "@synerise/ds-core";
5
5
  import FormField from "@synerise/ds-form-field";
6
- import { AutosizeWrapper } from "@synerise/ds-input";
7
- import { useResizeObserver } from "@synerise/ds-utils";
8
- import { InputNumberWrapper, AntdInputNumber, InputNumberAutosize, InputNumberContainer } from "./InputNumber.styles.js";
6
+ import { useAutosizeWidth, useStretchToFit, SIZER_STYLE } from "@synerise/ds-input";
7
+ import { InputNumberRoot, HandlerWrap, HandlerUp, HandlerDown, InputFieldWrap, InputField, InputNumberAutosize, InputNumberContainer, InputNumberWrapper, GroupWrapper, Group, Addon } from "./InputNumber.styles.js";
8
+ import { useStepper } from "./hooks/useStepper.js";
9
9
  import { formatNumber, parseFormattedNumber } from "./utils/inputNumber.utils.js";
10
- import "./style/index.css";
11
10
  const AUTOSIZE_EXTRA_WIDTH = 45;
11
+ const NUMERIC_KEY = /^-?\d*(\.|,)?\d*$/i;
12
+ const cx = (...classes) => classes.filter(Boolean).join(" ");
12
13
  const InputNumber = ({
13
14
  label,
14
15
  description,
@@ -18,94 +19,173 @@ const InputNumber = ({
18
19
  prefixel,
19
20
  suffixel,
20
21
  style,
22
+ className,
21
23
  tooltip,
22
24
  tooltipConfig,
23
25
  value,
24
26
  defaultValue,
25
27
  valueFormatOptions,
26
28
  onChange,
29
+ onBlur,
30
+ onKeyPress,
31
+ onKeyDown,
32
+ onStep,
27
33
  autoResize,
28
34
  autoResizeProps,
29
- ...antdProps
35
+ min,
36
+ max,
37
+ step,
38
+ disabled,
39
+ readOnly,
40
+ autoFocus,
41
+ inputMode,
42
+ placeholder,
43
+ size,
44
+ name,
45
+ id: idProp,
46
+ // forward remaining standard input attributes (maxLength, onFocus, tabIndex, data-*/aria-*, …)
47
+ // to the input, as antd's rc-input-number did
48
+ ...rest
30
49
  }) => {
31
50
  const {
32
51
  formatValue,
33
52
  thousandDelimiter,
34
53
  decimalDelimiter
35
54
  } = useDataFormat();
36
- const [displayValue, setDisplayValue] = useState("");
55
+ const formatter = useCallback((inputValue) => formatNumber(inputValue, formatValue, thousandDelimiter, decimalDelimiter, valueFormatOptions), [formatValue, valueFormatOptions, thousandDelimiter, decimalDelimiter]);
37
56
  const [localValue, setLocalValue] = useState(value ?? defaultValue);
38
- const antdInputRef = useRef(null);
57
+ const [displayValue, setDisplayValue] = useState(() => formatter(value ?? defaultValue));
39
58
  const inputRef = useRef(null);
40
- const inputNumberRef = useRef(null);
41
- const elementRef = useRef(null);
42
59
  const scrollLeftRef = useRef(0);
60
+ const id = useMemo(() => idProp ?? v4(), [idProp]);
61
+ const showError = Boolean(error || errorText);
43
62
  useEffect(() => {
44
63
  if (value !== void 0 && value !== localValue) {
45
64
  setLocalValue(value);
65
+ setDisplayValue(formatter(value));
46
66
  }
47
67
  }, [value]);
48
- const id = useMemo(() => v4(), []);
49
- const showError = Boolean(error || errorText);
50
- const formatter = useCallback((inputValue) => {
51
- const formatted = formatNumber(inputValue, formatValue, thousandDelimiter, decimalDelimiter, valueFormatOptions);
52
- return formatted;
53
- }, [formatValue, valueFormatOptions, thousandDelimiter, decimalDelimiter]);
54
- const parser = useCallback((inputValue) => {
55
- return parseFloat(`${parseFormattedNumber(inputValue, formatValue, thousandDelimiter, decimalDelimiter)}`);
56
- }, [formatValue, thousandDelimiter, decimalDelimiter]);
57
- const handleOnChange = useCallback((changedValue) => {
58
- const formattedValue = formatter(changedValue);
59
- const parsedFormattedValue = parser(formattedValue);
60
- const valueAsNumber = typeof parsedFormattedValue === "string" ? parseFloat(parsedFormattedValue) : parsedFormattedValue;
68
+ const handleInputChange = useCallback((rawValue) => {
69
+ const jsString = `${parseFormattedNumber(rawValue, formatValue, thousandDelimiter, decimalDelimiter)}`;
70
+ const formattedValue = formatNumber(jsString, formatValue, thousandDelimiter, decimalDelimiter, valueFormatOptions);
71
+ const valueAsNumber = parseFloat(jsString);
61
72
  const resultValue = Number.isNaN(valueAsNumber) ? defaultValue : valueAsNumber;
62
73
  setDisplayValue(formattedValue);
63
74
  setLocalValue(resultValue);
64
75
  onChange && onChange(resultValue ?? null);
65
- }, [formatter, parser, defaultValue, onChange]);
66
- useEffect(() => {
67
- const input = antdInputRef.current;
68
- const cancelNonNumeric = (event) => {
69
- if (!/^-?\d*(\.|,)?\d*$/i.test(event.key)) {
70
- event.preventDefault();
71
- }
72
- };
73
- if (input) {
74
- input.addEventListener("keypress", cancelNonNumeric);
76
+ }, [formatValue, thousandDelimiter, decimalDelimiter, valueFormatOptions, defaultValue, onChange]);
77
+ const commitStep = useCallback((next, info) => {
78
+ setDisplayValue(formatter(next));
79
+ setLocalValue(next);
80
+ onChange && onChange(next);
81
+ onStep && onStep(next, info);
82
+ }, [formatter, onChange, onStep]);
83
+ const precision = valueFormatOptions?.maximumFractionDigits;
84
+ const {
85
+ step: stepOnce,
86
+ startStep,
87
+ stopStep,
88
+ upDisabled,
89
+ downDisabled
90
+ } = useStepper({
91
+ value: localValue,
92
+ step,
93
+ min,
94
+ max,
95
+ precision,
96
+ disabled,
97
+ readOnly,
98
+ onStep: commitStep
99
+ });
100
+ const handleKeyDown = useCallback((event) => {
101
+ onKeyDown && onKeyDown(event);
102
+ if (event.key === "ArrowUp") {
103
+ event.preventDefault();
104
+ stepOnce(1, event.shiftKey);
105
+ } else if (event.key === "ArrowDown") {
106
+ event.preventDefault();
107
+ stepOnce(-1, event.shiftKey);
108
+ }
109
+ }, [stepOnce, onKeyDown]);
110
+ const handleKeyPress = useCallback((event) => {
111
+ onKeyPress && onKeyPress(event);
112
+ if (!NUMERIC_KEY.test(event.key)) {
113
+ event.preventDefault();
75
114
  }
76
- return () => {
77
- if (input) {
78
- input.removeEventListener("keypress", cancelNonNumeric);
115
+ }, [onKeyPress]);
116
+ const handleBlur = useCallback((event) => {
117
+ if (typeof localValue === "number" && Number.isFinite(localValue)) {
118
+ let clamped = localValue;
119
+ if (max !== void 0 && clamped > max) {
120
+ clamped = max;
79
121
  }
80
- };
81
- });
122
+ if (min !== void 0 && clamped < min) {
123
+ clamped = min;
124
+ }
125
+ if (clamped !== localValue) {
126
+ setLocalValue(clamped);
127
+ setDisplayValue(formatter(clamped));
128
+ onChange && onChange(clamped);
129
+ }
130
+ }
131
+ onBlur && onBlur(event);
132
+ }, [localValue, min, max, formatter, onChange, onBlur]);
82
133
  const stretchToFit = autoResize && autoResize !== true && Boolean(autoResize.stretchToFit);
83
- const handlePreAutosize = useCallback(() => {
134
+ const {
135
+ sizerRef,
136
+ containerRef
137
+ } = useAutosizeWidth({
138
+ value: displayValue,
139
+ placeholder,
140
+ extraWidth: AUTOSIZE_EXTRA_WIDTH,
141
+ placeholderIsMinWidth: autoResizeProps?.placeholderIsMinWidth,
142
+ inputRef
143
+ });
144
+ const handleStretchBefore = useCallback(() => {
84
145
  scrollLeftRef.current = inputRef.current?.scrollLeft || 0;
85
- inputNumberRef.current && inputNumberRef.current.style.removeProperty("max-width");
86
146
  }, []);
87
- const handleAutosize = useCallback(() => {
88
- const parentRect = elementRef.current && elementRef.current.getBoundingClientRect();
89
- if (stretchToFit && inputNumberRef.current && parentRect?.width) {
90
- inputNumberRef.current.style.maxWidth = `${parentRect?.width}px`;
91
- inputRef.current && inputRef.current.scrollTo(scrollLeftRef.current, 0);
92
- }
93
- }, [stretchToFit]);
94
- const handleWrapperToResize = useCallback(() => {
95
- handlePreAutosize();
96
- handleAutosize();
97
- }, [handleAutosize, handlePreAutosize]);
98
- const transformRef = useCallback((element) => {
99
- inputNumberRef.current = element;
100
- return element.querySelector("input");
147
+ const handleStretchAfter = useCallback(() => {
148
+ inputRef.current && inputRef.current.scrollTo(scrollLeftRef.current, 0);
101
149
  }, []);
102
- useResizeObserver(elementRef, handleWrapperToResize);
103
- const rawInput = /* @__PURE__ */ jsx(InputNumberWrapper, { style, children: /* @__PURE__ */ jsx(AntdInputNumber, { ...antdProps, ref: antdInputRef, onChange: handleOnChange, addonBefore: prefixel, addonAfter: suffixel, id, error: showError, className: showError ? "error" : void 0, autoComplete: "off", formatter, parser, defaultValue, value: localValue, decimalSeparator: decimalDelimiter }) });
104
- const autoSizeInput = /* @__PURE__ */ jsx(InputNumberAutosize, { ref: elementRef, autoResize, children: /* @__PURE__ */ jsx(AutosizeWrapper, { ...autoResizeProps, extraWidth: AUTOSIZE_EXTRA_WIDTH, autoResize: !!autoResize, value: displayValue, transformRef, preAutosize: handlePreAutosize, onAutosize: handleAutosize, children: rawInput }) });
150
+ const {
151
+ outerRef
152
+ } = useStretchToFit({
153
+ enabled: !!stretchToFit,
154
+ targetRef: containerRef,
155
+ onBeforeResize: handleStretchBefore,
156
+ onAfterResize: handleStretchAfter
157
+ });
158
+ const showHandlers = !disabled && !readOnly;
159
+ const isLarge = size === "large";
160
+ const hasPrefix = prefixel !== void 0 && prefixel !== null;
161
+ const hasSuffix = suffixel !== void 0 && suffixel !== null;
162
+ const numberCore = /* @__PURE__ */ jsxs(InputNumberRoot, { className: cx("ds-input-number", isLarge && "ds-input-number-lg", showError && "error"), $error: showError, $disabled: disabled, $size: size, $hasPrefix: hasPrefix, $hasSuffix: hasSuffix, children: [
163
+ showHandlers && /* @__PURE__ */ jsxs(HandlerWrap, { className: "ds-input-number-handler-wrap", children: [
164
+ /* @__PURE__ */ jsx(HandlerUp, { role: "button", "aria-label": "Increase Value", className: "ds-input-number-handler ds-input-number-handler-up", $disabled: upDisabled, onMouseDown: (event) => {
165
+ event.preventDefault();
166
+ startStep(1, event.shiftKey);
167
+ }, onMouseUp: stopStep, onMouseLeave: stopStep }),
168
+ /* @__PURE__ */ jsx(HandlerDown, { role: "button", "aria-label": "Decrease Value", className: "ds-input-number-handler ds-input-number-handler-down", $disabled: downDisabled, onMouseDown: (event) => {
169
+ event.preventDefault();
170
+ startStep(-1, event.shiftKey);
171
+ }, onMouseUp: stopStep, onMouseLeave: stopStep })
172
+ ] }),
173
+ /* @__PURE__ */ jsx(InputFieldWrap, { className: "ds-input-number-input-wrap", $size: size, children: /* @__PURE__ */ jsx(InputField, { ...rest, ref: inputRef, type: "text", inputMode: inputMode ?? "decimal", role: "spinbutton", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": localValue ?? void 0, id, name, className: "ds-input-number-input", autoComplete: "off", placeholder, disabled, readOnly, autoFocus, value: displayValue, $autoResize: autoResize, onChange: (event) => handleInputChange(event.target.value), onKeyDown: handleKeyDown, onKeyPress: handleKeyPress, onBlur: handleBlur }) })
174
+ ] });
175
+ const withAddons = hasPrefix || hasSuffix ? /* @__PURE__ */ jsx(GroupWrapper, { className: "ds-input-number-group-wrapper", children: /* @__PURE__ */ jsxs(Group, { className: "ds-input-number-group", children: [
176
+ hasPrefix && /* @__PURE__ */ jsx(Addon, { className: "ds-input-number-group-addon", children: prefixel }),
177
+ numberCore,
178
+ hasSuffix && /* @__PURE__ */ jsx(Addon, { className: "ds-input-number-group-addon", children: suffixel })
179
+ ] }) }) : numberCore;
180
+ const rawInput = /* @__PURE__ */ jsx(InputNumberWrapper, { ref: containerRef, style, className, children: withAddons });
181
+ const autoSizeInput = /* @__PURE__ */ jsxs(InputNumberAutosize, { ref: outerRef, $autoResize: autoResize, className: autoResizeProps?.wrapperClassName, style: autoResizeProps?.wrapperStyle, children: [
182
+ rawInput,
183
+ /* @__PURE__ */ jsx("span", { ref: sizerRef, style: SIZER_STYLE, "aria-hidden": true })
184
+ ] });
105
185
  if (raw) {
106
186
  return /* @__PURE__ */ jsx(InputNumberContainer, { children: rawInput });
107
187
  }
108
- return /* @__PURE__ */ jsx(InputNumberContainer, { autoResize, children: /* @__PURE__ */ jsx(FormField, { id, label, tooltip, tooltipConfig, description, errorText, children: autoResize ? autoSizeInput : rawInput }) });
188
+ return /* @__PURE__ */ jsx(InputNumberContainer, { children: /* @__PURE__ */ jsx(FormField, { id, label, tooltip, tooltipConfig, description, errorText, children: autoResize ? autoSizeInput : rawInput }) });
109
189
  };
110
190
  export {
111
191
  InputNumber as default
@@ -1,16 +1,32 @@
1
- import { InputNumberProps } from 'antd';
2
- import { default as React } from 'react';
3
1
  import { ThemeProps } from '@synerise/ds-core';
4
2
  import { AutoResizeProp } from '@synerise/ds-input';
5
- export declare const InputNumberContainer: import('styled-components').StyledComponent<"div", any, {
6
- autoResize?: AutoResizeProp;
7
- }, never>;
8
- export declare const InputNumberAutosize: import('styled-components').StyledComponent<"div", any, {
9
- autoResize?: AutoResizeProp;
3
+ type SizeProp = 'small' | 'middle' | 'large';
4
+ export declare const HandlerUp: import('styled-components').StyledComponent<"span", any, {
5
+ $disabled?: boolean;
6
+ } & ThemeProps, never>;
7
+ export declare const HandlerDown: import('styled-components').StyledComponent<"span", any, {
8
+ $disabled?: boolean;
9
+ } & ThemeProps, never>;
10
+ export declare const HandlerWrap: import('styled-components').StyledComponent<"div", any, ThemeProps, never>;
11
+ export declare const InputField: import('styled-components').StyledComponent<"input", any, {
12
+ $autoResize?: AutoResizeProp;
13
+ } & ThemeProps, never>;
14
+ export declare const InputFieldWrap: import('styled-components').StyledComponent<"div", any, {
15
+ $size?: SizeProp;
10
16
  }, never>;
11
- export declare const AntdInputNumber: import('styled-components').StyledComponent<React.ForwardRefExoticComponent<InputNumberProps<number> & React.RefAttributes<HTMLInputElement>>, any, {
12
- error: boolean;
17
+ export declare const Addon: import('styled-components').StyledComponent<"div", any, ThemeProps, never>;
18
+ export declare const Group: import('styled-components').StyledComponent<"div", any, {}, never>;
19
+ export declare const GroupWrapper: import('styled-components').StyledComponent<"div", any, {}, never>;
20
+ export declare const InputNumberRoot: import('styled-components').StyledComponent<"div", any, {
21
+ $error?: boolean;
22
+ $disabled?: boolean;
23
+ $size?: SizeProp;
24
+ $hasPrefix?: boolean;
25
+ $hasSuffix?: boolean;
13
26
  } & ThemeProps, never>;
14
- export declare const Prefixel: import('styled-components').StyledComponent<"div", any, ThemeProps, never>;
15
- export declare const Suffixel: import('styled-components').StyledComponent<"div", any, ThemeProps, never>;
16
27
  export declare const InputNumberWrapper: import('styled-components').StyledComponent<"div", any, {}, never>;
28
+ export declare const InputNumberContainer: import('styled-components').StyledComponent<"div", any, {}, never>;
29
+ export declare const InputNumberAutosize: import('styled-components').StyledComponent<"div", any, {
30
+ $autoResize?: AutoResizeProp;
31
+ }, never>;
32
+ export {};
@@ -1,44 +1,69 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { InputNumber } from "antd";
3
- import { forwardRef } from "react";
4
- import styled from "styled-components";
5
- import { autoresizeConfObjToCss } from "@synerise/ds-input/dist/Input.styles";
6
- const InputNumberContainer = /* @__PURE__ */ styled.div.withConfig({
7
- displayName: "InputNumberstyles__InputNumberContainer",
1
+ import styled, { css } from "styled-components";
2
+ import { autoresizeConfObjToCss } from "@synerise/ds-input";
3
+ import { ANGLE_DOWN_SVG, ANGLE_UP_SVG } from "./constants/inputNumber.constants.js";
4
+ const handlerGlyph = /* @__PURE__ */ css(["position:relative;flex:1 1 50%;min-height:0;cursor:pointer;user-select:none;&::before{content:'';position:absolute;inset:0;background-color:", ";mask-repeat:no-repeat;mask-position:center;mask-size:16px 16px;-webkit-mask-repeat:no-repeat;-webkit-mask-position:center;-webkit-mask-size:16px 16px;}& > svg,& > i{display:none;}"], (props) => props.theme.palette["grey-600"]);
5
+ const HandlerUp = /* @__PURE__ */ styled.span.withConfig({
6
+ displayName: "InputNumberstyles__HandlerUp",
8
7
  componentId: "sc-abrul0-0"
9
- })(["display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;&&& .ant-input-number-input{padding:", ";}"], (props) => props.autoResize ? "0" : "6px 11px");
10
- const InputNumberAutosize = /* @__PURE__ */ styled.div.withConfig({
11
- displayName: "InputNumberstyles__InputNumberAutosize",
8
+ })(["", " &::before{mask-image:url('", "');-webkit-mask-image:url('", "');}", ""], handlerGlyph, ANGLE_UP_SVG, ANGLE_UP_SVG, (props) => props.$disabled && css(["cursor:not-allowed;opacity:0.4;"]));
9
+ const HandlerDown = /* @__PURE__ */ styled.span.withConfig({
10
+ displayName: "InputNumberstyles__HandlerDown",
12
11
  componentId: "sc-abrul0-1"
13
- })(["&&& .ant-input-number{width:", ";", ";grid-area:1 / 1;}input{text-indent:4px;}input.ant-input-number-input{letter-spacing:normal;font-feature-settings:'tnum' 0;font-variant-numeric:proportional-nums;}"], (props) => props.autoResize ? "100%" : "200px", (props) => autoresizeConfObjToCss({
14
- ...props,
15
- boxSizing: "border-box"
16
- }));
17
- const NumberOnlyBaseAntInputNumber = forwardRef((props, ref) => /* @__PURE__ */ jsx(InputNumber, { ...props, ref }));
18
- const AntdInputNumber = /* @__PURE__ */ styled(NumberOnlyBaseAntInputNumber).withConfig({
19
- displayName: "InputNumberstyles__AntdInputNumber",
12
+ })(["", " &::before{mask-image:url('", "');-webkit-mask-image:url('", "');}border-top:1px solid ", ";", ""], handlerGlyph, ANGLE_DOWN_SVG, ANGLE_DOWN_SVG, (props) => props.theme.palette["grey-300"], (props) => props.$disabled && css(["cursor:not-allowed;opacity:0.4;"]));
13
+ const HandlerWrap = /* @__PURE__ */ styled.div.withConfig({
14
+ displayName: "InputNumberstyles__HandlerWrap",
20
15
  componentId: "sc-abrul0-2"
21
- })(["color:", ";input,input:focus{", ";}.ant-input-number-group-addon{background-color:", ";padding:0 12px;}"], (props) => props.theme.palette["grey-700"], (props) => !!props.error && `
22
- background: ${props.theme.palette["red-050"]};
23
- border: 0;
24
- `, (props) => props.theme.palette["grey-050"]);
25
- const Prefixel = /* @__PURE__ */ styled.div.withConfig({
26
- displayName: "InputNumberstyles__Prefixel",
16
+ })(["position:absolute;z-index:3;top:1px;right:1px;width:22px;height:30px;display:flex;flex-direction:column;border-left:1px solid ", ";opacity:0;transition:opacity 0.24s linear 0.1s;"], (props) => props.theme.palette["grey-300"]);
17
+ const InputField = /* @__PURE__ */ styled.input.withConfig({
18
+ displayName: "InputNumberstyles__InputField",
27
19
  componentId: "sc-abrul0-3"
28
- })(["border:1px solid ", ";border-radius:3px 0 0 3px;border-right-width:0;"], (props) => props.theme.palette["grey-300"]);
29
- const Suffixel = /* @__PURE__ */ styled.div.withConfig({
30
- displayName: "InputNumberstyles__Suffixel",
20
+ })(["box-sizing:border-box;display:block;width:100%;height:100%;text-align:left;&&{height:100%;padding:", ";line-height:1;}color:inherit;font-family:inherit;font-size:13px;font-variant-numeric:tabular-nums;background:transparent;border:0;outline:0;transition:unset;&::placeholder{color:", ";}&:focus{box-shadow:unset;}"], (props) => props.$autoResize ? "0" : "7px 11px", (props) => props.theme.palette["grey-500"]);
21
+ const InputFieldWrap = /* @__PURE__ */ styled.div.withConfig({
22
+ displayName: "InputNumberstyles__InputFieldWrap",
31
23
  componentId: "sc-abrul0-4"
32
- })(["border:1px solid ", ";border-radius:0 3px 3px 0;border-left-width:0;"], (props) => props.theme.palette["grey-300"]);
24
+ })(["height:100%;margin:0 2px;"]);
25
+ const Addon = /* @__PURE__ */ styled.div.withConfig({
26
+ displayName: "InputNumberstyles__Addon",
27
+ componentId: "sc-abrul0-5"
28
+ })(["display:flex;align-items:center;padding:0 12px;background-color:", ";border:1px solid ", ";&:first-child{border-radius:3px 0 0 3px;border-right-width:0;}&:last-child{border-radius:0 3px 3px 0;border-left-width:0;}"], (props) => props.theme.palette["grey-050"], (props) => props.theme.palette["grey-300"]);
29
+ const Group = /* @__PURE__ */ styled.div.withConfig({
30
+ displayName: "InputNumberstyles__Group",
31
+ componentId: "sc-abrul0-6"
32
+ })(["display:inline-flex;width:100%;vertical-align:top;"]);
33
+ const GroupWrapper = /* @__PURE__ */ styled.div.withConfig({
34
+ displayName: "InputNumberstyles__GroupWrapper",
35
+ componentId: "sc-abrul0-7"
36
+ })(["display:inline-block;"]);
37
+ const InputNumberRoot = /* @__PURE__ */ styled.div.withConfig({
38
+ displayName: "InputNumberstyles__InputNumberRoot",
39
+ componentId: "sc-abrul0-8"
40
+ })(["position:relative;box-sizing:border-box;width:100%;height:", ";line-height:1.38;color:", ";background:", ";border:0;border-radius:", ";box-shadow:", ";&:focus-within{box-shadow:", ";background:", ";}", " &:hover ", ",&:focus-within ", "{opacity:1;}&:focus-within ", "{width:21px;height:28px;top:2px;right:2px;}", " ", " ", ""], (props) => props.$size === "large" ? "48px" : "32px", (props) => props.theme.palette["grey-700"], (props) => props.$error ? props.theme.palette["red-050"] : props.theme.palette.white, (props) => `${props.$hasPrefix ? "0" : "3px"} ${props.$hasSuffix ? "0" : "3px"} ${props.$hasSuffix ? "0" : "3px"} ${props.$hasPrefix ? "0" : "3px"}`, (props) => props.$error ? `inset 0px 0px 0px 2px ${props.theme.palette["red-600"]}` : `inset 0px 0px 0px 1px ${props.theme.palette["grey-300"]}`, (props) => props.$error ? `inset 0px 0px 0px 2px ${props.theme.palette["red-600"]}` : `inset 0px 0px 0px 2px ${props.theme.palette["blue-600"]}`, (props) => props.$error ? props.theme.palette["red-050"] : props.theme.palette["blue-050"], (props) => !props.$error && !props.$disabled && css(["&:hover:not(:focus-within){box-shadow:inset 0px 0px 0px 1px ", ";}"], props.theme.palette["grey-400"]), HandlerWrap, HandlerWrap, HandlerWrap, (props) => props.$error && css(["", "{width:21px;height:28px;top:2px;right:2px;}"], HandlerWrap), (props) => props.$size === "large" && css(["", "{width:24px;height:44px;}&:focus-within ", "{height:44px;}"], HandlerWrap, HandlerWrap), (props) => props.$disabled && css(["cursor:not-allowed;background:", ";", "{cursor:not-allowed;color:", ";}"], props.theme.palette["grey-050"], InputField, props.theme.palette["grey-400"]));
33
41
  const InputNumberWrapper = /* @__PURE__ */ styled.div.withConfig({
34
42
  displayName: "InputNumberstyles__InputNumberWrapper",
35
- componentId: "sc-abrul0-5"
36
- })([""]);
43
+ componentId: "sc-abrul0-9"
44
+ })(["display:block;"]);
45
+ const InputNumberContainer = /* @__PURE__ */ styled.div.withConfig({
46
+ displayName: "InputNumberstyles__InputNumberContainer",
47
+ componentId: "sc-abrul0-10"
48
+ })(["display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;"]);
49
+ const InputNumberAutosize = /* @__PURE__ */ styled.div.withConfig({
50
+ displayName: "InputNumberstyles__InputNumberAutosize",
51
+ componentId: "sc-abrul0-11"
52
+ })(["", " ", "{text-indent:4px;letter-spacing:normal;font-variant-numeric:tabular-nums;}"], (props) => css(["", "{width:", ";", ";grid-area:1 / 1;}"], InputNumberRoot, props.$autoResize ? "100%" : "200px", autoresizeConfObjToCss({
53
+ autoResize: props.$autoResize,
54
+ boxSizing: "border-box"
55
+ })), InputField);
37
56
  export {
38
- AntdInputNumber,
57
+ Addon,
58
+ Group,
59
+ GroupWrapper,
60
+ HandlerDown,
61
+ HandlerUp,
62
+ HandlerWrap,
63
+ InputField,
64
+ InputFieldWrap,
39
65
  InputNumberAutosize,
40
66
  InputNumberContainer,
41
- InputNumberWrapper,
42
- Prefixel,
43
- Suffixel
67
+ InputNumberRoot,
68
+ InputNumberWrapper
44
69
  };
@@ -1,10 +1,29 @@
1
- import { InputNumberProps as AntdInputNumberProps } from 'antd';
2
- import { ReactNode } from 'react';
1
+ import { CSSProperties, FocusEvent, InputHTMLAttributes, KeyboardEvent, ReactNode } from 'react';
3
2
  import { NumberToFormatOptions } from '@synerise/ds-core';
4
3
  import { FormFieldCommonProps } from '@synerise/ds-form-field';
5
4
  import { AutoResizeProp, AutosizeInputProps } from '@synerise/ds-input';
6
- export type InputNumberProps = AntdInputNumberProps<number> & {
5
+ import { PassthroughAttributes } from '@synerise/ds-utils';
6
+ type InputNumberOwnProps = {
7
+ value?: number | null;
7
8
  defaultValue?: number | null;
9
+ onChange?: (value: number | null) => void;
10
+ onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
11
+ onKeyPress?: (event: KeyboardEvent<HTMLInputElement>) => void;
12
+ onStep?: (value: number, info: {
13
+ offset: number;
14
+ type: 'up' | 'down';
15
+ }) => void;
16
+ min?: number;
17
+ max?: number;
18
+ step?: number | string;
19
+ disabled?: boolean;
20
+ readOnly?: boolean;
21
+ autoFocus?: boolean;
22
+ tabIndex?: number;
23
+ placeholder?: string;
24
+ size?: 'small' | 'middle' | 'large';
25
+ name?: string;
26
+ id?: string;
8
27
  error?: boolean;
9
28
  prefixel?: ReactNode;
10
29
  suffixel?: ReactNode;
@@ -12,8 +31,12 @@ export type InputNumberProps = AntdInputNumberProps<number> & {
12
31
  valueFormatOptions?: NumberToFormatOptions;
13
32
  autoResize?: AutoResizeProp;
14
33
  autoResizeProps?: Partial<Pick<AutosizeInputProps, 'placeholderIsMinWidth' | 'wrapperClassName' | 'wrapperStyle' | 'extraWidth'>>;
15
- } & FormFieldCommonProps;
34
+ style?: CSSProperties;
35
+ className?: string;
36
+ };
37
+ export type InputNumberProps = InputNumberOwnProps & FormFieldCommonProps & PassthroughAttributes & Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange' | 'onBlur' | 'size' | 'min' | 'max' | 'type'>;
16
38
  /**
17
39
  * @deprecated - use InputNumberProps
18
40
  */
19
41
  export type Props = InputNumberProps;
42
+ export {};
@@ -2,3 +2,5 @@ import { Delimiter } from '@synerise/ds-core';
2
2
  export declare const MAXIMUM_FRACTION_DIGITS = 20;
3
3
  export declare const NUMBER_DELIMITER: Delimiter;
4
4
  export declare const MAXIMUM_NUMBER_DIGITS = 15;
5
+ export declare const ANGLE_UP_SVG = "data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAxLWFuZ2xlLXVwLXM8L3RpdGxlPjxyZWN0IGlkPSJjYW52YXMiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMTUuMjUsMTQuMzhhLjc5Ljc5LDAsMCwxLS41My0uMjJMMTIsMTEuNDQsOS4yOCwxNC4xNmEuNzUuNzUsMCwwLDEtMS4wNiwwLC43Ny43NywwLDAsMSwwLTEuMDdsMy4yNS0zLjI1YS43NS43NSwwLDAsMSwxLjA2LDBsMy4yNSwzLjI1YS43Ny43NywwLDAsMSwwLDEuMDdBLjc5Ljc5LDAsMCwxLDE1LjI1LDE0LjM4WiIvPjwvc3ZnPg==";
6
+ export declare const ANGLE_DOWN_SVG = "data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAzLWFuZ2xlLWRvd24tczwvdGl0bGU+PHJlY3QgaWQ9ImNhbnZhcyIgY2xhc3M9ImNscy0xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xMiwxNC4zOGEuNzkuNzksMCwwLDEtLjUzLS4yMkw4LjIyLDEwLjkxYS43Ny43NywwLDAsMSwwLTEuMDcuNzUuNzUsMCwwLDEsMS4wNiwwTDEyLDEyLjU2bDIuNzItMi43MmEuNzUuNzUsMCwwLDEsMS4wNiwwLC43Ny43NywwLDAsMSwwLDEuMDdsLTMuMjUsMy4yNUEuNzkuNzksMCwwLDEsMTIsMTQuMzhaIi8+PC9zdmc+";
@@ -1,7 +1,11 @@
1
1
  const MAXIMUM_FRACTION_DIGITS = 20;
2
2
  const NUMBER_DELIMITER = ".";
3
3
  const MAXIMUM_NUMBER_DIGITS = 15;
4
+ const ANGLE_UP_SVG = "data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAxLWFuZ2xlLXVwLXM8L3RpdGxlPjxyZWN0IGlkPSJjYW52YXMiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMTUuMjUsMTQuMzhhLjc5Ljc5LDAsMCwxLS41My0uMjJMMTIsMTEuNDQsOS4yOCwxNC4xNmEuNzUuNzUsMCwwLDEtMS4wNiwwLC43Ny43NywwLDAsMSwwLTEuMDdsMy4yNS0zLjI1YS43NS43NSwwLDAsMSwxLjA2LDBsMy4yNSwzLjI1YS43Ny43NywwLDAsMSwwLDEuMDdBLjc5Ljc5LDAsMCwxLDE1LjI1LDE0LjM4WiIvPjwvc3ZnPg==";
5
+ const ANGLE_DOWN_SVG = "data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAzLWFuZ2xlLWRvd24tczwvdGl0bGU+PHJlY3QgaWQ9ImNhbnZhcyIgY2xhc3M9ImNscy0xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik0xMiwxNC4zOGEuNzkuNzksMCwwLDEtLjUzLS4yMkw4LjIyLDEwLjkxYS43Ny43NywwLDAsMSwwLTEuMDcuNzUuNzUsMCwwLDEsMS4wNiwwTDEyLDEyLjU2bDIuNzItMi43MmEuNzUuNzUsMCwwLDEsMS4wNiwwLC43Ny43NywwLDAsMSwwLDEuMDdsLTMuMjUsMy4yNUEuNzkuNzksMCwwLDEsMTIsMTQuMzhaIi8+PC9zdmc+";
4
6
  export {
7
+ ANGLE_DOWN_SVG,
8
+ ANGLE_UP_SVG,
5
9
  MAXIMUM_FRACTION_DIGITS,
6
10
  MAXIMUM_NUMBER_DIGITS,
7
11
  NUMBER_DELIMITER
@@ -0,0 +1,25 @@
1
+ type UseStepperArgs = {
2
+ value: number | null | undefined;
3
+ step?: number | string;
4
+ min?: number;
5
+ max?: number;
6
+ /** Display precision floor (from valueFormatOptions); the step's own decimals are added on top. */
7
+ precision?: number;
8
+ disabled?: boolean;
9
+ readOnly?: boolean;
10
+ onStep: (next: number, info: {
11
+ offset: number;
12
+ type: 'up' | 'down';
13
+ }) => void;
14
+ };
15
+ type UseStepperResult = {
16
+ /** Single step — used for keyboard (the OS repeats key-down events itself). */
17
+ step: (direction: 1 | -1, decuple?: boolean) => void;
18
+ /** Press-and-hold: immediate step, then auto-repeat — used for the mouse handlers. */
19
+ startStep: (direction: 1 | -1, decuple?: boolean) => void;
20
+ stopStep: () => void;
21
+ upDisabled: boolean;
22
+ downDisabled: boolean;
23
+ };
24
+ export declare const useStepper: ({ value, step, min, max, precision, disabled, readOnly, onStep, }: UseStepperArgs) => UseStepperResult;
25
+ export {};
@@ -0,0 +1,108 @@
1
+ import { useRef, useCallback, useEffect } from "react";
2
+ const STEP_DELAY = 600;
3
+ const STEP_INTERVAL = 200;
4
+ const DECUPLE = 10;
5
+ const countDecimals = (value) => {
6
+ if (!Number.isFinite(value)) {
7
+ return 0;
8
+ }
9
+ const text = String(value);
10
+ if (text.includes("e-")) {
11
+ return Number(text.split("e-")[1]);
12
+ }
13
+ return text.includes(".") ? text.split(".")[1].length : 0;
14
+ };
15
+ const scaledAdd = (a, b) => {
16
+ const factor = Math.pow(10, Math.max(countDecimals(a), countDecimals(b)));
17
+ return (Math.round(a * factor) + Math.round(b * factor)) / factor;
18
+ };
19
+ const clamp = (value, min, max) => {
20
+ let next = value;
21
+ if (max !== void 0 && next > max) {
22
+ next = max;
23
+ }
24
+ if (min !== void 0 && next < min) {
25
+ next = min;
26
+ }
27
+ return next;
28
+ };
29
+ const applyPrecision = (value, precision) => precision === void 0 ? value : Number(value.toFixed(precision));
30
+ const useStepper = ({
31
+ value,
32
+ step = 1,
33
+ min,
34
+ max,
35
+ precision,
36
+ disabled,
37
+ readOnly,
38
+ onStep
39
+ }) => {
40
+ const timerRef = useRef(null);
41
+ const latest = useRef({
42
+ value,
43
+ step,
44
+ min,
45
+ max,
46
+ precision,
47
+ disabled,
48
+ readOnly,
49
+ onStep
50
+ });
51
+ latest.current = {
52
+ value,
53
+ step,
54
+ min,
55
+ max,
56
+ precision,
57
+ disabled,
58
+ readOnly,
59
+ onStep
60
+ };
61
+ const base = value ?? 0;
62
+ const locked = Boolean(disabled || readOnly);
63
+ const upDisabled = locked || max !== void 0 && base >= max;
64
+ const downDisabled = locked || min !== void 0 && base <= min;
65
+ const stepOnce = useCallback((direction, decuple = false) => {
66
+ const args = latest.current;
67
+ if (args.disabled || args.readOnly) {
68
+ return;
69
+ }
70
+ const rawStep = typeof args.step === "string" ? parseFloat(args.step) || 1 : args.step ?? 1;
71
+ const delta = rawStep * (decuple ? DECUPLE : 1) * direction;
72
+ const effectivePrecision = Math.max(countDecimals(rawStep), args.precision ?? 0);
73
+ const next = applyPrecision(clamp(scaledAdd(args.value ?? 0, delta), args.min, args.max), effectivePrecision);
74
+ args.onStep(next, {
75
+ offset: Math.abs(delta),
76
+ type: direction === 1 ? "up" : "down"
77
+ });
78
+ }, []);
79
+ const clearTimer = useCallback(() => {
80
+ if (timerRef.current) {
81
+ clearTimeout(timerRef.current);
82
+ timerRef.current = null;
83
+ }
84
+ }, []);
85
+ const startStep = useCallback((direction, decuple = false) => {
86
+ clearTimer();
87
+ if (latest.current.disabled || latest.current.readOnly) {
88
+ return;
89
+ }
90
+ stepOnce(direction, decuple);
91
+ const loop = () => {
92
+ stepOnce(direction, decuple);
93
+ timerRef.current = setTimeout(loop, STEP_INTERVAL);
94
+ };
95
+ timerRef.current = setTimeout(loop, STEP_DELAY);
96
+ }, [clearTimer, stepOnce]);
97
+ useEffect(() => clearTimer, [clearTimer]);
98
+ return {
99
+ step: stepOnce,
100
+ startStep,
101
+ stopStep: clearTimer,
102
+ upDisabled,
103
+ downDisabled
104
+ };
105
+ };
106
+ export {
107
+ useStepper
108
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-input-number",
3
- "version": "1.2.48",
3
+ "version": "1.2.50",
4
4
  "description": "Input-Number UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -35,23 +36,19 @@
35
36
  "check:circular-dependencies": "madge --circular --extensions ts,tsx,js,jsx --ts-config tsconfig.json src/ --exclude '/dist/'",
36
37
  "upgrade:ds": "ncu -f \"@synerise/ds-*\" -u"
37
38
  },
38
- "sideEffects": [
39
- "dist/style/*",
40
- "*.less"
41
- ],
39
+ "sideEffects": false,
42
40
  "types": "dist/index.d.ts",
43
41
  "dependencies": {
44
- "@synerise/ds-form-field": "^1.3.23",
45
- "@synerise/ds-input": "^1.7.11",
46
- "@synerise/ds-utils": "^1.10.1",
42
+ "@synerise/ds-form-field": "^1.3.24",
43
+ "@synerise/ds-input": "^1.7.13",
44
+ "@synerise/ds-utils": "^1.10.2",
47
45
  "uuid": "^8.3.2"
48
46
  },
49
47
  "peerDependencies": {
50
48
  "@synerise/ds-core": "*",
51
- "antd": "4.24.16",
52
49
  "react": ">=16.9.0 <= 18.3.1",
53
50
  "styled-components": "^5.3.3",
54
51
  "vitest": "4"
55
52
  },
56
- "gitHead": "d8c64070f58f14e3fb1bfbcbf00d1e3b8fd51eb8"
53
+ "gitHead": "d0a43cc43d8528a36f105aceea52ab470edb71d9"
57
54
  }
File without changes
@@ -1 +0,0 @@
1
- .ant-input-number-affix-wrapper{display:inline-block;width:100%;min-width:0;padding:4px 15px;color:#6a7580;font-size:13px;line-height:1.38;background-color:#fff;background-image:none;border:1px solid #dbe0e3;border-radius:3px;transition:all .3s;position:relative;display:inline-flex;width:90px;padding:0;padding-inline-start:15px}.ant-input-number-affix-wrapper::placeholder{color:#b5bdc3;user-select:none}.ant-input-number-affix-wrapper:placeholder-shown{text-overflow:ellipsis}.ant-input-number-affix-wrapper:hover{border-color:#b5bdc3;border-right-width:1px}.ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper:focus{border-color:#38f;box-shadow:0 0 0 0 rgba(11,104,255,.2);border-right-width:1px;outline:0}.ant-input-number-affix-wrapper-disabled{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper-disabled:hover{border-color:#dbe0e3;border-right-width:1px}.ant-input-number-affix-wrapper[disabled]{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-affix-wrapper[disabled]:hover{border-color:#dbe0e3;border-right-width:1px}.ant-input-number-affix-wrapper-borderless,.ant-input-number-affix-wrapper-borderless-disabled,.ant-input-number-affix-wrapper-borderless-focused,.ant-input-number-affix-wrapper-borderless:focus,.ant-input-number-affix-wrapper-borderless:hover,.ant-input-number-affix-wrapper-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number-affix-wrapper{max-width:100%;height:auto;min-height:32px;line-height:1.38;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-affix-wrapper-lg{padding:6px 15px;font-size:13px}.ant-input-number-affix-wrapper-sm{padding:1px 7px}.ant-input-number-affix-wrapper:not(.ant-input-number-affix-wrapper-disabled):hover{border-color:#b5bdc3;border-right-width:1px;z-index:1}.ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper:focus{z-index:1}.ant-input-number-affix-wrapper-disabled .ant-input-number[disabled]{background:0 0}.ant-input-number-affix-wrapper>div.ant-input-number{width:100%;border:none;outline:0}.ant-input-number-affix-wrapper>div.ant-input-number.ant-input-number-focused{box-shadow:none!important}.ant-input-number-affix-wrapper input.ant-input-number-input{padding:0}.ant-input-number-affix-wrapper::before{display:inline-block;width:0;visibility:hidden;content:'\a0'}.ant-input-number-affix-wrapper .ant-input-number-handler-wrap{z-index:2}.ant-input-number-prefix,.ant-input-number-suffix{display:flex;flex:none;align-items:center;pointer-events:none}.ant-input-number-prefix{margin-inline-end:4px}.ant-input-number-suffix{position:absolute;top:0;right:0;z-index:1;height:100%;margin-right:15px;margin-left:4px}.ant-input-number-group-wrapper .ant-input-number-affix-wrapper{width:100%}.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number,.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover{background:#fff;border-color:#f52922}.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused,.ant-input-number-status-error:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus{border-color:#ff584d;box-shadow:0 0 0 0 rgba(245,41,34,.2);border-right-width:1px;outline:0}.ant-input-number-status-error .ant-input-number-prefix{color:#f52922}.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number,.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:hover{background:#fff;border-color:#fab700}.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number-focused,.ant-input-number-status-warning:not(.ant-input-number-disabled):not(.ant-input-number-borderless).ant-input-number:focus{border-color:#ffcd29;box-shadow:0 0 0 0 rgba(250,183,0,.2);border-right-width:1px;outline:0}.ant-input-number-status-warning .ant-input-number-prefix{color:#fab700}.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background:#fff;border-color:#f52922}.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper-status-error:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus{border-color:#ff584d;box-shadow:0 0 0 0 rgba(245,41,34,.2);border-right-width:1px;outline:0}.ant-input-number-affix-wrapper-status-error .ant-input-number-prefix{color:#f52922}.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper,.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:hover{background:#fff;border-color:#fab700}.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper-focused,.ant-input-number-affix-wrapper-status-warning:not(.ant-input-number-affix-wrapper-disabled):not(.ant-input-number-affix-wrapper-borderless).ant-input-number-affix-wrapper:focus{border-color:#ffcd29;box-shadow:0 0 0 0 rgba(250,183,0,.2);border-right-width:1px;outline:0}.ant-input-number-affix-wrapper-status-warning .ant-input-number-prefix{color:#fab700}.ant-input-number-group-wrapper-status-error .ant-input-number-group-addon{color:#f52922;border-color:#f52922}.ant-input-number-group-wrapper-status-warning .ant-input-number-group-addon{color:#fab700;border-color:#fab700}.ant-input-number{box-sizing:border-box;font-variant:tabular-nums;list-style:none;font-feature-settings:'tnum';position:relative;width:100%;min-width:0;padding:4px 15px;color:#6a7580;font-size:13px;line-height:1.38;background-color:#fff;background-image:none;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #dbe0e3;border-radius:3px}.ant-input-number::placeholder{color:#b5bdc3;user-select:none}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-number:hover{border-color:#b5bdc3;border-right-width:1px}.ant-input-number-focused,.ant-input-number:focus{border-color:#38f;box-shadow:0 0 0 0 rgba(11,104,255,.2);border-right-width:1px;outline:0}.ant-input-number-disabled{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#dbe0e3;border-right-width:1px}.ant-input-number[disabled]{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#dbe0e3;border-right-width:1px}.ant-input-number-borderless,.ant-input-number-borderless-disabled,.ant-input-number-borderless-focused,.ant-input-number-borderless:focus,.ant-input-number-borderless:hover,.ant-input-number-borderless[disabled]{background-color:transparent;border:none;box-shadow:none}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.38;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-lg{padding:6px 15px;font-size:13px}.ant-input-number-sm{padding:1px 7px}.ant-input-number-group{box-sizing:border-box;margin:0;padding:0;color:#6a7580;font-size:13px;font-variant:tabular-nums;line-height:1.38;list-style:none;font-feature-settings:'tnum';position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-number-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-number-group>[class*=col-]{padding-right:8px}.ant-input-number-group>[class*=col-]:last-child{padding-right:0}.ant-input-number-group-addon,.ant-input-number-group-wrap,.ant-input-number-group>.ant-input-number{display:table-cell}.ant-input-number-group-addon:not(:first-child):not(:last-child),.ant-input-number-group-wrap:not(:first-child):not(:last-child),.ant-input-number-group>.ant-input-number:not(:first-child):not(:last-child){border-radius:0}.ant-input-number-group-addon,.ant-input-number-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-number-group-wrap>*{display:block!important}.ant-input-number-group .ant-input-number{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-number-group .ant-input-number:focus{z-index:1;border-right-width:1px}.ant-input-number-group .ant-input-number:hover{z-index:1;border-right-width:1px}.ant-input-search-with-button .ant-input-number-group .ant-input-number:hover{z-index:0}.ant-input-number-group-addon{position:relative;padding:0 15px;color:#6a7580;font-weight:400;font-size:13px;text-align:center;background-color:#e9edee;border:1px solid #dbe0e3;border-radius:3px;transition:all .3s}.ant-input-number-group-addon .ant-select{margin:-5px -15px}.ant-input-number-group-addon .ant-select.ant-select-single:not(.ant-select-customize-input) .ant-select-selector{background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-number-group-addon .ant-select-focused .ant-select-selector,.ant-input-number-group-addon .ant-select-open .ant-select-selector{color:#0b68ff}.ant-input-number-group-addon .ant-cascader-picker{margin:-9px -16px;background-color:transparent}.ant-input-number-group-addon .ant-cascader-picker .ant-cascader-input{text-align:left;border:0;box-shadow:none}.ant-input-number-group-addon:first-child,.ant-input-number-group>.ant-input-number:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group-addon:first-child .ant-select .ant-select-selector,.ant-input-number-group>.ant-input-number:first-child .ant-select .ant-select-selector{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:first-child) .ant-input-number{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group>.ant-input-number-affix-wrapper:not(:last-child) .ant-input-number{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-group-addon:first-child{border-right:0}.ant-input-number-group-addon:last-child{border-left:0}.ant-input-number-group-addon:last-child,.ant-input-number-group>.ant-input-number:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group-addon:last-child .ant-select .ant-select-selector,.ant-input-number-group>.ant-input-number:last-child .ant-select .ant-select-selector{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group-lg .ant-input-number,.ant-input-number-group-lg>.ant-input-number-group-addon{padding:6px 15px;font-size:13px}.ant-input-number-group-sm .ant-input-number,.ant-input-number-group-sm>.ant-input-number-group-addon{padding:1px 7px}.ant-input-number-group-lg .ant-select-single .ant-select-selector{height:48px}.ant-input-number-group-sm .ant-select-single .ant-select-selector{height:24px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:last-child){border-top-left-radius:3px;border-bottom-left-radius:3px}.ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child),.ant-input-search .ant-input-number-group .ant-input-number-affix-wrapper:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-group.ant-input-number-group-compact{display:block}.ant-input-number-group.ant-input-number-group-compact::before{display:table;content:''}.ant-input-number-group.ant-input-number-group-compact::after{display:table;clear:both;content:''}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number:not(:first-child):not(:last-child):focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-input-number-affix-wrapper,.ant-input-number-group.ant-input-number-group-compact>.ant-input-number-number-affix-wrapper,.ant-input-number-group.ant-input-number-group-compact>.ant-picker-range{display:inline-flex}.ant-input-number-group.ant-input-number-group-compact>:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-number{float:none}.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector{border-right-width:1px;border-radius:0}.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:hover{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-input-group-wrapper .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-selector:focus{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select-focused{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-select>.ant-select-arrow{z-index:1}.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:first-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-select:first-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker-focused:last-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-number-group.ant-input-number-group-compact>.ant-select:last-child>.ant-select-selector,.ant-input-number-group.ant-input-number-group-compact>:last-child{border-right-width:1px;border-top-right-radius:3px;border-bottom-right-radius:3px}.ant-input-number-group.ant-input-number-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper{margin-left:-1px}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper+.ant-input-group-wrapper .ant-input-affix-wrapper{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input-group-addon>.ant-input-search-button{border-radius:0}.ant-input-number-group.ant-input-number-group-compact .ant-input-group-wrapper:not(:last-child).ant-input-search>.ant-input-group>.ant-input{border-radius:3px 0 0 3px}.ant-input-number-group>.ant-input-number-rtl:first-child{border-radius:0 3px 3px 0}.ant-input-number-group>.ant-input-number-rtl:last-child{border-radius:3px 0 0 3px}.ant-input-number-group-rtl .ant-input-number-group-addon:first-child{border-right:1px solid #dbe0e3;border-left:0;border-radius:0 3px 3px 0}.ant-input-number-group-rtl .ant-input-number-group-addon:last-child{border-right:0;border-left:1px solid #dbe0e3;border-radius:3px 0 0 3px}.ant-input-number-group-wrapper{display:inline-block;text-align:start;vertical-align:top}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:#232936;font-weight:700;line-height:0;text-align:center;border-left:1px solid #dbe0e3;transition:all .1s linear}.ant-input-number-handler:active{background:#f4f4f4}.ant-input-number-handler:hover .ant-input-number-handler-down-inner,.ant-input-number-handler:hover .ant-input-number-handler-up-inner{color:#38f}.ant-input-number-handler-down-inner,.ant-input-number-handler-up-inner{display:inline-flex;align-items:center;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:#232936;line-height:12px;transition:all .1s linear;user-select:none}.ant-input-number-handler-down-inner>*,.ant-input-number-handler-up-inner>*{line-height:1}.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}.ant-input-number-handler-down-inner::before,.ant-input-number-handler-up-inner::before{display:none}.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}.ant-input-number:hover{border-color:#b5bdc3;border-right-width:1px}.ant-input-number:hover+.ant-form-item-children-icon{opacity:0;transition:opacity .24s linear .24s}.ant-input-number-focused{border-color:#38f;box-shadow:0 0 0 0 rgba(11,104,255,.2);border-right-width:1px;outline:0}.ant-input-number-disabled{color:#b5bdc3;background-color:#f9fafb;border-color:#dbe0e3;box-shadow:none;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#dbe0e3;border-right-width:1px}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap{display:none}.ant-input-number-readonly .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 15px;text-align:left;background-color:transparent;border:0;border-radius:3px;outline:0;transition:all .3s linear;appearance:textfield!important}.ant-input-number-input::placeholder{color:#b5bdc3;user-select:none}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none;appearance:none}.ant-input-number-lg{padding:0;font-size:13px}.ant-input-number-lg input{height:46px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#fff;border-radius:0 3px 3px 0;opacity:0;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner{display:flex;align-items:center;justify-content:center;min-width:auto;margin-right:0;font-size:7px}.ant-input-number-borderless .ant-input-number-handler-wrap{border-left-width:0}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number-focused .ant-input-number-handler-wrap,.ant-input-number:hover .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:3px;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #dbe0e3;border-bottom-right-radius:3px;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;text-align:center;transform:translateY(-50%)}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-borderless .ant-input-number-handler-down{border-top-width:0}.ant-input-number-focused:not(.ant-input-number-borderless) .ant-input-number-handler-down,.ant-input-number:hover:not(.ant-input-number-borderless) .ant-input-number-handler-down{border-top:1px solid #dbe0e3}.ant-input-number-handler-down-disabled,.ant-input-number-handler-up-disabled{cursor:not-allowed}.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner,.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner{color:#b5bdc3}.ant-input-number-borderless{box-shadow:none}.ant-input-number-out-of-range input{color:#f52922}.ant-input-number-compact-item:not(.ant-input-number-compact-last-item):not(.ant-input-number-compact-item-rtl){margin-right:-1px}.ant-input-number-compact-item:not(.ant-input-number-compact-last-item).ant-input-number-compact-item-rtl{margin-left:-1px}.ant-input-number-compact-item:active,.ant-input-number-compact-item:focus,.ant-input-number-compact-item:hover{z-index:2}.ant-input-number-compact-item.ant-input-number-focused{z-index:2}.ant-input-number-compact-item[disabled]{z-index:0}.ant-input-number-compact-item:not(.ant-input-number-compact-first-item):not(.ant-input-number-compact-last-item).ant-input-number{border-radius:0}.ant-input-number-compact-item.ant-input-number.ant-input-number-compact-first-item:not(.ant-input-number-compact-last-item):not(.ant-input-number-compact-item-rtl){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-compact-item.ant-input-number.ant-input-number-compact-last-item:not(.ant-input-number-compact-first-item):not(.ant-input-number-compact-item-rtl){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-compact-item.ant-input-number.ant-input-number-compact-item-rtl.ant-input-number-compact-first-item:not(.ant-input-number-compact-last-item){border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-number-compact-item.ant-input-number.ant-input-number-compact-item-rtl.ant-input-number-compact-last-item:not(.ant-input-number-compact-first-item){border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-number-rtl{direction:rtl}.ant-input-number-rtl .ant-input-number-handler{border-right:1px solid #dbe0e3;border-left:0}.ant-input-number-rtl .ant-input-number-handler-wrap{right:auto;left:0}.ant-input-number-rtl.ant-input-number-borderless .ant-input-number-handler-wrap{border-right-width:0}.ant-input-number-rtl .ant-input-number-handler-up{border-top-right-radius:0}.ant-input-number-rtl .ant-input-number-handler-down{border-bottom-right-radius:0}.ant-input-number-rtl .ant-input-number-input{direction:ltr;text-align:right}.ant-input-number-input{height:32px;border:0;outline:0;transition:unset}.ant-input-number-input *{transition:unset}.ant-input-number{width:100%}.ant-input-number-input:focus{box-shadow:unset}.ant-input-number{border:0}.ant-input-number.ant-input-number{line-height:1.38;height:32px;box-shadow:inset 0 0 0 1px #dbe0e3}.ant-input-number.ant-input-number input::placeholder{color:#949ea6}.ant-input-number.ant-input-number.ant-input-number-focused{box-shadow:inset 0 0 0 2px #0b68ff;border:0!important}.ant-input-number.ant-input-number.ant-input-number-lg{height:48px}.ant-input-number.ant-input-number.ant-input-number-lg .ant-input-number-input-wrap{height:44px}.ant-input-number .ant-input-number-handler svg{display:none}.ant-input-number.ant-input-number.ant-input-number-focused .ant-input-number-handler-wrap{width:21px;height:28px;top:2px;right:2px}.ant-input-number.ant-input-number.ant-input-number-lg.ant-input-number-focused .ant-input-number-handler-wrap{height:44px}.ant-input-number.ant-input-number.ant-input-number-lg .ant-input-number-handler-wrap{width:24px;height:44px}.ant-input-number .ant-input-number-handler-wrap{z-index:3;height:30px;top:1px;right:1px}.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-down,.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-up{background-repeat:no-repeat;background-size:cover;background-position:center}.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-down i,.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-up i{display:none}.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-up{background-image:url('data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAxLWFuZ2xlLXVwLXM8L3RpdGxlPjxyZWN0IGlkPSJjYW52YXMiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ii8+PHBhdGggZmlsbD0iIzZhNzU4MCIgZD0iTTE1LjI1LDE0LjM4YS43OS43OSwwLDAsMS0uNTMtLjIyTDEyLDExLjQ0LDkuMjgsMTQuMTZhLjc1Ljc1LDAsMCwxLTEuMDYsMCwuNzcuNzcsMCwwLDEsMC0xLjA3bDMuMjUtMy4yNWEuNzUuNzUsMCwwLDEsMS4wNiwwbDMuMjUsMy4yNWEuNzcuNzcsMCwwLDEsMCwxLjA3QS43OS43OSwwLDAsMSwxNS4yNSwxNC4zOFoiLz48L3N2Zz4=')}.ant-input-number .ant-input-number-handler-wrap .ant-input-number-handler-down{background-image:url('data:image/svg+xml;base64,PHN2ZyBpZD0iaWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDI0IDI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPjAxLTAzLWFuZ2xlLWRvd24tczwvdGl0bGU+PHJlY3QgaWQ9ImNhbnZhcyIgY2xhc3M9ImNscy0xIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48cGF0aCBmaWxsPSIjNmE3NTgwIiBkPSJNMTIsMTQuMzhhLjc5Ljc5LDAsMCwxLS41My0uMjJMOC4yMiwxMC45MWEuNzcuNzcsMCwwLDEsMC0xLjA3Ljc1Ljc1LDAsMCwxLDEuMDYsMEwxMiwxMi41NmwyLjcyLTIuNzJhLjc1Ljc1LDAsMCwxLDEuMDYsMCwuNzcuNzcsMCwwLDEsMCwxLjA3bC0zLjI1LDMuMjVBLjc5Ljc5LDAsMCwxLDEyLDE0LjM4WiIvPjwvc3ZnPg==')}.ant-input-number .ant-input-number-input-wrap{height:28px;margin-top:2px;margin:2px}.ant-input-number .ant-input-number-input{position:relative;top:0;height:inherit}.ant-input-number.ant-input-number-focused .ant-input-number-input{top:0;box-shadow:unset}.ant-input-number.error.ant-input-number.error{box-shadow:inset 0 0 0 2px #f52922}.ant-input-number.error .ant-input-number-handler-wrap{z-index:3;width:21px;height:28px;top:2px;right:2px}.ant-input-group.ant-input-group-compact>:not(:first-child).ant-input-number.error{margin-left:-2px}.ant-input-group.ant-input-group-compact>:not(:last-child).ant-input-number.error{margin-right:-2px}