UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

598 lines (597 loc) 25 kB
/*! * Built by Revolist OU ❤️ */ import { h, Host, } from "@stencil/core"; import GridResizeService from "../revoGrid/viewport.resize.service"; import LocalScrollService from "../../services/local.scroll.service"; import { LocalScrollTimer } from "../../services/local.scroll.timer"; import { CONTENT_SLOT, FOOTER_SLOT, HEADER_SLOT, } from "../revoGrid/viewport.helpers"; /** * Viewport scroll component for RevoGrid * @slot - content * @slot header - header * @slot footer - footer */ export class RevogrViewportScroll { constructor() { this.rowHeader = undefined; this.contentWidth = 0; this.contentHeight = 0; this.colType = undefined; } async setScroll(e) { var _a; this.localScrollTimer.latestScrollUpdate(e.dimension); (_a = this.localScrollService) === null || _a === void 0 ? void 0 : _a.setScroll(e); } /** * update on delta in case we don't know existing position or external change * @param e */ async changeScroll(e, silent = false) { var _a, _b; if (silent) { if (e.coordinate && this.verticalScroll) { switch (e.dimension) { // for mobile devices to skip negative scroll loop. only on vertical scroll case 'rgRow': this.verticalScroll.style.transform = `translateY(${-1 * e.coordinate}px)`; break; } } return; } if (e.delta) { switch (e.dimension) { case 'rgCol': e.coordinate = this.horizontalScroll.scrollLeft + e.delta; break; case 'rgRow': e.coordinate = ((_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0) + e.delta; break; } this.setScroll(e); } return e; } /** * Dispatch this event to trigger vertical mouse wheel from plugins */ mousewheelVertical({ detail: e, }) { this.verticalMouseWheel(e); } /** * Dispatch this event to trigger horizontal mouse wheel from plugins */ mousewheelHorizontal({ detail: e, }) { this.horizontalMouseWheel(e); } /** * Allows to use outside listener */ scrollApply({ detail: { type, coordinate }, }) { this.applyOnScroll(type, coordinate, true); } connectedCallback() { /** * Bind scroll functions for farther usage */ // allow mousewheel for all devices including mobile this.verticalMouseWheel = this.onVerticalMouseWheel.bind(this, 'rgRow', 'deltaY'); this.horizontalMouseWheel = this.onHorizontalMouseWheel.bind(this, 'rgCol', 'deltaX'); this.localScrollTimer = new LocalScrollTimer('ontouchstart' in document.documentElement ? 0 : 10); /** * Create local scroll service */ this.localScrollService = new LocalScrollService({ // to improve safari smoothnes on scroll // skipAnimationFrame: isSafariDesktop(), runScroll: e => this.scrollViewport.emit(e), applyScroll: e => { this.localScrollTimer.setCoordinate(e); switch (e.dimension) { case 'rgCol': // this will trigger on scroll event this.horizontalScroll.scrollLeft = e.coordinate; break; case 'rgRow': if (this.verticalScroll) { // this will trigger on scroll event this.verticalScroll.scrollTop = e.coordinate; // for mobile devices to skip negative scroll loop. only on vertical scroll if (this.verticalScroll.style.transform) { this.verticalScroll.style.transform = ''; } } break; } }, }); } componentDidLoad() { // track viewport resize this.resizeService = new GridResizeService(this.horizontalScroll, (entry) => { var _a, _b, _c, _d, _e, _f, _g, _h; const els = {}; let calculatedHeight = entry.height || 0; if (calculatedHeight) { calculatedHeight -= ((_b = (_a = this.header) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0) + ((_d = (_c = this.footer) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0); } els.rgRow = { size: calculatedHeight, contentSize: this.contentHeight, scroll: (_f = (_e = this.verticalScroll) === null || _e === void 0 ? void 0 : _e.scrollTop) !== null && _f !== void 0 ? _f : 0, noScroll: false, }; const calculatedWidth = entry.width || 0; els.rgCol = { size: calculatedWidth, contentSize: this.contentWidth, scroll: this.horizontalScroll.scrollLeft, noScroll: this.colType !== 'rgCol', }; // Process changes in order: width first, then height const dimensions = ['rgCol', 'rgRow']; for (const dimension of dimensions) { const item = els[dimension]; if (!item) continue; this.resizeViewport.emit({ dimension, size: item.size, rowHeader: this.rowHeader, }); if (item.noScroll) { continue; } (_g = this.localScrollService) === null || _g === void 0 ? void 0 : _g.scroll((_h = item.scroll) !== null && _h !== void 0 ? _h : 0, dimension, true); // track scroll visibility on outer element change this.setScrollVisibility(dimension, item.size, item.contentSize); } }); } /** * Check if scroll present or not per type * Trigger this method on inner content size change or on outer element size change * If inner content bigger then outer size then scroll is present and mousewheel binding required * @param type - dimension type 'rgRow/y' or 'rgCol/x' * @param size - outer content size * @param innerContentSize - inner content size */ setScrollVisibility(type, size, innerContentSize) { // test if scroll present const hasScroll = size < innerContentSize; let el; // event reference for binding switch (type) { case 'rgCol': el = this.horizontalScroll; break; case 'rgRow': el = this.verticalScroll; break; } // based on scroll visibility assign or remove class and event if (hasScroll) { el === null || el === void 0 ? void 0 : el.classList.add(`scroll-${type}`); } else { el === null || el === void 0 ? void 0 : el.classList.remove(`scroll-${type}`); } this.scrollchange.emit({ type, hasScroll }); } disconnectedCallback() { var _a; (_a = this.resizeService) === null || _a === void 0 ? void 0 : _a.destroy(); } async componentDidRender() { var _a, _b, _c, _d; this.localScrollService.setParams({ contentSize: this.contentHeight, clientSize: (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.clientHeight) !== null && _b !== void 0 ? _b : 0, virtualSize: 0, }, 'rgRow'); this.localScrollService.setParams({ contentSize: this.contentWidth, clientSize: this.horizontalScroll.clientWidth, virtualSize: 0, }, 'rgCol'); this.setScrollVisibility('rgRow', (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0, this.contentHeight); this.setScrollVisibility('rgCol', this.horizontalScroll.clientWidth, this.contentWidth); } render() { return (h(Host, { key: '09545ebf834794ac525565fb39b4c097e6dd6aa6', onWheel: this.horizontalMouseWheel, onScroll: (e) => this.applyScroll('rgCol', e) }, h("div", { key: 'f407a5b9ed9ca3ced107efb3a80a4eb0db92cefc', class: "inner-content-table", style: { width: `${this.contentWidth}px` } }, h("div", { key: 'cd01ed9ae19a4bbf2616e3a69435674ef624ca1f', class: "header-wrapper", ref: e => (this.header = e) }, h("slot", { key: '1673f06c5d5fafed2dec3a3fc5468cdf31490988', name: HEADER_SLOT })), h("div", { key: '9330984fa03ef2d323a8921ddb9b3c0e19596099', class: "vertical-inner", ref: el => (this.verticalScroll = el), onWheel: this.verticalMouseWheel, onScroll: (e) => this.applyScroll('rgRow', e) }, h("div", { key: 'cbb7fbb938e1d38e3572a3f6b30143cbf76fff7f', class: "content-wrapper", style: { height: `${this.contentHeight}px` } }, h("slot", { key: '592d4a7cca591cfb37b4828388fb8d63b3c52054', name: CONTENT_SLOT }))), h("div", { key: '7a69a5f8cce686efdf2a90a0cca386262c2db3bb', class: "footer-wrapper", ref: e => (this.footer = e) }, h("slot", { key: 'fceccd7653e0171ef52d5974fa1e465412bcfc08', name: FOOTER_SLOT }))))); } /** * Extra layer for scroll event monitoring, where MouseWheel event is not passing * We need to trigger scroll event in case there is no mousewheel event */ async applyScroll(type, e) { if (!(e.target instanceof HTMLElement)) { return; } let scroll = 0; switch (type) { case 'rgCol': scroll = e.target.scrollLeft; break; case 'rgRow': scroll = e.target.scrollTop; break; } // for mobile devices to skip negative scroll loop if (scroll < 0) { this.silentScroll.emit({ dimension: type, coordinate: scroll }); return; } this.applyOnScroll(type, scroll); } /** * Applies change on scroll event only if mousewheel event happened some time ago */ applyOnScroll(type, coordinate, outside = false) { const lastScrollUpdate = () => { var _a; (_a = this.localScrollService) === null || _a === void 0 ? void 0 : _a.scroll(coordinate, type, undefined, undefined, outside); }; // apply after throttling if (this.localScrollTimer.isReady(type, coordinate)) { lastScrollUpdate(); } else { this.localScrollTimer.throttleLastScrollUpdate(type, coordinate, () => lastScrollUpdate()); } } /** * On vertical mousewheel event * @param type * @param delta * @param e */ onVerticalMouseWheel(type, delta, e) { var _a, _b, _c, _d, _e, _f, _g, _h; const scrollTop = (_b = (_a = this.verticalScroll) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0; const clientHeight = (_d = (_c = this.verticalScroll) === null || _c === void 0 ? void 0 : _c.clientHeight) !== null && _d !== void 0 ? _d : 0; const scrollHeight = (_f = (_e = this.verticalScroll) === null || _e === void 0 ? void 0 : _e.scrollHeight) !== null && _f !== void 0 ? _f : 0; // Detect if the user has reached the bottom const atBottom = scrollTop + clientHeight >= scrollHeight && e.deltaY > 0; const atTop = scrollTop === 0 && e.deltaY < 0; if (!atBottom && !atTop) { (_g = e.preventDefault) === null || _g === void 0 ? void 0 : _g.call(e); } const pos = scrollTop + e[delta]; (_h = this.localScrollService) === null || _h === void 0 ? void 0 : _h.scroll(pos, type, undefined, e[delta]); this.localScrollTimer.latestScrollUpdate(type); } /** * On horizontal mousewheel event * @param type * @param delta * @param e */ onHorizontalMouseWheel(type, delta, e) { var _a, _b; if (!e.deltaX) { return; } const { scrollLeft, scrollWidth, clientWidth } = this.horizontalScroll; // Detect if the user has reached the right end const atRight = scrollLeft + clientWidth >= scrollWidth && e.deltaX > 0; // Detect if the user has reached the left end const atLeft = scrollLeft === 0 && e.deltaX < 0; if (!atRight && !atLeft) { (_a = e.preventDefault) === null || _a === void 0 ? void 0 : _a.call(e); } const pos = scrollLeft + e[delta]; (_b = this.localScrollService) === null || _b === void 0 ? void 0 : _b.scroll(pos, type, undefined, e[delta]); this.localScrollTimer.latestScrollUpdate(type); } static get is() { return "revogr-viewport-scroll"; } static get originalStyleUrls() { return { "$": ["revogr-viewport-scroll-style.scss"] }; } static get styleUrls() { return { "$": ["revogr-viewport-scroll-style.css"] }; } static get properties() { return { "rowHeader": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Enable row header" }, "getter": false, "setter": false, "attribute": "row-header", "reflect": false }, "contentWidth": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Width of inner content" }, "getter": false, "setter": false, "attribute": "content-width", "reflect": false, "defaultValue": "0" }, "contentHeight": { "type": "number", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Height of inner content" }, "getter": false, "setter": false, "attribute": "content-height", "reflect": false, "defaultValue": "0" }, "colType": { "type": "string", "mutable": false, "complexType": { "original": "DimensionCols | 'rowHeaders'", "resolved": "\"colPinEnd\" | \"colPinStart\" | \"rgCol\" | \"rowHeaders\"", "references": { "DimensionCols": { "location": "import", "path": "@type", "id": "src/types/index.ts::DimensionCols" } } }, "required": true, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "col-type", "reflect": false } }; } static get events() { return [{ "method": "scrollViewport", "name": "scrollviewport", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Before scroll event" }, "complexType": { "original": "ViewPortScrollEvent", "resolved": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }", "references": { "ViewPortScrollEvent": { "location": "import", "path": "@type", "id": "src/types/index.ts::ViewPortScrollEvent" } } } }, { "method": "resizeViewport", "name": "resizeviewport", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Viewport resize" }, "complexType": { "original": "ViewPortResizeEvent", "resolved": "{ dimension: DimensionType; size: number; rowHeader?: boolean | undefined; }", "references": { "ViewPortResizeEvent": { "location": "import", "path": "@type", "id": "src/types/index.ts::ViewPortResizeEvent" } } } }, { "method": "scrollchange", "name": "scrollchange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Triggered on scroll change, can be used to get information about scroll visibility" }, "complexType": { "original": "{\n type: DimensionType;\n hasScroll: boolean;\n }", "resolved": "{ type: DimensionType; hasScroll: boolean; }", "references": { "DimensionType": { "location": "import", "path": "@type", "id": "src/types/index.ts::DimensionType" } } } }, { "method": "silentScroll", "name": "scrollviewportsilent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Silently scroll to coordinate\nMade to align negative coordinates for mobile devices" }, "complexType": { "original": "ViewPortScrollEvent", "resolved": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }", "references": { "ViewPortScrollEvent": { "location": "import", "path": "@type", "id": "src/types/index.ts::ViewPortScrollEvent" } } } }]; } static get methods() { return { "setScroll": { "complexType": { "signature": "(e: ViewPortScrollEvent) => Promise<void>", "parameters": [{ "name": "e", "type": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "ViewPortScrollEvent": { "location": "import", "path": "@type", "id": "src/types/index.ts::ViewPortScrollEvent" } }, "return": "Promise<void>" }, "docs": { "text": "", "tags": [] } }, "changeScroll": { "complexType": { "signature": "(e: ViewPortScrollEvent, silent?: boolean) => Promise<ViewPortScrollEvent | undefined>", "parameters": [{ "name": "e", "type": "{ dimension: DimensionType; coordinate: number; delta?: number | undefined; outside?: boolean | undefined; }", "docs": "" }, { "name": "silent", "type": "boolean", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "ViewPortScrollEvent": { "location": "import", "path": "@type", "id": "src/types/index.ts::ViewPortScrollEvent" } }, "return": "Promise<ViewPortScrollEvent | undefined>" }, "docs": { "text": "update on delta in case we don't know existing position or external change", "tags": [{ "name": "param", "text": "e" }] } }, "applyScroll": { "complexType": { "signature": "(type: DimensionType, e: UIEvent) => Promise<void>", "parameters": [{ "name": "type", "type": "\"rgCol\" | \"rgRow\"", "docs": "" }, { "name": "e", "type": "UIEvent", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "DimensionType": { "location": "import", "path": "@type", "id": "src/types/index.ts::DimensionType" }, "UIEvent": { "location": "global", "id": "global::UIEvent" } }, "return": "Promise<void>" }, "docs": { "text": "Extra layer for scroll event monitoring, where MouseWheel event is not passing\nWe need to trigger scroll event in case there is no mousewheel event", "tags": [] } } }; } static get elementRef() { return "horizontalScroll"; } static get listeners() { return [{ "name": "mousewheel-vertical", "method": "mousewheelVertical", "target": undefined, "capture": false, "passive": false }, { "name": "mousewheel-horizontal", "method": "mousewheelHorizontal", "target": undefined, "capture": false, "passive": false }, { "name": "scroll-coordinate", "method": "scrollApply", "target": undefined, "capture": false, "passive": false }]; } } //# sourceMappingURL=revogr-viewport-scroll.js.map