Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add experimental drag-drop package #11864

Merged
merged 1 commit into from
Jun 28, 2018

Conversation

crisbeto
Copy link
Member

@crisbeto crisbeto commented Jun 20, 2018

Adds an initial version of the drag-drop module to cdk-experimental with support for the following:

  • Dragging an element on its own.
  • Dragging an element using a handle.
  • Sorting an element within a drop zone.
  • Moving an element between drop zones.
  • Touch and mouse support.
  • Custom template for the preview element.
  • Custom template for the placeholder element.
  • Animation support for the preview.

Demo: https://material-cb7ec.firebaseapp.com/drag-drop

Relates to #8963.

Note: this is still missing a lot of unit tests. I'll continue adding them to this branch or open a follow-up PR. Also will continue adding features, docs and a11y guidelines in follow-ups.

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jun 20, 2018
@crisbeto crisbeto force-pushed the drag-and-drop-poc branch 4 times, most recently from 3d4aa8d to 3e401e7 Compare June 20, 2018 19:44
@josephperrott josephperrott self-requested a review June 20, 2018 22:36
* @param fromIndex Starting index of the item.
* @param toIndex Index to which the item should be moved.
*/
export function moveItemInArray(array: any[], fromIndex: number, toIndex: number): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use a generic here instead of any for the array? I would just prefer to avoid any where possible personally, though since the typing never leaves this function, I suppose its not crucial.

moveItemInArray<T>(array: T[], fromIndex: number, toIndex: number): void {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it would be better to have a generic

import {dispatchMouseEvent, dispatchTouchEvent} from '@angular/cdk/testing';
import {CdkDrag} from './drag';
import {CdkDragDrop} from './drag-events';
import { moveItemInArray } from './drag-utils';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra spacing around symbol


constructor(
/** Element that the draggable is attached to. */
public element: ElementRef<HTMLElement>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be public but not internal (_ prefixed)?

Copy link
Member Author

@crisbeto crisbeto Jun 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it public so people can use the element to add custom styling while it's being dragged.

ngAfterContentInit() {
// TODO: doesn't handle (pun intended) the handle being destroyed
const element = (this._handle ? this._handle.element : this.element).nativeElement;
element.addEventListener('mousedown', this._pointerDown);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like these event listeners are not cleaned up with a removeEventListener

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're not, but presumably they live for the lifetime of the directive since they're bound either on the host or the handle which is inside the host.


if (this.dropContainer) {
const element = this.element.nativeElement;
const preview = this._preview = this._createPreviewElement();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than the multiple assignment here, should _createPreviewElement also assign its output to this._preview?

It could be thought of as setupPreviewElement rather than just creating. I think this especially makes sense since _createPreviewElement already has side effects of creating the refs needed for the preview.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is more of a personal preference. I prefer not to have functions initialize properties like this since it isn't very transparent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree w/ Kristiyan

}

/** Point on the page or within an element. */
interface Point {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This same interface is created and used in flexible-connected-position-strategy.ts, perhaps it should be moved to a common place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to duplicate it since it's fairly small.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that it's trivial enough to not bother finding a common place for it, which would necessitate it being part of a public API

@crisbeto crisbeto force-pushed the drag-and-drop-poc branch from 3e401e7 to 4b5602d Compare June 21, 2018 17:31
@crisbeto
Copy link
Member Author

Addressed the current feedback @josephperrott.


/** Event emitted when the user moves an item into a new drop container. */
export interface CdkDragEnter<T> {
/** Container from which the user has a removed an item. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is accidentally using the same comments as CdkDragExit

/** Index of the item when it was picked up. */
previousIndex: number;
/** Current index of the item. */
currentIndex: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do dragged items have to inherently have an index?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop event only fires on items inside a drop zone which ensures that they have an index.

/** Handle that can be used to drag and CdkDrag instance. */
@Directive({
selector: '[cdkDragHandle]',
host: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a TODO here for a11y treatment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have it on my list, I just haven't added any extra APIs to facilitate it (e.g. we need something pick up and swap items programmatically).

* @param fromIndex Starting index of the item.
* @param toIndex Index to which the item should be moved.
*/
export function moveItemInArray(array: any[], fromIndex: number, toIndex: number): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it would be better to have a generic

/** Coordinates on the page at which the user picked up the element. */
private _pickupPositionOnPage: Point;

/** CSS `transform` applied to the element when it isn't being dragged. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand this comment to explain why there's a passive transform applied when the element isn't being dragged?

/**
* Drops an item into this container.
* @param item Item being dropped into the container.
* @param currentIndex Index at which the item should be inserted.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar question as above: could you have a dropzone without indices? E.g., you have free-floating draggables that can only be placed within certain regions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that makes sense. If you wanted a region with a bunch of freely-draggable items, you'd just place them in something that isn't a CdkDrop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about free-drag with constraints. I see those constraints as having two manifestations: blocking you from moving into/out of some element entirely, or "cancelling" a drop if it's in a disallowed area. I do think it is reasonable for that to be done with a different directive than cdkDrop

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was thinking either a directive or a straight-up @Input that accepts an ElementRef, although that could be problematic for components with an implicit exportAs.

import {CdkDrag} from './drag';

/** @docs-private */
export interface CdkDropContainer<T = any> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on DropContainer vs. DropZone or DropRegion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with DropZone, I just needed a name that isn't CdkDrop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry about changing it now; I'm not strongly attached to any of them. I'll sleep on it.

@ContentChild(CdkDragPlaceholder) _placeholderTemplate: CdkDragPlaceholder;

/** Emits when the user starts dragging the item. */
@Output() started = new EventEmitter<CdkDragStart>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Output('cdkDragStarted') started and similar for other outputs since this is an attribute selector directive

* Other draggable containers that this container is connected
* to and into which the container's items can be transferred.
*/
@Input('connectedTo') siblings: CdkDrop[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a little confusing for the input and the property to be so different

@@ -0,0 +1,38 @@
<div class="list">
Copy link
Member

@jelbourn jelbourn Jun 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Andrew has been pushing for is to stop adding demos that only live in demo-app/ and instead always embed something from the examples, since it forced us to flesh those out. Not necessary for this PR, but would be nice to do.

@crisbeto crisbeto force-pushed the drag-and-drop-poc branch from 4b5602d to 4b7b740 Compare June 22, 2018 15:19
@crisbeto
Copy link
Member Author

Addressed the latest set of feedback.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker labels Jun 22, 2018
@crisbeto crisbeto force-pushed the drag-and-drop-poc branch 3 times, most recently from 3740c18 to 4a40f9a Compare June 25, 2018 21:04
@josephperrott josephperrott added pr: needs rebase and removed action: merge The PR is ready for merge by the caretaker labels Jun 27, 2018
@josephperrott
Copy link
Member

@crisbeto Please rebase when you have a chance

Adds an initial version of the `drag-drop` module to `cdk-experimental` with support for the following:

* Dragging an element on its own.
* Dragging an element using a handle.
* Sorting an element within a drop zone.
* Moving an element between drop zones.
* Touch and mouse support.
* Custom template for the preview element.
* Custom template for the placeholder element.
* Animation support for the preview.

Demo: https://material-cb7ec.firebaseapp.com/drag-drop

Relates to angular#8963.
@crisbeto crisbeto force-pushed the drag-and-drop-poc branch from 4a40f9a to d3aaa18 Compare June 27, 2018 17:33
@crisbeto crisbeto added the action: merge The PR is ready for merge by the caretaker label Jun 27, 2018
@crisbeto
Copy link
Member Author

Rebased @josephperrott.

@josephperrott josephperrott merged commit 27811a3 into angular:master Jun 28, 2018
@cengiz-dev
Copy link

@josephperrott, @crisbeto, do you know when this feature will be included in a release?

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants