handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
386 lines (367 loc) • 14.9 kB
JavaScript
"use strict";
exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
var _element = require("../../../../helpers/dom/element");
var _top = _interopRequireDefault(require("./../table/top"));
var _base = require("./_base");
var _selection = require("../selection");
var _constants = require("./constants");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
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); }
/**
* @class TopOverlay
*/
class TopOverlay extends _base.Overlay {
/**
* @param {Walkontable} wotInstance The Walkontable instance. @TODO refactoring: check if can be deleted.
* @param {FacadeGetter} facadeGetter Function which return proper facade.
* @param {Settings} wtSettings The Walkontable settings.
* @param {DomBindings} domBindings Dom elements bound to the current instance.
*/
constructor(wotInstance, facadeGetter, wtSettings, domBindings) {
super(wotInstance, facadeGetter, _constants.CLONE_TOP, wtSettings, domBindings);
/**
* Cached value which holds the previous value of the `fixedRowsTop` option.
* It is used as a comparison value that can be used to detect changes in this value.
*
* @type {number}
*/
_defineProperty(this, "cachedFixedRowsTop", -1);
this.cachedFixedRowsTop = this.wtSettings.getSetting('fixedRowsTop');
}
/**
* Factory method to create a subclass of `Table` that is relevant to this overlay.
*
* @see Table#constructor
* @param {...*} args Parameters that will be forwarded to the `Table` constructor.
* @returns {TopOverlayTable}
*/
createTable() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return new _top.default(...args);
}
/**
* Checks if overlay should be fully rendered.
*
* @returns {boolean}
*/
shouldBeRendered() {
return this.wtSettings.getSetting('shouldRenderTopOverlay');
}
/**
* Updates the top overlay position.
*
* @returns {boolean}
*/
resetFixedPosition() {
if (!this.needFullRender || !this.shouldBeRendered() || !this.wot.wtTable.holder.parentNode) {
// removed from DOM
return false;
}
const overlayRoot = this.clone.wtTable.holder.parentNode;
const {
rootWindow
} = this.domBindings;
const preventOverflow = this.wtSettings.getSetting('preventOverflow');
let overlayPosition = 0;
let skipInnerBorderAdjusting = false;
if (this.trimmingContainer === rootWindow && (!preventOverflow || preventOverflow !== 'vertical')) {
const {
wtTable
} = this.wot;
const hiderRect = wtTable.hider.getBoundingClientRect();
const bottom = Math.ceil(hiderRect.bottom);
const rootHeight = overlayRoot.offsetHeight;
// This checks if the overlay is going to an infinite loop caused by added (or removed)
// `innerBorderTop` class name. Toggling the class name shifts the viewport by 1px and
// triggers the `scroll` event. It causes the table to render. The new render cycle takes into,
// account the shift and toggles the class name again. This causes the next loops. This
// happens only on Chrome (#7256).
//
// When we detect that the table bottom position is the same as the overlay bottom,
// do not toggle the class name.
//
// This workaround will be able to be cleared after merging the SVG borders, which introduces
// frozen lines (no more `innerBorderTop` workaround).
skipInnerBorderAdjusting = bottom === rootHeight;
overlayPosition = this.getOverlayOffset();
(0, _element.setOverlayPosition)(overlayRoot, '0px', `${overlayPosition}px`);
} else {
overlayPosition = this.getScrollPosition();
(0, _element.resetCssTransform)(overlayRoot);
}
const positionChanged = this.adjustHeaderBordersPosition(overlayPosition, skipInnerBorderAdjusting);
this.adjustElementsSize();
return positionChanged;
}
/**
* Sets the main overlay's vertical scroll position.
*
* @param {number} pos The scroll position.
* @returns {boolean}
*/
setScrollPosition(pos) {
const {
rootWindow
} = this.domBindings;
const scrollableElement = this.mainTableScrollableElement;
let result = false;
if (scrollableElement === rootWindow && pos !== rootWindow.scrollY) {
const oldScrollX = rootWindow.scrollY;
rootWindow.scrollTo((0, _element.getWindowScrollLeft)(rootWindow), pos);
result = oldScrollX !== rootWindow.scrollY;
} else if (pos !== scrollableElement.scrollTop) {
const oldScrollLeft = scrollableElement.scrollTop;
scrollableElement.scrollTop = pos;
result = oldScrollLeft !== scrollableElement.scrollTop;
}
return result;
}
/**
* Triggers onScroll hook callback.
*/
onScroll() {
this.wtSettings.getSetting('onScrollHorizontally');
}
/**
* Calculates total sum cells height.
*
* @param {number} from Row index which calculates started from.
* @param {number} to Row index where calculation is finished.
* @returns {number} Height sum.
*/
sumCellSizes(from, to) {
const defaultRowHeight = this.wtSettings.getSetting('stylesHandler').getDefaultRowHeight();
let row = from;
let sum = 0;
while (row < to) {
const height = this.wot.wtTable.getRowHeight(row);
sum += height === undefined ? defaultRowHeight : height;
row += 1;
}
return sum;
}
/**
* Adjust overlay root element, children and master table element sizes (width, height).
*/
adjustElementsSize() {
this.updateTrimmingContainer();
if (this.needFullRender) {
this.adjustRootElementSize();
this.adjustRootChildrenSize();
}
}
/**
* Adjust overlay root element size (width and height).
*/
adjustRootElementSize() {
const {
wtTable,
wtViewport
} = this.wot;
const {
rootDocument,
rootWindow
} = this.domBindings;
const overlayRoot = this.clone.wtTable.holder.parentNode;
const overlayRootStyle = overlayRoot.style;
const preventOverflow = this.wtSettings.getSetting('preventOverflow');
if (this.trimmingContainer !== rootWindow || preventOverflow === 'horizontal') {
let width = wtViewport.getWorkspaceWidth();
if (wtViewport.hasVerticalScroll()) {
width -= (0, _element.getScrollbarWidth)(rootDocument);
}
width = Math.min(width, wtTable.wtRootElement.scrollWidth);
overlayRootStyle.width = `${width}px`;
} else {
overlayRootStyle.width = '';
}
this.clone.wtTable.holder.style.width = overlayRootStyle.width;
let tableHeight = (0, _element.outerHeight)(this.clone.wtTable.TABLE);
if (!wtTable.hasDefinedSize()) {
tableHeight = 0;
}
overlayRootStyle.height = `${tableHeight}px`;
}
/**
* Adjust overlay root childs size.
*/
adjustRootChildrenSize() {
const {
holder
} = this.clone.wtTable;
const cornerStyle = (0, _selection.getCornerStyle)(this.wot);
const selectionCornerOffset = this.wot.selectionManager.getFocusSelection() ? parseInt(cornerStyle.height, 10) / 2 : 0;
this.clone.wtTable.hider.style.width = this.hider.style.width;
holder.style.width = holder.parentNode.style.width;
// Add selection corner protruding part to the holder total height to make sure that
// borders' corner won't be cut after vertical scroll (#6937).
holder.style.height = `${parseInt(holder.parentNode.style.height, 10) + selectionCornerOffset}px`;
}
/**
* Adjust the overlay dimensions and position.
*/
applyToDOM() {
const total = this.wtSettings.getSetting('totalRows');
if (typeof this.wot.wtViewport.rowsRenderCalculator.startPosition === 'number') {
this.spreader.style.top = `${this.wot.wtViewport.rowsRenderCalculator.startPosition}px`;
} else if (total === 0) {
// can happen if there are 0 rows
this.spreader.style.top = '0';
} else {
throw new Error('Incorrect value of the rowsRenderCalculator');
}
this.spreader.style.bottom = '';
if (this.needFullRender) {
this.syncOverlayOffset();
}
}
/**
* Synchronize calculated left position to an element.
*/
syncOverlayOffset() {
const styleProperty = this.isRtl() ? 'right' : 'left';
const {
spreader
} = this.clone.wtTable;
if (typeof this.wot.wtViewport.columnsRenderCalculator.startPosition === 'number') {
spreader.style[styleProperty] = `${this.wot.wtViewport.columnsRenderCalculator.startPosition}px`;
} else {
spreader.style[styleProperty] = '';
}
}
/**
* Scrolls vertically to a row.
*
* @param {number} sourceRow Row index which you want to scroll to.
* @param {boolean} [bottomEdge] If `true`, scrolls according to the bottom edge (top edge is by default).
* @returns {boolean}
*/
scrollTo(sourceRow, bottomEdge) {
const {
wot,
wtSettings
} = this;
const sourceInstance = wot.cloneSource ? wot.cloneSource : wot;
const mainHolder = sourceInstance.wtTable.holder;
const columnHeaders = wtSettings.getSetting('columnHeaders');
const fixedRowsTop = wtSettings.getSetting('fixedRowsTop');
const columnHeaderBorderCompensation = fixedRowsTop === 0 && columnHeaders.length > 0 && !(0, _element.hasClass)(mainHolder.parentNode, 'innerBorderTop') ? 1 : 0;
let newY = this.getTableParentOffset();
let scrollbarCompensation = 0;
if (bottomEdge) {
const rowHeight = this.wot.wtTable.getRowHeight(sourceRow);
const viewportHeight = this.wot.wtViewport.getViewportHeight();
if (rowHeight > viewportHeight) {
bottomEdge = false;
}
}
if (bottomEdge && mainHolder.offsetHeight !== mainHolder.clientHeight) {
scrollbarCompensation = (0, _element.getScrollbarWidth)(this.domBindings.rootDocument);
}
if (bottomEdge) {
const fixedRowsBottom = wtSettings.getSetting('fixedRowsBottom');
const totalRows = wtSettings.getSetting('totalRows');
newY += this.sumCellSizes(0, sourceRow + 1);
newY -= wot.wtViewport.getViewportHeight() - this.sumCellSizes(totalRows - fixedRowsBottom, totalRows);
// Fix 1 pixel offset when cell is selected
newY += 1;
// Compensate for the bottom header border if scrolled from the absolute top.
newY += columnHeaderBorderCompensation;
} else {
newY += this.sumCellSizes(wtSettings.getSetting('fixedRowsTop'), sourceRow);
}
newY += scrollbarCompensation;
// If the table is scrolled all the way up when starting the scroll and going to be scrolled to the bottom,
// we need to compensate for the potential header bottom border height.
if ((0, _element.getMaximumScrollTop)(this.mainTableScrollableElement) === newY - columnHeaderBorderCompensation && columnHeaderBorderCompensation > 0) {
this.wot.wtOverlays.expandHiderVerticallyBy(columnHeaderBorderCompensation);
}
return this.setScrollPosition(newY);
}
/**
* Gets table parent top position.
*
* @returns {number}
*/
getTableParentOffset() {
if (this.mainTableScrollableElement === this.domBindings.rootWindow) {
return this.wot.wtTable.holderOffset.top;
}
return 0;
}
/**
* Gets the main overlay's vertical scroll position.
*
* @returns {number} Main table's vertical scroll position.
*/
getScrollPosition() {
return (0, _element.getScrollTop)(this.mainTableScrollableElement, this.domBindings.rootWindow);
}
/**
* Gets the main overlay's vertical overlay offset.
*
* @returns {number} Main table's vertical overlay offset.
*/
getOverlayOffset() {
const {
rootWindow
} = this.domBindings;
const preventOverflow = this.wtSettings.getSetting('preventOverflow');
let overlayOffset = 0;
if (this.trimmingContainer === rootWindow && (!preventOverflow || preventOverflow !== 'vertical')) {
const rootHeight = this.wot.wtTable.getTotalHeight();
const overlayRootHeight = this.clone.wtTable.getTotalHeight();
const maxOffset = rootHeight - overlayRootHeight;
overlayOffset = Math.max(this.getScrollPosition() - this.getTableParentOffset(), 0);
if (overlayOffset > maxOffset) {
overlayOffset = 0;
}
}
return overlayOffset;
}
/**
* Adds css classes to hide the header border's header (cell-selection border hiding issue).
*
* @param {number} position Header Y position if trimming container is window or scroll top if not.
* @param {boolean} [skipInnerBorderAdjusting=false] If `true` the inner border adjusting will be skipped.
* @returns {boolean}
*/
adjustHeaderBordersPosition(position) {
let skipInnerBorderAdjusting = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
const {
wtSettings
} = this;
const masterParent = this.wot.wtTable.holder.parentNode;
const totalColumns = wtSettings.getSetting('totalColumns');
const preventHorizontalOverflow = wtSettings.getSetting('preventOverflow') === 'horizontal';
if (totalColumns) {
(0, _element.removeClass)(masterParent, 'emptyColumns');
} else {
(0, _element.addClass)(masterParent, 'emptyColumns');
}
let positionChanged = false;
if (!skipInnerBorderAdjusting && !preventHorizontalOverflow) {
const fixedRowsTop = wtSettings.getSetting('fixedRowsTop');
const areFixedRowsTopChanged = this.cachedFixedRowsTop !== fixedRowsTop;
const columnHeaders = wtSettings.getSetting('columnHeaders');
if ((areFixedRowsTopChanged || fixedRowsTop === 0) && columnHeaders.length > 0) {
const previousState = (0, _element.hasClass)(masterParent, 'innerBorderTop');
this.cachedFixedRowsTop = wtSettings.getSetting('fixedRowsTop');
if (position || wtSettings.getSetting('totalRows') === 0) {
(0, _element.addClass)(masterParent, 'innerBorderTop');
positionChanged = !previousState;
} else {
(0, _element.removeClass)(masterParent, 'innerBorderTop');
positionChanged = previousState;
}
}
}
return positionChanged;
}
}
exports.TopOverlay = TopOverlay;