-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(positioning): refactor positioning service (#5027)
* reafctor(positioning): add popper.js and migrate it to typescript * temporary disable tslint for popper * feat(position): refactored base functionality * update * feat(positioning): flip on overflow added (dirty) * refactor(positioning): add logic of arrow position correcting * refactor(positioning): add shift logic. placement: bottom left/bottom right * refactor(positioning): refactor logic of real-time processing events to avoid memory leaks * refactor(positioning): add types for all methods(minimal solution) && fix tslint errors * refactor(positioning): optimize and fix blur text on tooltip/popover * refactor(positioning): add auto class placement * refactor(positioning): clean up the code * refactor(positioning): unit tests are fixed * refactor(positioning): fix incorrect styles on bootstrap 3 && fix arrow in popovers when auto * refactor(positioning): fix tests for sauce * refactor(positioning): fix typeahead dropup issue * refactor(positioning): fix center for arrow on shift * refactor(positioning): fix tooltip out of screen on mobile * refactor(positioning): refactor modifiers flow * fix(tooltip): back css style Closes #3303 Closes #2993 Closes #4470
- Loading branch information
Showing
46 changed files
with
1,266 additions
and
274 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export interface Offsets { | ||
bottom?: number; | ||
height: number; | ||
left?: number; | ||
right?: number; | ||
top?: number; | ||
width: number; | ||
marginTop?: number; | ||
marginLeft?: number; | ||
} | ||
|
||
export interface Data { | ||
instance: { | ||
target: HTMLElement; | ||
host: HTMLElement; | ||
arrow: HTMLElement; | ||
}; | ||
offsets: { | ||
target: Offsets; | ||
host: Offsets; | ||
arrow: { [key: string]: string | number | HTMLElement }; | ||
}; | ||
positionFixed: boolean; | ||
placement: string; | ||
placementAuto: boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { getClientRect, getOuterSizes, getStyleComputedProperty } from '../utils'; | ||
import { Data } from '../models'; | ||
|
||
export function arrow(data: Data) { | ||
let targetOffsets = data.offsets.target; | ||
// if arrowElement is a string, suppose it's a CSS selector | ||
const arrowElement: HTMLElement | null = data.instance.target.querySelector('.arrow'); | ||
|
||
// if arrowElement is not found, don't run the modifier | ||
if (!arrowElement) { | ||
return data; | ||
} | ||
|
||
const isVertical = ['left', 'right'].indexOf(data.placement) !== -1; | ||
|
||
const len = isVertical ? 'height' : 'width'; | ||
const sideCapitalized = isVertical ? 'Top' : 'Left'; | ||
const side = sideCapitalized.toLowerCase(); | ||
const altSide = isVertical ? 'left' : 'top'; | ||
const opSide = isVertical ? 'bottom' : 'right'; | ||
const arrowElementSize = getOuterSizes(arrowElement)[len]; | ||
|
||
// top/left side | ||
if (data.offsets.host[opSide] - arrowElementSize < targetOffsets[side]) { | ||
targetOffsets[side] -= | ||
targetOffsets[side] - (data.offsets.host[opSide] - arrowElementSize); | ||
} | ||
// bottom/right side | ||
if (Number(data.offsets.host[side]) + Number(arrowElementSize) > targetOffsets[opSide]) { | ||
targetOffsets[side] += | ||
Number(data.offsets.host[side]) + Number(arrowElementSize) - Number(targetOffsets[opSide]); | ||
} | ||
targetOffsets = getClientRect(targetOffsets); | ||
|
||
// compute center of the target | ||
const center = Number(data.offsets.host[side]) + Number(data.offsets.host[len] / 2 - arrowElementSize / 2); | ||
|
||
// Compute the sideValue using the updated target offsets | ||
// take target margin in account because we don't have this info available | ||
const css = getStyleComputedProperty(data.instance.target); | ||
|
||
const targetMarginSide = parseFloat(css[`margin${sideCapitalized}`]); | ||
const targetBorderSide = parseFloat(css[`border${sideCapitalized}Width`]); | ||
let sideValue = | ||
center - targetOffsets[side] - targetMarginSide - targetBorderSide; | ||
|
||
// prevent arrowElement from being placed not contiguously to its target | ||
sideValue = Math.max(Math.min(targetOffsets[len] - arrowElementSize, sideValue), 0); | ||
|
||
data.offsets.arrow = { | ||
[side]: Math.round(sideValue), | ||
[altSide]: '' // make sure to unset any eventual altSide value from the DOM node | ||
}; | ||
|
||
data.instance.arrow = arrowElement; | ||
|
||
return data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
computeAutoPlacement, | ||
getBoundaries, getClientRect, | ||
getOppositeVariation, | ||
getTargetOffsets | ||
} from '../utils'; | ||
|
||
import { Data } from '../models'; | ||
|
||
export function flip(data: Data): Data { | ||
data.offsets.target = getClientRect(data.offsets.target); | ||
|
||
const boundaries = getBoundaries( | ||
data.instance.target, | ||
data.instance.host, | ||
0, // padding | ||
'viewport', | ||
false // positionFixed | ||
); | ||
|
||
let placement = data.placement.split(' ')[0]; | ||
let variation = data.placement.split(' ')[1] || ''; | ||
|
||
const autoPosition = computeAutoPlacement( | ||
'auto', data.offsets.host, data.instance.target, data.instance.host, 'viewport', 0 | ||
); | ||
const flipOrder = [placement, autoPosition]; | ||
|
||
/* tslint:disable-next-line: cyclomatic-complexity */ | ||
flipOrder.forEach((step, index) => { | ||
if (placement !== step || flipOrder.length === index + 1) { | ||
return data; | ||
} | ||
|
||
placement = data.placement.split(' ')[0]; | ||
|
||
// using floor because the host offsets may contain decimals we are not going to consider here | ||
const overlapsRef = | ||
(placement === 'left' && | ||
Math.floor(data.offsets.target.right) > Math.floor(data.offsets.host.left)) || | ||
(placement === 'right' && | ||
Math.floor(data.offsets.target.left) < Math.floor(data.offsets.host.right)) || | ||
(placement === 'top' && | ||
Math.floor(data.offsets.target.bottom) > Math.floor(data.offsets.host.top)) || | ||
(placement === 'bottom' && | ||
Math.floor(data.offsets.target.top) < Math.floor(data.offsets.host.bottom)); | ||
|
||
const overflowsLeft = Math.floor(data.offsets.target.left) < Math.floor(boundaries.left); | ||
const overflowsRight = Math.floor(data.offsets.target.right) > Math.floor(boundaries.right); | ||
const overflowsTop = Math.floor(data.offsets.target.top) < Math.floor(boundaries.top); | ||
const overflowsBottom = Math.floor(data.offsets.target.bottom) > Math.floor(boundaries.bottom); | ||
|
||
const overflowsBoundaries = | ||
(placement === 'left' && overflowsLeft) || | ||
(placement === 'right' && overflowsRight) || | ||
(placement === 'top' && overflowsTop) || | ||
(placement === 'bottom' && overflowsBottom); | ||
|
||
// flip the variation if required | ||
const isVertical = ['top', 'bottom'].indexOf(placement) !== -1; | ||
const flippedVariation = | ||
((isVertical && variation === 'left' && overflowsLeft) || | ||
(isVertical && variation === 'right' && overflowsRight) || | ||
(!isVertical && variation === 'left' && overflowsTop) || | ||
(!isVertical && variation === 'right' && overflowsBottom)); | ||
|
||
if (overlapsRef || overflowsBoundaries || flippedVariation) { | ||
// this boolean to detect any flip loop | ||
if (overlapsRef || overflowsBoundaries) { | ||
placement = flipOrder[index + 1]; | ||
} | ||
|
||
if (flippedVariation) { | ||
variation = getOppositeVariation(variation); | ||
} | ||
|
||
data.placement = placement + (variation ? ` ${variation}` : ''); | ||
|
||
data.offsets.target = { | ||
...data.offsets.target, | ||
...getTargetOffsets( | ||
data.instance.target, | ||
data.offsets.host, | ||
data.placement | ||
) | ||
}; | ||
} | ||
}); | ||
|
||
return data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export { arrow } from './arrow'; | ||
export { flip } from './flip'; | ||
export { initData } from './initData'; | ||
export { preventOverflow } from './preventOverflow'; | ||
export { shift } from './shift'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { | ||
computeAutoPlacement, | ||
getReferenceOffsets, | ||
getTargetOffsets | ||
} from '../utils'; | ||
|
||
import { Data } from '../models'; | ||
|
||
export function initData(targetElement: HTMLElement, hostElement: HTMLElement, position: string): Data { | ||
|
||
const hostElPosition = getReferenceOffsets(targetElement, hostElement); | ||
const targetOffset = getTargetOffsets(targetElement, hostElPosition, position); | ||
|
||
const placement = computeAutoPlacement(position, hostElPosition, targetElement, hostElement, 'viewport', 0); | ||
const placementAuto = position.indexOf('auto') !== -1; | ||
|
||
return { | ||
instance: { | ||
target: targetElement, | ||
host: hostElement, | ||
arrow: null | ||
}, | ||
offsets: { | ||
target: targetOffset, | ||
host: hostElPosition, | ||
arrow: null | ||
}, | ||
positionFixed: false, | ||
placement, | ||
placementAuto | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { getBoundaries } from '../utils'; | ||
import { Data } from '../models'; | ||
|
||
export function preventOverflow(data: Data) { | ||
|
||
// NOTE: DOM access here | ||
// resets the targetOffsets's position so that the document size can be calculated excluding | ||
// the size of the targetOffsets element itself | ||
const transformProp = 'transform'; | ||
const targetStyles = data.instance.target.style; // assignment to help minification | ||
const { top, left, [transformProp]: transform } = targetStyles; | ||
targetStyles.top = ''; | ||
targetStyles.left = ''; | ||
targetStyles[transformProp] = ''; | ||
|
||
const boundaries = getBoundaries( | ||
data.instance.target, | ||
data.instance.host, | ||
0, // padding | ||
'scrollParent', | ||
false // positionFixed | ||
); | ||
|
||
// NOTE: DOM access here | ||
// restores the original style properties after the offsets have been computed | ||
targetStyles.top = top; | ||
targetStyles.left = left; | ||
targetStyles[transformProp] = transform; | ||
|
||
const order = ['left', 'right', 'top', 'bottom']; | ||
|
||
const check = { | ||
primary(placement: string) { | ||
let value = data.offsets.target[placement]; | ||
if ( | ||
data.offsets.target[placement] < boundaries[placement] && | ||
!false // options.escapeWithReference | ||
) { | ||
value = Math.max(data.offsets.target[placement], boundaries[placement]); | ||
} | ||
|
||
return { [placement]: value }; | ||
}, | ||
secondary(placement: string) { | ||
const mainSide = placement === 'right' ? 'left' : 'top'; | ||
let value = data.offsets.target[mainSide]; | ||
if ( | ||
data.offsets.target[placement] > boundaries[placement] && | ||
!false // escapeWithReference | ||
) { | ||
value = Math.min( | ||
data.offsets.target[mainSide], | ||
boundaries[placement] - | ||
(placement === 'right' ? data.offsets.target.width : data.offsets.target.height) | ||
); | ||
} | ||
|
||
return { [mainSide]: value }; | ||
} | ||
}; | ||
|
||
let side: string; | ||
|
||
order.forEach(placement => { | ||
side = ['left', 'top'] | ||
.indexOf(placement) !== -1 | ||
? 'primary' | ||
: 'secondary'; | ||
|
||
data.offsets.target = { ...data.offsets.target, ...check[side](placement) }; | ||
|
||
}); | ||
|
||
return data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Data } from '../models'; | ||
|
||
export function shift(data: Data): Data { | ||
const placement = data.placement; | ||
const basePlacement = placement.split(' ')[0]; | ||
const shiftvariation = placement.split(' ')[1]; | ||
|
||
if (shiftvariation) { | ||
const { host, target } = data.offsets; | ||
const isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1; | ||
const side = isVertical ? 'left' : 'top'; | ||
const measurement = isVertical ? 'width' : 'height'; | ||
|
||
const shiftOffsets = { | ||
left: { [side]: host[side] }, | ||
right: { | ||
[side]: host[side] + host[measurement] - host[measurement] | ||
} | ||
}; | ||
|
||
data.offsets.target = { ...target, ...shiftOffsets[shiftvariation] }; | ||
} | ||
|
||
return data; | ||
} |
Oops, something went wrong.