update ColorPicker

This commit is contained in:
梁灏 2017-08-17 17:25:38 +08:00
parent dab3947668
commit e7893a68ed
12 changed files with 994 additions and 26 deletions

View file

@ -0,0 +1,77 @@
<template>
<div class="ivu-color-picker-alpha">
<div class="ivu-color-picker-alpha-checkboard-wrap">
<div class="ivu-color-picker-alpha-checkerboard"></div>
</div>
<div class="ivu-color-picker-alpha-gradient" :style="{background: gradientColor}"></div>
<div class="ivu-color-picker-alpha-container" ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange">
<div class="ivu-color-picker-alpha-pointer" :style="{left: colors.a * 100 + '%'}">
<div class="ivu-color-picker-alpha-picker"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Alpha',
props: {
value: Object,
onChange: Function
},
computed: {
colors () {
return this.value;
},
gradientColor () {
const rgba = this.colors.rgba;
const rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
return 'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)';
}
},
methods: {
handleChange (e, skip) {
!skip && e.preventDefault();
const container = this.$refs.container;
const containerWidth = container.clientWidth;
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
const left = pageX - xOffset;
let a;
if (left < 0) {
a = 0;
} else if (left > containerWidth) {
a = 1;
} else {
a = Math.round(left * 100 / containerWidth) / 100;
}
if (this.colors.a !== a) {
this.$emit('change', {
h: this.colors.hsl.h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: a,
source: 'rgba'
});
}
},
handleMouseDown (e) {
this.handleChange(e, true);
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp () {
this.unbindEventListeners();
},
unbindEventListeners () {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
}
}
};
</script>

View file

@ -8,12 +8,14 @@
</div>
<Dropdown-menu slot="list">
<div :class="[prefixCls + '-picker']">
<div :class="[prefixCls + '-picker-panel']"></div>
<div :class="[prefixCls + '-picker-panel']">
<Saturation v-model="saturationColors" @change="childChange"></Saturation>
</div>
<div :class="[prefixCls + '-picker-hue-slider']">
<Slider v-model="hueNumber" :min="0" :max="255"></Slider>
<Hue v-model="saturationColors" @change="childChange"></Hue>
</div>
<div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']">
<Slider v-model="alphaNumber" :min="0" :max="100"></Slider>
<Alpha v-model="saturationColors" @change="childChange"></Alpha>
</div>
<recommend-colors v-if="colors.length" :list="colors" :class="[prefixCls + '-picker-colors']"></recommend-colors>
<recommend-colors v-if="!colors.length && recommend" :list="recommendedColor" :class="[prefixCls + '-picker-colors']"></recommend-colors>
@ -23,22 +25,76 @@
</Dropdown>
</template>
<script>
import tinycolor from 'tinycolor2';
import Dropdown from '../dropdown/dropdown.vue';
import DropdownMenu from '../dropdown/dropdown-menu.vue';
import Slider from '../slider/slider.vue';
import RecommendColors from './recommend-colors.vue';
import Confirm from '../date-picker/base/confirm.vue';
import Saturation from './saturation.vue';
import Hue from './hue.vue';
import Alpha from './alpha.vue';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-color-picker';
const inputPrefixCls = 'ivu-input';
function _colorChange (data, oldHue) {
const alpha = data && data.a;
let color;
// hsl is better than hex between conversions
if (data && data.hsl) {
color = tinycolor(data.hsl);
} else if (data && data.hex && data.hex.length > 0) {
color = tinycolor(data.hex);
} else {
color = tinycolor(data);
}
if (color && (color._a === undefined || color._a === null)) {
color.setAlpha(alpha || 1);
}
const hsl = color.toHsl();
const hsv = color.toHsv();
if (hsl.s === 0) {
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
}
// when the hsv.v is less than 0.0164 (base on test)
// because of possible loss of precision
// the result of hue and saturation would be miscalculated
if (hsv.v < 0.0164) {
hsv.h = data.h || (data.hsv && data.hsv.h) || 0;
hsv.s = data.s || (data.hsv && data.hsv.s) || 0;
}
if (hsl.l < 0.01) {
hsl.h = data.h || (data.hsl && data.hsl.h) || 0;
hsl.s = data.s || (data.hsl && data.hsl.s) || 0;
}
return {
hsl: hsl,
hex: color.toHexString().toUpperCase(),
rgba: color.toRgb(),
hsv: hsv,
oldHue: data.h || oldHue || hsl.h,
source: data.source,
a: data.a || color.getAlpha()
};
}
export default {
name: 'ColorPicker',
components: { Dropdown, DropdownMenu, Slider, Confirm, RecommendColors },
components: { Dropdown, DropdownMenu, Confirm, RecommendColors, Saturation, Hue, Alpha },
props: {
value: {
type: String
type: Object
},
alpha: {
type: Boolean,
@ -81,10 +137,8 @@
},
data () {
return {
val: _colorChange(this.value),
prefixCls: prefixCls,
currentValue: this.value,
hueNumber: 0,
alphaNumber: 0,
recommendedColor: [
'#2d8cf0',
'#19be6b',
@ -110,6 +164,15 @@
};
},
computed: {
saturationColors: {
get () {
return this.val;
},
set (newVal) {
this.val = newVal;
this.$emit('input', newVal);
}
},
wrapClasses () {
return [
`${prefixCls}-rel`,
@ -129,8 +192,41 @@
];
}
},
watch: {
value (newVal) {
this.val = _colorChange(newVal);
}
},
methods: {
childChange (data) {
this.colorChange(data);
},
colorChange (data, oldHue) {
this.oldHue = this.saturationColors.hsl.h;
this.saturationColors = _colorChange(data, oldHue || this.oldHue);
},
isValidHex (hex) {
return tinycolor(hex).isValid();
},
simpleCheckForValidColor (data) {
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
let checked = 0;
let passed = 0;
for (let i = 0; i < keysToCheck.length; i++) {
const letter = keysToCheck[i];
if (data[letter]) {
checked++;
if (!isNaN(data[letter])) {
passed++;
}
}
}
if (checked === passed) {
return data;
}
}
}
};
</script>

View file

@ -0,0 +1,86 @@
<template>
<div class="ivu-color-picker-hue">
<div class="ivu-color-picker-hue-container" ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange">
<div class="ivu-color-picker-hue-pointer" :style="{top: 0, left: pointerLeft}">
<div class="ivu-color-picker-hue-picker"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Hue',
props: {
value: Object
},
data () {
return {
oldHue: 0,
pullDirection: ''
};
},
computed: {
colors () {
const h = this.value.hsl.h;
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
this.oldHue = h;
return this.value;
},
pointerLeft () {
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
return (this.colors.hsl.h * 100) / 360 + '%';
}
},
methods: {
handleChange (e, skip) {
!skip && e.preventDefault();
const container = this.$refs.container;
const containerWidth = container.clientWidth;
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
const left = pageX - xOffset;
let h;
let percent;
if (left < 0) {
h = 0;
} else if (left > containerWidth) {
h = 360;
} else {
percent = left * 100 / containerWidth;
h = (360 * percent / 100);
}
if (this.colors.hsl.h !== h) {
this.$emit('change', {
h: h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: this.colors.hsl.a,
source: 'hsl'
});
}
},
handleMouseDown (e) {
this.handleChange(e, true);
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp () {
this.unbindEventListeners();
},
unbindEventListeners () {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
}
}
};
</script>

View file

@ -0,0 +1,98 @@
<template>
<div class="ivu-color-picker-saturation-wrapper">
<div
class="ivu-color-picker-saturation"
:style="{background: bgColor}"
ref="container"
@mousedown="handleMouseDown">
<div class="ivu-color-picker-saturation--white"></div>
<div class="ivu-color-picker-saturation--black"></div>
<div class="ivu-color-picker-saturation-pointer" :style="{top: pointerTop, left: pointerLeft}">
<div class="ivu-color-picker-saturation-circle"></div>
</div>
</div>
</div>
</template>
<script>
import throttle from '../../utils/throttle';
export default {
name: 'Saturation',
props: {
value: Object
},
data () {
return {};
},
computed: {
colors () {
return this.value;
},
bgColor () {
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
},
pointerTop () {
return (-(this.colors.hsv.v * 100) + 1) + 100 + '%';
},
pointerLeft () {
return this.colors.hsv.s * 100 + '%';
}
},
methods: {
throttle: throttle((fn, data) => {fn(data);}, 20,
{
'leading': true,
'trailing': false
}),
handleChange (e, skip) {
!skip && e.preventDefault();
const container = this.$refs.container;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
const pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
let left = pageX - xOffset;
let top = pageY - yOffset;
if (left < 0) {
left = 0;
} else if (left > containerWidth) {
left = containerWidth;
} else if (top < 0) {
top = 0;
} else if (top > containerHeight) {
top = containerHeight;
}
const saturation = left / containerWidth;
let bright = -(top / containerHeight) + 1;
bright = bright > 0 ? bright : 0;
bright = bright > 1 ? 1 : bright;
this.throttle(this.onChange, {
h: this.colors.hsv.h,
s: saturation,
v: bright,
a: this.colors.hsv.a,
source: 'hsva'
});
},
onChange (param) {
this.$emit('change', param);
},
handleMouseDown () {
// this.handleChange(e, true)
window.addEventListener('mousemove', this.handleChange);
window.addEventListener('mouseup', this.handleChange);
window.addEventListener('mouseup', this.handleMouseUp);
},
handleMouseUp () {
this.unbindEventListeners();
},
unbindEventListeners () {
window.removeEventListener('mousemove', this.handleChange);
window.removeEventListener('mouseup', this.handleChange);
window.removeEventListener('mouseup', this.handleMouseUp);
}
}
};
</script>

View file

@ -24,30 +24,31 @@
}
&-picker{
padding: 8px 8px 0;
padding: 4px 8px 0;
&-panel{
width: 200px;
height: 200px;
margin: 0 auto;
background: #47cb89;
border-radius: 50%;
box-sizing: initial;
position: relative;
}
&-hue-slider{
}
&-alpha-slider{
&-hue-slider, &-alpha-slider{
height: 10px;
margin-top: 8px;
position: relative;
}
&-colors{
margin-top: 8px;
overflow: hidden;
span{
display: inline-block;
width: 18px;
height: 18px;
width: 20px;
height: 20px;
float: left;
em{
display: block;
width: 16px;
height: 16px;
margin: 2px;
cursor: pointer;
border-radius: 2px;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.15);
@ -58,4 +59,121 @@
margin-top: 8px;
}
}
&-saturation{
&-wrapper{
width: 100%;
padding-bottom: 75%;
position: relative;
overflow: hidden;
}
&, &--white, &--black{
cursor: pointer;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
&--white{
background: linear-gradient(to right, #fff, rgba(255,255,255,0));
}
&--black{
background: linear-gradient(to top, #000, rgba(0,0,0,0));
}
&-pointer{
cursor: pointer;
position: absolute;
}
&-circle{
cursor: head;
width: 4px;
height: 4px;
box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0,0,0,.3), 0 0 1px 2px rgba(0,0,0,.4);
border-radius: 50%;
transform: translate(-2px, -2px);
}
}
&-hue{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: 2px;
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
&-container{
cursor: pointer;
margin: 0 2px;
position: relative;
height: 100%;
}
&-pointer{
z-index: 2;
position: absolute;
}
&-picker{
cursor: pointer;
margin-top: 1px;
width: 4px;
border-radius: 1px;
height: 8px;
box-shadow: 0 0 2px rgba(0, 0, 0, .6);
background: #fff;
transform: translateX(-2px);
}
}
&-alpha{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
&-checkboard-wrap{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
&-checkerboard{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
&-gradient{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&-container{
cursor: pointer;
position: relative;
z-index: 2;
height: 100%;
margin: 0 3px;
}
&-pointer{
z-index: 2;
position: absolute;
}
&-picker{
cursor: pointer;
width: 4px;
border-radius: 1px;
height: 8px;
box-shadow: 0 0 2px rgba(0, 0, 0, .6);
background: #fff;
margin-top: 1px;
transform: translateX(-2px);
}
}
}

439
src/utils/throttle.js Normal file
View file

@ -0,0 +1,439 @@
/**
* lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';
/** Used as references for various `Number` constants. */
var NAN = 0 / 0;
/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';
/** Used to match leading and trailing whitespace. */
var reTrim = /^\s+|\s+$/g;
/** Used to detect bad signed hexadecimal string values. */
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
/** Used to detect binary string values. */
var reIsBinary = /^0b[01]+$/i;
/** Used to detect octal string values. */
var reIsOctal = /^0o[0-7]+$/i;
/** Built-in method references without a dependency on `root`. */
var freeParseInt = parseInt;
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();
/** Used for built-in method references. */
var objectProto = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString = objectProto.toString;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max,
nativeMin = Math.min;
/**
* Gets the timestamp of the number of milliseconds that have elapsed since
* the Unix epoch (1 January 1970 00:00:00 UTC).
*
* @static
* @memberOf _
* @since 2.4.0
* @category Date
* @returns {number} Returns the timestamp.
* @example
*
* _.defer(function(stamp) {
* console.log(_.now() - stamp);
* }, _.now());
* // => Logs the number of milliseconds it took for the deferred invocation.
*/
var now = function() {
return root.Date.now();
};
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked. The debounced function comes with a `cancel` method to cancel
* delayed `func` invocations and a `flush` method to immediately invoke them.
* Provide `options` to indicate whether `func` should be invoked on the
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
* with the last arguments provided to the debounced function. Subsequent
* calls to the debounced function return the result of the last `func`
* invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the debounced function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.debounce` and `_.throttle`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0] The number of milliseconds to delay.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=false]
* Specify invoking on the leading edge of the timeout.
* @param {number} [options.maxWait]
* The maximum time `func` is allowed to be delayed before it's invoked.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // Avoid costly calculations while the window size is in flux.
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
* jQuery(element).on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* }));
*
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
* var source = new EventSource('/stream');
* jQuery(source).on('message', debounced);
*
* // Cancel the trailing debounced invocation.
* jQuery(window).on('popstate', debounced.cancel);
*/
function debounce(func, wait, options) {
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = toNumber(wait) || 0;
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait);
// Invoke the leading edge.
return leading ? invokeFunc(time) : result;
}
function remainingWait(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
result = wait - timeSinceLastCall;
return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
}
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
}
function timerExpired() {
var time = now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time));
}
function trailingEdge(time) {
timerId = undefined;
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time);
}
lastArgs = lastThis = undefined;
return result;
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
function flush() {
return timerId === undefined ? result : trailingEdge(now());
}
function debounced() {
var time = now(),
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time;
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
/**
* Creates a throttled function that only invokes `func` at most once per
* every `wait` milliseconds. The throttled function comes with a `cancel`
* method to cancel delayed `func` invocations and a `flush` method to
* immediately invoke them. Provide `options` to indicate whether `func`
* should be invoked on the leading and/or trailing edge of the `wait`
* timeout. The `func` is invoked with the last arguments provided to the
* throttled function. Subsequent calls to the throttled function return the
* result of the last `func` invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the throttled function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.throttle` and `_.debounce`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to throttle.
* @param {number} [wait=0] The number of milliseconds to throttle invocations to.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=true]
* Specify invoking on the leading edge of the timeout.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new throttled function.
* @example
*
* // Avoid excessively updating the position while scrolling.
* jQuery(window).on('scroll', _.throttle(updatePosition, 100));
*
* // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
* var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
* jQuery(element).on('click', throttled);
*
* // Cancel the trailing throttled invocation.
* jQuery(window).on('popstate', throttled.cancel);
*/
function throttle(func, wait, options) {
var leading = true,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
return debounce(func, wait, {
'leading': leading,
'maxWait': wait,
'trailing': trailing
});
}
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/**
* Checks if `value` is classified as a `Symbol` primitive or object.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
* @example
*
* _.isSymbol(Symbol.iterator);
* // => true
*
* _.isSymbol('abc');
* // => false
*/
function isSymbol(value) {
return typeof value == 'symbol' ||
(isObjectLike(value) && objectToString.call(value) == symbolTag);
}
/**
* Converts `value` to a number.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to process.
* @returns {number} Returns the number.
* @example
*
* _.toNumber(3.2);
* // => 3.2
*
* _.toNumber(Number.MIN_VALUE);
* // => 5e-324
*
* _.toNumber(Infinity);
* // => Infinity
*
* _.toNumber('3.2');
* // => 3.2
*/
function toNumber(value) {
if (typeof value == 'number') {
return value;
}
if (isSymbol(value)) {
return NAN;
}
if (isObject(value)) {
var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
value = isObject(other) ? (other + '') : other;
}
if (typeof value != 'string') {
return value === 0 ? value : +value;
}
value = value.replace(reTrim, '');
var isBinary = reIsBinary.test(value);
return (isBinary || reIsOctal.test(value))
? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: (reIsBadHex.test(value) ? NAN : +value);
}
module.exports = throttle;