diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 5df46aa42222..2ff613f5b6aa 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -3299,6 +3299,36 @@ describe('CdkDrag', () => { cleanup(); })); + it('should be able to re-enable a disabled drop list', fakeAsync(() => { + const fixture = createComponent(DraggableInDropZone); + fixture.detectChanges(); + const dragItems = fixture.componentInstance.dragItems; + const tryDrag = () => { + const firstItem = dragItems.first; + const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect(); + dragElementViaMouse(fixture, firstItem.element.nativeElement, + thirdItemRect.left + 1, thirdItemRect.top + 1); + flush(); + fixture.detectChanges(); + }; + + expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())) + .toEqual(['Zero', 'One', 'Two', 'Three']); + + fixture.componentInstance.dropInstance.disabled = true; + fixture.detectChanges(); + tryDrag(); + + expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())) + .toEqual(['Zero', 'One', 'Two', 'Three']); + + fixture.componentInstance.dropInstance.disabled = false; + fixture.detectChanges(); + tryDrag(); + + expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim())) + .toEqual(['One', 'Two', 'Zero', 'Three']); + })); }); describe('in a connected drop container', () => { diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index ffc629ce19aa..465da130a034 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -104,7 +104,11 @@ export class CdkDropList implements AfterContentInit, OnDestroy { return this._disabled || (!!this._group && this._group.disabled); } set disabled(value: boolean) { - this._disabled = coerceBooleanProperty(value); + // Usually we sync the directive and ref state right before dragging starts, in order to have + // a single point of failure and to avoid having to use setters for everything. `disabled` is + // a special case, because it can prevent the `beforeStarted` event from firing, which can lock + // the user in a disabled state, so we also need to sync it as it's being set. + this._dropListRef.disabled = this._disabled = coerceBooleanProperty(value); } private _disabled = false; @@ -159,7 +163,7 @@ export class CdkDropList implements AfterContentInit, OnDestroy { return this.enterPredicate(drag.data, drop.data); }; - this._syncInputs(this._dropListRef); + this._setupInputSyncSubscription(this._dropListRef); this._handleEvents(this._dropListRef); CdkDropList._dropLists.push(this); @@ -251,7 +255,7 @@ export class CdkDropList implements AfterContentInit, OnDestroy { } /** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */ - private _syncInputs(ref: DropListRef) { + private _setupInputSyncSubscription(ref: DropListRef) { if (this._dir) { this._dir.change .pipe(startWith(this._dir.value), takeUntil(this._destroyed))