Skip to content

Commit 2f90fc2

Browse files
crisbetowagnermaciel
authored andcommitted
fix(cdk/drag-drop): avoid conflicts with sticky table headers (#22864)
The Material table with sticky headers sets its `position` as `!important` which ends up overriding the dragging styles. These changes rework the internals to use `setProperty` instead of `element.style.position` which allows us to set the `position` as `!important` as well. It also has the advantage that we can write the properties as dash case, instead of having to guess whether the vendor-prefixed properties are camel case or pascal case. Fixes #22781. (cherry picked from commit 6cfb549)
1 parent 1408490 commit 2f90fc2

File tree

3 files changed

+47
-36
lines changed

3 files changed

+47
-36
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,8 @@ describe('CdkDrag', () => {
21292129

21302130
expect(item.parentNode).toBe(document.body, 'Expected element to be moved out into the body');
21312131
expect(item.style.position).toBe('fixed', 'Expected element to be removed from layout');
2132+
expect(item.style.getPropertyPriority('position'))
2133+
.toBe('important', 'Expect element position to be !important');
21322134
// Use a regex here since some browsers normalize 0 to 0px, but others don't.
21332135
expect(item.style.top).toMatch(zeroPxRegex, 'Expected element to be removed from layout');
21342136
expect(item.style.left).toBe('-999em', 'Expected element to be removed from layout');

src/cdk/drag-drop/drag-ref.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ export interface Point {
8888
y: number;
8989
}
9090

91+
/** Inline styles to be set as `!important` while dragging. */
92+
const dragImportantProperties = new Set([
93+
// Needs to be important, because some `mat-table` sets `position: sticky !important`. See #22781.
94+
'position'
95+
]);
96+
9197
/**
9298
* Possible places into which the preview of a drag item can be inserted.
9399
* - `global` - Preview will be inserted at the bottom of the `<body>`. The advantage is that
@@ -817,7 +823,7 @@ export class DragRef<T = any> {
817823
// We move the element out at the end of the body and we make it hidden, because keeping it in
818824
// place will throw off the consumer's `:last-child` selectors. We can't remove the element
819825
// from the DOM completely, because iOS will stop firing all subsequent events in the chain.
820-
toggleVisibility(element, false);
826+
toggleVisibility(element, false, dragImportantProperties);
821827
this._document.body.appendChild(parent.replaceChild(placeholder, element));
822828
this._getPreviewInsertionPoint(parent, shadowRoot).appendChild(this._preview);
823829
this.started.next({source: this}); // Emit before notifying the container.
@@ -914,7 +920,7 @@ export class DragRef<T = any> {
914920
// It's important that we maintain the position, because moving the element around in the DOM
915921
// can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
916922
// while moving the existing elements in all other cases.
917-
toggleVisibility(this._rootElement, true);
923+
toggleVisibility(this._rootElement, true, dragImportantProperties);
918924
this._anchor.parentNode!.replaceChild(this._rootElement, this._anchor);
919925

920926
this._destroyPreview();
@@ -1030,14 +1036,14 @@ export class DragRef<T = any> {
10301036
extendStyles(preview.style, {
10311037
// It's important that we disable the pointer events on the preview, because
10321038
// it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
1033-
pointerEvents: 'none',
1039+
'pointer-events': 'none',
10341040
// We have to reset the margin, because it can throw off positioning relative to the viewport.
1035-
margin: '0',
1036-
position: 'fixed',
1037-
top: '0',
1038-
left: '0',
1039-
zIndex: `${this._config.zIndex || 1000}`
1040-
});
1041+
'margin': '0',
1042+
'position': 'fixed',
1043+
'top': '0',
1044+
'left': '0',
1045+
'z-index': `${this._config.zIndex || 1000}`
1046+
}, dragImportantProperties);
10411047

10421048
toggleNativeDragInteractions(preview, false);
10431049
preview.classList.add('cdk-drag-preview');

src/cdk/drag-drop/drag-styling.ts

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,32 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
10-
// Helper type that ignores `readonly` properties. This is used in
11-
// `extendStyles` to ignore the readonly properties on CSSStyleDeclaration
12-
// since we won't be touching those anyway.
13-
type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };
14-
159
/**
1610
* Extended CSSStyleDeclaration that includes a couple of drag-related
1711
* properties that aren't in the built-in TS typings.
1812
*/
19-
export interface DragCSSStyleDeclaration extends CSSStyleDeclaration {
20-
webkitUserDrag: string;
21-
MozUserSelect: string; // For some reason the Firefox property is in PascalCase.
13+
export interface DragCSSStyleDeclaration extends CSSStyleDeclaration {
2214
msScrollSnapType: string;
2315
scrollSnapType: string;
24-
msUserSelect: string;
2516
}
2617

2718
/**
28-
* Shallow-extends a stylesheet object with another stylesheet object.
19+
* Shallow-extends a stylesheet object with another stylesheet-like object.
20+
* Note that the keys in `source` have to be dash-cased.
2921
* @docs-private
3022
*/
31-
export function extendStyles(
32-
dest: Writeable<CSSStyleDeclaration>,
33-
source: Partial<DragCSSStyleDeclaration>) {
23+
export function extendStyles(dest: CSSStyleDeclaration,
24+
source: Record<string, string>,
25+
importantProperties?: Set<string>) {
3426
for (let key in source) {
3527
if (source.hasOwnProperty(key)) {
36-
dest[key] = source[key]!;
28+
const value = source[key];
29+
30+
if (value) {
31+
dest.setProperty(key, value, importantProperties?.has(key) ? 'important' : '');
32+
} else {
33+
dest.removeProperty(key);
34+
}
3735
}
3836
}
3937

@@ -51,27 +49,32 @@ export function toggleNativeDragInteractions(element: HTMLElement, enable: boole
5149
const userSelect = enable ? '' : 'none';
5250

5351
extendStyles(element.style, {
54-
touchAction: enable ? '' : 'none',
55-
webkitUserDrag: enable ? '' : 'none',
56-
webkitTapHighlightColor: enable ? '' : 'transparent',
57-
userSelect: userSelect,
58-
msUserSelect: userSelect,
59-
webkitUserSelect: userSelect,
60-
MozUserSelect: userSelect
52+
'touch-action': enable ? '' : 'none',
53+
'-webkit-user-drag': enable ? '' : 'none',
54+
'-webkit-tap-highlight-color': enable ? '' : 'transparent',
55+
'user-select': userSelect,
56+
'-ms-user-select': userSelect,
57+
'-webkit-user-select': userSelect,
58+
'-moz-user-select': userSelect
6159
});
6260
}
6361

6462
/**
6563
* Toggles whether an element is visible while preserving its dimensions.
6664
* @param element Element whose visibility to toggle
6765
* @param enable Whether the element should be visible.
66+
* @param importantProperties Properties to be set as `!important`.
6867
* @docs-private
6968
*/
70-
export function toggleVisibility(element: HTMLElement, enable: boolean) {
71-
const styles = element.style;
72-
styles.position = enable ? '' : 'fixed';
73-
styles.top = styles.opacity = enable ? '' : '0';
74-
styles.left = enable ? '' : '-999em';
69+
export function toggleVisibility(element: HTMLElement,
70+
enable: boolean,
71+
importantProperties?: Set<string>) {
72+
extendStyles(element.style, {
73+
position: enable ? '' : 'fixed',
74+
top: enable ? '' : '0',
75+
opacity: enable ? '' : '0',
76+
left: enable ? '' : '-999em'
77+
}, importantProperties);
7578
}
7679

7780
/**

0 commit comments

Comments
 (0)