[improvement] rename packages dir to src (#3659)

This commit is contained in:
neverland
2019-06-27 11:25:57 +08:00
committed by GitHub
parent 8489918dca
commit 75c79b7044
619 changed files with 21 additions and 21 deletions
+6
View File
@@ -0,0 +1,6 @@
export const RED = '#f44';
export const BLUE = '#1989fa';
export const GREEN = '#07c160';
export const WHITE = '#fff';
export const GRAY = '#c9c9c9';
export const GRAY_DARK = '#969799';
+49
View File
@@ -0,0 +1,49 @@
/**
* bem helper
* b() // 'button'
* b('text') // 'button__text'
* b({ disabled }) // 'button button--disabled'
* b('text', { disabled }) // 'button__text button__text--disabled'
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
*/
export type Mod = string | { [key: string]: any };
export type Mods = Mod | Mod[];
const ELEMENT = '__';
const MODS = '--';
function join(name: string, el?: string, symbol?: string): string {
return el ? name + symbol + el : name;
}
function prefix(name: string, mods: Mods): Mods {
if (typeof mods === 'string') {
return join(name, mods, MODS);
}
if (Array.isArray(mods)) {
return mods.map(item => <Mod>prefix(name, item));
}
const ret: Mods = {};
if (mods) {
Object.keys(mods).forEach(key => {
ret[name + MODS + key] = mods[key];
});
}
return ret;
}
export function createBEM(name: string) {
return function (el?: Mods, mods?: Mods): Mods {
if (el && typeof el !== 'string') {
mods = el;
el = '';
}
el = join(name, el, ELEMENT);
return mods ? [el, prefix(el, mods)] : el;
};
}
+101
View File
@@ -0,0 +1,101 @@
/**
* Create a basic component with common options
*/
import '../../locale';
import { camelize } from '../format/string';
import { SlotsMixin } from '../../mixins/slots';
import Vue, { VNode, VueConstructor, ComponentOptions, RenderContext } from 'vue';
import { DefaultProps, FunctionComponent } from '../types';
export interface VantComponentOptions extends ComponentOptions<Vue> {
functional?: boolean;
install?: (Vue: VueConstructor) => void;
}
export type TsxBaseProps<Slots> = {
key: string | number;
// hack for jsx prop spread
props: any;
class: any;
style: string | object[] | object;
scopedSlots: Slots;
};
export type TsxComponent<Props, Events, Slots> = (
props: Partial<Props & Events & TsxBaseProps<Slots>>
) => VNode;
const arrayProp = {
type: Array,
default: () => []
};
const numberProp = {
type: Number,
default: 0
};
function defaultProps(props: any) {
Object.keys(props).forEach(key => {
if (props[key] === Array) {
props[key] = arrayProp;
} else if (props[key] === Number) {
props[key] = numberProp;
}
});
}
function install(this: ComponentOptions<Vue>, Vue: VueConstructor) {
const { name } = this;
Vue.component(name as string, this);
Vue.component(camelize(`-${name}`), this);
}
// unify slots & scopedSlots
export function unifySlots(context: RenderContext) {
// use data.scopedSlots in lower Vue version
const scopedSlots = context.scopedSlots || context.data.scopedSlots || {};
const slots = context.slots();
Object.keys(slots).forEach(key => {
if (!scopedSlots[key]) {
scopedSlots[key] = () => slots[key];
}
});
return scopedSlots;
}
// should be removed after Vue 3
function transformFunctionComponent(pure: FunctionComponent): VantComponentOptions {
return {
functional: true,
props: pure.props,
model: pure.model,
render: (h, context): any => pure(h, context.props, unifySlots(context), context)
};
}
export function createComponent(name: string) {
return function<Props = DefaultProps, Events = {}, Slots = {}> (
sfc: VantComponentOptions | FunctionComponent
): TsxComponent<Props, Events, Slots> {
if (typeof sfc === 'function') {
sfc = transformFunctionComponent(sfc);
}
if (!sfc.functional) {
sfc.mixins = sfc.mixins || [];
sfc.mixins.push(SlotsMixin);
}
if (sfc.props) {
defaultProps(sfc.props);
}
sfc.name = name;
sfc.install = install;
return sfc as TsxComponent<Props, Events, Slots>;
};
}
+12
View File
@@ -0,0 +1,12 @@
import { get } from '..';
import { camelize } from '../format/string';
import locale from '../../locale';
export function createI18N(name: string) {
const prefix = camelize(name) + '.';
return function (path: string, ...args: any[]): string {
const message = get(locale.messages(), prefix + path) || get(locale.messages(), path);
return typeof message === 'function' ? message(...args) : message;
};
}
+14
View File
@@ -0,0 +1,14 @@
import { createBEM } from './bem';
import { createComponent } from './component';
import { createI18N } from './i18n';
type CreateNamespaceReturn = [
ReturnType<typeof createComponent>,
ReturnType<typeof createBEM>,
ReturnType<typeof createI18N>
];
export function createNamespace(name: string): CreateNamespaceReturn {
name = 'van-' + name;
return [createComponent(name), createBEM(name), createI18N(name)];
}
+27
View File
@@ -0,0 +1,27 @@
/* eslint-disable no-use-before-define */
import { isDef, isObj } 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) || !isObj(val) || typeof val === 'function') {
to[key] = val;
} else {
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
View 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
View File
@@ -0,0 +1,56 @@
/* eslint-disable no-empty */
/* eslint-disable getter-return */
/* eslint-disable import/no-mutable-exports */
import { isServer } from '..';
import { EventHanlder } from '../types';
export let supportsPassive = false;
if (!isServer) {
try {
const opts = {};
Object.defineProperty(opts, 'passive', {
get() {
/* istanbul ignore next */
supportsPassive = true;
}
});
window.addEventListener('test-passive', null as any, opts);
} catch (e) {}
}
export function on(
target: HTMLElement,
event: string,
handler: EventHanlder,
passive = false
) {
if (!isServer) {
target.addEventListener(
event,
handler,
supportsPassive ? { capture: false, passive } : false
);
}
}
export function off(target: HTMLElement, event: string, handler: EventHanlder) {
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);
}
}
+33
View File
@@ -0,0 +1,33 @@
/**
* 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 = <Window>(isServer ? global : 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);
}
export function cancelRaf(id: number) {
iCancel.call(root, id);
}
+48
View File
@@ -0,0 +1,48 @@
type ScrollElement = HTMLElement | Window;
// get nearest scroll element
// http://w3help.org/zh-cn/causes/SD9013
// http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
export function getScrollEventTarget(element: HTMLElement, rootParent: ScrollElement = window) {
let node = element;
while (
node &&
node.tagName !== 'HTML' &&
node.tagName !== 'BODY' &&
node.nodeType === 1 &&
node !== rootParent
) {
const { overflowY } = window.getComputedStyle(node);
if (overflowY === 'scroll' || overflowY === 'auto') {
return node;
}
node = <HTMLElement>node.parentNode;
}
return rootParent;
}
export function getScrollTop(element: ScrollElement): number {
return 'scrollTop' in element ? element.scrollTop : element.pageYOffset;
}
export function setScrollTop(element: ScrollElement, value: number) {
'scrollTop' in element ? (element.scrollTop = value) : element.scrollTo(element.scrollX, value);
}
export function getRootScrollTop(): number {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}
// get distance from element top to page top
export function getElementTop(element: ScrollElement) {
return (
(element === window ? 0 : (<HTMLElement>element).getBoundingClientRect().top) +
getScrollTop(window)
);
}
export function getVisibleHeight(element: ScrollElement) {
return element === window
? element.innerHeight
: (<HTMLElement>element).getBoundingClientRect().height;
}
+3
View File
@@ -0,0 +1,3 @@
export function range(num: number, min: number, max: number): number {
return Math.min(Math.max(num, min), max);
}
+9
View File
@@ -0,0 +1,9 @@
const camelizeRE = /-(\w)/g;
export function camelize(str: string): string {
return str.replace(camelizeRE, (_, c) => c.toUpperCase());
}
export function padZero(num: number | string): string {
return (num < 10 ? '0' : '') + num;
}
+11
View File
@@ -0,0 +1,11 @@
import { isDef } from '..';
import { isNumber } from '../validate/number';
export function suffixPx(value?: string | number): string | undefined {
if (!isDef(value)) {
return undefined;
}
value = String(value);
return isNumber(value) ? `${value}px` : value;
}
+71
View 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;
}
+28
View File
@@ -0,0 +1,28 @@
import Vue from 'vue';
export { createNamespace } from './create';
export { suffixPx } from './format/unit';
export const isServer: boolean = Vue.prototype.$isServer;
export function noop() {}
export function isDef(value: any): boolean {
return value !== undefined && value !== null;
}
export function isObj(x: any): boolean {
const type = typeof x;
return x !== null && (type === 'object' || type === 'function');
}
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;
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Vue Router support
*/
import { RenderContext } from 'vue/types';
import VueRouter, { RawLocation } from 'vue-router/types';
export type RouteConfig = {
url?: string;
to?: RawLocation;
replace?: boolean;
};
export function route(router: VueRouter, config: RouteConfig) {
const { to, url, replace } = config;
if (to && router) {
router[replace ? 'replace' : 'push'](to);
} 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]
};
+100
View File
@@ -0,0 +1,100 @@
import { deepClone } from '../deep-clone';
import { deepAssign } from '../deep-assign';
import { isDef, get } from '..';
import { raf, cancelRaf } from '../dom/raf';
import { later } from '../../../test/utils';
import { isEmail } from '../validate/email';
import { isMobile } from '../validate/mobile';
import { isNumber } from '../validate/number';
import { isAndroid } from '../validate/system';
import { camelize } from '../format/string';
test('deepClone', () => {
const a = { foo: 0 };
const b = { foo: 0, bar: 1 };
const fn = () => {};
const arr = [a, b];
expect(deepClone(a)).toEqual(a);
expect(deepClone(b)).toEqual(b);
expect(deepClone(fn)).toEqual(fn);
expect(deepClone(arr)).toEqual(arr);
expect(deepClone(undefined)).toEqual(undefined);
expect(deepClone(1)).toEqual(1);
});
test('deepAssign', () => {
const fn = () => {};
expect(deepAssign({}, { foo: null })).toEqual({});
expect(deepAssign({}, { foo: undefined })).toEqual({});
expect(deepAssign({ fn: null }, { fn })).toEqual({ fn });
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(() => {})).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('is-email', () => {
expect(isEmail('abc@gmail.com')).toBeTruthy();
expect(isEmail('abc@@gmail.com')).toBeFalsy();
expect(isEmail('@gmail.com')).toBeFalsy();
expect(isEmail('abc@')).toBeFalsy();
});
test('is-mobile', () => {
expect(isMobile('13000000000')).toBeTruthy();
expect(isMobile('+8613000000000')).toBeTruthy();
expect(isMobile('8613000000000')).toBeTruthy();
expect(isMobile('1300000000')).toBeFalsy();
expect(isMobile('abc')).toBeFalsy();
});
test('is-number', () => {
expect(isNumber('1')).toBeTruthy();
expect(isNumber('1.2')).toBeTruthy();
expect(isNumber('1..2')).toBeFalsy();
expect(isNumber('abc')).toBeFalsy();
expect(isNumber('1b2')).toBeFalsy();
});
+34
View File
@@ -0,0 +1,34 @@
import { VNode, CreateElement, RenderContext } from 'vue';
import { InjectOptions, PropsDefinition } from 'vue/types/options';
export type EventHanlder = (eventName?: Event) => void;
export type ObjectIndex = {
[key: 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 = Record<string, any>;
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;
};
+5
View File
@@ -0,0 +1,5 @@
/* eslint-disable */
export function isEmail(value: string): boolean {
const reg = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
return reg.test(value);
}
+4
View File
@@ -0,0 +1,4 @@
export function isMobile(value: string): boolean {
value = value.replace(/[^-|\d]/g, '');
return /^((\+86)|(86))?(1)\d{10}$/.test(value) || /^0[0-9-]{10,13}$/.test(value);
}
+3
View File
@@ -0,0 +1,3 @@
export function isNumber(value: string): boolean {
return /^\d+(\.\d+)?$/.test(value);
}
+11
View File
@@ -0,0 +1,11 @@
import { isServer } from '..';
export function isAndroid(): boolean {
/* istanbul ignore next */
return isServer ? false : /android/.test(navigator.userAgent.toLowerCase());
}
export function isIOS(): boolean {
/* istanbul ignore next */
return isServer ? false : /ios|iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase());
}