diff --git a/packages/common/src/interfaces/slickGrid.interface.ts b/packages/common/src/interfaces/slickGrid.interface.ts index 9054d97b4..1e2bf0b22 100644 --- a/packages/common/src/interfaces/slickGrid.interface.ts +++ b/packages/common/src/interfaces/slickGrid.interface.ts @@ -234,7 +234,7 @@ export interface SlickGrid { getPreHeaderPanelRight(): HTMLElement; /** Get rendered range */ - getRenderedRange(viewportTop: number, viewportLeft: number): { top: number; bottom: number; leftPx: number; rightPx: number; }; + getRenderedRange(viewportTop?: number, viewportLeft?: number): { top: number; bottom: number; leftPx: number; rightPx: number; }; /** Get scrollbar dimensions */ getScrollbarDimensions(): { height: number; width: number; }; diff --git a/packages/common/src/services/__tests__/resizer.service.spec.ts b/packages/common/src/services/__tests__/resizer.service.spec.ts index d037b55f9..227621ef3 100644 --- a/packages/common/src/services/__tests__/resizer.service.spec.ts +++ b/packages/common/src/services/__tests__/resizer.service.spec.ts @@ -31,6 +31,7 @@ const mockDataView = { init: jest.fn(), destroy: jest.fn(), getItemMetadata: jest.fn(), + getItemCount: jest.fn(), getItems: jest.fn(), }; @@ -40,6 +41,7 @@ const gridStub = { getColumnIndex: jest.fn(), getColumns: jest.fn(), getOptions: jest.fn(), + getRenderedRange: jest.fn(), getViewports: jest.fn(), getData: () => mockDataView, getUID: () => GRID_UID, @@ -636,6 +638,38 @@ describe('Resizer Service', () => { }, 15); }); + it('should try to resize grid when its UI is deemed broken by the 2nd condition check of "getRenderedRange"', (done) => { + const resizeSpy = jest.spyOn(service, 'resizeGrid').mockReturnValue(Promise.resolve({ height: 150, width: 350 })); + Object.defineProperty(document.querySelector(`.${GRID_UID}`), 'offsetParent', { writable: true, configurable: true, value: 55 }); + jest.spyOn(mockDataView, 'getItemCount').mockReturnValue(99); + jest.spyOn(gridStub, 'getRenderedRange').mockReturnValue({ top: 0, bottom: 0, leftPx: 0, rightPx: 0 }); + + mockGridOptions.autoFixResizeTimeout = 10; + mockGridOptions.autoFixResizeRequiredGoodCount = 5; + mockGridOptions.autoFixResizeWhenBrokenStyleDetected = true; + service.intervalRetryDelay = 1; + + const divHeaderElm = divContainer.querySelector('.slick-header') as HTMLDivElement; + const divViewportElm = divContainer.querySelector('.slick-viewport') as HTMLDivElement; + jest.spyOn(divContainer, 'getBoundingClientRect').mockReturnValue({ top: 10, left: 20 } as unknown as DOMRect); + jest.spyOn(divHeaderElm, 'getBoundingClientRect').mockReturnValue({ top: 5, left: 25 } as unknown as DOMRect); + jest.spyOn(divViewportElm, 'getBoundingClientRect').mockReturnValue({ top: 98, left: 25 } as unknown as DOMRect); + divHeaderElm.style.top = '5px'; + divHeaderElm.style.left = '25px'; + divContainer.style.top = '10px'; + divContainer.style.left = '20px'; + service.init(gridStub, divContainer); + + setTimeout(() => { + expect(divContainer.outerHTML).toBeTruthy(); + expect(resizeSpy).toHaveBeenCalled(); + expect(resizeSpy).toHaveBeenNthCalledWith(2); + expect(resizeSpy).toHaveBeenNthCalledWith(3); + done(); + service.requestStopOfAutoFixResizeGrid(); + }, 20); + }); + it('should try to resize grid when its UI is deemed broken but expect an error shown in the console when "resizeGrid" throws an error', (done) => { const consoleSpy = jest.spyOn(global.console, 'log').mockReturnValue(); const promise = new Promise((_resolve, reject) => setTimeout(() => reject('some error'), 0)); diff --git a/packages/common/src/services/resizer.service.ts b/packages/common/src/services/resizer.service.ts index fdfcce54c..32bb79afe 100644 --- a/packages/common/src/services/resizer.service.ts +++ b/packages/common/src/services/resizer.service.ts @@ -586,12 +586,15 @@ export class ResizerService { const autoFixResizeTimeout = this.gridOptions?.autoFixResizeTimeout ?? (5 * 60 * 60); // interval is 200ms, so 4x is 1sec, so (4 * 60 * 60 = 60min) const autoFixResizeRequiredGoodCount = this.gridOptions?.autoFixResizeRequiredGoodCount ?? 5; - const headerElm = document.querySelector(`.${this.gridUid} .slick-header`); - const viewportElm = document.querySelector(`.${this.gridUid} .slick-viewport`); + const headerElm = this._gridParentContainerElm.querySelector(`.${this.gridUid} .slick-header`); + const viewportElm = this._gridParentContainerElm.querySelector(`.${this.gridUid} .slick-viewport`); let intervalExecutionCounter = 0; let resizeGoodCount = 0; if (headerElm && viewportElm && this.gridOptions.autoFixResizeWhenBrokenStyleDetected) { + const dataLn = this.dataView.getItemCount(); + const columns = this._grid.getColumns() || []; + this._intervalId = setInterval(async () => { const headerTitleRowHeight = 44; // this one is set by SASS/CSS so let's hard code it const headerPos = getHtmlElementOffset(headerElm); @@ -613,6 +616,13 @@ export class ResizerService { const containerElmOffset = getHtmlElementOffset(this._gridParentContainerElm); let isResizeRequired = (headerPos?.top === 0 || ((headerOffsetTop - viewportOffsetTop) > 2) || (containerElmOffset?.left === 0 && containerElmOffset?.top === 0)) ? true : false; + // another condition for a required resize is when the grid is hidden (not in current tab) then its "rightPx" rendered range will be 0px + // if that's the case then we know the grid is still hidden and we need to resize it whenever it becomes visible (when its "rightPx" becomes greater than 0 then it's visible) + const renderedRangeRightPx = this._grid.getRenderedRange?.()?.rightPx ?? 0; + if (!isResizeRequired && dataLn > 0 && renderedRangeRightPx === 0 && columns.length > 1) { + isResizeRequired = true; + } + // user could choose to manually stop the looped of auto resize fix if (this._isStopResizeIntervalRequested) { isResizeRequired = false; @@ -620,19 +630,24 @@ export class ResizerService { } // visible grid (shown to the user and not hidden in another Tab will have an offsetParent defined) - const isGridVisible = !!(document.querySelector(`.${this.gridUid}`)?.offsetParent ?? false); + let isGridVisible = !!(document.querySelector(`.${this.gridUid}`)?.offsetParent ?? false); - if (isGridVisible && (isResizeRequired || resizeGoodCount < autoFixResizeRequiredGoodCount) && (containerElmOffset?.left > 0 || containerElmOffset?.top > 0)) { + if (isGridVisible && (isResizeRequired || containerElmOffset?.left === 0 || containerElmOffset?.top === 0)) { await this.resizeGrid(); - // make sure the grid is still visible after doing the resize and if so we consider it a good resize (it might not be visible if user quickly switch to another Tab) - const isGridStillVisible = !!(document.querySelector(`.${this.gridUid}`)?.offsetParent ?? false); - if (isGridStillVisible) { + // make sure the grid is still visible after doing the resize + isGridVisible = !!(document.querySelector(`.${this.gridUid}`)?.offsetParent ?? false); + if (isGridVisible) { isResizeRequired = false; - resizeGoodCount++; } } + // make sure the grid is still visible after optionally doing a resize + // if it visible then we can consider it a good resize (it might not be visible if user quickly switch to another Tab) + if (isGridVisible) { + resizeGoodCount++; + } + if (isGridVisible && !isResizeRequired && (resizeGoodCount >= autoFixResizeRequiredGoodCount || intervalExecutionCounter++ >= autoFixResizeTimeout)) { clearInterval(this._intervalId); // stop the interval if we don't need resize or if we passed let say 70min } diff --git a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip index 1cbe13465..f2cba3eb5 100644 Binary files a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip and b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip differ diff --git a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts index ddddd53d6..ac4b551fe 100644 --- a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts +++ b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts @@ -5,7 +5,7 @@ export const SalesforceGlobalGridOptions = { autoEdit: true, // true single click (false for double-click) autoCommitEdit: true, autoFixResizeTimeout: 5 * 60 * 60, // interval is 200ms, so 4x is 1sec, so (5 * 60 * 60 = 60min) - autoFixResizeRequiredGoodCount: 5, + autoFixResizeRequiredGoodCount: 5 * 60 * 60, // make it the same as the interval timeout, this is equivalent to say don't stop until the timeout is over autoFixResizeWhenBrokenStyleDetected: true, cellValueCouldBeUndefined: true, eventNamingStyle: EventNamingStyle.lowerCaseWithoutOnPrefix,