/* global: defineProperty */
|
import { Dictionary, ArrayLike, KeyOfDistributive } from './types';
|
import { GradientObject } from '../graphic/Gradient';
|
import { ImagePatternObject } from '../graphic/Pattern';
|
import { platformApi } from './platform';
|
|
// 用于处理merge时无法遍历Date等对象的问题
|
const BUILTIN_OBJECT: Record<string, boolean> = reduce([
|
'Function',
|
'RegExp',
|
'Date',
|
'Error',
|
'CanvasGradient',
|
'CanvasPattern',
|
// For node-canvas
|
'Image',
|
'Canvas'
|
], (obj, val) => {
|
obj['[object ' + val + ']'] = true;
|
return obj;
|
}, {} as Record<string, boolean>);
|
|
const TYPED_ARRAY: Record<string, boolean> = reduce([
|
'Int8',
|
'Uint8',
|
'Uint8Clamped',
|
'Int16',
|
'Uint16',
|
'Int32',
|
'Uint32',
|
'Float32',
|
'Float64'
|
], (obj, val) => {
|
obj['[object ' + val + 'Array]'] = true;
|
return obj;
|
}, {} as Record<string, boolean>);
|
|
const objToString = Object.prototype.toString;
|
|
const arrayProto = Array.prototype;
|
const nativeForEach = arrayProto.forEach;
|
const nativeFilter = arrayProto.filter;
|
const nativeSlice = arrayProto.slice;
|
const nativeMap = arrayProto.map;
|
// In case some env may redefine the global variable `Function`.
|
const ctorFunction = function () {}.constructor;
|
const protoFunction = ctorFunction ? ctorFunction.prototype : null;
|
const protoKey = '__proto__';
|
|
|
let idStart = 0x0907;
|
|
/**
|
* Generate unique id
|
*/
|
export function guid(): number {
|
return idStart++;
|
}
|
|
export function logError(...args: any[]) {
|
if (typeof console !== 'undefined') {
|
console.error.apply(console, args);
|
}
|
}
|
/**
|
* Those data types can be cloned:
|
* Plain object, Array, TypedArray, number, string, null, undefined.
|
* Those data types will be assigned using the original data:
|
* BUILTIN_OBJECT
|
* Instance of user defined class will be cloned to a plain object, without
|
* properties in prototype.
|
* Other data types is not supported (not sure what will happen).
|
*
|
* Caution: do not support clone Date, for performance consideration.
|
* (There might be a large number of date in `series.data`).
|
* So date should not be modified in and out of echarts.
|
*/
|
export function clone<T extends any>(source: T): T {
|
if (source == null || typeof source !== 'object') {
|
return source;
|
}
|
|
let result = source as any;
|
const typeStr = <string>objToString.call(source);
|
|
if (typeStr === '[object Array]') {
|
if (!isPrimitive(source)) {
|
result = [] as any;
|
for (let i = 0, len = (source as any[]).length; i < len; i++) {
|
result[i] = clone((source as any[])[i]);
|
}
|
}
|
}
|
else if (TYPED_ARRAY[typeStr]) {
|
if (!isPrimitive(source)) {
|
/* eslint-disable-next-line */
|
const Ctor = source.constructor as typeof Float32Array;
|
if (Ctor.from) {
|
result = Ctor.from(source as Float32Array);
|
}
|
else {
|
result = new Ctor((source as Float32Array).length);
|
for (let i = 0, len = (source as Float32Array).length; i < len; i++) {
|
result[i] = (source as Float32Array)[i];
|
}
|
}
|
}
|
}
|
else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
|
result = {} as any;
|
for (let key in source) {
|
// Check if key is __proto__ to avoid prototype pollution
|
if (source.hasOwnProperty(key) && key !== protoKey) {
|
result[key] = clone(source[key]);
|
}
|
}
|
}
|
|
return result;
|
}
|
|
export function merge<
|
T extends Dictionary<any>,
|
S extends Dictionary<any>
|
>(target: T, source: S, overwrite?: boolean): T & S;
|
export function merge<
|
T extends any,
|
S extends any
|
>(target: T, source: S, overwrite?: boolean): T | S;
|
export function merge(target: any, source: any, overwrite?: boolean): any {
|
// We should escapse that source is string
|
// and enter for ... in ...
|
if (!isObject(source) || !isObject(target)) {
|
return overwrite ? clone(source) : target;
|
}
|
|
for (let key in source) {
|
// Check if key is __proto__ to avoid prototype pollution
|
if (source.hasOwnProperty(key) && key !== protoKey) {
|
const targetProp = target[key];
|
const sourceProp = source[key];
|
|
if (isObject(sourceProp)
|
&& isObject(targetProp)
|
&& !isArray(sourceProp)
|
&& !isArray(targetProp)
|
&& !isDom(sourceProp)
|
&& !isDom(targetProp)
|
&& !isBuiltInObject(sourceProp)
|
&& !isBuiltInObject(targetProp)
|
&& !isPrimitive(sourceProp)
|
&& !isPrimitive(targetProp)
|
) {
|
// 如果需要递归覆盖,就递归调用merge
|
merge(targetProp, sourceProp, overwrite);
|
}
|
else if (overwrite || !(key in target)) {
|
// 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
|
// NOTE,在 target[key] 不存在的时候也是直接覆盖
|
target[key] = clone(source[key]);
|
}
|
}
|
}
|
|
return target;
|
}
|
|
/**
|
* @param targetAndSources The first item is target, and the rests are source.
|
* @param overwrite
|
* @return Merged result
|
*/
|
export function mergeAll(targetAndSources: any[], overwrite?: boolean): any {
|
let result = targetAndSources[0];
|
for (let i = 1, len = targetAndSources.length; i < len; i++) {
|
result = merge(result, targetAndSources[i], overwrite);
|
}
|
return result;
|
}
|
|
export function extend<
|
T extends Dictionary<any>,
|
S extends Dictionary<any>
|
>(target: T, source: S): T & S {
|
// @ts-ignore
|
if (Object.assign) {
|
// @ts-ignore
|
Object.assign(target, source);
|
}
|
else {
|
for (let key in source) {
|
// Check if key is __proto__ to avoid prototype pollution
|
if (source.hasOwnProperty(key) && key !== protoKey) {
|
(target as S & T)[key] = (source as T & S)[key];
|
}
|
}
|
}
|
return target as T & S;
|
}
|
|
export function defaults<
|
T extends Dictionary<any>,
|
S extends Dictionary<any>
|
>(target: T, source: S, overlay?: boolean): T & S {
|
const keysArr = keys(source);
|
for (let i = 0; i < keysArr.length; i++) {
|
let key = keysArr[i];
|
if ((overlay ? source[key] != null : (target as T & S)[key] == null)) {
|
(target as S & T)[key] = (source as T & S)[key];
|
}
|
}
|
return target as T & S;
|
}
|
|
// Expose createCanvas in util for compatibility
|
export const createCanvas = platformApi.createCanvas;
|
|
/**
|
* 查询数组中元素的index
|
*/
|
export function indexOf<T>(array: T[] | readonly T[] | ArrayLike<T>, value: T): number {
|
if (array) {
|
if ((array as T[]).indexOf) {
|
return (array as T[]).indexOf(value);
|
}
|
for (let i = 0, len = array.length; i < len; i++) {
|
if (array[i] === value) {
|
return i;
|
}
|
}
|
}
|
return -1;
|
}
|
|
/**
|
* 构造类继承关系
|
*
|
* @param clazz 源类
|
* @param baseClazz 基类
|
*/
|
export function inherits(clazz: Function, baseClazz: Function) {
|
const clazzPrototype = clazz.prototype;
|
function F() {}
|
F.prototype = baseClazz.prototype;
|
clazz.prototype = new (F as any)();
|
|
for (let prop in clazzPrototype) {
|
if (clazzPrototype.hasOwnProperty(prop)) {
|
clazz.prototype[prop] = clazzPrototype[prop];
|
}
|
}
|
clazz.prototype.constructor = clazz;
|
(clazz as any).superClass = baseClazz;
|
}
|
|
export function mixin<T, S>(target: T | Function, source: S | Function, override?: boolean) {
|
target = 'prototype' in target ? target.prototype : target;
|
source = 'prototype' in source ? source.prototype : source;
|
// If build target is ES6 class. prototype methods is not enumerable. Use getOwnPropertyNames instead
|
// TODO: Determine if source is ES6 class?
|
if (Object.getOwnPropertyNames) {
|
const keyList = Object.getOwnPropertyNames(source);
|
for (let i = 0; i < keyList.length; i++) {
|
const key = keyList[i];
|
if (key !== 'constructor') {
|
if ((override ? (source as any)[key] != null : (target as any)[key] == null)) {
|
(target as any)[key] = (source as any)[key];
|
}
|
}
|
}
|
}
|
else {
|
defaults(target, source, override);
|
}
|
}
|
|
/**
|
* Consider typed array.
|
* @param data
|
*/
|
export function isArrayLike(data: any): data is ArrayLike<any> {
|
if (!data) {
|
return false;
|
}
|
if (typeof data === 'string') {
|
return false;
|
}
|
return typeof data.length === 'number';
|
}
|
|
/**
|
* 数组或对象遍历
|
*/
|
export function each<I extends Dictionary<any> | any[] | readonly any[] | ArrayLike<any>, Context>(
|
arr: I,
|
cb: (
|
this: Context,
|
// Use unknown to avoid to infer to "any", which may disable typo check.
|
value: I extends (infer T)[] | readonly (infer T)[] | ArrayLike<infer T> ? T
|
// Use Dictionary<infer T> may cause infer fail when I is an interface.
|
// So here use a Record to infer type.
|
: I extends Dictionary<any> ? I extends Record<infer K, infer T> ? T : unknown : unknown,
|
index?: I extends any[] | readonly any[] | ArrayLike<any> ? number : keyof I & string, // keyof Dictionary will return number | string
|
arr?: I
|
) => void,
|
context?: Context
|
) {
|
if (!(arr && cb)) {
|
return;
|
}
|
if ((arr as any).forEach && (arr as any).forEach === nativeForEach) {
|
(arr as any).forEach(cb, context);
|
}
|
else if (arr.length === +arr.length) {
|
for (let i = 0, len = arr.length; i < len; i++) {
|
// FIXME: should the elided item be travelled? like `[33,,55]`.
|
cb.call(context, (arr as any[])[i], i as any, arr);
|
}
|
}
|
else {
|
for (let key in arr) {
|
if (arr.hasOwnProperty(key)) {
|
cb.call(context, (arr as Dictionary<any>)[key], key as any, arr);
|
}
|
}
|
}
|
}
|
|
/**
|
* Array mapping.
|
* @typeparam T Type in Array
|
* @typeparam R Type Returned
|
* @return Must be an array.
|
*/
|
export function map<T, R, Context>(
|
arr: readonly T[],
|
cb: (this: Context, val: T, index?: number, arr?: readonly T[]) => R,
|
context?: Context
|
): R[] {
|
// Take the same behavior with lodash when !arr and !cb,
|
// which might be some common sense.
|
if (!arr) {
|
return [];
|
}
|
if (!cb) {
|
return slice(arr) as unknown[] as R[];
|
}
|
if (arr.map && arr.map === nativeMap) {
|
return arr.map(cb, context);
|
}
|
else {
|
const result = [];
|
for (let i = 0, len = arr.length; i < len; i++) {
|
// FIXME: should the elided item be travelled, like `[33,,55]`.
|
result.push(cb.call(context, arr[i], i, arr));
|
}
|
return result;
|
}
|
}
|
|
export function reduce<T, S, Context>(
|
arr: readonly T[],
|
cb: (this: Context, previousValue: S, currentValue: T, currentIndex?: number, arr?: readonly T[]) => S,
|
memo?: S,
|
context?: Context
|
): S {
|
if (!(arr && cb)) {
|
return;
|
}
|
for (let i = 0, len = arr.length; i < len; i++) {
|
memo = cb.call(context, memo, arr[i], i, arr);
|
}
|
return memo;
|
}
|
|
/**
|
* Array filtering.
|
* @return Must be an array.
|
*/
|
export function filter<T, Context>(
|
arr: readonly T[],
|
cb: (this: Context, value: T, index: number, arr: readonly T[]) => boolean,
|
context?: Context
|
): T[] {
|
// Take the same behavior with lodash when !arr and !cb,
|
// which might be some common sense.
|
if (!arr) {
|
return [];
|
}
|
if (!cb) {
|
return slice(arr);
|
}
|
if (arr.filter && arr.filter === nativeFilter) {
|
return arr.filter(cb, context);
|
}
|
else {
|
const result = [];
|
for (let i = 0, len = arr.length; i < len; i++) {
|
// FIXME: should the elided items be travelled? like `[33,,55]`.
|
if (cb.call(context, arr[i], i, arr)) {
|
result.push(arr[i]);
|
}
|
}
|
return result;
|
}
|
}
|
|
/**
|
* 数组项查找
|
*/
|
export function find<T, Context>(
|
arr: readonly T[],
|
cb: (this: Context, value: T, index?: number, arr?: readonly T[]) => boolean,
|
context?: Context
|
): T {
|
if (!(arr && cb)) {
|
return;
|
}
|
for (let i = 0, len = arr.length; i < len; i++) {
|
if (cb.call(context, arr[i], i, arr)) {
|
return arr[i];
|
}
|
}
|
}
|
|
/**
|
* Get all object keys
|
*
|
* Will return an empty array if obj is null/undefined
|
*/
|
export function keys<T extends object>(obj: T): (KeyOfDistributive<T> & string)[] {
|
if (!obj) {
|
return [];
|
}
|
// Return type should be `keyof T` but exclude `number`, becuase
|
// `Object.keys` only return string rather than `number | string`.
|
type TKeys = KeyOfDistributive<T> & string;
|
if (Object.keys) {
|
return Object.keys(obj) as TKeys[];
|
}
|
let keyList: TKeys[] = [];
|
for (let key in obj) {
|
if (obj.hasOwnProperty(key)) {
|
keyList.push(key as any);
|
}
|
}
|
return keyList;
|
}
|
|
// Remove this type in returned function. Or it will conflicts wicth callback with given context. Like Eventful.
|
// According to lib.es5.d.ts
|
/* eslint-disable max-len*/
|
export type Bind1<F, Ctx> = F extends (this: Ctx, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Bind2<F, Ctx, T1> = F extends (this: Ctx, a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Bind3<F, Ctx, T1, T2> = F extends (this: Ctx, a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Bind4<F, Ctx, T1, T2, T3> = F extends (this: Ctx, a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Bind5<F, Ctx, T1, T2, T3, T4> = F extends (this: Ctx, a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
type BindFunc<Ctx> = (this: Ctx, ...arg: any[]) => any
|
|
interface FunctionBind {
|
<F extends BindFunc<Ctx>, Ctx>(func: F, ctx: Ctx): Bind1<F, Ctx>
|
<F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0]>(func: F, ctx: Ctx, a: T1): Bind2<F, Ctx, T1>
|
<F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1]>(func: F, ctx: Ctx, a: T1, b: T2): Bind3<F, Ctx, T1, T2>
|
<F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3): Bind4<F, Ctx, T1, T2, T3>
|
<F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2], T4 extends Parameters<F>[3]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3, d: T4): Bind5<F, Ctx, T1, T2, T3, T4>
|
}
|
function bindPolyfill<Ctx, Fn extends(...args: any) => any>(
|
func: Fn, context: Ctx, ...args: any[]
|
): (...args: Parameters<Fn>) => ReturnType<Fn> {
|
return function (this: Ctx) {
|
return func.apply(context, args.concat(nativeSlice.call(arguments)));
|
};
|
}
|
export const bind: FunctionBind = (protoFunction && isFunction(protoFunction.bind))
|
? protoFunction.call.bind(protoFunction.bind)
|
: bindPolyfill;
|
|
export type Curry1<F, T1> = F extends (a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Curry2<F, T1, T2> = F extends (a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Curry3<F, T1, T2, T3> = F extends (a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
export type Curry4<F, T1, T2, T3, T4> = F extends (a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
|
type CurryFunc = (...arg: any[]) => any
|
|
function curry<F extends CurryFunc, T1 extends Parameters<F>[0]>(func: F, a: T1): Curry1<F, T1>
|
function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1]>(func: F, a: T1, b: T2): Curry2<F, T1, T2>
|
function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2]>(func: F, a: T1, b: T2, c: T3): Curry3<F, T1, T2, T3>
|
function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2], T4 extends Parameters<F>[3]>(func: F, a: T1, b: T2, c: T3, d: T4): Curry4<F, T1, T2, T3, T4>
|
function curry(func: Function, ...args: any[]): Function {
|
return function (this: any) {
|
return func.apply(this, args.concat(nativeSlice.call(arguments)));
|
};
|
}
|
export {curry};
|
/* eslint-enable max-len*/
|
|
export function isArray(value: any): value is any[] {
|
if (Array.isArray) {
|
return Array.isArray(value);
|
}
|
return objToString.call(value) === '[object Array]';
|
}
|
|
export function isFunction(value: any): value is Function {
|
return typeof value === 'function';
|
}
|
|
export function isString(value: any): value is string {
|
// Faster than `objToString.call` several times in chromium and webkit.
|
// And `new String()` is rarely used.
|
return typeof value === 'string';
|
}
|
|
export function isStringSafe(value: any): value is string {
|
return objToString.call(value) === '[object String]';
|
}
|
|
export function isNumber(value: any): value is number {
|
// Faster than `objToString.call` several times in chromium and webkit.
|
// And `new Number()` is rarely used.
|
return typeof value === 'number';
|
}
|
|
// Usage: `isObject(xxx)` or `isObject(SomeType)(xxx)`
|
// Generic T can be used to avoid "ts type gruards" casting the `value` from its original
|
// type `Object` implicitly so that loose its original type info in the subsequent code.
|
export function isObject<T = unknown>(value: T): value is (object & T) {
|
// Avoid a V8 JIT bug in Chrome 19-20.
|
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
|
const type = typeof value;
|
return type === 'function' || (!!value && type === 'object');
|
}
|
|
export function isBuiltInObject(value: any): boolean {
|
return !!BUILTIN_OBJECT[objToString.call(value)];
|
}
|
|
export function isTypedArray(value: any): boolean {
|
return !!TYPED_ARRAY[objToString.call(value)];
|
}
|
|
export function isDom(value: any): value is HTMLElement {
|
return typeof value === 'object'
|
&& typeof value.nodeType === 'number'
|
&& typeof value.ownerDocument === 'object';
|
}
|
|
export function isGradientObject(value: any): value is GradientObject {
|
return (value as GradientObject).colorStops != null;
|
}
|
|
export function isImagePatternObject(value: any): value is ImagePatternObject {
|
return (value as ImagePatternObject).image != null;
|
}
|
|
export function isRegExp(value: unknown): value is RegExp {
|
return objToString.call(value) === '[object RegExp]';
|
}
|
|
/**
|
* Whether is exactly NaN. Notice isNaN('a') returns true.
|
*/
|
export function eqNaN(value: any): boolean {
|
/* eslint-disable-next-line no-self-compare */
|
return value !== value;
|
}
|
|
/**
|
* If value1 is not null, then return value1, otherwise judget rest of values.
|
* Low performance.
|
* @return Final value
|
*/
|
export function retrieve<T>(...args: T[]): T {
|
for (let i = 0, len = args.length; i < len; i++) {
|
if (args[i] != null) {
|
return args[i];
|
}
|
}
|
}
|
|
export function retrieve2<T, R>(value0: T, value1: R): T | R {
|
return value0 != null
|
? value0
|
: value1;
|
}
|
|
export function retrieve3<T, R, W>(value0: T, value1: R, value2: W): T | R | W {
|
return value0 != null
|
? value0
|
: value1 != null
|
? value1
|
: value2;
|
}
|
|
type SliceParams = Parameters<typeof nativeSlice>;
|
export function slice<T>(arr: ArrayLike<T>, ...args: SliceParams): T[] {
|
return nativeSlice.apply(arr, args as any[]);
|
}
|
|
/**
|
* Normalize css liked array configuration
|
* e.g.
|
* 3 => [3, 3, 3, 3]
|
* [4, 2] => [4, 2, 4, 2]
|
* [4, 3, 2] => [4, 3, 2, 3]
|
*/
|
export function normalizeCssArray(val: number | number[]) {
|
if (typeof (val) === 'number') {
|
return [val, val, val, val];
|
}
|
const len = val.length;
|
if (len === 2) {
|
// vertical | horizontal
|
return [val[0], val[1], val[0], val[1]];
|
}
|
else if (len === 3) {
|
// top | horizontal | bottom
|
return [val[0], val[1], val[2], val[1]];
|
}
|
return val;
|
}
|
|
export function assert(condition: any, message?: string) {
|
if (!condition) {
|
throw new Error(message);
|
}
|
}
|
|
/**
|
* @param str string to be trimmed
|
* @return trimmed string
|
*/
|
export function trim(str: string): string {
|
if (str == null) {
|
return null;
|
}
|
else if (typeof str.trim === 'function') {
|
return str.trim();
|
}
|
else {
|
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
|
}
|
}
|
|
const primitiveKey = '__ec_primitive__';
|
/**
|
* Set an object as primitive to be ignored traversing children in clone or merge
|
*/
|
export function setAsPrimitive(obj: any) {
|
obj[primitiveKey] = true;
|
}
|
|
export function isPrimitive(obj: any): boolean {
|
return obj[primitiveKey];
|
}
|
|
interface MapInterface<T, KEY extends string | number = string | number> {
|
delete(key: KEY): boolean;
|
has(key: KEY): boolean;
|
get(key: KEY): T | undefined;
|
set(key: KEY, value: T): this;
|
keys(): KEY[];
|
forEach(callback: (value: T, key: KEY) => void): void;
|
}
|
|
class MapPolyfill<T, KEY extends string | number = string | number> implements MapInterface<T, KEY> {
|
private data: Record<KEY, T> = {} as Record<KEY, T>;
|
|
delete(key: KEY): boolean {
|
const existed = this.has(key);
|
if (existed) {
|
delete this.data[key];
|
}
|
return existed;
|
}
|
has(key: KEY): boolean {
|
return this.data.hasOwnProperty(key);
|
}
|
get(key: KEY): T | undefined {
|
return this.data[key];
|
}
|
set(key: KEY, value: T): this {
|
this.data[key] = value;
|
return this;
|
}
|
keys(): KEY[] {
|
return keys(this.data);
|
}
|
forEach(callback: (value: T, key: KEY) => void): void {
|
// This is a potential performance bottleneck, see details in
|
// https://github.com/ecomfe/zrender/issues/965, however it is now
|
// less likely to occur as we default to native maps when possible.
|
const data = this.data;
|
for (const key in data) {
|
if (data.hasOwnProperty(key)) {
|
callback(data[key], key);
|
}
|
}
|
}
|
}
|
|
// We want to use native Map if it is available, but we do not want to polyfill the global scope
|
// in case users ship their own polyfills or patch the native map object in any way.
|
const isNativeMapSupported = typeof Map === 'function';
|
function maybeNativeMap<T, KEY extends string | number = string | number>(): MapInterface<T, KEY> {
|
// Map may be a native class if we are running in an ES6 compatible environment.
|
// eslint-disable-next-line
|
return (isNativeMapSupported ? new Map<KEY, T>() : new MapPolyfill<T, KEY>()) as MapInterface<T, KEY>;
|
}
|
|
/**
|
* @constructor
|
* @param {Object} obj
|
*/
|
export class HashMap<T, KEY extends string | number = string | number> {
|
data: MapInterface<T, KEY>
|
|
constructor(obj?: HashMap<T, KEY> | { [key in KEY]?: T } | KEY[]) {
|
const isArr = isArray(obj);
|
// Key should not be set on this, otherwise
|
// methods get/set/... may be overridden.
|
this.data = maybeNativeMap<T, KEY>();
|
const thisMap = this;
|
|
(obj instanceof HashMap)
|
? obj.each(visit)
|
: (obj && each(obj, visit));
|
|
function visit(value: any, key: any) {
|
isArr ? thisMap.set(value, key) : thisMap.set(key, value);
|
}
|
}
|
|
// `hasKey` instead of `has` for potential misleading.
|
hasKey(key: KEY): boolean {
|
return this.data.has(key);
|
}
|
get(key: KEY): T {
|
return this.data.get(key);
|
}
|
set(key: KEY, value: T): T {
|
// Comparing with invocation chaining, `return value` is more commonly
|
// used in this case: `const someVal = map.set('a', genVal());`
|
this.data.set(key, value);
|
return value;
|
}
|
// Although util.each can be performed on this hashMap directly, user
|
// should not use the exposed keys, who are prefixed.
|
each<Context>(
|
cb: (this: Context, value?: T, key?: KEY) => void,
|
context?: Context
|
) {
|
this.data.forEach((value, key) => {
|
cb.call(context, value, key);
|
});
|
}
|
keys(): KEY[] {
|
const keys = this.data.keys();
|
return isNativeMapSupported
|
// Native map returns an iterator so we need to convert it to an array
|
? Array.from(keys)
|
: keys;
|
}
|
// Do not use this method if performance sensitive.
|
removeKey(key: KEY): void {
|
this.data.delete(key);
|
}
|
}
|
|
export function createHashMap<T, KEY extends string | number = string | number>(
|
obj?: HashMap<T, KEY> | { [key in KEY]?: T } | KEY[]
|
) {
|
return new HashMap<T, KEY>(obj);
|
}
|
|
export function concatArray<T, R>(a: ArrayLike<T>, b: ArrayLike<R>): ArrayLike<T | R> {
|
const newArray = new (a as any).constructor(a.length + b.length);
|
for (let i = 0; i < a.length; i++) {
|
newArray[i] = a[i];
|
}
|
const offset = a.length;
|
for (let i = 0; i < b.length; i++) {
|
newArray[i + offset] = b[i];
|
}
|
return newArray;
|
}
|
|
export function createObject<T>(proto?: object, properties?: T): T {
|
// Performance of Object.create
|
// https://jsperf.com/style-strategy-proto-or-others
|
let obj: T;
|
if (Object.create) {
|
obj = Object.create(proto);
|
}
|
else {
|
const StyleCtor = function () {};
|
StyleCtor.prototype = proto;
|
obj = new (StyleCtor as any)();
|
}
|
if (properties) {
|
extend(obj, properties);
|
}
|
|
return obj;
|
}
|
|
|
export function disableUserSelect(dom: HTMLElement) {
|
const domStyle = dom.style;
|
domStyle.webkitUserSelect = 'none';
|
domStyle.userSelect = 'none';
|
// @ts-ignore
|
domStyle.webkitTapHighlightColor = 'rgba(0,0,0,0)';
|
(domStyle as any)['-webkit-touch-callout'] = 'none';
|
}
|
|
export function hasOwn(own: object, prop: string): boolean {
|
return own.hasOwnProperty(prop);
|
}
|
|
export function noop() {}
|
|
export const RADIAN_TO_DEGREE = 180 / Math.PI;
|