@tarojs/taro-h5 3.4.5 → 3.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/api/base/system.js +28 -24
  2. package/dist/api/canvas/CanvasContext.js +27 -16
  3. package/dist/api/device/accelerometer.js +9 -17
  4. package/dist/api/device/battery.js +12 -3
  5. package/dist/api/device/clipboard.js +13 -4
  6. package/dist/api/device/compass.js +32 -18
  7. package/dist/api/device/motion.js +8 -17
  8. package/dist/api/device/network.js +12 -3
  9. package/dist/api/device/scan.js +8 -4
  10. package/dist/api/location/chooseLocation.js +13 -8
  11. package/dist/api/location/getLocation.js +64 -0
  12. package/dist/api/location/index.js +6 -3
  13. package/dist/api/media/image/previewImage.js +12 -3
  14. package/dist/api/taro.js +2 -3
  15. package/dist/api/ui/fonts.js +12 -3
  16. package/dist/api/ui/interaction/actionSheet.js +3 -12
  17. package/dist/api/ui/interaction/index.js +17 -8
  18. package/dist/api/ui/interaction/modal.js +6 -27
  19. package/dist/api/ui/interaction/toast.js +14 -40
  20. package/dist/api/utils/animation.js +14 -0
  21. package/dist/api/utils/index.js +33 -46
  22. package/dist/api/utils/lodash.js +29 -0
  23. package/dist/api/utils/valid.js +7 -0
  24. package/dist/dist/api/device/scan.d.ts +3 -1
  25. package/dist/dist/api/location/getLocation.d.ts +2 -0
  26. package/dist/dist/api/location/index.d.ts +5 -3
  27. package/dist/dist/api/taro.d.ts +2 -2
  28. package/dist/dist/api/utils/animation.d.ts +6 -0
  29. package/dist/dist/api/utils/index.d.ts +11 -10
  30. package/dist/dist/api/utils/lodash.d.ts +2 -0
  31. package/dist/dist/api/utils/valid.d.ts +2 -0
  32. package/dist/index.cjs.js +529 -260
  33. package/dist/index.cjs.js.map +1 -1
  34. package/package.json +11 -12
  35. package/src/api/device/accelerometer.ts +8 -17
  36. package/src/api/device/compass.ts +34 -18
  37. package/src/api/device/motion.ts +9 -17
  38. package/src/api/device/scan.ts +8 -4
  39. package/src/api/location/getLocation.ts +80 -0
  40. package/src/api/location/index.ts +6 -3
  41. package/src/api/taro.ts +2 -7
  42. package/src/api/ui/scroll/index.ts +1 -1
  43. package/src/api/utils/animation.ts +15 -0
  44. package/src/api/utils/index.ts +40 -46
  45. package/src/api/utils/lodash.ts +30 -0
  46. package/src/api/utils/valid.ts +8 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarojs/taro-h5",
3
- "version": "3.4.5",
3
+ "version": "3.5.0-alpha.0",
4
4
  "description": "Taro h5 framework",
5
5
  "main:h5": "dist/index.js",
6
6
  "main": "dist/index.cjs.js",
@@ -12,15 +12,14 @@
12
12
  ],
13
13
  "sideEffects": false,
14
14
  "scripts": {
15
+ "copy:assets": "copy src/**/*.css dist",
16
+ "build": "rollup -c && npm run copy:assets && tsc",
17
+ "predev": "npm run copy:assets",
18
+ "dev": "tsc -w",
15
19
  "test": "jest",
16
20
  "test:ci": "jest -i --coverage false",
17
21
  "test:dev": "jest --watch",
18
- "test:coverage": "jest --coverage",
19
- "copy:assets": "copy src/**/*.css dist",
20
- "build": "rollup -c && tsc",
21
- "dev": "tsc -w",
22
- "prebuild": "npm run copy:assets",
23
- "predev": "npm run copy:assets"
22
+ "test:coverage": "jest --coverage"
24
23
  },
25
24
  "repository": {
26
25
  "type": "git",
@@ -32,15 +31,15 @@
32
31
  "author": "O2Team",
33
32
  "license": "MIT",
34
33
  "dependencies": {
35
- "@tarojs/api": "3.4.5",
36
- "@tarojs/router": "3.4.5",
37
- "@tarojs/runtime": "3.4.5",
38
- "@tarojs/taro": "3.4.5",
34
+ "@tarojs/api": "3.5.0-alpha.0",
35
+ "@tarojs/router": "3.5.0-alpha.0",
36
+ "@tarojs/runtime": "3.5.0-alpha.0",
37
+ "@tarojs/taro": "3.5.0-alpha.0",
39
38
  "base64-js": "^1.3.0",
40
39
  "jsonp-retry": "^1.0.3",
41
40
  "mobile-detect": "^1.4.2",
42
41
  "query-string": "^7.1.1",
43
42
  "whatwg-fetch": "^3.4.0"
44
43
  },
45
- "gitHead": "789e873f7b9477124b44a900cd4f6a2810e4e06a"
44
+ "gitHead": "8b2acf55c5f98e54bb2788a723a2ef0e0a1b75b6"
46
45
  }
@@ -1,4 +1,5 @@
1
1
  import Taro from '@tarojs/api'
2
+ import { throttle } from '../utils'
2
3
  import { CallbackManager, MethodHandler } from '../utils/handler'
3
4
 
4
5
  const callbackManager = new CallbackManager()
@@ -34,22 +35,6 @@ const INTERVAL_MAP = {
34
35
  }
35
36
  }
36
37
 
37
- const getDevicemotionListener = interval => {
38
- let lock
39
- let timer
40
- return evt => {
41
- if (lock) return
42
- lock = true
43
- timer && clearTimeout(timer)
44
- callbackManager.trigger({
45
- x: evt.acceleration.x || 0,
46
- y: evt.acceleration.y || 0,
47
- z: evt.acceleration.z || 0
48
- })
49
- timer = setTimeout(() => { lock = false }, interval)
50
- }
51
- }
52
-
53
38
  /**
54
39
  * 开始监听加速度数据。
55
40
  */
@@ -61,7 +46,13 @@ export const startAccelerometer: typeof Taro.startAccelerometer = ({ interval =
61
46
  if (devicemotionListener) {
62
47
  stopAccelerometer()
63
48
  }
64
- devicemotionListener = getDevicemotionListener(intervalObj.interval)
49
+ devicemotionListener = throttle((evt: DeviceMotionEvent) => {
50
+ callbackManager.trigger({
51
+ x: evt.acceleration?.x || 0,
52
+ y: evt.acceleration?.y || 0,
53
+ z: evt.acceleration?.z || 0
54
+ })
55
+ }, intervalObj.interval)
65
56
  window.addEventListener('devicemotion', devicemotionListener, true)
66
57
  } else {
67
58
  throw new Error('accelerometer is not supported')
@@ -1,48 +1,64 @@
1
1
  import Taro from '@tarojs/api'
2
+
3
+ import { getDeviceInfo } from '../base/system'
4
+ import { throttle } from '../utils'
2
5
  import { CallbackManager, MethodHandler } from '../utils/handler'
3
6
 
4
7
  const callbackManager = new CallbackManager()
5
8
  let compassListener
6
9
 
10
+ /**
11
+ * Note: 按系统类型获取对应绝对 orientation 事件名,因为安卓系统中直接监听 deviceorientation 事件得到的不是绝对 orientation
12
+ */
13
+ const deviceorientationEventName = ['absolutedeviceorientation', 'deviceorientationabsolute', 'deviceorientation'].find(item => {
14
+ if ('on' + item in window) {
15
+ return item
16
+ }
17
+ }) || ''
18
+
7
19
  /**
8
20
  * 停止监听罗盘数据
9
21
  */
10
22
  export const stopCompass: typeof Taro.stopCompass = ({ success, fail, complete } = {}) => {
11
23
  const handle = new MethodHandler({ name: 'stopCompass', success, fail, complete })
12
24
  try {
13
- window.removeEventListener('deviceorientation', compassListener, true)
25
+ window.removeEventListener(deviceorientationEventName, compassListener, true)
14
26
  return handle.success()
15
27
  } catch (e) {
16
28
  return handle.fail({ errMsg: e.message })
17
29
  }
18
30
  }
19
31
 
20
- const getDeviceOrientationListener = interval => {
21
- let lock
22
- let timer
23
- return evt => {
24
- if (lock) return
25
- lock = true
26
- timer && clearTimeout(timer)
27
- callbackManager.trigger({
28
- direction: 360 - evt.alpha
29
- })
30
- timer = setTimeout(() => { lock = false }, interval)
31
- }
32
- }
33
-
32
+ let CompassChangeTrigger = false
34
33
  /**
35
34
  * 开始监听罗盘数据
36
35
  */
37
36
  export const startCompass: typeof Taro.startCompass = ({ success, fail, complete } = {}) => {
38
37
  const handle = new MethodHandler({ name: 'startCompass', success, fail, complete })
39
38
  try {
40
- if (window.DeviceOrientationEvent) {
39
+ if (deviceorientationEventName !== '') {
41
40
  if (compassListener) {
42
41
  stopCompass()
43
42
  }
44
- compassListener = getDeviceOrientationListener(200)
45
- window.addEventListener('deviceorientation', compassListener, true)
43
+ compassListener = throttle((evt: DeviceOrientationEvent) => {
44
+ const isAndroid = getDeviceInfo().system === 'AndroidOS'
45
+ if (isAndroid && !evt.absolute && !CompassChangeTrigger) {
46
+ CompassChangeTrigger = true
47
+ console.warn('Warning: In \'onCompassChange\', your browser is not supported to get the orientation relative to the earth, the orientation data will be related to the initial orientation of the device .')
48
+ }
49
+ const alpha = evt.alpha || 0
50
+ /**
51
+ * 由于平台差异,accuracy 在 iOS/Android 的值不同。
52
+ * - iOS:accuracy 是一个 number 类型的值,表示相对于磁北极的偏差。0 表示设备指向磁北,90 表示指向东,180 表示指向南,依此类推。
53
+ * - Android:accuracy 是一个 string 类型的枚举值。
54
+ */
55
+ const accuracy = isAndroid ? evt.absolute ? 'high' : 'medium' : alpha
56
+ callbackManager.trigger({
57
+ direction: 360 - alpha,
58
+ accuracy: accuracy
59
+ } as unknown as Parameters<typeof Taro.onCompassChange>[number])
60
+ }, 5000)
61
+ window.addEventListener(deviceorientationEventName, compassListener, true)
46
62
  } else {
47
63
  throw new Error('compass is not supported')
48
64
  }
@@ -1,4 +1,6 @@
1
1
  import Taro from '@tarojs/api'
2
+
3
+ import { throttle } from '../utils'
2
4
  import { CallbackManager, MethodHandler } from '../utils/handler'
3
5
 
4
6
  const callbackManager = new CallbackManager()
@@ -32,22 +34,6 @@ export const stopDeviceMotionListening: typeof Taro.stopDeviceMotionListening =
32
34
  }
33
35
  }
34
36
 
35
- const getDeviceOrientationListener = interval => {
36
- let lock
37
- let timer
38
- return evt => {
39
- if (lock) return
40
- lock = true
41
- timer && clearTimeout(timer)
42
- callbackManager.trigger({
43
- alpha: evt.alpha,
44
- beta: evt.beta,
45
- gamma: evt.gamma
46
- })
47
- timer = setTimeout(() => { lock = false }, interval)
48
- }
49
- }
50
-
51
37
  /**
52
38
  * 开始监听设备方向的变化。
53
39
  */
@@ -59,7 +45,13 @@ export const startDeviceMotionListening: typeof Taro.startDeviceMotionListening
59
45
  if (deviceMotionListener) {
60
46
  stopDeviceMotionListening()
61
47
  }
62
- deviceMotionListener = getDeviceOrientationListener(intervalObj.interval)
48
+ deviceMotionListener = throttle((evt: DeviceOrientationEvent) => {
49
+ callbackManager.trigger({
50
+ alpha: evt.alpha,
51
+ beta: evt.beta,
52
+ gamma: evt.gamma
53
+ })
54
+ }, intervalObj.interval)
63
55
  window.addEventListener('deviceorientation', deviceMotionListener, true)
64
56
  } else {
65
57
  throw new Error('deviceMotion is not supported')
@@ -1,7 +1,11 @@
1
1
  import { processOpenApi } from '../utils'
2
2
 
3
3
  // 扫码
4
- export const scanCode = processOpenApi('scanQRCode', { needResult: 1 }, res => ({
5
- errMsg: res.errMsg === 'scanQRCode:ok' ? 'scanCode:ok' : res.errMsg,
6
- result: res.resultStr
7
- }))
4
+ export const scanCode = processOpenApi({
5
+ name: 'scanQRCode',
6
+ defaultOptions: { needResult: 1 },
7
+ formatResult: res => ({
8
+ errMsg: res.errMsg === 'scanQRCode:ok' ? 'scanCode:ok' : res.errMsg,
9
+ result: res.resultStr
10
+ })
11
+ })
@@ -0,0 +1,80 @@
1
+ import Taro from '@tarojs/api'
2
+ import { processOpenApi, shouldBeObject } from '../utils'
3
+ import { MethodHandler } from '../utils/handler'
4
+
5
+ const getLocationByW3CApi: (options: Taro.getLocation.Option) => Promise<Taro.getLocation.SuccessCallbackResult> = (options: Taro.getLocation.Option): Promise<Taro.getLocation.SuccessCallbackResult> => {
6
+ // 断言 options 必须是 Object
7
+ const isObject = shouldBeObject(options)
8
+ if (!isObject.flag) {
9
+ const res = { errMsg: `getLocation:fail ${isObject.msg}` }
10
+ console.error(res.errMsg)
11
+ return Promise.reject(res)
12
+ }
13
+
14
+ // 解构回调函数
15
+ const { success, fail, complete } = options
16
+
17
+ const handle = new MethodHandler({ name: 'getLocation', success, fail, complete })
18
+
19
+ // const defaultMaximumAge = 5 * 1000
20
+
21
+ const positionOptions: PositionOptions = {
22
+ enableHighAccuracy: options.isHighAccuracy || (options.altitude != null), // 海拔定位需要高精度
23
+ // maximumAge: defaultMaximumAge, // 允许取多久以内的缓存位置
24
+ timeout: options.highAccuracyExpireTime // 高精度定位超时时间
25
+ }
26
+
27
+ // Web端API实现暂时仅支持GPS坐标系
28
+ if (options.type?.toUpperCase() !== 'WGS84') {
29
+ return handle.fail({
30
+ errMsg: 'This coordinate system type is not temporarily supported'
31
+ })
32
+ }
33
+
34
+ // 判断当前浏览器是否支持位置API
35
+ const geolocationSupported = navigator.geolocation
36
+
37
+ if (!geolocationSupported) {
38
+ return handle.fail({
39
+ errMsg: 'The current browser does not support this feature'
40
+ })
41
+ }
42
+
43
+ // 开始获取位置
44
+ return new Promise<Taro.getLocation.SuccessCallbackResult>(
45
+ (resolve, reject) => {
46
+ navigator.geolocation.getCurrentPosition(
47
+ (position) => {
48
+ const result: Taro.getLocation.SuccessCallbackResult = {
49
+ /** 位置的精确度 */
50
+ accuracy: position.coords.accuracy,
51
+ /** 高度,单位 m */
52
+ altitude: position.coords.altitude!,
53
+ /** 水平精度,单位 m */
54
+ horizontalAccuracy: position.coords.accuracy,
55
+ /** 纬度,范围为 -90~90,负数表示南纬 */
56
+ latitude: position.coords.latitude,
57
+ /** 经度,范围为 -180~180,负数表示西经 */
58
+ longitude: position.coords.longitude,
59
+ /** 速度,单位 m/s */
60
+ speed: position.coords.speed!,
61
+ /** 垂直精度,单位 m(Android 无法获取,返回 0) */
62
+ verticalAccuracy: position.coords.altitudeAccuracy || 0,
63
+ /** 调用结果,自动补充 */
64
+ errMsg: ''
65
+ }
66
+ handle.success(result, resolve)
67
+ },
68
+ (error) => {
69
+ handle.fail({ errMsg: error.message }, reject)
70
+ },
71
+ positionOptions
72
+ )
73
+ }
74
+ )
75
+ }
76
+
77
+ export const getLocation = processOpenApi({
78
+ name: 'getLocation',
79
+ standardMethod: getLocationByW3CApi
80
+ })
@@ -5,15 +5,18 @@ export const stopLocationUpdate = temporarilyNotSupport('stopLocationUpdate')
5
5
  export const startLocationUpdateBackground = temporarilyNotSupport('startLocationUpdateBackground')
6
6
  export const startLocationUpdate = temporarilyNotSupport('startLocationUpdate')
7
7
 
8
- export const openLocation = processOpenApi('openLocation', { scale: 18 })
8
+ export const openLocation = processOpenApi({
9
+ name: 'openLocation',
10
+ defaultOptions: { scale: 18 }
11
+ })
9
12
 
10
13
  export const onLocationChangeError = temporarilyNotSupport('onLocationChangeError')
11
14
  export const onLocationChange = temporarilyNotSupport('onLocationChange')
12
15
  export const offLocationChangeError = temporarilyNotSupport('offLocationChangeError')
13
16
  export const offLocationChange = temporarilyNotSupport('offLocationChange')
14
17
 
15
- export const getLocation = processOpenApi('getLocation')
18
+ export { getLocation } from './getLocation'
16
19
 
17
20
  export const choosePoi = temporarilyNotSupport('choosePoi')
18
21
 
19
- export * from './chooseLocation'
22
+ export { chooseLocation } from './chooseLocation'
package/src/api/taro.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import Taro from '@tarojs/api'
2
2
 
3
- import {
4
- history,
5
- createRouter
6
- } from '@tarojs/router'
3
+ import { history } from '@tarojs/router'
7
4
  import { getApp, getCurrentInstance, getCurrentPages, nextTick, navigateBack, navigateTo, reLaunch, redirectTo, switchTab } from '../api'
8
5
  import { permanentlyNotSupport } from '../api/utils'
9
6
 
@@ -36,7 +33,6 @@ const taro: typeof Taro = {
36
33
  Events,
37
34
  preload,
38
35
  history,
39
- createRouter,
40
36
  navigateBack,
41
37
  navigateTo,
42
38
  reLaunch,
@@ -84,6 +80,5 @@ export {
84
80
  requirePlugin,
85
81
  pxTransform,
86
82
  canIUseWebp,
87
- history,
88
- createRouter
83
+ history
89
84
  }
@@ -4,7 +4,7 @@ import { Current } from '@tarojs/runtime'
4
4
  import { MethodHandler } from '../../utils/handler'
5
5
  import { getTimingFunc, easeInOut } from '../../utils'
6
6
 
7
- let timer: NodeJS.Timer
7
+ let timer: NodeJS.Timeout
8
8
  const FRAME_DURATION = 17
9
9
 
10
10
  /**
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ease-in-out的函数
3
+ * @param t 0-1的数字
4
+ */
5
+ export const easeInOut = (t: number) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1)
6
+
7
+ export const getTimingFunc = (easeFunc, frameCnt) => {
8
+ return x => {
9
+ if (frameCnt <= 1) {
10
+ return easeFunc(1)
11
+ }
12
+ const t = x / (frameCnt - 1)
13
+ return easeFunc(t)
14
+ }
15
+ }
@@ -104,7 +104,7 @@ export function temporarilyNotSupport (apiName) {
104
104
 
105
105
  export function weixinCorpSupport (apiName) {
106
106
  return () => {
107
- const errMsg = `h5端仅在微信公众号中支持 API ${apiName}`
107
+ const errMsg = `h5端当前仅在微信公众号JS-SDK环境下支持此 API ${apiName}`
108
108
  if (process.env.NODE_ENV !== 'production') {
109
109
  console.error(errMsg)
110
110
  return Promise.reject({
@@ -136,55 +136,49 @@ export function permanentlyNotSupport (apiName) {
136
136
  }
137
137
  }
138
138
 
139
- export function isFunction (obj) {
140
- return typeof obj === 'function'
139
+ interface IProcessOpenApi<TOptions = Record<string, unknown>, TResult extends TaroGeneral.CallbackResult = any> {
140
+ name: string
141
+ defaultOptions?: TOptions
142
+ standardMethod?: (opts: TOptions) => Promise<TResult>
143
+ formatOptions?: (opts: TOptions) => TOptions
144
+ formatResult?: (res: TResult) => TResult
141
145
  }
142
146
 
143
- const VALID_COLOR_REG = /^#[0-9a-fA-F]{6}$/
144
-
145
- export const isValidColor = (color) => {
146
- return VALID_COLOR_REG.test(color)
147
- }
148
-
149
- export function processOpenApi (apiName: string, defaultOptions?: Record<string, unknown>, formatResult = res => res, formatParams = options => options) {
150
- // @ts-ignore
151
- if (!window.wx) {
152
- return weixinCorpSupport(apiName)
153
- }
154
- return options => {
155
- options = options || {}
156
- const obj = Object.assign({}, defaultOptions, options)
157
- const p = new Promise((resolve, reject) => {
158
- ['fail', 'success', 'complete'].forEach(k => {
159
- obj[k] = oriRes => {
160
- const res = formatResult(oriRes)
161
- options[k] && options[k](res)
162
- if (k === 'success') {
163
- resolve(res)
164
- } else if (k === 'fail') {
165
- reject(res)
147
+ export function processOpenApi<TOptions = Record<string, unknown>, TResult extends TaroGeneral.CallbackResult = any> ({
148
+ name,
149
+ defaultOptions,
150
+ standardMethod,
151
+ formatOptions = options => options,
152
+ formatResult = res => res
153
+ }: IProcessOpenApi<TOptions, TResult>) {
154
+ const notSupported = weixinCorpSupport(name)
155
+ return (options: Partial<TOptions> = {}): Promise<TResult> => {
156
+ // @ts-ignore
157
+ const targetApi = window?.wx?.[name]
158
+ const opts = formatOptions(Object.assign({}, defaultOptions, options))
159
+ if (typeof targetApi === 'function') {
160
+ return new Promise<TResult>((resolve, reject) => {
161
+ ['fail', 'success', 'complete'].forEach(k => {
162
+ opts[k] = preRef => {
163
+ const res = formatResult(preRef)
164
+ options[k] && options[k](res)
165
+ if (k === 'success') {
166
+ resolve(res)
167
+ } else if (k === 'fail') {
168
+ reject(res)
169
+ }
166
170
  }
167
- }
171
+ return targetApi(opts)
172
+ })
168
173
  })
169
- // @ts-ignore
170
- wx[apiName](formatParams(obj))
171
- })
172
- return p
173
- }
174
- }
175
-
176
- /**
177
- * ease-in-out的函数
178
- * @param t 0-1的数字
179
- */
180
- export const easeInOut = (t: number) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
181
-
182
- export const getTimingFunc = (easeFunc, frameCnt) => {
183
- return x => {
184
- if (frameCnt <= 1) {
185
- return easeFunc(1)
174
+ } else if (typeof standardMethod === 'function') {
175
+ return standardMethod(opts)
176
+ } else {
177
+ return notSupported() as Promise<TResult>
186
178
  }
187
- const t = x / (frameCnt - 1)
188
- return easeFunc(t)
189
179
  }
190
180
  }
181
+
182
+ export * from './animation'
183
+ export * from './lodash'
184
+ export * from './valid'
@@ -0,0 +1,30 @@
1
+ export function throttle (fn, threshold = 250, scope?) {
2
+ let lastTime = 0
3
+ let deferTimer: NodeJS.Timeout
4
+ return function (...args) {
5
+ const context = scope || this
6
+ const now = Date.now()
7
+ if (now - lastTime > threshold) {
8
+ fn.apply(this, args)
9
+ lastTime = now
10
+ } else {
11
+ clearTimeout(deferTimer)
12
+ deferTimer = setTimeout(() => {
13
+ lastTime = now
14
+ fn.apply(context, args)
15
+ }, threshold)
16
+ }
17
+ }
18
+ }
19
+
20
+ export function debounce (fn, ms = 250, scope?) {
21
+ let timer: NodeJS.Timeout
22
+
23
+ return function (...args) {
24
+ const context = scope || this
25
+ clearTimeout(timer)
26
+ timer = setTimeout(function () {
27
+ fn.apply(context, args)
28
+ }, ms)
29
+ }
30
+ }
@@ -0,0 +1,8 @@
1
+ export function isFunction (obj) {
2
+ return typeof obj === 'function'
3
+ }
4
+
5
+ const VALID_COLOR_REG = /^#[0-9a-fA-F]{6}$/
6
+ export const isValidColor = (color) => {
7
+ return VALID_COLOR_REG.test(color)
8
+ }