Skip to content

Commit e5c279d

Browse files
committed
feat(ui-color-picker): improve ColorPicker paste behavior
INSTUI-4834
1 parent de45942 commit e5c279d

3 files changed

Lines changed: 144 additions & 2 deletions

File tree

packages/ui-color-picker/src/ColorPicker/__tests__/ColorPicker.test.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,133 @@ describe('<ColorPicker />', () => {
392392

393393
expect(inputRef).toHaveBeenCalledWith(input)
394394
})
395+
396+
describe('paste behavior', () => {
397+
it('should strip leading # from pasted value', async () => {
398+
render(<SimpleExample />)
399+
const input = screen.getByRole('textbox') as HTMLInputElement
400+
401+
fireEvent.paste(input, {
402+
clipboardData: { getData: () => '#FF0000' }
403+
})
404+
405+
await waitFor(() => {
406+
expect(input).toHaveValue('FF0000')
407+
})
408+
})
409+
410+
it('should block pasted value that exceeds 6 characters', async () => {
411+
render(<SimpleExample />)
412+
const input = screen.getByRole('textbox') as HTMLInputElement
413+
414+
fireEvent.paste(input, {
415+
clipboardData: { getData: () => 'FF00001' }
416+
})
417+
418+
await waitFor(() => {
419+
expect(input).toHaveValue('')
420+
})
421+
})
422+
423+
it('should block pasted value with invalid hex characters', async () => {
424+
render(<SimpleExample />)
425+
const input = screen.getByRole('textbox') as HTMLInputElement
426+
427+
fireEvent.paste(input, {
428+
clipboardData: { getData: () => 'ZZZZZZ' }
429+
})
430+
431+
await waitFor(() => {
432+
expect(input).toHaveValue('')
433+
})
434+
})
435+
436+
it('should replace entirely selected text when pasting', async () => {
437+
render(<SimpleExample />)
438+
const input = screen.getByRole('textbox') as HTMLInputElement
439+
440+
fireEvent.change(input, { target: { value: 'FF0000' } })
441+
await waitFor(() => expect(input).toHaveValue('FF0000'))
442+
443+
input.setSelectionRange(0, 6)
444+
fireEvent.paste(input, {
445+
clipboardData: { getData: () => 'AABBCC' }
446+
})
447+
448+
await waitFor(() => {
449+
expect(input).toHaveValue('AABBCC')
450+
})
451+
})
452+
453+
it('should replace partially selected text when pasting', async () => {
454+
render(<SimpleExample />)
455+
const input = screen.getByRole('textbox') as HTMLInputElement
456+
457+
fireEvent.change(input, { target: { value: 'FF0000' } })
458+
await waitFor(() => expect(input).toHaveValue('FF0000'))
459+
460+
// select the two middle zeros (positions 2–4), paste FF → FFFF00
461+
input.setSelectionRange(2, 4)
462+
fireEvent.paste(input, {
463+
clipboardData: { getData: () => 'FF' }
464+
})
465+
466+
await waitFor(() => {
467+
expect(input).toHaveValue('FFFF00')
468+
})
469+
})
470+
471+
it('should insert pasted text at cursor start position', async () => {
472+
render(<SimpleExample />)
473+
const input = screen.getByRole('textbox') as HTMLInputElement
474+
475+
fireEvent.change(input, { target: { value: '0000' } })
476+
await waitFor(() => expect(input).toHaveValue('0000'))
477+
478+
input.setSelectionRange(0, 0)
479+
fireEvent.paste(input, {
480+
clipboardData: { getData: () => 'FF' }
481+
})
482+
483+
await waitFor(() => {
484+
expect(input).toHaveValue('FF0000')
485+
})
486+
})
487+
488+
it('should insert pasted text at cursor middle position', async () => {
489+
render(<SimpleExample />)
490+
const input = screen.getByRole('textbox') as HTMLInputElement
491+
492+
fireEvent.change(input, { target: { value: 'FF00' } })
493+
await waitFor(() => expect(input).toHaveValue('FF00'))
494+
495+
input.setSelectionRange(2, 2)
496+
fireEvent.paste(input, {
497+
clipboardData: { getData: () => 'AB' }
498+
})
499+
500+
await waitFor(() => {
501+
expect(input).toHaveValue('FFAB00')
502+
})
503+
})
504+
505+
it('should insert pasted text at cursor end position', async () => {
506+
render(<SimpleExample />)
507+
const input = screen.getByRole('textbox') as HTMLInputElement
508+
509+
fireEvent.change(input, { target: { value: '0000' } })
510+
await waitFor(() => expect(input).toHaveValue('0000'))
511+
512+
input.setSelectionRange(4, 4)
513+
fireEvent.paste(input, {
514+
clipboardData: { getData: () => 'FF' }
515+
})
516+
517+
await waitFor(() => {
518+
expect(input).toHaveValue('0000FF')
519+
})
520+
})
521+
})
395522
})
396523

397524
describe('complex mode', () => {

packages/ui-color-picker/src/ColorPicker/v1/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,14 @@ class ColorPicker extends Component<ColorPickerProps, ColorPickerState> {
431431
return event.preventDefault()
432432
}
433433

434-
const newHex = `${this.state.hexCode}${toPaste}`
434+
const input = event.target as HTMLInputElement
435+
const currentHex = this.state.hexCode
436+
const selectionStart = input.selectionStart ?? currentHex.length
437+
const selectionEnd = input.selectionEnd ?? currentHex.length
438+
const newHex =
439+
currentHex.slice(0, selectionStart) +
440+
toPaste +
441+
currentHex.slice(selectionEnd)
435442
if (isValid(newHex)) {
436443
if (typeof this.props.onChange === 'function') {
437444
this.props.onChange(`#${newHex}`)

packages/ui-color-picker/src/ColorPicker/v2/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,15 @@ class ColorPicker extends Component<ColorPickerProps, ColorPickerState> {
431431
return event.preventDefault()
432432
}
433433

434-
const newHex = `${this.state.hexCode}${toPaste}`
434+
const input = event.target as HTMLInputElement
435+
const currentHex = this.state.hexCode
436+
const selectionStart = input.selectionStart ?? currentHex.length
437+
const selectionEnd = input.selectionEnd ?? currentHex.length
438+
const newHex =
439+
currentHex.slice(0, selectionStart) +
440+
toPaste +
441+
currentHex.slice(selectionEnd)
442+
435443
if (isValid(newHex)) {
436444
if (typeof this.props.onChange === 'function') {
437445
this.props.onChange(`#${newHex}`)

0 commit comments

Comments
 (0)