Skip to content

Commit

Permalink
Split GridScrollbar into separate component
Browse files Browse the repository at this point in the history
  • Loading branch information
ghsolomon committed Nov 6, 2023
1 parent 54a9f1c commit 150882b
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 110 deletions.
125 changes: 15 additions & 110 deletions cmp/grid/Grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import composeRefs from '@seznam/compose-react-refs';
import {agGrid, AgGrid} from '@xh/hoist/cmp/ag-grid';
import {getTreeStyleClasses} from '@xh/hoist/cmp/grid';
import {gridScrollbar} from '@xh/hoist/cmp/grid/impl/GridScrollbar';
import {getAgGridMenuItems} from '@xh/hoist/cmp/grid/impl/MenuSupport';
import {div, fragment, vframe} from '@xh/hoist/cmp/layout';
import {div, frame, vframe} from '@xh/hoist/cmp/layout';
import {
hoistCmp,
HoistModel,
Expand Down Expand Up @@ -38,14 +39,12 @@ import type {
GridReadyEvent,
ProcessCellForExportParams
} from '@xh/hoist/kit/ag-grid';
import {computed, makeObservable, observer} from '@xh/hoist/mobx';
import {computed, observer} from '@xh/hoist/mobx';
import {wait} from '@xh/hoist/promise';
import {consumeEvent, isDisplayed, logDebug, logWithDebug, observeResize} from '@xh/hoist/utils/js';
import {getLayoutProps} from '@xh/hoist/utils/react';
import {consumeEvent, isDisplayed, logDebug, logWithDebug} from '@xh/hoist/utils/js';
import {createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
import classNames from 'classnames';
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge, sumBy} from 'lodash';
import {action, observable} from 'mobx';
import {createRef} from 'react';
import {debounce, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
import './Grid.scss';
import {GridModel} from './GridModel';
import {columnGroupHeader} from './impl/ColumnGroupHeader';
Expand Down Expand Up @@ -96,7 +95,6 @@ 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 @@ -109,34 +107,20 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
highlightRowOnClick ? 'xh-grid--highlight-row-on-click' : null
);

return fragment(
vframe({
const container = model.experimental['enableFullWidthScroll'] ? vframe : frame;

return container(
frame({
className,
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'
}
gridScrollbar({
omit: !model.experimental['enableFullWidthScroll'],
viewRef: impl.viewRef
})
],
testId,
Expand All @@ -160,7 +144,7 @@ class GridLocalModel extends HoistModel {
@lookup(GridModel)
private model: GridModel;
agOptions: GridOptions;
viewRef = createRef<HTMLElement>();
viewRef = createObservableRef<HTMLElement>();
private rowKeyNavSupport: RowKeyNavSupport;
private prevRs: RecordSet;

Expand Down Expand Up @@ -297,7 +281,7 @@ class GridLocalModel extends HoistModel {
}

// Support for FullWidthScroll
if (this.isFullWidthScrollEnabled) {
if (model.experimental['enableFullWidthScroll']) {
ret.suppressHorizontalScroll = true;
}

Expand Down Expand Up @@ -870,83 +854,4 @@ 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;
}
}
119 changes: 119 additions & 0 deletions cmp/grid/impl/GridScrollbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import {GridModel} from '@xh/hoist/cmp/grid';
import {div} from '@xh/hoist/cmp/layout';
import {hoistCmp, HoistModel, HoistProps, useLocalModel} from '@xh/hoist/core';
import {makeObservable} from '@xh/hoist/mobx';
import {observeResize} from '@xh/hoist/utils/js';
import {sumBy} from 'lodash';
import {action, observable} from 'mobx';
import {createRef, RefObject} from 'react';

export interface GridScrollbarProps extends HoistProps<GridModel> {
viewRef: RefObject<HTMLElement>;
}

export const gridScrollbar = hoistCmp.factory<GridScrollbarProps>({
className: 'xh-grid__grid-scrollbar',
render({className}) {
const impl = useLocalModel(GridScrollbarModel),
{scrollerRef, viewportWidth, visibleColumnWidth, SCROLLBAR_SIZE} = impl;

return div({
className,
omit: viewportWidth > visibleColumnWidth,
item: div({
className: `${className}__filler`,
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'
}
});
}
});

class GridScrollbarModel extends HoistModel {
readonly SCROLLBAR_SIZE = 10;
readonly scrollerRef = createRef<HTMLDivElement>();

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

private viewportResizeObserver: ResizeObserver;

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');
}

private get model(): GridModel {
return this.componentProps.model as GridModel;
}

private get viewRef(): RefObject<HTMLElement> {
return this.componentProps.viewRef;
}

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

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

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

override afterLinked() {
this.addReaction({
when: () => !!this.viewRef.current && this.model.isReady,
run: () => {
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 150882b

Please sign in to comment.