From 7dee7e6ff2383ceca7638cf1a7e1c128fad1b8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=8C=80=EC=97=B0?= Date: Mon, 25 Oct 2021 17:48:17 +0900 Subject: [PATCH] fix: disable context menu on dummy cells (fix #1463) (#1490) * fix: disable context menu on dummy cells (fix #1463) * test: add test for context menu on dummy rows * chore: apply code reviews * chore: fix lint error --- .../cypress/integration/contextMenu.spec.ts | 14 +++++++++++ packages/toast-ui.grid/src/editor/dom.ts | 4 ++-- packages/toast-ui.grid/src/helper/dom.ts | 6 ++++- packages/toast-ui.grid/src/view/bodyArea.tsx | 9 +++++--- packages/toast-ui.grid/src/view/container.tsx | 23 ++++++++++++++----- .../toast-ui.grid/src/view/headerArea.tsx | 12 +++++++--- .../toast-ui.grid/src/view/sortingButton.tsx | 4 ++-- .../src/view/treeCellContents.tsx | 6 ++--- 8 files changed, 58 insertions(+), 20 deletions(-) diff --git a/packages/toast-ui.grid/cypress/integration/contextMenu.spec.ts b/packages/toast-ui.grid/cypress/integration/contextMenu.spec.ts index 062848657..b31c21ca4 100644 --- a/packages/toast-ui.grid/cypress/integration/contextMenu.spec.ts +++ b/packages/toast-ui.grid/cypress/integration/contextMenu.spec.ts @@ -195,4 +195,18 @@ describe('context menu', () => { // bypassing the clipboard test using our own clipboard element. cy.getByCls('clipboard').should('have.text', 'Lee\t20'); }); + + it('should not display the context menu on dummy rows', () => { + const data = [ + { name: 'Lee', age: 20 }, + { name: 'Han', age: 28 }, + { name: 'Ryu', age: 22 }, + ]; + const columns = [{ name: 'name' }, { name: 'age' }]; + + cy.createGrid({ data, columns, bodyHeight: 300, showDummyRows: true }); + + cy.getByCls('cell-dummy').first().rightclick(); + cy.getByCls('context-menu').should('be.not.visible'); + }); }); diff --git a/packages/toast-ui.grid/src/editor/dom.ts b/packages/toast-ui.grid/src/editor/dom.ts index a4db6e4d1..b68c24a41 100644 --- a/packages/toast-ui.grid/src/editor/dom.ts +++ b/packages/toast-ui.grid/src/editor/dom.ts @@ -1,4 +1,4 @@ -import { findParent } from '../helper/dom'; +import { findParentByClassName } from '../helper/dom'; const INDENT = 5; const SCROLL_BAR_WIDTH = 17; @@ -9,7 +9,7 @@ export function setOpacity(el: HTMLElement, opacity: number | string) { } export function getContainerElement(el: HTMLElement) { - return findParent(el, 'container')!; + return findParentByClassName(el, 'container')!; } export function setLayerPosition( diff --git a/packages/toast-ui.grid/src/helper/dom.ts b/packages/toast-ui.grid/src/helper/dom.ts index cb878718e..10f8111af 100644 --- a/packages/toast-ui.grid/src/helper/dom.ts +++ b/packages/toast-ui.grid/src/helper/dom.ts @@ -197,7 +197,7 @@ export function findParentByTagName(el: HTMLElement, tagName: string) { return currentEl; } -export function findParent(el: HTMLElement, className: ClassNameType) { +export function findParentByClassName(el: HTMLElement, className: ClassNameType) { let currentEl: HTMLElement | null = el; while (currentEl && !hasClass(currentEl, className)) { currentEl = currentEl.parentElement; @@ -206,6 +206,10 @@ export function findParent(el: HTMLElement, className: ClassNameType) { return currentEl; } +export function isParentExistWithClassNames(el: HTMLElement, classNames: ClassNameType[]) { + return classNames.some((className) => !isNull(findParentByClassName(el, className))); +} + export function getCellAddress(el: HTMLElement) { const cellElement = findParentByTagName(el, 'td'); diff --git a/packages/toast-ui.grid/src/view/bodyArea.tsx b/packages/toast-ui.grid/src/view/bodyArea.tsx index 4147b97cc..36cd1e890 100644 --- a/packages/toast-ui.grid/src/view/bodyArea.tsx +++ b/packages/toast-ui.grid/src/view/bodyArea.tsx @@ -19,7 +19,7 @@ import { setCursorStyle, hasClass, isDatePickerElement, - findParent, + findParentByClassName, getCellAddress, } from '../helper/dom'; import { DispatchProps } from '../dispatch/create'; @@ -261,7 +261,7 @@ class BodyAreaComp extends Component { return isFocusedCell(this.context.store.focus, rowKey, columnName); } - return !!findParent(element, 'layer-selection'); + return !!findParentByClassName(element, 'layer-selection'); } private handleMouseDown = (ev: MouseEvent) => { @@ -296,7 +296,10 @@ class BodyAreaComp extends Component { return; } - if (!isDatePickerElement(targetElement) && !findParent(targetElement, 'layer-editing')) { + if ( + !isDatePickerElement(targetElement) && + !findParentByClassName(targetElement, 'layer-editing') + ) { dispatch( 'mouseDownBody', { scrollTop, scrollLeft, side, ...this.boundingRect }, diff --git a/packages/toast-ui.grid/src/view/container.tsx b/packages/toast-ui.grid/src/view/container.tsx index f02d9280d..51a8d84f0 100644 --- a/packages/toast-ui.grid/src/view/container.tsx +++ b/packages/toast-ui.grid/src/view/container.tsx @@ -11,7 +11,14 @@ import { ContextMenu } from './contextMenu'; import { HeightResizeHandle } from './heightResizeHandle'; import { Clipboard } from './clipboard'; import { Pagination } from './pagination'; -import { cls, getCellAddress, dataAttr, findParent, getCoordinateWithOffset } from '../helper/dom'; +import { + cls, + getCellAddress, + dataAttr, + findParentByClassName, + getCoordinateWithOffset, + isParentExistWithClassNames, +} from '../helper/dom'; import { DispatchProps } from '../dispatch/create'; import { connect } from './hoc'; import { EventBus, getEventBus } from '../event/eventBus'; @@ -299,10 +306,14 @@ export class ContainerComp extends Component { const { dispatch, filtering } = this.props; const target = ev.target as HTMLElement; - if (filtering && !findParent(target, 'btn-filter') && !findParent(target, 'filter-container')) { + if ( + filtering && + !findParentByClassName(target, 'btn-filter') && + !findParentByClassName(target, 'filter-container') + ) { dispatch('setActiveColumnAddress', null); } - if (!findParent(target, 'context-menu')) { + if (!findParentByClassName(target, 'context-menu')) { this.props.dispatch('hideContextMenu'); } }; @@ -325,7 +336,7 @@ export class ContainerComp extends Component { } handleContextMenu = (ev: MouseEvent) => { - if (findParent(ev.target as HTMLElement, 'cell-header')) { + if (isParentExistWithClassNames(ev.target as HTMLElement, ['cell-header', 'cell-dummy'])) { return; } @@ -335,8 +346,8 @@ export class ContainerComp extends Component { const pos = { left: ev.clientX - offsetLeft, top: ev.clientY - offsetTop }; const [pageX, pageY] = getCoordinateWithOffset(ev.pageX, ev.pageY); - const bodyArea = findParent(ev.target as HTMLElement, 'body-area')!; - const side: Side = findParent(bodyArea, 'lside-area') ? 'L' : 'R'; + const bodyArea = findParentByClassName(ev.target as HTMLElement, 'body-area')!; + const side: Side = findParentByClassName(bodyArea, 'lside-area') ? 'L' : 'R'; const { scrollTop, scrollLeft } = bodyArea; const { top, left } = bodyArea.getBoundingClientRect(); diff --git a/packages/toast-ui.grid/src/view/headerArea.tsx b/packages/toast-ui.grid/src/view/headerArea.tsx index 6f81f0a28..55ff6ff3c 100644 --- a/packages/toast-ui.grid/src/view/headerArea.tsx +++ b/packages/toast-ui.grid/src/view/headerArea.tsx @@ -3,7 +3,13 @@ import { Side } from '@t/store/focus'; import { ColumnInfo, ComplexColumnInfo } from '@t/store/column'; import { Range } from '@t/store/selection'; import { ColGroup } from './colGroup'; -import { cls, setCursorStyle, getCoordinateWithOffset, hasClass, findParent } from '../helper/dom'; +import { + cls, + setCursorStyle, + getCoordinateWithOffset, + hasClass, + findParentByClassName, +} from '../helper/dom'; import { connect } from './hoc'; import { ColumnResizer } from './columnResizer'; import { DispatchProps } from '../dispatch/create'; @@ -49,7 +55,7 @@ class HeaderAreaComp extends Component { const target = ev.target as HTMLElement; if ( - findParent(target, 'cell-row-header') || + findParentByClassName(target, 'cell-row-header') || hasClass(target, 'btn-sorting') || hasClass(target, 'btn-filter') || ev.button === RIGHT_MOUSE_BUTTON @@ -60,7 +66,7 @@ class HeaderAreaComp extends Component { let name = target.getAttribute('data-column-name')!; if (!name) { - const parent = findParent(target, 'cell-header'); + const parent = findParentByClassName(target, 'cell-header'); if (parent) { name = parent.getAttribute('data-column-name')!; } diff --git a/packages/toast-ui.grid/src/view/sortingButton.tsx b/packages/toast-ui.grid/src/view/sortingButton.tsx index b949949af..f846a529e 100644 --- a/packages/toast-ui.grid/src/view/sortingButton.tsx +++ b/packages/toast-ui.grid/src/view/sortingButton.tsx @@ -2,7 +2,7 @@ import { h, Component } from 'preact'; import { SortingType } from '@t/store/column'; import { SortState } from '@t/store/data'; import { DataProvider } from '@t/dataSource'; -import { cls, hasClass, findParent } from '../helper/dom'; +import { cls, hasClass, findParentByClassName } from '../helper/dom'; import { connect } from './hoc'; import { getDataProvider } from '../instance'; import { DispatchProps } from '../dispatch/create'; @@ -33,7 +33,7 @@ class SortingButtonComp extends Component { const { dispatch, sortState, dataProvider, defaultAscending } = this.props; const { columns } = sortState; - const th = findParent(target, 'cell'); + const th = findParentByClassName(target, 'cell'); const columnName = th!.getAttribute('data-column-name')!; const index = findPropIndex('columnName', columnName, columns); const ascending = index !== -1 ? !columns[index].ascending : defaultAscending; diff --git a/packages/toast-ui.grid/src/view/treeCellContents.tsx b/packages/toast-ui.grid/src/view/treeCellContents.tsx index ffeb983c6..d25ebb4fb 100644 --- a/packages/toast-ui.grid/src/view/treeCellContents.tsx +++ b/packages/toast-ui.grid/src/view/treeCellContents.tsx @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; import { RowKey, TreeCellInfo } from '@t/store/data'; -import { cls, findParent } from '../helper/dom'; +import { cls, findParentByClassName } from '../helper/dom'; import { connect } from './hoc'; import { DispatchProps } from '../dispatch/create'; import { TREE_INDENT_WIDTH } from '../helper/constant'; @@ -28,9 +28,9 @@ export class TreeCellContentsComp extends Component { const { dispatch, rowKey } = this.props; const target = ev.target as HTMLElement; - if (findParent(target, 'tree-button-collapse')) { + if (findParentByClassName(target, 'tree-button-collapse')) { dispatch('expandByRowKey', rowKey, false); - } else if (findParent(target, 'tree-button-expand')) { + } else if (findParentByClassName(target, 'tree-button-expand')) { dispatch('collapseByRowKey', rowKey, false); } };