blob: 4e77430ea0a6fdecaf233041d561254943bdf000 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// @flow
import {
isArray
} from './type'
/**
* Mix properties into target object.
* the rightest object's value has the highest priority.
*/
export function extend (to: {}, ...args: Array<{}>): {} {
if (!args || args.length <= 0) {
return to
}
args.forEach(from => {
if (typeof from !== 'object') {
return
}
for (const key in from) {
to[key] = from[key]
}
})
return to
}
/**
* Mix truthy or '' property values into target object.
* mostly for merging styles. (that's why '' is falsy but still should be counted in.)
* the rightest object's value has the highest priority.
*/
export function extendTruthy (to: {}, ...args: Array<{}>): {} {
if (!args || args.length <= 0) {
return to
}
args.forEach(from => {
if (typeof from !== 'object') {
return
}
let i
for (const key in from) {
if (((i = from[key]) || i === '' || i === 0) && i !== 'undefined') {
to[key] = i
}
}
})
return to
}
/**
* Mix specified properties into target object.
*/
export function extendKeys (to: {}, from: {} = {}, keys: Array<string>): {} {
(keys || []).forEach(key => {
from && (to[key] = from[key])
})
return to
}
/**
* Extract specified properties from src to target object.
*/
export function extractKeys (to: {}, from: {} = {}, keys: Array<string>) {
if (!from) {
return to
}
(keys || []).forEach(key => {
from && (to[key] = from[key])
from && (delete from[key])
})
return to
}
/**
* Simple bind, faster than native
*
* @param {Function} fn
* @param {Object} ctx
* @return {Function}
*/
export function bind (fn: Function, ctx: mixed) {
return function (a: mixed) {
const l = arguments.length
return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx)
}
}
/**
* Only call the func the last time before it's not that frequently called.
*/
export function debounce (func: Function, wait: number) {
let timerId
function later () {
timerId = null
func.apply(null)
}
return function () {
clearTimeout(timerId)
timerId = setTimeout(later, wait)
}
}
/**
* Only call the func the first time before a series frequently function calls happen.
*/
export function depress (func: Function, wait: number) {
let timerId
function later () {
timerId = null
}
return function () {
if (!timerId) {
func.apply()
}
clearTimeout(timerId)
timerId = setTimeout(later, wait)
}
}
/**
* Only call the func every time after a wait milliseconds if it's too frequently called.
*/
export function throttle (func: Function, wait: number, callLastTime: boolean) {
let last = 0
let lastTimer = null
const lastTimeDuration = wait + (wait > 25 ? wait : 25) // plus half wait time.
return function (...args: Array<mixed>) {
const context = this
const time = new Date().getTime()
if (time - last > wait) {
if (callLastTime) {
lastTimer && clearTimeout(lastTimer)
lastTimer = setTimeout(function () {
lastTimer = null
func.apply(context, args)
}, lastTimeDuration)
}
func.apply(context, args)
last = time
}
}
}
// direction: 'l' | 'r', default is 'r'
// num: how many times to loop, should be a positive integer
export function loopArray (arr: any, num: number, direction: string) {
if (!isArray(arr)) {
return
}
let isLeft = (direction + '').toLowerCase() === 'l'
const len = arr.length
num = num % len
if (num < 0) {
num = -num
isLeft = !isLeft
}
if (num === 0) {
return arr
}
let lp, rp
if (isLeft) {
lp = arr.slice(0, num)
rp = arr.slice(num)
}
else {
lp = arr.slice(0, len - num)
rp = arr.slice(len - num)
}
return rp.concat(lp)
}
/**
* Create a cached version of a pure function.
*/
export function cached (fn: any) {
const cache = Object.create(null)
return function cachedFn (str: string) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
/**
* Camelize a hyphen-delmited string.
*/
const camelizeRE = /-(\w)/g
export const camelize = cached(str => {
return str.replace(camelizeRE, (_, c) => c.toUpperCase())
})
export function camelizeKeys (obj: any) {
const res = {}
for (const key in obj) {
res[camelize(key)] = obj[key]
}
return res
}
/**
* Capitalize a string.
*/
export const capitalize = cached(str => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
/**
* Hyphenate a camelCase string.
*/
const hyphenateRE = /([^-])([A-Z])/g
export const hyphenate = cached(str => {
return str.replace(hyphenateRE, '$1-$2').replace(hyphenateRE, '$1-$2').toLowerCase()
})
export function hyphenateKeys (obj: any) {
const res = {}
for (const key in obj) {
res[hyphenate(key)] = obj[key]
}
return res
}
const vendorsReg = /webkit-|moz-|o-|ms-/
export function hyphenateStyleKeys (obj: any) {
const res = {}
for (const key in obj) {
const hk = hyphenate(key).replace(vendorsReg, function ($0) {
return '-' + $0
})
res[hk] = obj[key]
}
return res
}
export function camelToKebab (name: string) {
if (!name) {
return ''
}
return name.replace(/([A-Z])/g, function (g, g1) {
return `-${g1.toLowerCase()}`
})
}
export function appendCss (css: string, cssId: string, replace: boolean) {
let style: any = document.getElementById(cssId)
if (style && replace) {
style.parentNode.removeChild(style)
style = null
}
if (!style) {
style = document.createElement('style')
style.type = 'text/css'
cssId && (style.id = cssId)
document.getElementsByTagName('head')[0].appendChild(style)
}
style.appendChild(document.createTextNode(css))
}
export function nextFrame (callback: any) {
const runner = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| (cb => setTimeout(cb, 16))
runner(callback)
}
export function toCSSText (object: any) {
if (!object) {
return
}
const obj = hyphenateStyleKeys(object)
let cssText = ''
for (const key in obj) {
cssText += `${key}:${obj[key]};`
}
return cssText
}