This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import * as React from 'react';
import type { InputNumberProps as RcInputNumberProps, InputNumberRef as RcInputNumberRef, ValueType } from '@rc-component/input-number';
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
import type { InputStatus } from '../_util/statusUtils';
import type { Variant } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
export type InputNumberSemanticName = keyof InputNumberSemanticClassNames & keyof InputNumberSemanticStyles;
export type InputNumberSemanticClassNames = {
root?: string;
prefix?: string;
suffix?: string;
input?: string;
actions?: string;
};
export type InputNumberSemanticStyles = {
root?: React.CSSProperties;
prefix?: React.CSSProperties;
suffix?: React.CSSProperties;
input?: React.CSSProperties;
actions?: React.CSSProperties;
};
export type InputNumberClassNamesType<T extends ValueType = ValueType> = SemanticClassNamesType<InputNumberProps<T>, InputNumberSemanticClassNames>;
export type InputNumberStylesType<T extends ValueType = ValueType> = SemanticStylesType<InputNumberProps<T>, InputNumberSemanticStyles>;
export interface InputNumberProps<T extends ValueType = ValueType> extends Omit<RcInputNumberProps<T>, 'prefix' | 'size' | 'controls' | 'classNames' | 'styles'> {
prefixCls?: string;
rootClassName?: string;
classNames?: InputNumberClassNamesType;
styles?: InputNumberStylesType;
/**
* @deprecated Use `Space.Compact` instead.
*
* @example
* ```tsx
* import { Space, InputNumber } from 'antd';
*
* <Space.Compact>
* {addon}
* <InputNumber defaultValue={1} />
* </Space.Compact>
* ```
*/
addonBefore?: React.ReactNode;
/**
* @deprecated Use `Space.Compact` instead.
*
* @example
* ```tsx
* import { Space, InputNumber } from 'antd';
*
* <Space.Compact>
* <InputNumber defaultValue={1} />
* {addon}
* </Space.Compact>
* ```
*/
addonAfter?: React.ReactNode;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
size?: SizeType;
disabled?: boolean;
/** @deprecated Use `variant` instead. */
bordered?: boolean;
status?: InputStatus;
controls?: boolean | {
upIcon?: React.ReactNode;
downIcon?: React.ReactNode;
};
/**
* @since 5.13.0
* @default "outlined"
*/
variant?: Variant;
}
declare const TypedInputNumber: (<T extends ValueType = ValueType>(props: React.PropsWithChildren<InputNumberProps<T>> & React.RefAttributes<RcInputNumberRef>) => React.ReactElement) & {
displayName?: string;
_InternalPanelDoNotUseOrYouWillBeFired: typeof PureInputNumber;
};
/** @private Internal Component. Do not use in your production. */
declare const PureInputNumber: React.FC<InputNumberProps>;
export default TypedInputNumber;
+205
View File
@@ -0,0 +1,205 @@
"use client";
import * as React from 'react';
import DownOutlined from "@ant-design/icons/es/icons/DownOutlined";
import MinusOutlined from "@ant-design/icons/es/icons/MinusOutlined";
import PlusOutlined from "@ant-design/icons/es/icons/PlusOutlined";
import UpOutlined from "@ant-design/icons/es/icons/UpOutlined";
import RcInputNumber from '@rc-component/input-number';
import { clsx } from 'clsx';
import ContextIsolator from '../_util/ContextIsolator';
import { useMergeSemantic } from '../_util/hooks';
import { isPlainObject } from '../_util/is';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
import ConfigProvider from '../config-provider';
import { useComponentConfig } from '../config-provider/context';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize';
import { FormItemInputContext } from '../form/context';
import useVariant from '../form/hooks/useVariants';
import SpaceAddon from '../space/Addon';
import Compact, { useCompactItemContext } from '../space/Compact';
import useStyle from './style';
const InternalInputNumber = /*#__PURE__*/React.forwardRef((props, ref) => {
const inputRef = React.useRef(null);
React.useImperativeHandle(ref, () => inputRef.current);
const {
rootClassName,
size: customizeSize,
disabled: customDisabled,
prefixCls,
addonBefore: _addonBefore,
addonAfter: _addonAfter,
prefix,
suffix,
bordered,
readOnly,
status,
controls = true,
variant: customVariant,
className,
style,
classNames,
styles,
mode,
...others
} = props;
const {
direction,
className: contextClassName,
style: contextStyle,
styles: contextStyles,
classNames: contextClassNames
} = useComponentConfig('inputNumber');
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
// controls && !mergedDisabled && !readOnly;
const mergedControls = React.useMemo(() => {
if (!controls || mergedDisabled || readOnly) {
return false;
}
return controls;
}, [controls, mergedDisabled, readOnly]);
const {
compactSize,
compactItemClassnames
} = useCompactItemContext(prefixCls, direction);
let upIcon = mode === 'spinner' ? /*#__PURE__*/React.createElement(PlusOutlined, null) : /*#__PURE__*/React.createElement(UpOutlined, null);
let downIcon = mode === 'spinner' ? /*#__PURE__*/React.createElement(MinusOutlined, null) : /*#__PURE__*/React.createElement(DownOutlined, null);
const controlsTemp = typeof mergedControls === 'boolean' ? mergedControls : undefined;
if (isPlainObject(mergedControls)) {
upIcon = mergedControls.upIcon || upIcon;
downIcon = mergedControls.downIcon || downIcon;
}
const {
hasFeedback,
isFormItemInput,
feedbackIcon
} = React.useContext(FormItemInputContext);
const mergedSize = useSize(ctx => customizeSize ?? compactSize ?? ctx);
const [variant, enableVariantCls] = useVariant('inputNumber', customVariant, bordered);
const suffixNode = hasFeedback && /*#__PURE__*/React.createElement(React.Fragment, null, feedbackIcon);
// =========== Merged Props for Semantic ==========
const mergedProps = {
...props,
size: mergedSize,
disabled: mergedDisabled,
controls: mergedControls
};
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
props: mergedProps
});
return /*#__PURE__*/React.createElement(RcInputNumber, {
ref: inputRef,
mode: mode,
disabled: mergedDisabled,
className: clsx(className, rootClassName, mergedClassNames.root, contextClassName, compactItemClassnames, getStatusClassNames(prefixCls, status, hasFeedback), {
[`${prefixCls}-${variant}`]: enableVariantCls,
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-in-form-item`]: isFormItemInput,
[`${prefixCls}-without-controls`]: !mergedControls
}),
style: {
...mergedStyles.root,
...contextStyle,
...style
},
upHandler: upIcon,
downHandler: downIcon,
prefixCls: prefixCls,
readOnly: readOnly,
controls: controlsTemp,
prefix: prefix,
suffix: suffixNode || suffix,
classNames: mergedClassNames,
styles: mergedStyles,
...others
});
});
// ===================================================================
// == InputNumber ==
// ===================================================================
const InputNumber = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
addonBefore,
addonAfter,
prefixCls: customizePrefixCls,
className,
status: customStatus,
rootClassName,
...rest
} = props;
const {
getPrefixCls
} = useComponentConfig('inputNumber');
const prefixCls = getPrefixCls('input-number', customizePrefixCls);
const {
status: contextStatus
} = React.useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const hasLegacyAddon = addonBefore || addonAfter;
// ======================= Warn =======================
if (process.env.NODE_ENV !== 'production') {
const typeWarning = devUseWarning('InputNumber');
[['bordered', 'variant'], ['addonAfter', 'Space.Compact'], ['addonBefore', 'Space.Compact']].forEach(([prop, newProp]) => {
typeWarning.deprecated(!(prop in props), prop, newProp);
});
typeWarning(!(props.type === 'number' && props.changeOnWheel), 'usage', 'When `type=number` is used together with `changeOnWheel`, changeOnWheel may not work properly. Please delete `type=number` if it is not necessary.');
}
// ====================== Render ======================
const inputNumberNode = /*#__PURE__*/React.createElement(InternalInputNumber, {
ref: ref,
...rest,
prefixCls: prefixCls,
status: mergedStatus,
className: clsx(cssVarCls, rootCls, hashId, className),
rootClassName: !hasLegacyAddon ? rootClassName : undefined
});
if (hasLegacyAddon) {
const renderAddon = node => {
if (!node) {
return null;
}
return /*#__PURE__*/React.createElement(SpaceAddon, {
className: clsx(`${prefixCls}-addon`, cssVarCls, hashId),
variant: props.variant,
disabled: props.disabled,
status: mergedStatus
}, /*#__PURE__*/React.createElement(ContextIsolator, {
form: true
}, node));
};
const addonBeforeNode = renderAddon(addonBefore);
const addonAfterNode = renderAddon(addonAfter);
return /*#__PURE__*/React.createElement(Compact, {
rootClassName: rootClassName
}, addonBeforeNode, inputNumberNode, addonAfterNode);
}
return inputNumberNode;
});
const TypedInputNumber = InputNumber;
/** @private Internal Component. Do not use in your production. */
const PureInputNumber = props => (/*#__PURE__*/React.createElement(ConfigProvider, {
theme: {
components: {
InputNumber: {
handleVisible: true
}
}
}
}, /*#__PURE__*/React.createElement(InputNumber, {
...props
})));
if (process.env.NODE_ENV !== 'production') {
InternalInputNumber.displayName = 'InternalInputNumber';
TypedInputNumber.displayName = 'InputNumber';
}
TypedInputNumber._InternalPanelDoNotUseOrYouWillBeFired = PureInputNumber;
export default TypedInputNumber;
+4
View File
@@ -0,0 +1,4 @@
import type { ComponentToken } from './token';
export type { ComponentToken };
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
export default _default;
+316
View File
@@ -0,0 +1,316 @@
import { unit } from '@ant-design/cssinjs';
import { genBasicInputStyle, genPlaceholderStyle, initInputToken } from '../../input/style';
import { genBorderlessStyle, genFilledStyle, genOutlinedStyle, genUnderlinedStyle } from '../../input/style/variants';
import { resetComponent, resetIcon } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import { genCssVar } from '../../theme/util/genStyleUtils';
import { prepareComponentToken } from './token';
const genInputNumberStyles = token => {
const {
componentCls,
lineWidth,
lineType,
borderRadius,
inputFontSizeSM,
inputFontSizeLG,
colorError,
paddingInlineSM,
paddingBlockSM,
paddingBlockLG,
paddingInlineLG,
colorIcon,
colorTextDisabled,
motionDurationMid,
handleHoverColor,
handleOpacity,
paddingInline,
paddingBlock,
handleBg,
handleActiveBg,
inputAffixPadding,
borderRadiusSM,
controlWidth,
handleBorderColor,
filledHandleBg,
lineHeightLG,
antCls
} = token;
const borderStyle = `${unit(lineWidth)} ${lineType} ${handleBorderColor}`;
const [varName, varRef] = genCssVar(antCls, 'input-number');
return [
// ==========================================================
// == Base ==
// ==========================================================
{
[componentCls]: {
...resetComponent(token),
...genBasicInputStyle(token),
[varName('input-padding-block')]: unit(paddingBlock),
[varName('input-padding-inline')]: unit(paddingInline),
display: 'inline-flex',
width: controlWidth,
margin: 0,
paddingBlock: 0,
borderRadius,
// ======================= Variants =======================
...genOutlinedStyle(token, {
[`${componentCls}-actions`]: {
background: handleBg,
[`${componentCls}-action-down`]: {
borderBlockStart: borderStyle
}
}
}),
...genFilledStyle(token, {
[`${componentCls}-actions`]: {
background: filledHandleBg,
[`${componentCls}-action-down`]: {
borderBlockStart: borderStyle
}
},
'&:focus-within': {
[`${componentCls}-actions`]: {
background: handleBg
}
}
}),
...genUnderlinedStyle(token, {
[`${componentCls}-actions`]: {
background: handleBg,
[`${componentCls}-action-down`]: {
borderBlockStart: borderStyle
}
}
}),
...genBorderlessStyle(token),
// InputNumber 两层结构:borderless 补偿只加在内层 input 的 CSS 变量上,避免外层+内层双重 padding 导致高度异常
[`&${componentCls}-borderless`]: {
paddingBlock: 0,
[varName('input-padding-block')]: unit(token.calc(paddingBlock).add(lineWidth).equal())
},
[`&${componentCls}-borderless${componentCls}-sm`]: {
paddingBlock: 0,
[varName('input-padding-block')]: unit(token.calc(paddingBlockSM).add(lineWidth).equal())
},
[`&${componentCls}-borderless${componentCls}-lg`]: {
paddingBlock: 0,
[varName('input-padding-block')]: unit(token.calc(paddingBlockLG).add(lineWidth).equal())
},
// ========================= RTL ==========================
'&-rtl': {
direction: 'rtl',
[`${componentCls}-input`]: {
direction: 'rtl'
}
},
// ===================== Out Of Range =====================
[`&${componentCls}-out-of-range`]: {
[`${componentCls}-input`]: {
color: colorError
}
},
// ======================== Input =========================
[`${componentCls}-input`]: {
...resetComponent(token),
width: '100%',
paddingBlock: varRef('input-padding-block'),
textAlign: 'start',
backgroundColor: 'transparent',
border: 0,
borderRadius: 0,
outline: 0,
transition: `all ${motionDurationMid} linear`,
appearance: 'textfield',
fontSize: 'inherit',
lineHeight: 'inherit',
...genPlaceholderStyle(token.colorTextPlaceholder),
'&[type="number"]::-webkit-inner-spin-button, &[type="number"]::-webkit-outer-spin-button': {
margin: 0,
appearance: 'none'
}
},
[`&:hover ${componentCls}-handler-wrap, &-focused ${componentCls}-handler-wrap`]: {
width: token.handleWidth,
opacity: 1
},
// ======================= Disabled =======================
[`&-disabled ${componentCls}-input`]: {
cursor: 'not-allowed',
color: token.colorTextDisabled
}
}
},
// ==========================================================
// == Action ==
// ==========================================================
{
[componentCls]: {
// ======================= Shared =======================
[`${componentCls}-action`]: {
...resetIcon(),
userSelect: 'none',
overflow: 'hidden',
fontWeight: 'bold',
lineHeight: 0,
textAlign: 'center',
cursor: 'pointer',
transition: `all ${motionDurationMid} linear`,
// Active: change background not disabled only;
[`&:active:not(${componentCls}-action-up-disabled):not(${componentCls}-action-down-disabled)`]: {
background: handleActiveBg
},
// Hover: change color not disabled only;
[`&:hover:not(${componentCls}-action-up-disabled):not(${componentCls}-action-down-disabled)`]: {
color: handleHoverColor
},
[`&${componentCls}-action-up-disabled, &${componentCls}-action-down-disabled`]: {
cursor: 'not-allowed',
color: colorTextDisabled
}
},
// ===================== Input Mode =====================
'&-mode-input': {
overflow: 'hidden',
[`${componentCls}-actions`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineEnd: 0,
width: token.handleVisibleWidth,
opacity: handleOpacity,
height: '100%',
borderRadius: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
transition: `all ${motionDurationMid}`,
overflow: 'hidden',
// Fix input number inside Menu makes icon too large
// We arise the selector priority by nest selector here
// https://github.com/ant-design/ant-design/issues/14367
[`${componentCls}-action`]: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: 'auto',
height: '40%',
marginInlineEnd: 0,
fontSize: token.handleFontSize
}
},
[`&:hover ${componentCls}-actions, &-focused ${componentCls}-actions`]: {
width: token.handleWidth,
opacity: 1
},
[`${componentCls}-action`]: {
color: colorIcon,
height: '50%',
borderInlineStart: borderStyle,
// Hover: change height not disabled only;
[`&:hover:not(${componentCls}-action-up-disabled):not(${componentCls}-action-down-disabled)`]: {
height: `60%`
}
},
[`&${componentCls}-disabled, &${componentCls}-readonly`]: {
[`${componentCls}-actions`]: {
display: 'none'
}
}
},
// ==================== Spinner Mode ====================
[`&${componentCls}-mode-spinner`]: {
padding: 0,
width: 'auto',
[`${componentCls}-action`]: {
flex: 'none',
paddingInline: varRef('input-padding-inline'),
'&-up': {
borderInlineStart: borderStyle
},
'&-down': {
borderInlineEnd: borderStyle
}
},
[`${componentCls}-input`]: {
textAlign: 'center',
paddingInline: varRef('input-padding-inline')
}
}
}
},
// ==========================================================
// == Size ==
// ==========================================================
{
[componentCls]: {
'&-lg': {
[varName('input-padding-block')]: unit(paddingBlockLG),
[varName('input-padding-inline')]: unit(paddingInlineLG),
paddingBlock: 0,
fontSize: inputFontSizeLG,
lineHeight: lineHeightLG
},
'&-sm': {
[varName('input-padding-block')]: unit(paddingBlockSM),
[varName('input-padding-inline')]: unit(paddingInlineSM),
paddingBlock: 0,
fontSize: inputFontSizeSM,
borderRadius: borderRadiusSM
}
}
},
// ==========================================================
// == Pre/Suffix ==
// ==========================================================
{
[componentCls]: {
[`${componentCls}-prefix, ${componentCls}-suffix`]: {
display: 'flex',
flex: 'none',
alignItems: 'center',
alignSelf: 'center',
pointerEvents: 'none'
},
[`${componentCls}-prefix`]: {
marginInlineEnd: inputAffixPadding
},
[`${componentCls}-suffix`]: {
height: '100%',
marginInlineStart: inputAffixPadding,
transition: `margin ${motionDurationMid}`
},
[`&:hover:not(${componentCls}-without-controls)`]: {
[`${componentCls}-suffix`]: {
marginInlineEnd: token.handleWidth
}
}
}
}];
};
const genCompatibleStyles = token => {
const {
componentCls,
antCls
} = token;
return {
[`${componentCls}-addon`]: {
[`&:has(${antCls}-select)`]: {
border: 0,
padding: 0
}
}
};
};
export default genStyleHooks('InputNumber', token => {
const inputNumberToken = mergeToken(token, initInputToken(token));
return [genInputNumberStyles(inputNumberToken), genCompatibleStyles(inputNumberToken),
// =====================================================
// == Space Compact ==
// =====================================================
genCompactItemStyle(inputNumberToken)];
}, prepareComponentToken, {
unitless: {
handleOpacity: true
},
resetFont: false
});
+52
View File
@@ -0,0 +1,52 @@
import type { SharedComponentToken, SharedInputToken } from '../../input/style/token';
import type { FullToken, GetDefaultToken } from '../../theme/internal';
export interface ComponentToken extends SharedComponentToken {
/**
* @desc 输入框宽度
* @descEN Width of input
*/
controlWidth: number;
/**
* @desc 操作按钮宽度
* @descEN Width of control button
*/
handleWidth: number;
/**
* @desc 操作按钮图标大小
* @descEN Icon size of control button
*/
handleFontSize: number;
/**
* Default `auto`. Set `true` will always show the handle
* @desc 操作按钮可见性
* @descEN Handle visible
*/
handleVisible: 'auto' | true;
/**
* @desc 操作按钮背景色
* @descEN Background color of handle
*/
handleBg: string;
/**
* @desc 操作按钮激活背景色
* @descEN Active background color of handle
*/
handleActiveBg: string;
/**
* @desc 操作按钮悬浮颜色
* @descEN Hover color of handle
*/
handleHoverColor: string;
/**
* @desc 操作按钮边框颜色
* @descEN Border color of handle
*/
handleBorderColor: string;
/**
* @desc 面性变体操作按钮背景色
* @descEN Background color of handle in filled variant
*/
filledHandleBg: string;
}
export type InputNumberToken = FullToken<'InputNumber'> & SharedInputToken;
export declare const prepareComponentToken: GetDefaultToken<'InputNumber'>;
+20
View File
@@ -0,0 +1,20 @@
import { FastColor } from '@ant-design/fast-color';
import { initComponentToken } from '../../input/style/token';
export const prepareComponentToken = token => {
const handleVisible = token.handleVisible ?? 'auto';
const handleWidth = token.controlHeightSM - token.lineWidth * 2;
return {
...initComponentToken(token),
controlWidth: 90,
handleWidth,
handleFontSize: token.fontSize / 2,
handleVisible,
handleActiveBg: token.colorFillAlter,
handleBg: token.colorBgContainer,
filledHandleBg: new FastColor(token.colorFillSecondary).onBackground(token.colorBgContainer).toHexString(),
handleHoverColor: token.colorPrimary,
handleBorderColor: token.colorBorder,
handleOpacity: handleVisible === true ? 1 : 0,
handleVisibleWidth: handleVisible === true ? handleWidth : 0
};
};