@pluginjs/styled
Version:
A flexible modern styled js plugin.
439 lines (361 loc) • 9.54 kB
JavaScript
import { curryWith, camelize, dasherize } from '@pluginjs/utils'
import {
isObject,
isElement,
isNumeric,
isString,
isWindow,
isArray,
isDocument
} from '@pluginjs/is'
import { offsetParent } from '@pluginjs/dom'
export const getDefaultView = el => {
let view = el.ownerDocument.defaultView
if (!view || !view.opener) {
view = window
}
return view
}
const isCssNumber = name => {
return ![
'animationIterationCount',
'columnCount',
'fillOpacity',
'flexGrow',
'flexShrink',
'fontWeight',
'lineHeight',
'opacity',
'order',
'orphans',
'widows',
'zIndex',
'zoom'
].includes(name)
}
export const isCSSVariable = name => {
return /^--/.test(name)
}
export const setStyle = (key, value, el) => {
if (isString(key) && isElement(el)) {
if (value || value === 0) {
if (isCSSVariable(key)) {
el.style.setProperty(key, value)
} else {
key = camelize(key, false)
if (isNumeric(value) && isCssNumber(key)) {
value += 'px'
}
el.style[key] = value
}
} else {
el.style.removeProperty(dasherize(key))
}
} else if (isObject(key)) {
if (isElement(value) && typeof el === 'undefined') {
el = value
value = undefined
}
let prop
for (prop in key) {
if (Object.prototype.hasOwnProperty.call(key, prop)) {
setStyle(prop, key[prop], el)
}
}
}
return el
}
export const getStyle = (key, el) => {
let value
if (isArray(key)) {
value = {}
key.forEach(k => {
value[k] = getStyle(k, el)
})
return value
}
if (!isCSSVariable(key)) {
key = dasherize(key)
}
value = getDefaultView(el).getComputedStyle(el, '').getPropertyValue(key)
return isNumeric(value) ? parseFloat(value) : value
}
export const css = curryWith((key, value, el) => {
if (isElement(value) && typeof el === 'undefined') {
el = value
value = undefined
}
if (typeof key === 'string' && typeof value === 'undefined') {
return getStyle(key, el)
}
return setStyle(key, value, el)
}, isElement)
// ----------
// Dimensions
// ----------
export const outerHeight = (includeMargins, el, isFloat = false) => {
if (isElement(includeMargins) && typeof el === 'undefined') {
el = includeMargins
includeMargins = false
}
if (isWindow(el)) {
return el.outerHeight
}
const offsetHeight = isFloat
? parseFloat(getStyle('height', el), 10)
: el.offsetHeight
if (includeMargins) {
let { marginTop, marginBottom } = getStyle(
['marginTop', 'marginBottom'],
el
)
marginTop = isFloat ? parseFloat(marginTop, 10) : parseInt(marginTop, 10)
marginBottom = isFloat
? parseFloat(marginBottom, 10)
: parseInt(marginBottom, 10)
return marginTop + marginBottom + offsetHeight
}
return offsetHeight
}
export const outerWidth = (includeMargins, el, isFloat = false) => {
if (isElement(includeMargins) && typeof el === 'undefined') {
el = includeMargins
includeMargins = false
}
if (isWindow(el)) {
return el.outerWidth
}
const offsetWidth = isFloat
? parseFloat(getStyle('width', el), 10)
: el.offsetWidth
if (includeMargins) {
let { marginLeft, marginRight } = getStyle(
['marginLeft', 'marginRight'],
el
)
marginLeft = isFloat ? parseFloat(marginLeft, 10) : parseInt(marginLeft, 10)
marginRight = isFloat
? parseFloat(marginRight, 10)
: parseInt(marginRight, 10)
return marginLeft + marginRight + offsetWidth
}
return offsetWidth
}
export const innerWidth = el => {
if (isWindow(el)) {
return el.innerWidth
}
return el.clientWidth
}
export const innerHeight = el => {
if (isWindow(el)) {
return el.innerHeight
}
return el.clientHeight
}
export const getWidth = el => {
if (isWindow(el)) {
return el.innerWidth
}
if (isDocument(el)) {
const doc = el.documentElement
return Math.max(
el.body.scrollWidth,
doc.scrollWidth,
el.body.offsetWidth,
doc.offsetWidth,
doc.clientWidth
)
}
let { width } = el.getBoundingClientRect()
const { paddingLeft, paddingRight } = getStyle(
['paddingLeft', 'paddingRight'],
el
)
width = width - parseInt(paddingLeft, 10) - parseInt(paddingRight, 10)
if (getStyle('boxSizing', el) === 'border-box') {
const { borderLeftWidth, borderRightWidth } = getStyle(
['borderLeftWidth', 'borderRightWidth'],
el
)
width =
width - parseInt(borderLeftWidth, 10) - parseInt(borderRightWidth, 10)
}
return width
}
export const setWidth = (value, el) => {
if (el.nodeType !== 1) {
return el
}
if (getStyle('boxSizing', el) === 'border-box') {
const {
paddingLeft,
paddingRight,
borderLeftWidth,
borderRightWidth
} = getStyle(
['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'],
el
)
value += paddingLeft + paddingRight + borderLeftWidth + borderRightWidth
}
return setStyle('width', value, el)
}
export const width = (value, el) => {
if (isElement(value) && typeof el === 'undefined') {
el = value
value = undefined
}
if (typeof value === 'undefined') {
return getWidth(el)
}
return setWidth(value, el)
}
export const getHeight = el => {
if (isWindow(el)) {
return el.innerHeight
}
if (isDocument(el)) {
const doc = el.documentElement
return Math.max(
el.body.scrollHeight,
doc.scrollHeight,
el.body.offsetHeight,
doc.offsetHeight,
doc.clientHeight
)
}
let { height } = el.getBoundingClientRect()
const { paddingTop, paddingBottom } = getStyle(
['paddingTop', 'paddingBottom'],
el
)
height = height - parseInt(paddingTop, 10) - parseInt(paddingBottom, 10)
if (getStyle('boxSizing', el) === 'border-box') {
const borderStyles = getStyle(['borderTopHeight', 'borderBottomHeight'], el)
const borderTopHeight = borderStyles.borderTopHeight || 0
const borderBottomHeight = borderStyles.borderBottomHeight || 0
height =
height - parseInt(borderTopHeight, 10) - parseInt(borderBottomHeight, 10)
}
return height
}
export const setHeight = (value, el) => {
if (el.nodeType !== 1) {
return el
}
if (getStyle('boxSizing', el) === 'border-box') {
const {
paddingTop,
paddingBottom,
borderTopHeight,
borderBottomHeight
} = getStyle(
['paddingTop', 'paddingBottom', 'borderTopHeight', 'borderBottomHeight'],
el
)
value += paddingTop + paddingBottom + borderTopHeight + borderBottomHeight
}
return setStyle('height', value, el)
}
export const height = (value, el) => {
if (isElement(value) && typeof el === 'undefined') {
el = value
value = undefined
}
if (typeof value === 'undefined') {
return getHeight(el)
}
return setHeight(value, el)
}
// ----------
// Offset
// ----------
export const getOffset = el => {
const box = el.getBoundingClientRect()
const win = getDefaultView(el)
return {
top: box.top + win.pageYOffset,
left: box.left + win.pageXOffset
}
}
export const setOffset = (coordinates, el) => {
const props = {}
const parentOffset = offset(offsetParent(el))
if (typeof coordinates.top !== 'undefined') {
props.top = coordinates.top - parentOffset.top
}
if (typeof coordinates.left !== 'undefined') {
props.left = coordinates.left - parentOffset.left
}
if (getStyle('position', el) === 'static') {
props.position = 'relative'
}
return setStyle(props, el)
}
export const offset = (coordinates, el) => {
if (isElement(coordinates) && typeof el === 'undefined') {
el = coordinates
coordinates = undefined
}
if (coordinates) {
return setOffset(coordinates, el)
}
return getOffset(el)
}
export const position = el => {
let parentOffset = { top: 0, left: 0 }
let coords
if (getStyle('position', el) === 'fixed') {
coords = el.getBoundingClientRect()
} else {
coords = offset(el)
const parent = offsetParent(el)
if (parent && parent !== el && parent.nodeType === 1) {
parentOffset = offset(parent)
parentOffset.top += parseFloat(parent.style.borderTopWidth) || 0
parentOffset.left += parseFloat(parent.style.borderLeftWidth) || 0
}
}
coords.top -= parseFloat(el.style.marginTop) || 0
coords.left -= parseFloat(el.style.marginLeft) || 0
return {
top: coords.top - parentOffset.top,
left: coords.left - parentOffset.left
}
}
// -------------
// Show and Hide
// -------------
const defaultDisplayMap = {}
const getDefaultDisplay = nodeName => {
let display
let element
if (!defaultDisplayMap[nodeName]) {
element = document.createElement(nodeName)
document.body.appendChild(element)
display = getComputedStyle(element, '').getPropertyValue('display')
element.parentNode.removeChild(element)
if (display === 'none') {
display = 'block'
}
defaultDisplayMap[nodeName] = display
}
return defaultDisplayMap[nodeName]
}
export const hideElement = el => {
if (el.style.display !== 'none') {
el.style.display = 'none'
}
return el
}
export const showElement = el => {
if (el.style.display === 'none') {
el.style.display = ''
}
if (getComputedStyle(el, '').getPropertyValue('display') === 'none') {
// is hidden within tree
el.style.display = getDefaultDisplay(el.nodeName)
}
return el
}