mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(drag-drop): not picking up handle that isn't a direct descendant (#13360)
This commit is contained in:
committed by
Joey Perrott
parent
fb8496dc45
commit
c38ebb63cb
@@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef} from '@angular/core';
|
||||
import {Directive, ElementRef, Inject, Optional} from '@angular/core';
|
||||
import {CDK_DRAG_PARENT} from './drag-parent';
|
||||
import {toggleNativeDragInteractions} from './drag-styling';
|
||||
|
||||
/** Handle that can be used to drag and CdkDrag instance. */
|
||||
@@ -17,7 +18,14 @@ import {toggleNativeDragInteractions} from './drag-styling';
|
||||
}
|
||||
})
|
||||
export class CdkDragHandle {
|
||||
constructor(public element: ElementRef<HTMLElement>) {
|
||||
/** Closest parent draggable instance. */
|
||||
_parentDrag: {} | undefined;
|
||||
|
||||
constructor(
|
||||
public element: ElementRef<HTMLElement>,
|
||||
@Inject(CDK_DRAG_PARENT) @Optional() parentDrag?: any) {
|
||||
|
||||
this._parentDrag = parentDrag;
|
||||
toggleNativeDragInteractions(element.nativeElement, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Injection token that can be used for a `CdkDrag` to provide itself as a parent to the
|
||||
* drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily
|
||||
* to avoid circular imports.
|
||||
* @docs-private
|
||||
*/
|
||||
export const CDK_DRAG_PARENT = new InjectionToken<{}>('CDK_DRAG_PARENT');
|
||||
@@ -33,7 +33,7 @@ describe('CdkDrag', () => {
|
||||
ComponentFixture<T> {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DragDropModule],
|
||||
declarations: [componentType],
|
||||
declarations: [componentType, PassthroughComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: CDK_DRAG_CONFIG,
|
||||
@@ -466,6 +466,23 @@ describe('CdkDrag', () => {
|
||||
expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)');
|
||||
}));
|
||||
|
||||
it('should be able to drag with a handle that is not a direct descendant', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggableWithIndirectHandle);
|
||||
fixture.detectChanges();
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
const handle = fixture.componentInstance.handleElement.nativeElement;
|
||||
|
||||
expect(dragElement.style.transform).toBeFalsy();
|
||||
dragElementViaMouse(fixture, dragElement, 50, 100);
|
||||
|
||||
expect(dragElement.style.transform)
|
||||
.toBeFalsy('Expected not to be able to drag the element by itself.');
|
||||
|
||||
dragElementViaMouse(fixture, handle, 50, 100);
|
||||
expect(dragElement.style.transform)
|
||||
.toBe('translate3d(50px, 100px, 0px)', 'Expected to drag the element by its handle.');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('in a drop container', () => {
|
||||
@@ -1746,6 +1763,26 @@ class StandaloneDraggableWithDelayedHandle {
|
||||
showHandle = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #dragElement cdkDrag
|
||||
style="width: 100px; height: 100px; background: red; position: relative">
|
||||
|
||||
<passthrough-component>
|
||||
<div
|
||||
#handleElement
|
||||
cdkDragHandle
|
||||
style="width: 10px; height: 10px; background: green;"></div>
|
||||
</passthrough-component>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class StandaloneDraggableWithIndirectHandle {
|
||||
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
|
||||
@ViewChild('handleElement') handleElement: ElementRef<HTMLElement>;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: [`
|
||||
@@ -1986,6 +2023,16 @@ class ConnectedDropZonesWithSingleItems {
|
||||
droppedSpy = jasmine.createSpy('dropped spy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that passes through whatever content is projected into it.
|
||||
* Used to test having drag elements being projected into a component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'passthrough-component',
|
||||
template: '<ng-content></ng-content>'
|
||||
})
|
||||
class PassthroughComponent {}
|
||||
|
||||
/**
|
||||
* Drags an element to a position on the page using the mouse.
|
||||
* @param fixture Fixture on which to run change detection.
|
||||
|
||||
@@ -46,6 +46,7 @@ import {CdkDragPreview} from './drag-preview';
|
||||
import {CDK_DROP_LIST_CONTAINER, CdkDropListContainer} from './drop-list-container';
|
||||
import {getTransformTransitionDurationInMs} from './transition-duration';
|
||||
import {extendStyles, toggleNativeDragInteractions} from './drag-styling';
|
||||
import {CDK_DRAG_PARENT} from './drag-parent';
|
||||
|
||||
|
||||
// TODO(crisbeto): add auto-scrolling functionality.
|
||||
@@ -89,7 +90,11 @@ const passiveEventListenerOptions = supportsPassiveEventListeners() ?
|
||||
host: {
|
||||
'class': 'cdk-drag',
|
||||
'[class.cdk-drag-dragging]': '_hasStartedDragging && _isDragging()',
|
||||
}
|
||||
},
|
||||
providers: [{
|
||||
provide: CDK_DRAG_PARENT,
|
||||
useExisting: CdkDrag
|
||||
}]
|
||||
})
|
||||
export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
private _document: Document;
|
||||
@@ -172,7 +177,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
private _pointerUpSubscription = Subscription.EMPTY;
|
||||
|
||||
/** Elements that can be used to drag the draggable item. */
|
||||
@ContentChildren(CdkDragHandle) _handles: QueryList<CdkDragHandle>;
|
||||
@ContentChildren(CdkDragHandle, {descendants: true}) _handles: QueryList<CdkDragHandle>;
|
||||
|
||||
/** Element that will be used as a template to create the draggable item's preview. */
|
||||
@ContentChild(CdkDragPreview) _previewTemplate: CdkDragPreview;
|
||||
@@ -298,9 +303,12 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
|
||||
|
||||
/** Handler for the `mousedown`/`touchstart` events. */
|
||||
_pointerDown = (event: MouseEvent | TouchEvent) => {
|
||||
// Skip handles inside descendant `CdkDrag` instances.
|
||||
const handles = this._handles.filter(handle => handle._parentDrag === this);
|
||||
|
||||
// Delegate the event based on whether it started from a handle or the element itself.
|
||||
if (this._handles.length) {
|
||||
const targetHandle = this._handles.find(handle => {
|
||||
if (handles.length) {
|
||||
const targetHandle = handles.find(handle => {
|
||||
const element = handle.element.nativeElement;
|
||||
const target = event.target;
|
||||
return !!target && (target === element || element.contains(target as HTMLElement));
|
||||
|
||||
Reference in New Issue
Block a user