vue-uiv
Version:
Bootstrap 3 components implemented by Vue 2.
320 lines (295 loc) • 10.3 kB
JavaScript
import {isExist} from './objectUtils'
export const EVENTS = {
MOUSE_ENTER: 'mouseenter',
MOUSE_LEAVE: 'mouseleave',
FOCUS: 'focus',
BLUR: 'blur',
CLICK: 'click',
INPUT: 'input',
KEY_DOWN: 'keydown',
KEY_UP: 'keyup',
KEY_PRESS: 'keypress',
RESIZE: 'resize',
SCROLL: 'scroll'
}
export const TRIGGERS = {
CLICK: 'click',
HOVER: 'hover',
FOCUS: 'focus',
HOVER_FOCUS: 'hover-focus',
OUTSIDE_CLICK: 'outside-click',
MANUAL: 'manual'
}
export const PLACEMENTS = {
TOP: 'top',
RIGHT: 'right',
BOTTOM: 'bottom',
LEFT: 'left'
}
export function isIE11 () {
return !!window.MSInputMethodContext && !!document.documentMode
}
export function isIE10 () {
return window.navigator.appVersion.indexOf('MSIE 10') !== -1
}
export function getComputedStyle (el) {
return window.getComputedStyle(el)
}
export function getViewportSize () {
let width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
let height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
return {width, height}
}
let scrollbarWidth = null
let savedScreenSize = null
export function getScrollbarWidth (recalculate = false) {
let screenSize = getViewportSize()
// return directly when already calculated & not force recalculate & screen size not changed
if (scrollbarWidth !== null && !recalculate &&
screenSize.height === savedScreenSize.height && screenSize.width === savedScreenSize.width) {
return scrollbarWidth
}
if (document.readyState === 'loading') {
return null
}
const div1 = document.createElement('div')
const div2 = document.createElement('div')
div1.style.width = div2.style.width = div1.style.height = div2.style.height = '100px'
div1.style.overflow = 'scroll'
div2.style.overflow = 'hidden'
document.body.appendChild(div1)
document.body.appendChild(div2)
scrollbarWidth = Math.abs(div1.scrollHeight - div2.scrollHeight)
document.body.removeChild(div1)
document.body.removeChild(div2)
// save new screen size
savedScreenSize = screenSize
return scrollbarWidth
}
export function on (element, event, handler) {
element.addEventListener(event, handler)
}
export function off (element, event, handler) {
element.removeEventListener(event, handler)
}
export function isElement (el) {
return el && el.nodeType === Node.ELEMENT_NODE
}
export function removeFromDom (el) {
isElement(el) && isElement(el.parentNode) && el.parentNode.removeChild(el)
}
export function ensureElementMatchesFunction () {
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function (s) {
let matches = (this.document || this.ownerDocument).querySelectorAll(s)
let i = matches.length
while (--i >= 0 && matches.item(i) !== this) {
}
return i > -1
}
}
}
export function addClass (el, className) {
if (!isElement(el)) {
return
}
if (el.className) {
let classes = el.className.split(' ')
if (classes.indexOf(className) < 0) {
classes.push(className)
el.className = classes.join(' ')
}
} else {
el.className = className
}
}
export function removeClass (el, className) {
if (!isElement(el)) {
return
}
if (el.className) {
let classes = el.className.split(' ')
let newClasses = []
for (let i = 0, l = classes.length; i < l; i++) {
if (classes[i] !== className) {
newClasses.push(classes[i])
}
}
el.className = newClasses.join(' ')
}
}
export function hasClass (el, className) {
if (!isElement(el)) {
return false
}
let classes = el.className.split(' ')
for (let i = 0, l = classes.length; i < l; i++) {
if (classes[i] === className) {
return true
}
}
return false
}
export function setDropdownPosition (dropdown, trigger, options = {}) {
let doc = document.documentElement
let containerScrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0)
let containerScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
let rect = trigger.getBoundingClientRect()
let dropdownRect = dropdown.getBoundingClientRect()
dropdown.style.right = 'auto'
dropdown.style.bottom = 'auto'
if (options.menuRight) {
dropdown.style.left = containerScrollLeft + rect.left + rect.width - dropdownRect.width + 'px'
} else {
dropdown.style.left = containerScrollLeft + rect.left + 'px'
}
if (options.dropup) {
dropdown.style.top = containerScrollTop + rect.top - dropdownRect.height - 4 + 'px'
} else {
dropdown.style.top = containerScrollTop + rect.top + rect.height + 'px'
}
}
export function isAvailableAtPosition (trigger, popup, placement) {
let triggerRect = trigger.getBoundingClientRect()
let popupRect = popup.getBoundingClientRect()
let viewPortSize = getViewportSize()
let top = true
let right = true
let bottom = true
let left = true
switch (placement) {
case PLACEMENTS.TOP:
top = triggerRect.top >= popupRect.height
left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2
right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width
break
case PLACEMENTS.BOTTOM:
bottom = triggerRect.bottom + popupRect.height <= viewPortSize.height
left = triggerRect.left + triggerRect.width / 2 >= popupRect.width / 2
right = triggerRect.right - triggerRect.width / 2 + popupRect.width / 2 <= viewPortSize.width
break
case PLACEMENTS.RIGHT:
right = triggerRect.right + popupRect.width <= viewPortSize.width
top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2
bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height
break
case PLACEMENTS.LEFT:
left = triggerRect.left >= popupRect.width
top = triggerRect.top + triggerRect.height / 2 >= popupRect.height / 2
bottom = triggerRect.bottom - triggerRect.height / 2 + popupRect.height / 2 <= viewPortSize.height
break
}
return top && right && bottom && left
}
export function setTooltipPosition (tooltip, trigger, placement, auto, appendToSelector) {
let container
let containerScrollTop
let containerScrollLeft
if (!isExist(appendToSelector) || appendToSelector === 'body') {
container = document.body
let doc = document.documentElement
containerScrollLeft = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0)
containerScrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
} else {
container = document.querySelector(appendToSelector)
containerScrollLeft = container.scrollLeft
containerScrollTop = container.scrollTop
}
// auto adjust placement
if (auto) {
// Try: right -> bottom -> left -> top
// Cause the default placement is top
let placements = [PLACEMENTS.RIGHT, PLACEMENTS.BOTTOM, PLACEMENTS.LEFT, PLACEMENTS.TOP]
// The class switch helper function
const changePlacementClass = (placement) => {
// console.log(placement)
placements.forEach(placement => {
removeClass(tooltip, placement)
})
addClass(tooltip, placement)
}
// No need to adjust if the default placement fits
if (!isAvailableAtPosition(trigger, tooltip, placement)) {
for (let i = 0, l = placements.length; i < l; i++) {
// Re-assign placement class
changePlacementClass(placements[i])
// Break if new placement fits
if (isAvailableAtPosition(trigger, tooltip, placements[i])) {
placement = placements[i]
break
}
}
changePlacementClass(placement)
}
}
// fix left and top for tooltip
let rect = trigger.getBoundingClientRect()
let tooltipRect = tooltip.getBoundingClientRect()
if (placement === PLACEMENTS.BOTTOM) {
tooltip.style.top = containerScrollTop + rect.top + rect.height + 'px'
tooltip.style.left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2 + 'px'
} else if (placement === PLACEMENTS.LEFT) {
tooltip.style.top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2 + 'px'
tooltip.style.left = containerScrollLeft + rect.left - tooltipRect.width + 'px'
} else if (placement === PLACEMENTS.RIGHT) {
tooltip.style.top = containerScrollTop + rect.top + rect.height / 2 - tooltipRect.height / 2 + 'px'
tooltip.style.left = containerScrollLeft + rect.left + rect.width + 'px'
} else {
tooltip.style.top = containerScrollTop + rect.top - tooltipRect.height + 'px'
tooltip.style.left = containerScrollLeft + rect.left + rect.width / 2 - tooltipRect.width / 2 + 'px'
}
}
export function hasScrollbar (el) {
const SCROLL = 'scroll'
const hasVScroll = el.scrollHeight > el.clientHeight
const style = getComputedStyle(el)
return hasVScroll || style.overflow === SCROLL || style.overflowY === SCROLL
}
export function toggleBodyOverflow (enable) {
const MODAL_OPEN = 'modal-open'
const body = document.body
if (enable) {
removeClass(body, MODAL_OPEN)
body.style.paddingRight = null
} else {
const browsersWithFloatingScrollbar = isIE10() || isIE11()
const documentHasScrollbar = hasScrollbar(document.documentElement) || hasScrollbar(document.body)
if (documentHasScrollbar && !browsersWithFloatingScrollbar) {
body.style.paddingRight = `${getScrollbarWidth()}px`
}
addClass(body, MODAL_OPEN)
}
}
export function getClosest (el, selector) {
ensureElementMatchesFunction()
let parent
let _el = el
while (_el) {
parent = _el.parentElement
if (parent && parent.matches(selector)) {
return parent
}
_el = parent
}
return null
}
export function getParents (el, selector, until = null) {
ensureElementMatchesFunction()
let parents = []
let parent = el.parentElement
while (parent) {
if (parent.matches(selector)) {
parents.push(parent)
} else if (until && (until === parent || parent.matches(until))) {
break
}
parent = parent.parentElement
}
return parents
}