fix: excel scroll bugs

This commit is contained in:
sealday 2024-03-21 16:08:32 +08:00
parent 6dab16e437
commit 18e929a275
4 changed files with 83 additions and 151 deletions

View File

@ -1,31 +1,29 @@
/* global document */
/* global window */
class Element {
constructor(tag, className = '') {
private el: HTMLElement;
private _data: Record<string, any>;
constructor(tag: HTMLElement | string, className = '') {
if (typeof tag === 'string') {
this.el = document.createElement(tag);
this.el.className = className;
} else {
this.el = tag;
}
this.data = {};
this._data = {};
}
data(key, value) {
if (value !== undefined) {
this.data[key] = value;
this._data[key] = value;
return this;
}
return this.data[key];
return this._data[key];
}
on(eventNames, handler) {
const [fen, ...oen] = eventNames.split('.');
let eventName = fen;
if (eventName === 'mousewheel' && /Firefox/i.test(window.navigator.userAgent)) {
eventName = 'DOMMouseScroll';
}
const eventName = fen;
this.el.addEventListener(eventName, (evt) => {
console.log('excel debug', eventName, eventNames, oen, evt);
handler(evt);
for (let i = 0; i < oen.length; i += 1) {
const k = oen[i];
@ -37,6 +35,7 @@ class Element {
}
if (k === 'stop') {
evt.stopPropagation();
evt.preventDefault();
}
}
});
@ -50,9 +49,7 @@ class Element {
});
return this;
}
const {
offsetTop, offsetLeft, offsetHeight, offsetWidth,
} = this.el;
const { offsetTop, offsetLeft, offsetHeight, offsetWidth } = this.el;
return {
top: offsetTop,
left: offsetLeft,
@ -61,7 +58,7 @@ class Element {
};
}
scroll(v) {
scroll(v?) {
const { el } = this;
if (v !== undefined) {
if (v.left !== undefined) {
@ -79,14 +76,14 @@ class Element {
}
parent() {
return new Element(this.el.parentNode);
return new Element(this.el.parentElement);
}
children(...eles) {
if (arguments.length === 0) {
return this.el.childNodes;
}
eles.forEach(ele => this.child(ele));
eles.forEach((ele) => this.child(ele));
return this;
}
@ -94,38 +91,6 @@ class Element {
this.el.removeChild(el);
}
/*
first() {
return this.el.firstChild;
}
last() {
return this.el.lastChild;
}
remove(ele) {
return this.el.removeChild(ele);
}
prepend(ele) {
const { el } = this;
if (el.children.length > 0) {
el.insertBefore(ele, el.firstChild);
} else {
el.appendChild(ele);
}
return this;
}
prev() {
return this.el.previousSibling;
}
next() {
return this.el.nextSibling;
}
*/
child(arg) {
let ele = arg;
if (typeof arg === 'string') {
@ -219,6 +184,9 @@ class Element {
}
val(v) {
if (!('value' in this.el)) {
return;
}
if (v !== undefined) {
this.el.value = v;
return this;
@ -231,14 +199,17 @@ class Element {
}
cssRemoveKeys(...keys) {
keys.forEach(k => this.el.style.removeProperty(k));
keys.forEach((k) => this.el.style.removeProperty(k));
return this;
}
css(name: string): string;
css(properties: any): Element;
css(name: string, value: any): Element;
// css( propertyName )
// css( propertyName, value )
// css( properties )
css(name, value) {
css(name, value?): Element | string {
if (value === undefined && typeof name !== 'string') {
Object.keys(name).forEach((k) => {
this.el.style[k] = name[k];
@ -269,7 +240,4 @@ class Element {
const h = (tag, className = '') => new Element(tag, className);
export {
Element,
h,
};
export { Element, h };

View File

@ -1,23 +1,27 @@
import { h } from './element';
import { h, Element } from './element';
import { cssPrefix } from '../config';
export default class Scrollbar {
constructor(vertical) {
this.vertical = vertical;
this.moveFn = null;
private el: Element;
private contentEl: Element;
private _moveFn: (type: string, handler: (e: Event) => void) => void;
constructor(private vertical: 'vertical' | 'horizontal') {
this._moveFn = null;
this.el = h('div', `${cssPrefix}-scrollbar ${vertical ? 'vertical' : 'horizontal'}`)
.child(this.contentEl = h('div', ''))
.child((this.contentEl = h('div', '')))
.on('mousemove.stop', () => {})
.on('scroll.stop', (evt) => {
const { scrollTop, scrollLeft } = evt.target;
// console.log('scrollTop:', scrollTop);
if (this.moveFn) {
this.moveFn(this.vertical ? scrollTop : scrollLeft, evt);
if (this._moveFn) {
this._moveFn(this.vertical ? scrollTop : scrollLeft, evt);
}
// console.log('evt:::', evt);
});
}
set moveFn(fn: (type: string, handler: (e: Event) => void) => void) {
this._moveFn = fn;
}
move(v) {
this.el.scroll(v);
return this;
@ -29,14 +33,10 @@ export default class Scrollbar {
set(distance, contentDistance) {
const d = distance - 1;
// console.log('distance:', distance, ', contentDistance:', contentDistance);
if (contentDistance > d) {
const cssKey = this.vertical ? 'height' : 'width';
// console.log('d:', d);
this.el.css(cssKey, `${d - 15}px`).show();
this.contentEl
.css(this.vertical ? 'width' : 'height', '1px')
.css(cssKey, `${contentDistance}px`);
this.contentEl.css(this.vertical ? 'width' : 'height', '1px').css(cssKey, `${contentDistance}px`);
} else {
this.el.hide();
}

View File

@ -1,11 +1,6 @@
/* global window */
import { h } from './element';
import {
bind,
mouseMoveUp,
bindTouch,
createEventEmitter,
} from './event';
import { bind, mouseMoveUp, bindTouch, createEventEmitter } from './event';
import Resizer from './resizer';
import Scrollbar from './scrollbar';
import Selector from './selector';
@ -40,12 +35,8 @@ function throttle(func, wait) {
}
function scrollbarMove() {
const {
data, verticalScrollbar, horizontalScrollbar,
} = this;
const {
l, t, left, top, width, height,
} = data.getSelectedRect();
const { data, verticalScrollbar, horizontalScrollbar } = this;
const { l, t, left, top, width, height } = data.getSelectedRect();
const tableOffset = this.getTableOffset();
// console.log(',l:', l, ', left:', left, ', tOffset.left:', tableOffset.width);
if (Math.abs(left) + width > tableOffset.width) {
@ -69,10 +60,7 @@ function scrollbarMove() {
function selectorSet(multiple, ri, ci, indexesUpdated = true, moving = false) {
if (ri === -1 && ci === -1) return;
const {
table, selector, toolbar, data,
contextMenu,
} = this;
const { table, selector, toolbar, data, contextMenu } = this;
const cell = data.getCell(ri, ci);
if (multiple) {
selector.setEnd(ri, ci, moving);
@ -82,7 +70,7 @@ function selectorSet(multiple, ri, ci, indexesUpdated = true, moving = false) {
selector.set(ri, ci, indexesUpdated);
this.trigger('cell-selected', cell, ri, ci);
}
contextMenu.setMode((ri === -1 || ci === -1) ? 'row-col' : 'range');
contextMenu.setMode(ri === -1 || ci === -1 ? 'row-col' : 'range');
toolbar.reset();
table.render();
}
@ -90,9 +78,7 @@ function selectorSet(multiple, ri, ci, indexesUpdated = true, moving = false) {
// multiple: boolean
// direction: left | right | up | down | row-first | row-last | col-first | col-last
function selectorMove(multiple, direction) {
const {
selector, data,
} = this;
const { selector, data } = this;
const { rows, cols } = data;
let [ri, ci] = selector.indexes;
const { eri, eci } = selector.range;
@ -132,9 +118,7 @@ function overlayerMousemove(evt) {
if (evt.buttons !== 0) return;
if (evt.target.className === `${cssPrefix}-resizer-hover`) return;
const { offsetX, offsetY } = evt;
const {
rowResizer, colResizer, tableEl, data,
} = this;
const { rowResizer, colResizer, tableEl, data } = this;
const { rows, cols } = data;
if (offsetX > cols.indexWidth && offsetY > rows.height) {
rowResizer.hide();
@ -202,14 +186,14 @@ function overlayerMousescroll(evt) {
// up
const ri = data.scroll.ri + 1;
if (ri < rows.len) {
const rh = loopValue(ri, i => rows.getHeight(i));
const rh = loopValue(ri, (i) => rows.getHeight(i));
verticalScrollbar.move({ top: top + rh - 1 });
}
} else {
// down
const ri = data.scroll.ri - 1;
if (ri >= 0) {
const rh = loopValue(ri, i => rows.getHeight(i));
const rh = loopValue(ri, (i) => rows.getHeight(i));
verticalScrollbar.move({ top: ri === 0 ? 0 : top - rh });
}
}
@ -221,14 +205,14 @@ function overlayerMousescroll(evt) {
// left
const ci = data.scroll.ci + 1;
if (ci < cols.len) {
const cw = loopValue(ci, i => cols.getWidth(i));
const cw = loopValue(ci, (i) => cols.getWidth(i));
horizontalScrollbar.move({ left: left + cw - 1 });
}
} else {
// right
const ci = data.scroll.ci - 1;
if (ci >= 0) {
const cw = loopValue(ci, i => cols.getWidth(i));
const cw = loopValue(ci, (i) => cols.getWidth(i));
horizontalScrollbar.move({ left: ci === 0 ? 0 : left - cw });
}
}
@ -272,9 +256,7 @@ function horizontalScrollbarSet() {
}
function sheetFreeze() {
const {
selector, data, editor,
} = this;
const { selector, data, editor } = this;
const [ri, ci] = data.freeze;
if (ri > 0 || ci > 0) {
const fwidth = data.freezeTotalWidth();
@ -285,15 +267,7 @@ function sheetFreeze() {
}
function sheetReset() {
const {
tableEl,
overlayerEl,
overlayerCEl,
table,
toolbar,
selector,
el,
} = this;
const { tableEl, overlayerEl, overlayerCEl, table, toolbar, selector, el } = this;
const tOffset = this.getTableOffset();
const vRect = this.getRect();
tableEl.attr(vRect);
@ -340,7 +314,7 @@ function paste(what, evt) {
// pastFromSystemClipboard is async operation, need to tell it how to reset sheet and trigger event after it finishes
// pasting content from system clipboard
data.pasteFromSystemClipboard(resetSheet, eventTrigger);
} else if (data.paste(what, msg => xtoast('Tip', msg))) {
} else if (data.paste(what, (msg) => xtoast('Tip', msg))) {
sheetReset.call(this);
} else if (evt) {
const cdata = evt.clipboardData.getData('text/plain');
@ -377,15 +351,11 @@ function toolbarChangePaintformatPaste() {
function overlayerMousedown(evt) {
// console.log(':::::overlayer.mousedown:', evt.detail, evt.button, evt.buttons, evt.shiftKey);
// console.log('evt.target.className:', evt.target.className);
const {
selector, data, table, sortFilter,
} = this;
const { selector, data, table, sortFilter } = this;
const { offsetX, offsetY } = evt;
const isAutofillEl = evt.target.className === `${cssPrefix}-selector-corner`;
const cellRect = data.getCellRectByXY(offsetX, offsetY);
const {
left, top, width, height,
} = cellRect;
const { left, top, width, height } = cellRect;
let { ri, ci } = cellRect;
// sort or filter
const { autoFilter } = data;
@ -409,23 +379,27 @@ function overlayerMousedown(evt) {
}
// mouse move up
mouseMoveUp(window, (e) => {
// console.log('mouseMoveUp::::');
({ ri, ci } = data.getCellRectByXY(e.offsetX, e.offsetY));
if (isAutofillEl) {
selector.showAutofill(ri, ci);
} else if (e.buttons === 1 && !e.shiftKey) {
selectorSet.call(this, true, ri, ci, true, true);
}
}, () => {
if (isAutofillEl && selector.arange && data.settings.mode !== 'read') {
if (data.autofill(selector.arange, 'all', msg => xtoast('Tip', msg))) {
table.render();
mouseMoveUp(
window,
(e) => {
// console.log('mouseMoveUp::::');
({ ri, ci } = data.getCellRectByXY(e.offsetX, e.offsetY));
if (isAutofillEl) {
selector.showAutofill(ri, ci);
} else if (e.buttons === 1 && !e.shiftKey) {
selectorSet.call(this, true, ri, ci, true, true);
}
}
selector.hideAutofill();
toolbarChangePaintformatPaste.call(this);
});
},
() => {
if (isAutofillEl && selector.arange && data.settings.mode !== 'read') {
if (data.autofill(selector.arange, 'all', (msg) => xtoast('Tip', msg))) {
table.render();
}
}
selector.hideAutofill();
toolbarChangePaintformatPaste.call(this);
},
);
}
if (!isAutofillEl && evt.buttons === 1) {
@ -633,7 +607,7 @@ function sheetInitEvents() {
overlayerMousedown.call(this, evt);
}
})
.on('mousewheel.stop', (evt) => {
.on('wheel.stop', (evt) => {
overlayerMousescroll.call(this, evt);
})
.on('mouseout', (evt) => {
@ -739,9 +713,7 @@ function sheetInitEvents() {
bind(window, 'keydown', (evt) => {
if (!this.focusing) return;
const keyCode = evt.keyCode || evt.which;
const {
key, ctrlKey, shiftKey, metaKey,
} = evt;
const { key, ctrlKey, shiftKey, metaKey } = evt;
// console.log('keydown.evt: ', keyCode);
if (ctrlKey || metaKey) {
// const { sIndexes, eIndexes } = selector;
@ -870,10 +842,11 @@ function sheetInitEvents() {
if (key === 'Delete') {
insertDeleteRowColumn.call(this, 'delete-cell-text');
evt.preventDefault();
} else if ((keyCode >= 65 && keyCode <= 90)
|| (keyCode >= 48 && keyCode <= 57)
|| (keyCode >= 96 && keyCode <= 105)
|| evt.key === '='
} else if (
(keyCode >= 65 && keyCode <= 90) ||
(keyCode >= 48 && keyCode <= 57) ||
(keyCode >= 96 && keyCode <= 105) ||
evt.key === '='
) {
dataSetCellText.call(this, evt.key, 'input');
editorSet.call(this);
@ -903,24 +876,15 @@ export default class Sheet {
this.verticalScrollbar = new Scrollbar(true);
this.horizontalScrollbar = new Scrollbar(false);
// editor
this.editor = new Editor(
formulas,
() => this.getTableOffset(),
data.rows.height,
);
this.editor = new Editor(formulas, () => this.getTableOffset(), data.rows.height);
// data validation
this.modalValidation = new ModalValidation();
// contextMenu
this.contextMenu = new ContextMenu(() => this.getRect(), !showContextmenu);
// selector
this.selector = new Selector(data);
this.overlayerCEl = h('div', `${cssPrefix}-overlayer-content`)
.children(
this.editor.el,
this.selector.el,
);
this.overlayerEl = h('div', `${cssPrefix}-overlayer`)
.child(this.overlayerCEl);
this.overlayerCEl = h('div', `${cssPrefix}-overlayer-content`).children(this.editor.el, this.selector.el);
this.overlayerEl = h('div', `${cssPrefix}-overlayer`).child(this.overlayerCEl);
// sortFilter
this.sortFilter = new SortFilter();
// root element

View File

@ -589,7 +589,7 @@ export const demoData = [
'1': {
width: 200,
},
len: 10,
len: 50,
},
validations: [],
autofilter: {},