mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
chore: move utils
This commit is contained in:
parent
0a4c6676ba
commit
12faaa2179
14
src-next/utils/constant.ts
Normal file
14
src-next/utils/constant.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// color
|
||||
export const RED = '#ee0a24';
|
||||
export const BLUE = '#1989fa';
|
||||
export const GREEN = '#07c160';
|
||||
export const WHITE = '#fff';
|
||||
|
||||
// border
|
||||
export const BORDER = 'van-hairline';
|
||||
export const BORDER_TOP = `${BORDER}--top`;
|
||||
export const BORDER_LEFT = `${BORDER}--left`;
|
||||
export const BORDER_BOTTOM = `${BORDER}--bottom`;
|
||||
export const BORDER_SURROUND = `${BORDER}--surround`;
|
||||
export const BORDER_TOP_BOTTOM = `${BORDER}--top-bottom`;
|
||||
export const BORDER_UNSET_TOP_BOTTOM = `${BORDER}-unset--top-bottom`;
|
@ -1,5 +1,5 @@
|
||||
import { get, isFunction } from '../../../src/utils';
|
||||
import { camelize } from '../../../src/utils/format/string';
|
||||
import { get, isFunction } from '..';
|
||||
import { camelize } from '../format/string';
|
||||
import locale from '../../locale';
|
||||
|
||||
export function createI18N(name: string) {
|
||||
|
27
src-next/utils/deep-assign.ts
Normal file
27
src-next/utils/deep-assign.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { isDef, isObject } from '.';
|
||||
import { ObjectIndex } from './types';
|
||||
|
||||
const { hasOwnProperty } = Object.prototype;
|
||||
|
||||
function assignKey(to: ObjectIndex, from: ObjectIndex, key: string) {
|
||||
const val = from[key];
|
||||
|
||||
if (!isDef(val)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasOwnProperty.call(to, key) || !isObject(val)) {
|
||||
to[key] = val;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
to[key] = deepAssign(Object(to[key]), from[key]);
|
||||
}
|
||||
}
|
||||
|
||||
export function deepAssign(to: ObjectIndex, from: ObjectIndex): ObjectIndex {
|
||||
Object.keys(from).forEach((key) => {
|
||||
assignKey(to, from, key);
|
||||
});
|
||||
|
||||
return to;
|
||||
}
|
13
src-next/utils/deep-clone.ts
Normal file
13
src-next/utils/deep-clone.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { deepAssign } from './deep-assign';
|
||||
|
||||
export function deepClone(obj: object): object {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => deepClone(item));
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return deepAssign({}, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
56
src-next/utils/dom/event.ts
Normal file
56
src-next/utils/dom/event.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { isServer } from '..';
|
||||
import { EventHandler } from '../types';
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let supportsPassive = false;
|
||||
|
||||
if (!isServer) {
|
||||
try {
|
||||
const opts = {};
|
||||
Object.defineProperty(opts, 'passive', {
|
||||
// eslint-disable-next-line getter-return
|
||||
get() {
|
||||
/* istanbul ignore next */
|
||||
supportsPassive = true;
|
||||
},
|
||||
});
|
||||
window.addEventListener('test-passive', null as any, opts);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export function on(
|
||||
target: EventTarget,
|
||||
event: string,
|
||||
handler: EventHandler,
|
||||
passive = false
|
||||
) {
|
||||
if (!isServer) {
|
||||
target.addEventListener(
|
||||
event,
|
||||
handler,
|
||||
supportsPassive ? { capture: false, passive } : false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function off(target: EventTarget, event: string, handler: EventHandler) {
|
||||
if (!isServer) {
|
||||
target.removeEventListener(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
export function stopPropagation(event: Event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
export function preventDefault(event: Event, isStopPropagation?: boolean) {
|
||||
/* istanbul ignore else */
|
||||
if (typeof event.cancelable !== 'boolean' || event.cancelable) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (isStopPropagation) {
|
||||
stopPropagation(event);
|
||||
}
|
||||
}
|
7
src-next/utils/dom/node.ts
Normal file
7
src-next/utils/dom/node.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function removeNode(el: Node) {
|
||||
const parent = el.parentNode;
|
||||
|
||||
if (parent) {
|
||||
parent.removeChild(el);
|
||||
}
|
||||
}
|
40
src-next/utils/dom/raf.ts
Normal file
40
src-next/utils/dom/raf.ts
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* requestAnimationFrame polyfill
|
||||
*/
|
||||
|
||||
import { isServer } from '..';
|
||||
|
||||
let prev = Date.now();
|
||||
|
||||
/* istanbul ignore next */
|
||||
function fallback(fn: FrameRequestCallback): number {
|
||||
const curr = Date.now();
|
||||
const ms = Math.max(0, 16 - (curr - prev));
|
||||
const id = setTimeout(fn, ms);
|
||||
prev = curr + ms;
|
||||
return id;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const root = (isServer ? global : window) as Window;
|
||||
|
||||
/* istanbul ignore next */
|
||||
const iRaf = root.requestAnimationFrame || fallback;
|
||||
|
||||
/* istanbul ignore next */
|
||||
const iCancel = root.cancelAnimationFrame || root.clearTimeout;
|
||||
|
||||
export function raf(fn: FrameRequestCallback): number {
|
||||
return iRaf.call(root, fn);
|
||||
}
|
||||
|
||||
// double raf for animation
|
||||
export function doubleRaf(fn: FrameRequestCallback): void {
|
||||
raf(() => {
|
||||
raf(fn);
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelRaf(id: number) {
|
||||
iCancel.call(root, id);
|
||||
}
|
16
src-next/utils/dom/reset-scroll.ts
Normal file
16
src-next/utils/dom/reset-scroll.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Hack for iOS12 page scroll
|
||||
* https://developers.weixin.qq.com/community/develop/doc/00044ae90742f8c82fb78fcae56800
|
||||
*/
|
||||
|
||||
import { isIOS as checkIsIOS } from '../validate/system';
|
||||
import { getRootScrollTop, setRootScrollTop } from './scroll';
|
||||
|
||||
const isIOS = checkIsIOS();
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function resetScroll() {
|
||||
if (isIOS) {
|
||||
setRootScrollTop(getRootScrollTop());
|
||||
}
|
||||
}
|
90
src-next/utils/dom/scroll.ts
Normal file
90
src-next/utils/dom/scroll.ts
Normal file
@ -0,0 +1,90 @@
|
||||
type ScrollElement = HTMLElement | Window;
|
||||
|
||||
function isWindow(val: unknown): val is Window {
|
||||
return val === window;
|
||||
}
|
||||
|
||||
// get nearest scroll element
|
||||
// http://w3help.org/zh-cn/causes/SD9013
|
||||
// http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
|
||||
const overflowScrollReg = /scroll|auto/i;
|
||||
export function getScroller(el: HTMLElement, root: ScrollElement = window) {
|
||||
let node = el;
|
||||
|
||||
while (
|
||||
node &&
|
||||
node.tagName !== 'HTML' &&
|
||||
node.nodeType === 1 &&
|
||||
node !== root
|
||||
) {
|
||||
const { overflowY } = window.getComputedStyle(node);
|
||||
|
||||
if (overflowScrollReg.test(overflowY)) {
|
||||
if (node.tagName !== 'BODY') {
|
||||
return node;
|
||||
}
|
||||
|
||||
// see: https://github.com/youzan/vant/issues/3823
|
||||
const { overflowY: htmlOverflowY } = window.getComputedStyle(
|
||||
node.parentNode as Element
|
||||
);
|
||||
|
||||
if (overflowScrollReg.test(htmlOverflowY)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
node = node.parentNode as HTMLElement;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
export function getScrollTop(el: ScrollElement): number {
|
||||
return 'scrollTop' in el ? el.scrollTop : el.pageYOffset;
|
||||
}
|
||||
|
||||
export function setScrollTop(el: ScrollElement, value: number) {
|
||||
if ('scrollTop' in el) {
|
||||
el.scrollTop = value;
|
||||
} else {
|
||||
el.scrollTo(el.scrollX, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function getRootScrollTop(): number {
|
||||
return (
|
||||
window.pageYOffset ||
|
||||
document.documentElement.scrollTop ||
|
||||
document.body.scrollTop ||
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
export function setRootScrollTop(value: number) {
|
||||
setScrollTop(window, value);
|
||||
setScrollTop(document.body, value);
|
||||
}
|
||||
|
||||
// get distance from element top to page top or scroller top
|
||||
export function getElementTop(el: ScrollElement, scroller?: HTMLElement) {
|
||||
if (isWindow(el)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const scrollTop = scroller ? getScrollTop(scroller) : getRootScrollTop();
|
||||
return el.getBoundingClientRect().top + scrollTop;
|
||||
}
|
||||
|
||||
export function getVisibleHeight(el: ScrollElement) {
|
||||
if (isWindow(el)) {
|
||||
return el.innerHeight;
|
||||
}
|
||||
return el.getBoundingClientRect().height;
|
||||
}
|
||||
|
||||
export function getVisibleTop(el: ScrollElement) {
|
||||
if (isWindow(el)) {
|
||||
return 0;
|
||||
}
|
||||
return el.getBoundingClientRect().top;
|
||||
}
|
11
src-next/utils/dom/style.ts
Normal file
11
src-next/utils/dom/style.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export function isHidden(el: HTMLElement) {
|
||||
const style = window.getComputedStyle(el);
|
||||
const hidden = style.display === 'none';
|
||||
|
||||
// offsetParent returns null in the following situations:
|
||||
// 1. The element or its parent element has the display property set to none.
|
||||
// 2. The element has the position property set to fixed
|
||||
const parentHidden = el.offsetParent === null && style.position !== 'fixed';
|
||||
|
||||
return hidden || parentHidden;
|
||||
}
|
31
src-next/utils/format/number.ts
Normal file
31
src-next/utils/format/number.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export function range(num: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
}
|
||||
|
||||
function trimExtraChar(value: string, char: string, regExp: RegExp) {
|
||||
const index = value.indexOf(char);
|
||||
|
||||
if (index === -1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (char === '-' && index !== 0) {
|
||||
return value.slice(0, index);
|
||||
}
|
||||
|
||||
return value.slice(0, index + 1) + value.slice(index).replace(regExp, '');
|
||||
}
|
||||
|
||||
export function formatNumber(value: string, allowDot?: boolean) {
|
||||
if (allowDot) {
|
||||
value = trimExtraChar(value, '.', /\./g);
|
||||
} else {
|
||||
value = value.split('.')[0];
|
||||
}
|
||||
|
||||
value = trimExtraChar(value, '-', /-/g);
|
||||
|
||||
const regExp = allowDot ? /[^-0-9.]/g : /[^-0-9]/g;
|
||||
|
||||
return value.replace(regExp, '');
|
||||
}
|
15
src-next/utils/format/string.ts
Normal file
15
src-next/utils/format/string.ts
Normal file
@ -0,0 +1,15 @@
|
||||
const camelizeRE = /-(\w)/g;
|
||||
|
||||
export function camelize(str: string): string {
|
||||
return str.replace(camelizeRE, (_, c) => c.toUpperCase());
|
||||
}
|
||||
|
||||
export function padZero(num: number | string, targetLength = 2): string {
|
||||
let str = num + '';
|
||||
|
||||
while (str.length < targetLength) {
|
||||
str = '0' + str;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
43
src-next/utils/format/unit.ts
Normal file
43
src-next/utils/format/unit.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { isDef } from '..';
|
||||
import { isNumeric } from '../validate/number';
|
||||
|
||||
export function addUnit(value?: string | number): string | undefined {
|
||||
if (!isDef(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
value = String(value);
|
||||
return isNumeric(value) ? `${value}px` : value;
|
||||
}
|
||||
|
||||
// cache
|
||||
let rootFontSize: number;
|
||||
|
||||
function getRootFontSize() {
|
||||
if (!rootFontSize) {
|
||||
const doc = document.documentElement;
|
||||
const fontSize =
|
||||
doc.style.fontSize || window.getComputedStyle(doc).fontSize;
|
||||
|
||||
rootFontSize = parseFloat(fontSize);
|
||||
}
|
||||
|
||||
return rootFontSize;
|
||||
}
|
||||
|
||||
function convertRem(value: string) {
|
||||
value = value.replace(/rem/g, '');
|
||||
return +value * getRootFontSize();
|
||||
}
|
||||
|
||||
export function unitToPx(value: string | number): number {
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.indexOf('rem') !== -1) {
|
||||
return convertRem(value);
|
||||
}
|
||||
|
||||
return parseFloat(value);
|
||||
}
|
71
src-next/utils/functional.ts
Normal file
71
src-next/utils/functional.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import Vue, { RenderContext, VNodeData } from 'vue';
|
||||
import { ObjectIndex } from './types';
|
||||
|
||||
type Context = RenderContext & { data: VNodeData & ObjectIndex };
|
||||
|
||||
type InheritContext = Partial<VNodeData> & ObjectIndex;
|
||||
|
||||
const inheritKey = [
|
||||
'ref',
|
||||
'style',
|
||||
'class',
|
||||
'attrs',
|
||||
'nativeOn',
|
||||
'directives',
|
||||
'staticClass',
|
||||
'staticStyle',
|
||||
];
|
||||
|
||||
const mapInheritKey: ObjectIndex = { nativeOn: 'on' };
|
||||
|
||||
// inherit partial context, map nativeOn to on
|
||||
export function inherit(
|
||||
context: Context,
|
||||
inheritListeners?: boolean
|
||||
): InheritContext {
|
||||
const result = inheritKey.reduce((obj, key) => {
|
||||
if (context.data[key]) {
|
||||
obj[mapInheritKey[key] || key] = context.data[key];
|
||||
}
|
||||
return obj;
|
||||
}, {} as InheritContext);
|
||||
|
||||
if (inheritListeners) {
|
||||
result.on = result.on || {};
|
||||
Object.assign(result.on, context.data.on);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// emit event
|
||||
export function emit(context: Context, eventName: string, ...args: any[]) {
|
||||
const listeners = context.listeners[eventName];
|
||||
if (listeners) {
|
||||
if (Array.isArray(listeners)) {
|
||||
listeners.forEach((listener) => {
|
||||
listener(...args);
|
||||
});
|
||||
} else {
|
||||
listeners(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mount functional component
|
||||
export function mount(Component: any, data?: VNodeData) {
|
||||
const instance = new Vue({
|
||||
el: document.createElement('div'),
|
||||
props: Component.props,
|
||||
render(h) {
|
||||
return h(Component, {
|
||||
props: this.$props,
|
||||
...data,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(instance.$el);
|
||||
|
||||
return instance;
|
||||
}
|
32
src-next/utils/index.ts
Normal file
32
src-next/utils/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export { addUnit } from './format/unit';
|
||||
export { createNamespace } from './create';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
export function noop() {}
|
||||
|
||||
export function isDef(val: unknown): boolean {
|
||||
return val !== undefined && val !== null;
|
||||
}
|
||||
|
||||
export function isFunction(val: unknown): val is Function {
|
||||
return typeof val === 'function';
|
||||
}
|
||||
|
||||
export function isObject(val: unknown): val is Record<any, any> {
|
||||
return val !== null && typeof val === 'object';
|
||||
}
|
||||
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
|
||||
}
|
||||
|
||||
export function get(object: any, path: string): any {
|
||||
const keys = path.split('.');
|
||||
let result = object;
|
||||
|
||||
keys.forEach((key) => {
|
||||
result = isDef(result[key]) ? result[key] : '';
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
54
src-next/utils/router.ts
Normal file
54
src-next/utils/router.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Vue Router support
|
||||
*/
|
||||
|
||||
import { RenderContext } from 'vue/types';
|
||||
import VueRouter, { RawLocation } from 'vue-router/types';
|
||||
|
||||
export type RouteConfig = {
|
||||
url?: string;
|
||||
to?: RawLocation;
|
||||
replace?: boolean;
|
||||
};
|
||||
|
||||
function isRedundantNavigation(err: Error) {
|
||||
return (
|
||||
err.name === 'NavigationDuplicated' ||
|
||||
// compatible with vue-router@3.3
|
||||
(err.message && err.message.indexOf('redundant navigation') !== -1)
|
||||
);
|
||||
}
|
||||
|
||||
export function route(router: VueRouter, config: RouteConfig) {
|
||||
const { to, url, replace } = config;
|
||||
if (to && router) {
|
||||
const promise = router[replace ? 'replace' : 'push'](to);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (promise && promise.catch) {
|
||||
promise.catch((err) => {
|
||||
if (err && !isRedundantNavigation(err)) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (url) {
|
||||
replace ? location.replace(url) : (location.href = url);
|
||||
}
|
||||
}
|
||||
|
||||
export function functionalRoute(context: RenderContext) {
|
||||
route(context.parent && context.parent.$router, context.props);
|
||||
}
|
||||
|
||||
export type RouteProps = {
|
||||
url?: string;
|
||||
replace?: boolean;
|
||||
to?: RawLocation;
|
||||
};
|
||||
|
||||
export const routeProps = {
|
||||
url: String,
|
||||
replace: Boolean,
|
||||
to: [String, Object],
|
||||
};
|
39
src-next/utils/test/bem.spec.js
Normal file
39
src-next/utils/test/bem.spec.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { createBEM } from '../create/bem';
|
||||
|
||||
test('bem', () => {
|
||||
const bem = createBEM('button');
|
||||
|
||||
expect(bem()).toEqual('button');
|
||||
|
||||
expect(bem('text')).toEqual('button__text');
|
||||
|
||||
expect(bem({ disabled: false })).toEqual('button');
|
||||
|
||||
expect(bem({ disabled: true })).toEqual('button button--disabled');
|
||||
|
||||
expect(bem('text', { disabled: true })).toEqual(
|
||||
'button__text button__text--disabled'
|
||||
);
|
||||
|
||||
expect(bem(['disabled', 'primary'])).toEqual(
|
||||
'button button--disabled button--primary'
|
||||
);
|
||||
|
||||
expect(bem([])).toEqual('button');
|
||||
|
||||
expect(bem(null)).toEqual('button');
|
||||
|
||||
expect(bem([null])).toEqual('button');
|
||||
|
||||
expect(bem(['disabled', ''])).toEqual('button button--disabled');
|
||||
|
||||
expect(bem(['disabled', undefined])).toEqual('button button--disabled');
|
||||
|
||||
expect(bem('text', ['disabled', 'primary'])).toEqual(
|
||||
'button__text button__text--disabled button__text--primary'
|
||||
);
|
||||
|
||||
expect(bem('text', [{ disabled: true }, 'primary'])).toEqual(
|
||||
'button__text button__text--disabled button__text--primary'
|
||||
);
|
||||
});
|
142
src-next/utils/test/index.spec.js
Normal file
142
src-next/utils/test/index.spec.js
Normal file
@ -0,0 +1,142 @@
|
||||
import { deepClone } from '../deep-clone';
|
||||
import { deepAssign } from '../deep-assign';
|
||||
import { isDef, get, noop } from '..';
|
||||
import { raf, cancelRaf } from '../dom/raf';
|
||||
import { later } from '../../../test';
|
||||
import { isEmail } from '../validate/email';
|
||||
import { isMobile } from '../validate/mobile';
|
||||
import { isNumeric } from '../validate/number';
|
||||
import { isAndroid } from '../validate/system';
|
||||
import { camelize } from '../format/string';
|
||||
import { formatNumber } from '../format/number';
|
||||
import { addUnit, unitToPx } from '../format/unit';
|
||||
|
||||
test('deepClone', () => {
|
||||
const a = { foo: 0 };
|
||||
const b = { foo: 0, bar: 1 };
|
||||
const arr = [a, b];
|
||||
expect(deepClone(a)).toEqual(a);
|
||||
expect(deepClone(b)).toEqual(b);
|
||||
expect(deepClone(noop)).toEqual(noop);
|
||||
expect(deepClone(arr)).toEqual(arr);
|
||||
expect(deepClone(undefined)).toEqual(undefined);
|
||||
expect(deepClone(1)).toEqual(1);
|
||||
});
|
||||
|
||||
test('deepAssign', () => {
|
||||
expect(deepAssign({}, { foo: null })).toEqual({});
|
||||
expect(deepAssign({}, { foo: undefined })).toEqual({});
|
||||
expect(deepAssign({ noop: null }, { noop })).toEqual({ noop });
|
||||
expect(deepAssign({ foo: 0 }, { bar: 1 })).toEqual({ foo: 0, bar: 1 });
|
||||
expect(
|
||||
deepAssign({ foo: { bar: false } }, { foo: { bar: true, foo: false } })
|
||||
).toEqual({
|
||||
foo: {
|
||||
bar: true,
|
||||
foo: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('isDef', () => {
|
||||
expect(isDef(null)).toBeFalsy();
|
||||
expect(isDef(undefined)).toBeFalsy();
|
||||
expect(isDef(1)).toBeTruthy();
|
||||
expect(isDef('1')).toBeTruthy();
|
||||
expect(isDef({})).toBeTruthy();
|
||||
expect(isDef(noop)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('camelize', () => {
|
||||
expect(camelize('ab')).toEqual('ab');
|
||||
expect(camelize('a-b')).toEqual('aB');
|
||||
expect(camelize('a-b-c-d')).toEqual('aBCD');
|
||||
expect(camelize('a-b-')).toEqual('aB-');
|
||||
expect(camelize('-a-b')).toEqual('AB');
|
||||
expect(camelize('-')).toEqual('-');
|
||||
});
|
||||
|
||||
test('get', () => {
|
||||
expect(get({ a: 1 }, 'a')).toEqual(1);
|
||||
expect(get({ a: { b: 2 } }, 'a.b')).toEqual(2);
|
||||
expect(get({ a: { b: 2 } }, 'a.b.c')).toEqual('');
|
||||
});
|
||||
|
||||
test('isAndroid', () => {
|
||||
expect(isAndroid()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('raf', async () => {
|
||||
const spy = jest.fn();
|
||||
raf(spy);
|
||||
|
||||
await later(50);
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
cancelRaf(1);
|
||||
});
|
||||
|
||||
test('isEmail', () => {
|
||||
expect(isEmail('abc@gmail.com')).toBeTruthy();
|
||||
expect(isEmail('abc@@gmail.com')).toBeFalsy();
|
||||
expect(isEmail('@gmail.com')).toBeFalsy();
|
||||
expect(isEmail('abc@')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isMobile', () => {
|
||||
expect(isMobile('13000000000')).toBeTruthy();
|
||||
expect(isMobile('+8613000000000')).toBeTruthy();
|
||||
expect(isMobile('8613000000000')).toBeTruthy();
|
||||
expect(isMobile('1300000000')).toBeFalsy();
|
||||
expect(isMobile('abc')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isNumeric', () => {
|
||||
expect(isNumeric('1')).toBeTruthy();
|
||||
expect(isNumeric('1.2')).toBeTruthy();
|
||||
expect(isNumeric('1..2')).toBeFalsy();
|
||||
expect(isNumeric('abc')).toBeFalsy();
|
||||
expect(isNumeric('1b2')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('formatNumber', () => {
|
||||
expect(formatNumber('abc')).toEqual('');
|
||||
expect(formatNumber('1.2')).toEqual('1');
|
||||
expect(formatNumber('abc1.2')).toEqual('1');
|
||||
expect(formatNumber('123.4.')).toEqual('123');
|
||||
|
||||
// with dot
|
||||
expect(formatNumber('abc', true)).toEqual('');
|
||||
expect(formatNumber('1.2', true)).toEqual('1.2');
|
||||
expect(formatNumber('abc1.2', true)).toEqual('1.2');
|
||||
expect(formatNumber('123.4.', true)).toEqual('123.4');
|
||||
|
||||
// minus
|
||||
expect(formatNumber('-1.2')).toEqual('-1');
|
||||
expect(formatNumber('-1.2', true)).toEqual('-1.2');
|
||||
expect(formatNumber('-1.2-', true)).toEqual('-1.2');
|
||||
expect(formatNumber('123-')).toEqual('123');
|
||||
});
|
||||
|
||||
test('addUnit', () => {
|
||||
expect(addUnit(0)).toEqual('0px');
|
||||
expect(addUnit(10)).toEqual('10px');
|
||||
expect(addUnit('1%')).toEqual('1%');
|
||||
expect(addUnit('1px')).toEqual('1px');
|
||||
expect(addUnit('1vw')).toEqual('1vw');
|
||||
expect(addUnit('1vh')).toEqual('1vh');
|
||||
expect(addUnit('1rem')).toEqual('1rem');
|
||||
});
|
||||
|
||||
test('unitToPx', () => {
|
||||
const originGetComputedStyle = window.getComputedStyle;
|
||||
|
||||
window.getComputedStyle = () => ({ fontSize: '16px' });
|
||||
|
||||
expect(unitToPx(0)).toEqual(0);
|
||||
expect(unitToPx(10)).toEqual(10);
|
||||
expect(unitToPx('10px')).toEqual(10);
|
||||
expect(unitToPx('0rem')).toEqual(0);
|
||||
expect(unitToPx('10rem')).toEqual(160);
|
||||
|
||||
window.getComputedStyle = originGetComputedStyle;
|
||||
});
|
40
src-next/utils/types.ts
Normal file
40
src-next/utils/types.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { VNode, CreateElement, RenderContext } from 'vue';
|
||||
import { InjectOptions, PropsDefinition } from 'vue/types/options';
|
||||
|
||||
export type EventHandler = (event: Event) => void;
|
||||
|
||||
export type ObjectIndex = Record<string, any>;
|
||||
|
||||
export type ScopedSlot<Props = any> = (
|
||||
props?: Props
|
||||
) => VNode[] | VNode | undefined;
|
||||
|
||||
export type DefaultSlots = {
|
||||
default?: ScopedSlot;
|
||||
};
|
||||
|
||||
export type ScopedSlots = DefaultSlots & {
|
||||
[key: string]: ScopedSlot | undefined;
|
||||
};
|
||||
|
||||
export type ModelOptions = {
|
||||
prop?: string;
|
||||
event?: string;
|
||||
};
|
||||
|
||||
export type DefaultProps = ObjectIndex;
|
||||
|
||||
export type FunctionComponent<
|
||||
Props = DefaultProps,
|
||||
PropDefs = PropsDefinition<Props>
|
||||
> = {
|
||||
(
|
||||
h: CreateElement,
|
||||
props: Props,
|
||||
slots: ScopedSlots,
|
||||
context: RenderContext<Props>
|
||||
): VNode | undefined;
|
||||
props?: PropDefs;
|
||||
model?: ModelOptions;
|
||||
inject?: InjectOptions;
|
||||
};
|
33
src-next/utils/vnodes.ts
Normal file
33
src-next/utils/vnodes.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { VNode } from 'vue';
|
||||
|
||||
function flattenVNodes(vnodes: VNode[]) {
|
||||
const result: VNode[] = [];
|
||||
|
||||
function traverse(vnodes: VNode[]) {
|
||||
vnodes.forEach((vnode) => {
|
||||
result.push(vnode);
|
||||
|
||||
if (vnode.componentInstance) {
|
||||
traverse(vnode.componentInstance.$children.map((item) => item.$vnode));
|
||||
}
|
||||
|
||||
if (vnode.children) {
|
||||
traverse(vnode.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
traverse(vnodes);
|
||||
return result;
|
||||
}
|
||||
|
||||
// sort children instances by vnodes order
|
||||
export function sortChildren(children: Vue[], parent: Vue) {
|
||||
const { componentOptions } = parent.$vnode;
|
||||
if (!componentOptions || !componentOptions.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vnodes = flattenVNodes(componentOptions.children);
|
||||
children.sort((a, b) => vnodes.indexOf(a.$vnode) - vnodes.indexOf(b.$vnode));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user