@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
221 lines (220 loc) • 7.83 kB
JavaScript
/*!
* Built by Revolist OU ❤️
*/
import debounce from "lodash/debounce";
import { h } from "@stencil/core";
import { CELL_HANDLER_CLASS, MOBILE_CLASS } from "../../utils/consts";
import { collectModelsOfRange, getCell, getCurrentCell, isAfterLast, } from "./selection.utils";
import { getRange } from "../../store/index";
import { getPropertyFromEvent } from "../../utils/events";
export class AutoFillService {
constructor(sv) {
this.sv = sv;
this.autoFillType = null;
this.autoFillInitial = null;
this.autoFillStart = null;
this.autoFillLast = null;
}
/**
* Render autofill box
* @param range
* @param selectionFocus
*/
renderAutofill(range, selectionFocus, isMobile = false) {
let handlerStyle;
if (range) {
handlerStyle = getCell(range, this.sv.dimensionRow.state, this.sv.dimensionCol.state);
}
else {
handlerStyle = getCell(Object.assign(Object.assign({}, selectionFocus), { x1: selectionFocus.x, y1: selectionFocus.y }), this.sv.dimensionRow.state, this.sv.dimensionCol.state);
}
return (h("div", { class: {
[CELL_HANDLER_CLASS]: true,
[MOBILE_CLASS]: isMobile,
}, style: {
left: `${handlerStyle.right}px`,
top: `${handlerStyle.bottom}px`,
}, onMouseDown: (e) => this.autoFillHandler(e), onTouchStart: (e) => this.autoFillHandler(e) }));
}
autoFillHandler(e, type = "AutoFill" /* AutoFillType.autoFill */) {
let target = null;
if (e.target instanceof Element) {
target = e.target;
}
if (!target) {
return;
}
this.selectionStart(target, this.sv.getData(), type);
e.preventDefault();
}
get isAutoFill() {
return !!this.autoFillType;
}
/**
* Process mouse move events
*/
selectionMouseMove(e) {
// initiate mouse move debounce if not present
if (!this.onMouseMoveAutofill) {
this.onMouseMoveAutofill = debounce((e, data) => this.doAutofillMouseMove(e, data), 5);
}
if (this.isAutoFill) {
this.onMouseMoveAutofill(e, this.sv.getData());
}
}
getFocus(focus, range) {
// there was an issue that it was taking last cell from range but focus was out
if (!focus && range) {
focus = { x: range.x, y: range.y };
}
return focus || null;
}
/**
* Autofill logic:
* on mouse move apply based on previous direction (if present)
*/
doAutofillMouseMove(event, data) {
// if no initial - not started
if (!this.autoFillInitial) {
return;
}
const x = getPropertyFromEvent(event, 'clientX', MOBILE_CLASS);
const y = getPropertyFromEvent(event, 'clientY', MOBILE_CLASS);
// skip touch
if (x === null || y === null) {
return;
}
const current = getCurrentCell({ x, y }, data);
// first time or direction equal to start(same as first time)
if (!this.autoFillLast) {
if (!this.autoFillLast) {
this.autoFillLast = this.autoFillStart;
}
}
// check if not the latest, if latest - do nothing
if (isAfterLast(current, data.lastCell)) {
return;
}
this.autoFillLast = current;
const isSame = current.x === this.autoFillInitial.x &&
current.y === this.autoFillInitial.y;
// if same as initial - clear
if (isSame) {
this.sv.setTempRange(null);
}
else {
const area = getRange(this.autoFillInitial, this.autoFillLast);
this.sv.setTempRange({
area,
type: this.autoFillType,
});
}
}
/**
* Range selection started
* Mode @param type:
* Can be triggered from MouseDown selection on element
* Or can be triggered on corner square drag
*/
selectionStart(target, data, type = "Selection" /* AutoFillType.selection */) {
/** Get cell by autofill element */
const { top, left } = target.getBoundingClientRect();
this.autoFillInitial = this.getFocus(data.focus, data.range);
this.autoFillType = type;
this.autoFillStart = getCurrentCell({ x: left, y: top }, data);
}
/**
* Clear current range selection on mouse up and mouse leave events
*/
clearAutoFillSelection(focus, oldRange) {
// If autofill was active, apply autofill values
if (this.autoFillInitial) {
// Fetch latest focus
this.autoFillInitial = this.getFocus(focus, oldRange);
// Apply range data if autofill mode is active
if (this.autoFillType === "AutoFill" /* AutoFillType.autoFill */) {
const range = getRange(this.autoFillInitial, this.autoFillLast);
// If range is present, apply data
if (range) {
const { defaultPrevented: stopApply, detail: { range: newRange }, } = this.sv.clearRangeDataApply({
range,
});
// If data apply was not prevented, apply new range
if (!stopApply && oldRange) {
this.applyRangeWithData(newRange, oldRange);
}
else {
// If data apply was prevented, clear temporary range
this.sv.setTempRange(null);
}
}
}
else {
// If not autofill mode, apply range only
this.applyRangeOnly(this.autoFillInitial, this.autoFillLast);
}
}
// Reset autofill state
this.resetAutoFillState();
}
/**
* Reset autofill state
*/
resetAutoFillState() {
this.autoFillType = null;
this.autoFillInitial = null;
this.autoFillLast = null;
this.autoFillStart = null;
}
/**
* Trigger range apply events and handle responses
*/
onRangeApply(newData, newRange, oldRange) {
this.sv.rangeDataApply({
data: newData,
models: collectModelsOfRange(newData, this.sv.dataStore),
type: this.sv.dataStore.get('type'),
oldRange,
newRange
});
this.sv.setRange(newRange);
}
/** Apply range and copy data during range application */
applyRangeWithData(newRange, rangeToCopy) {
const rangeData = {
type: this.sv.dataStore.get('type'),
colType: this.sv.columnService.type,
newData: {},
mapping: {},
newRange,
oldRange: rangeToCopy,
};
const { mapping, changed } = this.sv.columnService.getRangeData(rangeData, this.sv.columnService.columns);
rangeData.newData = changed;
rangeData.mapping = mapping;
let e = this.sv.selectionChanged(rangeData);
// if default prevented - clear range
if (e.defaultPrevented) {
this.sv.setTempRange(null);
return;
}
e = this.sv.rangeCopy(rangeData);
if (e.defaultPrevented) {
this.sv.setRange(newRange);
return;
}
this.onRangeApply(rangeData.newData, newRange, rangeToCopy);
}
/**
* Update range selection only,
* no data change (mouse selection)
*/
applyRangeOnly(start, end) {
// no changes to apply
if (!start || !end) {
return;
}
const newRange = getRange(start, end);
this.sv.setRange(newRange);
}
}