Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 43 additions & 33 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as React from 'react';
import useCursor from './hooks/useCursor';
import StepHandler from './StepHandler';
import { getDecupleSteps } from './utils/numberUtil';
import SemanticContext from './SemanticContext';

import type { HolderRef } from '@rc-component/input/lib/BaseInput';
import { BaseInputProps } from '@rc-component/input/lib/interface';
Expand Down Expand Up @@ -53,6 +54,7 @@ const getDecimalIfValidate = (value: ValueType) => {
return decimal.isInvalidate() ? null : decimal;
};

type SemanticName = 'handle' | 'input';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indicator or actions?

export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<
React.InputHTMLAttributes<HTMLInputElement>,
Expand All @@ -76,9 +78,8 @@ export interface InputNumberProps<T extends ValueType = ValueType>
suffix?: React.ReactNode;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
classNames?: BaseInputProps['classNames'] & {
input?: string;
};
classNames?: BaseInputProps['classNames'] & Partial<Record<SemanticName, string>>;
styles?: BaseInputProps['styles'] & Partial<Record<SemanticName, React.CSSProperties>>;

// Customize handler node
upHandler?: React.ReactNode;
Expand All @@ -99,7 +100,10 @@ export interface InputNumberProps<T extends ValueType = ValueType>
onChange?: (value: T | null) => void;
onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;

onStep?: (value: T, info: { offset: ValueType; type: 'up' | 'down', emitter: 'handler' | 'keyboard' | 'wheel' }) => void;
onStep?: (
value: T,
info: { offset: ValueType; type: 'up' | 'down'; emitter: 'handler' | 'keyboard' | 'wheel' },
) => void;

/**
* Trigger change onBlur event.
Expand Down Expand Up @@ -131,7 +135,6 @@ const InternalInputNumber = React.forwardRef(
changeOnWheel = false,
controls = true,

classNames,
stringMode,

parser,
Expand All @@ -151,6 +154,8 @@ const InternalInputNumber = React.forwardRef(
...inputProps
} = props;

const { classNames, styles } = React.useContext(SemanticContext) || {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这边不对,InputNumber 已经是顶层了,没有需要从 context 拿的东西


const inputClassName = `${prefixCls}-input`;

const inputRef = React.useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -614,7 +619,7 @@ const InternalInputNumber = React.forwardRef(
onStep={onInternalStep}
/>
)}
<div className={`${inputClassName}-wrap`}>
<div className={clsx(`${inputClassName}-wrap`, classNames?.handle)} style={styles?.handle}>
<input
autoComplete="off"
role="spinbutton"
Expand Down Expand Up @@ -648,6 +653,7 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
addonAfter,
className,
classNames,
styles,
...rest
} = props;

Expand All @@ -669,35 +675,39 @@ const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, r
);

return (
<BaseInput
className={className}
triggerFocus={focus}
prefixCls={prefixCls}
value={value}
disabled={disabled}
style={style}
prefix={prefix}
suffix={suffix}
addonAfter={addonAfter}
addonBefore={addonBefore}
classNames={classNames}
components={{
affixWrapper: 'div',
groupWrapper: 'div',
wrapper: 'div',
groupAddon: 'div',
}}
ref={holderRef}
>
<InternalInputNumber
<SemanticContext.Provider value={{ classNames, styles }}>
<BaseInput
className={className}
triggerFocus={focus}
prefixCls={prefixCls}
value={value}
disabled={disabled}
ref={inputFocusRef}
domRef={inputNumberDomRef}
className={classNames?.input}
{...rest}
/>
</BaseInput>
style={style}
prefix={prefix}
suffix={suffix}
addonAfter={addonAfter}
addonBefore={addonBefore}
classNames={classNames}
styles={styles}
components={{
affixWrapper: 'div',
groupWrapper: 'div',
wrapper: 'div',
groupAddon: 'div',
}}
ref={holderRef}
>
<InternalInputNumber
prefixCls={prefixCls}
disabled={disabled}
ref={inputFocusRef}
domRef={inputNumberDomRef}
className={classNames?.input}
style={styles?.input}
{...rest}
/>
</BaseInput>
</SemanticContext.Provider>
);
}) as (<T extends ValueType = ValueType>(
props: React.PropsWithChildren<InputNumberProps<T>> & {
Expand Down
11 changes: 11 additions & 0 deletions src/SemanticContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { InputNumberProps } from './InputNumber';

interface SemanticContextProps {
classNames?: InputNumberProps['classNames'];
styles?: InputNumberProps['styles'];
}

const SemanticContext = React.createContext<SemanticContextProps | undefined>(undefined);

export default SemanticContext;
28 changes: 16 additions & 12 deletions src/StepHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable react/no-unknown-property */
import * as React from 'react';
import classNames from 'classnames';
import cls from 'classnames';
import useMobile from '@rc-component/util/lib/hooks/useMobile';
import raf from '@rc-component/util/lib/raf';
import SemanticContext from './SemanticContext';

/**
* When click and hold on a button - the speed of auto changing the value.
Expand All @@ -22,7 +23,6 @@ export interface StepHandlerProps {
downDisabled?: boolean;
onStep: (up: boolean, emitter: 'handler' | 'keyboard' | 'wheel') => void;
}

export default function StepHandler({
prefixCls,
upNode,
Expand All @@ -38,11 +38,12 @@ export default function StepHandler({
const onStepRef = React.useRef<StepHandlerProps['onStep']>();
onStepRef.current = onStep;

const { classNames, styles } = React.useContext(SemanticContext) || {};

const onStopStep = () => {
clearTimeout(stepTimeoutRef.current);
};


// We will interval update step when hold mouse down
const onStepMouseDown = (e: React.MouseEvent, up: boolean) => {
e.preventDefault();
Expand All @@ -61,10 +62,13 @@ export default function StepHandler({
stepTimeoutRef.current = setTimeout(loopStep, STEP_DELAY);
};

React.useEffect(() => () => {
onStopStep();
frameIds.current.forEach(id => raf.cancel(id));
}, []);
React.useEffect(
() => () => {
onStopStep();
frameIds.current.forEach((id) => raf.cancel(id));
},
[],
);

// ======================= Render =======================
const isMobile = useMobile();
Expand All @@ -74,16 +78,16 @@ export default function StepHandler({

const handlerClassName = `${prefixCls}-handler`;

const upClassName = classNames(handlerClassName, `${handlerClassName}-up`, {
const upClassName = cls(handlerClassName, `${handlerClassName}-up`, {
[`${handlerClassName}-up-disabled`]: upDisabled,
});
const downClassName = classNames(handlerClassName, `${handlerClassName}-down`, {
const downClassName = cls(handlerClassName, `${handlerClassName}-down`, {
[`${handlerClassName}-down-disabled`]: downDisabled,
});

// fix: https://github.com/ant-design/ant-design/issues/43088
// In Safari, When we fire onmousedown and onmouseup events in quick succession,
// there may be a problem that the onmouseup events are executed first,
// In Safari, When we fire onmousedown and onmouseup events in quick succession,
// there may be a problem that the onmouseup events are executed first,
// resulting in a disordered program execution.
// So, we need to use requestAnimationFrame to ensure that the onmouseup event is executed after the onmousedown event.
const safeOnStopStep = () => frameIds.current.push(raf(onStopStep));
Expand All @@ -96,7 +100,7 @@ export default function StepHandler({
};

return (
<div className={`${handlerClassName}-wrap`}>
<div className={cls(`${handlerClassName}-wrap`, classNames?.handle)} style={styles?.handle}>
<span
{...sharedHandlerProps}
onMouseDown={(e) => {
Expand Down
41 changes: 41 additions & 0 deletions tests/semantic.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render } from '@testing-library/react';
import InputNumber from '../src';
import React from 'react';

describe('InputNumber.Semantic', () => {
it('support classNames and styles', () => {
const testClassNames = {
prefix: 'test-prefix',
input: 'test-input',
suffix: 'test-suffix',
handle: 'test-handle',
};
const testStyles = {
prefix: { color: 'red' },
input: { color: 'blue' },
suffix: { color: 'green' },
handle: { color: 'yellow' },
};
const { container } = render(
<InputNumber
prefixCls="rc-input-number"
prefix="prefix"
suffix={<div>suffix</div>}
styles={testStyles}
classNames={testClassNames}
/>,
);
const input = container.querySelector('.rc-input-number')!;
const prefix = container.querySelector('.rc-input-number-prefix')!;
const suffix = container.querySelector('.rc-input-number-suffix')!;
const handle = container.querySelector('.rc-input-number-input-wrap')!;
expect(input.className).toContain(testClassNames.input);
expect(prefix.className).toContain(testClassNames.prefix);
expect(suffix.className).toContain(testClassNames.suffix);
expect(handle.className).toContain(testClassNames.handle);
expect(prefix.style.color).toBe(testStyles.prefix.color);
expect(input.style.color).toBe(testStyles.input.color);
expect(suffix.style.color).toBe(testStyles.suffix.color);
expect(handle.style.color).toBe(testStyles.handle.color);
});
});