Merge branch 'dev' into next

This commit is contained in:
chenjiahan 2021-11-15 10:59:04 +08:00
commit 1bd2ab00ac
81 changed files with 565 additions and 753 deletions

View File

@ -1,4 +1,7 @@
{
"root": true,
"extends": ["@vant"]
"extends": ["@vant"],
"rules": {
"prefer-object-spread": "off"
}
}

View File

@ -1,7 +1,7 @@
name: 我要反馈 Vant 的 Bug
description: 通过标准模板进行 Bug 反馈。
title: "[Bug Report] 在此填写标题"
labels: ["🐞 bug"]
title: "[Bug Report] 在此填写标题"
labels: ["bug: need confirm"]
body:
- type: markdown
attributes:

View File

@ -1,7 +1,7 @@
name: 我要反馈 Vant Cli 的 Bug
description: 通过标准模板进行 Bug 反馈。
title: "[Bug Report] 在此填写标题"
labels: ["🐞 bug", "cli"]
title: "[Bug Report] 在此填写标题"
labels: ["bug: need confirm", "cli"]
body:
- type: markdown
attributes:

View File

@ -1,7 +1,7 @@
name: 我想要一个 Vant 的新功能
description: 通过标准模板描述一下你的功能需求。
title: "[Feature Request] 在此填写标题"
labels: ["💡 feature"]
title: "[Feature Request] 在此填写标题"
labels: ["feature: need confirm"]
body:
- type: markdown
attributes:

View File

@ -1,7 +1,7 @@
name: 我想要一个 Vant Cli 的新功能
description: 通过标准模板描述一下你的功能需求。
title: "[Feature Request] 在此填写标题"
labels: ["💡 feature", "cli"]
title: "[Feature Request] 在此填写标题"
labels: ["feature: need confirm", "cli"]
body:
- type: markdown
attributes:

View File

@ -1,7 +1,7 @@
name: Bug Report
description: Use issue template to report a bug.
title: "[Bug Report] Title"
labels: ["🐞 bug"]
labels: ["bug: need confirm"]
body:
- type: input
id: reproduce

View File

@ -1,7 +1,7 @@
name: Feature Request
description: Use issue template to request a new feature.
title: "[Feature Request] Title"
labels: ["💡 feature"]
labels: ["feature: need confirm"]
body:
- type: textarea
id: description

View File

@ -41,12 +41,24 @@
## Install
```bash
# Install Vant 2 for Vue 2 project
npm i vant -S
Using `npm` to install:
# Install Vant 3 for Vue 3 project
npm i vant@next -S
```bash
# install Vant 2 for Vue 2 project
npm i vant@2
# install Vant 3 for Vue 3 project
npm i vant@3
```
Using `yarn` or `pnpm`:
```bash
# with yarn
yarn add vant@3
# with pnpm
pnpm add vant@3
```
## Quickstart

View File

@ -45,12 +45,24 @@ Vant 是**有赞前端团队**开源的移动端组件库,于 2017 年开源
## 安装
```bash
# Vue 2 项目,安装 Vant 2
npm i vant -S
在现有项目中使用 Vant 时,可以通过 `npm` 进行安装:
# Vue 3 项目,安装 Vant 3
npm i vant@next -S
```bash
# Vue 2 项目,安装 Vant 2
npm i vant@2
# Vue 3 项目,安装 Vant 3
npm i vant@3
```
当然,你也可以通过 `yarn``pnpm` 进行安装:
```bash
# 通过 yarn 安装
yarn add vant@3
# 通过 pnpm 安装
pnpm add vant@3
```
## 快速上手

View File

@ -5,6 +5,7 @@
"dev": "pnpm dev --dir ./packages/vant",
"lint": "pnpm lint --dir ./packages/vant",
"test": "pnpm test --dir ./packages/vant",
"test:watch": "pnpm test:watch --dir ./packages/vant",
"build": "pnpm build --dir ./packages/vant",
"build:site": "pnpm build:site --dir ./packages/vant"
},
@ -18,10 +19,12 @@
"devDependencies": {
"@vant/cli": "workspace:*",
"@vant/eslint-config": "workspace:*",
"@vant/stylelint-config": "workspace:*",
"eslint": "^8.2.0",
"husky": "^7.0.4",
"lint-staged": "^11.2.6",
"prettier": "^2.4.1",
"rimraf": "^3.0.2",
"stylelint": "^13.13.1"
}
}

View File

@ -3,9 +3,12 @@
### 安装
```bash
# 通过 npm 安装
npm i <%= name %> -S
# 通过 npm
npm i <%= name %>
# 通过 yarn 安装
# 通过 yarn
yarn add <%= name %>
# 通过 pnpm
pnpm add <%= name %>
```

View File

@ -3,9 +3,12 @@
### 安装
```bash
# 通过 npm 安装
npm i <%= name %> -S
# 通过 npm
npm i <%= name %>
# 通过 yarn 安装
# 通过 yarn
yarn add <%= name %>
# 通过 pnpm
pnpm add <%= name %>
```

View File

@ -4,16 +4,15 @@
## 安装
#### NPM
```shell
npm i @vant/area-data -D
```
# 通过 npm
npm i @vant/area-data
#### YARN
# 通过 yarn
yarn add @vant/area-data
```shell
yarn add @vant/area-data --dev
# 通过 pnpm
pnpm add @vant/area-data
```
## 使用

View File

@ -20,11 +20,14 @@ yarn create vant-cli-app
### 手动安装
```shell
# 通过 npm 安装
# 通过 npm
npm i @vant/cli -D
# 通过 yarn 安装
yarn add @vant/cli --dev
# 通过 yarn
yarn add @vant/cli -D
# 通过 pnpm
pnpm add @vant/cli -D
```
安装完成后,请将以下配置添加到 package.json 文件中

View File

@ -36,6 +36,8 @@
yarn add sass
```
- 为了避免 Phantom dependency不再默认依赖 `@vue/test-utils`,使用时需要手动安装
### Features
- 新增 site.htmlMeta 配置项

View File

@ -55,7 +55,6 @@
"@vitejs/plugin-vue": "^1.9.4",
"@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/babel-plugin-jsx": "^1.1.1",
"@vue/test-utils": "2.0.0-rc.14",
"autoprefixer": "^10.4.0",
"babel-jest": "^27.3.1",
"chalk": "^4.1.2",

View File

@ -2,16 +2,15 @@
## Install
#### NPM
```shell
# with npm
npm i @vant/eslint-config -D
```
#### YARN
# with yarn
yarn add @vant/eslint-config -D
```shell
yarn add @vant/eslint-config --dev
# with pnpm
pnpm add @vant/eslint-config -D
```
## Usage

View File

@ -2,16 +2,15 @@
## Install
#### NPM
```shell
npm i @vant/icons -S
```
# with npm
npm i @vant/icons
#### YARN
```shell
# with yarn
yarn add @vant/icons
# with pnpm
pnpm add @vant/icons
```
## Document

View File

@ -1 +0,0 @@
lib

View File

@ -1,27 +0,0 @@
# @vant/lazyload
This is a fork of [vue-lazyload](https://github.com/hilongjw/vue-lazyload) with Vue 3 support.
## Install
```shell
yarn add @vant/lazyload
```
## Usage
```js
import { createApp } from 'vue';
import { LazyLoad } from '@vant/lazyload';
const app = createApp();
app.use(LazyLoad);
```
## API
see: https://github.com/hilongjw/vue-lazyload
## TODO
support TypeScript.

View File

@ -1,3 +0,0 @@
module.exports = {
presets: [['@vant/cli/preset.cjs', { loose: true }]],
};

View File

@ -1,42 +0,0 @@
{
"name": "@vant/lazyload",
"version": "1.4.0",
"description": "This is a fork of vue-lazyload",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "types/index.d.ts",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"files": [
"dist",
"types"
],
"scripts": {
"dev": "rollup --config rollup.config.js --watch",
"build": "rollup --config rollup.config.js",
"release": "pnpm build && release-it",
"prepare": "pnpm build"
},
"license": "MIT",
"repository": "https://github.com/youzan/vant/tree/dev/packages/vant-lazyload",
"dependencies": {
"@vant/use": "^1.3.2"
},
"devDependencies": {
"vue": "3.x",
"@vue/runtime-core": "3.x",
"@vant/cli": "workspace:*",
"@rollup/plugin-babel": "^5.2.1",
"@rollup/plugin-node-resolve": "^10.0.0",
"release-it": "^14.2.2",
"rollup": "^2.33.3"
},
"release-it": {
"git": {
"tag": false,
"commitMessage": "release: @vant/lazyload ${version}"
}
}
}

View File

@ -1,22 +0,0 @@
import path from 'path';
import babel from '@rollup/plugin-babel';
import nodeResolve from '@rollup/plugin-node-resolve';
export default {
input: path.join(__dirname, 'src', 'index.js'),
output: [
{
dir: 'dist/cjs',
format: 'cjs',
},
{
dir: 'dist/esm',
format: 'esm',
},
],
external: ['vue', '@vant/use'],
plugins: [
babel({ babelHelpers: 'bundled', extensions: ['.js', '.ts'] }),
nodeResolve(),
],
};

View File

@ -4,27 +4,26 @@ Simple and fast vue markdown loader, transform markdown to vue component.
## Install
### NPM
```shell
npm i @vant/markdown-loader -S
```
# with npm
npm i @vant/markdown-loader -D
### YARN
# with yarn
yarn add @vant/markdown-loader -D
```shell
yarn add @vant/markdown-loader
# with pnpm
pnpm add @vant/markdown-loader -D
```
## Options
- `enableMetaData`: Default `false`. Whether to use [front-matter](https://github.com/jxson/front-matter) to extract markdown meta data
- `linkOpen`: Default `true`. Whether to add target="_blank" to all links
- `linkOpen`: Default `true`. Whether to add target="\_blank" to all links
- `wrapper(html, fm)`: Format the returned content using a custom function
- `html`: The result of [markdown-it](https://github.com/markdown-it/markdown-it)'s render
- `fm`: See [fm(string)](https://github.com/jxson/front-matter#fmstring). If `enableMetaData` option is `false`, the value is `undefined`.
- `attributes`
- `body`
- `frontmatter`
- `html`: The result of [markdown-it](https://github.com/markdown-it/markdown-it)'s render
- `fm`: See [fm(string)](https://github.com/jxson/front-matter#fmstring). If `enableMetaData` option is `false`, the value is `undefined`.
- `attributes`
- `body`
- `frontmatter`

View File

@ -4,16 +4,15 @@
## Install
#### NPM
```shell
# with npm
npm i @vant/markdown-vetur -D
```
#### YARN
# with yarn
yarn add @vant/markdown-vetur -D
```shell
yarn add @vant/markdown-vetur --dev
# with pnpm
pnpm add @vant/markdown-vetur -D
```
## API

View File

@ -5,7 +5,14 @@
## Install
```shell
# with npm
npm i @vant/popperjs
# with yarn
yarn add @vant/popperjs
# with pnpm
pnpm add @vant/popperjs
```
## Usage

View File

@ -1,3 +0,0 @@
module.exports = {
presets: [['@vant/cli/preset.cjs', { loose: true }]],
};

View File

@ -1,10 +1,11 @@
{
"name": "@vant/popperjs",
"version": "1.1.0",
"description": "Precompiled popperjs core",
"description": "Pre-compiled popperjs core",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"files": [
"dist"
],
@ -13,8 +14,11 @@
"registry": "https://registry.npmjs.org/"
},
"scripts": {
"clean": "rimraf ./dist",
"dev": "rollup --config rollup.config.js --watch",
"build": "rollup --config rollup.config.js && tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:bundle": "rollup --config rollup.config.js",
"build": "pnpm clean && pnpm build:bundle && pnpm build:types",
"release": "pnpm build && release-it",
"prepare": "pnpm build"
},
@ -24,11 +28,11 @@
"@popperjs/core": "^2.9.2"
},
"devDependencies": {
"@vant/cli": "workspace:*",
"@rollup/plugin-babel": "^5.2.1",
"@rollup/plugin-node-resolve": "^10.0.0",
"release-it": "^14.2.2",
"rollup": "^2.33.3"
"typescript": "4.x",
"rollup": "^2.33.3",
"rollup-plugin-esbuild": "^4.6.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"release-it": "^14.2.2"
},
"release-it": {
"git": {

View File

@ -1,5 +1,5 @@
import path from 'path';
import babel from '@rollup/plugin-babel';
import esbuild from 'rollup-plugin-esbuild';
import nodeResolve from '@rollup/plugin-node-resolve';
export default {
@ -14,8 +14,5 @@ export default {
format: 'esm',
},
],
plugins: [
babel({ babelHelpers: 'bundled', extensions: ['.js', '.ts'] }),
nodeResolve(),
],
plugins: [esbuild(), nodeResolve()],
};

View File

@ -2,13 +2,11 @@
"compilerOptions": {
"target": "ES2015",
"outDir": "./dist",
"module": "ES2015",
"module": "ESNext",
"strict": true,
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"moduleResolution": "node",
"lib": ["esnext", "dom"]
"moduleResolution": "Node",
},
"include": ["src/**/*"]
}

View File

@ -2,16 +2,15 @@
## Install
#### NPM
```shell
# with npm
npm i @vant/stylelint-config -D
```
#### YARN
# with yarn
yarn add @vant/stylelint-config -D
```shell
yarn add @vant/stylelint-config --dev
# with pnpm
pnpm add @vant/stylelint-config -D
```
## Usage

View File

@ -4,16 +4,15 @@
## Install
#### NPM
```shell
npm i @vant/touch-emulator -S
```
# with npm
npm i @vant/touch-emulator
#### YARN
```shell
# with yarn
yarn add @vant/touch-emulator
# with pnpm
pnpm add @vant/touch-emulator
```
## 使用指南

View File

@ -1,17 +1,16 @@
# Vant Use
Vant Use 是从 Vant 实际应用场景中沉淀的 Vue 组合式 API 库。
Built-in composition APIs of Vant.
## 安装
## Install
如果项目中已经安装了 Vant 3则无须手动安装 Vant Use。
```shell
# with npm
npm i @vant/use
如果项目中未使用 Vant可以通过 `npm``yarn` 手动安装 Vant Use。
```bash
# 通过 npm 安装
npm i @vant/use -S
# 通过 yarn 安装
# with yarn
yarn add @vant/use
# with pnpm
pnpm add @vant/use
```

View File

@ -1,3 +0,0 @@
module.exports = {
presets: [['@vant/cli/preset.cjs', { loose: true }]],
};

View File

@ -1,144 +1,149 @@
# 更新日志
# Changelog
### v1.3.3
- Allow to call useWindowSize outside setup
- Improve usePageVisibility event bindings performance
### v1.3.2
- 移除 passive event 的 polyfill
-Remove passive event polyfill
### v1.3.1
- 移除 requestAnimationFrame 的 polyfill
- Remove requestAnimationFrame polyfill
### v1.3.0
- 新增 `useCustomFieldValue` 方法
- Added `useCustomFieldValue` method
### v1.2.2
`2021-07-22`
- `useEventListener`: 修复 `invalid watch source` 问题
- `useEventListener`: fix `invalid watch source` issue
### v1.2.1
`2021-07-21`
- `useEventListener` 现在会监听 target 的变化,并重新监听事件
- `useEventListener` will now watch the target changes and re-listen the events
### v1.2.0
`2021-07-12`
- 调整 `useParent``useChildren` 的类型定义
- Adjust type definition of `useParent` and `useChildren`
### v1.1.2
`2021-04-22`
- 修复 `useScrollParent` 在 SSR 下报错的问题
- Fix the issue of `useScrollParent` reporting errors under SSR
### v1.1.1
`2021-04-16`
- 移除 `@babel/runtime` 依赖
- Remove `@babel/runtime` dependency
### v1.1.0
`2021-04-06`
- 构建结果由 ES5 调整为 ES6
- Compile to ES6 instead of ES5
### v1.0.5
`2021-02-13`
- 优化 `useRect` 的返回值类型,始终返回 `DOMRect`
- Optimize the return value type of `useRect`, always return `DOMRect`
### v1.0.4
`2021-02-12`
- `useChildren` 支持通过泛型定义 Children 的类型
- `useChildren` supports defining the type of Children through generics
### v1.0.3
`2021-02-10`
- 当 parent 不存在时,`useParent` 现在返回的 index 为 -1 而不是 undefined
- When parent does not exist, `useParent` now returns index -1 instead of undefined
### v1.0.2
`2021-01-01`
- 修复 useToggle 类型定义错误的问题
- Fix the problem of incorrect useToggle type definition
### v1.0.1
`2020-12-27`
- 导出个别内部方法供 Vant 使用
- Export individual internal methods for use by Vant
### v1.0.0
`2020-12-15`
- 优化构建产物体积
- Optimize the volume of the build product
### v0.1.0
`2020-11-11`
- 新增 `onMountedOrActivated` 方法
- Added `onMountedOrActivated` method
### v0.0.8
`2020-10-09`
- 改进类型定义
- Improve type definition
### v0.0.7
`2020-10-06`
- 修复 `useCountDown` 未被导出的问题
- Fix the problem that `useCountDown` is not exported
### v0.0.6
`2020-10-06`
- 导出所有类型定义
- Export all type definitions
### v0.0.5
`2020-10-06`
- 新增 `useCountDown` 方法
- Added `useCountDown` method
### v0.0.4
`2020-10-05`
- 新增 `useRect` 方法
- Added `useRect` method
### v0.0.3
`2020-09-27`
- 新增 `useParent` 方法
- 新增 `useChildren` 方法
- Added `useParent` method
- Added `useChildren` method
### v0.0.2
`2020-09-15`
- 新增 `useWindowSize` 方法
- Added `useWindowSize` method
### v0.0.1
`2020-09-15`
- 新增 `useClickAway` 方法
- 新增 `useEventListener` 方法
- 新增 `usePageVisibility` 方法
- 新增 `useScrollParent` 方法
- 新增 `useToggle` 方法
- Added `useClickAway` method
- Added `useEventListener` method
- Added `usePageVisibility` method
- Added `useScrollParent` method
- Added `useToggle` method

View File

@ -1,19 +1,20 @@
{
"name": "@vant/use",
"version": "1.3.2",
"version": "1.3.3",
"description": "Vant Composition API",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"typings": "dist/types/index.d.ts",
"typings": "dist/index.d.ts",
"sideEffects": false,
"files": [
"dist"
],
"scripts": {
"clean": "rm -rf ./dist",
"build:lib": "node ./scripts/build.js",
"clean": "rimraf ./dist",
"dev": "rollup --config rollup.config.js --watch",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build": "pnpm build:lib && pnpm build:types",
"build:bundle": "rollup --config rollup.config.js",
"build": "pnpm clean && pnpm build:bundle && pnpm build:types",
"release": "pnpm build && release-it",
"prepare": "pnpm build"
},
@ -24,12 +25,10 @@
"license": "MIT",
"repository": "https://github.com/youzan/vant/tree/dev/packages/vant-use",
"devDependencies": {
"@babel/core": "^7.12.9",
"@vant/cli": "workspace:*",
"fast-glob": "^3.2.7",
"fs-extra": "^10.0.0",
"release-it": "^14.0.2",
"typescript": "4.x",
"rollup": "^2.33.3",
"rollup-plugin-esbuild": "^4.6.0",
"vue": "^3.2.20"
},
"release-it": {

View File

@ -0,0 +1,18 @@
import path from 'path';
import esbuild from 'rollup-plugin-esbuild';
export default {
input: path.join(__dirname, 'src', 'index.ts'),
output: [
{
dir: 'dist/cjs',
format: 'cjs',
},
{
dir: 'dist/esm',
format: 'esm',
},
],
external: ['vue'],
plugins: [esbuild()],
};

View File

@ -1,40 +0,0 @@
const glob = require('fast-glob');
const { join } = require('path');
const { transformAsync } = require('@babel/core');
const { readFileSync, outputFileSync } = require('fs-extra');
const srcDir = join(__dirname, '..', 'src');
const distDir = join(__dirname, '..', 'dist');
const srcFiles = glob.sync(join(srcDir, '**', '*.ts'), {
ignore: ['**/node_modules', '**/*.spec.ts'],
});
const compile = (filePath, distDir) =>
new Promise((resolve, reject) => {
const code = readFileSync(filePath, 'utf-8');
const distPath = filePath.replace(srcDir, distDir).replace('.ts', '.js');
transformAsync(code, { filename: filePath })
.then((result) => {
if (result) {
outputFileSync(distPath, result.code);
resolve();
}
})
.catch(reject);
});
async function build() {
// esm output
await Promise.all(
srcFiles.map((srcFile) => compile(srcFile, join(distDir, 'esm')))
);
// cjs output
process.env.BABEL_MODULE = 'commonjs';
await Promise.all(
srcFiles.map((srcFile) => compile(srcFile, join(distDir, 'cjs')))
);
}
build();

View File

@ -1,5 +1,4 @@
import { Ref, unref } from 'vue';
import { inBrowser } from '../utils';
import { useEventListener } from '../useEventListener';
export type UseClickAwayOptions = {
@ -11,10 +10,6 @@ export function useClickAway(
listener: EventListener,
options: UseClickAwayOptions = {}
) {
if (!inBrowser) {
return;
}
const { eventName = 'click' } = options;
const onClick = (event: Event) => {

View File

@ -1,18 +1,21 @@
import { ref } from 'vue';
import { ref, Ref } from 'vue';
import { inBrowser } from '../utils';
import { useEventListener } from '../useEventListener';
let visibility: Ref<VisibilityState>;
export function usePageVisibility() {
const visibility = ref<VisibilityState>('visible');
if (!visibility) {
visibility = ref<VisibilityState>('visible');
const setVisibility = () => {
if (inBrowser) {
visibility.value = document.hidden ? 'hidden' : 'visible';
}
};
const update = () => {
visibility.value = document.hidden ? 'hidden' : 'visible';
};
setVisibility();
useEventListener('visibilitychange', setVisibility);
update();
window.addEventListener('visibilitychange', update);
}
}
return visibility;
}

View File

@ -1,19 +1,16 @@
import { Ref, unref } from 'vue';
function isWindow(val: unknown): val is Window {
return val === window;
}
const isWindow = (val: unknown): val is Window => val === window;
function makeDOMRect(width: number, height: number) {
return {
const makeDOMRect = (width: number, height: number) =>
({
top: 0,
left: 0,
right: width,
bottom: height,
width,
height,
} as DOMRect;
}
} as DOMRect);
export const useRect = (
elementOrRef: Element | Window | Ref<Element | Window | undefined>
@ -26,7 +23,7 @@ export const useRect = (
return makeDOMRect(width, height);
}
if (element && element.getBoundingClientRect) {
if (element?.getBoundingClientRect) {
return element.getBoundingClientRect();
}

View File

@ -1,6 +1,5 @@
import { ref, Ref } from 'vue';
import { inBrowser } from '../utils';
import { useEventListener } from '../useEventListener';
let width: Ref<number>;
let height: Ref<number>;
@ -10,16 +9,16 @@ export function useWindowSize() {
width = ref(0);
height = ref(0);
const update = () => {
if (inBrowser) {
if (inBrowser) {
const update = () => {
width.value = window.innerWidth;
height.value = window.innerHeight;
}
};
};
update();
useEventListener('resize', update);
useEventListener('orientationchange', update);
update();
window.addEventListener('resize', update, { passive: true });
window.addEventListener('orientationchange', update, { passive: true });
}
}
return { width, height };

View File

@ -1,4 +1,4 @@
import { raf, cancelRaf } from '../utils';
import { raf, cancelRaf } from '../src/utils';
test('raf', async () => {
const spy = jest.fn();

View File

@ -1,13 +1,12 @@
{
"compilerOptions": {
"target": "ES2015",
"outDir": "./dist/types",
"module": "ES2015",
"outDir": "./dist",
"module": "ESNext",
"strict": true,
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"moduleResolution": "Node"
"moduleResolution": "Node",
},
"include": ["src/**/*"]
}

View File

@ -4,12 +4,24 @@
### npm
```bash
# Install Vant 2 for Vue 2 project
npm i vant -S
Using `npm` to install:
# Install Vant 3 for Vue 3 project
npm i vant@next -S
```bash
# install Vant 2 for Vue 2 project
npm i vant@2
# install Vant 3 for Vue 3 project
npm i vant@3
```
Using `yarn` or `pnpm`:
```bash
# with yarn
yarn add vant@3
# with pnpm
pnpm add vant@3
```
### CDN

View File

@ -8,14 +8,24 @@
### 通过 npm 安装
在现有项目中使用 Vant 时,可以通过 `npm` `yarn` 进行安装:
在现有项目中使用 Vant 时,可以通过 `npm` 进行安装:
```bash
# Vue 2 项目,安装 Vant 2
npm i vant -S
# Vue 2 项目,安装 Vant 2
npm i vant@2
# Vue 3 项目,安装 Vant 3
npm i vant@next -S
# Vue 3 项目,安装 Vant 3
npm i vant@3
```
当然,你也可以通过 `yarn``pnpm` 进行安装:
```bash
# 通过 yarn 安装
yarn add vant@3
# 通过 pnpm 安装
pnpm add vant@3
```
### 通过 CDN 安装

View File

@ -2,6 +2,7 @@ module.exports = {
testPathIgnorePatterns: ['/node_modules/'],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx,vue}',
'!src/lazyload/vue-lazyload/**',
'!**/demo/**',
'!**/test/**',
'!**/lang/**',

View File

@ -45,18 +45,18 @@
"license": "MIT",
"dependencies": {
"@vant/icons": "^1.7.1",
"@vant/lazyload": "^1.4.0",
"@vant/popperjs": "^1.1.0",
"@vant/use": "^1.3.2"
"@vant/use": "^1.3.3"
},
"peerDependencies": {
"vue": "^3.0.0"
},
"devDependencies": {
"@vant/area-data": "^1.1.3",
"@vant/cli": "^4.0.0-beta.5",
"@vant/cli": "workspace:*",
"@vue/compiler-sfc": "^3.2.20",
"@vue/runtime-core": "^3.2.20",
"@vue/test-utils": "^2.0.0-rc.16",
"typescript": "4.x",
"vue": "^3.2.20",
"vue-router": "^4.0.12"

View File

@ -27,7 +27,8 @@ import { useExpose } from '../composables/use-expose';
// Components
import { Area, AreaList, AreaColumnOption, AreaInstance } from '../area';
import { Cell } from '../cell';
import { Field } from '../field';
import { Form } from '../form';
import { Field, FieldRule } from '../field';
import { Popup } from '../popup';
import { Toast } from '../toast';
import { Button } from '../button';
@ -111,25 +112,16 @@ export default defineComponent({
setup(props, { emit, slots }) {
const areaRef = ref<AreaInstance>();
const state = reactive({
data: {} as AddressEditInfo,
showAreaPopup: false,
detailFocused: false,
errorInfo: {
tel: '',
name: '',
areaCode: '',
postalCode: '',
addressDetail: '',
} as Record<string, string>,
});
const data = reactive({} as AddressEditInfo);
const showAreaPopup = ref(false);
const detailFocused = ref(false);
const areaListLoaded = computed(
() => isObject(props.areaList) && Object.keys(props.areaList).length
);
const areaText = computed(() => {
const { country, province, city, county, areaCode } = state.data;
const { country, province, city, county, areaCode } = data;
if (areaCode) {
const arr = [country, province, city, county];
if (province && province === city) {
@ -142,7 +134,7 @@ export default defineComponent({
// hide bottom field when use search && detail get focused
const hideBottomFields = computed(
() => props.searchResult?.length && state.detailFocused
() => props.searchResult?.length && detailFocused.value
);
const assignAreaValues = () => {
@ -150,70 +142,52 @@ export default defineComponent({
const detail: Record<string, string> = areaRef.value.getArea();
detail.areaCode = detail.code;
delete detail.code;
extend(state.data, detail);
extend(data, detail);
}
};
const onFocus = (key: string) => {
state.errorInfo[key] = '';
state.detailFocused = key === 'addressDetail';
detailFocused.value = key === 'addressDetail';
emit('focus', key);
};
const getErrorMessage = (key: string) => {
const value = String((state.data as any)[key] || '').trim();
const rules = computed<Record<string, FieldRule[]>>(() => {
const { validator, telValidator, postalValidator } = props;
if (props.validator) {
const message = props.validator(key, value);
if (message) {
return message;
}
}
switch (key) {
case 'name':
return value ? '' : t('nameEmpty');
case 'tel':
return props.telValidator(value) ? '' : t('telInvalid');
case 'areaCode':
return value ? '' : t('areaEmpty');
case 'addressDetail':
return value ? '' : t('addressEmpty');
case 'postalCode':
return value && !props.postalValidator(value) ? t('postalEmpty') : '';
}
};
const onSave = () => {
const items = ['name', 'tel'];
if (props.showArea) {
items.push('areaCode');
}
if (props.showDetail) {
items.push('addressDetail');
}
if (props.showPostal) {
items.push('postalCode');
}
const isValid = items.every((item) => {
const msg = getErrorMessage(item);
if (msg) {
state.errorInfo[item] = msg;
}
return !msg;
const makeRule = (name: string, emptyMessage: string): FieldRule => ({
validator: (value) => {
if (validator) {
const message = validator(name, value);
if (message) {
return message;
}
}
if (!value) {
return emptyMessage;
}
return true;
},
});
if (isValid && !props.isSaving) {
emit('save', state.data);
}
};
return {
name: [makeRule('name', t('nameEmpty'))],
tel: [
makeRule('tel', t('telInvalid')),
{ validator: telValidator, message: t('telInvalid') },
],
areaCode: [makeRule('areaCode', t('areaEmpty'))],
addressDetail: [makeRule('addressDetail', t('addressEmpty'))],
postalCode: [
makeRule('addressDetail', t('postalEmpty')),
{ validator: postalValidator, message: t('postalEmpty') },
],
};
});
const onSave = () => emit('save', data);
const onChangeDetail = (val: string) => {
state.data.addressDetail = val;
data.addressDetail = val;
emit('change-detail', val);
};
@ -222,22 +196,21 @@ export default defineComponent({
if (values.some((value) => !value.code)) {
Toast(t('areaEmpty'));
return;
} else {
showAreaPopup.value = false;
assignAreaValues();
emit('change-area', values);
}
state.showAreaPopup = false;
assignAreaValues();
emit('change-area', values);
};
const onDelete = () => emit('delete', state.data);
const onDelete = () => emit('delete', data);
// get values of area component
const getArea = () => (areaRef.value ? areaRef.value.getValues() : []);
const getArea = () => areaRef.value?.getValues() || [];
// set area code to area component
const setAreaCode = (code?: string) => {
state.data.areaCode = code || '';
data.areaCode = code || '';
if (code) {
nextTick(assignAreaValues);
@ -247,12 +220,12 @@ export default defineComponent({
const onDetailBlur = () => {
// await for click search event
setTimeout(() => {
state.detailFocused = false;
detailFocused.value = false;
});
};
const setAddressDetail = (value: string) => {
state.data.addressDetail = value;
data.addressDetail = value;
};
const renderSetDefaultCell = () => {
@ -260,7 +233,7 @@ export default defineComponent({
const slots = {
'right-icon': () => (
<Switch
v-model={state.data.isDefault}
v-model={data.isDefault}
size="24"
onChange={(event) => emit('change-default', event)}
/>
@ -277,8 +250,6 @@ export default defineComponent({
/>
);
}
return null;
};
useExpose({
@ -289,13 +260,13 @@ export default defineComponent({
watch(
() => props.areaList,
() => setAreaCode(state.data.areaCode)
() => setAreaCode(data.areaCode)
);
watch(
() => props.addressInfo,
(value) => {
state.data = extend({}, DEFAULT_DATA, value);
extend(data, DEFAULT_DATA, value);
setAreaCode(value.areaCode);
},
{
@ -305,18 +276,17 @@ export default defineComponent({
);
return () => {
const { data, errorInfo } = state;
const { disableArea } = props;
return (
<div class={bem()}>
<Form class={bem()} onSubmit={onSave}>
<div class={bem('fields')}>
<Field
v-model={data.name}
clearable
label={t('name')}
rules={rules.value.name}
placeholder={t('name')}
errorMessage={errorInfo.name}
onFocus={() => onFocus('name')}
/>
<Field
@ -324,9 +294,9 @@ export default defineComponent({
clearable
type="tel"
label={t('tel')}
rules={rules.value.tel}
maxlength={props.telMaxlength}
placeholder={t('tel')}
errorMessage={errorInfo.tel}
onFocus={() => onFocus('tel')}
/>
<Field
@ -335,22 +305,22 @@ export default defineComponent({
label={t('area')}
is-link={!disableArea}
modelValue={areaText.value}
rules={rules.value.areaCode}
placeholder={props.areaPlaceholder || t('area')}
errorMessage={errorInfo.areaCode}
onFocus={() => onFocus('areaCode')}
onClick={() => {
emit('click-area');
state.showAreaPopup = !disableArea;
showAreaPopup.value = !disableArea;
}}
/>
<AddressEditDetail
show={props.showDetail}
rows={props.detailRows}
rules={rules.value.addressDetail}
value={data.addressDetail}
focused={state.detailFocused}
detailRows={props.detailRows}
errorMessage={errorInfo.addressDetail}
focused={detailFocused.value}
maxlength={props.detailMaxlength}
searchResult={props.searchResult}
detailMaxlength={props.detailMaxlength}
showSearchResult={props.showSearchResult}
onBlur={onDetailBlur}
onFocus={() => onFocus('addressDetail')}
@ -362,10 +332,10 @@ export default defineComponent({
v-show={!hideBottomFields.value}
v-model={data.postalCode}
type="tel"
rules={rules.value.postalCode}
label={t('postal')}
maxlength="6"
placeholder={t('postal')}
errorMessage={errorInfo.postalCode}
onFocus={() => onFocus('postalCode')}
/>
)}
@ -380,6 +350,7 @@ export default defineComponent({
text={props.saveButtonText || t('save')}
class={bem('button')}
loading={props.isSaving}
nativeType="submit"
onClick={onSave}
/>
{props.showDelete && (
@ -394,7 +365,7 @@ export default defineComponent({
)}
</div>
<Popup
v-model:show={state.showAreaPopup}
v-model:show={showAreaPopup.value}
round
teleport="body"
position="bottom"
@ -408,11 +379,11 @@ export default defineComponent({
columnsPlaceholder={props.areaColumnsPlaceholder}
onConfirm={onAreaConfirm}
onCancel={() => {
state.showAreaPopup = false;
showAreaPopup.value = false;
}}
/>
</Popup>
</div>
</Form>
);
};
},

View File

@ -9,7 +9,7 @@ import { Field } from '../field';
// Types
import type { AddressEditSearchItem } from './types';
import type { FieldInstance } from '../field/types';
import type { FieldRule, FieldInstance } from '../field/types';
const [name, bem, t] = createNamespace('address-edit-detail');
@ -18,12 +18,12 @@ export default defineComponent({
props: {
show: Boolean,
rows: numericProp,
value: String,
rules: Array as PropType<FieldRule[]>,
focused: Boolean,
detailRows: numericProp,
maxlength: numericProp,
searchResult: Array as PropType<AddressEditSearchItem[]>,
errorMessage: String,
detailMaxlength: numericProp,
showSearchResult: Boolean,
},
@ -86,14 +86,14 @@ export default defineComponent({
clearable
ref={field}
class={bem()}
rows={props.detailRows}
rows={props.rows}
type="textarea"
rules={props.rules}
label={t('label')}
border={!showSearchResult()}
maxlength={props.detailMaxlength}
maxlength={props.maxlength}
modelValue={props.value}
placeholder={t('placeholder')}
errorMessage={props.errorMessage}
onBlur={onBlur}
onFocus={onFocus}
onUpdate:modelValue={onInput}

View File

@ -2,7 +2,7 @@
exports[`should render demo and match snapshot 1`] = `
<div>
<div class="van-address-edit">
<form class="van-form van-address-edit">
<div class="van-address-edit__fields">
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -104,7 +104,7 @@ exports[`should render demo and match snapshot 1`] = `
</div>
</div>
<div class="van-address-edit__buttons">
<button type="button"
<button type="submit"
class="van-button van-button--danger van-button--normal van-button--block van-button--round van-address-edit__button"
>
<div class="van-button__content">
@ -123,6 +123,6 @@ exports[`should render demo and match snapshot 1`] = `
</div>
</button>
</div>
</div>
</form>
</div>
`;

View File

@ -7,7 +7,7 @@ exports[`should allow to custom validator with validator prop 1`] = `
`;
exports[`should render AddressEdit correctly 1`] = `
<div class="van-address-edit">
<form class="van-form van-address-edit">
<div class="van-address-edit__fields">
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -78,7 +78,7 @@ exports[`should render AddressEdit correctly 1`] = `
</div>
</div>
<div class="van-address-edit__buttons">
<button type="button"
<button type="submit"
class="van-button van-button--danger van-button--normal van-button--block van-button--round van-address-edit__button"
>
<div class="van-button__content">
@ -88,11 +88,11 @@ exports[`should render AddressEdit correctly 1`] = `
</div>
</button>
</div>
</div>
</form>
`;
exports[`should render AddressEdit with props correctly 1`] = `
<div class="van-address-edit">
<form class="van-form van-address-edit">
<div class="van-address-edit__fields">
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -193,7 +193,7 @@ exports[`should render AddressEdit with props correctly 1`] = `
</div>
</div>
<div class="van-address-edit__buttons">
<button type="button"
<button type="submit"
class="van-button van-button--danger van-button--normal van-button--block van-button--round van-address-edit__button"
>
<div class="van-button__content">
@ -203,7 +203,7 @@ exports[`should render AddressEdit with props correctly 1`] = `
</div>
</button>
</div>
</div>
</form>
`;
exports[`should valid address detail and render error message correctly 1`] = `
@ -229,26 +229,6 @@ exports[`should valid address detail and render error message correctly 1`] = `
</div>
`;
exports[`should valid address detail and render error message correctly 2`] = `
<div class="van-cell van-field van-address-edit-detail">
<div class="van-cell__title van-field__label">
<label>
Address
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<textarea rows="1"
class="van-field__control"
placeholder="Address"
style="height: auto;"
>
</textarea>
</div>
</div>
</div>
`;
exports[`should valid area code and render error message correctly 1`] = `
<div class="van-cell van-cell--clickable van-field"
role="button"
@ -276,30 +256,6 @@ exports[`should valid area code and render error message correctly 1`] = `
</div>
`;
exports[`should valid area code and render error message correctly 2`] = `
<div class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div class="van-cell__title van-field__label">
<label>
Area
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
class="van-field__control"
readonly
placeholder="Area"
>
</div>
</div>
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
</i>
</div>
`;
exports[`should valid name and render error message correctly 1`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -321,24 +277,6 @@ exports[`should valid name and render error message correctly 1`] = `
</div>
`;
exports[`should valid name and render error message correctly 2`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label>
Name
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
class="van-field__control"
placeholder="Name"
>
</div>
</div>
</div>
`;
exports[`should valid postal code and render error message correctly 1`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -360,24 +298,6 @@ exports[`should valid postal code and render error message correctly 1`] = `
</div>
`;
exports[`should valid postal code and render error message correctly 2`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label>
Postal
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
class="van-field__control"
placeholder="Postal"
>
</div>
</div>
</div>
`;
exports[`should valid tel and render error message correctly 1`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
@ -398,21 +318,3 @@ exports[`should valid tel and render error message correctly 1`] = `
</div>
</div>
`;
exports[`should valid tel and render error message correctly 2`] = `
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label>
Phone
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
class="van-field__control"
placeholder="Phone"
>
</div>
</div>
</div>
`;

View File

@ -1,6 +1,7 @@
import { AddressEdit } from '..';
import { areaList } from '../../area/demo/area-simple';
import { mount, later, trigger } from '../../../test';
import { submitForm } from '../../form/test/shared';
const defaultAddressInfo = {
name: '测试',
@ -27,12 +28,10 @@ const createComponent = (addressInfo = {}) => {
},
});
const button = wrapper.find('.van-button');
const fields = wrapper.findAll('.van-field');
return {
vm: wrapper.vm,
fields,
button,
wrapper,
};
};
@ -55,13 +54,6 @@ test('should render AddressEdit with props correctly', () => {
expect(wrapper.html()).toMatchSnapshot();
});
// test('set-default', () => {
// const { wrapper } = createComponent();
// wrapper.find('.van-switch').trigger('click');
// expect(wrapper.html()).toMatchSnapshot();
// });
test('should allow to custom validator with validator prop', async () => {
const wrapper = mount(AddressEdit, {
props: {
@ -70,63 +62,53 @@ test('should allow to custom validator with validator prop', async () => {
},
});
const button = wrapper.find('.van-button');
await button.trigger('click');
await submitForm(wrapper);
expect(wrapper.find('.van-field__error-message').html()).toMatchSnapshot();
});
test('should valid name and render error message correctly', async () => {
const { fields, button } = createComponent({
const { fields, wrapper } = createComponent({
name: '',
});
await button.trigger('click');
expect(fields[0].html()).toMatchSnapshot();
await fields[0].find('input').trigger('focus');
await submitForm(wrapper);
expect(fields[0].html()).toMatchSnapshot();
});
test('should valid tel and render error message correctly', async () => {
const { fields, button } = createComponent({
const { fields, wrapper } = createComponent({
tel: '',
});
await button.trigger('click');
expect(fields[1].html()).toMatchSnapshot();
await fields[1].find('input').trigger('focus');
await submitForm(wrapper);
expect(fields[1].html()).toMatchSnapshot();
});
test('should valid area code and render error message correctly', async () => {
const { fields, button } = createComponent({
const { fields, wrapper } = createComponent({
areaCode: '',
});
await button.trigger('click');
expect(fields[2].html()).toMatchSnapshot();
await fields[2].find('input').trigger('focus');
await submitForm(wrapper);
expect(fields[2].html()).toMatchSnapshot();
});
test('should valid address detail and render error message correctly', async () => {
const { fields, button } = createComponent({
const { fields, wrapper } = createComponent({
addressDetail: '',
});
await button.trigger('click');
expect(fields[3].html()).toMatchSnapshot();
await fields[3].find('textarea').trigger('focus');
await submitForm(wrapper);
await later();
expect(fields[3].html()).toMatchSnapshot();
});
test('should valid postal code and render error message correctly', async () => {
const { fields, button } = createComponent({
const { fields, wrapper } = createComponent({
postalCode: '123',
});
await button.trigger('click');
expect(fields[4].html()).toMatchSnapshot();
await fields[4].find('input').trigger('focus');
await submitForm(wrapper);
expect(fields[4].html()).toMatchSnapshot();
});

View File

@ -55,7 +55,14 @@ export default {
Vant officially provides a default area data, which can be imported through [@vant/area-data](https://github.com/youzan/vant/tree/dev/packages/vant-area-data):
```bash
# with npm
npm i @vant/area-data
# with yarn
yarn add @vant/area-data
# with pnpm
pnpm add @vant/area-data
```
```ts

View File

@ -57,7 +57,14 @@ const areaList = {
Vant 官方提供了一份默认的省市区数据,可以通过 [@vant/area-data](https://github.com/youzan/vant/tree/dev/packages/vant-area-data) 引入:
```bash
# 通过 npm
npm i @vant/area-data
# 通过 yarn
yarn add @vant/area-data
# 通过 pnpm
pnpm add @vant/area-data
```
```ts

View File

@ -7,13 +7,9 @@ export type CheckboxLabelPosition = CheckerLabelPosition;
export type CheckboxExpose = {
toggle: (newValue?: boolean) => void;
/**
* @private
*/
/** @private */
props: CheckboxProps;
/**
* @private
*/
/** @private */
checked: ComputedRef<boolean>;
};

View File

@ -17,10 +17,12 @@ export const routeProps = {
export type RouteProps = ExtractPropTypes<typeof routeProps>;
export function route(vm: ComponentPublicInstance<RouteProps>) {
const router = vm.$router;
const { to, url, replace } = vm;
export function route({
to,
url,
replace,
$router: router,
}: ComponentPublicInstance<RouteProps>) {
if (to && router) {
router[replace ? 'replace' : 'push'](to);
} else if (url) {

View File

@ -14,17 +14,13 @@ export type DropdownItemExpose = {
immediate?: boolean;
}
) => void;
/**
* @private
*/
/** @private */
state: {
showPopup: boolean;
transition: boolean;
showWrapper: boolean;
};
/**
* @private
*/
/** @private */
renderTitle: () => string | VNode[];
};

View File

@ -310,13 +310,16 @@ export default defineComponent({
nextTick(adjustTextareaSize);
// readonly not work in legacy mobile safari
const readonly = getProp('readonly');
if (readonly) {
if (getProp('readonly')) {
blur();
}
};
const onBlur = (event: Event) => {
if (getProp('readonly')) {
return;
}
state.focused = false;
updateValue(getModelValue(), 'onBlur');
emit('blur', event);

View File

@ -318,8 +318,11 @@ import type {
FieldProps,
FieldInstance,
FieldTextAlign,
FieldRuleMessage,
FieldClearTrigger,
FieldFormatTrigger,
FieldRuleValidator,
FiledRuleFormatter,
FieldValidateError,
FieldAutosizeConfig,
FieldValidateTrigger,

View File

@ -337,8 +337,11 @@ import type {
FieldProps,
FieldInstance,
FieldTextAlign,
FieldRuleMessage,
FieldClearTrigger,
FieldFormatTrigger,
FieldRuleValidator,
FiledRuleFormatter,
FieldValidateError,
FieldAutosizeConfig,
FieldValidateTrigger,

View File

@ -9,8 +9,11 @@ export type {
FieldRule,
FieldInstance,
FieldTextAlign,
FieldRuleMessage,
FieldClearTrigger,
FieldFormatTrigger,
FieldRuleValidator,
FiledRuleFormatter,
FieldValidateError,
FieldAutosizeConfig,
FieldValidateTrigger,

View File

@ -1,3 +1,4 @@
/* eslint-disable no-use-before-define */
import type { ComputedRef, ComponentPublicInstance } from 'vue';
import type { FieldProps } from './Field';
@ -28,16 +29,24 @@ export type FieldValidateError = {
message: string;
};
export type FieldRuleMessage =
| string
| ((value: any, rule: FieldRule) => string);
export type FieldRuleValidator = (
value: any,
rule: FieldRule
) => boolean | string | Promise<boolean | string>;
export type FiledRuleFormatter = (value: any, rule: FieldRule) => string;
export type FieldRule = {
pattern?: RegExp;
trigger?: FieldValidateTrigger;
message?: string | ((value: any, rule: FieldRule) => string);
message?: FieldRuleMessage;
required?: boolean;
validator?: (
value: any,
rule: FieldRule
) => boolean | string | Promise<boolean | string>;
formatter?: (value: any, rule: FieldRule) => string;
validator?: FieldRuleValidator;
formatter?: FiledRuleFormatter;
};
// Shared props of Field and Form
@ -57,9 +66,7 @@ export type FieldExpose = {
rules?: FieldRule[] | undefined
) => Promise<void | FieldValidateError>;
resetValidation: () => void;
/**
* @private
*/
/** @private */
formValue: ComputedRef<unknown>;
};

View File

@ -63,15 +63,10 @@ export default defineComponent({
const { $Lazyload } = getCurrentInstance()!.proxy!;
const style = computed(() => {
const style: CSSProperties = {};
if (isDef(props.width)) {
style.width = addUnit(props.width);
}
if (isDef(props.height)) {
style.height = addUnit(props.height);
}
const style: CSSProperties = {
width: addUnit(props.width),
height: addUnit(props.height),
};
if (isDef(props.radius)) {
style.overflow = 'hidden';

View File

@ -1,4 +1,4 @@
import { Lazyload } from '@vant/lazyload';
import { Lazyload } from './vue-lazyload';
export default Lazyload;
export { Lazyload };

View File

@ -1,4 +1,4 @@
import { App } from 'vue';
import type { App } from 'vue';
declare type ListenEvent =
| 'scroll'

View File

@ -1,3 +1,7 @@
/**
* This is a fork of [vue-lazyload](https://github.com/hilongjw/vue-lazyload) with Vue 3 support.
*/
import Lazy from './lazy';
import LazyComponent from './lazy-component';
import LazyContainer from './lazy-container';

View File

@ -1,5 +1,5 @@
import { h } from 'vue';
import { inBrowser } from '@vant/use';
import { inBrowser, useRect } from '@vant/use';
export default (lazy) => ({
props: {
@ -24,7 +24,6 @@ export default (lazy) => ({
state: {
loaded: false,
},
rect: {},
show: false,
};
},
@ -40,18 +39,14 @@ export default (lazy) => ({
},
methods: {
getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView() {
this.getRect();
const rect = useRect(this.$el);
return (
inBrowser &&
this.rect.top < window.innerHeight * lazy.options.preLoad &&
this.rect.bottom > 0 &&
this.rect.left < window.innerWidth * lazy.options.preLoad &&
this.rect.right > 0
rect.top < window.innerHeight * lazy.options.preLoad &&
rect.bottom > 0 &&
rect.left < window.innerWidth * lazy.options.preLoad &&
rect.right > 0
);
},

View File

@ -1,5 +1,6 @@
import { inBrowser } from '@vant/use';
import { loadImageAsync, noop } from './util';
import { useRect } from '@vant/use';
import { loadImageAsync } from './util';
import { noop } from '../../utils';
export default (lazyManager) => ({
props: {
@ -34,7 +35,6 @@ export default (lazyManager) => ({
error: false,
attempt: 0,
},
rect: {},
renderSrc: '',
};
},
@ -66,17 +66,13 @@ export default (lazyManager) => ({
this.options.loading = loading;
this.renderSrc = this.options.loading;
},
getRect() {
this.rect = this.$el.getBoundingClientRect();
},
checkInView() {
this.getRect();
const rect = useRect(this.$el);
return (
inBrowser &&
this.rect.top < window.innerHeight * lazyManager.options.preLoad &&
this.rect.bottom > 0 &&
this.rect.left < window.innerWidth * lazyManager.options.preLoad &&
this.rect.right > 0
rect.top < window.innerHeight * lazyManager.options.preLoad &&
rect.bottom > 0 &&
rect.left < window.innerWidth * lazyManager.options.preLoad &&
rect.right > 0
);
},
load(onFinish = noop) {

View File

@ -8,11 +8,11 @@ import {
supportWebp,
getDPR,
getBestSelectionFromSrcset,
isObject,
hasIntersectionObserver,
modeType,
ImageCache,
} from './util';
import { isObject } from '../../utils';
import ReactiveListener from './listener';
const DEFAULT_URL =
@ -64,7 +64,6 @@ export default function () {
attempt: attempt || 3,
scale: scale || getDPR(scale),
ListenEvents: listenEvents || DEFAULT_EVENTS,
hasbind: false,
supportWebp: supportWebp(),
filter: filter || {},
adapter: adapter || {},

View File

@ -1,4 +1,6 @@
import { loadImageAsync, noop } from './util';
import { useRect } from '@vant/use';
import { loadImageAsync } from './util';
import { noop } from '../../utils';
export default class ReactiveListener {
constructor({
@ -26,8 +28,6 @@ export default class ReactiveListener {
this.options = options;
this.rect = null;
this.$parent = $parent;
this.elRenderer = elRenderer;
this._imageCache = imageCache;
@ -87,25 +87,17 @@ export default class ReactiveListener {
}
}
/*
* get el node rect
* @return
*/
getRect() {
this.rect = this.el.getBoundingClientRect();
}
/*
* check el is in view
* @return {Boolean} el is in view
*/
checkInView() {
this.getRect();
const rect = useRect(this.el);
return (
this.rect.top < window.innerHeight * this.options.preLoad &&
this.rect.bottom > this.options.preLoadTop &&
this.rect.left < window.innerWidth * this.options.preLoad &&
this.rect.right > 0
rect.top < window.innerHeight * this.options.preLoad &&
rect.bottom > this.options.preLoadTop &&
rect.left < window.innerWidth * this.options.preLoad &&
rect.right > 0
);
}

View File

@ -158,12 +158,6 @@ export const loadImageAsync = (item, resolve, reject) => {
image.onerror = (e) => reject(e);
};
export function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
export function noop() {}
export class ImageCache {
constructor({ max }) {
this.options = {

View File

@ -19,9 +19,7 @@ export type SwipeExpose = {
next: () => void;
resize: () => void;
swipeTo: (index: number, options?: SwipeToOptions) => void;
/**
* @private
*/
/** @private */
state: SwipeState;
};

View File

@ -31,6 +31,8 @@
left: 0;
width: var(--van-switch-node-size);
height: var(--van-switch-node-size);
// https://github.com/youzan/vant/issues/9839
font-size: inherit;
background: var(--van-switch-node-background);
border-radius: 100%;
box-shadow: var(--van-switch-node-shadow);

View File

@ -63,13 +63,14 @@ export default defineComponent({
return pathMatched || nameMatched;
}
return (props.name || index.value) === modelValue;
return (props.name ?? index.value) === modelValue;
});
const onClick = (event: MouseEvent) => {
parent.setActive(props.name ?? index.value);
if (!active.value) {
parent.setActive(props.name ?? index.value, route);
}
emit('click', event);
route();
};
const renderIcon = () => {

View File

@ -44,7 +44,7 @@ export type TabbarProps = ExtractPropTypes<typeof tabbarProps>;
export type TabbarProvide = {
props: TabbarProps;
setActive: (active: number | string) => void;
setActive: (active: number | string, afterChange: () => void) => void;
};
export const TABBAR_KEY: InjectionKey<TabbarProvide> = Symbol(name);
@ -83,16 +83,15 @@ export default defineComponent({
);
};
const setActive = (active: number | string) => {
if (active !== props.modelValue) {
callInterceptor(props.beforeChange, {
args: [active],
done() {
emit('update:modelValue', active);
emit('change', active);
},
});
}
const setActive = (active: number | string, afterChange: () => void) => {
callInterceptor(props.beforeChange, {
args: [active],
done() {
emit('update:modelValue', active);
emit('change', active);
afterChange();
},
});
};
linkChildren({ props, setActive });

View File

@ -69,6 +69,34 @@ test('should match active tab by route path in route mode', async () => {
expect(items[3].classes()).not.toContain(activeClass);
});
test('should allow to use before-change prop in route mode', async () => {
const wrapper = mount(
{
render: () => (
<Tabbar route beforeChange={() => false}>
<TabbarItem replace to="/">
Tab
</TabbarItem>
<TabbarItem replace to="/search">
Tab
</TabbarItem>
</Tabbar>
),
},
{
global: {
mocks: getMockRouter(),
},
}
);
const items = wrapper.findAll('.van-tabbar-item');
expect(items[0].classes()).toContain(activeClass);
await items[1].trigger('click');
expect(items[1].classes()).not.toContain(activeClass);
});
test('should match active tab by route name in route mode', async () => {
const wrapper = mount(
{

View File

@ -1,13 +1,13 @@
import { CSSProperties } from 'vue';
import { useWindowSize } from '@vant/use';
import { inBrowser } from './basic';
import { isDef, isNumeric } from './validate';
export function addUnit(value?: string | number): string | undefined {
if (!isDef(value)) {
return undefined;
if (isDef(value)) {
return isNumeric(value) ? `${value}px` : String(value);
}
return isNumeric(value) ? `${value}px` : String(value);
return undefined;
}
export function getSizeStyle(
@ -51,13 +51,15 @@ function convertRem(value: string) {
}
function convertVw(value: string) {
const { width } = useWindowSize();
value = value.replace(/vw/g, '');
return (+value * window.innerWidth) / 100;
return (+value * width.value) / 100;
}
function convertVh(value: string) {
const { height } = useWindowSize();
value = value.replace(/vh/g, '');
return (+value * window.innerHeight) / 100;
return (+value * height.value) / 100;
}
export function unitToPx(value: string | number): number {

View File

@ -6,5 +6,10 @@
"emitDeclarationOnly": true
},
"include": ["es/**/*", "lib/**/*"],
"exclude": ["node_modules", "**/test/**/*", "**/demo/**/*"]
"exclude": [
"node_modules",
"**/test/**/*",
"**/demo/**/*",
"**/vue-lazyload/*"
]
}

117
pnpm-lock.yaml generated
View File

@ -6,18 +6,22 @@ importers:
specifiers:
'@vant/cli': workspace:*
'@vant/eslint-config': workspace:*
'@vant/stylelint-config': workspace:*
eslint: ^8.2.0
husky: ^7.0.4
lint-staged: ^11.2.6
prettier: ^2.4.1
rimraf: ^3.0.2
stylelint: ^13.13.1
devDependencies:
'@vant/cli': link:packages/vant-cli
'@vant/eslint-config': link:packages/vant-eslint-config
'@vant/stylelint-config': link:packages/vant-stylelint-config
eslint: 8.2.0
husky: 7.0.4
lint-staged: 11.2.6
prettier: 2.4.1
rimraf: 3.0.2
stylelint: 13.13.1
packages/create-vant-cli-app:
@ -46,19 +50,18 @@ importers:
packages/vant:
specifiers:
'@vant/area-data': ^1.1.3
'@vant/cli': ^4.0.0-beta.5
'@vant/cli': workspace:*
'@vant/icons': ^1.7.1
'@vant/lazyload': ^1.4.0
'@vant/popperjs': ^1.1.0
'@vant/use': ^1.3.2
'@vant/use': ^1.3.3
'@vue/compiler-sfc': ^3.2.20
'@vue/runtime-core': ^3.2.20
'@vue/test-utils': ^2.0.0-rc.16
typescript: 4.x
vue: ^3.2.20
vue-router: ^4.0.12
dependencies:
'@vant/icons': link:../vant-icons
'@vant/lazyload': link:../vant-lazyload
'@vant/popperjs': link:../vant-popperjs
'@vant/use': link:../vant-use
devDependencies:
@ -66,6 +69,7 @@ importers:
'@vant/cli': link:../vant-cli
'@vue/compiler-sfc': 3.2.21
'@vue/runtime-core': 3.2.21
'@vue/test-utils': 2.0.0-rc.16_vue@3.2.21
typescript: 4.4.4
vue: 3.2.21
vue-router: 4.0.12_vue@3.2.21
@ -93,7 +97,6 @@ importers:
'@vitejs/plugin-vue-jsx': ^1.2.0
'@vue/babel-plugin-jsx': ^1.1.1
'@vue/compiler-sfc': ^3.2.20
'@vue/test-utils': 2.0.0-rc.14
autoprefixer: ^10.4.0
babel-jest: ^27.3.1
chalk: ^4.1.2
@ -146,7 +149,6 @@ importers:
'@vitejs/plugin-vue': 1.9.4_vite@2.6.13
'@vitejs/plugin-vue-jsx': 1.2.0
'@vue/babel-plugin-jsx': 1.1.1_@babel+core@7.16.0
'@vue/test-utils': 2.0.0-rc.14_vue@3.2.21
autoprefixer: 10.4.0_postcss@8.3.11
babel-jest: 27.3.1_@babel+core@7.16.0
chalk: 4.1.2
@ -219,27 +221,6 @@ importers:
devDependencies:
release-it: 14.11.6
packages/vant-lazyload:
specifiers:
'@rollup/plugin-babel': ^5.2.1
'@rollup/plugin-node-resolve': ^10.0.0
'@vant/cli': workspace:*
'@vant/use': ^1.3.2
'@vue/runtime-core': 3.x
release-it: ^14.2.2
rollup: ^2.33.3
vue: 3.x
dependencies:
'@vant/use': link:../vant-use
devDependencies:
'@rollup/plugin-babel': 5.3.0_rollup@2.59.0
'@rollup/plugin-node-resolve': 10.0.0_rollup@2.59.0
'@vant/cli': link:../vant-cli
'@vue/runtime-core': 3.2.21
release-it: 14.11.6
rollup: 2.59.0
vue: 3.2.21
packages/vant-markdown-loader:
specifiers:
front-matter: ^4.0.2
@ -270,19 +251,19 @@ importers:
packages/vant-popperjs:
specifiers:
'@popperjs/core': ^2.9.2
'@rollup/plugin-babel': ^5.2.1
'@rollup/plugin-node-resolve': ^10.0.0
'@vant/cli': workspace:*
'@rollup/plugin-node-resolve': ^13.0.0
release-it: ^14.2.2
rollup: ^2.33.3
rollup-plugin-esbuild: ^4.6.0
typescript: 4.x
dependencies:
'@popperjs/core': 2.10.2
devDependencies:
'@rollup/plugin-babel': 5.3.0_rollup@2.59.0
'@rollup/plugin-node-resolve': 10.0.0_rollup@2.59.0
'@vant/cli': link:../vant-cli
'@rollup/plugin-node-resolve': 13.0.6_rollup@2.59.0
release-it: 14.11.6
rollup: 2.59.0
rollup-plugin-esbuild: 4.6.0_rollup@2.59.0
typescript: 4.4.4
packages/vant-stylelint-config:
specifiers:
@ -300,19 +281,15 @@ importers:
packages/vant-use:
specifiers:
'@babel/core': ^7.12.9
'@vant/cli': workspace:*
fast-glob: ^3.2.7
fs-extra: ^10.0.0
release-it: ^14.0.2
rollup: ^2.33.3
rollup-plugin-esbuild: ^4.6.0
typescript: 4.x
vue: ^3.2.20
devDependencies:
'@babel/core': 7.16.0
'@vant/cli': link:../vant-cli
fast-glob: 3.2.7
fs-extra: 10.0.0
release-it: 14.11.6
rollup: 2.59.0
rollup-plugin-esbuild: 4.6.0_rollup@2.59.0
typescript: 4.4.4
vue: 3.2.21
@ -1972,27 +1949,11 @@ packages:
resolution: {integrity: sha1-B5jAM1Hw3qGlpMq93yalWny+5ZA=, tarball: '@popperjs/core/download/@popperjs/core-2.10.2.tgz'}
dev: false
/@rollup/plugin-babel/5.3.0_rollup@2.59.0:
resolution: {integrity: sha1-nLHFFG3daklorZbyCcUMYvkvmHk=, tarball: '@rollup/plugin-babel/download/@rollup/plugin-babel-5.3.0.tgz'}
/@rollup/plugin-node-resolve/13.0.6_rollup@2.59.0:
resolution: {integrity: sha1-KWKQcLt2dWe+gVf1dc+o8rjp73c=, tarball: '@rollup/plugin-node-resolve/download/@rollup/plugin-node-resolve-13.0.6.tgz'}
engines: {node: '>= 10.0.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@types/babel__core': ^7.1.9
rollup: ^1.20.0||^2.0.0
peerDependenciesMeta:
'@types/babel__core':
optional: true
dependencies:
'@babel/helper-module-imports': 7.16.0
'@rollup/pluginutils': 3.1.0_rollup@2.59.0
rollup: 2.59.0
dev: true
/@rollup/plugin-node-resolve/10.0.0_rollup@2.59.0:
resolution: {integrity: sha1-RAZKK5jfdTDmas+JQf8mL8m06tg=, tarball: '@rollup/plugin-node-resolve/download/@rollup/plugin-node-resolve-10.0.0.tgz'}
engines: {node: '>= 10.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0
rollup: ^2.42.0
dependencies:
'@rollup/pluginutils': 3.1.0_rollup@2.59.0
'@types/resolve': 1.17.1
@ -2021,7 +1982,6 @@ packages:
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
dev: false
/@sindresorhus/is/0.14.0:
resolution: {integrity: sha1-n7OjzzEyMoFR81PeRjLgHlIQK+o=, tarball: '@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz'}
@ -2509,13 +2469,13 @@ packages:
resolution: {integrity: sha1-TNgMDmLPZaetqyRJ6GtvDLM6Ews=, tarball: '@vue/shared/download/@vue/shared-3.2.21.tgz'}
dev: true
/@vue/test-utils/2.0.0-rc.14_vue@3.2.21:
resolution: {integrity: sha1-naG+ew42X/X5RWd9oXv2yKeoOr0=, tarball: '@vue/test-utils/download/@vue/test-utils-2.0.0-rc.14.tgz'}
/@vue/test-utils/2.0.0-rc.16_vue@3.2.21:
resolution: {integrity: sha1-WTgPAocPhWrAAqKcAmgdPz/Lr+s=, tarball: '@vue/test-utils/download/@vue/test-utils-2.0.0-rc.16.tgz'}
peerDependencies:
vue: ^3.0.1
dependencies:
vue: 3.2.21
dev: false
dev: true
/JSONStream/1.3.5:
resolution: {integrity: sha1-MgjB8I06TZkmGrZPkjArwV4RHKA=, tarball: JSONStream/download/JSONStream-1.3.5.tgz}
@ -3594,7 +3554,7 @@ packages:
resolution: {integrity: sha1-pvLc5hL63S7x9Rm3NVHxfoUZmDE=, tarball: deep-is/download/deep-is-0.1.4.tgz}
/deepmerge/4.2.2:
resolution: {integrity: sha1-RNLqNnm49NT/ujPwPYZfwee/SVU=, tarball: deepmerge/download/deepmerge-4.2.2.tgz?cache=0&sync_timestamp=1632822781299&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdeepmerge%2Fdownload%2Fdeepmerge-4.2.2.tgz}
resolution: {integrity: sha1-RNLqNnm49NT/ujPwPYZfwee/SVU=, tarball: deepmerge/download/deepmerge-4.2.2.tgz}
engines: {node: '>=0.10.0'}
/defaults/1.0.3:
@ -4455,6 +4415,7 @@ packages:
graceful-fs: 4.2.8
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
/fs-extra/8.1.0:
resolution: {integrity: sha1-SdQ8RaiM2Wd2aMt74bRu/bjS4cA=, tarball: fs-extra/download/fs-extra-8.1.0.tgz?cache=0&sync_timestamp=1632822706452&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Ffs-extra%2Fdownload%2Ffs-extra-8.1.0.tgz}
@ -5812,6 +5773,11 @@ packages:
- utf-8-validate
dev: false
/joycon/3.0.1:
resolution: {integrity: sha1-kHTJsIzPN6Zyb/dKGEhfhe/K3a8=, tarball: joycon/download/joycon-3.0.1.tgz}
engines: {node: '>=10'}
dev: true
/js-tokens/4.0.0:
resolution: {integrity: sha1-GSA/tZmR35jjoocFDUZHzerzJJk=, tarball: js-tokens/download/js-tokens-4.0.0.tgz}
@ -5921,6 +5887,10 @@ packages:
dependencies:
minimist: 1.2.5
/jsonc-parser/3.0.0:
resolution: {integrity: sha1-q914VwHH5+rKip7IzwcMpRp0WiI=, tarball: jsonc-parser/download/jsonc-parser-3.0.0.tgz}
dev: true
/jsonfile/4.0.0:
resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=, tarball: jsonfile/download/jsonfile-4.0.0.tgz}
optionalDependencies:
@ -5933,6 +5903,7 @@ packages:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.8
dev: false
/jsonparse/1.3.1:
resolution: {integrity: sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=, tarball: jsonparse/download/jsonparse-1.3.1.tgz}
@ -7333,11 +7304,24 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
/rimraf/3.0.2:
resolution: {integrity: sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho=, tarball: rimraf/download/rimraf-3.0.2.tgz?cache=0&sync_timestamp=1632822764504&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Frimraf%2Fdownload%2Frimraf-3.0.2.tgz}
resolution: {integrity: sha1-8aVAK6YiCtUswSgrrBrjqkn9Bho=, tarball: rimraf/download/rimraf-3.0.2.tgz}
hasBin: true
dependencies:
glob: 7.2.0
/rollup-plugin-esbuild/4.6.0_rollup@2.59.0:
resolution: {integrity: sha1-KTsMjS5kt1ORjzAjwfuH3ErN7zs=, tarball: rollup-plugin-esbuild/download/rollup-plugin-esbuild-4.6.0.tgz}
engines: {node: '>=12'}
peerDependencies:
esbuild: '>=0.10.1'
rollup: ^1.20.0 || ^2.0.0
dependencies:
'@rollup/pluginutils': 4.1.1
joycon: 3.0.1
jsonc-parser: 3.0.0
rollup: 2.59.0
dev: true
/rollup/2.59.0:
resolution: {integrity: sha1-EIxhsPoKN+vI0fFk8oFiIFbw21k=, tarball: rollup/download/rollup-2.59.0.tgz}
engines: {node: '>=10.0.0'}
@ -8138,6 +8122,7 @@ packages:
/universalify/2.0.0:
resolution: {integrity: sha1-daSYTv7cSwiXXFrrc/Uw0C3yVxc=, tarball: universalify/download/universalify-2.0.0.tgz}
engines: {node: '>= 10.0.0'}
dev: false
/update-notifier/5.1.0:
resolution: {integrity: sha1-SrDXx/NqIx3XMWz3cpMT8CFNmtk=, tarball: update-notifier/download/update-notifier-5.1.0.tgz}