Skip to content

Commit

Permalink
feat: support nativeElement (#643)
Browse files Browse the repository at this point in the history
* feat: support nativeElement

* chore: update deps
  • Loading branch information
zombieJ authored May 17, 2024
1 parent 882958b commit de38d1a
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 72 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"@babel/runtime": "^7.10.1",
"@rc-component/mini-decimal": "^1.0.1",
"classnames": "^2.2.5",
"rc-input": "~1.4.0",
"rc-util": "^5.28.0"
"rc-input": "~1.5.0",
"rc-util": "^5.40.1"
},
"devDependencies": {
"@rc-component/father-plugin": "^1.0.1",
Expand Down
133 changes: 77 additions & 56 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@ import getMiniDecimal, {
import clsx from 'classnames';
import { BaseInput } from 'rc-input';
import { useLayoutUpdateEffect } from 'rc-util/lib/hooks/useLayoutEffect';
import proxyObject from 'rc-util/lib/proxyObject';
import { composeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import useCursor from './hooks/useCursor';
import StepHandler from './StepHandler';
import { getDecupleSteps } from './utils/numberUtil';

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

export type { ValueType };

export interface InputNumberRef extends HTMLInputElement {
nativeElement: HTMLElement;
}

/**
* We support `stringMode` which need handle correct type when user call in onChange
* format max or min value
Expand Down Expand Up @@ -100,12 +106,14 @@ export interface InputNumberProps<T extends ValueType = ValueType>
changeOnBlur?: boolean;
}

type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'>;
type InternalInputNumberProps = Omit<InputNumberProps, 'prefix' | 'suffix'> & {
domRef: React.Ref<HTMLDivElement>;
};

const InternalInputNumber = React.forwardRef(
(props: InternalInputNumberProps, ref: React.Ref<HTMLInputElement>) => {
const {
prefixCls = 'rc-input-number',
prefixCls,
className,
style,
min,
Expand Down Expand Up @@ -136,6 +144,8 @@ const InternalInputNumber = React.forwardRef(

changeOnBlur = true,

domRef,

...inputProps
} = props;

Expand Down Expand Up @@ -572,6 +582,7 @@ const InternalInputNumber = React.forwardRef(
// ============================ Render ============================
return (
<div
ref={domRef}
className={clsx(prefixCls, className, {
[`${prefixCls}-focused`]: focus,
[`${prefixCls}-disabled`]: disabled,
Expand Down Expand Up @@ -622,66 +633,76 @@ const InternalInputNumber = React.forwardRef(
},
);

const InputNumber = React.forwardRef(
(props: InputNumberProps, ref: React.Ref<HTMLInputElement>) => {
const {
disabled,
style,
prefixCls,
value,
prefix,
suffix,
addonBefore,
addonAfter,
className,
classNames,
...rest
} = props;

const inputFocusRef = React.useRef<HTMLInputElement>(null);

const focus = (option?: InputFocusOptions) => {
if (inputFocusRef.current) {
triggerFocus(inputFocusRef.current, option);
}
};
const InputNumber = React.forwardRef<InputNumberRef, InputNumberProps>((props, ref) => {
const {
disabled,
style,
prefixCls = 'rc-input-number',
value,
prefix,
suffix,
addonBefore,
addonAfter,
className,
classNames,
...rest
} = props;

const holderRef = React.useRef<HolderRef>(null);
const inputNumberDomRef = React.useRef<HTMLDivElement>(null);
const inputFocusRef = React.useRef<HTMLInputElement>(null);

const focus = (option?: InputFocusOptions) => {
if (inputFocusRef.current) {
triggerFocus(inputFocusRef.current, option);
}
};

return (
<BaseInput
className={className}
triggerFocus={focus}
React.useImperativeHandle(ref, () =>
proxyObject(inputFocusRef.current, {
nativeElement: holderRef.current.nativeElement || inputNumberDomRef.current,
}),
);

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
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',
}}
>
<InternalInputNumber
prefixCls={prefixCls}
disabled={disabled}
ref={composeRef(inputFocusRef, ref)}
className={classNames?.input}
{...rest}
/>
</BaseInput>
);
},
) as (<T extends ValueType = ValueType>(
ref={inputFocusRef}
domRef={inputNumberDomRef}
className={classNames?.input}
{...rest}
/>
</BaseInput>
);
}) as (<T extends ValueType = ValueType>(
props: React.PropsWithChildren<InputNumberProps<T>> & {
ref?: React.Ref<HTMLInputElement>;
},
) => React.ReactElement) & { displayName?: string };

InputNumber.displayName = 'InputNumber';
if (process.env.NODE_ENV !== 'production') {
InputNumber.displayName = 'InputNumber';
}

export default InputNumber;
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InputNumberProps, ValueType } from './InputNumber';
import type { InputNumberProps, ValueType, InputNumberRef } from './InputNumber';
import InputNumber from './InputNumber';

export type { InputNumberProps, ValueType };
export type { InputNumberProps, ValueType, InputNumberRef };

export default InputNumber;
3 changes: 1 addition & 2 deletions tests/github.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import { act } from 'react-dom/test-utils';
import InputNumber from '../src';
import { fireEvent, render, screen, waitFor } from './util/wrapper';
import { act, fireEvent, render, screen, waitFor } from './util/wrapper';

// Github issues
describe('InputNumber.Github', () => {
Expand Down
18 changes: 17 additions & 1 deletion tests/input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import InputNumber, { InputNumberProps } from '../src';
import InputNumber, { InputNumberProps, InputNumberRef } from '../src';
import { fireEvent, render } from './util/wrapper';

describe('InputNumber.Input', () => {
Expand Down Expand Up @@ -223,4 +223,20 @@ describe('InputNumber.Input', () => {
fireEvent.blur(container.querySelector('input'));
expect(onChange).not.toHaveBeenCalled();
});

describe('nativeElement', () => {
it('basic', () => {
const ref = React.createRef<InputNumberRef>();
const { container } = render(<InputNumber ref={ref} />);
expect(ref.current.nativeElement).toBe(container.querySelector('.rc-input-number'));
});

it('wrapper', () => {
const ref = React.createRef<InputNumberRef>();
const { container } = render(<InputNumber ref={ref} suffix="suffix" />);
expect(ref.current.nativeElement).toBe(
container.querySelector('.rc-input-number-affix-wrapper'),
);
});
});
});
4 changes: 1 addition & 3 deletions tests/longPress.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { render, fireEvent, waitFor } from './util/wrapper';
import InputNumber from '../src';
import { act, fireEvent, render, waitFor } from './util/wrapper';

// Jest will mass of advanceTimersByTime if other test case not use fakeTimer.
// Let's create a pure file here for test.
Expand Down
10 changes: 4 additions & 6 deletions tests/util/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { ReactElement } from 'react';
import { act } from 'react-dom/test-utils';
import type { RenderOptions } from '@testing-library/react';
import { render } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import type { ReactElement } from 'react';

const globalTimeout = global.setTimeout;

export const sleep = async (timeout = 0) => {
await act(async () => {
await new Promise(resolve => {
await new Promise((resolve) => {
globalTimeout(resolve, timeout);
});
});
Expand All @@ -16,6 +15,5 @@ export const sleep = async (timeout = 0) => {
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) =>
render(ui, { ...options });

export { customRender as render };

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

0 comments on commit de38d1a

Please sign in to comment.