Skip to content

Commit

Permalink
Add experimental enableFullWidthScroll Grid feature
Browse files Browse the repository at this point in the history
  • Loading branch information
ghsolomon committed Nov 2, 2023
1 parent 49f1c9d commit 54a9f1c
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
* The `XH.getActiveModels` method has been renamed to `XH.getModels` for clarity and consistency.
This is not expected to impact applications.

### ⚙️ Technical
* New flag `GridModel.experimental.enableFullWidthScroll` enables scrollbars to span pinned columns.

## 59.2.0 - 2023-10-16

### 🐞 Bug Fixes
Expand Down
128 changes: 118 additions & 10 deletions cmp/grid/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import composeRefs from '@seznam/compose-react-refs';
import {agGrid, AgGrid} from '@xh/hoist/cmp/ag-grid';
import {getTreeStyleClasses} from '@xh/hoist/cmp/grid';
import {getAgGridMenuItems} from '@xh/hoist/cmp/grid/impl/MenuSupport';
import {div, fragment, frame} from '@xh/hoist/cmp/layout';
import {div, fragment, vframe} from '@xh/hoist/cmp/layout';
import {
hoistCmp,
HoistModel,
Expand Down Expand Up @@ -38,12 +38,13 @@ import type {
GridReadyEvent,
ProcessCellForExportParams
} from '@xh/hoist/kit/ag-grid';
import {computed, observer} from '@xh/hoist/mobx';
import {computed, makeObservable, observer} from '@xh/hoist/mobx';
import {wait} from '@xh/hoist/promise';
import {consumeEvent, isDisplayed, logDebug, logWithDebug} from '@xh/hoist/utils/js';
import {consumeEvent, isDisplayed, logDebug, logWithDebug, observeResize} from '@xh/hoist/utils/js';
import {getLayoutProps} from '@xh/hoist/utils/react';
import classNames from 'classnames';
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge, sumBy} from 'lodash';
import {action, observable} from 'mobx';
import {createRef} from 'react';
import './Grid.scss';
import {GridModel} from './GridModel';
Expand Down Expand Up @@ -95,6 +96,7 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
const {store, treeMode, treeStyle, highlightRowOnClick, colChooserModel, filterModel} =
model,
impl = useLocalModel(GridLocalModel),
{scrollerRef, viewportWidth, visibleColumnWidth, SCROLLBAR_SIZE} = impl,
platformColChooser = XH.isMobileApp ? mobileColChooser : desktopColChooser,
maxDepth = impl.isHierarchical ? store.maxDepth : null;

Expand All @@ -108,13 +110,35 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
);

return fragment(
frame({
vframe({
className,
item: agGrid({
model: model.agGridModel,
...getLayoutProps(props),
...impl.agOptions
}),
items: [
agGrid({
model: model.agGridModel,
...getLayoutProps(props),
...impl.agOptions
}),
div({
className: 'xh-grid__scroll-viewport',
omit: !impl.isFullWidthScrollEnabled || viewportWidth > visibleColumnWidth,
item: div({
className: 'xh-grid__scroll-viewport__container',
style: {
height: SCROLLBAR_SIZE,
width: visibleColumnWidth
}
}),
onScroll: e => {
impl.scrollViewport((e.target as HTMLDivElement).scrollLeft);
},
ref: scrollerRef,
style: {
height: SCROLLBAR_SIZE,
overflowX: 'auto',
overflowY: 'hidden'
}
})
],
testId,
onKeyDown: impl.onKeyDown,
ref: composeRefs(impl.viewRef, ref)
Expand Down Expand Up @@ -272,6 +296,11 @@ class GridLocalModel extends HoistModel {
};
}

// Support for FullWidthScroll
if (this.isFullWidthScrollEnabled) {
ret.suppressHorizontalScroll = true;
}

return ret;
}

Expand Down Expand Up @@ -841,4 +870,83 @@ class GridLocalModel extends HoistModel {
consumeEvent(event);
}
};

//-----------------------------
// Support for FullWidthScroll
//-----------------------------

readonly SCROLLBAR_SIZE = 10;
readonly scrollerRef = createRef<HTMLDivElement>();

@observable viewportWidth: number;
@observable private isVerticalScrollbarVisible = false;

private viewportResizeObserver: ResizeObserver;

@computed
get isFullWidthScrollEnabled(): boolean {
return this.model.experimental['enableFullWidthScroll'];
}

get visibleColumnWidth(): number {
const {model, SCROLLBAR_SIZE} = this;
return (
sumBy(model.columnState, it =>
it.hidden ? 0 : it.width ?? model.getColumn(it.colId).minWidth ?? 0
) + (this.isVerticalScrollbarVisible ? SCROLLBAR_SIZE : 0)
);
}

private get agViewport(): HTMLDivElement {
return this.viewRef.current.querySelector('.ag-center-cols-viewport');
}

private get agVerticalScrollContainer(): HTMLDivElement {
return this.viewRef.current.querySelector('.ag-body-vertical-scroll-container');
}

constructor() {
super();
makeObservable(this);
}

scrollScroller(left: number) {
this.scrollerRef.current.scrollLeft = left;
}

scrollViewport(left: number) {
this.agViewport.scrollLeft = left;
}

override afterLinked() {
if (!this.isFullWidthScrollEnabled) return;
this.addReaction({
track: () => this.model.isReady,
run: isReady => {
if (!isReady) return;
const {agViewport, viewportResizeObserver} = this;
this.viewportWidth = agViewport.clientWidth;
agViewport.addEventListener('scroll', e =>
this.scrollScroller((e.target as HTMLDivElement).scrollLeft)
);
viewportResizeObserver?.disconnect();
this.viewportResizeObserver = observeResize(
rect => this.onViewResized(rect),
agViewport,
{debounce: 100}
);
}
});
}

override destroy() {
super.destroy();
this.viewportResizeObserver?.disconnect();
}

@action
private onViewResized({width}: DOMRect) {
this.viewportWidth = width;
this.isVerticalScrollbarVisible = !!this.agVerticalScrollContainer.clientHeight;
}
}

0 comments on commit 54a9f1c

Please sign in to comment.