handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
852 lines (830 loc) • 36.1 kB
JavaScript
import "core-js/modules/es.error.cause.js";
import "core-js/modules/es.array.push.js";
import "core-js/modules/es.json.stringify.js";
import "core-js/modules/esnext.iterator.constructor.js";
import "core-js/modules/esnext.iterator.filter.js";
import "core-js/modules/esnext.iterator.map.js";
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
import { BasePlugin } from "../base/index.mjs";
import { Hooks } from "../../core/hooks/index.mjs";
import { stringify, parse } from "../../3rdparty/SheetClip/index.mjs";
import { arrayEach } from "../../helpers/array.mjs";
import { sanitize, isJSON } from "../../helpers/string.mjs";
import { isObject } from "../../helpers/object.mjs";
import { removeContentEditableFromElementAndDeselect, runWithSelectedContendEditableElement, makeElementContentEditableAndSelectItsContent, isHTMLElement, isInternalElement } from "../../helpers/dom/element.mjs";
import { isSafari } from "../../helpers/browser.mjs";
import copyItem from "./contextMenuItem/copy.mjs";
import copyColumnHeadersOnlyItem from "./contextMenuItem/copyColumnHeadersOnly.mjs";
import copyWithColumnGroupHeadersItem from "./contextMenuItem/copyWithColumnGroupHeaders.mjs";
import copyWithColumnHeadersItem from "./contextMenuItem/copyWithColumnHeaders.mjs";
import cutItem from "./contextMenuItem/cut.mjs";
import PasteEvent from "./pasteEvent.mjs";
import { CopyableRangesFactory, normalizeRanges } from "./copyableRanges.mjs";
import { _dataToHTML, htmlToGridSettings } from "../../utils/parseTable.mjs";
Hooks.getSingleton().register('afterCopyLimit');
Hooks.getSingleton().register('modifyCopyableRange');
Hooks.getSingleton().register('beforeCut');
Hooks.getSingleton().register('afterCut');
Hooks.getSingleton().register('beforePaste');
Hooks.getSingleton().register('afterPaste');
Hooks.getSingleton().register('beforeCopy');
Hooks.getSingleton().register('afterCopy');
export const PLUGIN_KEY = 'copyPaste';
export const PLUGIN_PRIORITY = 80;
const SETTING_KEYS = ['fragmentSelection'];
const SOURCE_DATA_HTML_MIME_TYPE = 'application/ht-source-data-json-html';
const META_HEAD = ['<meta name="generator" content="Handsontable"/>', '<style type="text/css">td{white-space:normal}br{mso-data-placement:same-cell}</style>'].join('');
/* eslint-disable jsdoc/require-description-complete-sentence */
/**
* @description
* Copy, cut, and paste data by using the `CopyPaste` plugin.
*
* Control the `CopyPaste` plugin programmatically through its [API methods](#methods).
*
* The user can access the copy-paste features through:
* - The [context menu](@/guides/cell-features/clipboard/clipboard.md#context-menu).
* - The [keyboard shortcuts](@/guides/cell-features/clipboard/clipboard.md#related-keyboard-shortcuts).
* - The browser's menu bar.
*
* Read more:
* - [Guides: Clipboard](@/guides/cell-features/clipboard/clipboard.md)
* - [Configuration options: `copyPaste`](@/api/options.md#copypaste)
*
* @example
* ```js
* // enable the plugin with the default configuration
* copyPaste: true,
*
* // or, enable the plugin with a custom configuration
* copyPaste: {
* columnsLimit: 25,
* rowsLimit: 50,
* pasteMode: 'shift_down',
* copyColumnHeaders: true,
* copyColumnGroupHeaders: true,
* copyColumnHeadersOnly: true,
* uiContainer: document.body,
* },
* ```
* @class CopyPaste
* @plugin CopyPaste
*/
var _enableCopyColumnHeaders = /*#__PURE__*/new WeakMap();
var _enableCopyColumnGroupHeaders = /*#__PURE__*/new WeakMap();
var _enableCopyColumnHeadersOnly = /*#__PURE__*/new WeakMap();
var _copyMode = /*#__PURE__*/new WeakMap();
var _isTriggeredByCopy = /*#__PURE__*/new WeakMap();
var _isTriggeredByCut = /*#__PURE__*/new WeakMap();
var _copyableRangesFactory = /*#__PURE__*/new WeakMap();
var _preventViewportScrollOnPaste = /*#__PURE__*/new WeakMap();
var _CopyPaste_brand = /*#__PURE__*/new WeakSet();
export class CopyPaste extends BasePlugin {
constructor() {
super(...arguments);
/**
* Ensure that the `copy`/`cut` events get triggered properly in Safari.
*
* @param {string} eventName Name of the event to get triggered.
*/
_classPrivateMethodInitSpec(this, _CopyPaste_brand);
/**
* The maximum number of columns than can be copied to the clipboard.
*
* @type {number}
* @default Infinity
*/
_defineProperty(this, "columnsLimit", Infinity);
/**
* The maximum number of rows than can be copied to the clipboard.
*
* @type {number}
* @default Infinity
*/
_defineProperty(this, "rowsLimit", Infinity);
/**
* When pasting:
* - `'overwrite'` - overwrite the currently-selected cells
* - `'shift_down'` - move currently-selected cells down
* - `'shift_right'` - move currently-selected cells to the right
*
* @type {string}
* @default 'overwrite'
*/
_defineProperty(this, "pasteMode", 'overwrite');
/**
* The UI container for the secondary focusable element.
*
* @type {HTMLElement}
*/
_defineProperty(this, "uiContainer", this.hot.rootDocument.body);
/**
* Shows the "Copy with headers" item in the context menu and extends the context menu with the
* `'copy_with_column_headers'` option that can be used for creating custom menus arrangements.
*
* @type {boolean}
* @default false
*/
_classPrivateFieldInitSpec(this, _enableCopyColumnHeaders, false);
/**
* Shows the "Copy with group headers" item in the context menu and extends the context menu with the
* `'copy_with_column_group headers'` option that can be used for creating custom menus arrangements.
*
* @type {boolean}
* @default false
*/
_classPrivateFieldInitSpec(this, _enableCopyColumnGroupHeaders, false);
/**
* Shows the "Copy headers only" item in the context menu and extends the context menu with the
* `'copy_column_headers_only'` option that can be used for creating custom menus arrangements.
*
* @type {boolean}
* @default false
*/
_classPrivateFieldInitSpec(this, _enableCopyColumnHeadersOnly, false);
/**
* Defines the data range to copy. Possible values:
* * `'cells-only'` Copy selected cells only;
* * `'column-headers-only'` Copy column headers only;
* * `'with-column-group-headers'` Copy cells with all column headers;
* * `'with-column-headers'` Copy cells with column headers;
*
* @type {'cells-only' | 'column-headers-only' | 'with-column-group-headers' | 'with-column-headers'}
*/
_classPrivateFieldInitSpec(this, _copyMode, 'cells-only');
/**
* Flag that is used to prevent copying when the native shortcut was not pressed.
*
* @type {boolean}
*/
_classPrivateFieldInitSpec(this, _isTriggeredByCopy, false);
/**
* Flag that is used to prevent cutting when the native shortcut was not pressed.
*
* @type {boolean}
*/
_classPrivateFieldInitSpec(this, _isTriggeredByCut, false);
/**
* Class that helps generate copyable ranges based on the current selection for different copy mode
* types.
*
* @type {CopyableRangesFactory}
*/
_classPrivateFieldInitSpec(this, _copyableRangesFactory, new CopyableRangesFactory({
countRows: () => this.hot.countRows(),
countColumns: () => this.hot.countCols(),
rowsLimit: () => this.rowsLimit,
columnsLimit: () => this.columnsLimit,
countColumnHeaders: () => this.hot.view.getColumnHeadersCount()
}));
/**
* Flag that indicates if the viewport scroll should be prevented after pasting the data.
*
* @type {boolean}
*/
_classPrivateFieldInitSpec(this, _preventViewportScrollOnPaste, false);
/**
* Ranges of the cells coordinates, which should be used to copy/cut/paste actions.
*
* @private
* @type {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>}
*/
_defineProperty(this, "copyableRanges", []);
}
static get PLUGIN_KEY() {
return PLUGIN_KEY;
}
static get SETTING_KEYS() {
return [PLUGIN_KEY, ...SETTING_KEYS];
}
static get PLUGIN_PRIORITY() {
return PLUGIN_PRIORITY;
}
static get DEFAULT_SETTINGS() {
return {
pasteMode: 'overwrite',
rowsLimit: Infinity,
columnsLimit: Infinity,
copyColumnHeaders: false,
copyColumnGroupHeaders: false,
copyColumnHeadersOnly: false
};
}
/**
* Checks if the [`CopyPaste`](#copypaste) plugin is enabled.
*
* This method gets called by Handsontable's [`beforeInit`](@/api/hooks.md#beforeinit) hook.
* If it returns `true`, the [`enablePlugin()`](#enableplugin) method gets called.
*
* @returns {boolean}
*/
isEnabled() {
return !!this.hot.getSettings()[PLUGIN_KEY];
}
/**
* Enables the [`CopyPaste`](#copypaste) plugin for your Handsontable instance.
*/
enablePlugin() {
var _this$getSetting,
_this$getSetting2,
_this = this;
if (this.enabled) {
return;
}
this.pasteMode = (_this$getSetting = this.getSetting('pasteMode')) !== null && _this$getSetting !== void 0 ? _this$getSetting : this.pasteMode;
this.rowsLimit = isNaN(this.getSetting('rowsLimit')) ? this.rowsLimit : this.getSetting('rowsLimit');
this.columnsLimit = isNaN(this.getSetting('columnsLimit')) ? this.columnsLimit : this.getSetting('columnsLimit');
_classPrivateFieldSet(_enableCopyColumnHeaders, this, this.getSetting('copyColumnHeaders'));
_classPrivateFieldSet(_enableCopyColumnGroupHeaders, this, this.getSetting('copyColumnGroupHeaders'));
_classPrivateFieldSet(_enableCopyColumnHeadersOnly, this, this.getSetting('copyColumnHeadersOnly'));
this.uiContainer = (_this$getSetting2 = this.getSetting('uiContainer')) !== null && _this$getSetting2 !== void 0 ? _this$getSetting2 : this.uiContainer;
this.addHook('afterContextMenuDefaultOptions', options => _assertClassBrand(_CopyPaste_brand, this, _onAfterContextMenuDefaultOptions).call(this, options));
this.addHook('afterSelection', function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _assertClassBrand(_CopyPaste_brand, _this, _onAfterSelection).call(_this, ...args);
});
this.addHook('afterSelectionEnd', () => _assertClassBrand(_CopyPaste_brand, this, _onAfterSelectionEnd).call(this));
// Events are attached to the document, not the root table element - as it should,
// for Chrome 133 and lower to copy/paste/cut work properly (#dev-2277).
this.eventManager.addEventListener(this.hot.rootDocument, 'copy', function () {
return _this.onCopy(...arguments);
});
this.eventManager.addEventListener(this.hot.rootDocument, 'cut', function () {
return _this.onCut(...arguments);
});
this.eventManager.addEventListener(this.hot.rootDocument, 'paste', function () {
return _this.onPaste(...arguments);
});
// Without this workaround Safari (tested on Safari@16.5.2) does allow copying/cutting from the browser menu.
if (isSafari()) {
this.eventManager.addEventListener(this.hot.rootDocument.body, 'mouseenter', function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _assertClassBrand(_CopyPaste_brand, _this, _onSafariMouseEnter).call(_this, ...args);
});
this.eventManager.addEventListener(this.hot.rootDocument.body, 'mouseleave', function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _assertClassBrand(_CopyPaste_brand, _this, _onSafariMouseLeave).call(_this, ...args);
});
this.addHook('afterSelection', () => _assertClassBrand(_CopyPaste_brand, this, _onSafariAfterSelection).call(this));
}
super.enablePlugin();
}
/**
* Updates the state of the [`CopyPaste`](#copypaste) plugin.
*
* Gets called when [`updateSettings()`](@/api/core.md#updatesettings)
* is invoked with any of the following configuration options:
* - [`copyPaste`](@/api/options.md#copypaste)
* - [`fragmentSelection`](@/api/options.md#fragmentselection)
*/
updatePlugin() {
this.disablePlugin();
this.enablePlugin();
super.updatePlugin();
}
/**
* Disables the [`CopyPaste`](#copypaste) plugin for your Handsontable instance.
*/
disablePlugin() {
super.disablePlugin();
}
/**
* Copies the contents of the selected cells (and/or their related column headers) to the system clipboard.
*
* Takes an optional parameter (`copyMode`) that defines the scope of copying:
*
* | `copyMode` value | Description |
* | ----------------------------- | --------------------------------------------------------------- |
* | `'cells-only'` (default) | Copy the selected cells |
* | `'with-column-headers'` | - Copy the selected cells<br>- Copy the nearest column headers |
* | `'with-column-group-headers'` | - Copy the selected cells<br>- Copy all related columns headers |
* | `'column-headers-only'` | Copy the nearest column headers (without copying cells) |
*
* @param {string} [copyMode='cells-only'] Copy mode.
*/
copy() {
let copyMode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'cells-only';
_classPrivateFieldSet(_copyMode, this, copyMode);
_classPrivateFieldSet(_isTriggeredByCopy, this, true);
_assertClassBrand(_CopyPaste_brand, this, _ensureClipboardEventsGetTriggered).call(this, 'copy');
}
/**
* Copies the contents of the selected cells.
*/
copyCellsOnly() {
this.copy('cells-only');
}
/**
* Copies the contents of column headers that are nearest to the selected cells.
*/
copyColumnHeadersOnly() {
this.copy('column-headers-only');
}
/**
* Copies the contents of the selected cells and all their related column headers.
*/
copyWithAllColumnHeaders() {
this.copy('with-column-group-headers');
}
/**
* Copies the contents of the selected cells and their nearest column headers.
*/
copyWithColumnHeaders() {
this.copy('with-column-headers');
}
/**
* Cuts the contents of the selected cells to the system clipboard.
*/
cut() {
_classPrivateFieldSet(_isTriggeredByCut, this, true);
_assertClassBrand(_CopyPaste_brand, this, _ensureClipboardEventsGetTriggered).call(this, 'cut');
}
/**
* Converts the contents of multiple ranges (`ranges`) into a single string.
*
* @param {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>} ranges Array of objects with properties `startRow`, `endRow`, `startCol` and `endCol`.
* @returns {string} A string that will be copied to the clipboard.
*/
getRangedCopyableData(ranges) {
return stringify(this.getRangedData(ranges));
}
/**
* Converts the contents of multiple ranges (`ranges`) into an array of arrays.
*
* @param {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>} ranges Array of objects with properties `startRow`, `startCol`, `endRow` and `endCol`.
* @param {boolean} [useSourceData=false] Whether to use the source data instead of the data. This will stringify objects as JSON.
* @returns {Array[]} An array of arrays that will be copied to the clipboard.
*/
getRangedData(ranges) {
let useSourceData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
const data = [];
const {
rows,
columns
} = normalizeRanges(ranges);
// concatenate all rows and columns data defined in ranges into one copyable string
arrayEach(rows, row => {
const rowSet = [];
arrayEach(columns, column => {
if (row < 0) {
// `row` as the second argument acts here as the `headerLevel` argument
rowSet.push(this.hot.getColHeader(column, row));
} else {
let copyableCellData = useSourceData ? this.hot.getCopyableSourceData(row, column) : this.hot.getCopyableData(row, column);
if (useSourceData && isObject(copyableCellData)) {
copyableCellData = JSON.stringify(copyableCellData);
}
rowSet.push(copyableCellData);
}
});
data.push(rowSet);
});
return data;
}
/**
* Simulates the paste action.
*
* For security reasons, modern browsers don't allow reading from the system clipboard.
*
* @param {string} pastableText The value to paste, as a raw string.
* @param {string} [pastableHtml=''] The value to paste, as HTML.
*/
paste() {
let pastableText = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let pastableHtml = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : pastableText;
if (!pastableText && !pastableHtml) {
return;
}
const pasteData = new PasteEvent();
if (pastableText) {
pasteData.clipboardData.setData('text/plain', pastableText);
}
if (pastableHtml) {
pasteData.clipboardData.setData('text/html', pastableHtml);
}
this.onPaste(pasteData);
}
/**
* Prepares copyable text from the cells selection in the invisible textarea.
*/
setCopyableText() {
const selectionRange = this.hot.getSelectedRangeActive();
if (!selectionRange) {
return;
}
if (selectionRange.isSingleHeader()) {
this.copyableRanges = [];
return;
}
_classPrivateFieldGet(_copyableRangesFactory, this).setSelectedRange(selectionRange);
const groupedRanges = new Map([['headers', null], ['cells', null]]);
if (_classPrivateFieldGet(_copyMode, this) === 'column-headers-only') {
groupedRanges.set('headers', _classPrivateFieldGet(_copyableRangesFactory, this).getMostBottomColumnHeadersRange());
} else {
if (_classPrivateFieldGet(_copyMode, this) === 'with-column-headers') {
groupedRanges.set('headers', _classPrivateFieldGet(_copyableRangesFactory, this).getMostBottomColumnHeadersRange());
} else if (_classPrivateFieldGet(_copyMode, this) === 'with-column-group-headers') {
groupedRanges.set('headers', _classPrivateFieldGet(_copyableRangesFactory, this).getAllColumnHeadersRange());
}
groupedRanges.set('cells', _classPrivateFieldGet(_copyableRangesFactory, this).getCellsRange());
}
this.copyableRanges = Array.from(groupedRanges.values()).filter(range => range !== null).map(_ref => {
let {
startRow,
startCol,
endRow,
endCol
} = _ref;
return {
startRow,
startCol,
endRow,
endCol
};
});
this.copyableRanges = this.hot.runHooks('modifyCopyableRange', this.copyableRanges);
const cellsRange = groupedRanges.get('cells');
if (cellsRange !== null && cellsRange.isRangeTrimmed) {
const {
startRow,
startCol,
endRow,
endCol
} = cellsRange;
this.hot.runHooks('afterCopyLimit', endRow - startRow + 1, endCol - startCol + 1, this.rowsLimit, this.columnsLimit);
}
}
/**
* Verifies if editor exists and is open.
*
* @private
* @returns {boolean}
*/
isEditorOpened() {
var _this$hot$getActiveEd;
return (_this$hot$getActiveEd = this.hot.getActiveEditor()) === null || _this$hot$getActiveEd === void 0 ? void 0 : _this$hot$getActiveEd.isOpened();
}
/**
* Prepares new values to populate them into datasource.
*
* @private
* @param {Array} inputArray An array of the data to populate.
* @param {Array} sourceInputArray An array of the source data to populate.
* @param {Array} [selection] The selection which indicates from what position the data will be populated.
* @returns {Array} Range coordinates after populate data.
*/
populateValues(inputArray, sourceInputArray) {
let selection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.hot.getSelectedRangeActive();
if (!inputArray.length) {
return [null, null, null, null];
}
const populatedRowsLength = inputArray.length;
const populatedColumnsLength = inputArray[0].length;
const newRows = [];
const {
row: startRow,
col: startColumn
} = selection.getTopStartCorner();
const {
row: endRowFromSelection,
col: endColumnFromSelection
} = selection.getBottomEndCorner();
let visualRowForPopulatedData = startRow;
let visualColumnForPopulatedData = startColumn;
let lastVisualRow = startRow;
let lastVisualColumn = startColumn;
// We try to populate just all copied data or repeat copied data within a selection. Please keep in mind that we
// don't know whether populated data is bigger than selection on start as there are some cells for which values
// should be not inserted (it's known right after getting cell meta).
while (newRows.length < populatedRowsLength || visualRowForPopulatedData <= endRowFromSelection) {
const {
skipRowOnPaste,
visualRow
} = this.hot.getCellMeta(visualRowForPopulatedData, startColumn);
visualRowForPopulatedData = visualRow + 1;
if (skipRowOnPaste === true) {
/* eslint-disable no-continue */
continue;
}
lastVisualRow = visualRow;
visualColumnForPopulatedData = startColumn;
const newRow = [];
const insertedRow = newRows.length % populatedRowsLength;
while (newRow.length < populatedColumnsLength || visualColumnForPopulatedData <= endColumnFromSelection) {
var _sourceInputArray$ins;
const {
skipColumnOnPaste,
visualCol
} = this.hot.getCellMeta(visualRow, visualColumnForPopulatedData);
const sourceDataAtTarget = this.hot.getSourceDataAtCell(visualRow, visualColumnForPopulatedData);
const insertedColumn = newRow.length % populatedColumnsLength;
visualColumnForPopulatedData = visualCol + 1;
if (skipColumnOnPaste === true) {
/* eslint-disable no-continue */
continue;
}
lastVisualColumn = visualCol;
const sourceCellValue = sourceInputArray === null || sourceInputArray === void 0 || (_sourceInputArray$ins = sourceInputArray[insertedRow]) === null || _sourceInputArray$ins === void 0 ? void 0 : _sourceInputArray$ins[insertedColumn];
let cellValue = inputArray[insertedRow][insertedColumn];
if (sourceInputArray && isJSON(sourceCellValue)) {
const parsedCellValue = JSON.parse(sourceCellValue);
if (isObject(sourceDataAtTarget) || sourceDataAtTarget === null) {
cellValue = parsedCellValue;
}
}
newRow.push(cellValue);
}
newRows.push(newRow);
}
_classPrivateFieldSet(_preventViewportScrollOnPaste, this, true);
this.hot.populateFromArray(startRow, startColumn, newRows, undefined, undefined, 'CopyPaste.paste', this.pasteMode);
return [startRow, startColumn, lastVisualRow, lastVisualColumn];
}
/**
* Add the `contenteditable` attribute to the highlighted cell and select its content.
*/
/**
* `copy` event callback on textarea element.
*
* @param {Event} event ClipboardEvent.
* @private
*/
onCopy(event) {
const eventTarget = event.composedPath()[0];
const focusedElement = this.hot.getFocusManager().getRefocusElement();
const isHotInput = eventTarget === null || eventTarget === void 0 ? void 0 : eventTarget.hasAttribute('data-hot-input');
if (!this.hot.isListening() && !_classPrivateFieldGet(_isTriggeredByCopy, this) || this.isEditorOpened() || isHTMLElement(eventTarget) && (isHotInput && eventTarget !== focusedElement || !isHotInput && eventTarget !== this.hot.rootDocument.body && !isInternalElement(eventTarget, this.hot.rootElement))) {
return;
}
event.preventDefault();
this.setCopyableText();
_classPrivateFieldSet(_isTriggeredByCopy, this, false);
const rangedData = this.getRangedData(this.copyableRanges);
const rangedSourceData = this.getRangedData(this.copyableRanges, true);
const copiedHeadersCount = _assertClassBrand(_CopyPaste_brand, this, _countCopiedHeaders).call(this, this.copyableRanges);
const allowCopying = !!this.hot.runHooks('beforeCopy', rangedData, this.copyableRanges, copiedHeadersCount);
if (allowCopying) {
_assertClassBrand(_CopyPaste_brand, this, _setClipboardData).call(this, event, rangedData, rangedSourceData);
this.hot.runHooks('afterCopy', rangedData, this.copyableRanges, copiedHeadersCount);
}
_classPrivateFieldSet(_copyMode, this, 'cells-only');
}
/**
* `cut` event callback on textarea element.
*
* @param {Event} event ClipboardEvent.
* @private
*/
onCut(event) {
const eventTarget = event.composedPath()[0];
const focusedElement = this.hot.getFocusManager().getRefocusElement();
const isHotInput = eventTarget === null || eventTarget === void 0 ? void 0 : eventTarget.hasAttribute('data-hot-input');
if (!this.hot.isListening() && !_classPrivateFieldGet(_isTriggeredByCut, this) || this.isEditorOpened() || isHTMLElement(eventTarget) && (isHotInput && eventTarget !== focusedElement || !isHotInput && eventTarget !== this.hot.rootDocument.body && !isInternalElement(eventTarget, this.hot.rootElement))) {
return;
}
event.preventDefault();
this.setCopyableText();
_classPrivateFieldSet(_isTriggeredByCut, this, false);
const rangedData = this.getRangedData(this.copyableRanges);
const rangedSourceData = this.getRangedData(this.copyableRanges, true);
const allowCuttingOut = !!this.hot.runHooks('beforeCut', rangedData, this.copyableRanges);
if (allowCuttingOut) {
_assertClassBrand(_CopyPaste_brand, this, _setClipboardData).call(this, event, rangedData, rangedSourceData);
this.hot.emptySelectedCells('CopyPaste.cut');
this.hot.runHooks('afterCut', rangedData, this.copyableRanges);
}
}
/**
* `paste` event callback on textarea element.
*
* @param {Event} event ClipboardEvent or pseudo ClipboardEvent, if paste was called manually.
* @private
*/
onPaste(event) {
const eventTarget = event.composedPath()[0];
const focusedElement = this.hot.getFocusManager().getRefocusElement();
const isHotInput = eventTarget === null || eventTarget === void 0 ? void 0 : eventTarget.hasAttribute('data-hot-input');
if (!this.hot.isListening() || this.isEditorOpened() || !this.hot.getSelected() || isHTMLElement(eventTarget) && (isHotInput && eventTarget !== focusedElement || !isHotInput && eventTarget !== this.hot.rootDocument.body && !isInternalElement(eventTarget, this.hot.rootElement))) {
return;
}
event.preventDefault();
let pastedData;
let pastedSourceData;
if (event && typeof event.clipboardData !== 'undefined') {
const sourceDataHTML = event.clipboardData.getData(SOURCE_DATA_HTML_MIME_TYPE);
if (sourceDataHTML) {
const parsedSourceConfig = htmlToGridSettings(sourceDataHTML, this.hot.rootDocument);
pastedSourceData = parsedSourceConfig.data;
}
const textHTML = sanitize(event.clipboardData.getData('text/html'), {
ADD_TAGS: ['meta'],
ADD_ATTR: ['content'],
FORCE_BODY: true
});
if (textHTML && /(<table)|(<TABLE)/g.test(textHTML)) {
const parsedConfig = htmlToGridSettings(textHTML, this.hot.rootDocument);
pastedData = parsedConfig.data;
} else {
pastedData = event.clipboardData.getData('text/plain');
}
} else if (typeof ClipboardEvent === 'undefined' && typeof this.hot.rootWindow.clipboardData !== 'undefined') {
pastedData = this.hot.rootWindow.clipboardData.getData('Text');
}
if (typeof pastedData === 'string') {
pastedData = parse(pastedData);
}
if (pastedData === void 0 || pastedData && pastedData.length === 0) {
return;
}
if (this.hot.runHooks('beforePaste', pastedData, this.copyableRanges) === false) {
return;
}
const [startRow, startColumn, endRow, endColumn] = this.populateValues(pastedData, pastedSourceData);
if (startRow !== null && startColumn !== null) {
this.hot.selectCell(startRow, startColumn, Math.min(this.hot.countRows() - 1, endRow), Math.min(this.hot.countCols() - 1, endColumn));
}
this.hot.runHooks('afterPaste', pastedData, this.copyableRanges);
}
/**
* Sets the clipboard data.
*
* @param {ClipboardEvent} event The Clipboard event.
* @param {Array} rangedData Ranged data to set to the clipboard.
* @param {Array} rangedSourceData Ranged source data to set to the clipboard.
*/
/**
* Destroys the `CopyPaste` plugin instance.
*/
destroy() {
super.destroy();
}
}
function _ensureClipboardEventsGetTriggered(eventName) {
// Without this workaround Safari (tested on Safari@16.5.2) does not trigger the 'copy' event.
if (isSafari()) {
const activeSelectedRange = this.hot.getSelectedRangeActive();
if (activeSelectedRange) {
const {
row: highlightRow,
col: highlightColumn
} = activeSelectedRange.highlight;
const currentlySelectedCell = this.hot.getCell(highlightRow, highlightColumn, true);
if (currentlySelectedCell) {
runWithSelectedContendEditableElement(currentlySelectedCell, () => {
this.hot.rootDocument.execCommand(eventName);
});
}
}
} else {
this.hot.rootDocument.execCommand(eventName);
}
}
/**
* Counts how many column headers will be copied based on the passed range.
*
* @private
* @param {Array<{startRow: number, startCol: number, endRow: number, endCol: number}>} ranges Array of objects with properties `startRow`, `startCol`, `endRow` and `endCol`.
* @returns {{ columnHeadersCount: number }} Returns an object with keys that holds
* information with the number of copied headers.
*/
function _countCopiedHeaders(ranges) {
const {
rows
} = normalizeRanges(ranges);
let columnHeadersCount = 0;
for (let row = 0; row < rows.length; row++) {
if (rows[row] >= 0) {
break;
}
columnHeadersCount += 1;
}
return {
columnHeadersCount
};
}
function _addContentEditableToHighlightedCell() {
if (this.hot.isListening()) {
const activeSelectedRange = this.hot.getSelectedRangeActive();
if (activeSelectedRange) {
const {
row: highlightRow,
col: highlightColumn
} = activeSelectedRange.highlight;
const currentlySelectedCell = this.hot.getCell(highlightRow, highlightColumn, true);
if (currentlySelectedCell) {
makeElementContentEditableAndSelectItsContent(currentlySelectedCell);
}
}
}
}
/**
* Remove the `contenteditable` attribute from the highlighted cell and deselect its content.
*/
function _removeContentEditableFromHighlightedCell() {
// If the instance is not listening, the workaround is not needed.
if (this.hot.isListening()) {
const activeSelectedRange = this.hot.getSelectedRangeActive();
if (activeSelectedRange) {
const {
row: highlightRow,
col: highlightColumn
} = activeSelectedRange.highlight;
const currentlySelectedCell = this.hot.getCell(highlightRow, highlightColumn, true);
if (currentlySelectedCell !== null && currentlySelectedCell !== void 0 && currentlySelectedCell.hasAttribute('contenteditable')) {
removeContentEditableFromElementAndDeselect(currentlySelectedCell);
}
}
}
}
function _setClipboardData(event, rangedData, rangedSourceData) {
const textPlain = stringify(rangedData);
if (event && event.clipboardData) {
const textHTML = _dataToHTML(rangedData);
const textSourceDataHTML = _dataToHTML(rangedSourceData);
event.clipboardData.setData('text/plain', textPlain);
event.clipboardData.setData('text/html', [META_HEAD, textHTML].join(''));
event.clipboardData.setData(SOURCE_DATA_HTML_MIME_TYPE, [META_HEAD, textSourceDataHTML].join(''));
} else if (typeof ClipboardEvent === 'undefined') {
this.hot.rootWindow.clipboardData.setData('Text', textPlain);
}
}
/**
* Add copy and cut options to the Context Menu.
*
* @param {object} options Contains default added options of the Context Menu.
*/
function _onAfterContextMenuDefaultOptions(options) {
options.items.push({
name: '---------'
}, copyItem(this));
if (_classPrivateFieldGet(_enableCopyColumnHeaders, this)) {
options.items.push(copyWithColumnHeadersItem(this));
}
if (_classPrivateFieldGet(_enableCopyColumnGroupHeaders, this)) {
options.items.push(copyWithColumnGroupHeadersItem(this));
}
if (_classPrivateFieldGet(_enableCopyColumnHeadersOnly, this)) {
options.items.push(copyColumnHeadersOnlyItem(this));
}
options.items.push(cutItem(this));
}
/**
* Disables the viewport scroll after pasting the data.
*
* @param {number} fromRow Selection start row visual index.
* @param {number} fromColumn Selection start column visual index.
* @param {number} toRow Selection end row visual index.
* @param {number} toColumn Selection end column visual index.
* @param {object} preventScrolling Object with `value` property. If `true`, the viewport scroll will be prevented.
*/
function _onAfterSelection(fromRow, fromColumn, toRow, toColumn, preventScrolling) {
if (_classPrivateFieldGet(_preventViewportScrollOnPaste, this)) {
preventScrolling.value = true;
}
_classPrivateFieldSet(_preventViewportScrollOnPaste, this, false);
}
/**
* Force focus on focusableElement after end of the selection.
*/
function _onAfterSelectionEnd() {
if (this.isEditorOpened()) {
return;
}
if (this.hot.getSettings().fragmentSelection) {
return;
}
this.setCopyableText();
}
/**
* `document.body` `mouseenter` callback used to work around a Safari's problem with copying/cutting from the
* browser's menu.
*/
function _onSafariMouseEnter() {
_assertClassBrand(_CopyPaste_brand, this, _removeContentEditableFromHighlightedCell).call(this);
}
/**
* `document.body` `mouseleave` callback used to work around a Safari's problem with copying/cutting from the
* browser's menu.
*/
function _onSafariMouseLeave() {
_assertClassBrand(_CopyPaste_brand, this, _addContentEditableToHighlightedCell).call(this);
}
/**
* `afterSelection` hook callback triggered only on Safari.
*/
function _onSafariAfterSelection() {
_assertClassBrand(_CopyPaste_brand, this, _removeContentEditableFromHighlightedCell).call(this);
}