Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions src/components/virtual-input/demos/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const TWO_DIGIT_NUMBER_REGEX = /^(([1-9]\d{0,11})|0)(\.\d{0,2}?)?$/

export default () => {
const [value, setValue] = useState('')
const ref = React.useRef(null)

return (
<>
<DemoBlock title='配合 NumberKeyboard 使用'>
<VirtualInput
placeholder='请输入内容'
keyboard={<NumberKeyboard confirmText='确定' customKey={'.'} />}
ref={ref}
keyboard={<NumberKeyboard confirmText='确定' customKey='.' />}
/>
</DemoBlock>

Expand All @@ -24,6 +26,13 @@ export default () => {
/>
</DemoBlock>

<DemoBlock title='光标位置不可调整'>
<VirtualInput
placeholder='请输入内容'
keyboard={<NumberKeyboard confirmText='确定' customKey='.' />}
/>
</DemoBlock>

<DemoBlock title='禁用状态'>
<VirtualInput
value='这是一个被禁用的输入框'
Expand Down Expand Up @@ -61,11 +70,14 @@ export default () => {
}
}}
placeholder='请输入内容'
keyboard={<NumberKeyboard confirmText='确定' customKey={'.'} />}
keyboard={<NumberKeyboard confirmText='确定' customKey='.' />}
style={{
'--font-size': '40px',
}}
/>
</DemoBlock>

<div style={{ height: '100vh' }}></div>
<div style={{ height: '200vh' }}></div>
</>
)
}
52 changes: 19 additions & 33 deletions src/components/virtual-input/tests/virtual-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,16 @@ describe('VirtualInput', () => {
if (caretContainer != null) {
expect(getCaretPosition(caretContainer)).toBe(3)

// click '1' left side in inputbox, caret position should be 0
// click '1' left side in inputbox, caret position should move to end
clickSiblingElements(caretContainer, 0, true)
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
).toBeVisible()
})
expect(getCaretPosition(caretContainer)).toBe(0)
expect(getCaretPosition(caretContainer)).toBe(3)

// click '9' by keyboard, content should be '9123', caret position should be 1
// click '9' by keyboard, content should be '1239', caret position should be ended
fireEvent.touchEnd(screen.getByText('9'))
await waitFor(() => {
expect(
Expand All @@ -298,8 +298,8 @@ describe('VirtualInput', () => {
})
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('9123')
expect(getCaretPosition(caretContainer)).toBe(1)
).toHaveTextContent('1239')
expect(getCaretPosition(caretContainer)).toBe(4)

// click delete by keyboard, content should be '123', caret position should be 1
fireEvent.touchEnd(screen.getByTitle('清除'))
Expand All @@ -311,7 +311,7 @@ describe('VirtualInput', () => {
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('123')
expect(getCaretPosition(caretContainer)).toBe(0)
expect(getCaretPosition(caretContainer)).toBe(3)

// click input box, caret position should be 3
fireEvent.click(document.querySelector(`.${classPrefix}-content`) as any)
Expand All @@ -324,7 +324,7 @@ describe('VirtualInput', () => {
}
})

test('只支持两位金额的受控组件,光标处理正常', async () => {
test('controlled component with 2-digit decimal amount should handle cursor correctly', async () => {
const KeyBoardClassPrefix = 'adm-number-keyboard'
const Wrapper = () => {
const [value, setValue] = React.useState('0')
Expand Down Expand Up @@ -371,7 +371,7 @@ describe('VirtualInput', () => {
if (caretContainer != null) {
expect(getCaretPosition(caretContainer)).toBe(3)

// 输入小数部分
// Input decimal part
fireEvent.touchEnd(screen.getByTitle('.'))
fireEvent.touchEnd(screen.getByTitle('4'))
fireEvent.touchEnd(screen.getByTitle('5'))
Expand All @@ -381,7 +381,7 @@ describe('VirtualInput', () => {
).toHaveTextContent('103.45')
expect(getCaretPosition(caretContainer)).toBe(6)

// 光标移动到 10x3.45, 输入小数点无效
// Move cursor to between 10 and 3.45, decimal input should be invalid
clickSiblingElements(caretContainer, 2, true)
await waitFor(() => {
expect(
Expand All @@ -395,30 +395,16 @@ describe('VirtualInput', () => {
).toHaveTextContent('103.45')
expect(getCaretPosition(caretContainer)).toBe(2)

// 光标移动到 x103.45,输入 0 无效
clickSiblingElements(caretContainer, 0, true)
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
).toBeVisible()
})
expect(getCaretPosition(caretContainer)).toBe(0)
fireEvent.touchEnd(screen.getByTitle('.'))
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('103.45')
expect(getCaretPosition(caretContainer)).toBe(0)

// 光标移动到 1x03.45,并删除 1
clickSiblingElements(caretContainer, 0, false)
// Move cursor between 1 and 03.45, then delete 1
clickSiblingElements(caretContainer, 1, true)
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
).toBeVisible()
})
expect(getCaretPosition(caretContainer)).toBe(1)

fireEvent.touchEnd(screen.getByTitle('清除')) // 点删除
fireEvent.touchEnd(screen.getByTitle('清除')) // Click delete
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
Expand All @@ -427,18 +413,18 @@ describe('VirtualInput', () => {
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('3.45')
expect(getCaretPosition(caretContainer)).toBe(4) // 变为 3.45 光标到最末尾
expect(getCaretPosition(caretContainer)).toBe(4) // Value becomes 3.45 with cursor at end

// 光标移动到 3x.45,并删除 3
clickSiblingElements(caretContainer, 0, false)
// Move cursor between 3 and .45, then delete 3
clickSiblingElements(caretContainer, 1, true)
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
).toBeVisible()
})
expect(getCaretPosition(caretContainer)).toBe(1)

fireEvent.touchEnd(screen.getByTitle('清除')) // 点删除
fireEvent.touchEnd(screen.getByTitle('清除')) // Click delete
await waitFor(() => {
expect(
document.querySelector(`.${KeyBoardClassPrefix}-popup`)
Expand All @@ -447,15 +433,15 @@ describe('VirtualInput', () => {
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('0.45')
expect(getCaretPosition(caretContainer)).toBe(4) // 变为 0.45 光标到最末尾
expect(getCaretPosition(caretContainer)).toBe(4) // Value becomes 0.45 with cursor at end

// 全部删除,最后为 0
// Delete all, value becomes 0
fireEvent.click(document.querySelector(`.${classPrefix}-clear`) as any)
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('0')

fireEvent.touchEnd(screen.getByTitle('9')) // 在 0 时输入 9,则为 9
fireEvent.touchEnd(screen.getByTitle('9')) // When value is 0, input 9 becomes 9
expect(
document.querySelector(`.${classPrefix}-content`)
).toHaveTextContent('9')
Expand Down
26 changes: 16 additions & 10 deletions src/components/virtual-input/virtual-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
value.substring(0, caretPosition) +
v +
value.substring(caretPosition)
// 临时记录,用于后续光标位置
// Temporarily store for subsequent cursor positioning
keyboardDataRef.current = { newValue, mode: 'input' }
setValue(newValue)
keyboard.props.onInput?.(v)
Expand All @@ -138,7 +138,7 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
const newValue =
value.substring(0, caretPosition - 1) +
value.substring(caretPosition)
// 临时记录,用于后续光标位置
// Temporarily store for subsequent cursor positioning
keyboardDataRef.current = { newValue, mode: 'delete' }
setValue(newValue)
keyboard.props.onDelete?.()
Expand All @@ -160,19 +160,22 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
getContainer: null,
} as NumberKeyboardProps)

// 点击输入框时,将光标置于最后
// When clicking input box, place cursor at end
const setCaretPositionToEnd = () => {
setCaretPosition(value.length)
}

// 点击单个字符时,根据点击位置置于字符前或后
// When clicking character, position cursor before or after based on click position
const changeCaretPosition = (index: number) => (e: React.MouseEvent) => {
e.stopPropagation()

if (index === 0) {
setCaretPosition(value.length)
return
}
const rect = (e.target as HTMLElement).getBoundingClientRect()
const midX = rect.left + rect.width / 2
const clickX = e.clientX
// 点击区域是否偏右
// Check if click area is right-biased
const isRight = clickX > midX

setCaretPosition(isRight ? index + 1 : index)
Expand All @@ -187,15 +190,16 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
className={classNames(classPrefix, {
[`${classPrefix}-disabled`]: mergedProps.disabled,
})}
tabIndex={mergedProps.disabled ? undefined : 0}
role='textbox'
tabIndex={mergedProps.disabled ? undefined : 0}
onFocus={onFocus}
onBlur={onBlur}
onClick={mergedProps.onClick}
>
<div
className={`${classPrefix}-content`}
ref={contentRef}
role='textbox'
aria-disabled={mergedProps.disabled}
aria-label={mergedProps.placeholder}
onClick={setCaretPositionToEnd}
Expand All @@ -205,9 +209,11 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
{i}
</span>
))}
<div className={`${classPrefix}-caret-container`}>
{hasFocus && <div className={`${classPrefix}-caret`} />}
</div>
{hasFocus && (
<div className={`${classPrefix}-caret-container`}>
<div className={`${classPrefix}-caret`} />
</div>
)}
{chars.slice(caretPosition).map((i: string, index: number) => (
<span
key={index}
Expand Down
Loading