From 308a731dc66f74fe92de3aecd7fbb7ecc45e150d Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Mon, 28 Nov 2022 01:07:41 +0100 Subject: [PATCH 01/19] 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); + } } From 22ea2cc96d80ab93822271e6031d735df3793ab5 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Thu, 5 Nov 2020 22:06:42 -0500 Subject: [PATCH 02/19] feat(lib): use inject to pass container reference to selectableItem --- .../src/lib/select-container.component.ts | 48 ++++++++++--------- .../src/lib/select-item.directive.ts | 12 ++++- 2 files changed, 37 insertions(+), 23 deletions(-) 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 c822515..f950c93 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 @@ -15,6 +15,7 @@ import { PLATFORM_ID, Inject, AfterContentInit, + InjectionToken, } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; @@ -68,6 +69,8 @@ import { } from './utils'; import { KeyboardEventsService } from './keyboard-events.service'; +export const DTS_SELECT_CONTAINER = new InjectionToken('SelectContainerComponent'); + @Component({ selector: 'dts-select-container', exportAs: 'dts-select-container', @@ -81,6 +84,7 @@ import { KeyboardEventsService } from './keyboard-events.service'; > `, styleUrls: ['./select-container.component.scss'], + providers: [{ provide: DTS_SELECT_CONTAINER, useExisting: SelectContainerComponent }], }) export class SelectContainerComponent implements AfterViewInit, OnDestroy, AfterContentInit { host: SelectContainerHost; @@ -269,8 +273,13 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); } + updateSelectableItems() { + this._selectableItems = Array.from(this._registry); //this.$selectableItems.toArray(); + this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); + } + selectAll() { - this.$selectableItems.forEach((item) => { + this._selectableItems.forEach((item) => { this._selectItem(item); }); } @@ -288,14 +297,26 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After } clearSelection() { - this.$selectableItems.forEach((item) => { + this._selectableItems.forEach((item) => { this._deselectItem(item); }); } + _registry: Set = new Set(); + + register(item: SelectItemDirective) { + this._registry.add(item); + this.updateSelectableItems(); + } + unregister(item: SelectItemDirective) { + this._registry.delete(item); + this.updateSelectableItems(); + this._removeItem(item, this.selectedItems); + } + update() { this._calculateBoundingClientRect(); - this.$selectableItems.forEach((item) => item.calculateBoundingClientRect()); + this._selectableItems.forEach((item) => item.calculateBoundingClientRect()); } ngOnDestroy() { @@ -346,23 +367,6 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After break; } }); - - // Update the container as well as all selectable items if the list has changed - this.$selectableItems.changes - .pipe(withLatestFrom(this._selectedItems$), observeOn(asyncScheduler), takeUntil(this.destroy$)) - .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)); - - if (removedItems.length) { - removedItems.forEach((item) => this._removeItem(item, selectedItems)); - } - - this.update(); - }); } private _observeBoundingRectChanges() { @@ -459,7 +463,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After return; } - this.$selectableItems.forEach((item, index) => { + this._selectableItems.forEach((item, index) => { const itemRect = item.getBoundingClientRect(); const withinBoundingBox = inBoundingBox(mousePoint, itemRect); @@ -522,7 +526,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After private _selectItems(event: Event) { const selectionBox = calculateBoundingClientRect(this.$selectBox.nativeElement); - this.$selectableItems.forEach((item, index) => { + this._selectableItems.forEach((item, index) => { if (this._isExtendedSelection(event)) { this._extendedSelectionMode(selectionBox, item, event); } else { diff --git a/projects/ngx-drag-to-select/src/lib/select-item.directive.ts b/projects/ngx-drag-to-select/src/lib/select-item.directive.ts index 87eafec..7811ca2 100644 --- a/projects/ngx-drag-to-select/src/lib/select-item.directive.ts +++ b/projects/ngx-drag-to-select/src/lib/select-item.directive.ts @@ -10,9 +10,13 @@ import { Renderer2, OnInit, HostBinding, + Optional, + SkipSelf, + OnDestroy, } from '@angular/core'; import { DragToSelectConfig, BoundingBox } from './models'; +import { DTS_SELECT_CONTAINER, SelectContainerComponent } from './select-container.component'; import { CONFIG } from './tokens'; import { calculateBoundingClientRect } from './utils'; @@ -22,7 +26,7 @@ export const SELECT_ITEM_INSTANCE = Symbol(); selector: '[dtsSelectItem]', exportAs: 'dtsSelectItem', }) -export class SelectItemDirective implements OnInit, DoCheck { +export class SelectItemDirective implements OnInit, DoCheck, OnDestroy { private _boundingClientRect: BoundingBox | undefined; selected = false; @@ -46,18 +50,24 @@ export class SelectItemDirective implements OnInit, DoCheck { constructor( @Inject(CONFIG) private config: DragToSelectConfig, @Inject(PLATFORM_ID) private platformId: Record, + @Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent, private host: ElementRef, private renderer: Renderer2 ) {} ngOnInit() { this.nativeElememnt[SELECT_ITEM_INSTANCE] = this; + this.container.register(this); } ngDoCheck() { this.applySelectedClass(); } + ngOnDestroy() { + this.container.unregister(this); + } + toggleRangeStart() { this.rangeStart = !this.rangeStart; } From f08cae2d3292e97b9c35c001fb9f0973ac72b621 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Sat, 7 Nov 2020 21:52:43 -0500 Subject: [PATCH 03/19] docs: added nested directive demo with drag and drop refactor: removed dead code from change and fixed circular dependency caused by DI --- projects/ngx-drag-to-select/src/lib/models.ts | 7 ++ .../src/lib/select-container.component.ts | 16 ++--- .../src/lib/select-item.directive.ts | 7 +- projects/ngx-drag-to-select/src/lib/tokens.ts | 4 +- src/app/app.component.html | 21 ++++-- src/app/app.module.ts | 11 +++ src/app/dragndrop/dragndrop.component.html | 9 +++ src/app/dragndrop/dragndrop.component.scss | 20 ++++++ src/app/dragndrop/dragndrop.component.ts | 14 ++++ .../task-list/task-list.component.html | 19 +++++ .../task-list/task-list.component.scss | 69 +++++++++++++++++++ .../task-list/task-list.component.ts | 61 ++++++++++++++++ src/app/dragndrop/task/task.component.html | 4 ++ src/app/dragndrop/task/task.component.scss | 8 +++ src/app/dragndrop/task/task.component.ts | 14 ++++ 15 files changed, 266 insertions(+), 18 deletions(-) create mode 100644 src/app/dragndrop/dragndrop.component.html create mode 100644 src/app/dragndrop/dragndrop.component.scss create mode 100644 src/app/dragndrop/dragndrop.component.ts create mode 100644 src/app/dragndrop/task-list/task-list.component.html create mode 100644 src/app/dragndrop/task-list/task-list.component.scss create mode 100644 src/app/dragndrop/task-list/task-list.component.ts create mode 100644 src/app/dragndrop/task/task.component.html create mode 100644 src/app/dragndrop/task/task.component.scss create mode 100644 src/app/dragndrop/task/task.component.ts diff --git a/projects/ngx-drag-to-select/src/lib/models.ts b/projects/ngx-drag-to-select/src/lib/models.ts index 4122c2f..582da6d 100644 --- a/projects/ngx-drag-to-select/src/lib/models.ts +++ b/projects/ngx-drag-to-select/src/lib/models.ts @@ -1,3 +1,4 @@ +import { ComponentType } from '@angular/cdk/portal'; import { Observable } from 'rxjs'; import { SelectItemDirective } from './select-item.directive'; @@ -62,3 +63,9 @@ export enum Action { Delete, None, } + +export interface SelectContainer { + selectedItems: T[]; + register(item: T): void; + unregister(item: T): void; +} 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 f950c93..e0f157e 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 @@ -53,6 +53,7 @@ import { UpdateActions, PredicateFn, BoundingBox, + SelectContainer, } from './models'; import { AUDIT_TIME, NO_SELECT_CLASS } from './constants'; @@ -68,8 +69,7 @@ import { hasMinimumSize, } from './utils'; import { KeyboardEventsService } from './keyboard-events.service'; - -export const DTS_SELECT_CONTAINER = new InjectionToken('SelectContainerComponent'); +import { DTS_SELECT_CONTAINER } from './tokens'; @Component({ selector: 'dts-select-container', @@ -86,7 +86,9 @@ export const DTS_SELECT_CONTAINER = new InjectionToken styleUrls: ['./select-container.component.scss'], providers: [{ provide: DTS_SELECT_CONTAINER, useExisting: SelectContainerComponent }], }) -export class SelectContainerComponent implements AfterViewInit, OnDestroy, AfterContentInit { +export class SelectContainerComponent + implements AfterViewInit, OnDestroy, AfterContentInit, SelectContainer +{ host: SelectContainerHost; selectBoxStyles$: Observable>; selectBoxClasses$: Observable<{ [key: string]: boolean }>; @@ -94,9 +96,6 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After @ViewChild('selectBox', { static: true }) private $selectBox: ElementRef; - @ContentChildren(SelectItemDirective, { descendants: true }) - private $selectableItems: QueryList; - @Input() selectedItems: any; @Input() selectOnDrag = true; @Input() disabled = false; @@ -269,12 +268,11 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After } ngAfterContentInit() { - this._selectableItems = this.$selectableItems.toArray(); - this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); + // removed query list, maybe remove this too } updateSelectableItems() { - this._selectableItems = Array.from(this._registry); //this.$selectableItems.toArray(); + this._selectableItems = Array.from(this._registry); this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); } diff --git a/projects/ngx-drag-to-select/src/lib/select-item.directive.ts b/projects/ngx-drag-to-select/src/lib/select-item.directive.ts index 7811ca2..e00e694 100644 --- a/projects/ngx-drag-to-select/src/lib/select-item.directive.ts +++ b/projects/ngx-drag-to-select/src/lib/select-item.directive.ts @@ -15,9 +15,8 @@ import { OnDestroy, } from '@angular/core'; -import { DragToSelectConfig, BoundingBox } from './models'; -import { DTS_SELECT_CONTAINER, SelectContainerComponent } from './select-container.component'; -import { CONFIG } from './tokens'; +import { DragToSelectConfig, BoundingBox, SelectContainer } from './models'; +import { CONFIG, DTS_SELECT_CONTAINER } from './tokens'; import { calculateBoundingClientRect } from './utils'; export const SELECT_ITEM_INSTANCE = Symbol(); @@ -50,7 +49,7 @@ export class SelectItemDirective implements OnInit, DoCheck, OnDestroy { constructor( @Inject(CONFIG) private config: DragToSelectConfig, @Inject(PLATFORM_ID) private platformId: Record, - @Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent, + @Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainer, private host: ElementRef, private renderer: Renderer2 ) {} diff --git a/projects/ngx-drag-to-select/src/lib/tokens.ts b/projects/ngx-drag-to-select/src/lib/tokens.ts index 6e3122b..429828f 100644 --- a/projects/ngx-drag-to-select/src/lib/tokens.ts +++ b/projects/ngx-drag-to-select/src/lib/tokens.ts @@ -1,5 +1,7 @@ +import { ComponentType } from '@angular/cdk/portal'; import { InjectionToken } from '@angular/core'; -import { DragToSelectConfig } from './models'; +import { DragToSelectConfig, SelectContainer } from './models'; export const CONFIG = new InjectionToken('DRAG_TO_SELECT_CONFIG'); export const USER_CONFIG = new InjectionToken('USER_CONFIG'); +export const DTS_SELECT_CONTAINER = new InjectionToken>('SelectContainerComponent'); diff --git a/src/app/app.component.html b/src/app/app.component.html index 8929b47..f60e41f 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -52,9 +52,10 @@

> + [dtsSelectItem]="document" + [dtsDisabled]="disableEvenItems && document.disabled" + *ngFor="let document of documents" + > {{ document.name }} @@ -77,7 +78,19 @@

Mobile Demo SOURCE

-
+
+ +
+ + +
+

+ Drag & Drop Demo SOURCE +

+ + +
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a7bcc84..0333950 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,9 @@ import { MatChipsModule } from '@angular/material/chips'; import { MatTabsModule } from '@angular/material/tabs'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; + +import { DragDropModule } from '@angular/cdk/drag-drop'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -19,6 +22,9 @@ import { AppComponent } from './app.component'; import { FooterComponent } from './footer/footer.component'; import { HeaderComponent } from './header/header.component'; import { PhoneComponent } from './phone/phone.component'; +import { DragNDropComponent } from './dragndrop/dragndrop.component'; +import { TaskComponent } from './dragndrop/task/task.component'; +import { TaskListComponent } from './dragndrop/task-list/task-list.component'; import { KeyComponent, @@ -36,6 +42,8 @@ const MATERIAL_MODULES = [ MatTabsModule, MatIconModule, MatButtonModule, + MatCardModule, + DragDropModule, ]; @NgModule({ @@ -49,6 +57,9 @@ const MATERIAL_MODULES = [ FooterComponent, HeaderComponent, PhoneComponent, + DragNDropComponent, + TaskComponent, + TaskListComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'demo-app' }), diff --git a/src/app/dragndrop/dragndrop.component.html b/src/app/dragndrop/dragndrop.component.html new file mode 100644 index 0000000..b766471 --- /dev/null +++ b/src/app/dragndrop/dragndrop.component.html @@ -0,0 +1,9 @@ + +
+ + + + + +
+
\ No newline at end of file diff --git a/src/app/dragndrop/dragndrop.component.scss b/src/app/dragndrop/dragndrop.component.scss new file mode 100644 index 0000000..4497a6f --- /dev/null +++ b/src/app/dragndrop/dragndrop.component.scss @@ -0,0 +1,20 @@ +.list-wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + min-height: 400px; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/src/app/dragndrop/dragndrop.component.ts b/src/app/dragndrop/dragndrop.component.ts new file mode 100644 index 0000000..e444307 --- /dev/null +++ b/src/app/dragndrop/dragndrop.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { CdkDragDrop, CdkDragStart, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; + +@Component({ + selector: 'app-dragndrop', + templateUrl: './dragndrop.component.html', + styleUrls: ['./dragndrop.component.scss'], +}) +export class DragNDropComponent { + todo = ['Next Task', 'Very Important Item', 'Open Ticket #1', 'Open Ticket #2', 'Not Important Task']; + doing = ['High Priority Task', 'In Progress Item']; + done = ['Completed Item']; + selectedItems: any[] = []; +} diff --git a/src/app/dragndrop/task-list/task-list.component.html b/src/app/dragndrop/task-list/task-list.component.html new file mode 100644 index 0000000..f432a2f --- /dev/null +++ b/src/app/dragndrop/task-list/task-list.component.html @@ -0,0 +1,19 @@ +

{{title}}

+ +
+ +
+ {{ selected }} +
+
+ {{ selected }} +
+
+
\ No newline at end of file diff --git a/src/app/dragndrop/task-list/task-list.component.scss b/src/app/dragndrop/task-list/task-list.component.scss new file mode 100644 index 0000000..ba84a72 --- /dev/null +++ b/src/app/dragndrop/task-list/task-list.component.scss @@ -0,0 +1,69 @@ +:host { + background-color: #ebecf0; + border-radius: 3px; + box-sizing: border-box; + display: flex; + flex-direction: column; + max-height: 100%; + position: relative; + white-space: normal; +} + +.list-header { + padding: 0.5rem; +} + +.list-cards { + margin: 0 4px; + padding: 0 4px; + display: grid; + grid-template-columns: 1fr; + grid-auto-rows: min-content; + gap: 0.4rem; + height: -webkit-fill-available; +} + +.list-cards.cdk-drop-list-dragging app-task:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +:host(.item-dragging) { + ::ng-deep { + mat-card.selected:not(.cdk-drag-placeholder) { + opacity: 0.3; + } + } +} + +.drag-placeholder-container { + display: flex; + flex-flow: column; + gap: 0.5rem; + mat-card { + opacity: 0.5; + } +} + +.drag-preview-container { + gap: 0.5rem; + position: relative; + + mat-card { + position: absolute; + width: 200px; + top: -10px; + left: -25px; + } + mat-card:nth-child(2) { + left: -40px; + top: -10px; + transform: rotate(-1deg); + z-index: -1; + } + mat-card:nth-child(3) { + top: -5px; + left: -20px; + transform: rotate(1deg); + z-index: -2; + } +} diff --git a/src/app/dragndrop/task-list/task-list.component.ts b/src/app/dragndrop/task-list/task-list.component.ts new file mode 100644 index 0000000..4848dd5 --- /dev/null +++ b/src/app/dragndrop/task-list/task-list.component.ts @@ -0,0 +1,61 @@ +import { CdkDragStart, CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; +import { ThrowStmt } from '@angular/compiler'; +import { Component, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; +import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; +import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; + +@Component({ + selector: 'app-task-list', + styleUrls: ['./task-list.component.scss'], + templateUrl: './task-list.component.html', +}) +export class TaskListComponent { + @Input() title = 'Tasks'; + @Input() tasks = []; + + @HostBinding('class.item-dragging') + dragging = false; + + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent) {} + + dragStarted(ev: CdkDragStart, index: number): void { + this.dragging = !!ev.source._dragRef; + this.container.selectItems((item) => { + return item === this.tasks[index]; + }); + } + + dragEnded(): void { + this.dragging = null; + } + + dropped(event: CdkDragDrop): void { + this.dragging = null; + const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); + indices.sort().reverse(); + indices.forEach((idx) => { + this.tasks.splice(idx, 1); + }); + + setTimeout(() => this.container.clearSelection()); + } + + drop(event: CdkDragDrop) { + const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); + console.log('Selected', indices); + + const spliceIntoIndex = event.currentIndex; + this.tasks.splice(spliceIntoIndex, 0, ...this.container.selectedItems); + + this.container.update(); + // if (event.previousContainer === event.container) { + // for (const item of this.container.selectedItems) { + // moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + // } + // } else { + // for (const item of this.container.selectedItems) { + // transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); + // } + // } + } +} diff --git a/src/app/dragndrop/task/task.component.html b/src/app/dragndrop/task/task.component.html new file mode 100644 index 0000000..4ca1a8a --- /dev/null +++ b/src/app/dragndrop/task/task.component.html @@ -0,0 +1,4 @@ + + {{ item }} + \ No newline at end of file diff --git a/src/app/dragndrop/task/task.component.scss b/src/app/dragndrop/task/task.component.scss new file mode 100644 index 0000000..3d2d13e --- /dev/null +++ b/src/app/dragndrop/task/task.component.scss @@ -0,0 +1,8 @@ +:host { + height: min-content; +} +mat-card { + &.selected { + border: 1px solid blue; + } +} diff --git a/src/app/dragndrop/task/task.component.ts b/src/app/dragndrop/task/task.component.ts new file mode 100644 index 0000000..b6fe6c0 --- /dev/null +++ b/src/app/dragndrop/task/task.component.ts @@ -0,0 +1,14 @@ +import { Component, Inject, Input, Optional, SkipSelf } from '@angular/core'; +import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; +import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; + +@Component({ + selector: 'app-task', + styleUrls: ['./task.component.scss'], + templateUrl: './task.component.html', +}) +export class TaskComponent { + @Input() item = ''; + // @Input() selectedItems = []; + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent) {} +} From 132e52a6ef6914f6a4777889c697f7a5ba3346dc Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Sat, 7 Nov 2020 22:09:39 -0500 Subject: [PATCH 04/19] style: fixed lint error for member ordering --- .../ngx-drag-to-select/src/lib/select-container.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e0f157e..feccd27 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 @@ -134,6 +134,8 @@ export class SelectContainerComponent private _newRangeStart = false; private _lastRangeSelection: Map = new Map(); + private _registry: Set = new Set(); + constructor( @Inject(PLATFORM_ID) private platformId: Record, private shortcuts: ShortcutService, @@ -300,8 +302,6 @@ export class SelectContainerComponent }); } - _registry: Set = new Set(); - register(item: SelectItemDirective) { this._registry.add(item); this.updateSelectableItems(); From 907488daff0a82af62aa25886eb5410d226d0bb7 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Sun, 8 Nov 2020 08:06:40 -0500 Subject: [PATCH 05/19] docs: added new section to README --- README.md | 16 ++++++++++++++++ .../dragndrop/task-list/task-list.component.ts | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d56303d..5bdd531 100644 --- a/README.md +++ b/README.md @@ -553,6 +553,22 @@ Here we listen for `scroll` on the `div` and call `container.update()` in case t In order not to kill the performance, because the `scroll` event is called many many times, you may want to **throttle** it to only call `update` every 16ms or so. +### Does the `selectItem` directive need to be a direct child of the `dts-select-container`? + +As of version `4.1.0`, an injetion token is used to pass the SelectContainerComponent parent to the directive. You can use this in any component nested within the `dts-select-container`. + +```javascript +import { DTS_SELECT_CONTAINER } from 'ngx-drag-to-select'; + +@Component({...}) +export class TaskListComponent { + constructor( + @Inject(DTS_SELECT_CONTAINER) @Optional() + public container: SelectContainerComponent) {} +``` + +You can see an example of the this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). You can see that the `[selectItem]` directive is set in `app-task` component and the `dts-select-container` is in the `drag-n-drop` component. + ## Want to contribute? If you want to file a bug, contribute some code, or improve our documentation, read up on our [contributing guidelines](CONTRIBUTING.md) and [code of conduct](CODE_OF_CONDUCT.md), and check out [open issues](/issues). diff --git a/src/app/dragndrop/task-list/task-list.component.ts b/src/app/dragndrop/task-list/task-list.component.ts index 4848dd5..7ee1479 100644 --- a/src/app/dragndrop/task-list/task-list.component.ts +++ b/src/app/dragndrop/task-list/task-list.component.ts @@ -1,5 +1,4 @@ -import { CdkDragStart, CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; -import { ThrowStmt } from '@angular/compiler'; +import { CdkDragStart, CdkDragDrop } from '@angular/cdk/drag-drop'; import { Component, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; From 750c669f2fd7ffb39efc4a4077327f653936026a Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Sun, 8 Nov 2020 21:25:43 -0500 Subject: [PATCH 06/19] docs(lib): added section to readme about components capturing events test(app): added cypress test for new example demo --- README.md | 6 +- cypress/integration/drag-and-drop.spec.ts | 80 +++++++++++++++++++ cypress/support/utils.ts | 16 ++++ .../src/lib/select-container.component.ts | 5 +- src/app/app.component.html | 2 +- src/app/dragndrop/dragndrop.component.html | 8 +- .../task-list/task-list.component.scss | 2 +- .../task-list/task-list.component.ts | 4 +- src/app/dragndrop/task/task.component.html | 4 +- src/app/dragndrop/task/task.component.ts | 8 +- 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 cypress/integration/drag-and-drop.spec.ts diff --git a/README.md b/README.md index 5bdd531..efbfd06 100644 --- a/README.md +++ b/README.md @@ -567,7 +567,11 @@ export class TaskListComponent { public container: SelectContainerComponent) {} ``` -You can see an example of the this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). You can see that the `[selectItem]` directive is set in `app-task` component and the `dts-select-container` is in the `drag-n-drop` component. +You can see an example of the this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). You can see that the `[selectItem]` directive is set in `app-task` component and the `dts-select-container` is in the `dragndrop` component. + +### Why is my `selectItem` directive not selecting why I click on it? + +If you are using the `selectItem` within a nested component then your mousedown/up events might being captured by another directive or component in your code. For example if you are using this libary with Angular CDK's DragDropModule, the mouse events are captured by the `cdkDrag` directive, [see here](https://github.com/angular/components/pull/19674). ## Want to contribute? diff --git a/cypress/integration/drag-and-drop.spec.ts b/cypress/integration/drag-and-drop.spec.ts new file mode 100644 index 0000000..ea90f16 --- /dev/null +++ b/cypress/integration/drag-and-drop.spec.ts @@ -0,0 +1,80 @@ +import { DEFAULT_CONFIG } from '../../projects/ngx-drag-to-select/src/lib/config'; + +import { + disableSelection, + disableSelectOnDrag, + enableSelectMode, + getDesktopExample, + getDoingList, + getDragAndDropExample, + getTodoList, + shouldBeInvisible, + shouldBeVisible, + toggleItem, +} from '../support/utils'; + +const SELECTED_CLASS = DEFAULT_CONFIG.selectedClass; + +describe('Drag And Drop', () => { + beforeEach(() => { + cy.visit('/'); + }); + + describe('Select on Drag', () => { + it('should start new selection', () => { + getDragAndDropExample().within(() => { + getTodoList() + .dispatch('mousedown', 'topLeft', { button: 0 }) + .getSelectItem(2) + .dispatch('mousemove') + .dispatch('mouseup'); + + cy.get('.selected').should('have.length', 3); + }); + }); + + it('should drag to new list', () => { + getDragAndDropExample().within(() => { + getTodoList() + // Select First 3 items in list + .dispatch('mousedown', 'topLeft', { button: 0 }) + .getSelectItem(2) + .dispatch('mousemove') + .dispatch('mouseup') + // Click on second item + .getSelectItem(1) + .dispatch('mousedown', { button: 0 }) + // Drag to Select Item in other list + .getSelectItem(5) + .wait(16) + .dispatch('mousemove') + .dispatch('mousemove') + .dispatch('mouseup'); + + getDoingList().within(() => { + cy.get('app-task').should('have.length', 5); + }); + }); + }); + + it('should reorder within list', () => { + getDragAndDropExample().within(() => { + getTodoList() + .dispatch('mousedown', 'bottomRight', { button: 0 }) + .getSelectItem(2) + .dispatch('mousemove') + .dispatch('mouseup') + .getSelectItem(3) + .dispatch('mousedown', { button: 0 }) + .getSelectItem(0) + .wait(16) + .dispatch('mousemove') + .dispatch('mousemove') + .dispatch('mouseup'); + + cy.get('app-task').eq(0).should('contain', 'Open Ticket #1'); + cy.get('app-task').eq(1).should('contain', 'Open Ticket #2'); + }); + }); + }); +}); diff --git a/cypress/support/utils.ts b/cypress/support/utils.ts index a881d32..444b35a 100644 --- a/cypress/support/utils.ts +++ b/cypress/support/utils.ts @@ -28,6 +28,10 @@ export const getMobileExample = () => { return cy.get('[data-cy="mobile"]'); }; +export const getDragAndDropExample = () => { + return cy.get('[data-cy="drag-and-drop"]'); +}; + export const getSelectCount = () => { return cy.get('[data-cy="select-count"]'); }; @@ -48,6 +52,18 @@ export const getClearButton = () => { return cy.get('[data-cy="clearSelection"]'); }; +export const getTodoList = () => { + return cy.get('[data-cy="todo-list"]'); +}; + +export const getDoingList = () => { + return cy.get('[data-cy="doing-list"]'); +}; + +export const getDoneList = () => { + return cy.get('[data-cy="done-list"]'); +}; + export const disableSelectOnDrag = () => { return cy.get('[data-cy="selectOnDrag"]').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 feccd27..b54b196 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 @@ -8,14 +8,11 @@ import { Renderer2, ViewChild, NgZone, - ContentChildren, - QueryList, HostBinding, AfterViewInit, PLATFORM_ID, Inject, AfterContentInit, - InjectionToken, } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; @@ -33,7 +30,6 @@ import { share, withLatestFrom, distinctUntilChanged, - observeOn, startWith, concatMapTo, first, @@ -410,6 +406,7 @@ export class SelectContainerComponent } private _onMouseDown(event: MouseEvent) { + console.log('Mouse Down'); if (this.shortcuts.disableSelection(event) || this.disabled) { return; } diff --git a/src/app/app.component.html b/src/app/app.component.html index f60e41f..c6f18f4 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -83,7 +83,7 @@

-
+

Drag & Drop Demo SOURCE diff --git a/src/app/dragndrop/dragndrop.component.html b/src/app/dragndrop/dragndrop.component.html index b766471..7c44eea 100644 --- a/src/app/dragndrop/dragndrop.component.html +++ b/src/app/dragndrop/dragndrop.component.html @@ -1,9 +1,9 @@ - +
- - - + + +
\ No newline at end of file diff --git a/src/app/dragndrop/task-list/task-list.component.scss b/src/app/dragndrop/task-list/task-list.component.scss index ba84a72..23030ba 100644 --- a/src/app/dragndrop/task-list/task-list.component.scss +++ b/src/app/dragndrop/task-list/task-list.component.scss @@ -30,7 +30,7 @@ :host(.item-dragging) { ::ng-deep { mat-card.selected:not(.cdk-drag-placeholder) { - opacity: 0.3; + display: none; //opacity: 0.3; } } } diff --git a/src/app/dragndrop/task-list/task-list.component.ts b/src/app/dragndrop/task-list/task-list.component.ts index 7ee1479..52e8ee2 100644 --- a/src/app/dragndrop/task-list/task-list.component.ts +++ b/src/app/dragndrop/task-list/task-list.component.ts @@ -15,7 +15,7 @@ export class TaskListComponent { @HostBinding('class.item-dragging') dragging = false; - constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent) {} + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} dragStarted(ev: CdkDragStart, index: number): void { this.dragging = !!ev.source._dragRef; @@ -46,7 +46,7 @@ export class TaskListComponent { const spliceIntoIndex = event.currentIndex; this.tasks.splice(spliceIntoIndex, 0, ...this.container.selectedItems); - this.container.update(); + setTimeout(() => this.container.update()); // if (event.previousContainer === event.container) { // for (const item of this.container.selectedItems) { // moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); diff --git a/src/app/dragndrop/task/task.component.html b/src/app/dragndrop/task/task.component.html index 4ca1a8a..423ac50 100644 --- a/src/app/dragndrop/task/task.component.html +++ b/src/app/dragndrop/task/task.component.html @@ -1,4 +1,6 @@ + data-cy="selected-item" + [dtsSelectItem]="item" + (click)="select()"> {{ item }} \ No newline at end of file diff --git a/src/app/dragndrop/task/task.component.ts b/src/app/dragndrop/task/task.component.ts index b6fe6c0..b2279c9 100644 --- a/src/app/dragndrop/task/task.component.ts +++ b/src/app/dragndrop/task/task.component.ts @@ -9,6 +9,10 @@ import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens }) export class TaskComponent { @Input() item = ''; - // @Input() selectedItems = []; - constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() @SkipSelf() public container: SelectContainerComponent) {} + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} + + select() { + this.container.clearSelection(); + this.container.selectItems((it) => it === this.item); + } } From f14b9f921777ee8176ddaa203411a72fb36f96a7 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Mon, 9 Nov 2020 11:30:55 -0500 Subject: [PATCH 07/19] fix(lib): new Token was not export with the public api --- projects/ngx-drag-to-select/src/public_api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/ngx-drag-to-select/src/public_api.ts b/projects/ngx-drag-to-select/src/public_api.ts index e1d010e..aab3305 100644 --- a/projects/ngx-drag-to-select/src/public_api.ts +++ b/projects/ngx-drag-to-select/src/public_api.ts @@ -5,3 +5,4 @@ export * from './lib/drag-to-select.module'; export * from './lib/select-container.component'; export * from './lib/select-item.directive'; +export { DTS_SELECT_CONTAINER } from './lib/tokens'; From f21b63e4259ca41afb10b220a6e821b71e434ff2 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Tue, 10 Nov 2020 19:14:09 -0500 Subject: [PATCH 08/19] refactor(app): renamed drag-and-drop docs: updated grammar --- README.md | 15 ++-- projects/ngx-drag-to-select/src/lib/models.ts | 3 +- .../src/lib/select-container.component.ts | 11 +-- projects/ngx-drag-to-select/src/lib/tokens.ts | 3 +- src/app/app.component.html | 4 +- src/app/app.module.ts | 8 +-- src/app/dragndrop/dragndrop.component.html | 9 --- src/app/dragndrop/dragndrop.component.scss | 20 ------ src/app/dragndrop/dragndrop.component.ts | 14 ---- .../task-list/task-list.component.html | 19 ----- .../task-list/task-list.component.scss | 69 ------------------- .../task-list/task-list.component.ts | 60 ---------------- src/app/dragndrop/task/task.component.html | 6 -- src/app/dragndrop/task/task.component.scss | 8 --- src/app/dragndrop/task/task.component.ts | 18 ----- 15 files changed, 19 insertions(+), 248 deletions(-) delete mode 100644 src/app/dragndrop/dragndrop.component.html delete mode 100644 src/app/dragndrop/dragndrop.component.scss delete mode 100644 src/app/dragndrop/dragndrop.component.ts delete mode 100644 src/app/dragndrop/task-list/task-list.component.html delete mode 100644 src/app/dragndrop/task-list/task-list.component.scss delete mode 100644 src/app/dragndrop/task-list/task-list.component.ts delete mode 100644 src/app/dragndrop/task/task.component.html delete mode 100644 src/app/dragndrop/task/task.component.scss delete mode 100644 src/app/dragndrop/task/task.component.ts diff --git a/README.md b/README.md index efbfd06..95330d3 100644 --- a/README.md +++ b/README.md @@ -553,25 +553,26 @@ Here we listen for `scroll` on the `div` and call `container.update()` in case t In order not to kill the performance, because the `scroll` event is called many many times, you may want to **throttle** it to only call `update` every 16ms or so. -### Does the `selectItem` directive need to be a direct child of the `dts-select-container`? +### Does the `dtsSelectItem` directive need to be a direct child of the `dts-select-container`? -As of version `4.1.0`, an injetion token is used to pass the SelectContainerComponent parent to the directive. You can use this in any component nested within the `dts-select-container`. +As of version `4.1.0`, an injection token is used to pass the `SelectContainerComponent` parent to the directive. You can use this in any component nested within the `dts-select-container`. -```javascript +```ts import { DTS_SELECT_CONTAINER } from 'ngx-drag-to-select'; @Component({...}) export class TaskListComponent { constructor( @Inject(DTS_SELECT_CONTAINER) @Optional() - public container: SelectContainerComponent) {} + public container: SelectContainerComponent + ) {} ``` -You can see an example of the this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). You can see that the `[selectItem]` directive is set in `app-task` component and the `dts-select-container` is in the `dragndrop` component. +You can find an example of this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). -### Why is my `selectItem` directive not selecting why I click on it? +### Why is my `dtsSelectItem` directive not selecting when I click on it? -If you are using the `selectItem` within a nested component then your mousedown/up events might being captured by another directive or component in your code. For example if you are using this libary with Angular CDK's DragDropModule, the mouse events are captured by the `cdkDrag` directive, [see here](https://github.com/angular/components/pull/19674). +If you are using the `dtsSelectItem` within a nested component then your mousedown/up events might being captured by another directive or component in your code. For example if you are using this libary with Angular CDK's DragDropModule, the mouse events are captured by the `cdkDrag` directive, [see here](https://github.com/angular/components/pull/19674). ## Want to contribute? diff --git a/projects/ngx-drag-to-select/src/lib/models.ts b/projects/ngx-drag-to-select/src/lib/models.ts index 582da6d..5ebf228 100644 --- a/projects/ngx-drag-to-select/src/lib/models.ts +++ b/projects/ngx-drag-to-select/src/lib/models.ts @@ -1,4 +1,3 @@ -import { ComponentType } from '@angular/cdk/portal'; import { Observable } from 'rxjs'; import { SelectItemDirective } from './select-item.directive'; @@ -69,3 +68,5 @@ export interface SelectContainer { register(item: T): void; unregister(item: T): void; } + +export type ComponentType = new (...args: any[]) => T; 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 b54b196..1afd0d5 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 @@ -12,7 +12,6 @@ import { AfterViewInit, PLATFORM_ID, Inject, - AfterContentInit, } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; @@ -38,7 +37,7 @@ import { import { SelectItemDirective, SELECT_ITEM_INSTANCE } from './select-item.directive'; import { ShortcutService } from './shortcut.service'; -import { createSelectBox, whenSelectBoxVisible, distinctKeyEvents } from './operators'; +import { createSelectBox, whenSelectBoxVisible } from './operators'; import { Action, @@ -82,9 +81,7 @@ import { DTS_SELECT_CONTAINER } from './tokens'; styleUrls: ['./select-container.component.scss'], providers: [{ provide: DTS_SELECT_CONTAINER, useExisting: SelectContainerComponent }], }) -export class SelectContainerComponent - implements AfterViewInit, OnDestroy, AfterContentInit, SelectContainer -{ +export class SelectContainerComponent implements AfterViewInit, OnDestroy, SelectContainer { host: SelectContainerHost; selectBoxStyles$: Observable>; selectBoxClasses$: Observable<{ [key: string]: boolean }>; @@ -265,10 +262,6 @@ export class SelectContainerComponent } } - ngAfterContentInit() { - // removed query list, maybe remove this too - } - updateSelectableItems() { this._selectableItems = Array.from(this._registry); this._selectableItemsNative = this._selectableItems.map((directive) => directive.nativeElememnt); diff --git a/projects/ngx-drag-to-select/src/lib/tokens.ts b/projects/ngx-drag-to-select/src/lib/tokens.ts index 429828f..87d3856 100644 --- a/projects/ngx-drag-to-select/src/lib/tokens.ts +++ b/projects/ngx-drag-to-select/src/lib/tokens.ts @@ -1,6 +1,5 @@ -import { ComponentType } from '@angular/cdk/portal'; import { InjectionToken } from '@angular/core'; -import { DragToSelectConfig, SelectContainer } from './models'; +import { DragToSelectConfig, SelectContainer, ComponentType } from './models'; export const CONFIG = new InjectionToken('DRAG_TO_SELECT_CONFIG'); export const USER_CONFIG = new InjectionToken('USER_CONFIG'); diff --git a/src/app/app.component.html b/src/app/app.component.html index c6f18f4..a0594a1 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -86,10 +86,10 @@

Drag & Drop Demo SOURCE + href="https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/drag-and-drop">SOURCE

- +
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0333950..e0b8e68 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -22,9 +22,9 @@ import { AppComponent } from './app.component'; import { FooterComponent } from './footer/footer.component'; import { HeaderComponent } from './header/header.component'; import { PhoneComponent } from './phone/phone.component'; -import { DragNDropComponent } from './dragndrop/dragndrop.component'; -import { TaskComponent } from './dragndrop/task/task.component'; -import { TaskListComponent } from './dragndrop/task-list/task-list.component'; +import { DragAndDropComponent } from './drag-and-drop/drag-and-drop.component'; +import { TaskComponent } from './drag-and-drop/task/task.component'; +import { TaskListComponent } from './drag-and-drop/task-list/task-list.component'; import { KeyComponent, @@ -57,7 +57,7 @@ const MATERIAL_MODULES = [ FooterComponent, HeaderComponent, PhoneComponent, - DragNDropComponent, + DragAndDropComponent, TaskComponent, TaskListComponent, ], diff --git a/src/app/dragndrop/dragndrop.component.html b/src/app/dragndrop/dragndrop.component.html deleted file mode 100644 index 7c44eea..0000000 --- a/src/app/dragndrop/dragndrop.component.html +++ /dev/null @@ -1,9 +0,0 @@ - -
- - - - - -
-
\ No newline at end of file diff --git a/src/app/dragndrop/dragndrop.component.scss b/src/app/dragndrop/dragndrop.component.scss deleted file mode 100644 index 4497a6f..0000000 --- a/src/app/dragndrop/dragndrop.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.list-wrapper { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 1rem; - min-height: 400px; -} - -.cdk-drag-preview { - box-sizing: border-box; - border-radius: 4px; - box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); -} - -.cdk-drag-placeholder { - opacity: 0; -} - -.cdk-drag-animating { - transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); -} diff --git a/src/app/dragndrop/dragndrop.component.ts b/src/app/dragndrop/dragndrop.component.ts deleted file mode 100644 index e444307..0000000 --- a/src/app/dragndrop/dragndrop.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component } from '@angular/core'; -import { CdkDragDrop, CdkDragStart, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; - -@Component({ - selector: 'app-dragndrop', - templateUrl: './dragndrop.component.html', - styleUrls: ['./dragndrop.component.scss'], -}) -export class DragNDropComponent { - todo = ['Next Task', 'Very Important Item', 'Open Ticket #1', 'Open Ticket #2', 'Not Important Task']; - doing = ['High Priority Task', 'In Progress Item']; - done = ['Completed Item']; - selectedItems: any[] = []; -} diff --git a/src/app/dragndrop/task-list/task-list.component.html b/src/app/dragndrop/task-list/task-list.component.html deleted file mode 100644 index f432a2f..0000000 --- a/src/app/dragndrop/task-list/task-list.component.html +++ /dev/null @@ -1,19 +0,0 @@ -

{{title}}

- -
- -
- {{ selected }} -
-
- {{ selected }} -
-
-
\ No newline at end of file diff --git a/src/app/dragndrop/task-list/task-list.component.scss b/src/app/dragndrop/task-list/task-list.component.scss deleted file mode 100644 index 23030ba..0000000 --- a/src/app/dragndrop/task-list/task-list.component.scss +++ /dev/null @@ -1,69 +0,0 @@ -:host { - background-color: #ebecf0; - border-radius: 3px; - box-sizing: border-box; - display: flex; - flex-direction: column; - max-height: 100%; - position: relative; - white-space: normal; -} - -.list-header { - padding: 0.5rem; -} - -.list-cards { - margin: 0 4px; - padding: 0 4px; - display: grid; - grid-template-columns: 1fr; - grid-auto-rows: min-content; - gap: 0.4rem; - height: -webkit-fill-available; -} - -.list-cards.cdk-drop-list-dragging app-task:not(.cdk-drag-placeholder) { - transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); -} - -:host(.item-dragging) { - ::ng-deep { - mat-card.selected:not(.cdk-drag-placeholder) { - display: none; //opacity: 0.3; - } - } -} - -.drag-placeholder-container { - display: flex; - flex-flow: column; - gap: 0.5rem; - mat-card { - opacity: 0.5; - } -} - -.drag-preview-container { - gap: 0.5rem; - position: relative; - - mat-card { - position: absolute; - width: 200px; - top: -10px; - left: -25px; - } - mat-card:nth-child(2) { - left: -40px; - top: -10px; - transform: rotate(-1deg); - z-index: -1; - } - mat-card:nth-child(3) { - top: -5px; - left: -20px; - transform: rotate(1deg); - z-index: -2; - } -} diff --git a/src/app/dragndrop/task-list/task-list.component.ts b/src/app/dragndrop/task-list/task-list.component.ts deleted file mode 100644 index 52e8ee2..0000000 --- a/src/app/dragndrop/task-list/task-list.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { CdkDragStart, CdkDragDrop } from '@angular/cdk/drag-drop'; -import { Component, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; -import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; -import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; - -@Component({ - selector: 'app-task-list', - styleUrls: ['./task-list.component.scss'], - templateUrl: './task-list.component.html', -}) -export class TaskListComponent { - @Input() title = 'Tasks'; - @Input() tasks = []; - - @HostBinding('class.item-dragging') - dragging = false; - - constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} - - dragStarted(ev: CdkDragStart, index: number): void { - this.dragging = !!ev.source._dragRef; - this.container.selectItems((item) => { - return item === this.tasks[index]; - }); - } - - dragEnded(): void { - this.dragging = null; - } - - dropped(event: CdkDragDrop): void { - this.dragging = null; - const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); - indices.sort().reverse(); - indices.forEach((idx) => { - this.tasks.splice(idx, 1); - }); - - setTimeout(() => this.container.clearSelection()); - } - - drop(event: CdkDragDrop) { - const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); - console.log('Selected', indices); - - const spliceIntoIndex = event.currentIndex; - this.tasks.splice(spliceIntoIndex, 0, ...this.container.selectedItems); - - setTimeout(() => this.container.update()); - // if (event.previousContainer === event.container) { - // for (const item of this.container.selectedItems) { - // moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); - // } - // } else { - // for (const item of this.container.selectedItems) { - // transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); - // } - // } - } -} diff --git a/src/app/dragndrop/task/task.component.html b/src/app/dragndrop/task/task.component.html deleted file mode 100644 index 423ac50..0000000 --- a/src/app/dragndrop/task/task.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - {{ item }} - \ No newline at end of file diff --git a/src/app/dragndrop/task/task.component.scss b/src/app/dragndrop/task/task.component.scss deleted file mode 100644 index 3d2d13e..0000000 --- a/src/app/dragndrop/task/task.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -:host { - height: min-content; -} -mat-card { - &.selected { - border: 1px solid blue; - } -} diff --git a/src/app/dragndrop/task/task.component.ts b/src/app/dragndrop/task/task.component.ts deleted file mode 100644 index b2279c9..0000000 --- a/src/app/dragndrop/task/task.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, Inject, Input, Optional, SkipSelf } from '@angular/core'; -import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; -import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; - -@Component({ - selector: 'app-task', - styleUrls: ['./task.component.scss'], - templateUrl: './task.component.html', -}) -export class TaskComponent { - @Input() item = ''; - constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} - - select() { - this.container.clearSelection(); - this.container.selectItems((it) => it === this.item); - } -} From 22f82aa64c791b78143a9a3ba52af29b0a57b69e Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Tue, 10 Nov 2020 19:14:53 -0500 Subject: [PATCH 09/19] refactor(app): forgot to add new files --- .../drag-and-drop.component.html | 7 ++ .../drag-and-drop.component.scss | 20 ++++++ .../drag-and-drop/drag-and-drop.component.ts | 13 ++++ .../task-list/task-list.component.html | 19 +++++ .../task-list/task-list.component.scss | 69 +++++++++++++++++++ .../task-list/task-list.component.ts | 60 ++++++++++++++++ .../drag-and-drop/task/task.component.html | 6 ++ .../drag-and-drop/task/task.component.scss | 8 +++ src/app/drag-and-drop/task/task.component.ts | 18 +++++ 9 files changed, 220 insertions(+) create mode 100644 src/app/drag-and-drop/drag-and-drop.component.html create mode 100644 src/app/drag-and-drop/drag-and-drop.component.scss create mode 100644 src/app/drag-and-drop/drag-and-drop.component.ts create mode 100644 src/app/drag-and-drop/task-list/task-list.component.html create mode 100644 src/app/drag-and-drop/task-list/task-list.component.scss create mode 100644 src/app/drag-and-drop/task-list/task-list.component.ts create mode 100644 src/app/drag-and-drop/task/task.component.html create mode 100644 src/app/drag-and-drop/task/task.component.scss create mode 100644 src/app/drag-and-drop/task/task.component.ts diff --git a/src/app/drag-and-drop/drag-and-drop.component.html b/src/app/drag-and-drop/drag-and-drop.component.html new file mode 100644 index 0000000..5fde904 --- /dev/null +++ b/src/app/drag-and-drop/drag-and-drop.component.html @@ -0,0 +1,7 @@ + +
+ + + +
+
\ No newline at end of file diff --git a/src/app/drag-and-drop/drag-and-drop.component.scss b/src/app/drag-and-drop/drag-and-drop.component.scss new file mode 100644 index 0000000..4497a6f --- /dev/null +++ b/src/app/drag-and-drop/drag-and-drop.component.scss @@ -0,0 +1,20 @@ +.list-wrapper { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 1rem; + min-height: 400px; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/src/app/drag-and-drop/drag-and-drop.component.ts b/src/app/drag-and-drop/drag-and-drop.component.ts new file mode 100644 index 0000000..9d81538 --- /dev/null +++ b/src/app/drag-and-drop/drag-and-drop.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-drag-and-drop', + templateUrl: './drag-and-drop.component.html', + styleUrls: ['./drag-and-drop.component.scss'], +}) +export class DragAndDropComponent { + todo = ['Next Task', 'Very Important Item', 'Open Ticket #1', 'Open Ticket #2', 'Not Important Task']; + doing = ['High Priority Task', 'In Progress Item']; + done = ['Completed Item']; + selectedItems: any[] = []; +} diff --git a/src/app/drag-and-drop/task-list/task-list.component.html b/src/app/drag-and-drop/task-list/task-list.component.html new file mode 100644 index 0000000..f432a2f --- /dev/null +++ b/src/app/drag-and-drop/task-list/task-list.component.html @@ -0,0 +1,19 @@ +

{{title}}

+ +
+ +
+ {{ selected }} +
+
+ {{ selected }} +
+
+
\ No newline at end of file diff --git a/src/app/drag-and-drop/task-list/task-list.component.scss b/src/app/drag-and-drop/task-list/task-list.component.scss new file mode 100644 index 0000000..23030ba --- /dev/null +++ b/src/app/drag-and-drop/task-list/task-list.component.scss @@ -0,0 +1,69 @@ +:host { + background-color: #ebecf0; + border-radius: 3px; + box-sizing: border-box; + display: flex; + flex-direction: column; + max-height: 100%; + position: relative; + white-space: normal; +} + +.list-header { + padding: 0.5rem; +} + +.list-cards { + margin: 0 4px; + padding: 0 4px; + display: grid; + grid-template-columns: 1fr; + grid-auto-rows: min-content; + gap: 0.4rem; + height: -webkit-fill-available; +} + +.list-cards.cdk-drop-list-dragging app-task:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +:host(.item-dragging) { + ::ng-deep { + mat-card.selected:not(.cdk-drag-placeholder) { + display: none; //opacity: 0.3; + } + } +} + +.drag-placeholder-container { + display: flex; + flex-flow: column; + gap: 0.5rem; + mat-card { + opacity: 0.5; + } +} + +.drag-preview-container { + gap: 0.5rem; + position: relative; + + mat-card { + position: absolute; + width: 200px; + top: -10px; + left: -25px; + } + mat-card:nth-child(2) { + left: -40px; + top: -10px; + transform: rotate(-1deg); + z-index: -1; + } + mat-card:nth-child(3) { + top: -5px; + left: -20px; + transform: rotate(1deg); + z-index: -2; + } +} diff --git a/src/app/drag-and-drop/task-list/task-list.component.ts b/src/app/drag-and-drop/task-list/task-list.component.ts new file mode 100644 index 0000000..7fcb27c --- /dev/null +++ b/src/app/drag-and-drop/task-list/task-list.component.ts @@ -0,0 +1,60 @@ +import { CdkDragStart, CdkDragDrop } from '@angular/cdk/drag-drop'; +import { Component, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; +import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; +import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; + +@Component({ + selector: 'app-task-list', + styleUrls: ['./task-list.component.scss'], + templateUrl: './task-list.component.html', +}) +export class TaskListComponent { + @Input() title = 'Tasks'; + @Input() tasks = []; + + @HostBinding('class.item-dragging') + dragging = false; + + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} + + dragStarted(ev: CdkDragStart, index: number): void { + this.dragging = !!ev.source._dragRef; + this.container.selectItems((item) => { + return item === this.tasks[index]; + }); + } + + dragEnded(): void { + this.dragging = null; + } + + dropped(event: CdkDragDrop): void { + this.dragging = null; + const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); + indices.sort().reverse(); + indices.forEach((idx) => { + this.tasks.splice(idx, 1); + }); + + setTimeout(() => this.container.clearSelection()); + } + + drop(event: CdkDragDrop) { + const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); + console.log('Selected', indices); + + const spliceIntoIndex = event.currentIndex; + this.tasks.splice(spliceIntoIndex, 0, ...this.container.selectedItems); + + /** + * Caution! + * When drop event is within the same drop-list the bounding box of + * drag events might not update to there new positions. Use the + * `update` method from the `SelectContainerComponent` to force the + * bounding boxes to be recalculated. + * This is put behind `setTimeout` because we have to wait until the next + * frame before angular change detection has updated the dom. + * */ + setTimeout(() => this.container.update()); + } +} diff --git a/src/app/drag-and-drop/task/task.component.html b/src/app/drag-and-drop/task/task.component.html new file mode 100644 index 0000000..423ac50 --- /dev/null +++ b/src/app/drag-and-drop/task/task.component.html @@ -0,0 +1,6 @@ + + {{ item }} + \ No newline at end of file diff --git a/src/app/drag-and-drop/task/task.component.scss b/src/app/drag-and-drop/task/task.component.scss new file mode 100644 index 0000000..3d2d13e --- /dev/null +++ b/src/app/drag-and-drop/task/task.component.scss @@ -0,0 +1,8 @@ +:host { + height: min-content; +} +mat-card { + &.selected { + border: 1px solid blue; + } +} diff --git a/src/app/drag-and-drop/task/task.component.ts b/src/app/drag-and-drop/task/task.component.ts new file mode 100644 index 0000000..b2279c9 --- /dev/null +++ b/src/app/drag-and-drop/task/task.component.ts @@ -0,0 +1,18 @@ +import { Component, Inject, Input, Optional, SkipSelf } from '@angular/core'; +import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; +import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; + +@Component({ + selector: 'app-task', + styleUrls: ['./task.component.scss'], + templateUrl: './task.component.html', +}) +export class TaskComponent { + @Input() item = ''; + constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} + + select() { + this.container.clearSelection(); + this.container.selectItems((it) => it === this.item); + } +} From 7aaf9344c41469634cf677672fbf13f8d40ba8bd Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Wed, 11 Nov 2020 08:57:42 -0500 Subject: [PATCH 10/19] style: updated cypress comments style: update whitespace --- README.md | 1 + cypress/integration/drag-and-drop.spec.ts | 19 ++++--------------- .../src/lib/select-container.component.ts | 1 + 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 95330d3..e9ddf93 100644 --- a/README.md +++ b/README.md @@ -566,6 +566,7 @@ export class TaskListComponent { @Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent ) {} +} ``` You can find an example of this in the [drag and drop example](https://github.com/d3lm/ngx-drag-to-select/blob/master/src/app/dragndrop). diff --git a/cypress/integration/drag-and-drop.spec.ts b/cypress/integration/drag-and-drop.spec.ts index ea90f16..c0ffcd7 100644 --- a/cypress/integration/drag-and-drop.spec.ts +++ b/cypress/integration/drag-and-drop.spec.ts @@ -1,17 +1,6 @@ import { DEFAULT_CONFIG } from '../../projects/ngx-drag-to-select/src/lib/config'; -import { - disableSelection, - disableSelectOnDrag, - enableSelectMode, - getDesktopExample, - getDoingList, - getDragAndDropExample, - getTodoList, - shouldBeInvisible, - shouldBeVisible, - toggleItem, -} from '../support/utils'; +import { getDoingList, getDragAndDropExample, getTodoList } from '../support/utils'; const SELECTED_CLASS = DEFAULT_CONFIG.selectedClass; @@ -36,15 +25,15 @@ describe('Drag And Drop', () => { it('should drag to new list', () => { getDragAndDropExample().within(() => { getTodoList() - // Select First 3 items in list + // select first 3 items in list .dispatch('mousedown', 'topLeft', { button: 0 }) .getSelectItem(2) .dispatch('mousemove') .dispatch('mouseup') - // Click on second item + // click on second item .getSelectItem(1) .dispatch('mousedown', { button: 0 }) - // Drag to Select Item in other list + // drag to SelectItem in other list .getSelectItem(5) .wait(16) .dispatch('mousemove') 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 1afd0d5..d8585ab 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 @@ -295,6 +295,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, Selec this._registry.add(item); this.updateSelectableItems(); } + unregister(item: SelectItemDirective) { this._registry.delete(item); this.updateSelectableItems(); From 69b9cde19b951ced208228fa900b968fef6ebb74 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Thu, 12 Nov 2020 10:47:03 -0500 Subject: [PATCH 11/19] refactor(app): moved drag-and-drop above mobile and hides style(app): linting some scss --- src/app/app.component.html | 21 ++++++++++--------- src/app/app.module.ts | 1 - .../task-list/task-list.component.scss | 3 +++ .../drag-and-drop/task/task.component.scss | 5 ++++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index a0594a1..2993545 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -73,24 +73,25 @@

Meta Information

-
+

- Mobile Demo SOURCE + Drag & Drop Demo + SOURCE

-
- -
+ +
-
+

- Drag & Drop Demo SOURCE + Mobile Demo + SOURCE

- - +
+ +
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e0b8e68..2f00890 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -10,7 +10,6 @@ import { MatTabsModule } from '@angular/material/tabs'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; - import { DragDropModule } from '@angular/cdk/drag-drop'; import { BrowserModule } from '@angular/platform-browser'; diff --git a/src/app/drag-and-drop/task-list/task-list.component.scss b/src/app/drag-and-drop/task-list/task-list.component.scss index 23030ba..d1210eb 100644 --- a/src/app/drag-and-drop/task-list/task-list.component.scss +++ b/src/app/drag-and-drop/task-list/task-list.component.scss @@ -39,6 +39,7 @@ display: flex; flex-flow: column; gap: 0.5rem; + mat-card { opacity: 0.5; } @@ -54,12 +55,14 @@ top: -10px; left: -25px; } + mat-card:nth-child(2) { left: -40px; top: -10px; transform: rotate(-1deg); z-index: -1; } + mat-card:nth-child(3) { top: -5px; left: -20px; diff --git a/src/app/drag-and-drop/task/task.component.scss b/src/app/drag-and-drop/task/task.component.scss index 3d2d13e..8346b9d 100644 --- a/src/app/drag-and-drop/task/task.component.scss +++ b/src/app/drag-and-drop/task/task.component.scss @@ -1,8 +1,11 @@ :host { height: min-content; } + mat-card { + border: 1px solid transparent; + &.selected { - border: 1px solid blue; + border: 1px solid #2196f3; } } From 56a1f77f985f5ae0c62f6d56157576ae60d1a400 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Thu, 12 Nov 2020 11:51:19 -0500 Subject: [PATCH 12/19] fix(app): fixed cypress test, error with test --- cypress/integration/drag-and-drop.spec.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cypress/integration/drag-and-drop.spec.ts b/cypress/integration/drag-and-drop.spec.ts index c0ffcd7..8567e5d 100644 --- a/cypress/integration/drag-and-drop.spec.ts +++ b/cypress/integration/drag-and-drop.spec.ts @@ -49,14 +49,13 @@ describe('Drag And Drop', () => { it('should reorder within list', () => { getDragAndDropExample().within(() => { getTodoList() - .dispatch('mousedown', 'bottomRight', { button: 0 }) - .getSelectItem(2) + .dispatch('mousedown', 'topRight', { button: 0 }) + .getSelectItem(1) .dispatch('mousemove') .dispatch('mouseup') - .getSelectItem(3) - .dispatch('mousedown', { button: 0 }) .getSelectItem(0) - .wait(16) + .dispatch('mousedown', 'bottom', { button: 0 }) + .getSelectItem(4) .dispatch('mousemove') .dispatch('mousemove') .dispatch('mouseup'); From 6960d4c1c92ebb16097adb22146859b06b6681b4 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Fri, 13 Nov 2020 21:47:14 -0500 Subject: [PATCH 13/19] fix(app): visual issues with hidden placeholders --- .../task-list/task-list.component.html | 3 +- .../task-list/task-list.component.scss | 8 ++--- .../task-list/task-list.component.ts | 31 ++++++++++++++++--- .../drag-and-drop/task/task.component.html | 1 - .../drag-and-drop/task/task.component.scss | 11 ++++--- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/app/drag-and-drop/task-list/task-list.component.html b/src/app/drag-and-drop/task-list/task-list.component.html index f432a2f..6a726fe 100644 --- a/src/app/drag-and-drop/task-list/task-list.component.html +++ b/src/app/drag-and-drop/task-list/task-list.component.html @@ -8,7 +8,8 @@

{{title}}

+ (cdkDragDropped)="dropped($event)" + [dtsSelectItem]="item">
{{ selected }}
diff --git a/src/app/drag-and-drop/task-list/task-list.component.scss b/src/app/drag-and-drop/task-list/task-list.component.scss index d1210eb..4691402 100644 --- a/src/app/drag-and-drop/task-list/task-list.component.scss +++ b/src/app/drag-and-drop/task-list/task-list.component.scss @@ -28,10 +28,8 @@ } :host(.item-dragging) { - ::ng-deep { - mat-card.selected:not(.cdk-drag-placeholder) { - display: none; //opacity: 0.3; - } + app-task.selected:not(.cdk-drag-placeholder) { + display: none; } } @@ -39,6 +37,8 @@ display: flex; flex-flow: column; gap: 0.5rem; + min-height: 53px; + height: min-content; mat-card { opacity: 0.5; diff --git a/src/app/drag-and-drop/task-list/task-list.component.ts b/src/app/drag-and-drop/task-list/task-list.component.ts index 7fcb27c..f3eadda 100644 --- a/src/app/drag-and-drop/task-list/task-list.component.ts +++ b/src/app/drag-and-drop/task-list/task-list.component.ts @@ -1,5 +1,5 @@ import { CdkDragStart, CdkDragDrop } from '@angular/cdk/drag-drop'; -import { Component, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; +import { Component, ElementRef, HostBinding, Inject, Input, Optional, SkipSelf } from '@angular/core'; import { DTS_SELECT_CONTAINER } from 'projects/ngx-drag-to-select/src/lib/tokens'; import { SelectContainerComponent } from 'projects/ngx-drag-to-select/src/public_api'; @@ -15,16 +15,40 @@ export class TaskListComponent { @HostBinding('class.item-dragging') dragging = false; - constructor(@Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent) {} + // CdkDragModule only supports dragging a single dom element + private selectDomRefs: { parent: Node; node: Node }[] = []; + + constructor( + @Inject(DTS_SELECT_CONTAINER) @Optional() public container: SelectContainerComponent, + private element: ElementRef + ) {} dragStarted(ev: CdkDragStart, index: number): void { this.dragging = !!ev.source._dragRef; this.container.selectItems((item) => { return item === this.tasks[index]; }); + + /** + * We remove the selected elements from the DOM, because the + * CdkDragDropModule includes them when reordering even if they. + * are hidden. + **/ + this.selectDomRefs = []; + this.element.nativeElement.querySelectorAll('.selected').forEach((node: Node) => { + this.selectDomRefs.push({ parent: node.parentNode, node }); + node.parentNode.removeChild(node); + }); } dragEnded(): void { + /** + * Add the DOM elements back in because the Angular refs are still + * bound to them. + **/ + this.selectDomRefs.forEach(({ parent, node }) => { + parent.appendChild(node); + }); this.dragging = null; } @@ -40,9 +64,6 @@ export class TaskListComponent { } drop(event: CdkDragDrop) { - const indices = this.container.selectedItems.map((it) => event.previousContainer.data.findIndex((i) => it === i)); - console.log('Selected', indices); - const spliceIntoIndex = event.currentIndex; this.tasks.splice(spliceIntoIndex, 0, ...this.container.selectedItems); diff --git a/src/app/drag-and-drop/task/task.component.html b/src/app/drag-and-drop/task/task.component.html index 423ac50..1a52761 100644 --- a/src/app/drag-and-drop/task/task.component.html +++ b/src/app/drag-and-drop/task/task.component.html @@ -1,6 +1,5 @@ {{ item }} \ No newline at end of file diff --git a/src/app/drag-and-drop/task/task.component.scss b/src/app/drag-and-drop/task/task.component.scss index 8346b9d..4f9f40a 100644 --- a/src/app/drag-and-drop/task/task.component.scss +++ b/src/app/drag-and-drop/task/task.component.scss @@ -1,11 +1,12 @@ :host { - height: min-content; + display: block; + &.selected { + mat-card { + border: 1px solid #2196f3; + } + } } mat-card { border: 1px solid transparent; - - &.selected { - border: 1px solid #2196f3; - } } From 68bf5b751bed35a063d90571ac2bf8e087ba4411 Mon Sep 17 00:00:00 2001 From: Brian Kimball Date: Sun, 15 Nov 2020 21:27:52 -0500 Subject: [PATCH 14/19] style(app): trying to force tests to re-run --- src/app/drag-and-drop/task/task.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/drag-and-drop/task/task.component.scss b/src/app/drag-and-drop/task/task.component.scss index 4f9f40a..2b3cb9e 100644 --- a/src/app/drag-and-drop/task/task.component.scss +++ b/src/app/drag-and-drop/task/task.component.scss @@ -1,5 +1,6 @@ :host { display: block; + &.selected { mat-card { border: 1px solid #2196f3; From f0a11a946df9e776609085579087dfdf22f87747 Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Thu, 1 Dec 2022 12:02:07 +0100 Subject: [PATCH 15/19] test(lib): fixed bug when running all tests For some reason `itemDeselected` emits another event after Jest starts cleaning up the test component which resulted in a failing `expect` --- .../ngx-drag-to-select/src/lib/select-container.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/projects/ngx-drag-to-select/src/lib/select-container.spec.ts b/projects/ngx-drag-to-select/src/lib/select-container.spec.ts index 1761811..d60b407 100644 --- a/projects/ngx-drag-to-select/src/lib/select-container.spec.ts +++ b/projects/ngx-drag-to-select/src/lib/select-container.spec.ts @@ -4,7 +4,8 @@ import { By } from '@angular/platform-browser'; import { DragToSelectModule } from './drag-to-select.module'; import { SelectContainerComponent } from './select-container.component'; import { SelectItemDirective } from './select-item.directive'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; function triggerDomEvent( eventType: string, @@ -94,14 +95,16 @@ describe('SelectContainerComponent', () => { }); it('should update its selection when selectable items change', (done) => { + const done$ = new Subject(); selectContainerInstance.selectItems((item: SelectItemValue) => item.id === 1 || item.id === 2); - selectContainerInstance.itemDeselected.subscribe((item: SelectItemValue) => { + selectContainerInstance.itemDeselected.pipe(takeUntil(done$)).subscribe((item: SelectItemValue) => { expect(item).toEqual({ id: 1 }); }); selectContainerInstance.select.subscribe((items) => { expect(items).toEqual([{ id: 2 }]); + done$.next(); done(); }); From d695b8363eec6cc1c5993e1a0c970b7eecac5226 Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Fri, 2 Dec 2022 11:56:40 +0100 Subject: [PATCH 16/19] fix(app): disable selectOnClick and dragOverItems to make drag work --- src/app/drag-and-drop/drag-and-drop.component.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/drag-and-drop/drag-and-drop.component.html b/src/app/drag-and-drop/drag-and-drop.component.html index 5fde904..494cd58 100644 --- a/src/app/drag-and-drop/drag-and-drop.component.html +++ b/src/app/drag-and-drop/drag-and-drop.component.html @@ -1,7 +1,13 @@ - +
-
\ No newline at end of file +
From b5584858d7f288bf0807885aff8e03825875614f Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Fri, 2 Dec 2022 11:59:32 +0100 Subject: [PATCH 17/19] fix(lib): use internal memory of currentlyselected items If `selectedItems` has not two-way binding by an outside component then it will not contain a list of the currently selected items --- .../ngx-drag-to-select/src/lib/select-container.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d8585ab..8f567d1 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 @@ -299,7 +299,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, Selec unregister(item: SelectItemDirective) { this._registry.delete(item); this.updateSelectableItems(); - this._removeItem(item, this.selectedItems); + this._removeItem(item, this._selectedItems$.value); } update() { From 619943aa632875ade375332c19e0562baa08ff75 Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Fri, 2 Dec 2022 12:48:29 +0100 Subject: [PATCH 18/19] refactor(lib): remove debugging output --- .../ngx-drag-to-select/src/lib/select-container.component.ts | 1 - 1 file changed, 1 deletion(-) 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 8f567d1..8725d3a 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 @@ -400,7 +400,6 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, Selec } private _onMouseDown(event: MouseEvent) { - console.log('Mouse Down'); if (this.shortcuts.disableSelection(event) || this.disabled) { return; } From 40316b1198cf3eb9e9498fc95b493aeb2b4f0e1e Mon Sep 17 00:00:00 2001 From: Philip Odermatt Date: Fri, 2 Dec 2022 16:50:44 +0100 Subject: [PATCH 19/19] test: fixed 'element is currently animating' issue in headless e2e tests --- cypress/integration/drag-and-drop.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/integration/drag-and-drop.spec.ts b/cypress/integration/drag-and-drop.spec.ts index 8567e5d..0121358 100644 --- a/cypress/integration/drag-and-drop.spec.ts +++ b/cypress/integration/drag-and-drop.spec.ts @@ -36,8 +36,8 @@ describe('Drag And Drop', () => { // drag to SelectItem in other list .getSelectItem(5) .wait(16) - .dispatch('mousemove') - .dispatch('mousemove') + .dispatch('mousemove', { force: true }) + .dispatch('mousemove', { force: true }) .dispatch('mouseup'); getDoingList().within(() => { @@ -56,8 +56,8 @@ describe('Drag And Drop', () => { .getSelectItem(0) .dispatch('mousedown', 'bottom', { button: 0 }) .getSelectItem(4) - .dispatch('mousemove') - .dispatch('mousemove') + .dispatch('mousemove', { force: true }) + .dispatch('mousemove', { force: true }) .dispatch('mouseup'); cy.get('app-task').eq(0).should('contain', 'Open Ticket #1');