paste = function(x, y, data) {
    const obj = this;

    // Controls
    const dataHash = hash(data);
    let style = (dataHash == obj.hashString) ? obj.style : null;

    // Depending on the behavior
    if (dataHash == obj.hashString) {
        data = obj.data;
    }

    // Split new line
    data = parseCSV(data, "\t");

    const ex = obj.selectedCell[2];
    const ey = obj.selectedCell[3];

    const w = ex - x + 1;
    const h = ey - y + 1;

    // Modify data to allow wor extending paste range in multiples of input range
    const srcW = data[0].length;
    if ((w > 1) & Number.isInteger(w / srcW)) {
        const repeats = w / srcW;
        if( style ){
            const newStyle = [];
            for(let i=0 ;i <style.length ; i+=srcW ){
                const chunk = style.slice(i,i+srcW);
                for (let j = 0; j < repeats; j++) {
                    newStyle.push(...chunk);
                }
            }
            style = newStyle;
        };

        const arrayB = data.map(function (row, i) {
            const arrayC = Array.apply(null, { length: repeats * row.length }).map(
                function (e, i) { return row[i % row.length]; }
            );
            return arrayC;
        });
        data = arrayB;
    }
    const srcH = data.length;
    if ((h > 1) & Number.isInteger(h / srcH)) {
        const repeats = h / srcH;
        if( style ){
            const newStyle = [];
            for (let j = 0; j < repeats; j++) {
                newStyle.push(...style);
            }
            style = newStyle;
        };
        const arrayB = Array.apply(null, { length: repeats * srcH }).map(
            function (e, i) { return data[i % srcH]; }
        );
        data = arrayB;
    }

    // Paste filter
    const ret = dispatch.call(
        obj,
        'onbeforepaste',
        obj,
        data.map(function(row) {
            return row.map(function(item) {
                return { value: item }
            });
        }),
        x,
        y
    );

    if (ret === false) {
        return false;
    } else if (ret) {
        data = ret;
    }

    if (x != null && y != null && data) {
        // Records
        let i = 0;
        let j = 0;
        const records = [];
        const newStyle = {};
        const oldStyle = {};
        let styleIndex = 0;

        // Index
        let colIndex = parseInt(x);
        let rowIndex = parseInt(y);
        let row = null;

        const hiddenColCount = obj.headers.slice(colIndex).filter(x=>x.style.display === 'none').length;
        const expandedColCount = colIndex + hiddenColCount + data[0].length;
        const currentColCount = obj.headers.length;
        if( expandedColCount > currentColCount){
            obj.skipUpdateTableReferences = true;
            obj.insertColumn( expandedColCount - currentColCount);
        }
        const hiddenRowCount = obj.rows.slice(rowIndex).filter(x=>x.element.style.display === 'none').length;
        const expandedRowCount = rowIndex + hiddenRowCount + data.length;
        const currentRowCount = obj.rows.length;
        if( expandedRowCount > currentRowCount){
            obj.skipUpdateTableReferences = true;
            obj.insertRow( expandedRowCount - currentRowCount);
        }

        if( obj.skipUpdateTableReferences ){
            obj.skipUpdateTableReferences = false;
            updateTableReferences.call(obj);
        }

        // Go through the columns to get the data
        while (row = data[j]) {
            i = 0;
            colIndex = parseInt(x);

            while (row[i] != null) {
                let value = row[i];

                if (
                    obj.options.columns &&
                    obj.options.columns[i] &&
                    (
                        obj.options.columns[i].type == 'calendar'
                    )
                ) {
                    value = jSuites.calendar.extractDateFromString(value, (obj.options.columns[i].options && obj.options.columns[i].options.format) || 'YYYY-MM-DD');
                }

                // Update and keep history
                const record = updateCell.call(obj, colIndex, rowIndex, value);
                // Keep history
                records.push(record);
                // Update all formulas in the chain
                updateFormulaChain.call(obj, colIndex, rowIndex, records);
                // Style
                if (style && style[styleIndex]) {
                    const columnName = getColumnNameFromId([colIndex, rowIndex]);
                    newStyle[columnName] = style[styleIndex];
                    oldStyle[columnName] = obj.getStyle(columnName);
                    obj.records[rowIndex][colIndex].element.setAttribute('style', style[styleIndex]);
                    styleIndex++
                }
                i++;
                if (row[i] != null) {
                    if (colIndex >= obj.headers.length - 1) {
                        // If the pasted column is out of range, create it if possible
                        if (obj.options.allowInsertColumn != false) {
                            obj.insertColumn();
                            // Otherwise skip the pasted data that overflows
                        } else {
                            break;
                        }
                    }
                    colIndex = rightGet.call(obj, colIndex, rowIndex);
                }
            }

            j++;
            if (data[j]) {
                if (rowIndex >= obj.rows.length-1) {
                    // If the pasted row is out of range, create it if possible
                    if (obj.options.allowInsertRow != false) {
                        obj.insertRow();
                        // Otherwise skip the pasted data that overflows
                    } else {
                        break;
                    }
                }
                rowIndex = downGet.call(obj, x, rowIndex);
            }
        }

        // Select the new cells
        updateSelectionFromCoords.call(obj, x, y, colIndex, rowIndex);

        // Update history
        setHistory.call(obj, {
            action:'setValue',
            records:records,
            selection:obj.selectedCell,
            newStyle:newStyle,
            oldStyle:oldStyle,
        });

        // Update table
        updateTable.call(obj);

        // Paste event
        const eventRecords = [];

        for (let j = 0; j < data.length; j++) {
            for (let i = 0; i < data[j].length; i++) {
                eventRecords.push({
                    x: i + x,
                    y: j + y,
                    value: data[j][i],
                });
            }
        }

        dispatch.call(obj, 'onpaste', obj, eventRecords);

        // On after changes
        const onafterchangesRecords = records.map(function(record) {
            return {
                x: record.x,
                y: record.y,
                value: record.newValue,
                oldValue: record.oldValue,
            };
        });

        dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords);
    }

    removeCopyingSelection();
}
Jspreadsheet paste method

@param x target column
@param y target row
@param data paste data. if data hash is the same as the copied data, apply style from copied cells  
@return string value