-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
Copy pathget-styles.js
170 lines (143 loc) · 5.22 KB
/
get-styles.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// @flow
import type { ContextId } from '../../types';
import { transitions } from '../../animation';
import * as attributes from '../data-attributes';
export type Styles = {|
always: string,
dragging: string,
resting: string,
dropAnimating: string,
userCancel: string,
|};
type Rule = {|
selector: string,
styles: {|
always?: string,
resting?: string,
dragging?: string,
dropAnimating?: string,
userCancel?: string,
|},
|};
const makeGetSelector = (context: string) => (attribute: string) =>
`[${attribute}="${context}"]`;
const getStyles = (rules: Rule[], property: string): string =>
rules
.map((rule: Rule): string => {
const value: ?string = rule.styles[property];
if (!value) {
return '';
}
return `${rule.selector} { ${value} }`;
})
.join(' ');
const noPointerEvents: string = 'pointer-events: none;';
export default (contextId: ContextId): Styles => {
const getSelector = makeGetSelector(contextId);
// ## Drag handle styles
// -webkit-touch-callout
// A long press on anchors usually pops a content menu that has options for
// the link such as 'Open in new tab'. Because long press is used to start
// a drag we need to opt out of this behavior
// -webkit-tap-highlight-color
// Webkit based browsers add a grey overlay to anchors when they are active.
// We remove this tap overlay as it is confusing for users
// https://css-tricks.com/snippets/css/remove-gray-highlight-when-tapping-links-in-mobile-safari/
// touch-action: manipulation
// Avoid the *pull to refresh action* and *delayed anchor focus* on Android Chrome
// cursor: grab
// We apply this by default for an improved user experience. It is such a common default that we
// bake it right in. Consumers can opt out of this by adding a selector with higher specificity
// The cursor will not apply when pointer-events is set to none
// pointer-events: none
// this is used to prevent pointer events firing on draggables during a drag
// Reasons:
// 1. performance: it stops the other draggables from processing mouse events
// 2. scrolling: it allows the user to scroll through the current draggable
// to scroll the list behind
// 3.* function: it blocks other draggables from starting. This is not relied on though as there
// is a function on the context (canLift) which is a more robust way of controlling this
const dragHandle: Rule = (() => {
const grabCursor = `
cursor: -webkit-grab;
cursor: grab;
`;
return {
selector: getSelector(attributes.dragHandle.contextId),
styles: {
always: `
-webkit-touch-callout: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
touch-action: manipulation;
`,
resting: grabCursor,
dragging: noPointerEvents,
// it is fine for users to start dragging another item when a drop animation is occurring
dropAnimating: grabCursor,
// Not applying grab cursor during a user cancel as it is not possible for users to reorder
// items during a cancel
},
};
})();
// ## Draggable styles
// transition: transform
// This controls the animation of draggables that are moving out of the way
// The main draggable is controlled by react-motion.
const draggable: Rule = (() => {
const transition: string = `
transition: ${transitions.outOfTheWay};
`;
return {
selector: getSelector(attributes.draggable.contextId),
styles: {
dragging: transition,
dropAnimating: transition,
userCancel: transition,
},
};
})();
// ## Droppable styles
// overflow-anchor: none;
// Opting out of the browser feature which tries to maintain
// the scroll position when the DOM changes above the fold.
// This does not work well with reordering DOM nodes.
// When we drop a Draggable it already has the correct scroll applied.
const droppable: Rule = {
selector: getSelector(attributes.droppable.contextId),
styles: {
always: `overflow-anchor: none;`,
// need pointer events on the droppable to allow manual scrolling
},
};
// ## Body styles
// cursor: grab
// We apply this by default for an improved user experience. It is such a common default that we
// bake it right in. Consumers can opt out of this by adding a selector with higher specificity
// user-select: none
// This prevents the user from selecting text on the page while dragging
// overflow-anchor: none
// We are in control and aware of all of the window scrolls that occur
// we do not want the browser to have behaviors we do not expect
const body: Rule = {
selector: 'body',
styles: {
dragging: `
cursor: grabbing;
cursor: -webkit-grabbing;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
overflow-anchor: none;
`,
},
};
const rules: Rule[] = [draggable, dragHandle, droppable, body];
return {
always: getStyles(rules, 'always'),
resting: getStyles(rules, 'resting'),
dragging: getStyles(rules, 'dragging'),
dropAnimating: getStyles(rules, 'dropAnimating'),
userCancel: getStyles(rules, 'userCancel'),
};
};