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

View File

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

View File

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

View File

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