-
Notifications
You must be signed in to change notification settings - Fork 830
/
Copy pathutils.js.flow
130 lines (118 loc) · 3.54 KB
/
utils.js.flow
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
/*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
// @flow
/* eslint-disable import/prefer-default-export */
import { ARROW_SIZE, PLACEMENT } from './constants.js';
import type { OffsetT, PopoverPlacementT } from './types.js';
const OPPOSITE_POSITIONS = {
top: 'bottom',
bottom: 'top',
right: 'left',
left: 'right',
};
/**
* Returns the opposite of the specified position. Useful for tooltip
* positioning logic.
* Examples:
* top -> bottom
* left -> right
*/
export function getOppositePosition(position: string): string {
return OPPOSITE_POSITIONS[position];
}
/**
* Determines whether or not the specified position is a vertical one (top or bottom)
*/
export function isVerticalPosition(position: string): boolean {
return position === 'top' || position === 'bottom';
}
/**
* Simple utility function for capitalizing the first letter of a string
*/
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Opposite of function above, converts from Popper.js placement
* to our placement prop
*/
export function fromPopperPlacement(placement: string): PopoverPlacementT | null {
const popoverPlacement: string = placement
.replace(/(top|bottom)-start$/, '$1Left')
.replace(/(top|bottom)-end$/, '$1Right')
.replace(/(left|right)-start$/, '$1Top')
.replace(/(left|right)-end$/, '$1Bottom');
return PLACEMENT[popoverPlacement] || null;
}
/**
* Splits something like 'topLeft' to ['top', 'left'] for easier usage
*/
export function splitPlacement(placement: PopoverPlacementT): string[] {
const matches = placement.match(/^([a-z]+)([A-Z][a-z]+)?/) || [];
return (matches: string[])
.slice(1, 3)
.filter(Boolean)
.map((s) => s.toLowerCase());
}
/**
* Returns margin styles to add spacing between the popover
* and its anchor.
*/
export function getPopoverMarginStyles(
arrowSize: number,
placement: PopoverPlacementT,
popoverMargin: number
) {
const [position] = splitPlacement(placement);
const opposite = getOppositePosition(position);
if (!opposite) {
return null;
}
const property = `margin${capitalize(opposite)}`;
return {
[property]: `${arrowSize + popoverMargin}px`,
};
}
/**
* Returns CSS rules for the popover animation start keyframe
*/
export function getStartPosition(
offset: OffsetT,
placement: PopoverPlacementT,
arrowSize: number,
popoverMargin: number
) {
offset = { ...offset };
const [position] = splitPlacement(placement);
const margin = (arrowSize > 0 ? arrowSize : popoverMargin) * 2;
if (isVerticalPosition(position)) {
offset.top += position === 'top' ? margin : -margin;
} else {
offset.left += position === 'left' ? margin : -margin;
}
return `translate3d(${offset.left}px, ${offset.top}px, 0)`;
}
/**
* Returns CSS rules for the popover animation end keyframe
*/
export function getEndPosition(offset: OffsetT) {
return `translate3d(${offset.left}px, ${offset.top}px, 0)`;
}
/**
* Returns top/left styles to position the popover arrow
*/
export function getArrowPositionStyles(offsets: OffsetT, placement: PopoverPlacementT) {
const [position] = splitPlacement(placement);
const oppositePosition = getOppositePosition(position);
if (!oppositePosition) {
return null;
}
const alignmentProperty: string = isVerticalPosition(position) ? 'left' : 'top';
return {
[alignmentProperty]: `${offsets[alignmentProperty]}px`,
[oppositePosition]: `-${ARROW_SIZE - 2}px`,
};
}