Skip to content

refactor(cdk/drag-drop): clean up IE workarounds #23293

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

Merged
merged 1 commit into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 0 additions & 47 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,6 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
}));

it('should drag an SVG element freely to a particular position', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggableSvg);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

expect(dragElement.getAttribute('transform')).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 50, 100);
expect(dragElement.getAttribute('transform')).toBe('translate(50 100)');
}));

it('should drag an SVG element freely to a particular position in SVG viewBox coordinates',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggableSvgWithViewBox);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

expect(dragElement.getAttribute('transform')).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 50, 100);
expect(dragElement.getAttribute('transform')).toBe('translate(100 200)');
}));

it('should drag an element freely to a particular position when the page is scrolled',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
Expand Down Expand Up @@ -5959,32 +5938,6 @@ class StandaloneDraggableWithOnPush {
@ViewChild(CdkDrag) dragInstance: CdkDrag;
}

@Component({
template: `
<svg><g
cdkDrag
#dragElement>
<circle fill="red" r="50" cx="50" cy="50"/>
</g></svg>
`
})
class StandaloneDraggableSvg {
@ViewChild('dragElement') dragElement: ElementRef<SVGElement>;
}

@Component({
template: `
<svg width="400px" height="400px" viewBox="0 0 800 800"><g
cdkDrag
#dragElement>
<circle fill="red" r="50" cx="50" cy="50"/>
</g></svg>
`
})
class StandaloneDraggableSvgWithViewBox {
@ViewChild('dragElement') dragElement: ElementRef<SVGElement>;
}

@Component({
template: `
<div #dragElement cdkDrag [cdkDragDisabled]="draggingDisabled"
Expand Down
25 changes: 3 additions & 22 deletions src/cdk/drag-drop/directives/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
private _updateRootElement() {
const element = this.element.nativeElement;
const rootElement = this.rootElementSelector ?
getClosestMatchingAncestor(element, this.rootElementSelector) : element;
element.closest<HTMLElement>(this.rootElementSelector) : element;

if (rootElement && (typeof ngDevMode === 'undefined' || ngDevMode)) {
assertElementNode(rootElement, 'cdkDrag');
Expand All @@ -373,7 +373,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
}

if (typeof boundary === 'string') {
return getClosestMatchingAncestor(this.element.nativeElement, boundary);
return this.element.nativeElement.closest<HTMLElement>(boundary);
}

const element = coerceElement(boundary);
Expand Down Expand Up @@ -434,8 +434,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
// the item was projected into another item by something like `ngTemplateOutlet`.
let parent = this.element.nativeElement.parentElement;
while (parent) {
// `classList` needs to be null checked, because IE doesn't have it on some elements.
if (parent.classList?.contains(DRAG_HOST_CLASS)) {
if (parent.classList.contains(DRAG_HOST_CLASS)) {
ref.withParent(CdkDrag._dragInstances.find(drag => {
return drag.element.nativeElement === parent;
})?._dragRef || null);
Expand Down Expand Up @@ -538,21 +537,3 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {

static ngAcceptInputType_disabled: BooleanInput;
}

/** Gets the closest ancestor of an element that matches a selector. */
function getClosestMatchingAncestor(element: HTMLElement, selector: string) {
let currentElement = element.parentElement as HTMLElement | null;

while (currentElement) {
// IE doesn't support `matches` so we have to fall back to `msMatchesSelector`.
if (currentElement.matches ? currentElement.matches(selector) :
(currentElement as any).msMatchesSelector(selector)) {
return currentElement;
}

currentElement = currentElement.parentElement;
}

return null;
}

23 changes: 7 additions & 16 deletions src/cdk/drag-drop/drag-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,12 +715,6 @@ export class DragRef<T = any> {
constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;

this._applyRootElementTransform(activeTransform.x, activeTransform.y);

// Apply transform as attribute if dragging and svg element to work for IE
if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
const appliedTransform = `translate(${activeTransform.x} ${activeTransform.y})`;
this._rootElement.setAttribute('transform', appliedTransform);
}
}

// Since this event gets fired for every pixel while dragging, we only
Expand Down Expand Up @@ -1274,21 +1268,20 @@ export class DragRef<T = any> {
*/
private _applyRootElementTransform(x: number, y: number) {
const transform = getTransform(x, y);
const styles = this._rootElement.style;

// Cache the previous transform amount only after the first drag sequence, because
// we don't want our own transforms to stack on top of each other.
// Should be excluded none because none + translate3d(x, y, x) is invalid css
if (this._initialTransform == null) {
this._initialTransform = this._rootElement.style.transform
&& this._rootElement.style.transform != 'none'
? this._rootElement.style.transform
: '';
this._initialTransform =
styles.transform && styles.transform != 'none' ? styles.transform : '';
}

// Preserve the previous `transform` value, if there was one. Note that we apply our own
// transform before the user's, because things like rotation can affect which direction
// the element will be translated towards.
this._rootElement.style.transform = combineTransforms(transform, this._initialTransform);
styles.transform = combineTransforms(transform, this._initialTransform);
}

/**
Expand Down Expand Up @@ -1403,11 +1396,9 @@ export class DragRef<T = any> {
if (scrollDifference) {
const target = _getEventTarget<HTMLElement|Document>(event)!;

// ClientRect dimensions are based on the scroll position of the page and its parent node so
// we have to update the cached boundary ClientRect if the user has scrolled. Check for
// the `document` specifically since IE doesn't support `contains` on it.
if (this._boundaryRect && (target === this._document ||
(target !== this._boundaryElement && target.contains(this._boundaryElement)))) {
// ClientRect dimensions are based on the scroll position of the page and its parent
// node so we have to update the cached boundary ClientRect if the user has scrolled.
if (this._boundaryRect && target.contains(this._boundaryElement)) {
adjustClientRect(this._boundaryRect, scrollDifference.top, scrollDifference.left);
}

Expand Down
60 changes: 7 additions & 53 deletions src/cdk/drag-drop/drop-list-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ export class DropListRef<T = any> {
const items = this._orientation === 'horizontal' && this._direction === 'rtl' ?
this._itemPositions.slice().reverse() : this._itemPositions;

return findIndex(items, currentItem => currentItem.drag === item);
return items.findIndex(currentItem => currentItem.drag === item);
}

/**
Expand Down Expand Up @@ -468,7 +468,7 @@ export class DropListRef<T = any> {
}

const isHorizontal = this._orientation === 'horizontal';
const currentIndex = findIndex(siblings, currentItem => currentItem.drag === item);
const currentIndex = siblings.findIndex(currentItem => currentItem.drag === item);
const siblingAtNewPosition = siblings[newIndex];
const currentPosition = siblings[currentIndex].clientRect;
const newPosition = siblingAtNewPosition.clientRect;
Expand Down Expand Up @@ -754,7 +754,7 @@ export class DropListRef<T = any> {
private _getItemIndexFromPointerPosition(item: DragRef, pointerX: number, pointerY: number,
delta?: {x: number, y: number}): number {
const isHorizontal = this._orientation === 'horizontal';
const index = findIndex(this._itemPositions, ({drag, clientRect}, _, array) => {
const index = this._itemPositions.findIndex(({drag, clientRect}, _, array) => {
if (drag === item) {
// If there's only one item left in the container, it must be
// the dragged item itself so we use it as a reference.
Expand Down Expand Up @@ -801,15 +801,15 @@ export class DropListRef<T = any> {
const scrollStep = this.autoScrollStep;

if (this._verticalScrollDirection === AutoScrollVerticalDirection.UP) {
incrementVerticalScroll(node, -scrollStep);
node.scrollBy(0, -scrollStep);
} else if (this._verticalScrollDirection === AutoScrollVerticalDirection.DOWN) {
incrementVerticalScroll(node, scrollStep);
node.scrollBy(0, scrollStep);
}

if (this._horizontalScrollDirection === AutoScrollHorizontalDirection.LEFT) {
incrementHorizontalScroll(node, -scrollStep);
node.scrollBy(-scrollStep, 0);
} else if (this._horizontalScrollDirection === AutoScrollHorizontalDirection.RIGHT) {
incrementHorizontalScroll(node, scrollStep);
node.scrollBy(scrollStep, 0);
}
});
}
Expand Down Expand Up @@ -953,52 +953,6 @@ export class DropListRef<T = any> {
}


/**
* Finds the index of an item that matches a predicate function. Used as an equivalent
* of `Array.prototype.findIndex` which isn't part of the standard Google typings.
* @param array Array in which to look for matches.
* @param predicate Function used to determine whether an item is a match.
*/
function findIndex<T>(array: T[],
predicate: (value: T, index: number, obj: T[]) => boolean): number {

for (let i = 0; i < array.length; i++) {
if (predicate(array[i], i, array)) {
return i;
}
}

return -1;
}

/**
* Increments the vertical scroll position of a node.
* @param node Node whose scroll position should change.
* @param amount Amount of pixels that the `node` should be scrolled.
*/
function incrementVerticalScroll(node: HTMLElement | Window, amount: number) {
if (node === window) {
(node as Window).scrollBy(0, amount);
} else {
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
(node as HTMLElement).scrollTop += amount;
}
}

/**
* Increments the horizontal scroll position of a node.
* @param node Node whose scroll position should change.
* @param amount Amount of pixels that the `node` should be scrolled.
*/
function incrementHorizontalScroll(node: HTMLElement | Window, amount: number) {
if (node === window) {
(node as Window).scrollBy(amount, 0);
} else {
// Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
(node as HTMLElement).scrollLeft += amount;
}
}

/**
* Gets whether the vertical auto-scroll direction of a node.
* @param clientRect Dimensions of the node.
Expand Down
6 changes: 1 addition & 5 deletions src/cdk/drag-drop/parent-position-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ export class ParentPositionTracker {
return null;
}

// Used when figuring out whether an element is inside the scroll parent. If the scrolled
// parent is the `document`, we use the `documentElement`, because IE doesn't support
// `contains` on the `document`.
const scrolledParentNode = target === this._document ? target.documentElement : target;
const scrollPosition = cachedPosition.scrollPosition;
let newTop: number;
let newLeft: number;
Expand All @@ -78,7 +74,7 @@ export class ParentPositionTracker {
// Go through and update the cached positions of the scroll
// parents that are inside the element that was scrolled.
this.positions.forEach((position, node) => {
if (position.clientRect && target !== node && scrolledParentNode.contains(node)) {
if (position.clientRect && target !== node && target.contains(node)) {
adjustClientRect(position.clientRect, topDifference, leftDifference);
}
});
Expand Down