Demand background

Components that require multiple lines of input (copy-and-paste support) have been heavily used in recent refactoring projects. For example, enter a list of IP addresses and keywords to be excluded. In the original implementation, textarea was used directly, but this product requires automatic numbering before each line.

Implementation scheme

After some thinking, I came up with the following three schemes:

    1. Don’t have totextarea, using multipleinputBox to simulate. Generate the next one after each press enterinputBox and serial number.
    1. usetextareaAfter pressing Enter, change the text entered by the user and add the ordinal number.
    1. usetextarea, the serial number column uses absolute positioning, monitoring text changes to synchronize the serial number column.

A brief comparison of the advantages and disadvantages of the three schemes:

plan advantage disadvantage
one Without considering the positioning problems caused by scrolling, it is convenient to obtain the results. Multiple input naturally corresponds to a result array Event handling after Enter takes into account the current index location, deletes rows, and copy-paste does extra processing
two Never mind the positioning problems associated with scrolling Row numbers are removed when fetching results, row deletions are handled, and copy-pasting is done extra
three There is no extra copy-paste to do, just listen for the change event on the Textarea Consider styling due to absolute positioning and positioning issues due to scrolling

After comparison and trial, we decided to use scheme 3. The specific implementation is discussed below

The specific implementation

Scheme three needs to listen to the onChange event of textarea, cut out the result array through the newline character, and then generate the ordinal column according to the array. The challenge is to make each row of the ordinal column look the same as each row of the Textarea.

The height of each line of text for a Textarea can only be set by line-height, so you need to set the line height of the absolutely positioned ordinal column to the line height of the Textarea.

The padding-left of the textarea is left empty to hold the ordinal column.

Listen to the onScroll event of textarea to dynamically set the top value of the serial number column to synchronize scrolling.

Note that the newline escape character in JS is \n.

code

import React, { useState, FunctionComponent } from "react";
import { Input } from "antd"
import { throttle } from "lodash"

const { TextArea } = Input

exportinterface IAreaFormProps { defaultList? : string[]; onChange? : (list: string[]) => void; } const AreaForm: FunctionComponent<IAreaFormProps> = (props) => { const { defaultList = [], onChange } = props; const [list,setList] = useState(defaultList);

    const [areaValue, setAreaValue] = useState(defaultList.join("\n"))

    const [top, setTop] = useState(0)

    const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
        const value = event.currentTarget.value
        const list = value ? value.split("\n") : []
        onChange && onChange(list)
        setList(list)
        setAreaValue(value)
    }


    const handleScroll = (event: React.UIEvent<HTMLTextAreaElement>) => {
        setTop(-event.currentTarget.scrollTop)
    }


    return (
        <div className="area-form">
            <div className="area-form__content">
                <ul className="area-form__index"style={{ top: top + 11 }}> { list.map((_, Index) => (<li key={index}> <span>{index + 1}, </span> </li>))} </ul> <TextArea autoFocus onScroll={throttle(handleScroll)} value={areaValue} onChange={e => handleChange(e)} autoComplete="off"></TextArea>
            </div>
        </div>
    )

}

export default AreaForm;
Copy the code

If you have a better solution, please let me know in the comments section.