From 308a731dc66f74fe92de3aecd7fbb7ecc45e150d Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Mon, 28 Nov 2022 01:07:41 +0100 Subject: [PATCH] fix(lib): start selection inbetween select items This commit fixes the issue of not being able to start a selection if mousedown occured on neither Host element nor select item element but inbetween. The issue occurs if `SelectItem`s are not direct children of the `SelectContainer`. Fixes #144 --- cypress/integration/dragging.spec.ts | 78 +++++++++++++++++++ cypress/support/utils.ts | 4 + .../src/lib/select-container.component.ts | 16 +++- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/cypress/integration/dragging.spec.ts b/cypress/integration/dragging.spec.ts index 7a1e220..c2a5603 100644 --- a/cypress/integration/dragging.spec.ts +++ b/cypress/integration/dragging.spec.ts @@ -1,7 +1,9 @@ import { DEFAULT_CONFIG } from '../../projects/ngx-drag-to-select/src/lib/config'; import { + disableDragOverItems, disableSelection, + disableSelectOnClick, disableSelectOnDrag, enableSelectMode, getDesktopExample, @@ -91,6 +93,82 @@ describe('Dragging', () => { .dispatch('mouseup'); }); }); + + describe('selection with dragOverItems set to false', () => { + it('should not start selection over items', () => { + disableDragOverItems().then(() => { + getDesktopExample().within(() => { + cy.getSelectItem(0) + .dispatch('mousedown', { button: 0 }) + .getSelectItem(6, 'end') + .dispatch('mousemove') + .shouldSelect([1]) + .getSelectBox() + .then(shouldBeInvisible) + .get('@end') + .dispatch('mouseup'); + }); + }); + }); + + it('should start selection in element inbetween SelectContainer and SelectItem', () => { + disableDragOverItems().then(() => { + getDesktopExample().within(() => { + cy.get('mat-grid-list') + .as('end') + .scrollIntoView() + .wait(16) + .trigger('mousedown', 210, 50, { button: 0 }) + .wait(16) + .getSelectItem(6) + .dispatch('mousemove') + .shouldSelect([2, 3, 6, 7]) + .getSelectBox() + .then(shouldBeVisible) + .get('@end') + .dispatch('mouseup'); + }); + }); + }); + }); + + describe('selection with selectOnClick set to false', () => { + it('should not start selection over items', () => { + disableSelectOnClick().then(() => { + getDesktopExample().within(() => { + cy.getSelectItem(0) + .dispatch('mousedown', { button: 0 }) + .getSelectItem(6, 'end') + .dispatch('mousemove') + .shouldSelect([]) + .getSelectBox() + .then(shouldBeInvisible) + .get('@end') + .dispatch('mouseup'); + }); + }); + }); + + it('should start selection in element inbetween SelectContainer and SelectItem', () => { + disableSelectOnClick().then(() => { + getDesktopExample().within(() => { + cy.get('mat-grid-list') + .as('end') + .scrollIntoView() + .wait(16) + .trigger('mousedown', 210, 50, { button: 0 }) + .wait(16) + .getSelectItem(6) + .dispatch('mousemove') + .shouldSelect([2, 3, 6, 7]) + .getSelectBox() + .then(shouldBeVisible) + .get('@end') + .dispatch('mouseup'); + }); + }); + }); + }); }); describe('Keyboard Events', () => { diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index e046644..a881d32 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -52,6 +52,10 @@ export const disableSelectOnDrag = () => { return cy.get('[data-cy="selectOnDrag"]').click(); }; +export const disableDragOverItems = () => { + return cy.get('[data-cy="dragOverItems"]').click(); +}; + export const disableSelectOnClick = () => { return cy.get('[data-cy="selectOnClick"]').click(); }; diff --git a/projects/ngx-drag-to-select/src/lib/select-container.component.ts b/projects/ngx-drag-to-select/src/lib/select-container.component.ts index 9b4c764..c822515 100644 --- a/projects/ngx-drag-to-select/src/lib/select-container.component.ts +++ b/projects/ngx-drag-to-select/src/lib/select-container.component.ts @@ -122,6 +122,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After private _selectedItems$ = new BehaviorSubject>([]); private _selectableItems: Array = []; + private _selectableItemsNative: Array = []; private updateItems$ = new Subject(); private destroy$ = new Subject(); @@ -163,7 +164,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After const mousedown$ = fromEvent(this.host, 'mousedown').pipe( filter((event) => event.button === 0), // only emit left mouse filter(() => !this.disabled), - filter((event) => this.selectOnClick || event.target === this.host), + filter((event) => this.selectOnClick || this._isClickOutsideSelectableItem(event.target)), tap((event) => this._onMouseDown(event)), share() ); @@ -172,7 +173,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After filter((event) => !this.shortcuts.disableSelection(event)), filter(() => !this.selectMode), filter(() => !this.disableDrag), - filter((event) => this.dragOverItems || event.target === this.host), + filter((event) => this.dragOverItems || this._isClickOutsideSelectableItem(event.target)), switchMap(() => mousemove$.pipe(takeUntil(mouseup$))), share() ); @@ -265,6 +266,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After ngAfterContentInit() { this._selectableItems = this.$selectableItems.toArray(); + this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); } selectAll() { @@ -351,6 +353,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After .subscribe(([items, selectedItems]: [QueryList, any[]]) => { const newList = items.toArray(); this._selectableItems = newList; + this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); const newValues = newList.map((item) => item.value); const removedItems = selectedItems.filter((item) => !newValues.includes(item)); @@ -680,4 +683,13 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After return null; } + + private _isClickOutsideSelectableItem(element: EventTarget): boolean { + if (!(element instanceof HTMLElement)) return false; + + if (element === this.host) return true; + if (this._selectableItemsNative.includes(element)) return false; + + return this._isClickOutsideSelectableItem(element.parentElement); + } }