Skip to content

Commit de38d1a

Browse files
authored
feat: support nativeElement (#643)
* feat: support nativeElement * chore: update deps
1 parent 882958b commit de38d1a

File tree

7 files changed

+104
-72
lines changed

7 files changed

+104
-72
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
"@babel/runtime": "^7.10.1",
5050
"@rc-component/mini-decimal": "^1.0.1",
5151
"classnames": "^2.2.5",
52-
"rc-input": "~1.4.0",
53-
"rc-util": "^5.28.0"
52+
"rc-input": "~1.5.0",
53+
"rc-util": "^5.40.1"
5454
},
5555
"devDependencies": {
5656
"@rc-component/father-plugin": "^1.0.1",

src/InputNumber.tsx

Lines changed: 77 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,24 @@ import getMiniDecimal, {
99
import clsx from 'classnames';
1010
import { BaseInput } from 'rc-input';
1111
import { useLayoutUpdateEffect } from 'rc-util/lib/hooks/useLayoutEffect';
12+
import proxyObject from 'rc-util/lib/proxyObject';
1213
import { composeRef } from 'rc-util/lib/ref';
1314
import * as React from 'react';
1415
import useCursor from './hooks/useCursor';
1516
import StepHandler from './StepHandler';
1617
import { getDecupleSteps } from './utils/numberUtil';
1718

19+
import type { HolderRef } from 'rc-input/lib/BaseInput';
20+
import { BaseInputProps } from 'rc-input/lib/interface';
1821
import { InputFocusOptions, triggerFocus } from 'rc-input/lib/utils/commonUtils';
1922
import useFrame from './hooks/useFrame';
20-
import { BaseInputProps } from 'rc-input/lib/interface';
2123

2224
export type { ValueType };
2325

26+
export interface InputNumberRef extends HTMLInputElement {
27+
nativeElement: HTMLElement;
28+
}
29+
2430
/**
2531
* We support `stringMode` which need handle correct type when user call in onChange
2632
* format max or min value
@@ -100,12 +106,14 @@ export interface InputNumberProps<T extends ValueType = ValueType>
100106
changeOnBlur?: boolean;
101107
}
102108

103-
type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'>;
109+
type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'> & {
110+
domRef: React.Ref<HTMLDivElement>;
111+
};
104112

105113
const InternalInputNumber = React.forwardRef(
106114
(props: InternalInputNumberProps, ref: React.Ref<HTMLInputElement>) => {
107115
const {
108-
prefixCls = 'rc-input-number',
116+
prefixCls,
109117
className,
110118
style,
111119
min,
@@ -136,6 +144,8 @@ const InternalInputNumber = React.forwardRef(
136144

137145
changeOnBlur = true,
138146

147+
domRef,
148+
139149
...inputProps
140150
} = props;
141151

@@ -572,6 +582,7 @@ const InternalInputNumber = React.forwardRef(
572582
// ============================ Render ============================
573583
return (
574584
<div
585+
ref={domRef}
575586
className={clsx(prefixCls, className, {
576587
[`${prefixCls}-focused`]: focus,
577588
[`${prefixCls}-disabled`]: disabled,
@@ -622,66 +633,76 @@ const InternalInputNumber = React.forwardRef(
622633
},
623634
);
624635

625-
const InputNumber = React.forwardRef(
626-
(props: InputNumberProps, ref: React.Ref<HTMLInputElement>) => {
627-
const {
628-
disabled,
629-
style,
630-
prefixCls,
631-
value,
632-
prefix,
633-
suffix,
634-
addonBefore,
635-
addonAfter,
636-
className,
637-
classNames,
638-
...rest
639-
} = props;
640-
641-
const inputFocusRef = React.useRef<HTMLInputElement>(null);
642-
643-
const focus = (option?: InputFocusOptions) => {
644-
if (inputFocusRef.current) {
645-
triggerFocus(inputFocusRef.current, option);
646-
}
647-
};
636+
const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, ref) => {
637+
const {
638+
disabled,
639+
style,
640+
prefixCls = 'rc-input-number',
641+
value,
642+
prefix,
643+
suffix,
644+
addonBefore,
645+
addonAfter,
646+
className,
647+
classNames,
648+
...rest
649+
} = props;
650+
651+
const holderRef = React.useRef<HolderRef>(null);
652+
const inputNumberDomRef = React.useRef<HTMLDivElement>(null);
653+
const inputFocusRef = React.useRef<HTMLInputElement>(null);
654+
655+
const focus = (option?: InputFocusOptions) => {
656+
if (inputFocusRef.current) {
657+
triggerFocus(inputFocusRef.current, option);
658+
}
659+
};
648660

649-
return (
650-
<BaseInput
651-
className={className}
652-
triggerFocus={focus}
661+
React.useImperativeHandle(ref, () =>
662+
proxyObject(inputFocusRef.current, {
663+
nativeElement: holderRef.current.nativeElement || inputNumberDomRef.current,
664+
}),
665+
);
666+
667+
return (
668+
<BaseInput
669+
className={className}
670+
triggerFocus={focus}
671+
prefixCls={prefixCls}
672+
value={value}
673+
disabled={disabled}
674+
style={style}
675+
prefix={prefix}
676+
suffix={suffix}
677+
addonAfter={addonAfter}
678+
addonBefore={addonBefore}
679+
classNames={classNames}
680+
components={{
681+
affixWrapper: 'div',
682+
groupWrapper: 'div',
683+
wrapper: 'div',
684+
groupAddon: 'div',
685+
}}
686+
ref={holderRef}
687+
>
688+
<InternalInputNumber
653689
prefixCls={prefixCls}
654-
value={value}
655690
disabled={disabled}
656-
style={style}
657-
prefix={prefix}
658-
suffix={suffix}
659-
addonAfter={addonAfter}
660-
addonBefore={addonBefore}
661-
classNames={classNames}
662-
components={{
663-
affixWrapper: 'div',
664-
groupWrapper: 'div',
665-
wrapper: 'div',
666-
groupAddon: 'div',
667-
}}
668-
>
669-
<InternalInputNumber
670-
prefixCls={prefixCls}
671-
disabled={disabled}
672-
ref={composeRef(inputFocusRef, ref)}
673-
className={classNames?.input}
674-
{...rest}
675-
/>
676-
</BaseInput>
677-
);
678-
},
679-
) as (<T extends ValueType = ValueType>(
691+
ref={inputFocusRef}
692+
domRef={inputNumberDomRef}
693+
className={classNames?.input}
694+
{...rest}
695+
/>
696+
</BaseInput>
697+
);
698+
}) as (<T extends ValueType = ValueType>(
680699
props: React.PropsWithChildren<InputNumberProps<T>> & {
681700
ref?: React.Ref<HTMLInputElement>;
682701
},
683702
) => React.ReactElement) & { displayName?: string };
684703

685-
InputNumber.displayName = 'InputNumber';
704+
if (process.env.NODE_ENV !== 'production') {
705+
InputNumber.displayName = 'InputNumber';
706+
}
686707

687708
export default InputNumber;

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { InputNumberProps, ValueType } from './InputNumber';
1+
import type { InputNumberProps, ValueType, InputNumberRef } from './InputNumber';
22
import InputNumber from './InputNumber';
33

4-
export type { InputNumberProps, ValueType };
4+
export type { InputNumberProps, ValueType, InputNumberRef };
55

66
export default InputNumber;

tests/github.test.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import KeyCode from 'rc-util/lib/KeyCode';
22
import React from 'react';
3-
import { act } from 'react-dom/test-utils';
43
import InputNumber from '../src';
5-
import { fireEvent, render, screen, waitFor } from './util/wrapper';
4+
import { act, fireEvent, render, screen, waitFor } from './util/wrapper';
65

76
// Github issues
87
describe('InputNumber.Github', () => {

tests/input.test.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import KeyCode from 'rc-util/lib/KeyCode';
22
import React from 'react';
3-
import InputNumber, { InputNumberProps } from '../src';
3+
import InputNumber, { InputNumberProps, InputNumberRef } from '../src';
44
import { fireEvent, render } from './util/wrapper';
55

66
describe('InputNumber.Input', () => {
@@ -223,4 +223,20 @@ describe('InputNumber.Input', () => {
223223
fireEvent.blur(container.querySelector('input'));
224224
expect(onChange).not.toHaveBeenCalled();
225225
});
226+
227+
describe('nativeElement', () => {
228+
it('basic', () => {
229+
const ref = React.createRef<InputNumberRef>();
230+
const { container } = render(<InputNumber ref={ref} />);
231+
expect(ref.current.nativeElement).toBe(container.querySelector('.rc-input-number'));
232+
});
233+
234+
it('wrapper', () => {
235+
const ref = React.createRef<InputNumberRef>();
236+
const { container } = render(<InputNumber ref={ref} suffix="suffix" />);
237+
expect(ref.current.nativeElement).toBe(
238+
container.querySelector('.rc-input-number-affix-wrapper'),
239+
);
240+
});
241+
});
226242
});

tests/longPress.test.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import React from 'react';
2-
import { act } from 'react-dom/test-utils';
3-
import { render, fireEvent, waitFor } from './util/wrapper';
41
import InputNumber from '../src';
2+
import { act, fireEvent, render, waitFor } from './util/wrapper';
53

64
// Jest will mass of advanceTimersByTime if other test case not use fakeTimer.
75
// Let's create a pure file here for test.

tests/util/wrapper.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import type { ReactElement } from 'react';
2-
import { act } from 'react-dom/test-utils';
31
import type { RenderOptions } from '@testing-library/react';
4-
import { render } from '@testing-library/react';
2+
import { act, render } from '@testing-library/react';
3+
import type { ReactElement } from 'react';
54

65
const globalTimeout = global.setTimeout;
76

87
export const sleep = async (timeout = 0) => {
98
await act(async () => {
10-
await new Promise(resolve => {
9+
await new Promise((resolve) => {
1110
globalTimeout(resolve, timeout);
1211
});
1312
});
@@ -16,6 +15,5 @@ export const sleep = async (timeout = 0) => {
1615
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) =>
1716
render(ui, { ...options });
1817

19-
export { customRender as render };
20-
2118
export * from '@testing-library/react';
19+
export { customRender as render };

0 commit comments

Comments
 (0)