@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
457 lines (449 loc) • 16.5 kB
JavaScript
/*!
* Built by Revolist OU ❤️
*/
'use strict';
var index = require('./index-Dq8Xzj5l.js');
var dimension_helpers = require('./dimension.helpers-B9HgANnM.js');
var textEditor = require('./text-editor-B4W-m-r-.js');
var edit_utils = require('./edit.utils-pKeiYFLJ.js');
var debounce = require('./debounce-CcpHiH2p.js');
require('./index-DxaSE5uZ.js');
const Clipboard = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.beforePaste = index.createEvent(this, "beforepaste", 7);
this.beforePasteApply = index.createEvent(this, "beforepasteapply", 7);
this.pasteRegion = index.createEvent(this, "pasteregion", 7);
this.afterPasteApply = index.createEvent(this, "afterpasteapply", 7);
this.beforeCut = index.createEvent(this, "beforecut", 7);
this.clearRegion = index.createEvent(this, "clearregion", 7);
this.beforeCopy = index.createEvent(this, "beforecopy", 7);
this.beforeCopyApply = index.createEvent(this, "beforecopyapply", 7);
this.copyRegion = index.createEvent(this, "copyregion", 7);
}
onPaste(e) {
// if readonly do nothing
if (this.readonly) {
return;
}
const clipboardData = this.getData(e);
const isHTML = ((clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.types.indexOf('text/html')) || -1) > -1;
const data = (isHTML
? clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text/html')
: clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text')) || '';
const dataText = (clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text')) || '';
const beforePaste = this.beforePaste.emit({
raw: data,
dataText,
isHTML,
event: e,
});
if (beforePaste.defaultPrevented) {
return;
}
let parsedData;
// if html, then search for table if no table fallback to regular text parsing
if (beforePaste.detail.isHTML) {
const table = this.htmlParse(beforePaste.detail.raw);
// fallback to text if not possible to parse as html
parsedData = table || this.textParse(dataText || '');
}
else {
parsedData = this.textParse(beforePaste.detail.raw);
}
const beforePasteApply = this.beforePasteApply.emit({
raw: data,
parsed: parsedData,
dataText,
event: e,
});
if (beforePasteApply.defaultPrevented) {
return;
}
this.pasteRegion.emit(beforePasteApply.detail.parsed);
// post paste action
const afterPasteApply = this.afterPasteApply.emit({
raw: data,
parsed: parsedData,
dataText,
event: e,
});
// keep default behavior if needed
if (afterPasteApply.defaultPrevented) {
return;
}
e.preventDefault();
}
/**
* Listen to copy event and emit copy region event
*/
copyStarted(e) {
const beforeCopy = this.beforeCopy.emit({
event: e,
});
if (beforeCopy.defaultPrevented) {
return;
}
const data = this.getData(beforeCopy.detail.event);
e.preventDefault();
this.copyRegion.emit(data || undefined);
}
/**
* Listen to copy event and emit copy region event
*/
cutStarted(e) {
const beforeCut = this.beforeCut.emit({
event: e,
});
if (beforeCut.defaultPrevented) {
return;
}
const data = this.getData(beforeCut.detail.event);
this.copyStarted(e);
// if readonly do nothing
if (this.readonly) {
return;
}
this.clearRegion.emit(data || undefined);
e.preventDefault();
}
async doCopy(e, data) {
const beforeCopyApply = this.beforeCopyApply.emit({
event: e,
data,
});
if (beforeCopyApply.defaultPrevented) {
return;
}
const parsed = data ? this.parserCopy(data) : '';
e.setData('text/plain', parsed);
}
parserCopy(data) {
return data.map(rgRow => rgRow.join('\t')).join('\n');
}
textParse(data) {
const result = [];
const rows = data.split(/\r\n|\n|\r/);
for (let y in rows) {
result.push(rows[y].split('\t'));
}
return result;
}
htmlParse(data) {
const result = [];
const fragment = document.createRange().createContextualFragment(data);
const table = fragment.querySelector('table');
if (!table) {
return null;
}
for (const rgRow of Array.from(table.rows)) {
result.push(Array.from(rgRow.cells).map(cell => cell.innerText));
}
return result;
}
getData(e) {
return (e.clipboardData ||
(window === null || window === void 0 ? void 0 : window.clipboardData));
}
};
const revogrEditStyleCss = () => `revogr-edit{display:block;position:absolute;background-color:#fff}revogr-edit input{height:100%;width:100%;box-sizing:border-box}revogr-edit revo-dropdown{height:100%}revogr-edit revo-dropdown.shrink fieldset legend>span{display:none}`;
const RevoEdit = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.cellEdit = index.createEvent(this, "celleditinit", 7);
this.closeEdit = index.createEvent(this, "closeedit", 7);
/**
* Save on editor close. Defines if data should be saved on editor close.
*/
this.saveOnClose = false;
this.currentEditor = null;
this.preventSaveOnClose = false;
}
/**
* Cancel pending changes flag. Editor will be closed without autosave.
*/
async cancelChanges() {
this.preventSaveOnClose = true;
}
/**
* Before editor got disconnected.
* Can be triggered multiple times before actual disconnect.
*/
async beforeDisconnect() {
var _a, _b;
(_b = (_a = this.currentEditor) === null || _a === void 0 ? void 0 : _a.beforeDisconnect) === null || _b === void 0 ? void 0 : _b.call(_a);
}
onAutoSave() {
var _a, _b, _c;
this.preventSaveOnClose = true;
const val = (_b = (_a = this.currentEditor) === null || _a === void 0 ? void 0 : _a.getValue) === null || _b === void 0 ? void 0 : _b.call(_a);
// For Editor plugin internal usage.
// When you want to prevent save and use custom save of your own.
if ((_c = this.currentEditor) === null || _c === void 0 ? void 0 : _c.beforeAutoSave) {
const canSave = this.currentEditor.beforeAutoSave(val);
if (canSave === false) {
return;
}
}
this.onSave(val, true);
}
/**
* Callback triggered when cell editor saved.
* Closes editor when called.
* @param preventFocus - if true, editor will not be closed & next cell will not be focused.
*/
onSave(val, preventFocus) {
this.preventSaveOnClose = true;
if (this.editCell) {
this.cellEdit.emit({
rgCol: this.editCell.x,
rgRow: this.editCell.y,
type: this.editCell.type,
prop: this.editCell.prop,
val,
preventFocus,
});
}
}
componentWillRender() {
// Active editor present and not yet closed.
if (this.currentEditor || !this.column) {
return;
}
this.preventSaveOnClose = false;
// Custom editor usage.
// Start with TextEditor (editors/text.tsx) for Custom editor.
// It can be class or function
if (this.editor) {
// if editor is constructible
if (edit_utils.isEditorCtrConstructible(this.editor)) {
this.currentEditor = new this.editor(this.column,
// save callback
(e, preventFocus) => {
this.onSave(e, preventFocus);
},
// cancel callback
focusNext => {
this.preventSaveOnClose = true;
this.closeEdit.emit(focusNext);
});
// if editor is function
}
else {
this.currentEditor = this.editor(this.column,
// save callback
(e, preventFocus) => {
this.onSave(e, preventFocus);
},
// cancel callback
focusNext => {
this.preventSaveOnClose = true;
this.closeEdit.emit(focusNext);
});
}
return;
}
// Default text editor usage
this.currentEditor = new textEditor.TextEditor(this.column, (e, preventFocus) => this.onSave(e, preventFocus));
}
componentDidRender() {
var _a, _b;
if (!this.currentEditor) {
return;
}
this.currentEditor.element = this.element.firstElementChild;
(_b = (_a = this.currentEditor).componentDidRender) === null || _b === void 0 ? void 0 : _b.call(_a);
}
disconnectedCallback() {
var _a, _b;
if (this.saveOnClose) {
// Can not be cancelled by `preventSaveOnClose` prop.
// Editor requires `getValue` to be able to save.
if (!this.preventSaveOnClose) {
this.onAutoSave();
}
}
this.preventSaveOnClose = false;
if (!this.currentEditor) {
return;
}
(_b = (_a = this.currentEditor).disconnectedCallback) === null || _b === void 0 ? void 0 : _b.call(_a);
this.currentEditor.element = null;
this.currentEditor = null;
}
render() {
if (this.currentEditor) {
this.currentEditor.editCell = this.editCell;
return (index.h(index.Host, { class: dimension_helpers.EDIT_INPUT_WR }, this.currentEditor.render(index.h, this.additionalData)));
}
return '';
}
get element() { return index.getElement(this); }
};
RevoEdit.style = revogrEditStyleCss();
class RowOrderService {
constructor(config) {
this.config = config;
this.currentCell = null;
this.previousRow = null;
}
/** Drag finished, calculate and apply changes */
endOrder(e, data) {
if (this.currentCell === null) {
return;
}
const newRow = this.getCell(e, data);
// if position changed
if (newRow.y !== this.currentCell.y) {
// rgRow dragged out table
if (newRow.y < 0) {
newRow.y = 0;
}
// rgRow dragged to the top
else if (newRow.y < this.currentCell.y) {
newRow.y++;
}
this.config.positionChanged(this.currentCell.y, newRow.y);
}
this.clear();
}
/** Drag started, reserve initial cell for farther use */
startOrder(e, data) {
this.currentCell = this.getCell(e, data);
return this.currentCell;
}
move(y, data) {
const rgRow = this.getRow(y, data);
// if rgRow same as previous or below range (-1 = 0) do nothing
if (this.previousRow === rgRow.itemIndex || rgRow.itemIndex < -1) {
return null;
}
this.previousRow = rgRow.itemIndex;
return rgRow;
}
/** Drag stopped, probably cursor outside of document area */
clear() {
this.currentCell = null;
this.previousRow = null;
}
/** Calculate cell based on x, y position */
getRow(y, { el, rows }) {
const { top } = el.getBoundingClientRect();
const topRelative = y - top;
const rowOffset = rows.renderOffset || 0;
const rgRow = dimension_helpers.getItemByPosition(rows, topRelative + rowOffset);
const absolutePosition = {
itemIndex: rgRow.itemIndex,
start: rgRow.start - rowOffset + top,
end: rgRow.end - rowOffset + top,
};
return absolutePosition;
}
/** Calculate cell based on x, y position */
getCell({ x, y }, { el, rows, cols }) {
const { top, left } = el.getBoundingClientRect();
const topRelative = y - top;
const leftRelative = x - left;
const rgRow = dimension_helpers.getItemByPosition(rows, topRelative + (rows.renderOffset || 0));
const rgCol = dimension_helpers.getItemByPosition(cols, leftRelative + (cols.renderOffset || 0));
return { x: rgCol.itemIndex, y: rgRow.itemIndex };
}
}
const OrderEditor = class {
constructor(hostRef) {
index.registerInstance(this, hostRef);
this.rowDragStart = index.createEvent(this, "rowdragstartinit", 7);
this.rowDragEnd = index.createEvent(this, "rowdragendinit", 7);
this.rowDrag = index.createEvent(this, "rowdragmoveinit", 7);
this.rowMouseMove = index.createEvent(this, "rowdragmousemove", 7);
this.rowDropped = index.createEvent(this, "rowdropinit", 7);
this.rowOrderChange = index.createEvent(this, "roworderchange", 7);
this.events = [];
this.rowMoveFunc = debounce.debounce((y) => {
const rgRow = this.rowOrderService.move(y, this.getData());
if (rgRow !== null) {
this.rowDrag.emit(Object.assign(Object.assign({}, rgRow), { rowType: this.rowType }));
}
}, 5);
}
// #endregion
// #region Methods
async dragStart(e) {
e.originalEvent.preventDefault();
// extra check if previous ended
if (this.events.length) {
this.clearOrder();
}
const data = this.getData();
const cell = this.rowOrderService.startOrder(e.originalEvent, data);
const pos = this.rowOrderService.getRow(e.originalEvent.y, data);
const dragStartEvent = this.rowDragStart.emit({
cell,
text: dimension_helpers.DRAGG_TEXT,
pos,
event: e.originalEvent,
rowType: this.rowType,
model: dimension_helpers.getSourceItem(this.dataStore, pos.itemIndex),
});
if (dragStartEvent.defaultPrevented) {
return;
}
const moveMove = (e) => this.move(e);
const mouseUp = (e) => this.endOrder(e);
const mouseLeave = () => this.clearOrder();
this.events.push({
name: 'mousemove',
listener: moveMove,
}, {
name: 'mouseup',
listener: mouseUp,
}, {
name: 'mouseleave',
listener: mouseLeave,
});
document.addEventListener('mousemove', moveMove);
// Action finished inside of the document
document.addEventListener('mouseup', mouseUp);
document.addEventListener('mouseleave', mouseLeave);
}
async endOrder(e) {
this.rowOrderService.endOrder(e, this.getData());
this.clearOrder();
}
async clearOrder() {
this.rowOrderService.clear();
this.events.forEach(v => document.removeEventListener(v.name, v.listener));
this.events.length = 0;
this.rowDragEnd.emit({ rowType: this.rowType });
}
// #endregion
move({ x, y }) {
this.rowMouseMove.emit({ x, y, rowType: this.rowType });
this.rowMoveFunc(y);
}
connectedCallback() {
this.rowOrderService = new RowOrderService({
positionChanged: (from, to) => {
const dropEvent = this.rowDropped.emit({
from,
to,
rowType: this.rowType,
});
if (dropEvent.defaultPrevented) {
return;
}
this.rowOrderChange.emit(dropEvent.detail);
},
});
}
getData() {
return {
el: this.parent,
rows: this.dimensionRow.state,
cols: this.dimensionCol.state,
};
}
};
exports.revogr_clipboard = Clipboard;
exports.revogr_edit = RevoEdit;
exports.revogr_order_editor = OrderEditor;