Skip to content

Commit

Permalink
fix: editors with drop-down layer to follow scrolling (#1542)
Browse files Browse the repository at this point in the history
* fix: editors with drop-down layer to follow scrolling

* test: add tests for editors with drop-down layer to follow scrolling

* chore: apply code reviews

* chore: fix lint error

* chore: apply code review
  • Loading branch information
jajugoguma authored Jan 7, 2022
1 parent b4f41c7 commit 95eb78f
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 16 deletions.
58 changes: 58 additions & 0 deletions packages/toast-ui.grid/cypress/integration/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,3 +585,61 @@ describe('original cell value should be kept', () => {
cy.get('input').should('have.value', 'undefined');
});
});

describe.only('Scroll with editor that has drop-down layer', () => {
function scrollTo(position: Cypress.PositionType) {
cy.getByCls('rside-area', 'body-area').wait(0).scrollTo(position);
}

beforeEach(() => {
const data = [
{ id: '1', name: 'Kim', age: '10', school: '1', grade: 'First' },
{ id: '2', name: 'Lee', age: '11', school: '1', grade: 'Second' },
{ id: '3', name: 'Park', age: '12', school: '1', grade: 'First' },
{ id: '4', name: 'Choi', age: '13', school: '1', grade: 'Second' },
{ id: '5', name: 'Cho', age: '14', school: '1', grade: 'Third' },
{ id: '6', name: 'Han', age: '14', school: '1', grade: 'Second' },
{ id: '7', name: 'Lim', age: '14', school: '1', grade: 'First' },
{ id: '8', name: 'Ahn', age: '14', school: '1', grade: 'Second' },
{ id: '9', name: 'Ryu', age: '14', school: '1', grade: 'First' },
];
const columns = [
{ name: 'id', minWidth: 100 },
{ name: 'name', minWidth: 100 },
{
name: 'school',
formatter: 'listItemText',
editor: {
type: 'select',
options: {
listItems: [
{ text: 'Primary', value: '1' },
{ text: 'Middle', value: '2' },
{ text: 'High', value: '3' },
],
},
},
minWidth: 100,
},
{ name: 'age', minWidth: 100 },
{ name: 'grade', minWidth: 100 },
];

cy.createGrid({ data, columns, width: 200, bodyHeight: 150 });
});

[
{ direcion: 'down', position: 'bottom' },
{ direcion: 'up', position: 'top' },
{ direcion: 'right', position: 'right' },
{ direcion: 'left', position: 'left' },
].forEach(({ direcion, position }) => {
it(`should hides the drop-down layer when scrolling ${direcion}`, () => {
cy.gridInstance().invoke('startEditing', 4, 'school');

scrollTo(position as Cypress.PositionType);

cy.getByCls('editor-select-box-layer').should('have.css', 'z-index', '-100');
});
});
});
26 changes: 23 additions & 3 deletions packages/toast-ui.grid/src/editor/checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { CellEditor, CellEditorProps, PortalEditingKeydown } from '@t/editor';
import {
CellEditor,
CellEditorProps,
GridRectForDropDownLayerPos,
LayerPos,
PortalEditingKeydown,
} from '@t/editor';
import { CellValue, ListItem } from '@t/store/data';
import { getListItems } from '../helper/editor';
import { cls, hasClass } from '../helper/dom';
import { getKeyStrokeString, isArrowKey } from '../helper/keyboard';
import { findIndex, isNil } from '../helper/common';
import { getContainerElement, setLayerPosition, setOpacity } from './dom';
import { findIndex, isNil, pixelToNumber } from '../helper/common';
import { getContainerElement, setLayerPosition, setOpacity, moveLayer } from './dom';

const LAYER_CLASSNAME = cls('editor-checkbox-list-layer');
const LIST_ITEM_CLASSNAME = cls('editor-checkbox');
Expand All @@ -27,6 +33,8 @@ export class CheckboxEditor implements CellEditor {

private elementIds: string[] = [];

private initLayerPos: LayerPos | null = null;

public constructor(props: CellEditorProps) {
const { columnInfo, width, formattedValue, portalEditingKeydown } = props;
const el = document.createElement('div');
Expand Down Expand Up @@ -159,6 +167,12 @@ export class CheckboxEditor implements CellEditor {
this.layer.querySelector('input')) as HTMLInputElement;
}

public moveDropdownLayer(gridRect: GridRectForDropDownLayerPos) {
if (this.initLayerPos) {
moveLayer(this.layer, this.initLayerPos, gridRect);
}
}

public getElement() {
return this.el;
}
Expand Down Expand Up @@ -191,6 +205,11 @@ export class CheckboxEditor implements CellEditor {
// @ts-ignore
setLayerPosition(this.el, this.layer);

this.initLayerPos = {
top: pixelToNumber(this.layer.style.top),
left: pixelToNumber(this.layer.style.left),
};

const checkedInput = this.getCheckedInput();
if (checkedInput) {
this.highlightItem(`checkbox-${checkedInput.value}`);
Expand All @@ -204,5 +223,6 @@ export class CheckboxEditor implements CellEditor {
this.layer.removeEventListener('mouseover', this.onMouseover);
this.layer.removeEventListener('keydown', this.onKeydown);
getContainerElement(this.el).removeChild(this.layer);
this.initLayerPos = null;
}
}
21 changes: 18 additions & 3 deletions packages/toast-ui.grid/src/editor/datePicker.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import TuiDatePicker from 'tui-date-picker';
import { Dictionary } from '@t/options';
import { CellEditor, CellEditorProps } from '@t/editor';
import { CellEditor, CellEditorProps, GridRectForDropDownLayerPos, LayerPos } from '@t/editor';
import { cls } from '../helper/dom';
import { deepMergedCopy, isNumber, isString, isNil } from '../helper/common';
import { setLayerPosition, getContainerElement, setOpacity } from './dom';
import { deepMergedCopy, isNumber, isString, isNil, pixelToNumber } from '../helper/common';
import { setLayerPosition, getContainerElement, setOpacity, moveLayer } from './dom';

export class DatePickerEditor implements CellEditor {
public el: HTMLDivElement;
Expand All @@ -16,6 +16,8 @@ export class DatePickerEditor implements CellEditor {

private iconEl?: HTMLElement;

private initLayerPos: LayerPos | null = null;

private createInputElement() {
const inputEl = document.createElement('input');
inputEl.className = cls('content-text');
Expand Down Expand Up @@ -102,6 +104,12 @@ export class DatePickerEditor implements CellEditor {
this.datePickerEl.on('close', () => this.focus());
}

public moveDropdownLayer(gridRect: GridRectForDropDownLayerPos) {
if (this.initLayerPos) {
moveLayer(this.layer, this.initLayerPos, gridRect);
}
}

public getElement() {
return this.el;
}
Expand All @@ -119,6 +127,12 @@ export class DatePickerEditor implements CellEditor {

// `this.layer.firstElementChild` is real datePicker layer(it is need to get total height)
setLayerPosition(this.el, this.layer, this.layer.firstElementChild as HTMLElement, true);

this.initLayerPos = {
top: pixelToNumber(this.layer.style.top),
left: pixelToNumber(this.layer.style.left),
};

// To show the layer which has appropriate position
setOpacity(this.layer, 1);
}
Expand All @@ -129,5 +143,6 @@ export class DatePickerEditor implements CellEditor {
}
this.datePickerEl.destroy();
getContainerElement(this.el).removeChild(this.layer);
this.initLayerPos = null;
}
}
34 changes: 34 additions & 0 deletions packages/toast-ui.grid/src/editor/dom.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { GridRectForDropDownLayerPos, LayerPos } from '@t/editor';
import { isBetween } from '../helper/common';
import { findParentByClassName } from '../helper/dom';

const INDENT = 5;
const SCROLL_BAR_WIDTH = 17;
const SCROLL_BAR_HEIGHT = 17;

function exceedGridViewport(
top: number,
left: number,
{ bodyHeight, bodyWidth, headerHeight, leftSideWidth }: GridRectForDropDownLayerPos
) {
return !(
isBetween(top, headerHeight, bodyHeight + headerHeight) &&
isBetween(left, leftSideWidth, bodyWidth)
);
}

export function setOpacity(el: HTMLElement, opacity: number | string) {
el.style.opacity = String(opacity);
}
Expand All @@ -12,6 +25,27 @@ export function getContainerElement(el: HTMLElement) {
return findParentByClassName(el, 'container')!;
}

export function moveLayer(
layerEl: HTMLElement,
initLayerPos: LayerPos,
gridRect: GridRectForDropDownLayerPos
) {
const { top, left } = initLayerPos;
const { initBodyScrollTop, initBodyScrollLeft, bodyScrollTop, bodyScrollLeft } = gridRect;
const newTop = top + initBodyScrollTop - bodyScrollTop;
const newLeft = left + initBodyScrollLeft - bodyScrollLeft;

if (exceedGridViewport(newTop, newLeft, gridRect)) {
layerEl.style.zIndex = '-100';
layerEl.style.top = '0px';
layerEl.style.left = '0px';
} else {
layerEl.style.zIndex = '';
layerEl.style.top = `${newTop}px`;
layerEl.style.left = `${newLeft}px`;
}
}

export function setLayerPosition(
innerEl: HTMLElement,
layerEl: HTMLElement,
Expand Down
27 changes: 24 additions & 3 deletions packages/toast-ui.grid/src/editor/select.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import SelectBox from '@toast-ui/select-box';
import '@toast-ui/select-box/dist/toastui-select-box.css';
import { CellEditor, CellEditorProps, PortalEditingKeydown } from '@t/editor';
import {
CellEditor,
CellEditorProps,
GridRectForDropDownLayerPos,
LayerPos,
PortalEditingKeydown,
} from '@t/editor';
import { CellValue, ListItem } from '@t/store/data';
import { getListItems } from '../helper/editor';
import { cls } from '../helper/dom';
import { setLayerPosition, getContainerElement, setOpacity } from './dom';
import { setLayerPosition, getContainerElement, setOpacity, moveLayer } from './dom';
import { getKeyStrokeString } from '../helper/keyboard';
import { includes, isNil } from '../helper/common';
import { includes, isNil, pixelToNumber } from '../helper/common';

export class SelectEditor implements CellEditor {
public el: HTMLDivElement;
Expand All @@ -21,6 +27,8 @@ export class SelectEditor implements CellEditor {

private portalEditingKeydown: PortalEditingKeydown;

private initLayerPos: LayerPos | null = null;

public constructor(props: CellEditorProps) {
const { width, formattedValue, portalEditingKeydown } = props;
const el = document.createElement('div');
Expand Down Expand Up @@ -93,6 +101,12 @@ export class SelectEditor implements CellEditor {
this.selectBoxEl.input.focus();
}

public moveDropdownLayer(gridRect: GridRectForDropDownLayerPos) {
if (this.initLayerPos) {
moveLayer(this.layer, this.initLayerPos, gridRect);
}
}

public getElement() {
return this.el;
}
Expand All @@ -107,6 +121,12 @@ export class SelectEditor implements CellEditor {
getContainerElement(this.el).appendChild(this.layer);
// @ts-ignore
setLayerPosition(this.el, this.layer, this.selectBoxEl.dropdown.el);

this.initLayerPos = {
top: pixelToNumber(this.layer.style.top),
left: pixelToNumber(this.layer.style.left),
};

this.focusSelectBox();
this.isMounted = true;
// To show the layer which has appropriate position
Expand All @@ -117,5 +137,6 @@ export class SelectEditor implements CellEditor {
this.selectBoxEl.destroy();
this.layer.removeEventListener('keydown', this.onKeydown);
getContainerElement(this.el).removeChild(this.layer);
this.initLayerPos = null;
}
}
9 changes: 9 additions & 0 deletions packages/toast-ui.grid/src/helper/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,12 @@ export function convertDataToText(data: string[][], delimiter: string) {
export function silentSplice<T>(arr: T[], start: number, deleteCount: number, ...items: T[]): T[] {
return Array.prototype.splice.call(arr, start, deleteCount, ...items);
}

export function isBetween(value: number, start: number, end: number) {
return start <= value && value <= end;
}

export function pixelToNumber(pixelString: string) {
const regExp = new RegExp(/[0-9]+px/);
return regExp.test(pixelString) ? parseInt(pixelString.replace('px', ''), 10) : 0;
}
Loading

0 comments on commit 95eb78f

Please sign in to comment.