vuikit
Version:
A Vuejs component library based on UIkit
318 lines (246 loc) • 7.31 kB
JavaScript
/**
* Vuikit 0.7.0
* (c) 2018 Miljan Aleksic
* @license MIT
*/
import {
each,
toFloat,
toArray,
includes,
isWindow,
endsWith,
toCapital,
isDocument,
isUndefined
} from 'vuikit/core/util'
import css from 'vuikit/core/helpers/css'
const dirs = {
width: ['x', 'left', 'right'],
height: ['y', 'top', 'bottom']
}
export const height = dimension('height')
export const width = dimension('width')
export function positionAt ({ element, target, elAttach, targetAttach, elOffset, targetOffset, flip, boundary }) {
elAttach = getPos(elAttach)
targetAttach = getPos(targetAttach)
var flipped = { element: elAttach, target: targetAttach }
if (!element || !target) {
return flipped
}
var dim = getDimensions(element)
var targetDim = getDimensions(target)
var position = targetDim
moveTo(position, elAttach, dim, -1)
moveTo(position, targetAttach, targetDim, 1)
elOffset = getOffsets(elOffset, dim.width, dim.height)
targetOffset = getOffsets(targetOffset, targetDim.width, targetDim.height)
elOffset['x'] += targetOffset['x']
elOffset['y'] += targetOffset['y']
position.left += elOffset['x']
position.top += elOffset['y']
boundary = getDimensions(boundary || getWindow(element))
if (flip) {
each(dirs, ([dir, align, alignFlip], prop) => {
if (!(flip === true || includes(flip, dir))) {
return
}
var elemOffset = elAttach[dir] === align
? -dim[prop]
: elAttach[dir] === alignFlip
? dim[prop]
: 0
var targetOffset = targetAttach[dir] === align
? targetDim[prop]
: targetAttach[dir] === alignFlip
? -targetDim[prop]
: 0
if (position[align] < boundary[align] || position[align] + dim[prop] > boundary[alignFlip]) {
var centerOffset = dim[prop] / 2
var centerTargetOffset = targetAttach[dir] === 'center' ? -targetDim[prop] / 2 : 0
if (elAttach[dir] === 'center') {
apply(centerOffset, centerTargetOffset) || apply(-centerOffset, -centerTargetOffset)
} else {
apply(elemOffset, targetOffset)
}
}
function apply (elemOffset, targetOffset) {
var newVal = position[align] + elemOffset + targetOffset - elOffset[dir] * 2
if (newVal >= boundary[align] && newVal + dim[prop] <= boundary[alignFlip]) {
position[align] = newVal
;['element', 'target'].forEach(el => {
flipped[el][dir] = !elemOffset
? flipped[el][dir]
: flipped[el][dir] === dirs[prop][1]
? dirs[prop][2]
: dirs[prop][1]
})
return true
}
}
})
}
offset(element, position)
return flipped
}
export function offset (element, coordinates) {
if (coordinates) {
var currentOffset = offset(element)
var pos = css(element, 'position')
;['left', 'top'].forEach(prop => {
if (prop in coordinates) {
var value = css(element, prop)
element.style[prop] = `${(coordinates[prop] - currentOffset[prop]) +
toFloat(pos === 'absolute' && value === 'auto' ? position(element)[prop] : value)
}px`
}
})
return
}
return getDimensions(element)
}
function getDimensions (element) {
var { pageYOffset: top, pageXOffset: left } = getWindow(element)
if (isWindow(element)) {
var height = element.innerHeight
var width = element.innerWidth
return {
top,
left,
height,
width,
bottom: top + height,
right: left + width
}
}
var display = false
if (!isVisible(element)) {
display = element.style.display
element.style.display = 'block'
}
var rect = element.getBoundingClientRect()
if (display !== false) {
element.style.display = display
}
return {
height: rect.height,
width: rect.width,
top: rect.top + top,
left: rect.left + left,
bottom: rect.bottom + top,
right: rect.right + left
}
}
export function position (element) {
var parent = offsetParent(element)
var parentOffset = parent === docEl(element) ? {top: 0, left: 0} : offset(parent)
return ['top', 'left'].reduce((props, prop) => {
var propName = toCapital(prop)
props[prop] -= parentOffset[prop] +
(toFloat(css(element, `margin${propName}`)) || 0) +
(toFloat(css(parent, `border${propName}Width`)) || 0)
return props
}, offset(element))
}
function offsetParent (element) {
var parent = element.offsetParent
while (parent && css(parent, 'position') === 'static') {
parent = parent.offsetParent
}
return parent || docEl(element)
}
function dimension (prop) {
var propName = toCapital(prop)
return (element, value) => {
if (isUndefined(value)) {
if (isWindow(element)) {
return element[`inner${propName}`]
}
if (isDocument(element)) {
var doc = element.documentElement
return Math.max(doc.offsetHeight, doc.scrollHeight)
}
value = css(element, prop)
value = value === 'auto' ? element[`offset${propName}`] : toFloat(value) || 0
return getContentSize(prop, element, value)
} else {
css(element, prop, !value && value !== 0
? ''
: getContentSize(prop, element, value) + 'px'
)
}
}
}
function getContentSize (prop, element, value) {
return css(element, 'boxSizing') === 'border-box' ? dirs[prop].slice(1).map(toCapital).reduce((value, prop) =>
value -
toFloat(css(element, `padding${prop}`)) -
toFloat(css(element, `border${prop}Width`))
, value) : value
}
function getWindow (element) {
return isWindow(element)
? element
: document(element).defaultView
}
function moveTo (position, attach, dim, factor) {
each(dirs, function ([dir, align, alignFlip], prop) {
if (attach[dir] === alignFlip) {
position[align] += dim[prop] * factor
} else if (attach[dir] === 'center') {
position[align] += dim[prop] * factor / 2
}
})
}
function getPos (pos) {
var x = /left|center|right/
var y = /top|center|bottom/
pos = (pos || '').split(' ')
if (pos.length === 1) {
pos = x.test(pos[0])
? pos.concat(['center'])
: y.test(pos[0])
? ['center'].concat(pos)
: ['center', 'center']
}
return {
x: x.test(pos[0]) ? pos[0] : 'center',
y: y.test(pos[1]) ? pos[1] : 'center'
}
}
function getOffsets (offsets, width, height) {
var [x, y] = (offsets || '').split(' ')
return {
x: x ? toFloat(x) * (endsWith(x, '%') ? width / 100 : 1) : 0,
y: y ? toFloat(y) * (endsWith(y, '%') ? height / 100 : 1) : 0
}
}
export function flipPosition (pos) {
switch (pos) {
case 'left':
return 'right'
case 'right':
return 'left'
case 'top':
return 'bottom'
case 'bottom':
return 'top'
default:
return pos
}
}
export function getPositionAxis (position) {
const [dir] = position.split('-')
return dir === 'top' || dir === 'bottom'
? 'y'
: 'x'
}
function document (element) {
return element.ownerDocument
}
function docEl (element) {
return document(element).documentElement
}
function isVisible (element) {
return toArray(element).some(element => element.offsetHeight)
}