[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n\troot: true,\n\tparser: 'babel-eslint',\n\tparserOptions: {\n\t\tsourceType: 'module'\n\t},\n\t// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style\n\textends: 'standard',\n  env: {\n    browser: true,\n  },\n\t// required to lint *.vue files\n\tplugins: [\n\t\t'html'\n\t],\n\t// add your custom rules here\n\t'rules': {\n\t\t// allow paren-less arrow functions\n\t\t'arrow-parens': 0,\n\t\t// allow debugger during development\n\t\t'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,\n\t\t'indent': 0,\n    'no-tabs': 0,\n\t\t'space-before-function-paren': 0,\n    'eol-last': 0,\n    'no-unused-expressions': 0\n\t}\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--\nIMPORTANT: Please use the following link to create a new issue:\n\n  https://cube-ui.github.io/cube-issue-helper/?repo=ustbhuangyi/better-scroll\n\nIf your issue was not created using the app above, it will be closed immediately.\n\n中文用户请注意：\n请使用上面的链接来创建新的 issue。如果不是用上述工具创建的 issue 会被自动关闭。\n-->\n"
  },
  {
    "path": ".gitignore",
    "content": ".bin/\nnode_modules/\n.idea/\n.DS_Store\n.npm-debug.log\ncoverage/\n.rpt2_cache\n.vscode/\n\ndist/\nyarn-debug.log*\nyarn-error.log*\nyarn.lock\n"
  },
  {
    "path": ".gitpod.Dockerfile",
    "content": "FROM gitpod/workspace-full\n\nRUN sudo apt-get update && \\\n    sudo apt-get install -y \\\n        ca-certificates \\\n        fonts-liberation \\\n        libappindicator3-1 \\\n        libasound2 \\\n        libatk-bridge2.0-0 \\\n        libatk1.0-0 \\\n        libc6 \\\n        libcairo2 \\\n        libcups2 \\\n        libdbus-1-3 \\\n        libexpat1 \\\n        libfontconfig1 \\\n        libgbm1 \\\n        libgcc1 \\\n        libglib2.0-0 \\\n        libgtk-3-0 \\\n        libnspr4 \\\n        libnss3 \\\n        libpango-1.0-0 \\\n        libpangocairo-1.0-0 \\\n        libstdc++6 \\\n        libx11-6 \\\n        libx11-xcb1 \\\n        libxcb1 \\\n        libxcomposite1 \\\n        libxcursor1 \\\n        libxdamage1 \\\n        libxext6 \\\n        libxfixes3 \\\n        libxi6 \\\n        libxrandr2 \\\n        libxrender1 \\\n        libxss1 \\\n        libxtst6 \\\n        lsb-release \\\n        wget \\\n        xdg-utils && \\\n    sudo rm -rf /var/lib/apt/lists/*\n\n\nRUN sudo apt-get update && \\\n    sudo apt-get install -yq chromium-browser && \\\n    sudo rm -rf /var/lib/apt/lists/*\n\nENV GITPOD=true\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "image:\n  file: .gitpod.Dockerfile\ntasks:\n  - command: gp await-port 8080 && sleep 3 && gp preview $(gp url 8080)/docs\n  - name: Dev\n    init: yarn install && gp sync-done install\n    command: yarn vue:dev\n\n  - name: Docs\n    init: gp sync-await install && yarn docs:build\n    command: yarn docs:dev\n    openMode: split-right\n\nports:\n  - port: 8080\n    onOpen: ignore\n  - port: 8932\n    onOpen: open-preview\n"
  },
  {
    "path": ".huskyrc.json",
    "content": "{\n  \"hooks\": {\n    \"pre-commit\": \"lint-staged\",\n    \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\"\n  }\n}\n"
  },
  {
    "path": ".npmignore",
    "content": "build/\nconfig/\ndocs/\ndoc/\nexample/\nstatic/\n.idea/\n.github/\npostcss.config.js\n.bin/\n.DS_Store\n.npm-debug.log\n.babelrc\n.npmignore\n.editorconfig\n.eslintrc.js\n.tsconfig.json\npackage-lock.json\n\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nsudo: false\ncache:\n  directories:\n    - node_modules\nnode_js:\n  - \"lts/*\"\nbranches:\n  only:\n    - master\n    - dev\nscript:\n  - npm test\n  - ./node_modules/.bin/codecov\n"
  },
  {
    "path": ".yarnrc",
    "content": "registry \"https://registry.npmjs.org\""
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 HuangYi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# better-scroll\n\n<img src=\"https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg\">\n\n[![npm version](https://img.shields.io/npm/v/better-scroll.svg)](https://www.npmjs.com/package/better-scroll) [![downloads](https://img.shields.io/npm/dm/better-scroll.svg)](https://www.npmjs.com/package/better-scroll) [![Build Status](https://travis-ci.org/ustbhuangyi/better-scroll.svg?branch=master)](https://travis-ci.org/ustbhuangyi/better-scroll)  [![Package Quality](http://npm.packagequality.com/shield/better-scroll.svg)](http://packagequality.com/#?package=better-scroll) [![codecov.io](http://codecov.io/github/ustbhuangyi/better-scroll/coverage.svg?branch=master)](http://codecov.io/github/ustbhuangyi/better-scroll) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/)\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/README_zh-CN.md)\n\n[1.x Docs](https://better-scroll.github.io/docs-v1/)\n\n[2.x Docs](https://better-scroll.github.io/docs/en-US/)\n\n[2.x Demo](https://better-scroll.github.io/examples/)\n\n> **Note**: `1.x` is not maintained. please migrate your version as soon as possible\n\n# Install\n\n```bash\nnpm install better-scroll -S # install 2.x，with full-featured plugin.\n\nnpm install @better-scroll/core # only CoreScroll\n```\n\n```js\nimport BetterScroll from 'better-scroll'\n\nlet bs = new BetterScroll('.wrapper', {\n  movable: true,\n  zoom: true\n})\n\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('.wrapper', {})\n```\n\n# CDN\n\nBetterScroll with full-featured plugin.\n\n```html\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js\"></script>\n```\n\n```js\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = BetterScroll.createBScroll(wrapper, {})\n```\n\n\nOnly CoreScroll\n\n```html\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.min.js\"></script>\n```\n\n```js\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = new BScroll(wrapper, {})\n```\n\n## What is BetterScroll ?\n\nBetterScroll is a plugin which is aimed at solving scrolling circumstances on the mobile side (PC supported already). The core is inspired by the implementation of [iscroll](https://github.com/cubiq/iscroll), so the APIs of BetterScroll are compatible with iscroll on the whole. What's more, BetterScroll also extends some features and optimizes for performance based on iscroll.\n\nBetterScroll is implemented with plain JavaScript, which means it's dependency free.\n\n## Getting started\n\nThe most common application scenario of BetterScroll is list scrolling. Let's see its HTML:\n\n```html\n<div class=\"wrapper\">\n  <ul class=\"content\">\n    <li>...</li>\n    <li>...</li>\n    ...\n  </ul>\n  <!-- you can put some other DOMs here, it won't affect the scrolling\n</div>\n```\n\nIn the code above, BetterScroll is applied to the outer `wrapper` container, and the scrolling part is `content` element. Pay attention that BetterScroll handles the scroll of the first child element (content) of the container (`wrapper`) by default, which means other elements will be ignored. However, for BetterScroll v2.0.4, content can be specified through the `specifiedIndexAsContent` option. Please refer to the docs for details.\n\nThe simplest initialization code is as follow:\n\n```javascript\nimport BScroll from '@better-scroll/core'\nlet wrapper = document.querySelector('.wrapper')\nlet scroll = new BScroll(wrapper)\n```\n\nBetterScroll provides a class whose first parameter is a plain DOM object when instantiated. Certainly, BetterScroll inside would try to use querySelector to get the DOM object.\n\n## The principle of scrolling\n\nMany developers have used BetterScroll, but the most common problem they have met is:\n\n> I have initiated BetterScroll, but the content can't scroll.\n\nThe phenomenon is 'the content can't scroll' and we need to figure out the root cause. Before that, let's take a look at the browser's scrolling principle: everyone can see the browser's scroll bar. When the height of the page content exceeds the viewport height, the vertical scroll bar will appear; When the width of page content exceeds the viewport width, the horizontal bar will appear. That is to say, when the viewport can't display all the content, the browser would guide the user to scroll the screen with scroll bar to see the rest of content.\n\nThe principle of BetterScroll is samed as the browser. We can feel about this more obviously using a picture:\n\n![布局](https://raw.githubusercontent.com/ustbhuangyi/better-scroll/master/packages/vuepress-docs/docs/.vuepress/public/assets/images/schematic.png)\n\nThe green part is the wrapper, also known as the parent container, which has **fixed height**. The yellow part is the content, which is **the first child element** of the parent container and whose height would grow with the size of its content. Then, when the height of the content doesn't exceed the height of the parent container, the content would not scroll. Once exceeded, the content can be scrolled. That is the principle of BetterScroll.\n\n## Plugins\n\nEnhance the ability of BetterScroll core scroll through plugins, such as\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullUp from '@better-scroll/pull-up'\n\nlet bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n```\n\nPlease see for details, [Plugins](https://better-scroll.github.io/docs/en-US/plugins/).\n\n## Using BetterScroll with MVVM frameworks\n\nI wrote an article [When BetterScroll meets Vue](https://zhuanlan.zhihu.com/p/27407024) (in Chinese). I also hope that developers can contribute to share the experience of using BetterScroll with other frameworks.\n\nA fantastic mobile ui lib implement by Vue: [cube-ui](https://github.com/didi/cube-ui/)\n\n## Contributing\n\n### Online one-click setup\n\nYou can use Gitpod(An Online Open Source VS Code like IDE which is free for Open Source) for contributing. With a single click it will launch a workspace and automatically:\n\n- clone the `better-scroll` repo.\n- install all of the dependencies.\n- run `yarn vue:dev`,\n- run `yarn docs:build` and `yarn docs:dev`.\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)\n\n## Using BetterScroll in the real project\n\nIf you want to learn how to use BetterScroll in the real project，you can learn my two practical courses(in Chinese)。\n\n[High imitating starvation takeout practical course base on Vue.js](https://coding.imooc.com/class/74.html)\n\n[Project demo address](http://ustbhuangyi.com/sell/)\n\n![QR Code](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fsell%252F%2523%252Fgoods&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=686203a49c4613080b5b3004323ff977)\n\n[Music App advanced practical course base on Vue.js](http://coding.imooc.com/class/107.html)\n\n[Project demo address](http://ustbhuangyi.com/music/)\n\n![QR Code](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fmusic%252F&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=731bbcc2b490454d2cc604f98539952c)\n"
  },
  {
    "path": "README_zh-CN.md",
    "content": "# better-scroll\n\n<img src=\"https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg\">\n\n[![npm version](https://img.shields.io/npm/v/better-scroll.svg)](https://www.npmjs.com/package/better-scroll) [![downloads](https://img.shields.io/npm/dm/better-scroll.svg)](https://www.npmjs.com/package/better-scroll) [![Build Status](https://travis-ci.org/ustbhuangyi/better-scroll.svg?branch=master)](https://travis-ci.org/ustbhuangyi/better-scroll)  [![Package Quality](http://npm.packagequality.com/shield/better-scroll.svg)](http://packagequality.com/#?package=better-scroll)  [![codecov.io](http://codecov.io/github/ustbhuangyi/better-scroll/coverage.svg?branch=master)](http://codecov.io/github/ustbhuangyi/better-scroll)\n\n[1.x Docs](https://better-scroll.github.io/docs-v1/)\n\n[2.x Docs](https://better-scroll.github.io/docs/zh-CN/)\n\n> **注意**：1.x 的代码已经不维护，请尽早升级版本。\n\n# puppeteer 安装\n\nBetterScroll 依赖 puppeteer，在国内下载 puppeteer 容易失败，可以设置 puppeteer 的淘宝镜像。\n\n```sh\nPUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors yarn\n```\n\n# 安装\n\n```bash\nnpm install better-scroll -S  # 安装带有所有插件的 BetterScroll\n\nnpm install @better-scroll/core # 核心滚动，大部分情况可能只需要一个简单的滚动\n```\n\n```js\nimport BetterScroll from 'better-scroll'\n\nlet bs = new BetterScroll('.wrapper', {\n  movable: true,\n  zoom: true\n})\n\nimport BScroll from '@better-scroll/core'\nlet bs = new BScroll('.wrapper', {})\n```\n\n# CDN\n\n带有所有插件的 BetterScroll\n\n```js\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.js\"></script>\n\n// minify\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js\"></script>\n\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = BetterScroll.createBScroll(wrapper, {})\n```\n\n不带有任何插件的 CoreScroll\n\n```js\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.js\"></script>\n\n// minify\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.min.js\"></script>\n\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = new BScroll(wrapper, {})\n```\n\n# BetterScroll 是什么\n\nBetterScroll 是一款重点解决移动端（已支持 PC）各种滚动场景需求的插件。它的核心是借鉴的 [iscroll](https://github.com/cubiq/iscroll) 的实现，它的 API 设计基本兼容 iscroll，在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。\n\nBetterScroll 是使用纯 JavaScript 实现的，这意味着它是无依赖的。\n\n## 起步\n\nBetterScroll 最常见的应用场景是列表滚动，我们来看一下它的 html 结构。\n\n```html\n<div class=\"wrapper\">\n  <ul class=\"content\">\n    <li>...</li>\n    <li>...</li>\n    ...\n  </ul>\n  <!-- 这里可以放一些其它的 DOM，但不会影响滚动 -->\n</div>\n```\n上面的代码中 BetterScroll 是作用在外层 wrapper 容器上的，滚动的部分是 content 元素。这里要注意的是，BetterScroll 默认处理容器（wrapper）的第一个子元素（content）的滚动，其它的元素都会被忽略。不过对于 BetterScroll v2.0.4 版本，可以通过 specifiedIndexAsContent 配置项来指定 content，详细的请参考文档。\n\n最简单的初始化代码如下：\n\n``` js\nimport BScroll from '@better-scroll/core'\nlet wrapper = document.querySelector('.wrapper')\nlet scroll = new BScroll(wrapper)\n```\nBetterScroll 提供了一个类，实例化的第一个参数是一个原生的 DOM 对象。当然，如果传递的是一个字符串，BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。\n\n## 滚动原理\n\n很多人已经用过 BetterScroll，我收到反馈最多的问题是：\n\n> BetterScroll 初始化了， 但是没法滚动。\n\n不能滚动是现象，我们得搞清楚这其中的根本原因。在这之前，我们先来看一下浏览器的滚动原理：\n浏览器的滚动条大家都会遇到，当页面内容的高度超过视口高度的时候，会出现纵向滚动条；当页面内容的宽度超过视口宽度的时候，会出现横向滚动条。也就是当我们的视口展示不下内容的时候，会通过滚动条的方式让用户滚动屏幕看到剩余的内容。\n\nBetterScroll 也是一样的原理，我们可以用一张图更直观的感受一下：\n\n![布局](https://raw.githubusercontent.com/ustbhuangyi/better-scroll/master/packages/vuepress-docs/docs/.vuepress/public/assets/images/schematic.png)\n\n绿色部分为 wrapper，也就是父容器，它会有**固定的高度**。黄色部分为 content，它是父容器的**第一个子元素**，它的高度会随着内容的大小而撑高。那么，当 content 的高度不超过父容器的高度，是不能滚动的，而它一旦超过了父容器的高度，我们就可以滚动内容区了，这就是 BetterScroll 的滚动原理。\n\n## 插件\n\n通过插件，增强 BetterScroll core scroll 的能力，比如\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullUp from '@better-scroll/pull-up'\n\nlet bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n```\n\n详细请看[插件文档](https://better-scroll.github.io/docs/zh-CN/plugins/)\n\n## BetterScroll 在 MVVM 框架的应用\n\n我之前写过一篇[当 BetterScroll 遇见 Vue](https://zhuanlan.zhihu.com/p/27407024)，也希望大家投稿，分享一下 BetterScroll 在其它框架下的使用心得。\n\n一款超赞的基于 Vue 实现的组件库 [cube-ui](https://github.com/didi/cube-ui/)。\n\n## BetterScroll 在实战项目中的运用\n\n如果你想学习 BetterScroll 在实战项目中的运用，也可以去学习我的 2 门实战课程。\n\n[Vue.js 高仿外卖饿了么实战课程](https://coding.imooc.com/class/74.html)\n\n[项目演示地址](http://ustbhuangyi.com/sell/)\n\n![二维码](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fsell%252F%2523%252Fgoods&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=686203a49c4613080b5b3004323ff977)\n\n[Vue.js 音乐 App 高级实战课程](http://coding.imooc.com/class/107.html)\n\n[项目演示地址](http://ustbhuangyi.com/music/)\n\n![二维码](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fmusic%252F&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=731bbcc2b490454d2cc604f98539952c)\n"
  },
  {
    "path": "jest-e2e.config.js",
    "content": "module.exports = {\n  \"verbose\": true,\n  \"roots\": [\"<rootDir>\"],\n  \"cache\": false,\n  \"globals\": {\n    \"ts-jest\": {\n      \"diagnostics\": false\n    }\n  },\n  \"preset\": \"jest-puppeteer\",\n  \"testMatch\": [\"**/tests/e2e/**/*.e2e.ts\"],\n  \"transform\": {\n    \".ts\": \"ts-jest\"\n  },\n  \"moduleFileExtensions\": [\n    \"ts\",\n    \"js\"\n  ]\n}\n"
  },
  {
    "path": "jest-puppeteer.config.js",
    "content": "module.exports = {\n  launch: {\n    headless: process.env.GITPOD !== undefined,\n    defaultViewport: {\n      width: 375,\n      height: 667,\n      deviceScaleFactor: 2,\n      isMobile: true,\n      hasTouch: true\n    },\n    args: ['--disable-infobars', '--no-sandbox', '--disable-setuid-sandbox'],\n  }\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  \"verbose\": true,\n  globals: {},\n  \"roots\": [\"<rootDir>\"],\n  \"cache\": false,\n  \"testRegex\": \"(/__tests__/.*|\\\\.(test|spec))\\\\.(ts|js)$\",\n  \"testPathIgnorePatterns\": [\n    '/__tests__/__utils__'\n  ],\n  \"transform\": {\n    \".ts\": \"ts-jest\"\n  },\n  \"testEnvironment\": \"jsdom\",\n  \"moduleFileExtensions\": [\n    \"ts\",\n    \"js\"\n  ],\n  \"moduleNameMapper\": {\n    '^@better-scroll/(.*)/(.*)$': '<rootDir>/packages/$1/$2',\n    '^@better-scroll/(.*)$': '<rootDir>/packages/$1/src/index',\n    '^@/(.*)$': '<rootDir>/$1'\n  },\n  \"coverageDirectory\": \"<rootDir>/tests/coverage\",\n  \"coveragePathIgnorePatterns\": [\n    \"/test/\",\n    \"/__tests__/\"\n  ],\n  \"coverageReporters\": ['json', 'text', 'lcov', 'clover']\n}\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"npmClient\": \"yarn\",\n  \"useWorkspaces\": true,\n  \"packages\": [\n    \"packages/*\"\n  ],\n  \"version\": \"2.5.1\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"scripts\": {\n    \"bootstrap\": \"lerna bootstrap\",\n    \"packages:build\": \"node scripts/build.js\",\n    \"packages:release\": \"node scripts/release.js\",\n    \"docs:dev\": \"lerna run --stream --scope vuepress-docs docs:dev\",\n    \"docs:build\": \"lerna run --stream --scope vuepress-docs docs:build\",\n    \"docs:release\": \"lerna run --stream --scope vuepress-docs docs:release\",\n    \"vue:dev\": \"lerna run --stream --scope examples vue:dev\",\n    \"vue:build\": \"lerna run --stream --scope examples vue:build\",\n    \"vue:release\": \"lerna run --stream --scope examples vue:release\",\n    \"react:dev\": \"lerna run --stream --scope react-examples dev\",\n    \"react:build\": \"lerna run --stream --scope react-examples build\",\n    \"lint\": \"tslint  --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'\",\n    \"test\": \"jest --coverage && yarn test:tsd\",\n    \"test:e2e\": \"jest --config=jest-e2e.config.js --runInBand\",\n    \"test:tsd\": \"tsc -p ./test-dts/tsconfig.json\",\n    \"vue:test:e2e\": \"lerna run --stream --scope examples vue:test:e2e\",\n    \"cm\": \"git-cz\",\n    \"preinstall\": \"node ./scripts/checkYarn.js\",\n    \"postinstall\": \"yarn bootstrap\"\n  },\n  \"lint-staged\": {\n    \"*.ts\": [\n      \"prettier --write\"\n    ]\n  },\n  \"prettier\": {\n    \"semi\": false,\n    \"singleQuote\": true\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@commitlint/config-conventional\"\n    ]\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^9.1.2\",\n    \"@commitlint/config-conventional\": \"^9.1.2\",\n    \"@types/jest\": \"^26.0.10\",\n    \"@types/node\": \"^14.6.0\",\n    \"@types/puppeteer\": \"^3.0.1\",\n    \"@vuepress/plugin-back-to-top\": \"^1.8.0\",\n    \"@vuepress/plugin-medium-zoom\": \"^1.5.4\",\n    \"codecov\": \"^3.5.0\",\n    \"commitizen\": \"^4.1.5\",\n    \"coveralls\": \"^3.0.2\",\n    \"cross-env\": \"^7.0.2\",\n    \"cz-conventional-changelog\": \"^3.2.0\",\n    \"execa\": \"^4.0.3\",\n    \"husky\": \"^4.2.5\",\n    \"inquirer\": \"^7.3.3\",\n    \"jest\": \"^26.0.1\",\n    \"jest-config\": \"^26.4.2\",\n    \"jest-puppeteer\": \"4.4.0\",\n    \"lerna\": \"^3.14.1\",\n    \"lint-staged\": \"^10.2.11\",\n    \"ora\": \"^5.0.0\",\n    \"prettier\": \"^2.0.5\",\n    \"puppeteer\": \"^5.2.1\",\n    \"rimraf\": \"^3.0.2\",\n    \"rollup\": \"^2.23.0\",\n    \"rollup-plugin-commonjs\": \"^10.1.0\",\n    \"rollup-plugin-json\": \"^4.0.0\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-sourcemaps\": \"^0.6.2\",\n    \"rollup-plugin-typescript2\": \"^0.27.1\",\n    \"rollup-plugin-uglify\": \"^6.0.1\",\n    \"semver\": \"^7.3.2\",\n    \"ts-jest\": \"^26.2.0\",\n    \"ts-loader\": \"^8.0.2\",\n    \"ts-node\": \"^9.0.0\",\n    \"tslint\": \"^6.1.3\",\n    \"tslint-config-prettier\": \"^1.15.0\",\n    \"tslint-config-standard\": \"^9.0.0\",\n    \"typescript\": \"4.0.2\",\n    \"vconsole\": \"^3.3.4\",\n    \"zlib\": \"^1.0.5\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"cz-conventional-changelog\"\n    }\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\",\n    \"Android >= 4.0\",\n    \"iOS >= 8\"\n  ],\n  \"name\": \"better-scroll\"\n}\n"
  },
  {
    "path": "packages/better-scroll/README.md",
    "content": "# better-scroll\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/better-scroll/README_zh-CN.md)\n\nBetterScroll with full plugin capabilities, don't care about the details of various plugin registrations.\n\n## Usage\n\n```js\nimport BScroll from 'better-scroll'\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true,\n  scrollbar: true,\n  pullDownRefresh: true\n  // and so on\n})\n```\n"
  },
  {
    "path": "packages/better-scroll/README_zh-CN.md",
    "content": "# better-scroll\n\n具备完整插件能力的 BetterScroll，不用关心各种插件注册的细节。\n\n## 使用\n\n```js\nimport BScroll from 'better-scroll'\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true,\n  scrollbar: true,\n  pullDownRefresh: true\n  // and so on\n})\n```\n"
  },
  {
    "path": "packages/better-scroll/package.json",
    "content": "{\n  \"name\": \"better-scroll\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Full-featured BetterScroll\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"main\": \"dist/better-scroll.js\",\n  \"module\": \"dist/better-scroll.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"scripts\": {},\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/better-scroll\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\",\n    \"@better-scroll/indicators\": \"^2.5.1\",\n    \"@better-scroll/infinity\": \"^2.5.1\",\n    \"@better-scroll/mouse-wheel\": \"^2.5.1\",\n    \"@better-scroll/movable\": \"^2.5.1\",\n    \"@better-scroll/nested-scroll\": \"^2.5.1\",\n    \"@better-scroll/observe-dom\": \"^2.5.1\",\n    \"@better-scroll/observe-image\": \"^2.5.1\",\n    \"@better-scroll/pull-down\": \"^2.5.1\",\n    \"@better-scroll/pull-up\": \"^2.5.1\",\n    \"@better-scroll/scroll-bar\": \"^2.5.1\",\n    \"@better-scroll/slide\": \"^2.5.1\",\n    \"@better-scroll/wheel\": \"^2.5.1\",\n    \"@better-scroll/zoom\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/better-scroll/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\nimport ObserveDom from '@better-scroll/observe-dom'\nimport PullDownRefresh from '@better-scroll/pull-down'\nimport PullUpLoad from '@better-scroll/pull-up'\nimport ScrollBar from '@better-scroll/scroll-bar'\nimport Slide from '@better-scroll/slide'\nimport Wheel from '@better-scroll/wheel'\nimport Zoom from '@better-scroll/zoom'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport InfinityScroll from '@better-scroll/infinity'\nimport Movable from '@better-scroll/movable'\nimport ObserveImage from '@better-scroll/observe-image'\nimport Indicators from '@better-scroll/indicators'\n\nexport {\n  createBScroll,\n  BScrollInstance,\n  Options,\n  CustomOptions,\n  TranslaterPoint,\n  MountedBScrollHTMLElement,\n  Behavior,\n  Boundary,\n  CustomAPI\n} from '@better-scroll/core'\n\nexport {\n  MouseWheel,\n  ObserveDom,\n  PullDownRefresh,\n  PullUpLoad,\n  ScrollBar,\n  Slide,\n  Wheel,\n  Zoom,\n  NestedScroll,\n  InfinityScroll,\n  Movable,\n  ObserveImage,\n  Indicators\n}\n\nBScroll.use(MouseWheel)\n  .use(ObserveDom)\n  .use(PullDownRefresh)\n  .use(PullUpLoad)\n  .use(ScrollBar)\n  .use(Slide)\n  .use(Wheel)\n  .use(Zoom)\n  .use(NestedScroll)\n  .use(InfinityScroll)\n  .use(Movable)\n  .use(ObserveImage)\n  .use(Indicators)\n\nexport default BScroll\n"
  },
  {
    "path": "packages/core/README.md",
    "content": "# @better-scroll/core\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/core/README_zh-CN.md)\n\ncore scroll from BetterScroll.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\n\nconst bs = new BScroll('.wrapper', {/* ... */})\n```\n"
  },
  {
    "path": "packages/core/README_zh-CN.md",
    "content": "# @better-scroll/core\n\n核心滚动，实现基础的列表滚动效果。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\n\nconst bs = new BScroll('.wrapper', {/* ... */})\n```\n"
  },
  {
    "path": "packages/core/package.json",
    "content": "{\n  \"name\": \"@better-scroll/core\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Minimalistic core scrolling for BetterScroll, it is pure and tiny\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"main\": \"dist/core.js\",\n  \"module\": \"dist/core.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"scripts\": {},\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/core\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/shared-utils\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/core/src/BScroll.ts",
    "content": "import { BScrollInstance, propertiesConfig } from './Instance'\nimport { Options, DefOptions, OptionsConstructor } from './Options'\nimport Scroller from './scroller/Scroller'\nimport {\n  getElement,\n  warn,\n  isUndef,\n  propertiesProxy,\n  ApplyOrder,\n  EventEmitter\n} from '@better-scroll/shared-utils'\nimport { bubbling } from './utils/bubbling'\nimport { UnionToIntersection } from './utils/typesHelper'\n\ninterface PluginCtor {\n  pluginName: string\n  applyOrder?: ApplyOrder\n  new (scroll: BScroll): any\n}\n\ninterface PluginItem {\n  name: string\n  applyOrder?: ApplyOrder.Pre | ApplyOrder.Post\n  ctor: PluginCtor\n}\ninterface PluginsMap {\n  [key: string]: boolean\n}\ninterface PropertyConfig {\n  key: string\n  sourceKey: string\n}\n\ntype ElementParam = HTMLElement | string\n\nexport interface MountedBScrollHTMLElement extends HTMLElement {\n  isBScrollContainer?: boolean\n}\n\nexport class BScrollConstructor<O = {}> extends EventEmitter {\n  static plugins: PluginItem[] = []\n  static pluginsMap: PluginsMap = {}\n  scroller: Scroller\n  options: OptionsConstructor\n  hooks: EventEmitter\n  plugins: { [name: string]: any }\n  wrapper: HTMLElement\n  content: HTMLElement;\n  [key: string]: any\n\n  static use(ctor: PluginCtor) {\n    const name = ctor.pluginName\n    const installed = BScrollConstructor.plugins.some(\n      plugin => ctor === plugin.ctor\n    )\n    if (installed) return BScrollConstructor\n    if (isUndef(name)) {\n      warn(\n        `Plugin Class must specify plugin's name in static property by 'pluginName' field.`\n      )\n      return BScrollConstructor\n    }\n    BScrollConstructor.pluginsMap[name] = true\n    BScrollConstructor.plugins.push({\n      name,\n      applyOrder: ctor.applyOrder,\n      ctor\n    })\n    return BScrollConstructor\n  }\n\n  constructor(el: ElementParam, options?: Options & O) {\n    super([\n      'refresh',\n      'contentChanged',\n      'enable',\n      'disable',\n      'beforeScrollStart',\n      'scrollStart',\n      'scroll',\n      'scrollEnd',\n      'scrollCancel',\n      'touchEnd',\n      'flick',\n      'destroy'\n    ])\n\n    const wrapper = getElement(el)\n\n    if (!wrapper) {\n      warn('Can not resolve the wrapper DOM.')\n      return\n    }\n\n    this.plugins = {}\n    this.options = new OptionsConstructor().merge(options).process()\n\n    if (!this.setContent(wrapper).valid) {\n      return\n    }\n\n    this.hooks = new EventEmitter([\n      'refresh',\n      'enable',\n      'disable',\n      'destroy',\n      'beforeInitialScrollTo',\n      'contentChanged'\n    ])\n    this.init(wrapper)\n  }\n\n  setContent(wrapper: MountedBScrollHTMLElement) {\n    let contentChanged = false\n    let valid = true\n    const content = wrapper.children[\n      this.options.specifiedIndexAsContent\n    ] as HTMLElement\n    if (!content) {\n      warn(\n        'The wrapper need at least one child element to be content element to scroll.'\n      )\n      valid = false\n    } else {\n      contentChanged = this.content !== content\n      if (contentChanged) {\n        this.content = content\n      }\n    }\n    return {\n      valid,\n      contentChanged\n    }\n  }\n\n  private init(wrapper: MountedBScrollHTMLElement) {\n    this.wrapper = wrapper\n\n    // mark wrapper to recognize bs instance by DOM attribute\n    wrapper.isBScrollContainer = true\n    this.scroller = new Scroller(wrapper, this.content, this.options)\n    this.scroller.hooks.on(this.scroller.hooks.eventTypes.resize, () => {\n      this.refresh()\n    })\n\n    this.eventBubbling()\n    this.handleAutoBlur()\n    this.enable()\n\n    this.proxy(propertiesConfig)\n    this.applyPlugins()\n\n    // maybe boundary has changed, should refresh\n    this.refreshWithoutReset(this.content)\n    const { startX, startY } = this.options\n    const position = {\n      x: startX,\n      y: startY\n    }\n    // maybe plugins want to control scroll position\n    if (\n      this.hooks.trigger(this.hooks.eventTypes.beforeInitialScrollTo, position)\n    ) {\n      return\n    }\n    this.scroller.scrollTo(position.x, position.y)\n  }\n\n  private applyPlugins() {\n    const options = this.options\n    BScrollConstructor.plugins\n      .sort((a, b) => {\n        const applyOrderMap = {\n          [ApplyOrder.Pre]: -1,\n          [ApplyOrder.Post]: 1\n        }\n        const aOrder = a.applyOrder ? applyOrderMap[a.applyOrder] : 0\n        const bOrder = b.applyOrder ? applyOrderMap[b.applyOrder] : 0\n        return aOrder - bOrder\n      })\n      .forEach((item: PluginItem) => {\n        const ctor = item.ctor\n        if (options[item.name] && typeof ctor === 'function') {\n          this.plugins[item.name] = new ctor(this)\n        }\n      })\n  }\n\n  private handleAutoBlur() {\n    /* istanbul ignore if  */\n    if (this.options.autoBlur) {\n      this.on(this.eventTypes.beforeScrollStart, () => {\n        let activeElement = document.activeElement as HTMLElement\n        if (\n          activeElement &&\n          (activeElement.tagName === 'INPUT' ||\n            activeElement.tagName === 'TEXTAREA')\n        ) {\n          activeElement.blur()\n        }\n      })\n    }\n  }\n\n  private eventBubbling() {\n    bubbling(this.scroller.hooks, this, [\n      this.eventTypes.beforeScrollStart,\n      this.eventTypes.scrollStart,\n      this.eventTypes.scroll,\n      this.eventTypes.scrollEnd,\n      this.eventTypes.scrollCancel,\n      this.eventTypes.touchEnd,\n      this.eventTypes.flick\n    ])\n  }\n\n  private refreshWithoutReset(content: HTMLElement) {\n    this.scroller.refresh(content)\n    this.hooks.trigger(this.hooks.eventTypes.refresh, content)\n    this.trigger(this.eventTypes.refresh, content)\n  }\n\n  proxy(propertiesConfig: PropertyConfig[]) {\n    propertiesConfig.forEach(({ key, sourceKey }) => {\n      propertiesProxy(this, sourceKey, key)\n    })\n  }\n  refresh() {\n    const { contentChanged, valid } = this.setContent(this.wrapper)\n    if (valid) {\n      const content = this.content\n      this.refreshWithoutReset(content)\n      if (contentChanged) {\n        this.hooks.trigger(this.hooks.eventTypes.contentChanged, content)\n        this.trigger(this.eventTypes.contentChanged, content)\n      }\n      this.scroller.resetPosition()\n    }\n  }\n\n  enable() {\n    this.scroller.enable()\n    this.hooks.trigger(this.hooks.eventTypes.enable)\n    this.trigger(this.eventTypes.enable)\n  }\n\n  disable() {\n    this.scroller.disable()\n    this.hooks.trigger(this.hooks.eventTypes.disable)\n    this.trigger(this.eventTypes.disable)\n  }\n\n  destroy() {\n    this.hooks.trigger(this.hooks.eventTypes.destroy)\n    this.trigger(this.eventTypes.destroy)\n    this.scroller.destroy()\n  }\n  eventRegister(names: string[]) {\n    this.registerType(names)\n  }\n}\n\nexport interface BScrollConstructor extends BScrollInstance {}\n\nexport interface CustomAPI {\n  [key: string]: {}\n}\n\ntype ExtractAPI<O> = {\n  [K in keyof O]: K extends string\n    ? DefOptions[K] extends undefined\n      ? CustomAPI[K]\n      : never\n    : never\n}[keyof O]\n\nexport function createBScroll<O = {}>(\n  el: ElementParam,\n  options?: Options & O\n): BScrollConstructor & UnionToIntersection<ExtractAPI<O>> {\n  const bs = new BScrollConstructor(el, options)\n  return (bs as unknown) as BScrollConstructor &\n    UnionToIntersection<ExtractAPI<O>>\n}\n\ncreateBScroll.use = BScrollConstructor.use\ncreateBScroll.plugins = BScrollConstructor.plugins\ncreateBScroll.pluginsMap = BScrollConstructor.pluginsMap\n\ntype createBScroll = typeof createBScroll\nexport interface BScrollFactory extends createBScroll {\n  new <O = {}>(el: ElementParam, options?: Options & O): BScrollConstructor &\n    UnionToIntersection<ExtractAPI<O>>\n}\n\nexport type BScroll<O = Options> = BScrollConstructor<O> &\n  UnionToIntersection<ExtractAPI<O>>\n\nexport const BScroll = (createBScroll as unknown) as BScrollFactory\n"
  },
  {
    "path": "packages/core/src/Instance.ts",
    "content": "import { Behavior } from './scroller/Behavior'\nimport Actions from './scroller/Actions'\nimport { ExposedAPI as ExposedAPIByScroller } from './scroller/Scroller'\nimport { Animater } from './animater'\nimport { ExposedAPI as ExposedAPIByAnimater } from './animater/Base'\n\nexport interface BScrollInstance\n  extends ExposedAPIByScroller,\n    ExposedAPIByAnimater {\n  [key: string]: any\n  x: Behavior['currentPos']\n  y: Behavior['currentPos']\n  hasHorizontalScroll: Behavior['hasScroll']\n  hasVerticalScroll: Behavior['hasScroll']\n  scrollerWidth: Behavior['contentSize']\n  scrollerHeight: Behavior['contentSize']\n  maxScrollX: Behavior['maxScrollPos']\n  maxScrollY: Behavior['maxScrollPos']\n  minScrollX: Behavior['minScrollPos']\n  minScrollY: Behavior['minScrollPos']\n  movingDirectionX: Behavior['movingDirection']\n  movingDirectionY: Behavior['movingDirection']\n  directionX: Behavior['direction']\n  directionY: Behavior['direction']\n  enabled: Actions['enabled']\n  pending: Animater['pending']\n}\n\nexport const propertiesConfig = [\n  {\n    sourceKey: 'scroller.scrollBehaviorX.currentPos',\n    key: 'x'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.currentPos',\n    key: 'y'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.hasScroll',\n    key: 'hasHorizontalScroll'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.hasScroll',\n    key: 'hasVerticalScroll'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.contentSize',\n    key: 'scrollerWidth'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.contentSize',\n    key: 'scrollerHeight'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.maxScrollPos',\n    key: 'maxScrollX'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.maxScrollPos',\n    key: 'maxScrollY'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.minScrollPos',\n    key: 'minScrollX'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.minScrollPos',\n    key: 'minScrollY'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.movingDirection',\n    key: 'movingDirectionX'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.movingDirection',\n    key: 'movingDirectionY'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorX.direction',\n    key: 'directionX'\n  },\n  {\n    sourceKey: 'scroller.scrollBehaviorY.direction',\n    key: 'directionY'\n  },\n  {\n    sourceKey: 'scroller.actions.enabled',\n    key: 'enabled'\n  },\n  {\n    sourceKey: 'scroller.animater.pending',\n    key: 'pending'\n  },\n  {\n    sourceKey: 'scroller.animater.stop',\n    key: 'stop'\n  },\n  {\n    sourceKey: 'scroller.scrollTo',\n    key: 'scrollTo'\n  },\n  {\n    sourceKey: 'scroller.scrollBy',\n    key: 'scrollBy'\n  },\n  {\n    sourceKey: 'scroller.scrollToElement',\n    key: 'scrollToElement'\n  },\n  {\n    sourceKey: 'scroller.resetPosition',\n    key: 'resetPosition'\n  }\n]\n"
  },
  {
    "path": "packages/core/src/Options.ts",
    "content": "import {\n  hasTransition,\n  hasPerspective,\n  hasTouch,\n  Probe,\n  EventPassthrough,\n  extend,\n  Quadrant,\n} from '@better-scroll/shared-utils'\n\n// type\nexport type Tap = 'tap' | ''\nexport type BounceOptions = Partial<BounceConfig> | boolean\nexport type DblclickOptions = Partial<DblclickConfig> | boolean\n\n// interface\nexport interface BounceConfig {\n  top: boolean\n  bottom: boolean\n  left: boolean\n  right: boolean\n}\n\nexport interface DblclickConfig {\n  delay: number\n}\n\nexport interface CustomOptions {}\n\nexport interface DefOptions {\n  [key: string]: any\n  startX?: number\n  startY?: number\n  scrollX?: boolean\n  scrollY?: boolean\n  freeScroll?: boolean\n  directionLockThreshold?: number\n  eventPassthrough?: string\n  click?: boolean\n  tap?: Tap\n  bounce?: BounceOptions\n  bounceTime?: number\n  momentum?: boolean\n  momentumLimitTime?: number\n  momentumLimitDistance?: number\n  swipeTime?: number\n  swipeBounceTime?: number\n  deceleration?: number\n  flickLimitTime?: number\n  flickLimitDistance?: number\n  resizePolling?: number\n  probeType?: number\n  stopPropagation?: boolean\n  preventDefault?: boolean\n  preventDefaultException?: {\n    tagName?: RegExp\n    className?: RegExp\n  }\n  tagException?: {\n    tagName?: RegExp\n    className?: RegExp\n  }\n  HWCompositing?: boolean\n  useTransition?: boolean\n  bindToWrapper?: boolean\n  bindToTarget?: boolean\n  disableMouse?: boolean\n  disableTouch?: boolean\n  autoBlur?: boolean\n  translateZ?: string\n  dblclick?: DblclickOptions\n  autoEndDistance?: number\n  outOfBoundaryDampingFactor?: number\n  specifiedIndexAsContent?: number\n  quadrant?: Quadrant\n}\n\nexport interface Options extends DefOptions, CustomOptions {}\nexport class CustomOptions {}\nexport class OptionsConstructor extends CustomOptions implements DefOptions {\n  [key: string]: any\n  startX: number\n  startY: number\n  scrollX: boolean\n  scrollY: boolean\n  freeScroll: boolean\n  directionLockThreshold: number\n  eventPassthrough: string\n  click: boolean\n  tap: Tap\n  bounce: BounceConfig\n  bounceTime: number\n  momentum: boolean\n  momentumLimitTime: number\n  momentumLimitDistance: number\n  swipeTime: number\n  swipeBounceTime: number\n  deceleration: number\n  flickLimitTime: number\n  flickLimitDistance: number\n  resizePolling: number\n  probeType: number\n  stopPropagation: boolean\n  preventDefault: boolean\n  preventDefaultException: {\n    tagName?: RegExp\n    className?: RegExp\n  }\n  tagException: {\n    tagName?: RegExp\n    className?: RegExp\n  }\n  HWCompositing: boolean\n  useTransition: boolean\n  bindToWrapper: boolean\n  bindToTarget: boolean\n  disableMouse: boolean\n  disableTouch: boolean\n  autoBlur: boolean\n  translateZ: string\n  dblclick: DblclickOptions\n  autoEndDistance: number\n  outOfBoundaryDampingFactor: number\n  specifiedIndexAsContent: number\n  quadrant: Quadrant\n\n  constructor() {\n    super()\n    this.startX = 0\n    this.startY = 0\n    this.scrollX = false\n    this.scrollY = true\n    this.freeScroll = false\n    this.directionLockThreshold = 0\n    this.eventPassthrough = EventPassthrough.None\n    this.click = false\n    this.dblclick = false\n    this.tap = ''\n\n    this.bounce = {\n      top: true,\n      bottom: true,\n      left: true,\n      right: true,\n    }\n    this.bounceTime = 800\n\n    this.momentum = true\n    this.momentumLimitTime = 300\n    this.momentumLimitDistance = 15\n\n    this.swipeTime = 2500\n    this.swipeBounceTime = 500\n\n    this.deceleration = 0.0015\n\n    this.flickLimitTime = 200\n    this.flickLimitDistance = 100\n\n    this.resizePolling = 60\n    this.probeType = Probe.Default\n\n    this.stopPropagation = false\n    this.preventDefault = true\n    this.preventDefaultException = {\n      tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n    }\n    this.tagException = {\n      tagName: /^TEXTAREA$/,\n    }\n\n    this.HWCompositing = true\n    this.useTransition = true\n\n    this.bindToWrapper = false\n    this.bindToTarget = false\n    this.disableMouse = hasTouch\n    this.disableTouch = !hasTouch\n    this.autoBlur = true\n\n    this.autoEndDistance = 5\n    this.outOfBoundaryDampingFactor = 1 / 3\n    this.specifiedIndexAsContent = 0\n    this.quadrant = Quadrant.First\n  }\n  merge(options?: Options) {\n    if (!options) return this\n    for (let key in options) {\n      if (key === 'bounce') {\n        this.bounce = this.resolveBounce(options[key]!)\n        continue\n      }\n      this[key] = options[key]\n    }\n    return this\n  }\n  process() {\n    this.translateZ =\n      this.HWCompositing && hasPerspective ? ' translateZ(1px)' : ''\n\n    this.useTransition = this.useTransition && hasTransition\n\n    this.preventDefault = !this.eventPassthrough && this.preventDefault\n\n    // If you want eventPassthrough I have to lock one of the axes\n    this.scrollX =\n      this.eventPassthrough === EventPassthrough.Horizontal\n        ? false\n        : this.scrollX\n    this.scrollY =\n      this.eventPassthrough === EventPassthrough.Vertical ? false : this.scrollY\n\n    // With eventPassthrough we also need lockDirection mechanism\n    this.freeScroll = this.freeScroll && !this.eventPassthrough\n\n    // force true when freeScroll is true\n    this.scrollX = this.freeScroll ? true : this.scrollX\n    this.scrollY = this.freeScroll ? true : this.scrollY\n\n    this.directionLockThreshold = this.eventPassthrough\n      ? 0\n      : this.directionLockThreshold\n\n    return this\n  }\n\n  resolveBounce(bounceOptions: BounceOptions): BounceConfig {\n    const DEFAULT_BOUNCE = {\n      top: true,\n      right: true,\n      bottom: true,\n      left: true,\n    }\n    const NEGATED_BOUNCE = {\n      top: false,\n      right: false,\n      bottom: false,\n      left: false,\n    }\n\n    let ret: BounceConfig\n    if (typeof bounceOptions === 'object') {\n      ret = extend(DEFAULT_BOUNCE, bounceOptions)\n    } else {\n      ret = bounceOptions ? DEFAULT_BOUNCE : NEGATED_BOUNCE\n    }\n\n    return ret\n  }\n}\n"
  },
  {
    "path": "packages/core/src/__mocks__/Options.ts",
    "content": "const mockOptions = jest.fn().mockImplementation(() => {\n  return {\n    startX: 0,\n    startY: 0,\n    scrollX: false,\n    scrollY: true,\n    freeScroll: false,\n    directionLockThreshold: 0,\n    eventPassthrough: '',\n    click: false,\n    tap: '',\n    translateZ: ' translateZ(0)',\n\n    bounce: {\n      top: true,\n      bottom: true,\n      left: true,\n      right: true,\n    },\n    bounceTime: 800,\n\n    momentum: true,\n    momentumLimitTime: 300,\n    momentumLimitDistance: 15,\n\n    swipeTime: 2500,\n    swipeBounceTime: 500,\n\n    deceleration: 0.0015,\n\n    flickLimitTime: 200,\n    flickLimitDistance: 100,\n\n    resizePolling: 60,\n    probeType: 0,\n\n    stopPropagation: false,\n    preventDefault: true,\n    preventDefaultException: {\n      tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n    },\n\n    HWCompositing: true,\n\n    useTransition: true,\n    bindToWrapper: false,\n    disableMouse: true,\n    observeDOM: true,\n    autoBlur: true,\n    mouseWheel: false,\n    infinity: false,\n    specifiedIndexAsContent: 0,\n    quadrant: 0,\n    outOfBoundaryDampingFactor: 1 / 3,\n    merge: jest.fn(),\n    process: jest.fn(),\n  }\n})\n\nexport { mockOptions as OptionsConstructor }\n"
  },
  {
    "path": "packages/core/src/__mocks__/index.ts",
    "content": "import Scroller from '../scroller/Scroller'\nimport { OptionsConstructor } from '../Options'\nimport { EventEmitter } from '@better-scroll/shared-utils'\n\njest.mock('../scroller/Scroller')\njest.mock('../Options')\n\nconst BScroll = jest.fn().mockImplementation((wrapper, options) => {\n  options = Object.assign(new OptionsConstructor(), options)\n  const eventEmitter = new EventEmitter([\n    // bscroll\n    'refresh',\n    'enable',\n    'disable',\n    'destroy',\n    // scroller\n    'beforeScrollStart',\n    'scrollStart',\n    'scroll',\n    'scrollEnd',\n    'touchEnd',\n    'flick',\n    'alterOptions',\n    'mousewheelStart',\n    'mousewheelMove',\n    'mousewheelEnd',\n  ])\n  const res = {\n    wrapper: wrapper,\n    options: options,\n    hooks: new EventEmitter([\n      'refresh',\n      'enable',\n      'disable',\n      'destroy',\n      'beforeInitialScrollTo',\n    ]),\n    scroller: new Scroller(wrapper, wrapper.children[0], options),\n    // own methods\n    proxy: jest.fn(),\n    refresh: jest.fn(),\n    // proxy methods\n    scrollTo: jest.fn(),\n    resetPosition: jest.fn(),\n    registerType: jest.fn().mockImplementation((names: string[]) => {\n      names.forEach((name) => {\n        const eventTypes = eventEmitter.eventTypes\n        eventTypes[name] = name\n      })\n    }),\n    disable: jest.fn(),\n    enable: jest.fn(),\n    stop: jest.fn(),\n    plugins: {},\n    x: 0,\n    y: 0,\n    maxScrollY: 0,\n    maxScrollX: 0,\n    minScrollX: 0,\n    minScrollY: 0,\n    hasVerticalScroll: true,\n    hasHorizontalScroll: false,\n    enabled: true,\n    pending: false,\n  }\n\n  Object.setPrototypeOf(res, eventEmitter)\n  return res\n})\n\nexport default BScroll\n"
  },
  {
    "path": "packages/core/src/__tests__/Options.spec.ts",
    "content": "import { OptionsConstructor } from '../Options'\n\ndescribe('BetterScroll Options', () => {\n  let options: OptionsConstructor\n\n  beforeEach(() => {\n    options = new OptionsConstructor()\n  })\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should have default value', () => {\n    expect(options).toEqual({\n      HWCompositing: true,\n      autoBlur: true,\n      bindToWrapper: false,\n      bounce: {\n        bottom: true,\n        left: true,\n        right: true,\n        top: true,\n      },\n      bounceTime: 800,\n      click: false,\n      dblclick: false,\n      deceleration: 0.0015,\n      directionLockThreshold: 0,\n      disableMouse: false,\n      disableTouch: true,\n      eventPassthrough: '',\n      flickLimitDistance: 100,\n      flickLimitTime: 200,\n      freeScroll: false,\n      momentum: true,\n      momentumLimitDistance: 15,\n      momentumLimitTime: 300,\n      preventDefault: true,\n      preventDefaultException: {\n        tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n      },\n      tagException: {\n        tagName: /^TEXTAREA$/,\n      },\n      probeType: 0,\n      resizePolling: 60,\n      scrollX: false,\n      scrollY: true,\n      startX: 0,\n      startY: 0,\n      stopPropagation: false,\n      swipeBounceTime: 500,\n      swipeTime: 2500,\n      tap: '',\n      useTransition: true,\n      autoEndDistance: 5,\n      bindToTarget: false,\n      outOfBoundaryDampingFactor: 1 / 3,\n      specifiedIndexAsContent: 0,\n      quadrant: 1,\n    })\n  })\n\n  it('should shallow copy options when call merge(options)', () => {\n    options.merge({\n      scrollY: false,\n      scrollX: true,\n      bounce: false,\n    })\n\n    expect(options).toEqual({\n      HWCompositing: true,\n      autoBlur: true,\n      bindToWrapper: false,\n      bounce: {\n        top: false,\n        right: false,\n        bottom: false,\n        left: false,\n      },\n      bounceTime: 800,\n      click: false,\n      dblclick: false,\n      deceleration: 0.0015,\n      directionLockThreshold: 0,\n      disableMouse: false,\n      disableTouch: true,\n      eventPassthrough: '',\n      flickLimitDistance: 100,\n      flickLimitTime: 200,\n      freeScroll: false,\n      momentum: true,\n      momentumLimitDistance: 15,\n      momentumLimitTime: 300,\n      preventDefault: true,\n      preventDefaultException: {\n        tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n      },\n      tagException: {\n        tagName: /^TEXTAREA$/,\n      },\n      probeType: 0,\n      resizePolling: 60,\n      scrollX: true,\n      scrollY: false,\n      startX: 0,\n      startY: 0,\n      stopPropagation: false,\n      swipeBounceTime: 500,\n      swipeTime: 2500,\n      tap: '',\n      useTransition: true,\n      autoEndDistance: 5,\n      bindToTarget: false,\n      outOfBoundaryDampingFactor: 1 / 3,\n      specifiedIndexAsContent: 0,\n      quadrant: 1,\n    })\n    // an invalid parameter\n    const ret = options.merge()\n    expect(ret).toBe(options)\n  })\n\n  it('should generate some extra properties of options', () => {\n    options.process()\n\n    expect(options).toEqual({\n      HWCompositing: true,\n      autoBlur: true,\n      bindToWrapper: false,\n      bounce: {\n        bottom: true,\n        left: true,\n        right: true,\n        top: true,\n      },\n      bounceTime: 800,\n      click: false,\n      dblclick: false,\n      deceleration: 0.0015,\n      directionLockThreshold: 0,\n      disableMouse: false,\n      disableTouch: true,\n      eventPassthrough: '',\n      flickLimitDistance: 100,\n      flickLimitTime: 200,\n      freeScroll: false,\n      momentum: true,\n      momentumLimitDistance: 15,\n      momentumLimitTime: 300,\n      preventDefault: true,\n      preventDefaultException: {\n        tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n      },\n      tagException: {\n        tagName: /^TEXTAREA$/,\n      },\n      probeType: 0,\n      resizePolling: 60,\n      scrollX: false,\n      scrollY: true,\n      startX: 0,\n      startY: 0,\n      stopPropagation: false,\n      swipeBounceTime: 500,\n      swipeTime: 2500,\n      tap: '',\n      useTransition: true,\n      translateZ: '',\n      autoEndDistance: 5,\n      bindToTarget: false,\n      outOfBoundaryDampingFactor: 1 / 3,\n      specifiedIndexAsContent: 0,\n      quadrant: 1,\n    })\n  })\n\n  it('should resolve bounce when calling process', () => {\n    options.merge({\n      bounce: false,\n    })\n\n    expect(options.bounce).toEqual({\n      bottom: false,\n      left: false,\n      right: false,\n      top: false,\n    })\n\n    options.merge({\n      bounce: true,\n    })\n\n    expect(options.bounce).toEqual({\n      bottom: true,\n      left: true,\n      right: true,\n      top: true,\n    })\n\n    options.merge({\n      bounce: {\n        top: false,\n        bottom: false,\n      },\n    })\n\n    expect(options.bounce).toEqual({\n      bottom: false,\n      left: true,\n      right: true,\n      top: false,\n    })\n  })\n})\n"
  },
  {
    "path": "packages/core/src/__tests__/__utils__/event.ts",
    "content": "export function createEvent(type: string, name: string): Event {\n  const e = document.createEvent(type || 'Event')\n  e.initEvent(name, true, true)\n  return e\n}\n\ninterface CustomClickEvent extends MouseEvent {\n  pageX: number\n  pageY: number\n}\n\nexport function dispatchClick(target: EventTarget, name = 'click') {\n  const event = <CustomClickEvent>createEvent('', name)\n  event.pageX = 0\n  event.pageY = 0\n  target.dispatchEvent(event)\n}\n\ninterface CustomTouch {\n  pageX: number\n  pageY: number\n}\ntype CustomTouches = CustomTouch[] | CustomTouch\n\nexport interface CustomTouchEvent extends Event {\n  touches: CustomTouches\n  targetTouches: CustomTouches\n  changedTouches: CustomTouches\n}\n\ninterface CustomMouseEvent extends Event {\n  button: 0 | 1\n  pageX: number\n  pageY: number\n}\n\nexport function dispatchTouch(\n  target: EventTarget,\n  name = 'touchstart',\n  touches: CustomTouches\n): void {\n  const event = <CustomTouchEvent>createEvent('', name)\n  event.touches = event.targetTouches = event.changedTouches = touches\n  target.dispatchEvent(event)\n}\n\nexport function dispatchMouse(\n  target: EventTarget,\n  name = 'mousedown',\n  useLeftButton = true\n): void {\n  const event = <CustomMouseEvent>createEvent('', name)\n  event.button = useLeftButton ? 0 : 1\n  event.pageX = 0\n  event.pageY = 0\n  target.dispatchEvent(event)\n}\n\nexport function dispatchTouchStart(\n  target: EventTarget,\n  touches: CustomTouches\n): void {\n  dispatchTouch(target, 'touchstart', touches)\n}\n\nexport function dispatchTouchMove(\n  target: EventTarget,\n  touches: CustomTouches\n): void {\n  dispatchTouch(target, 'touchmove', touches)\n}\n\nexport function dispatchTouchEnd(\n  target: EventTarget,\n  touches: CustomTouches\n): void {\n  dispatchTouch(target, 'touchend', touches)\n}\n\nexport function dispatchTouchCancel(\n  target: EventTarget,\n  touches: CustomTouches\n): void {\n  dispatchTouch(target, 'touchcancel', touches)\n}\n\nexport function dispatchSwipe(\n  target: EventTarget,\n  touches: CustomTouches,\n  duration: number,\n  cb: () => any\n): void {\n  // TODO 优化写法\n  if (!Array.isArray(touches)) {\n    touches = [touches]\n  }\n  if (touches instanceof Array) {\n    dispatchTouchStart(target, touches[0])\n    const moveAndEnd = () => {\n      if (touches instanceof Array) {\n        dispatchTouchMove(target, touches[1] || touches[0])\n        dispatchTouchEnd(target, touches[2] || touches[1] || touches[0])\n      }\n      cb && cb()\n    }\n    if (duration) {\n      setTimeout(moveAndEnd, duration)\n    } else {\n      moveAndEnd()\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/__tests__/__utils__/layout.ts",
    "content": "export interface CustomHTMLDivElement extends HTMLDivElement {\n  clientWidth: number\n  clientHeight: number\n  offsetWidth: number\n  offsetHeight: number\n  offsetTop: number\n  offsetLeft: number\n  _jsdomMockClientWidth?: number\n  _jsdomMockClientHeight?: number\n  _jsdomMockOffsetWidth?: number\n  _jsdomMockOffsetHeight?: number\n  _jsdomMockOffsetTop?: number\n  _jsdomMockOffsetLeft?: number\n  [key: string]: any\n}\n\nfunction firstUpper(key: string) {\n  return key.charAt(0).toUpperCase() + key.slice(1)\n}\n\nfunction genMockPrototype(mockName: string) {\n  return {\n    get: jest.fn().mockImplementation(dom => {\n      return Number(dom.getAttribute(mockName))\n    })\n  }\n}\n\nfunction mockHTMLPrototype(propName: string, mockGetter: jest.Mock) {\n  Object.defineProperty(HTMLElement.prototype, propName, {\n    get: function() {\n      return mockGetter(this)\n    },\n    configurable: true\n  })\n}\n\nexport function mockDomOffset(\n  dom: CustomHTMLDivElement,\n  offsetObj: {\n    width?: number\n    height?: number\n    top?: number\n    left?: number\n    [key: string]: any\n  }\n) {\n  Object.keys(offsetObj).forEach(key => {\n    const mockName = `_jsdomMockOffset${firstUpper(key)}`\n    dom.setAttribute(mockName, offsetObj[key])\n  })\n}\n\nexport function mockDomClient(\n  dom: CustomHTMLDivElement,\n  clientObj: {\n    width?: number\n    height?: number\n    [key: string]: any\n  }\n) {\n  Object.keys(clientObj).forEach(key => {\n    const mockName = `_jsdomMockClient${firstUpper(key)}`\n    dom.setAttribute(mockName, clientObj[key])\n  })\n}\n\nexport function createDiv(\n  width: number = 0,\n  height: number = 0,\n  top: number = 0,\n  left: number = 0\n) {\n  const dom = document.createElement('div') as CustomHTMLDivElement\n  mockDomOffset(dom, {\n    width,\n    height,\n    top,\n    left\n  })\n  mockDomClient(dom, {\n    width,\n    height\n  })\n  return dom\n}\n\nexport const mockClientWidth = genMockPrototype('_jsdomMockClientWidth')\nmockHTMLPrototype('clientWidth', mockClientWidth.get)\n\nexport const mockClientHeight = genMockPrototype('_jsdomMockClientHeight')\nmockHTMLPrototype('clientHeight', mockClientHeight.get)\n\nexport const mockOffsetWidth = genMockPrototype('_jsdomMockOffsetWidth')\nmockHTMLPrototype('offsetWidth', mockOffsetWidth.get)\n\nexport const mockOffsetHeight = genMockPrototype('_jsdomMockOffsetHeight')\nmockHTMLPrototype('offsetHeight', mockOffsetHeight.get)\n\nexport const mockOffsetTop = genMockPrototype('_jsdomMockOffsetTop')\nmockHTMLPrototype('offsetTop', mockOffsetTop.get)\n\nexport const mockOffsetLeft = genMockPrototype('_jsdomMockOffsetLeft')\nmockHTMLPrototype('offsetLeft', mockOffsetLeft.get)\n"
  },
  {
    "path": "packages/core/src/__tests__/index.spec.ts",
    "content": "import BScroll from '../index'\n\ndescribe('BetterScroll Core', () => {\n  let bscroll: BScroll\n  let wrapper = document.createElement('div')\n  let content = document.createElement('p')\n  wrapper.appendChild(content)\n  beforeEach(() => {\n    bscroll = new BScroll(wrapper, {})\n  })\n  afterEach(() => {\n    BScroll.plugins = []\n    BScroll.pluginsMap = {}\n  })\n\n  it('use()', () => {\n    const plugin = class MyPlugin {\n      static pluginName = 'myPlugin'\n    }\n    BScroll.use(plugin)\n    // has installed\n    BScroll.use(plugin)\n\n    expect(BScroll.plugins.length).toBe(1)\n\n    // Plugin should specify pluginName\n    const spyFn = jest.spyOn(console, 'error')\n    const unnamedPlugin = class UnnamedPlugin {}\n    BScroll.use(unnamedPlugin as any)\n    expect(spyFn).toBeCalled()\n    spyFn.mockRestore()\n  })\n\n  it('should init plugins when set top-level of BScroll options', () => {\n    let mockFn = jest.fn()\n    const plugin = class MyPlugin {\n      static pluginName = 'myPlugin2'\n      constructor(bscroll: BScroll) {\n        mockFn(bscroll)\n      }\n    }\n    BScroll.use(plugin)\n    let wrapper = document.createElement('div')\n    wrapper.appendChild(document.createElement('p'))\n\n    let bs = new BScroll(wrapper, {\n      myPlugin2: true\n    })\n    expect(mockFn).toBeCalledWith(bs)\n  })\n\n  it('should throw error when wrapper is not a ElementNode or wrapper has no children ', () => {\n    let spy = jest.spyOn(console, 'error')\n    let bs = new BScroll('.div', {})\n    let bs2 = new BScroll(document.createElement('div'), {})\n\n    expect(spy).toHaveBeenCalled()\n    expect(spy).toBeCalledTimes(2)\n  })\n\n  it('disable()', () => {\n    const mockFn = jest.fn()\n    bscroll.on(bscroll.eventTypes.disable, mockFn)\n    bscroll.hooks.on(bscroll.hooks.eventTypes.disable, mockFn)\n    bscroll.disable()\n\n    expect(mockFn).toBeCalledTimes(2)\n  })\n\n  it('destroy()', () => {\n    const mockFn = jest.fn()\n    bscroll.on(bscroll.eventTypes.destroy, mockFn)\n    bscroll.hooks.on(bscroll.hooks.eventTypes.destroy, mockFn)\n    bscroll.destroy()\n\n    expect(mockFn).toBeCalledTimes(2)\n  })\n\n  it('eventRegister()', () => {\n    bscroll.eventRegister(['dummy'])\n    expect(bscroll.eventTypes.dummy).toBeTruthy()\n  })\n\n  it('should refresh when window resized', () => {\n    const mockFn = jest.fn()\n    bscroll.on(bscroll.eventTypes.refresh, mockFn)\n    bscroll.scroller.hooks.trigger(bscroll.scroller.hooks.eventTypes.resize)\n    expect(mockFn).toBeCalledTimes(1)\n  })\n\n  it('plugin wanna control scroll position ', () => {\n    const mockFn = jest.fn().mockImplementation(() => true)\n    class DummyPlugin {\n      static pluginName = 'dummy'\n      constructor(scroll: BScroll) {\n        scroll.hooks.on(scroll.hooks.eventTypes.beforeInitialScrollTo, mockFn)\n      }\n    }\n    BScroll.use(DummyPlugin)\n    bscroll = new BScroll(wrapper, { dummy: true })\n    expect(mockFn).toBeCalled()\n  })\n\n  it('should trigger contentChanged hook when content DOM has changed', () => {\n    const mockFn = jest.fn()\n    bscroll.on(bscroll.eventTypes.contentChanged, mockFn)\n\n    // content DOM has\n    wrapper.removeChild(content)\n    wrapper.appendChild(document.createElement('div'))\n    bscroll.refresh()\n    expect(mockFn).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/core/src/animater/Animation.ts",
    "content": "import Base from './Base'\nimport { TranslaterPoint } from '../translater'\nimport {\n  getNow,\n  requestAnimationFrame,\n  cancelAnimationFrame,\n  EaseFn,\n  Probe,\n} from '@better-scroll/shared-utils'\n\nexport default class Animation extends Base {\n  move(\n    startPoint: TranslaterPoint,\n    endPoint: TranslaterPoint,\n    time: number,\n    easingFn: EaseFn | string\n  ) {\n    // time is 0\n    if (!time) {\n      this.translate(endPoint)\n      if (this.options.probeType === Probe.Realtime) {\n        this.hooks.trigger(this.hooks.eventTypes.move, endPoint)\n      }\n      this.hooks.trigger(this.hooks.eventTypes.end, endPoint)\n      return\n    }\n    this.animate(startPoint, endPoint, time, easingFn as EaseFn)\n  }\n\n  private animate(\n    startPoint: TranslaterPoint,\n    endPoint: TranslaterPoint,\n    duration: number,\n    easingFn: EaseFn\n  ) {\n    let startTime = getNow()\n    const destTime = startTime + duration\n    const isRealtimeProbeType = this.options.probeType === Probe.Realtime\n    const step = () => {\n      let now = getNow()\n      // js animation end\n      if (now >= destTime) {\n        this.translate(endPoint)\n        if (isRealtimeProbeType) {\n          this.hooks.trigger(this.hooks.eventTypes.move, endPoint)\n        }\n        this.hooks.trigger(this.hooks.eventTypes.end, endPoint)\n        return\n      }\n\n      now = (now - startTime) / duration\n      let easing = easingFn(now)\n      const newPoint = {} as TranslaterPoint\n      Object.keys(endPoint).forEach((key) => {\n        const startValue = startPoint[key]\n        const endValue = endPoint[key]\n        newPoint[key] = (endValue - startValue) * easing + startValue\n      })\n      this.translate(newPoint)\n\n      if (isRealtimeProbeType) {\n        this.hooks.trigger(this.hooks.eventTypes.move, newPoint)\n      }\n\n      if (this.pending) {\n        this.timer = requestAnimationFrame(step)\n      }\n\n      // call bs.stop() should not dispatch end hook again.\n      // forceStop hook will do this.\n      /* istanbul ignore if  */\n      if (!this.pending) {\n        if (this.callStopWhenPending) {\n          this.callStopWhenPending = false\n        } else {\n          // raf ends should dispatch end hook.\n          this.hooks.trigger(this.hooks.eventTypes.end, endPoint)\n        }\n      }\n    }\n\n    this.setPending(true)\n    // when manually call bs.stop(), then bs.scrollTo()\n    // we should reset callStopWhenPending to dispatch end hook\n    if (this.callStopWhenPending) {\n      this.setCallStop(false)\n    }\n    cancelAnimationFrame(this.timer)\n    step()\n  }\n\n  doStop(): boolean {\n    const pending = this.pending\n    this.setForceStopped(false)\n    this.setCallStop(false)\n    // still in requestFrameAnimation\n    if (pending) {\n      this.setPending(false)\n      cancelAnimationFrame(this.timer)\n      const pos = this.translater.getComputedPosition()\n      this.setForceStopped(true)\n      this.setCallStop(true)\n\n      this.hooks.trigger(this.hooks.eventTypes.forceStop, pos)\n    }\n    return pending\n  }\n\n  stop() {\n    const stopFromAnimation = this.doStop()\n    if (stopFromAnimation) {\n      this.hooks.trigger(this.hooks.eventTypes.callStop)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/animater/Base.ts",
    "content": "import {\n  EaseFn,\n  safeCSSStyleDeclaration,\n  cancelAnimationFrame,\n  EventEmitter,\n  Probe,\n} from '@better-scroll/shared-utils'\nimport Translater, { TranslaterPoint } from '../translater'\n\nexport interface ExposedAPI {\n  stop(): void\n}\n\nexport default abstract class Base implements ExposedAPI {\n  content: HTMLElement\n  style: safeCSSStyleDeclaration\n  hooks: EventEmitter\n  timer: number = 0\n  pending: boolean\n  callStopWhenPending: boolean\n  forceStopped: boolean\n  _reflow: number;\n  [key: string]: any\n\n  constructor(\n    content: HTMLElement,\n    public translater: Translater,\n    public options: {\n      probeType: number\n    }\n  ) {\n    this.hooks = new EventEmitter([\n      'move',\n      'end',\n      'beforeForceStop',\n      'forceStop',\n      'callStop',\n      'time',\n      'timeFunction',\n    ])\n    this.setContent(content)\n  }\n\n  translate(endPoint: TranslaterPoint) {\n    this.translater.translate(endPoint)\n  }\n\n  setPending(pending: boolean) {\n    this.pending = pending\n  }\n\n  setForceStopped(forceStopped: boolean) {\n    this.forceStopped = forceStopped\n  }\n\n  setCallStop(called: boolean) {\n    this.callStopWhenPending = called\n  }\n\n  setContent(content: HTMLElement) {\n    if (this.content !== content) {\n      this.content = content\n      this.style = content.style as safeCSSStyleDeclaration\n      this.stop()\n    }\n  }\n  clearTimer() {\n    if (this.timer) {\n      cancelAnimationFrame(this.timer)\n      this.timer = 0\n    }\n  }\n\n  abstract move(\n    startPoint: TranslaterPoint,\n    endPoint: TranslaterPoint,\n    time: number,\n    easing: string | EaseFn\n  ): void\n\n  abstract doStop(): void\n  abstract stop(): void\n\n  destroy() {\n    this.hooks.destroy()\n\n    cancelAnimationFrame(this.timer)\n  }\n}\n"
  },
  {
    "path": "packages/core/src/animater/Transition.ts",
    "content": "import {\n  style,\n  requestAnimationFrame,\n  cancelAnimationFrame,\n  EaseFn,\n  Probe,\n} from '@better-scroll/shared-utils'\nimport Base from './Base'\nimport { TranslaterPoint } from '../translater'\nimport { isValidPostion } from '../utils/compat'\n\nexport default class Transition extends Base {\n  startProbe(startPoint: TranslaterPoint, endPoint: TranslaterPoint) {\n    let prePos = startPoint\n    const probe = () => {\n      let pos = this.translater.getComputedPosition()\n\n      if (isValidPostion(startPoint, endPoint, pos, prePos)) {\n        this.hooks.trigger(this.hooks.eventTypes.move, pos)\n      }\n      // call bs.stop() should not dispatch end hook again.\n      // forceStop hook will do this.\n      /* istanbul ignore if  */\n      if (!this.pending) {\n        if (this.callStopWhenPending) {\n          this.callStopWhenPending = false\n        } else {\n          // transition ends should dispatch end hook.\n          this.hooks.trigger(this.hooks.eventTypes.end, pos)\n        }\n      }\n      prePos = pos\n\n      if (this.pending) {\n        this.timer = requestAnimationFrame(probe)\n      }\n    }\n    // when manually call bs.stop(), then bs.scrollTo()\n    // we should reset callStopWhenPending to dispatch end hook\n    if (this.callStopWhenPending) {\n      this.setCallStop(false)\n    }\n\n    cancelAnimationFrame(this.timer)\n    probe()\n  }\n\n  transitionTime(time = 0) {\n    this.style[style.transitionDuration] = time + 'ms'\n    this.hooks.trigger(this.hooks.eventTypes.time, time)\n  }\n\n  transitionTimingFunction(easing: string) {\n    this.style[style.transitionTimingFunction] = easing\n    this.hooks.trigger(this.hooks.eventTypes.timeFunction, easing)\n  }\n\n  transitionProperty() {\n    this.style[style.transitionProperty] = style.transform\n  }\n\n  move(\n    startPoint: TranslaterPoint,\n    endPoint: TranslaterPoint,\n    time: number,\n    easingFn: string | EaseFn\n  ) {\n    this.setPending(time > 0)\n    this.transitionTimingFunction(easingFn as string)\n    this.transitionProperty()\n    this.transitionTime(time)\n    this.translate(endPoint)\n\n    const isRealtimeProbeType = this.options.probeType === Probe.Realtime\n\n    if (time && isRealtimeProbeType) {\n      this.startProbe(startPoint, endPoint)\n    }\n\n    // if we change content's transformY in a tick\n    // such as: 0 -> 50px -> 0\n    // transitionend will not be triggered\n    // so we forceupdate by reflow\n    if (!time) {\n      this._reflow = this.content.offsetHeight\n      if (isRealtimeProbeType) {\n        this.hooks.trigger(this.hooks.eventTypes.move, endPoint)\n      }\n      this.hooks.trigger(this.hooks.eventTypes.end, endPoint)\n    }\n  }\n\n  doStop(): boolean {\n    const pending = this.pending\n    this.setForceStopped(false)\n    this.setCallStop(false)\n    // still in transition\n    if (pending) {\n      this.setPending(false)\n      cancelAnimationFrame(this.timer)\n      const { x, y } = this.translater.getComputedPosition()\n\n      this.transitionTime()\n      this.translate({ x, y })\n      this.setForceStopped(true)\n      this.setCallStop(true)\n      this.hooks.trigger(this.hooks.eventTypes.forceStop, { x, y })\n    }\n    return pending\n  }\n\n  stop() {\n    const stopFromTransition = this.doStop()\n    if (stopFromTransition) {\n      this.hooks.trigger(this.hooks.eventTypes.callStop)\n    }\n  }\n}\n"
  },
  {
    "path": "packages/core/src/animater/__mocks__/Animation.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Animation = jest\n  .fn()\n  .mockImplementation((content, translater, bscrollOptions) => {\n    return {\n      content,\n      translater,\n      options: bscrollOptions,\n      style: content.style,\n      pending: false,\n      forceStopped: false,\n      timer: 0,\n      hooks: new EventEmitter([\n        'move',\n        'end',\n        'forceStop',\n        'beforeForceStop',\n        'callStop',\n        'time',\n        'timeFunction',\n      ]),\n      translate: jest.fn(),\n      stop: jest.fn(),\n      doStop: jest.fn(),\n      move: jest.fn(),\n      destroy: jest.fn(),\n      setPending: jest.fn(),\n      setForceStopped: jest.fn(),\n      setCallStop: jest.fn(),\n      setContent: jest.fn(),\n      clearTimer: jest.fn(),\n    }\n  })\n\nexport default Animation\n"
  },
  {
    "path": "packages/core/src/animater/__mocks__/Transition.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Transition = jest\n  .fn()\n  .mockImplementation((content, translater, bscrollOptions) => {\n    return {\n      content,\n      translater,\n      options: bscrollOptions,\n      style: content.style,\n      pending: false,\n      forceStopped: false,\n      timer: 0,\n      hooks: new EventEmitter([\n        'move',\n        'end',\n        'forceStop',\n        'beforeForceStop',\n        'callStop',\n        'time',\n        'timeFunction',\n      ]),\n      translate: jest.fn(),\n      stop: jest.fn(),\n      doStop: jest.fn(),\n      move: jest.fn(),\n      startProbe: jest.fn(),\n      transitionTime: jest.fn(),\n      transitionTimingFunction: jest.fn(),\n      destroy: jest.fn(),\n      setPending: jest.fn(),\n      setForceStopped: jest.fn(),\n      setCallStop: jest.fn(),\n      setContent: jest.fn(),\n      clearTimer: jest.fn(),\n    }\n  })\n\nexport default Transition\n"
  },
  {
    "path": "packages/core/src/animater/__mocks__/index.ts",
    "content": "import Transition from '../Transition'\nimport Animation from '../Animation'\n\njest.mock('../Transition')\njest.mock('../Animation')\n\nconst createAnimater = jest\n  .fn()\n  .mockImplementation((element, translater, bscrollOptions) => {\n    if (bscrollOptions.useTransition) {\n      return new Transition(element, translater, bscrollOptions as {\n        probeType: number\n      })\n    } else {\n      return new Animation(element, translater, bscrollOptions as {\n        probeType: number\n      })\n    }\n  })\n\nexport default createAnimater\n"
  },
  {
    "path": "packages/core/src/animater/__tests__/Animation.spec.ts",
    "content": "import Translater from '../../translater'\njest.mock('../../translater')\n\nlet mockRequestAnimationFrame = jest.fn()\nlet mockCancelAnimationFrame = jest.fn()\njest.mock('@better-scroll/shared-utils/src/raf', () => {\n  return {\n    requestAnimationFrame: (cb: any) => mockRequestAnimationFrame(cb),\n    cancelAnimationFrame: () => mockCancelAnimationFrame(),\n  }\n})\n\nlet mockGetNow = jest.fn()\njest.mock('@better-scroll/shared-utils/src/lang', () => {\n  return {\n    getNow: () => mockGetNow(),\n  }\n})\n\nimport Animation from '../Animation'\n\nfunction createAnimation(probeType: number) {\n  const dom = document.createElement('div')\n  const translater = new Translater(dom)\n  const animation = new Animation(dom, translater, { probeType })\n  return {\n    dom,\n    translater,\n    animation,\n  }\n}\ndescribe('Animation Class test suit', () => {\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n\n  afterEach(() => {\n    jest.clearAllTimers()\n    jest.clearAllMocks()\n  })\n\n  it('should off hooks and cancelAnimationFrame when destroy', () => {\n    const { animation } = createAnimation(0)\n    const hooksDestroySpy = jest.spyOn(animation.hooks, 'destroy')\n    animation.destroy()\n    expect(mockCancelAnimationFrame).toBeCalledTimes(1)\n    expect(hooksDestroySpy).toBeCalledTimes(1)\n  })\n\n  it('should move to endPoint and trigger hooks in one step when time=0', () => {\n    const { animation, translater } = createAnimation(0)\n    const onMove = jest.fn()\n    const onEnd = jest.fn()\n    animation.hooks.on('move', onMove)\n    animation.hooks.on('end', onEnd)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 10,\n      y: 10,\n    }\n\n    animation.options.probeType = 3\n\n    animation.move(startPoint, endPoint, 0, 'easing')\n    expect(translater.translate).toBeCalledTimes(1)\n    expect(translater.translate).toBeCalledWith(endPoint)\n    expect(onMove).toBeCalled()\n    expect(onEnd).toBeCalled()\n  })\n\n  it('should move to endPoint for serveral steps with time', () => {\n    const { animation, translater, dom } = createAnimation(3)\n    const onMove = jest.fn()\n    const onEnd = jest.fn()\n    const easeFn = jest.fn()\n    animation.hooks.on('move', onMove)\n    animation.hooks.on('end', onEnd)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 0,\n      y: 100,\n    }\n\n    mockRequestAnimationFrame.mockImplementation((cb) => {\n      setTimeout(() => {\n        cb()\n      }, 200)\n    })\n    mockGetNow\n      .mockImplementationOnce(() => {\n        return 1000\n      })\n      .mockImplementationOnce(() => {\n        return 1100\n      })\n      .mockImplementationOnce(() => {\n        return 1600\n      })\n    easeFn.mockImplementationOnce(() => {\n      return 0.2\n    })\n    animation.move(startPoint, endPoint, 500, easeFn)\n    expect(easeFn).toBeCalledWith(0.2)\n    expect(translater.translate).toBeCalledWith({\n      x: 0,\n      y: 20,\n    })\n    expect(onMove).toBeCalledTimes(1)\n\n    jest.advanceTimersByTime(200)\n    expect(translater.translate).toBeCalledWith({\n      x: 0,\n      y: 100,\n    })\n    expect(onMove).toBeCalledTimes(2)\n    expect(onEnd).toBeCalled()\n    animation.destroy()\n  })\n  it('should force stop', () => {\n    const { animation, translater } = createAnimation(3)\n    animation.setCallStop(true)\n    const onMove = jest.fn()\n    const onForceStop = jest.fn()\n    const easeFn = jest.fn()\n    animation.hooks.on('move', onMove)\n    animation.hooks.on('forceStop', onForceStop)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 0,\n      y: 100,\n    }\n\n    mockRequestAnimationFrame.mockImplementation((cb) => {\n      setTimeout(() => {\n        cb()\n      }, 200)\n    })\n    mockGetNow\n      .mockImplementationOnce(() => {\n        return 1000\n      })\n      .mockImplementationOnce(() => {\n        return 1100\n      })\n      .mockImplementationOnce(() => {\n        return 1600\n      })\n    easeFn.mockImplementationOnce(() => {\n      return 0.2\n    })\n    animation.move(startPoint, endPoint, 500, easeFn)\n    expect(easeFn).toBeCalledWith(0.2)\n    expect(translater.translate).toBeCalledWith({\n      x: 0,\n      y: 20,\n    })\n    expect(animation.pending).toBe(true)\n    expect(animation.callStopWhenPending).toBe(false)\n    ;(<jest.Mock>translater.getComputedPosition).mockImplementation(() => {\n      return 20\n    })\n    animation.stop()\n    expect(animation.pending).toBe(false)\n    expect(mockCancelAnimationFrame).toBeCalled()\n    expect(animation.callStopWhenPending).toBe(true)\n    expect(onForceStop).toBeCalledWith(20)\n\n    animation.destroy()\n  })\n})\n"
  },
  {
    "path": "packages/core/src/animater/__tests__/Transition.spec.ts",
    "content": "import Translater from '../../translater/index'\njest.mock('../../translater/index')\n\nlet mockRequestAnimationFrame = jest.fn()\nlet mockCancelAnimationFrame = jest.fn()\njest.mock('@better-scroll/shared-utils/src/raf', () => {\n  return {\n    requestAnimationFrame: (cb: any) => mockRequestAnimationFrame(cb),\n    cancelAnimationFrame: () => mockCancelAnimationFrame(),\n  }\n})\n\nimport Transition from '@better-scroll/core/src/animater/Transition'\n\nfunction createTransition(probeType: number) {\n  const dom = document.createElement('div')\n  const translater = new Translater(dom)\n  const transition = new Transition(dom, translater, { probeType })\n  return {\n    dom,\n    translater,\n    transition,\n  }\n}\ndescribe('Transition Class test suit', () => {\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n\n  afterEach(() => {\n    jest.clearAllTimers()\n    jest.clearAllMocks()\n  })\n  it('should off hooks and cancelAnimationFrame when destroy', () => {\n    const { transition } = createTransition(0)\n    const hooksDestroySpy = jest.spyOn(transition.hooks, 'destroy')\n    transition.destroy()\n    expect(mockCancelAnimationFrame).toBeCalledTimes(1)\n    expect(hooksDestroySpy).toBeCalledTimes(1)\n  })\n\n  it('should set timeFunction and trigger event', () => {\n    const { transition, dom } = createTransition(0)\n    const onTimeFunction = jest.fn()\n    const onTime = jest.fn()\n    transition.hooks.on('time', onTime)\n    transition.hooks.on('timeFunction', onTimeFunction)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 10,\n      y: 10,\n    }\n    transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')\n    expect(onTime).toHaveBeenCalledTimes(1)\n    expect(onTimeFunction).toHaveBeenCalledTimes(1)\n    expect(dom.style.transitionTimingFunction).toBe(\n      'cubic-bezier(0.23, 1, 0.32, 1)'\n    )\n    expect(dom.style.transitionDuration).toBe('200ms')\n    transition.destroy()\n  })\n\n  it('should call translater with right arguments', () => {\n    const { transition, translater } = createTransition(0)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 10,\n      y: 10,\n    }\n    transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')\n    expect(translater.translate).toBeCalledWith(endPoint)\n    transition.destroy()\n  })\n\n  it('should trigger end hook with time=0', () => {\n    const { transition } = createTransition(0)\n    const onEnd = jest.fn()\n    transition.hooks.on('end', onEnd)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 10,\n      y: 10,\n    }\n    transition.options.probeType = 3\n    transition.move(startPoint, endPoint, 0, 'cubic-bezier(0.23, 1, 0.32, 1)')\n    expect(onEnd).toHaveBeenCalled()\n    transition.destroy()\n  })\n  it('should stop', () => {\n    const { transition, translater, dom } = createTransition(0)\n    const onForceStop = jest.fn()\n    transition.hooks.on('forceStop', onForceStop)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 0,\n      y: 10,\n    }\n    transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')\n    ;(<jest.Mock>translater.getComputedPosition).mockImplementation(() => {\n      return { x: 10, y: 10 }\n    })\n    transition.stop()\n\n    expect(dom.style.transitionDuration).toBe('0ms')\n    expect(translater.translate).toBeCalledWith({ x: 10, y: 10 })\n    expect(onForceStop).toBeCalledWith({ x: 10, y: 10 })\n    expect(mockCancelAnimationFrame).toBeCalled()\n    expect(transition.callStopWhenPending).toBe(true)\n\n    transition.destroy()\n  })\n  it('should startProbe with probeType=3', () => {\n    const { transition, translater } = createTransition(3)\n    translater.getComputedPosition = jest.fn().mockImplementation(() => {\n      return { x: 0, y: 0 }\n    })\n    mockRequestAnimationFrame.mockImplementation((cb) => {\n      setTimeout(() => {\n        cb()\n      }, 200)\n    })\n    const onMove = jest.fn()\n    const onEnd = jest.fn()\n    transition.hooks.on('time', onMove)\n    transition.hooks.on('end', onEnd)\n\n    const startPoint = {\n      x: 0,\n      y: 0,\n    }\n    const endPoint = {\n      x: 10,\n      y: 10,\n    }\n    transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')\n    expect(transition.callStopWhenPending).toBe(false)\n    jest.advanceTimersByTime(200)\n    expect(onMove).toBeCalled()\n\n    transition.pending = false\n    jest.advanceTimersByTime(200)\n\n    expect(onEnd).toBeCalled()\n\n    transition.destroy()\n  })\n\n  it('clearTimer ', () => {\n    const { transition } = createTransition(0)\n    transition.timer = 1\n    transition.clearTimer()\n    expect(transition.timer).toBe(0)\n  })\n\n  it('should reset callStopWhenPending', () => {\n    const { transition } = createTransition(0)\n    transition.setPending(true)\n    transition.stop()\n    transition.startProbe({ x: 0, y: 0 }, { x: 0, y: -10 })\n    expect(transition.callStopWhenPending).toBe(false)\n  })\n})\n"
  },
  {
    "path": "packages/core/src/animater/__tests__/index.spec.ts",
    "content": "import Translater from '../../translater/index'\nimport Transition from '../Transition'\nimport Animation from '../Animation'\nimport { OptionsConstructor } from '../../Options'\njest.mock('../Animation')\njest.mock('../Transition')\njest.mock('../Base')\njest.mock('../../translater/index')\njest.mock('../../Options')\n\nimport createAnimater from '../index'\n\ndescribe('animater create test suit', () => {\n  const dom = document.createElement('div')\n  const translater = new Translater(dom)\n  it('should create Transition class when useTransition=true', () => {\n    const options = new OptionsConstructor()\n    options.probeType = 0\n    options.useTransition = true\n    const animater = createAnimater(dom, translater, options)\n    expect(Transition).toBeCalledWith(dom, translater, { probeType: 0 })\n  })\n  it('should create Animation class when useTransition=false', () => {\n    const options = new OptionsConstructor()\n    options.probeType = 0\n    options.useTransition = false\n    const animater = createAnimater(dom, translater, options)\n    expect(Animation).toBeCalledWith(dom, translater, { probeType: 0 })\n  })\n})\n"
  },
  {
    "path": "packages/core/src/animater/index.ts",
    "content": "import Translater from '../translater'\nimport { Options as BScrollOptions } from '../Options'\n\nimport Animater from './Base'\nimport Transition from './Transition'\nimport Animation from './Animation'\n\nexport { Animater, Transition, Animation }\n\nexport default function createAnimater(\n  element: HTMLElement,\n  translater: Translater,\n  options: BScrollOptions\n) {\n  const useTransition = options.useTransition\n  let animaterOptions = {}\n  Object.defineProperty(animaterOptions, 'probeType', {\n    enumerable: true,\n    configurable: false,\n    get() {\n      return options.probeType\n    },\n  })\n  if (useTransition) {\n    return new Transition(\n      element,\n      translater,\n      animaterOptions as {\n        probeType: number\n      }\n    )\n  } else {\n    return new Animation(\n      element,\n      translater,\n      animaterOptions as {\n        probeType: number\n      }\n    )\n  }\n}\n"
  },
  {
    "path": "packages/core/src/base/ActionsHandler.ts",
    "content": "import {\n  TouchEvent,\n  // dom\n  preventDefaultExceptionFn,\n  tagExceptionFn,\n  eventTypeMap,\n  EventType,\n  MouseButton,\n  EventRegister,\n  EventEmitter,\n} from '@better-scroll/shared-utils'\n\ntype Exception = {\n  tagName?: RegExp\n  className?: RegExp\n}\n\nexport interface Options {\n  [key: string]: boolean | number | Exception\n  click: boolean\n  bindToWrapper: boolean\n  disableMouse: boolean\n  disableTouch: boolean\n  preventDefault: boolean\n  stopPropagation: boolean\n  preventDefaultException: Exception\n  tagException: Exception\n  autoEndDistance: number\n}\n\nexport default class ActionsHandler {\n  hooks: EventEmitter\n  initiated: number\n  pointX: number\n  pointY: number\n  wrapperEventRegister: EventRegister\n  targetEventRegister: EventRegister\n  constructor(public wrapper: HTMLElement, public options: Options) {\n    this.hooks = new EventEmitter([\n      'beforeStart',\n      'start',\n      'move',\n      'end',\n      'click',\n    ])\n    this.handleDOMEvents()\n  }\n\n  private handleDOMEvents() {\n    const { bindToWrapper, disableMouse, disableTouch, click } = this.options\n    const wrapper = this.wrapper\n    const target = bindToWrapper ? wrapper : window\n    const wrapperEvents = []\n    const targetEvents = []\n    const shouldRegisterTouch = !disableTouch\n    const shouldRegisterMouse = !disableMouse\n\n    if (click) {\n      wrapperEvents.push({\n        name: 'click',\n        handler: this.click.bind(this),\n        capture: true,\n      })\n    }\n\n    if (shouldRegisterTouch) {\n      wrapperEvents.push({\n        name: 'touchstart',\n        handler: this.start.bind(this),\n      })\n\n      targetEvents.push(\n        {\n          name: 'touchmove',\n          handler: this.move.bind(this),\n        },\n        {\n          name: 'touchend',\n          handler: this.end.bind(this),\n        },\n        {\n          name: 'touchcancel',\n          handler: this.end.bind(this),\n        }\n      )\n    }\n\n    if (shouldRegisterMouse) {\n      wrapperEvents.push({\n        name: 'mousedown',\n        handler: this.start.bind(this),\n      })\n\n      targetEvents.push(\n        {\n          name: 'mousemove',\n          handler: this.move.bind(this),\n        },\n        {\n          name: 'mouseup',\n          handler: this.end.bind(this),\n        }\n      )\n    }\n    this.wrapperEventRegister = new EventRegister(wrapper, wrapperEvents)\n    this.targetEventRegister = new EventRegister(target, targetEvents)\n  }\n\n  private beforeHandler(e: TouchEvent, type: 'start' | 'move' | 'end') {\n    const {\n      preventDefault,\n      stopPropagation,\n      preventDefaultException,\n    } = this.options\n\n    const preventDefaultConditions = {\n      start: () => {\n        return (\n          preventDefault &&\n          !preventDefaultExceptionFn(e.target, preventDefaultException)\n        )\n      },\n      end: () => {\n        return (\n          preventDefault &&\n          !preventDefaultExceptionFn(e.target, preventDefaultException)\n        )\n      },\n      move: () => {\n        return preventDefault\n      },\n    }\n    if (preventDefaultConditions[type]()) {\n      e.preventDefault()\n    }\n\n    if (stopPropagation) {\n      e.stopPropagation()\n    }\n  }\n\n  setInitiated(type: number = 0) {\n    this.initiated = type\n  }\n\n  private start(e: TouchEvent) {\n    const _eventType = eventTypeMap[e.type]\n\n    if (this.initiated && this.initiated !== _eventType) {\n      return\n    }\n    this.setInitiated(_eventType)\n\n    // if textarea or other html tags in options.tagException is manipulated\n    // do not make bs scroll\n    if (tagExceptionFn(e.target, this.options.tagException)) {\n      this.setInitiated()\n      return\n    }\n\n    // only allow mouse left button\n    if (_eventType === EventType.Mouse && e.button !== MouseButton.Left) return\n\n    if (this.hooks.trigger(this.hooks.eventTypes.beforeStart, e)) {\n      return\n    }\n\n    this.beforeHandler(e, 'start')\n\n    let point = (e.touches ? e.touches[0] : e) as Touch\n    this.pointX = point.pageX\n    this.pointY = point.pageY\n\n    this.hooks.trigger(this.hooks.eventTypes.start, e)\n  }\n\n  private move(e: TouchEvent) {\n    if (eventTypeMap[e.type] !== this.initiated) {\n      return\n    }\n\n    this.beforeHandler(e, 'move')\n\n    let point = (e.touches ? e.touches[0] : e) as Touch\n    let deltaX = point.pageX - this.pointX\n    let deltaY = point.pageY - this.pointY\n    this.pointX = point.pageX\n    this.pointY = point.pageY\n\n    if (\n      this.hooks.trigger(this.hooks.eventTypes.move, {\n        deltaX,\n        deltaY,\n        e,\n      })\n    ) {\n      return\n    }\n\n    // auto end when out of viewport\n    let scrollLeft =\n      document.documentElement.scrollLeft ||\n      window.pageXOffset ||\n      document.body.scrollLeft\n    let scrollTop =\n      document.documentElement.scrollTop ||\n      window.pageYOffset ||\n      document.body.scrollTop\n\n    let pX = this.pointX - scrollLeft\n    let pY = this.pointY - scrollTop\n\n    const autoEndDistance = this.options.autoEndDistance\n    if (\n      pX > document.documentElement.clientWidth - autoEndDistance ||\n      pY > document.documentElement.clientHeight - autoEndDistance ||\n      pX < autoEndDistance ||\n      pY < autoEndDistance\n    ) {\n      this.end(e)\n    }\n  }\n  private end(e: TouchEvent) {\n    if (eventTypeMap[e.type] !== this.initiated) {\n      return\n    }\n    this.setInitiated()\n\n    this.beforeHandler(e, 'end')\n\n    this.hooks.trigger(this.hooks.eventTypes.end, e)\n  }\n\n  private click(e: TouchEvent) {\n    this.hooks.trigger(this.hooks.eventTypes.click, e)\n  }\n\n  setContent(content: HTMLElement) {\n    if (content !== this.wrapper) {\n      this.wrapper = content\n      this.rebindDOMEvents()\n    }\n  }\n\n  rebindDOMEvents() {\n    this.wrapperEventRegister.destroy()\n    this.targetEventRegister.destroy()\n    this.handleDOMEvents()\n  }\n\n  destroy() {\n    this.wrapperEventRegister.destroy()\n    this.targetEventRegister.destroy()\n    this.hooks.destroy()\n  }\n}\n"
  },
  {
    "path": "packages/core/src/base/__mocks__/ActionsHandler.ts",
    "content": "import { EventRegister, EventEmitter } from '@better-scroll/shared-utils'\n\nconst ActionsHandler = jest\n  .fn()\n  .mockImplementation((wrapper, bscrollOptions) => {\n    return {\n      wrapper,\n      options: bscrollOptions,\n      initiated: 1,\n      pointX: 0,\n      pointY: 0,\n      startClickRegister: new EventRegister(wrapper, []),\n      moveEndRegister: new EventRegister(wrapper, []),\n      hooks: new EventEmitter(['beforeStart', 'start', 'move', 'end', 'click']),\n      destroy: jest.fn(),\n      setInitiated: jest.fn(),\n      setContent: jest.fn(),\n      rebindDOMEvents: jest.fn(),\n    }\n  })\n\nexport default ActionsHandler\n"
  },
  {
    "path": "packages/core/src/base/__tests__/ActionsHandler.spec.ts",
    "content": "import ActionsHandler, {\n  Options,\n} from '@better-scroll/core/src/base/ActionsHandler'\nimport {\n  dispatchTouch,\n  dispatchMouse,\n  dispatchTouchStart,\n  dispatchTouchEnd,\n  dispatchTouchCancel,\n} from '@better-scroll/core/src/__tests__/__utils__/event'\n\ndescribe('ActionsHandler', () => {\n  let actionsHandler: ActionsHandler\n  let wrapper: HTMLElement\n  let options: Options\n\n  beforeEach(() => {\n    wrapper = document.createElement('wrapper')\n    options = {\n      click: false,\n      bindToWrapper: false,\n      disableMouse: false,\n      disableTouch: false,\n      preventDefault: true,\n      stopPropagation: true,\n      preventDefaultException: {\n        tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n      },\n      tagException: { tagName: /^TEXTAREA$/ },\n      autoEndDistance: 5,\n    }\n  })\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should bind mouse event when options.disableMouse is false', () => {\n    options.disableTouch = true\n    actionsHandler = new ActionsHandler(wrapper, options)\n\n    const wrapperEventsName = actionsHandler.wrapperEventRegister.events.map(\n      (event) => event.name\n    )\n\n    const targetEventsName = actionsHandler.targetEventRegister.events.map(\n      (event) => event.name\n    )\n\n    expect(wrapperEventsName).toMatchObject(['mousedown'])\n    expect(targetEventsName).toMatchObject(['mousemove', 'mouseup'])\n  })\n\n  it('should invoke start method when dispatch mousedown', () => {\n    actionsHandler = new ActionsHandler(wrapper, options)\n    const beforeStartMockHandler = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n    const startMockHandler = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n\n    actionsHandler.hooks.on('beforeStart', beforeStartMockHandler)\n    actionsHandler.hooks.on('start', startMockHandler)\n\n    dispatchMouse(wrapper, 'mousedown')\n\n    expect(beforeStartMockHandler).toBeCalledTimes(1)\n    expect(startMockHandler).toBeCalledTimes(1)\n\n    // return early\n    actionsHandler.setInitiated(1)\n    dispatchMouse(wrapper, 'mousedown')\n    expect(beforeStartMockHandler).toBeCalledTimes(1)\n    expect(startMockHandler).toBeCalledTimes(1)\n\n    // only allow mouse left button\n    actionsHandler.setInitiated(0)\n    dispatchMouse(wrapper, 'mousedown', false)\n    expect(beforeStartMockHandler).toBeCalledTimes(1)\n    expect(startMockHandler).toBeCalledTimes(1)\n\n    // cancelable beforeStart hook\n    actionsHandler.hooks.on('beforeStart', () => true)\n    dispatchMouse(wrapper, 'mousedown')\n    expect(beforeStartMockHandler).toBeCalledTimes(2)\n    expect(startMockHandler).toBeCalledTimes(1)\n  })\n\n  it('should invoke move method when dispatch touchmove', () => {\n    actionsHandler = new ActionsHandler(wrapper, options)\n    const moveMockHandler1 = jest.fn().mockImplementationOnce(() => {\n      return true\n    })\n    const moveMockHandler2 = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n\n    actionsHandler.hooks.on('move', moveMockHandler1)\n    actionsHandler.hooks.on('move', moveMockHandler2)\n\n    dispatchMouse(wrapper, 'mousedown')\n\n    dispatchMouse(window, 'mousemove')\n\n    expect(moveMockHandler1).toBeCalledTimes(1)\n\n    // cancelable move hook\n    expect(moveMockHandler2).not.toBeCalled()\n\n    // simulate finger moved out of viewport\n    actionsHandler.pointX = 5\n    const endMockHandler = jest.fn()\n    actionsHandler.hooks.on(actionsHandler.hooks.eventTypes.end, endMockHandler)\n    dispatchMouse(window, 'mousemove')\n\n    expect(endMockHandler).toBeCalled()\n  })\n\n  it('should invoke end method when dispatch touchend', () => {\n    actionsHandler = new ActionsHandler(wrapper, options)\n    const endMockHandler = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n\n    actionsHandler.hooks.on('end', endMockHandler)\n\n    dispatchTouchStart(wrapper, [{ pageX: 0, pageY: 0 }])\n\n    dispatchTouchEnd(window, [{ pageX: 0, pageY: 0 }])\n\n    expect(endMockHandler).toBeCalled()\n  })\n\n  it('should invoke end method when dispatch touchcancel', () => {\n    actionsHandler = new ActionsHandler(wrapper, options)\n    const endMockHandler = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n\n    actionsHandler.hooks.on('end', endMockHandler)\n\n    dispatchTouchStart(wrapper, [{ pageX: 0, pageY: 0 }])\n\n    dispatchTouchCancel(window, [{ pageX: 0, pageY: 0 }])\n\n    expect(endMockHandler).toBeCalled()\n  })\n\n  it('should call click method when dispatch click', () => {\n    options.click = true\n    actionsHandler = new ActionsHandler(wrapper, options)\n    const clickMockHandler = jest.fn().mockImplementation(() => {\n      return 'dummy test'\n    })\n\n    actionsHandler.hooks.on('click', clickMockHandler)\n\n    dispatchTouch(wrapper, 'click', [\n      {\n        pageX: 10,\n        pageY: 10,\n      },\n    ])\n\n    expect(clickMockHandler).toBeCalled()\n  })\n\n  it('should make bs not take effect when manipulate textarea DOM tag', () => {\n    const textarea = document.createElement('textarea')\n    const content = document.createElement('div')\n    content.appendChild(textarea)\n    wrapper.appendChild(content)\n    actionsHandler = new ActionsHandler(wrapper, options)\n\n    dispatchMouse(textarea, 'mousedown')\n\n    expect(actionsHandler.initiated).toBeFalsy()\n  })\n\n  it('destroy()', () => {\n    actionsHandler = new ActionsHandler(wrapper, options)\n\n    actionsHandler.destroy()\n\n    expect(actionsHandler.wrapperEventRegister.events.length).toBe(0)\n    expect(actionsHandler.targetEventRegister.events.length).toBe(0)\n    expect(actionsHandler.hooks.eventTypes).toMatchObject({})\n    expect(actionsHandler.hooks.events).toMatchObject({})\n  })\n\n  it('setContent()', () => {\n    const p = document.createElement('p')\n    actionsHandler.setContent(p)\n    expect(actionsHandler.wrapper).toBe(p)\n  })\n})\n"
  },
  {
    "path": "packages/core/src/index.ts",
    "content": "import { BScroll } from './BScroll'\n\nexport { BScrollInstance } from './Instance'\nexport { Options, CustomOptions } from './Options'\nexport { TranslaterPoint } from './translater'\nexport { MountedBScrollHTMLElement } from './BScroll'\nexport { Behavior, Boundary } from './scroller/Behavior'\nexport { createBScroll, CustomAPI } from './BScroll'\n\nexport default BScroll\n"
  },
  {
    "path": "packages/core/src/scroller/Actions.ts",
    "content": "import ActionsHandler from '../base/ActionsHandler'\nimport { Behavior } from './Behavior'\nimport DirectionLockAction from './DirectionLock'\nimport { Animater } from '../animater'\nimport { OptionsConstructor as BScrollOptions } from '../Options'\nimport { TranslaterPoint } from '../translater'\nimport {\n  preventDefaultExceptionFn,\n  TouchEvent,\n  getNow,\n  Probe,\n  EventEmitter,\n  between,\n  Quadrant,\n  maybePrevent,\n} from '@better-scroll/shared-utils'\n\nconst applyQuadrantTransformation = (\n  deltaX: number,\n  deltaY: number,\n  quadrant: Quadrant\n) => {\n  if (quadrant === Quadrant.Second) {\n    return [deltaY, -deltaX]\n  } else if (quadrant === Quadrant.Third) {\n    return [-deltaX, -deltaY]\n  } else if (quadrant === Quadrant.Forth) {\n    return [-deltaY, deltaX]\n  } else {\n    return [deltaX, deltaY]\n  }\n}\nexport default class ScrollerActions {\n  hooks: EventEmitter\n  scrollBehaviorX: Behavior\n  scrollBehaviorY: Behavior\n  actionsHandler: ActionsHandler\n  animater: Animater\n  options: BScrollOptions\n  directionLockAction: DirectionLockAction\n  fingerMoved: boolean\n  contentMoved: boolean\n  enabled: boolean\n  startTime: number\n  endTime: number\n  ensuringInteger: boolean\n  constructor(\n    scrollBehaviorX: Behavior,\n    scrollBehaviorY: Behavior,\n    actionsHandler: ActionsHandler,\n    animater: Animater,\n    options: BScrollOptions\n  ) {\n    this.hooks = new EventEmitter([\n      'start',\n      'beforeMove',\n      'scrollStart',\n      'scroll',\n      'beforeEnd',\n      'end',\n      'scrollEnd',\n      'contentNotMoved',\n      'detectMovingDirection',\n      'coordinateTransformation',\n    ])\n\n    this.scrollBehaviorX = scrollBehaviorX\n    this.scrollBehaviorY = scrollBehaviorY\n    this.actionsHandler = actionsHandler\n    this.animater = animater\n    this.options = options\n\n    this.directionLockAction = new DirectionLockAction(\n      options.directionLockThreshold,\n      options.freeScroll,\n      options.eventPassthrough\n    )\n\n    this.enabled = true\n\n    this.bindActionsHandler()\n  }\n\n  private bindActionsHandler() {\n    // [mouse|touch]start event\n    this.actionsHandler.hooks.on(\n      this.actionsHandler.hooks.eventTypes.start,\n      (e: TouchEvent) => {\n        if (!this.enabled) return true\n        return this.handleStart(e)\n      }\n    )\n    // [mouse|touch]move event\n    this.actionsHandler.hooks.on(\n      this.actionsHandler.hooks.eventTypes.move,\n      ({\n        deltaX,\n        deltaY,\n        e,\n      }: {\n        deltaX: number\n        deltaY: number\n        e: TouchEvent\n      }) => {\n        if (!this.enabled) return true\n\n        const [transformateDeltaX, transformateDeltaY] =\n          applyQuadrantTransformation(deltaX, deltaY, this.options.quadrant)\n        const transformateDeltaData = {\n          deltaX: transformateDeltaX,\n          deltaY: transformateDeltaY,\n        }\n\n        this.hooks.trigger(\n          this.hooks.eventTypes.coordinateTransformation,\n          transformateDeltaData\n        )\n\n        return this.handleMove(\n          transformateDeltaData.deltaX,\n          transformateDeltaData.deltaY,\n          e\n        )\n      }\n    )\n    // [mouse|touch]end event\n    this.actionsHandler.hooks.on(\n      this.actionsHandler.hooks.eventTypes.end,\n      (e: TouchEvent) => {\n        if (!this.enabled) return true\n        return this.handleEnd(e)\n      }\n    )\n\n    // click\n    this.actionsHandler.hooks.on(\n      this.actionsHandler.hooks.eventTypes.click,\n      (e: TouchEvent) => {\n        // handle native click event\n        if (this.enabled && !e._constructed) {\n          this.handleClick(e)\n        }\n      }\n    )\n  }\n\n  private handleStart(e: TouchEvent) {\n    const timestamp = getNow()\n    this.fingerMoved = false\n    this.contentMoved = false\n\n    this.startTime = timestamp\n\n    this.directionLockAction.reset()\n\n    this.scrollBehaviorX.start()\n    this.scrollBehaviorY.start()\n\n    // force stopping last transition or animation\n    this.animater.doStop()\n\n    this.scrollBehaviorX.resetStartPos()\n    this.scrollBehaviorY.resetStartPos()\n\n    this.hooks.trigger(this.hooks.eventTypes.start, e)\n  }\n\n  private handleMove(deltaX: number, deltaY: number, e: TouchEvent) {\n    if (this.hooks.trigger(this.hooks.eventTypes.beforeMove, e)) {\n      return\n    }\n\n    const absDistX = this.scrollBehaviorX.getAbsDist(deltaX)\n    const absDistY = this.scrollBehaviorY.getAbsDist(deltaY)\n    const timestamp = getNow()\n\n    // We need to move at least momentumLimitDistance pixels\n    // for the scrolling to initiate\n    if (this.checkMomentum(absDistX, absDistY, timestamp)) {\n      return true\n    }\n    if (this.directionLockAction.checkMovingDirection(absDistX, absDistY, e)) {\n      this.actionsHandler.setInitiated()\n      return true\n    }\n\n    const delta = this.directionLockAction.adjustDelta(deltaX, deltaY)\n\n    const prevX = this.scrollBehaviorX.getCurrentPos()\n    const newX = this.scrollBehaviorX.move(delta.deltaX)\n    const prevY = this.scrollBehaviorY.getCurrentPos()\n    const newY = this.scrollBehaviorY.move(delta.deltaY)\n\n    if (this.hooks.trigger(this.hooks.eventTypes.detectMovingDirection)) {\n      return\n    }\n\n    if (!this.fingerMoved) {\n      this.fingerMoved = true\n    }\n\n    const positionChanged = newX !== prevX || newY !== prevY\n\n    if (!this.contentMoved && !positionChanged) {\n      this.hooks.trigger(this.hooks.eventTypes.contentNotMoved)\n    }\n\n    if (!this.contentMoved && positionChanged) {\n      this.contentMoved = true\n      this.hooks.trigger(this.hooks.eventTypes.scrollStart)\n    }\n\n    if (this.contentMoved && positionChanged) {\n      this.animater.translate({\n        x: newX,\n        y: newY,\n      })\n\n      this.dispatchScroll(timestamp)\n    }\n  }\n\n  private dispatchScroll(timestamp: number) {\n    // dispatch scroll in interval time\n    if (timestamp - this.startTime > this.options.momentumLimitTime) {\n      // refresh time and starting position to initiate a momentum\n      this.startTime = timestamp\n      this.scrollBehaviorX.updateStartPos()\n      this.scrollBehaviorY.updateStartPos()\n      if (this.options.probeType === Probe.Throttle) {\n        this.hooks.trigger(this.hooks.eventTypes.scroll, this.getCurrentPos())\n      }\n    }\n\n    // dispatch scroll all the time\n    if (this.options.probeType > Probe.Throttle) {\n      this.hooks.trigger(this.hooks.eventTypes.scroll, this.getCurrentPos())\n    }\n  }\n\n  private checkMomentum(absDistX: number, absDistY: number, timestamp: number) {\n    return (\n      timestamp - this.endTime > this.options.momentumLimitTime &&\n      absDistY < this.options.momentumLimitDistance &&\n      absDistX < this.options.momentumLimitDistance\n    )\n  }\n\n  private handleEnd(e: TouchEvent) {\n    if (this.hooks.trigger(this.hooks.eventTypes.beforeEnd, e)) {\n      return\n    }\n    let currentPos = this.getCurrentPos()\n\n    this.scrollBehaviorX.updateDirection()\n    this.scrollBehaviorY.updateDirection()\n\n    if (this.hooks.trigger(this.hooks.eventTypes.end, e, currentPos)) {\n      return true\n    }\n\n    currentPos = this.ensureIntegerPos(currentPos)\n\n    this.animater.translate(currentPos)\n\n    this.endTime = getNow()\n\n    const duration = this.endTime - this.startTime\n\n    this.hooks.trigger(this.hooks.eventTypes.scrollEnd, currentPos, duration)\n  }\n\n  private ensureIntegerPos(currentPos: TranslaterPoint) {\n    this.ensuringInteger = true\n    let { x, y } = currentPos\n    const { minScrollPos: minScrollPosX, maxScrollPos: maxScrollPosX } =\n      this.scrollBehaviorX\n    const { minScrollPos: minScrollPosY, maxScrollPos: maxScrollPosY } =\n      this.scrollBehaviorY\n\n    x = x > 0 ? Math.ceil(x) : Math.floor(x)\n    y = y > 0 ? Math.ceil(y) : Math.floor(y)\n\n    x = between(x, maxScrollPosX, minScrollPosX)\n    y = between(y, maxScrollPosY, minScrollPosY)\n    return { x, y }\n  }\n\n  private handleClick(e: TouchEvent) {\n    if (\n      !preventDefaultExceptionFn(e.target, this.options.preventDefaultException)\n    ) {\n      maybePrevent(e)\n      e.stopPropagation()\n    }\n  }\n\n  getCurrentPos(): TranslaterPoint {\n    return {\n      x: this.scrollBehaviorX.getCurrentPos(),\n      y: this.scrollBehaviorY.getCurrentPos(),\n    }\n  }\n\n  refresh() {\n    this.endTime = 0\n  }\n\n  destroy() {\n    this.hooks.destroy()\n  }\n}\n"
  },
  {
    "path": "packages/core/src/scroller/Behavior.ts",
    "content": "import { getRect, Direction, EventEmitter } from '@better-scroll/shared-utils'\n\nexport type Bounces = [boolean, boolean]\n\nexport type Rect = { size: string; position: string }\n\nexport interface Options {\n  scrollable: boolean\n  momentum: boolean\n  momentumLimitTime: number\n  momentumLimitDistance: number\n  deceleration: number\n  swipeBounceTime: number\n  swipeTime: number\n  bounces: Bounces\n  rect: Rect\n  outOfBoundaryDampingFactor: number\n  specifiedIndexAsContent: number\n  [key: string]: number | boolean | Bounces | Rect\n}\n\nexport type Boundary = { minScrollPos: number; maxScrollPos: number }\n\nexport class Behavior {\n  content: HTMLElement\n  currentPos: number\n  startPos: number\n  absStartPos: number\n  dist: number\n  minScrollPos: number\n  maxScrollPos: number\n  hasScroll: boolean\n  direction: number\n  movingDirection: number\n  relativeOffset: number\n  wrapperSize: number\n  contentSize: number\n  hooks: EventEmitter\n  constructor(\n    public wrapper: HTMLElement,\n    content: HTMLElement,\n    public options: Options\n  ) {\n    this.hooks = new EventEmitter([\n      'beforeComputeBoundary',\n      'computeBoundary',\n      'momentum',\n      'end',\n      'ignoreHasScroll'\n    ])\n    this.refresh(content)\n  }\n\n  start() {\n    this.dist = 0\n    this.setMovingDirection(Direction.Default)\n    this.setDirection(Direction.Default)\n  }\n\n  move(delta: number) {\n    delta = this.hasScroll ? delta : 0\n    this.setMovingDirection(delta)\n    return this.performDampingAlgorithm(\n      delta,\n      this.options.outOfBoundaryDampingFactor\n    )\n  }\n\n  setMovingDirection(delta: number) {\n    this.movingDirection =\n      delta > 0\n        ? Direction.Negative\n        : delta < 0\n        ? Direction.Positive\n        : Direction.Default\n  }\n\n  setDirection(delta: number) {\n    this.direction =\n      delta > 0\n        ? Direction.Negative\n        : delta < 0\n        ? Direction.Positive\n        : Direction.Default\n  }\n\n  performDampingAlgorithm(delta: number, dampingFactor: number): number {\n    let newPos = this.currentPos + delta\n    // Slow down or stop if outside of the boundaries\n    if (newPos > this.minScrollPos || newPos < this.maxScrollPos) {\n      if (\n        (newPos > this.minScrollPos && this.options.bounces[0]) ||\n        (newPos < this.maxScrollPos && this.options.bounces[1])\n      ) {\n        newPos = this.currentPos + delta * dampingFactor\n      } else {\n        newPos =\n          newPos > this.minScrollPos ? this.minScrollPos : this.maxScrollPos\n      }\n    }\n    return newPos\n  }\n\n  end(duration: number) {\n    let momentumInfo: {\n      destination?: number\n      duration?: number\n    } = {\n      duration: 0\n    }\n\n    const absDist = Math.abs(this.currentPos - this.startPos)\n    // start momentum animation if needed\n    if (\n      this.options.momentum &&\n      duration < this.options.momentumLimitTime &&\n      absDist > this.options.momentumLimitDistance\n    ) {\n      const wrapperSize =\n        (this.direction === Direction.Negative && this.options.bounces[0]) ||\n        (this.direction === Direction.Positive && this.options.bounces[1])\n          ? this.wrapperSize\n          : 0\n\n      momentumInfo = this.hasScroll\n        ? this.momentum(\n            this.currentPos,\n            this.startPos,\n            duration,\n            this.maxScrollPos,\n            this.minScrollPos,\n            wrapperSize,\n            this.options\n          )\n        : { destination: this.currentPos, duration: 0 }\n    } else {\n      this.hooks.trigger(this.hooks.eventTypes.end, momentumInfo)\n    }\n    return momentumInfo\n  }\n\n  private momentum(\n    current: number,\n    start: number,\n    time: number,\n    lowerMargin: number,\n    upperMargin: number,\n    wrapperSize: number,\n    options = this.options\n  ) {\n    const distance = current - start\n    const speed = Math.abs(distance) / time\n\n    const { deceleration, swipeBounceTime, swipeTime } = options\n    const duration = Math.min(swipeTime, (speed * 2) / deceleration)\n    const momentumData = {\n      destination:\n        current + ((speed * speed) / deceleration) * (distance < 0 ? -1 : 1),\n      duration,\n      rate: 15\n    }\n\n    this.hooks.trigger(this.hooks.eventTypes.momentum, momentumData, distance)\n\n    if (momentumData.destination < lowerMargin) {\n      momentumData.destination = wrapperSize\n        ? Math.max(\n            lowerMargin - wrapperSize / 4,\n            lowerMargin - (wrapperSize / momentumData.rate) * speed\n          )\n        : lowerMargin\n      momentumData.duration = swipeBounceTime\n    } else if (momentumData.destination > upperMargin) {\n      momentumData.destination = wrapperSize\n        ? Math.min(\n            upperMargin + wrapperSize / 4,\n            upperMargin + (wrapperSize / momentumData.rate) * speed\n          )\n        : upperMargin\n      momentumData.duration = swipeBounceTime\n    }\n    momentumData.destination = Math.round(momentumData.destination)\n\n    return momentumData\n  }\n\n  updateDirection() {\n    const absDist = this.currentPos - this.absStartPos\n    this.setDirection(absDist)\n  }\n\n  refresh(content: HTMLElement) {\n    const { size, position } = this.options.rect\n    const isWrapperStatic =\n      window.getComputedStyle(this.wrapper, null).position === 'static'\n    // Force reflow\n    const wrapperRect = getRect(this.wrapper)\n    // use client is more fair than offset\n    this.wrapperSize = this.wrapper[\n      size === 'width' ? 'clientWidth' : 'clientHeight'\n    ]\n    this.setContent(content)\n    const contentRect = getRect(this.content)\n    this.contentSize = contentRect[size]\n\n    this.relativeOffset = contentRect[position]\n    /* istanbul ignore if  */\n    if (isWrapperStatic) {\n      this.relativeOffset -= wrapperRect[position]\n    }\n\n    this.computeBoundary()\n    this.setDirection(Direction.Default)\n  }\n\n  private setContent(content: HTMLElement) {\n    if (content !== this.content) {\n      this.content = content\n      this.resetState()\n    }\n  }\n\n  private resetState() {\n    this.currentPos = 0\n    this.startPos = 0\n    this.dist = 0\n    this.setDirection(Direction.Default)\n    this.setMovingDirection(Direction.Default)\n    this.resetStartPos()\n  }\n\n  computeBoundary() {\n    this.hooks.trigger(this.hooks.eventTypes.beforeComputeBoundary)\n\n    const boundary: Boundary = {\n      minScrollPos: 0,\n      maxScrollPos: this.wrapperSize - this.contentSize\n    }\n    if (boundary.maxScrollPos < 0) {\n      boundary.maxScrollPos -= this.relativeOffset\n      if (this.options.specifiedIndexAsContent === 0) {\n        boundary.minScrollPos = -this.relativeOffset\n      }\n    }\n    this.hooks.trigger(this.hooks.eventTypes.computeBoundary, boundary)\n\n    this.minScrollPos = boundary.minScrollPos\n    this.maxScrollPos = boundary.maxScrollPos\n\n    this.hasScroll =\n      this.options.scrollable && this.maxScrollPos < this.minScrollPos\n\n    if (!this.hasScroll && this.minScrollPos < this.maxScrollPos) {\n      this.maxScrollPos = this.minScrollPos\n      this.contentSize = this.wrapperSize\n    }\n  }\n\n  updatePosition(pos: number) {\n    this.currentPos = pos\n  }\n\n  getCurrentPos() {\n    return this.currentPos\n  }\n\n  checkInBoundary() {\n    const position = this.adjustPosition(this.currentPos)\n    const inBoundary = position === this.getCurrentPos()\n    return {\n      position,\n      inBoundary\n    }\n  }\n\n  // adjust position when out of boundary\n  adjustPosition(pos: number) {\n    if (\n      !this.hasScroll &&\n      !this.hooks.trigger(this.hooks.eventTypes.ignoreHasScroll)\n    ) {\n      pos = this.minScrollPos\n    } else if (pos > this.minScrollPos) {\n      pos = this.minScrollPos\n    } else if (pos < this.maxScrollPos) {\n      pos = this.maxScrollPos\n    }\n\n    return pos\n  }\n\n  updateStartPos() {\n    this.startPos = this.currentPos\n  }\n\n  updateAbsStartPos() {\n    this.absStartPos = this.currentPos\n  }\n\n  resetStartPos() {\n    this.updateStartPos()\n    this.updateAbsStartPos()\n  }\n\n  getAbsDist(delta: number) {\n    this.dist += delta\n    return Math.abs(this.dist)\n  }\n\n  destroy() {\n    this.hooks.destroy()\n  }\n}\n"
  },
  {
    "path": "packages/core/src/scroller/DirectionLock.ts",
    "content": "import {\n  TouchEvent,\n  DirectionLock,\n  EventPassthrough,\n  maybePrevent,\n} from '@better-scroll/shared-utils'\n\nconst enum Passthrough {\n  Yes = 'yes',\n  No = 'no',\n}\n\ninterface DirectionMap {\n  [key: string]: {\n    [key: string]: EventPassthrough\n  }\n}\n\nconst PassthroughHandlers = {\n  [Passthrough.Yes]: (e: TouchEvent) => {\n    return true\n  },\n  [Passthrough.No]: (e: TouchEvent) => {\n    maybePrevent(e)\n    return false\n  },\n}\nconst DirectionMap: DirectionMap = {\n  [DirectionLock.Horizontal]: {\n    [Passthrough.Yes]: EventPassthrough.Horizontal,\n    [Passthrough.No]: EventPassthrough.Vertical,\n  },\n  [DirectionLock.Vertical]: {\n    [Passthrough.Yes]: EventPassthrough.Vertical,\n    [Passthrough.No]: EventPassthrough.Horizontal,\n  },\n}\n\nexport default class DirectionLockAction {\n  directionLocked: DirectionLock\n  constructor(\n    public directionLockThreshold: number,\n    public freeScroll: boolean,\n    public eventPassthrough: string\n  ) {\n    this.reset()\n  }\n\n  reset() {\n    this.directionLocked = DirectionLock.Default\n  }\n\n  checkMovingDirection(absDistX: number, absDistY: number, e: TouchEvent) {\n    this.computeDirectionLock(absDistX, absDistY)\n\n    return this.handleEventPassthrough(e)\n  }\n\n  adjustDelta(deltaX: number, deltaY: number) {\n    if (this.directionLocked === DirectionLock.Horizontal) {\n      deltaY = 0\n    } else if (this.directionLocked === DirectionLock.Vertical) {\n      deltaX = 0\n    }\n    return {\n      deltaX,\n      deltaY,\n    }\n  }\n\n  private computeDirectionLock(absDistX: number, absDistY: number) {\n    // If you are scrolling in one direction, lock it\n    if (this.directionLocked === DirectionLock.Default && !this.freeScroll) {\n      if (absDistX > absDistY + this.directionLockThreshold) {\n        this.directionLocked = DirectionLock.Horizontal // lock horizontally\n      } else if (absDistY >= absDistX + this.directionLockThreshold) {\n        this.directionLocked = DirectionLock.Vertical // lock vertically\n      } else {\n        this.directionLocked = DirectionLock.None // no lock\n      }\n    }\n  }\n\n  private handleEventPassthrough(e: TouchEvent) {\n    const handleMap = DirectionMap[this.directionLocked]\n    if (handleMap) {\n      if (this.eventPassthrough === handleMap[Passthrough.Yes]) {\n        return PassthroughHandlers[Passthrough.Yes](e)\n      } else if (this.eventPassthrough === handleMap[Passthrough.No]) {\n        return PassthroughHandlers[Passthrough.No](e)\n      }\n    }\n    return false\n  }\n}\n"
  },
  {
    "path": "packages/core/src/scroller/Scroller.ts",
    "content": "import ActionsHandler from '../base/ActionsHandler'\nimport Translater, { TranslaterPoint } from '../translater'\nimport createAnimater, { Animater, Transition } from '../animater'\nimport { OptionsConstructor as BScrollOptions } from '../Options'\nimport { Behavior } from './Behavior'\nimport ScrollerActions from './Actions'\nimport {\n  createActionsHandlerOptions,\n  createBehaviorOptions,\n} from './createOptions'\nimport {\n  getElement,\n  ease,\n  offset,\n  style,\n  preventDefaultExceptionFn,\n  TouchEvent,\n  isAndroid,\n  isIOSBadVersion,\n  click,\n  dblclick,\n  tap,\n  isUndef,\n  getNow,\n  cancelAnimationFrame,\n  EaseItem,\n  Probe,\n  EventEmitter,\n  EventRegister,\n} from '@better-scroll/shared-utils'\nimport { bubbling } from '../utils/bubbling'\nimport { isSamePoint } from '../utils/compare'\nimport { MountedBScrollHTMLElement } from '../BScroll'\n\nconst MIN_SCROLL_DISTANCE = 1\nexport interface ExposedAPI {\n  scrollTo(\n    x: number,\n    y: number,\n    time?: number,\n    easing?: EaseItem,\n    extraTransform?: { start: object; end: object }\n  ): void\n  scrollBy(\n    deltaX: number,\n    deltaY: number,\n    time?: number,\n    easing?: EaseItem\n  ): void\n  scrollToElement(\n    el: HTMLElement | string,\n    time: number,\n    offsetX: number | boolean,\n    offsetY: number | boolean,\n    easing?: EaseItem\n  ): void\n  resetPosition(time?: number, easing?: EaseItem): boolean\n}\n\nexport default class Scroller implements ExposedAPI {\n  actionsHandler: ActionsHandler\n  translater: Translater\n  animater: Animater\n  scrollBehaviorX: Behavior\n  scrollBehaviorY: Behavior\n  actions: ScrollerActions\n  hooks: EventEmitter\n  resizeRegister: EventRegister\n  transitionEndRegister: EventRegister\n  options: BScrollOptions\n  wrapperOffset: {\n    left: number\n    top: number\n  }\n  _reflow: number\n  resizeTimeout: number = 0\n  lastClickTime: number | null;\n  [key: string]: any\n  constructor(\n    public wrapper: HTMLElement,\n    public content: HTMLElement,\n    options: BScrollOptions\n  ) {\n    this.hooks = new EventEmitter([\n      'beforeStart',\n      'beforeMove',\n      'beforeScrollStart',\n      'scrollStart',\n      'scroll',\n      'beforeEnd',\n      'scrollEnd',\n      'resize',\n      'touchEnd',\n      'end',\n      'flick',\n      'scrollCancel',\n      'momentum',\n      'scrollTo',\n      'minDistanceScroll',\n      'scrollToElement',\n      'beforeRefresh',\n    ])\n    this.options = options\n\n    const { left, right, top, bottom } = this.options.bounce\n    // direction X\n    this.scrollBehaviorX = new Behavior(\n      wrapper,\n      content,\n      createBehaviorOptions(options, 'scrollX', [left, right], {\n        size: 'width',\n        position: 'left',\n      })\n    )\n    // direction Y\n    this.scrollBehaviorY = new Behavior(\n      wrapper,\n      content,\n      createBehaviorOptions(options, 'scrollY', [top, bottom], {\n        size: 'height',\n        position: 'top',\n      })\n    )\n\n    this.translater = new Translater(this.content)\n\n    this.animater = createAnimater(this.content, this.translater, this.options)\n\n    this.actionsHandler = new ActionsHandler(\n      this.options.bindToTarget ? this.content : wrapper,\n      createActionsHandlerOptions(this.options)\n    )\n\n    this.actions = new ScrollerActions(\n      this.scrollBehaviorX,\n      this.scrollBehaviorY,\n      this.actionsHandler,\n      this.animater,\n      this.options\n    )\n\n    const resizeHandler = this.resize.bind(this)\n    this.resizeRegister = new EventRegister(window, [\n      {\n        name: 'orientationchange',\n        handler: resizeHandler,\n      },\n      {\n        name: 'resize',\n        handler: resizeHandler,\n      },\n    ])\n\n    this.registerTransitionEnd()\n\n    this.init()\n  }\n\n  private init() {\n    this.bindTranslater()\n    this.bindAnimater()\n    this.bindActions()\n    // enable pointer events when scrolling ends\n    this.hooks.on(this.hooks.eventTypes.scrollEnd, () => {\n      this.togglePointerEvents(true)\n    })\n  }\n\n  private registerTransitionEnd() {\n    this.transitionEndRegister = new EventRegister(this.content, [\n      {\n        name: style.transitionEnd,\n        handler: this.transitionEnd.bind(this),\n      },\n    ])\n  }\n\n  private bindTranslater() {\n    const hooks = this.translater.hooks\n    hooks.on(hooks.eventTypes.beforeTranslate, (transformStyle: string[]) => {\n      if (this.options.translateZ) {\n        transformStyle.push(this.options.translateZ)\n      }\n    })\n    // disable pointer events when scrolling\n    hooks.on(hooks.eventTypes.translate, (pos: TranslaterPoint) => {\n      const prevPos = this.getCurrentPos()\n      this.updatePositions(pos)\n      // scrollEnd will dispatch when scroll is force stopping in touchstart handler\n      // so in touchend handler, don't toggle pointer-events\n      if (this.actions.ensuringInteger === true) {\n        this.actions.ensuringInteger = false\n        return\n      }\n      // a valid translate\n      if (pos.x !== prevPos.x || pos.y !== prevPos.y) {\n        this.togglePointerEvents(false)\n      }\n    })\n  }\n\n  private bindAnimater() {\n    // reset position\n    this.animater.hooks.on(\n      this.animater.hooks.eventTypes.end,\n      (pos: TranslaterPoint) => {\n        if (!this.resetPosition(this.options.bounceTime)) {\n          this.animater.setPending(false)\n          this.hooks.trigger(this.hooks.eventTypes.scrollEnd, pos)\n        }\n      }\n    )\n\n    bubbling(this.animater.hooks, this.hooks, [\n      {\n        source: this.animater.hooks.eventTypes.move,\n        target: this.hooks.eventTypes.scroll,\n      },\n      {\n        source: this.animater.hooks.eventTypes.forceStop,\n        target: this.hooks.eventTypes.scrollEnd,\n      },\n    ])\n  }\n\n  private bindActions() {\n    const actions = this.actions\n\n    bubbling(actions.hooks, this.hooks, [\n      {\n        source: actions.hooks.eventTypes.start,\n        target: this.hooks.eventTypes.beforeStart,\n      },\n      {\n        source: actions.hooks.eventTypes.start,\n        target: this.hooks.eventTypes.beforeScrollStart, // just for event api\n      },\n      {\n        source: actions.hooks.eventTypes.beforeMove,\n        target: this.hooks.eventTypes.beforeMove,\n      },\n      {\n        source: actions.hooks.eventTypes.scrollStart,\n        target: this.hooks.eventTypes.scrollStart,\n      },\n      {\n        source: actions.hooks.eventTypes.scroll,\n        target: this.hooks.eventTypes.scroll,\n      },\n      {\n        source: actions.hooks.eventTypes.beforeEnd,\n        target: this.hooks.eventTypes.beforeEnd,\n      },\n    ])\n\n    actions.hooks.on(\n      actions.hooks.eventTypes.end,\n      (e: TouchEvent, pos: TranslaterPoint) => {\n        this.hooks.trigger(this.hooks.eventTypes.touchEnd, pos)\n\n        if (this.hooks.trigger(this.hooks.eventTypes.end, pos)) {\n          return true\n        }\n\n        // check if it is a click operation\n        if (!actions.fingerMoved) {\n          this.hooks.trigger(this.hooks.eventTypes.scrollCancel)\n          if (this.checkClick(e)) {\n            return true\n          }\n        }\n        // reset if we are outside of the boundaries\n        if (this.resetPosition(this.options.bounceTime, ease.bounce)) {\n          this.animater.setForceStopped(false)\n          return true\n        }\n      }\n    )\n\n    actions.hooks.on(\n      actions.hooks.eventTypes.scrollEnd,\n      (pos: TranslaterPoint, duration: number) => {\n        const deltaX = Math.abs(pos.x - this.scrollBehaviorX.startPos)\n        const deltaY = Math.abs(pos.y - this.scrollBehaviorY.startPos)\n\n        if (this.checkFlick(duration, deltaX, deltaY)) {\n          this.animater.setForceStopped(false)\n          this.hooks.trigger(this.hooks.eventTypes.flick)\n          return\n        }\n\n        if (this.momentum(pos, duration)) {\n          this.animater.setForceStopped(false)\n          return\n        }\n\n        if (actions.contentMoved) {\n          this.hooks.trigger(this.hooks.eventTypes.scrollEnd, pos)\n        }\n        if (this.animater.forceStopped) {\n          this.animater.setForceStopped(false)\n        }\n      }\n    )\n  }\n\n  private checkFlick(duration: number, deltaX: number, deltaY: number) {\n    const flickMinMovingDistance = 1 // distinguish flick from click\n    if (\n      this.hooks.events.flick.length > 1 &&\n      duration < this.options.flickLimitTime &&\n      deltaX < this.options.flickLimitDistance &&\n      deltaY < this.options.flickLimitDistance &&\n      (deltaY > flickMinMovingDistance || deltaX > flickMinMovingDistance)\n    ) {\n      return true\n    }\n  }\n\n  private momentum(pos: TranslaterPoint, duration: number) {\n    const meta = {\n      time: 0,\n      easing: ease.swiper,\n      newX: pos.x,\n      newY: pos.y,\n    }\n    // start momentum animation if needed\n    const momentumX = this.scrollBehaviorX.end(duration)\n    const momentumY = this.scrollBehaviorY.end(duration)\n\n    meta.newX = isUndef(momentumX.destination)\n      ? meta.newX\n      : (momentumX.destination as number)\n    meta.newY = isUndef(momentumY.destination)\n      ? meta.newY\n      : (momentumY.destination as number)\n    meta.time = Math.max(\n      momentumX.duration as number,\n      momentumY.duration as number\n    )\n\n    this.hooks.trigger(this.hooks.eventTypes.momentum, meta, this)\n    // when x or y changed, do momentum animation now!\n    if (meta.newX !== pos.x || meta.newY !== pos.y) {\n      // change easing function when scroller goes out of the boundaries\n      if (\n        meta.newX > this.scrollBehaviorX.minScrollPos ||\n        meta.newX < this.scrollBehaviorX.maxScrollPos ||\n        meta.newY > this.scrollBehaviorY.minScrollPos ||\n        meta.newY < this.scrollBehaviorY.maxScrollPos\n      ) {\n        meta.easing = ease.swipeBounce\n      }\n      this.scrollTo(meta.newX, meta.newY, meta.time, meta.easing)\n      return true\n    }\n  }\n\n  private checkClick(e: TouchEvent) {\n    const cancelable = {\n      preventClick: this.animater.forceStopped,\n    }\n    // we scrolled less than momentumLimitDistance pixels\n    if (this.hooks.trigger(this.hooks.eventTypes.checkClick)) {\n      this.animater.setForceStopped(false)\n      return true\n    }\n    if (!cancelable.preventClick) {\n      const _dblclick = this.options.dblclick\n      let dblclickTrigged = false\n      if (_dblclick && this.lastClickTime) {\n        const { delay = 300 } = _dblclick as any\n        if (getNow() - this.lastClickTime < delay) {\n          dblclickTrigged = true\n          dblclick(e)\n        }\n      }\n      if (this.options.tap) {\n        tap(e, this.options.tap)\n      }\n      if (\n        this.options.click &&\n        !preventDefaultExceptionFn(\n          e.target,\n          this.options.preventDefaultException\n        )\n      ) {\n        click(e)\n      }\n      this.lastClickTime = dblclickTrigged ? null : getNow()\n      return true\n    }\n    return false\n  }\n\n  private resize() {\n    if (!this.actions.enabled) {\n      return\n    }\n\n    // fix a scroll problem under Android condition\n    /* istanbul ignore if  */\n    if (isAndroid) {\n      this.wrapper.scrollTop = 0\n    }\n    clearTimeout(this.resizeTimeout)\n    this.resizeTimeout = window.setTimeout(() => {\n      this.hooks.trigger(this.hooks.eventTypes.resize)\n    }, this.options.resizePolling)\n  }\n\n  /* istanbul ignore next */\n  private transitionEnd(e: TouchEvent) {\n    if (e.target !== this.content || !this.animater.pending) {\n      return\n    }\n    const animater = this.animater as Transition\n    animater.transitionTime()\n\n    if (!this.resetPosition(this.options.bounceTime, ease.bounce)) {\n      this.animater.setPending(false)\n      if (this.options.probeType !== Probe.Realtime) {\n        this.hooks.trigger(\n          this.hooks.eventTypes.scrollEnd,\n          this.getCurrentPos()\n        )\n      }\n    }\n  }\n\n  togglePointerEvents(enabled = true) {\n    let el = this.content.children.length\n      ? this.content.children\n      : [this.content]\n    let pointerEvents = enabled ? 'auto' : 'none'\n    for (let i = 0; i < el.length; i++) {\n      let node = el[i] as MountedBScrollHTMLElement\n      // ignore BetterScroll instance's wrapper DOM\n      /* istanbul ignore if  */\n      if (node.isBScrollContainer) {\n        continue\n      }\n      node.style.pointerEvents = pointerEvents\n    }\n  }\n\n  refresh(content: HTMLElement) {\n    const contentChanged = this.setContent(content)\n    this.hooks.trigger(this.hooks.eventTypes.beforeRefresh)\n    this.scrollBehaviorX.refresh(content)\n    this.scrollBehaviorY.refresh(content)\n\n    if (contentChanged) {\n      this.translater.setContent(content)\n      this.animater.setContent(content)\n\n      this.transitionEndRegister.destroy()\n      this.registerTransitionEnd()\n\n      if (this.options.bindToTarget) {\n        this.actionsHandler.setContent(content)\n      }\n    }\n\n    this.actions.refresh()\n    this.wrapperOffset = offset(this.wrapper)\n  }\n\n  private setContent(content: HTMLElement): boolean {\n    const contentChanged = content !== this.content\n    if (contentChanged) {\n      this.content = content\n    }\n    return contentChanged\n  }\n\n  scrollBy(deltaX: number, deltaY: number, time = 0, easing?: EaseItem) {\n    const { x, y } = this.getCurrentPos()\n    easing = !easing ? ease.bounce : easing\n    deltaX += x\n    deltaY += y\n\n    this.scrollTo(deltaX, deltaY, time, easing)\n  }\n\n  scrollTo(\n    x: number,\n    y: number,\n    time = 0,\n    easing = ease.bounce,\n    extraTransform = {\n      start: {},\n      end: {},\n    }\n  ) {\n    const easingFn = this.options.useTransition ? easing.style : easing.fn\n    const currentPos = this.getCurrentPos()\n\n    const startPoint = {\n      x: currentPos.x,\n      y: currentPos.y,\n      ...extraTransform.start,\n    }\n    const endPoint = {\n      x,\n      y,\n      ...extraTransform.end,\n    }\n\n    this.hooks.trigger(this.hooks.eventTypes.scrollTo, endPoint)\n\n    // it is an useless move\n    if (isSamePoint(startPoint, endPoint)) return\n\n    const deltaX = Math.abs(endPoint.x - startPoint.x)\n    const deltaY = Math.abs(endPoint.y - startPoint.y)\n\n    // considering of browser compatibility for decimal transform value\n    // force translating immediately\n    if (deltaX < MIN_SCROLL_DISTANCE && deltaY < MIN_SCROLL_DISTANCE) {\n      time = 0\n      this.hooks.trigger(this.hooks.eventTypes.minDistanceScroll)\n    }\n    this.animater.move(startPoint, endPoint, time, easingFn)\n  }\n\n  scrollToElement(\n    el: HTMLElement | string,\n    time: number,\n    offsetX: number | boolean,\n    offsetY: number | boolean,\n    easing?: EaseItem\n  ) {\n    const targetEle = getElement(el)\n    const pos = offset(targetEle)\n\n    const getOffset = (\n      offset: number | boolean,\n      size: number,\n      wrapperSize: number\n    ) => {\n      if (typeof offset === 'number') {\n        return offset\n      }\n      // if offsetX/Y are true we center the element to the screen\n      return offset ? Math.round(size / 2 - wrapperSize / 2) : 0\n    }\n    offsetX = getOffset(\n      offsetX,\n      targetEle.offsetWidth,\n      this.wrapper.offsetWidth\n    )\n    offsetY = getOffset(\n      offsetY,\n      targetEle.offsetHeight,\n      this.wrapper.offsetHeight\n    )\n\n    const getPos = (\n      pos: number,\n      wrapperPos: number,\n      offset: number,\n      scrollBehavior: Behavior\n    ) => {\n      pos -= wrapperPos\n      pos = scrollBehavior.adjustPosition(pos - offset)\n      return pos\n    }\n\n    pos.left = getPos(\n      pos.left,\n      this.wrapperOffset.left,\n      offsetX,\n      this.scrollBehaviorX\n    )\n    pos.top = getPos(\n      pos.top,\n      this.wrapperOffset.top,\n      offsetY,\n      this.scrollBehaviorY\n    )\n\n    if (\n      this.hooks.trigger(this.hooks.eventTypes.scrollToElement, targetEle, pos)\n    ) {\n      return\n    }\n\n    this.scrollTo(pos.left, pos.top, time, easing)\n  }\n\n  resetPosition(time = 0, easing = ease.bounce) {\n    const {\n      position: x,\n      inBoundary: xInBoundary,\n    } = this.scrollBehaviorX.checkInBoundary()\n    const {\n      position: y,\n      inBoundary: yInBoundary,\n    } = this.scrollBehaviorY.checkInBoundary()\n\n    if (xInBoundary && yInBoundary) {\n      return false\n    }\n\n    /* istanbul ignore if  */\n    if (isIOSBadVersion) {\n      // fix ios 13.4 bouncing\n      // see it in issues 982\n      this.reflow()\n    }\n    // out of boundary\n    this.scrollTo(x, y, time, easing)\n\n    return true\n  }\n\n  /* istanbul ignore next */\n  reflow() {\n    this._reflow = this.content.offsetHeight\n  }\n\n  updatePositions(pos: TranslaterPoint) {\n    this.scrollBehaviorX.updatePosition(pos.x)\n    this.scrollBehaviorY.updatePosition(pos.y)\n  }\n\n  getCurrentPos() {\n    return this.actions.getCurrentPos()\n  }\n\n  enable() {\n    this.actions.enabled = true\n  }\n\n  disable() {\n    cancelAnimationFrame(this.animater.timer)\n    this.actions.enabled = false\n  }\n\n  destroy(this: Scroller) {\n    const keys = [\n      'resizeRegister',\n      'transitionEndRegister',\n      'actionsHandler',\n      'actions',\n      'hooks',\n      'animater',\n      'translater',\n      'scrollBehaviorX',\n      'scrollBehaviorY',\n    ]\n    keys.forEach((key) => this[key].destroy())\n  }\n}\n"
  },
  {
    "path": "packages/core/src/scroller/__mocks__/Actions.ts",
    "content": "import DirectionLock from '../DirectionLock'\n\njest.mock('../DirectionLock')\n\nimport { EventEmitter } from '@better-scroll/shared-utils'\n\nconst ScrollerActions = jest\n  .fn()\n  .mockImplementation(\n    (\n      scrollBehaviorX,\n      scrollBehaviorY,\n      actionsHandler,\n      animater,\n      bscrollOptions\n    ) => {\n      const directionLockAction = new DirectionLock(0, false, '')\n\n      return {\n        options: bscrollOptions,\n        scrollBehaviorX,\n        scrollBehaviorY,\n        actionsHandler,\n        animater,\n        directionLockAction,\n        moved: false,\n        enabled: true,\n        startTime: 0,\n        endTime: 0,\n        ensuringInteger: false,\n        getCurrentPos: jest.fn().mockImplementation(() => {\n          return {\n            x: 0,\n            y: 0,\n          }\n        }),\n        refresh: jest.fn(),\n        destroy: jest.fn(),\n        hooks: new EventEmitter([\n          'start',\n          'beforeMove',\n          'scrollStart',\n          'scroll',\n          'beforeEnd',\n          'end',\n          'scrollEnd',\n          'contentNotMoved',\n          'detectMovingDirection',\n        ]),\n      }\n    }\n  )\n\nexport default ScrollerActions\n"
  },
  {
    "path": "packages/core/src/scroller/__mocks__/Behavior.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Behavior = jest.fn().mockImplementation((content, bscrollOptions) => {\n  return {\n    content,\n    options: bscrollOptions,\n    startPos: 0,\n    currentPos: 0,\n    absStartPos: 0,\n    dist: 0,\n    minScrollPos: 0,\n    maxScrollPos: 0,\n    hasScroll: true,\n    direction: 0,\n    movingDirection: 0,\n    relativeOffset: 0,\n    wrapperSize: 0,\n    contentSize: 0,\n    hooks: new EventEmitter([\n      'momentum',\n      'end',\n      'beforeComputeBoundary',\n      'computeBoundary',\n      'ignoreHasScroll',\n    ]),\n    start: jest.fn(),\n    move: jest.fn(),\n    end: jest.fn(),\n    updateDirection: jest.fn(),\n    refresh: jest.fn(),\n    updatePosition: jest.fn(),\n    getCurrentPos: jest.fn().mockImplementation(() => {\n      return 0\n    }),\n    checkInBoundary: jest.fn().mockImplementation(() => {\n      return {\n        position: 0,\n        inBoundary: false,\n      }\n    }),\n    adjustPosition: jest.fn(),\n    updateStartPos: jest.fn(),\n    updateAbsStartPos: jest.fn(),\n    resetStartPos: jest.fn(),\n    getAbsDist: jest.fn().mockImplementation((delta: number) => {\n      return Math.abs(delta)\n    }),\n    destroy: jest.fn(),\n    computeBoundary: jest.fn(),\n    setMovingDirection: jest.fn(),\n    setDirection: jest.fn(),\n    performDampingAlgorithm: jest.fn(),\n  }\n})\n\nexport { Behavior }\n"
  },
  {
    "path": "packages/core/src/scroller/__mocks__/DirectionLock.ts",
    "content": "const DirectionLock = jest\n  .fn()\n  .mockImplementation((content, bscrollOptions) => {\n    return {\n      directionLocked: '',\n      directionLockThreshold: '5',\n      eventPassthrough: '',\n      freeScroll: false,\n      reset: jest.fn(),\n      checkMovingDirection: jest.fn().mockImplementation((ret = true) => {\n        return ret\n      }),\n      adjustDelta: jest\n        .fn()\n        .mockImplementation((deltaX: number = 0, deltaY: number = 0) => {\n          return {\n            deltaX,\n            deltaY,\n          }\n        }),\n    }\n  })\n\nexport default DirectionLock\n"
  },
  {
    "path": "packages/core/src/scroller/__mocks__/Scroller.ts",
    "content": "import createAnimater from '../../animater'\nimport Translater from '../../translater'\nimport { Behavior } from '../Behavior'\nimport ActionsHandler from '../../base/ActionsHandler'\nimport Actions from '../Actions'\n\njest.mock('../../animater')\njest.mock('../../translater')\njest.mock('../Behavior')\njest.mock('../../base/ActionsHandler')\njest.mock('../Actions')\n\nimport { EventEmitter, EventRegister } from '@better-scroll/shared-utils'\n\nconst Scroller = jest.fn().mockImplementation((wrapper, bscrollOptions) => {\n  const content = wrapper.children[0]\n  const translater = new Translater(content)\n  const animater = createAnimater(content, translater, bscrollOptions)\n  const actionsHandler = new ActionsHandler(wrapper, bscrollOptions)\n  const scrollBehaviorX = new Behavior(\n    wrapper,\n    content,\n    Object.assign(bscrollOptions, { scrollable: bscrollOptions.scrollX })\n  )\n  const scrollBehaviorY = new Behavior(\n    wrapper,\n    content,\n    Object.assign(bscrollOptions, { scrollable: bscrollOptions.scrollY })\n  )\n  const actions = new Actions(\n    scrollBehaviorX,\n    scrollBehaviorY,\n    actionsHandler,\n    animater,\n    bscrollOptions\n  )\n  return {\n    wrapper,\n    content,\n    options: bscrollOptions,\n    translater,\n    animater,\n    actionsHandler,\n    actions,\n    hooks: new EventEmitter([\n      'beforeStart',\n      'beforeMove',\n      'beforeScrollStart',\n      'scrollStart',\n      'scroll',\n      'beforeEnd',\n      'scrollEnd',\n      'resize',\n      'touchEnd',\n      'end',\n      'flick',\n      'scrollCancel',\n      'momentum',\n      'scrollTo',\n      'minDistanceScroll',\n      'scrollToElement',\n      'transitionEnd',\n      'checkClick',\n      'beforeRefresh',\n    ]),\n    scrollBehaviorX,\n    scrollBehaviorY,\n    resizeRegister: new EventRegister(wrapper, []),\n    transitionEndRegister: new EventRegister(wrapper, []),\n    scrollTo: jest.fn(),\n    resetPosition: jest.fn(),\n    togglePointerEvents: jest.fn(),\n    reflow: jest.fn(),\n  }\n})\n\nexport default Scroller\n"
  },
  {
    "path": "packages/core/src/scroller/__tests__/Actions.spec.ts",
    "content": "import { Behavior } from '../Behavior'\nimport createAnimater from '../../animater'\nimport Translater from '../../translater'\nimport { OptionsConstructor } from '../../Options'\nimport ActionsHandler from '../../base/ActionsHandler'\nimport DirectionLockAction from '../DirectionLock'\n\njest.mock('../Behavior')\njest.mock('../../animater')\njest.mock('../../translater')\njest.mock('../../Options')\njest.mock('../../base/ActionsHandler')\njest.mock('../DirectionLock')\n\nimport Actions from '../Actions'\n\ndescribe('Actions Class tests', () => {\n  let actions: Actions\n  beforeEach(() => {\n    // redefine window.performance\n    // because we will use window.performance.timing.navigationStart\n    // in our file('src/util/lang.ts')\n    Object.defineProperty(window, 'performance', {\n      get() {\n        return undefined\n      },\n    })\n\n    let content = document.createElement('div')\n    let wrapper = document.createElement('div')\n    let bscrollOptions = new OptionsConstructor() as any\n    let scrollBehaviorX = new Behavior(wrapper, content, bscrollOptions)\n    let scrollBehaviorY = new Behavior(wrapper, content, bscrollOptions)\n    let actionsHandler = new ActionsHandler(wrapper, bscrollOptions)\n    let translater = new Translater(content)\n    let animater = createAnimater(content, translater, bscrollOptions)\n    actions = new Actions(\n      scrollBehaviorX,\n      scrollBehaviorY,\n      actionsHandler,\n      animater,\n      bscrollOptions\n    )\n  })\n\n  it('should init hooks when call constructor function', () => {\n    expect(actions.hooks.eventTypes).toHaveProperty('start')\n    expect(actions.hooks.eventTypes).toHaveProperty('beforeMove')\n    expect(actions.hooks.eventTypes).toHaveProperty('scroll')\n    expect(actions.hooks.eventTypes).toHaveProperty('beforeEnd')\n    expect(actions.hooks.eventTypes).toHaveProperty('end')\n    expect(actions.hooks.eventTypes).toHaveProperty('scrollEnd')\n  })\n\n  it('should invoke handleStart when actionsHandler trigger start hook', () => {\n    actions.actionsHandler.hooks.trigger('start')\n\n    expect(actions.fingerMoved).toBe(false)\n    expect(actions.scrollBehaviorX.start).toBeCalled()\n    expect(actions.scrollBehaviorY.start).toBeCalled()\n    expect(actions.scrollBehaviorX.resetStartPos).toBeCalled()\n    expect(actions.scrollBehaviorY.resetStartPos).toBeCalled()\n    expect(actions.animater.doStop).toBeCalled()\n  })\n\n  it('should invoke handleMove when actionsHandler trigger move hook', () => {\n    let e = new Event('touchmove')\n    let beforeMoveMockHandler = jest.fn()\n    let scrollStartHandler = jest.fn()\n    let scrollHandler = jest.fn()\n    // cancelable beforeMove hook\n    actions.hooks.on(\n      actions.hooks.eventTypes.beforeMove,\n      jest.fn().mockImplementationOnce(() => true)\n    )\n    actions.hooks.on(actions.hooks.eventTypes.beforeMove, beforeMoveMockHandler)\n    actions.hooks.on(actions.hooks.eventTypes.scrollStart, scrollStartHandler)\n    actions.hooks.on(actions.hooks.eventTypes.scroll, scrollHandler)\n\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n\n    expect(beforeMoveMockHandler).toHaveBeenCalledTimes(0)\n\n    // moved less than 15 px\n    actions.endTime = Date.now() - 400\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: 10,\n      e,\n    })\n\n    expect(beforeMoveMockHandler).toHaveBeenCalledTimes(1)\n    expect(actions.directionLockAction.checkMovingDirection).not.toBeCalled()\n\n    // lock direction\n    actions.endTime = Date.now()\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 10,\n      deltaY: 0,\n      e,\n    })\n    expect(beforeMoveMockHandler).toHaveBeenCalledTimes(2)\n    expect(actions.actionsHandler.setInitiated).toBeCalled()\n\n    actions.startTime = Date.now() - 400\n    actions.options.probeType = 1\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n    expect(beforeMoveMockHandler).toHaveBeenCalledTimes(3)\n    expect(scrollStartHandler).toBeCalled()\n    expect(scrollHandler).toBeCalledTimes(1)\n    expect(actions.scrollBehaviorY.getAbsDist).toHaveBeenCalledWith(-20)\n    expect(actions.scrollBehaviorY.move).toHaveBeenCalledWith(-20)\n\n    actions.startTime = Date.now()\n    actions.options.probeType = 3\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n    expect(scrollHandler).toBeCalledTimes(2)\n\n    const cbMock = jest.fn().mockImplementationOnce(() => true)\n    actions.fingerMoved = true\n    actions.hooks.on(actions.hooks.eventTypes.detectMovingDirection, cbMock)\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n\n    expect(cbMock).toBeCalled()\n    expect(actions.fingerMoved).toBe(true)\n\n    // content not moved\n    const mockFn = jest.fn()\n    actions.contentMoved = false\n    actions.hooks.on(actions.hooks.eventTypes.contentNotMoved, mockFn)\n    actions.startTime = Date.now() - 400\n    actions.scrollBehaviorX.move = jest.fn().mockImplementation(() => 0)\n    actions.scrollBehaviorY.move = jest.fn().mockImplementation(() => 0)\n\n    actions.endTime = Date.now() + 400\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: 0,\n      e,\n    })\n    expect(mockFn).toBeCalled()\n  })\n\n  it('should invoke handleEnd when actionsHandler trigger end hook', () => {\n    let beforeEndMockHandler = jest.fn()\n    let endMockHandler = jest.fn()\n    let scrollEndHandler = jest.fn()\n    let e = new Event('touchend')\n    // cancelable beforeEnd hook\n    actions.hooks.on(\n      actions.hooks.eventTypes.beforeEnd,\n      jest.fn().mockImplementationOnce(() => true)\n    )\n    // cancelable end hook\n    actions.hooks.on(\n      actions.hooks.eventTypes.end,\n      jest.fn().mockImplementationOnce(() => true)\n    )\n    actions.hooks.on(actions.hooks.eventTypes.beforeEnd, beforeEndMockHandler)\n    actions.hooks.on(actions.hooks.eventTypes.end, endMockHandler)\n    actions.hooks.on(actions.hooks.eventTypes.scrollEnd, scrollEndHandler)\n\n    actions.actionsHandler.hooks.trigger(\n      actions.actionsHandler.hooks.eventTypes.end,\n      e\n    )\n    expect(beforeEndMockHandler).not.toBeCalled()\n    expect(actions.scrollBehaviorX.updateDirection).not.toBeCalled()\n\n    actions.actionsHandler.hooks.trigger(\n      actions.actionsHandler.hooks.eventTypes.end,\n      e\n    )\n\n    expect(beforeEndMockHandler).toHaveBeenCalledTimes(1)\n    expect(actions.scrollBehaviorX.updateDirection).toHaveBeenCalledTimes(1)\n    expect(actions.scrollBehaviorY.updateDirection).toHaveBeenCalledTimes(1)\n    expect(endMockHandler).not.toBeCalled()\n\n    actions.actionsHandler.hooks.trigger(\n      actions.actionsHandler.hooks.eventTypes.end,\n      e\n    )\n    expect(scrollEndHandler).toBeCalled()\n  })\n\n  it('should get correct position when invoking getCurrentPos method', () => {\n    actions.getCurrentPos()\n    expect(actions.scrollBehaviorX.getCurrentPos).toBeCalled()\n    expect(actions.scrollBehaviorY.getCurrentPos).toBeCalled()\n  })\n\n  it('should reset endTime when refreshed', () => {\n    actions.refresh()\n\n    expect(actions.endTime).toBe(0)\n  })\n\n  it('destroy()', () => {\n    actions.destroy()\n\n    expect(actions.hooks.events).toEqual({})\n    expect(actions.hooks.eventTypes).toEqual({})\n  })\n\n  it('should can be disabled', () => {\n    actions.enabled = false\n    const actionsHandler = actions.actionsHandler\n\n    actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.start)\n    expect(actions.directionLockAction.reset).not.toBeCalled()\n\n    actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.move, {\n      deltaX: 0,\n      deltaY: 0,\n    })\n    expect(actions.scrollBehaviorX.getAbsDist).not.toBeCalled()\n\n    actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.end)\n    expect(actions.scrollBehaviorX.updateDirection).not.toBeCalled()\n  })\n\n  it('should prevent native click event', () => {\n    actions.actionsHandler.hooks.trigger(\n      actions.actionsHandler.hooks.eventTypes.click,\n      {\n        _constructed: false,\n        target: document.createElement('div'),\n        preventDefault() {},\n        stopPropagation() {},\n      }\n    )\n  })\n\n  it('apply quadrant transformation when force rotating by CSS', () => {\n    let e = new Event('touchmove')\n\n    // second quadrant\n    actions.options.quadrant = 2\n\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n\n    expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(-20)\n    expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(-0)\n\n    // third quadrant\n    actions.options.quadrant = 3\n\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: -20,\n      deltaY: 0,\n      e,\n    })\n\n    expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(20)\n    expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(-0)\n\n    // forth quadrant\n    actions.options.quadrant = 4\n\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 20,\n      deltaY: 0,\n      e,\n    })\n\n    expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(-0)\n    expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(20)\n  })\n\n  it('coordinateTransformation hook', () => {\n    let e = new Event('touchmove')\n    const mockFn = jest.fn()\n    actions.hooks.on(actions.hooks.eventTypes.coordinateTransformation, mockFn)\n    actions.actionsHandler.hooks.trigger('move', {\n      deltaX: 0,\n      deltaY: -20,\n      e,\n    })\n\n    expect(mockFn).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/core/src/scroller/__tests__/Behavior.spec.ts",
    "content": "import { Behavior } from '../Behavior'\nimport { createDiv } from '../../__tests__/__utils__/layout'\n\ndescribe('Behavior Class tests', () => {\n  let behavior: Behavior\n  let content: HTMLElement\n  let wrapper: HTMLElement\n  let options = {\n    movable: false,\n    scrollable: true,\n    momentum: true,\n    momentumLimitTime: 300,\n    momentumLimitDistance: 15,\n    deceleration: 0.001,\n    swipeBounceTime: 2500,\n    outOfBoundaryDampingFactor: 1 / 3,\n    specifiedIndexAsContent: 0,\n    swipeTime: 2000,\n    bounces: [true, true] as [boolean, boolean],\n    rect: {\n      size: 'height',\n      position: 'top',\n    },\n  }\n  beforeEach(() => {\n    wrapper = createDiv(100, 200, 0, 0)\n    content = createDiv(100, 400, 0, 0)\n    document.body.appendChild(content)\n    wrapper.appendChild(content)\n    behavior = new Behavior(wrapper, content, options)\n  })\n\n  it('should init hooks when call constructor function', () => {\n    expect(behavior.hooks.eventTypes).toHaveProperty('momentum')\n    expect(behavior.hooks.eventTypes).toHaveProperty('end')\n    expect(behavior.currentPos).toBe(0)\n    expect(behavior.startPos).toBe(0)\n    expect(behavior.content).toEqual(content)\n  })\n\n  it('should refresh some properties when invoking refresh method', () => {\n    behavior.refresh(behavior.content)\n\n    expect(behavior.wrapperSize).toBe(200)\n    expect(behavior.contentSize).toBe(400)\n    expect(behavior.relativeOffset).toBe(0)\n    expect(behavior.minScrollPos).toBe(-0)\n    expect(behavior.maxScrollPos).toBe(-200)\n    expect(behavior.hasScroll).toBe(true)\n    expect(behavior.direction).toBe(0)\n  })\n\n  it('should refresh some properties when invoking start method', () => {\n    behavior.start()\n    expect(behavior.direction).toBe(0)\n    expect(behavior.movingDirection).toBe(0)\n    expect(behavior.dist).toBe(0)\n  })\n\n  it('should refresh some properties when invoking move method', () => {\n    behavior.refresh(behavior.content)\n    expect(behavior.move(-10)).toBe(-10)\n    expect(behavior.movingDirection).toBe(1)\n  })\n\n  it('should not trigger momentum scroll when duration is exceed momentumLimitTime', () => {\n    let endMockHandler = jest.fn()\n    behavior.hooks.on('end', endMockHandler)\n    behavior.refresh(behavior.content)\n    behavior.end(400)\n    expect(endMockHandler).toBeCalled()\n    expect(endMockHandler).toHaveBeenCalledWith({\n      duration: 0,\n    })\n  })\n\n  it('should trigger momentum scroll', () => {\n    behavior.refresh(behavior.content)\n    behavior.currentPos = -100\n\n    expect(behavior.end(100)).toEqual({\n      destination: -200,\n      duration: 2500,\n      rate: 15,\n    })\n    behavior.hooks.on(\n      behavior.hooks.eventTypes.momentum,\n      (momentumData: any) => {\n        momentumData.destination = 200\n      }\n    )\n    expect(behavior.end(100)).toEqual({\n      destination: -0,\n      duration: 2500,\n      rate: 15,\n    })\n  })\n\n  it('should keep direction unchanged when invoking updateDirection method', () => {\n    behavior.updateDirection()\n    expect(behavior.direction).toBe(0)\n  })\n\n  it('should update position when invoking updatePosition method', () => {\n    behavior.updatePosition(100)\n    expect(behavior.currentPos).toBe(100)\n  })\n\n  it('should auto bouncing within boundary when out of boundary', () => {\n    behavior.refresh(behavior.content)\n    behavior.updatePosition(-400)\n    expect(behavior.checkInBoundary()).toEqual({\n      position: -200,\n      inBoundary: false,\n    })\n  })\n\n  it('performDampingAlgorithm()', () => {\n    const ret = behavior.performDampingAlgorithm(20, 0.1)\n    expect(ret).toBe(2)\n\n    behavior.options.bounces = [false, false]\n    // simulate out of the boundaries and no bounce\n    const ret2 = behavior.performDampingAlgorithm(20, 0.1)\n    expect(ret2).toBe(-0)\n  })\n\n  it('getAbsDist()', () => {\n    const ret = behavior.getAbsDist(-20)\n    expect(ret).toBe(20)\n  })\n\n  it('adjustPosition()', () => {\n    const ret = behavior.adjustPosition(20.1)\n    expect(ret).toBe(-0)\n  })\n\n  it('computeBoundary()', () => {\n    behavior.hooks.on(\n      behavior.hooks.eventTypes.computeBoundary,\n      (boundary: { minScrollPos: number; maxScrollPos: number }) => {\n        boundary.minScrollPos = 20\n        boundary.maxScrollPos = 30\n      }\n    )\n    behavior.computeBoundary()\n\n    expect(behavior.maxScrollPos).toEqual(behavior.minScrollPos)\n  })\n})\n"
  },
  {
    "path": "packages/core/src/scroller/__tests__/DirectionLock.spec.ts",
    "content": "import DirectionLock from '../DirectionLock'\nimport {\n  Direction,\n  DirectionLock as DirectionLockEnum,\n} from '@better-scroll/shared-utils'\ndescribe('DirectionLock Class tests', () => {\n  let directionLock: DirectionLock\n  let e = new Event('touchstart') as any\n\n  beforeEach(() => {\n    directionLock = new DirectionLock(5, false, '')\n  })\n\n  it('should call reset when call constructor function', () => {\n    expect(directionLock.directionLocked).toBe('')\n  })\n\n  it('should lock vertically when scrolled in direction Y', () => {\n    directionLock.checkMovingDirection(0, 20, e)\n\n    expect(directionLock.directionLocked).toBe('vertical')\n  })\n\n  it('should lock horizontally when scrolled in direction X', () => {\n    directionLock.checkMovingDirection(20, 0, e)\n\n    expect(directionLock.directionLocked).toBe('horizontal')\n  })\n\n  it('should no lock when freeScroll is true', () => {\n    directionLock.freeScroll = true\n    directionLock.checkMovingDirection(20, 20, e)\n\n    expect(directionLock.directionLocked).toBe('')\n  })\n\n  it('adjustDelta() ', () => {\n    directionLock.directionLocked = DirectionLockEnum.Horizontal\n    const ret1 = directionLock.adjustDelta(20, 20)\n    expect(ret1).toMatchObject({\n      deltaX: 20,\n      deltaY: 0,\n    })\n\n    directionLock.directionLocked = DirectionLockEnum.Vertical\n    const ret2 = directionLock.adjustDelta(20, 20)\n    expect(ret2).toMatchObject({\n      deltaX: 0,\n      deltaY: 20,\n    })\n  })\n\n  it('reset()', () => {\n    directionLock.directionLocked = DirectionLockEnum.Vertical\n    directionLock.reset()\n    expect(directionLock.directionLocked).toBe(DirectionLockEnum.Default)\n  })\n\n  it('checkMovingDirection()', () => {\n    directionLock.directionLocked = DirectionLockEnum.Horizontal\n    directionLock.eventPassthrough = DirectionLockEnum.Horizontal\n    const ret1 = directionLock.checkMovingDirection(20, 20, {\n      preventDefault() {\n        return true\n      },\n    } as any)\n    expect(ret1).toBe(true)\n\n    directionLock.directionLocked = DirectionLockEnum.Horizontal\n    directionLock.eventPassthrough = DirectionLockEnum.Vertical\n    const ret2 = directionLock.checkMovingDirection(20, 20, {\n      preventDefault() {\n        return true\n      },\n    } as any)\n    expect(ret2).toBe(false)\n\n    // no locked\n    directionLock.directionLocked = DirectionLockEnum.Default\n    const ret3 = directionLock.checkMovingDirection(20, 20, {\n      preventDefault() {\n        return true\n      },\n    } as any)\n    expect(ret3).toBe(false)\n  })\n})\n"
  },
  {
    "path": "packages/core/src/scroller/__tests__/Scroller.spec.ts",
    "content": "import { Behavior } from '../Behavior'\nimport createAnimater from '../../animater'\nimport Translater from '../../translater'\nimport { OptionsConstructor } from '../../Options'\nimport ActionsHandler from '../../base/ActionsHandler'\nimport Actions from '../Actions'\n\njest.mock('../Behavior')\njest.mock('../../animater')\njest.mock('../../translater')\njest.mock('../../Options')\njest.mock('../../base/ActionsHandler')\njest.mock('../Actions')\n\nimport Scroller from '../Scroller'\nimport { createDiv } from '../../__tests__/__utils__/layout'\n\ndescribe('Scroller Class tests', () => {\n  let scroller: Scroller\n  let wrapper: HTMLElement\n  let content: HTMLElement\n\n  beforeEach(() => {\n    // redefine window.performance\n    // because we will use window.performance.timing.navigationStart\n    // in our file('src/util/lang.ts')\n    Object.defineProperty(window, 'performance', {\n      get() {\n        return undefined\n      },\n    })\n    wrapper = createDiv(100, 200, 0, 0)\n    content = createDiv(100, 400, 0, 0)\n    document.body.appendChild(content)\n    wrapper.appendChild(content)\n    let bscrollOptions = new OptionsConstructor() as any\n\n    scroller = new Scroller(wrapper, content, bscrollOptions)\n  })\n\n  it('should init hooks when call constructor function', () => {\n    ;[\n      'beforeStart',\n      'beforeMove',\n      'beforeScrollStart',\n      'scrollStart',\n      'scroll',\n      'beforeEnd',\n      'scrollEnd',\n      'resize',\n      'beforeRefresh',\n      'touchEnd',\n      'flick',\n      'scrollCancel',\n      'momentum',\n      'scrollTo',\n      'scrollToElement',\n      'minDistanceScroll',\n    ].forEach((key) => {\n      expect(scroller.hooks.eventTypes).toHaveProperty(key)\n    })\n  })\n\n  describe('bindTranslater', () => {\n    it('should bind beforeTranslate hook', () => {\n      let transform: string[] = []\n      scroller.translater.hooks.trigger('beforeTranslate', transform)\n\n      expect(transform).toContain(' translateZ(0)')\n    })\n\n    it('should bind translate hook', () => {\n      scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {\n        return {\n          x: 0,\n          y: 0,\n        }\n      })\n      scroller.translater.hooks.trigger('translate', { x: 0, y: -20 })\n\n      expect(scroller.scrollBehaviorX.updatePosition).toBeCalled()\n      expect(scroller.scrollBehaviorY.updatePosition).toBeCalled()\n\n      expect(scroller.scrollBehaviorX.updatePosition).toHaveBeenCalledWith(0)\n      expect(scroller.scrollBehaviorY.updatePosition).toHaveBeenCalledWith(-20)\n    })\n  })\n\n  describe('bindAnimater', () => {\n    it('should bind end hook ', () => {\n      let pos = {\n        x: 0,\n        y: 20,\n      }\n      let scrollEndMockHandler = jest.fn()\n      scroller.hooks.on('scrollEnd', scrollEndMockHandler)\n      scroller.scrollBehaviorX.checkInBoundary = jest\n        .fn()\n        .mockImplementation(() => {\n          return {\n            position: 0,\n            inBoundary: true,\n          }\n        })\n      scroller.scrollBehaviorY.checkInBoundary = jest\n        .fn()\n        .mockImplementation(() => {\n          return {\n            position: 0,\n            inBoundary: true,\n          }\n        })\n      scroller.animater.hooks.trigger('end', pos)\n\n      expect(scroller.animater.setPending).toHaveBeenCalledWith(false)\n      expect(scrollEndMockHandler).toHaveBeenCalledWith({\n        x: 0,\n        y: 20,\n      })\n    })\n\n    it('should bubble hooks', () => {\n      let scrollEndMockHandler = jest.fn()\n      let scrollMockHandler = jest.fn()\n      scroller.hooks.on('scrollEnd', scrollEndMockHandler)\n      scroller.hooks.on('scroll', scrollMockHandler)\n      scroller.animater.hooks.trigger('forceStop')\n      scroller.animater.hooks.trigger('move')\n\n      expect(scrollEndMockHandler).toBeCalled()\n      expect(scrollMockHandler).toBeCalled()\n    })\n  })\n\n  describe('bindActions', () => {\n    it('bind end hook', () => {\n      let touchEndMockHandler = jest.fn()\n      let e = new Event('touch') as any\n      Object.defineProperty(e, 'target', {\n        get() {\n          return scroller.wrapper\n        },\n      })\n      // cancelable scroller end hook\n      scroller.hooks.on(scroller.hooks.eventTypes.touchEnd, touchEndMockHandler)\n      scroller.hooks.trigger(scroller.hooks.eventTypes.touchEnd, { x: 0, y: 0 })\n      scroller.hooks.on(\n        scroller.hooks.eventTypes.end,\n        jest.fn().mockImplementationOnce(() => true)\n      )\n      const ret = scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.end,\n        e,\n        { x: 0, y: 0 }\n      )\n\n      expect(ret).toBe(true)\n      expect(touchEndMockHandler).toBeCalled()\n      expect(touchEndMockHandler).toHaveBeenCalledWith({\n        x: 0,\n        y: 0,\n      })\n\n      /* click operation */\n      // case 1\n\n      scroller.hooks.on(\n        scroller.hooks.eventTypes.checkClick,\n        jest.fn().mockImplementationOnce(() => true)\n      )\n      scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {\n        x: 0,\n        y: 0,\n      })\n\n      expect(scroller.animater.setForceStopped).toBeCalledWith(false)\n\n      // case 2 dblclick\n      const mockFn2 = jest.fn()\n      scroller.options.dblclick = true\n      scroller.lastClickTime = Date.now()\n      scroller.wrapper.addEventListener('dblclick', mockFn2)\n      scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {\n        x: 0,\n        y: 0,\n      })\n      expect(mockFn2).toBeCalled()\n\n      // case 3 tap\n      const mockFn3 = jest.fn()\n      scroller.options.tap = 'tap'\n      scroller.wrapper.addEventListener('tap', mockFn3)\n      scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {\n        x: 0,\n        y: 0,\n      })\n      expect(mockFn3).toBeCalled()\n\n      // case 4 click\n      const mockFn4 = jest.fn()\n      scroller.options.click = true\n      scroller.wrapper.addEventListener('click', mockFn4)\n      scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {\n        x: 0,\n        y: 0,\n      })\n      expect(mockFn4).toBeCalled()\n\n      // case 5 force stopped\n      scroller.animater.forceStopped = true\n      const ret2 = scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.end,\n        e,\n        { x: 0, y: 0 }\n      )\n      expect(ret2).toBe(true)\n    })\n\n    it('bind scrollEnd hook', () => {\n      let momentumMockHandler = jest.fn()\n      let noop = (() => {}) as any\n      scroller.hooks.on('momentum', momentumMockHandler)\n      scroller.scrollBehaviorX.end = jest.fn().mockImplementation(() => {\n        return {\n          duration: 400,\n          destination: 0,\n        }\n      })\n      scroller.scrollBehaviorY.end = jest.fn().mockImplementation(() => {\n        return {\n          duration: 400,\n          destination: -20,\n        }\n      })\n      // flick\n      const mockFn = jest.fn()\n      scroller.hooks.events['flick'] = [noop, noop]\n      scroller.hooks.on(scroller.hooks.eventTypes.flick, mockFn)\n      scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.scrollEnd,\n        { x: 0, y: -20 },\n        50\n      )\n      expect(mockFn).toBeCalled()\n\n      // momentum\n      scroller.hooks.events['flick'] = []\n      scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.scrollEnd,\n        { x: 0, y: -40 },\n        50\n      )\n      expect(scroller.animater.setForceStopped).toBeCalledWith(false)\n\n      // force stop from transition\n      scroller.actions.contentMoved = false\n      scroller.animater.forceStopped = true\n      scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.scrollEnd,\n        { x: 0, y: -20 },\n        50\n      )\n      expect(scroller.animater.setForceStopped).toBeCalledWith(false)\n\n      const mockFn2 = jest.fn()\n      scroller.actions.contentMoved = true\n      scroller.hooks.on(scroller.hooks.eventTypes.scrollEnd, mockFn2)\n      scroller.actions.hooks.trigger(\n        scroller.actions.hooks.eventTypes.scrollEnd,\n        { x: 0, y: -20 },\n        50\n      )\n      expect(mockFn2).toBeCalledWith({\n        x: 0,\n        y: -20,\n      })\n    })\n  })\n\n  it('should invoke resize method when window is resized', () => {\n    jest.useFakeTimers()\n    const mockFn = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.resize, mockFn)\n    const resizeEvent = document.createEvent('Event')\n    resizeEvent.initEvent('resize', true, true)\n    window.dispatchEvent(resizeEvent)\n    jest.advanceTimersByTime(60)\n    jest.clearAllTimers()\n    expect(mockFn).toBeCalledTimes(1)\n\n    // disable scroller\n    scroller.actions.enabled = false\n    resizeEvent.initEvent('resize', true, true)\n    window.dispatchEvent(resizeEvent)\n    expect(mockFn).toBeCalledTimes(1)\n  })\n\n  it('should trigger scrollTo hook when invoking scrollTo method', () => {\n    let scrollToMockHandler = jest.fn()\n    scroller.hooks.on('scrollTo', scrollToMockHandler)\n    scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {\n      return {\n        x: 0,\n        y: 0,\n      }\n    })\n    scroller.scrollTo(0, -20, 800)\n\n    expect(scrollToMockHandler).toBeCalledWith({\n      x: 0,\n      y: -20,\n    })\n    expect(scroller.animater.move).toBeCalledWith(\n      {\n        x: 0,\n        y: 0,\n      },\n      {\n        x: 0,\n        y: -20,\n      },\n      800,\n      'cubic-bezier(0.165, 0.84, 0.44, 1)'\n    )\n  })\n\n  it('scrollToElement()', () => {\n    let scrollToElementMockHandler = jest.fn()\n    scroller.hooks.on(\n      scroller.hooks.eventTypes.scrollToElement,\n      scrollToElementMockHandler\n    )\n\n    scroller.refresh(scroller.content)\n    scroller.scrollBehaviorX.adjustPosition = jest.fn(() => {\n      return 0\n    })\n    scroller.scrollBehaviorY.adjustPosition = jest.fn(() => {\n      return 0\n    })\n    scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {\n      return {\n        x: 0,\n        y: 0,\n      }\n    })\n\n    scroller.scrollToElement(content, 0, false, false)\n    expect(scrollToElementMockHandler).toBeCalled()\n    // to a specified position\n    scroller.scrollToElement(content, 0, 0, 0)\n    expect(scrollToElementMockHandler).toHaveBeenLastCalledWith(content, {\n      left: 0,\n      top: 0,\n    })\n\n    const mockFn2 = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, () => true)\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, mockFn2)\n    scroller.scrollToElement(content, 0, 0, 0)\n\n    expect(mockFn2).not.toBeCalled()\n  })\n\n  it('scrollBy ', () => {\n    const mockFn = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, mockFn)\n    scroller.scrollBy(20, 20)\n    expect(mockFn).toBeCalledWith({\n      x: 20,\n      y: 20,\n    })\n  })\n\n  it('enable() & disable()', () => {\n    scroller.disable()\n    expect(scroller.actions.enabled).toBe(false)\n\n    scroller.enable()\n    expect(scroller.actions.enabled).toBe(true)\n  })\n\n  it('should update postions when invoking updatePositions method', () => {\n    scroller.updatePositions({\n      x: 20,\n      y: -20,\n    })\n\n    expect(scroller.scrollBehaviorX.updatePosition).toHaveBeenCalledWith(20)\n    expect(scroller.scrollBehaviorY.updatePosition).toHaveBeenCalledWith(-20)\n  })\n\n  it('refresh()', () => {\n    scroller.options.bindToTarget = true\n    scroller.refresh(document.createElement('p'))\n\n    expect(scroller.scrollBehaviorX.refresh).toBeCalled()\n    expect(scroller.scrollBehaviorY.refresh).toBeCalled()\n    expect(scroller.actions.refresh).toBeCalled()\n    expect(scroller.actionsHandler.setContent).toBeCalled()\n  })\n\n  it('destroy()', () => {\n    scroller.destroy()\n    const keys = [\n      'actionsHandler',\n      'actions',\n      'animater',\n      'translater',\n      'scrollBehaviorX',\n      'scrollBehaviorY',\n    ]\n    keys.forEach((key) => {\n      expect(scroller[key].destroy).toBeCalled()\n    })\n  })\n\n  it('resetPosition() ', () => {\n    const mockFn = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, mockFn)\n    scroller.resetPosition()\n    expect(mockFn).toBeCalledWith({\n      x: 0,\n      y: 0,\n    })\n  })\n  it('scrollTo()', () => {\n    // minDistanceScroll\n    const mockFn = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.minDistanceScroll, mockFn)\n\n    scroller.scrollTo(0, 0.5, 300)\n  })\n  it('should not toggle pointer-events when casting last position into integer in touchend handlers', () => {\n    scroller.actions.ensuringInteger = true\n    scroller.translater.hooks.trigger('translate', { x: 0, y: -20 })\n    expect(scroller.actions.ensuringInteger).toBe(false)\n  })\n})\n"
  },
  {
    "path": "packages/core/src/scroller/__tests__/createOptions.spec.ts",
    "content": "import {\n  createActionsHandlerOptions,\n  createBehaviorOptions,\n} from '../createOptions'\nimport { OptionsConstructor } from '../../Options'\n\njest.mock('../../Options')\n\ndescribe('createOptions helper function tests', () => {\n  let bsOptions: any\n  beforeEach(() => {\n    bsOptions = new OptionsConstructor()\n  })\n  it('should return correct object when invoking createActionsHandlerOptions function', () => {\n    let ret = createActionsHandlerOptions(bsOptions)\n\n    expect(ret).toEqual({\n      click: false,\n      bindToWrapper: false,\n      disableMouse: true,\n      preventDefault: true,\n      stopPropagation: false,\n      preventDefaultException: {\n        tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,\n      },\n    })\n  })\n\n  it('should return correct object when invoking createBehaviorOptions function', () => {\n    let ret = createBehaviorOptions(bsOptions, 'scrollY', [true, true], {\n      size: 'width',\n      position: 'top',\n    })\n\n    expect(ret).toEqual({\n      momentum: true,\n      momentumLimitTime: 300,\n      momentumLimitDistance: 15,\n      deceleration: 0.0015,\n      swipeBounceTime: 500,\n      swipeTime: 2500,\n      scrollable: true,\n      outOfBoundaryDampingFactor: 1 / 3,\n      specifiedIndexAsContent: 0,\n      bounces: [true, true],\n      rect: {\n        size: 'width',\n        position: 'top',\n      },\n    })\n  })\n})\n"
  },
  {
    "path": "packages/core/src/scroller/createOptions.ts",
    "content": "import { Options as BScrollOptions } from '../Options'\nimport { Options as ActionsHandlerOptions } from '../base/ActionsHandler'\nimport { Options as BehaviorOptions, Bounces, Rect } from './Behavior'\n\nexport function createActionsHandlerOptions(bsOptions: BScrollOptions) {\n  const options = [\n    'click',\n    'bindToWrapper',\n    'disableMouse',\n    'disableTouch',\n    'preventDefault',\n    'stopPropagation',\n    'tagException',\n    'preventDefaultException',\n    'autoEndDistance',\n  ].reduce<ActionsHandlerOptions>((prev, cur) => {\n    prev[cur] = bsOptions[cur]\n    return prev\n  }, {} as ActionsHandlerOptions)\n  return options\n}\n\nexport function createBehaviorOptions(\n  bsOptions: BScrollOptions,\n  extraProp: 'scrollX' | 'scrollY',\n  bounces: Bounces,\n  rect: Rect\n) {\n  const options = [\n    'momentum',\n    'momentumLimitTime',\n    'momentumLimitDistance',\n    'deceleration',\n    'swipeBounceTime',\n    'swipeTime',\n    'outOfBoundaryDampingFactor',\n    'specifiedIndexAsContent',\n  ].reduce<BehaviorOptions>((prev, cur) => {\n    prev[cur] = bsOptions[cur]\n    return prev\n  }, {} as BehaviorOptions)\n  // add extra property\n  options.scrollable = !!bsOptions[extraProp]\n  options.bounces = bounces\n  options.rect = rect\n  return options\n}\n"
  },
  {
    "path": "packages/core/src/translater/__mocks__/index.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Translater = jest.fn().mockImplementation((content) => {\n  return {\n    style: content.style,\n    hooks: new EventEmitter(['beforeTranslate', 'translate']),\n    getComputedPosition: jest\n      .fn()\n      .mockImplementation((position = { x: 0, y: 0 }) => {\n        return position\n      }),\n    translate: jest.fn(),\n    destroy: jest.fn(),\n    setContent: jest.fn(),\n  }\n})\n\nexport default Translater\n"
  },
  {
    "path": "packages/core/src/translater/__tests__/index.spec.ts",
    "content": "import Translater from '../index'\n\ndescribe('Translater Class test suit', () => {\n  let translater: Translater\n  let contentEl = document.createElement('div')\n\n  beforeEach(() => {\n    translater = new Translater(contentEl)\n  })\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('work well when call translate()', () => {\n    const mockFn1 = jest.fn()\n    const mockFn2 = jest.fn()\n    translater.hooks.on(translater.hooks.eventTypes.beforeTranslate, mockFn1)\n    translater.hooks.on(translater.hooks.eventTypes.translate, mockFn2)\n\n    translater.translate({ x: 0, y: 0, dummy: 0 })\n\n    expect(mockFn1).toBeCalled()\n    expect(mockFn1).toBeCalledWith(['translateX(0px)', 'translateY(0px)'], {\n      x: 0,\n      y: 0,\n      dummy: 0,\n    })\n    expect(mockFn2).toBeCalled()\n    expect(mockFn2).toBeCalledWith({ x: 0, y: 0, dummy: 0 })\n    expect(translater.content.style.transform).toBe(\n      'translateX(0px) translateY(0px)'\n    )\n  })\n\n  it('get correct position when call getComputedPosition()', () => {\n    // jsDOM library's getComputedStyle is different from browser\n    window.getComputedStyle = jest.fn().mockImplementation(() => {\n      return {\n        transform: 'matrix(1, 0, 0, 1, 0, 0)',\n      }\n    })\n    const { x, y } = translater.getComputedPosition()\n\n    expect(x).toBe(0)\n    expect(y).toBe(0)\n  })\n\n  it('should clear hooks when destroyed', () => {\n    translater.hooks.on(translater.hooks.eventTypes.beforeTranslate, () => {})\n\n    expect(translater.hooks.eventTypes.beforeTranslate).toBe('beforeTranslate')\n\n    translater.destroy()\n\n    expect(translater.hooks.eventTypes.beforeTranslate).toBeFalsy()\n  })\n})\n"
  },
  {
    "path": "packages/core/src/translater/index.ts",
    "content": "import {\n  style,\n  safeCSSStyleDeclaration,\n  EventEmitter,\n} from '@better-scroll/shared-utils'\nexport interface TranslaterPoint {\n  x: number\n  y: number\n  [key: string]: number\n}\n\ninterface TranslaterMetaData {\n  x: [string, string]\n  y: [string, string]\n  [key: string]: any\n}\nconst translaterMetaData: TranslaterMetaData = {\n  x: ['translateX', 'px'],\n  y: ['translateY', 'px'],\n}\n\nexport default class Translater {\n  content: HTMLElement\n  style: CSSStyleDeclaration\n  hooks: EventEmitter\n  constructor(content: HTMLElement) {\n    this.setContent(content)\n    this.hooks = new EventEmitter(['beforeTranslate', 'translate'])\n  }\n\n  getComputedPosition() {\n    let cssStyle = window.getComputedStyle(\n      this.content,\n      null\n    ) as safeCSSStyleDeclaration\n    let matrix = cssStyle[style.transform].split(')')[0].split(', ')\n    const x = +(matrix[12] || matrix[4]) || 0\n    const y = +(matrix[13] || matrix[5]) || 0\n\n    return {\n      x,\n      y,\n    }\n  }\n\n  translate(point: TranslaterPoint) {\n    let transformStyle = [] as string[]\n    Object.keys(point).forEach((key) => {\n      if (!translaterMetaData[key]) {\n        return\n      }\n      const transformFnName = translaterMetaData[key][0]\n      if (transformFnName) {\n        const transformFnArgUnit = translaterMetaData[key][1]\n        const transformFnArg = point[key]\n        transformStyle.push(\n          `${transformFnName}(${transformFnArg}${transformFnArgUnit})`\n        )\n      }\n    })\n    this.hooks.trigger(\n      this.hooks.eventTypes.beforeTranslate,\n      transformStyle,\n      point\n    )\n    this.style[style.transform as any] = transformStyle.join(' ')\n    this.hooks.trigger(this.hooks.eventTypes.translate, point)\n  }\n\n  setContent(content: HTMLElement) {\n    if (this.content !== content) {\n      this.content = content\n      this.style = content.style\n    }\n  }\n\n  destroy() {\n    this.hooks.destroy()\n  }\n}\n"
  },
  {
    "path": "packages/core/src/utils/__tests__/bubbling.spec.ts",
    "content": "import { bubbling } from '../bubbling'\nimport { EventEmitter } from '@better-scroll/shared-utils'\n\ndescribe('bubbling', () => {\n  it('bubbling', () => {\n    const parentHooks = new EventEmitter(['test'])\n    const childHooks = new EventEmitter(['test'])\n    bubbling(childHooks, parentHooks, ['test'])\n    const handler = jest.fn(() => {})\n    parentHooks.on('test', handler)\n    childHooks.trigger('test', 'dummy test')\n    expect(handler).toBeCalledWith('dummy test')\n  })\n})\n"
  },
  {
    "path": "packages/core/src/utils/bubbling.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\ninterface BubblingEventMap {\n  source: string\n  target: string\n}\ntype BubblingEventConfig = BubblingEventMap | string\nexport function bubbling(\n  source: EventEmitter,\n  target: EventEmitter,\n  events: BubblingEventConfig[]\n) {\n  events.forEach(event => {\n    let sourceEvent: string\n    let targetEvent: string\n    if (typeof event === 'string') {\n      sourceEvent = targetEvent = event\n    } else {\n      sourceEvent = event.source\n      targetEvent = event.target\n    }\n    source.on(sourceEvent, function(...args: any[]) {\n      return target.trigger(targetEvent, ...args)\n    })\n  })\n}\n"
  },
  {
    "path": "packages/core/src/utils/compare.ts",
    "content": "import { TranslaterPoint } from '../translater'\n\nexport function isSamePoint(\n  startPoint: TranslaterPoint,\n  endPoint: TranslaterPoint\n): boolean {\n  // keys of startPoint and endPoint should be equal\n  const keys = Object.keys(startPoint)\n  for (let key of keys) {\n    if (startPoint[key] !== endPoint[key]) return false\n  }\n  return true\n}\n"
  },
  {
    "path": "packages/core/src/utils/compat.ts",
    "content": "import { Direction } from '@better-scroll/shared-utils'\nimport { TranslaterPoint } from '../translater'\n\ntype Position = {\n  x: number\n  y: number\n}\n// iOS 13.6 - 14.x, window.getComputedStyle sometimes will get wrong transform value\n// when bs use transition mode\n// eg: translateY -100px -> -200px, when the last frame which is about to scroll to -200px\n// window.getComputedStyle(this.content) will calculate transformY to be -100px(startPoint)\n// it is weird\n// so we should validate position caculated by 'window.getComputedStyle'\nexport const isValidPostion = (\n  startPoint: TranslaterPoint,\n  endPoint: TranslaterPoint,\n  currentPos: Position,\n  prePos: Position\n) => {\n  const computeDirection = (endValue: number, startValue: number) => {\n    const delta = endValue - startValue\n    const direction =\n      delta > 0\n        ? Direction.Negative\n        : delta < 0\n        ? Direction.Positive\n        : Direction.Default\n    return direction\n  }\n  const directionX = computeDirection(endPoint.x, startPoint.x)\n  const directionY = computeDirection(endPoint.y, startPoint.y)\n  const deltaX = currentPos.x - prePos.x\n  const deltaY = currentPos.y - prePos.y\n\n  return directionX * deltaX <= 0 && directionY * deltaY <= 0\n}\n"
  },
  {
    "path": "packages/core/src/utils/typesHelper.ts",
    "content": "export type UnionToIntersection<U> = (\n  U extends any ? (k: U) => void : never\n) extends (k: infer I) => void\n  ? I\n  : never\n"
  },
  {
    "path": "packages/examples/README.md",
    "content": "# examples\n\nBetterScroll example in different Vue scenarios.\n\n[vue demo](https://better-scroll.github.io/examples/#/)\n"
  },
  {
    "path": "packages/examples/build/vue-example-build.js",
    "content": "var ora = require('ora')\nvar rm = require('rimraf')\nvar path = require('path')\nvar chalk = require('chalk')\nvar webpack = require('webpack')\nvar webpackConfig = require('./vue-webpack.conf.js')\n\nvar spinner = ora('building for production...')\nspinner.start()\nconst assetsRoot = path.join(__dirname, '../dist/vue')\nconsole.log(chalk.red(assetsRoot))\nrm(assetsRoot, err => {\n    if (err) throw err\n    webpack(webpackConfig, function(err, stats) {\n        spinner.stop()\n        if (err) throw err\n        process.stdout.write(stats.toString({\n            colors: true,\n            modules: false,\n            children: false,\n            chunks: false,\n            chunkModules: false\n        }) + '\\n\\n')\n\n        console.log(chalk.cyan('  Build complete.\\n'))\n        console.log(chalk.yellow(\n            '  Tip: built files are meant to be served over an HTTP server.\\n' +\n            '  Opening index.html over file:// won\\'t work.\\n'\n        ))\n    })\n})\n"
  },
  {
    "path": "packages/examples/build/vue-webpack.conf.js",
    "content": "const Config = require('webpack-chain')\nconst VueLoaderPlugin = require('vue-loader/lib/plugin')\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin')\nconst webpack = require('webpack')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\nconst webpackBar = require('webpackbar')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\nconst path = require('path')\nconst fs = require('fs')\nconst { e2e } = require('yargs').argv\nconst execa = require('execa')\nconst isProd = process.env.NODE_ENV === 'production'\n\nfunction resolve(dir) {\n  return path.join(__dirname, '../', dir)\n}\n\nconst webpackConfig = new Config()\nconsole.log('-----', isProd)\nwebpackConfig\n  .mode(isProd ? 'production' : 'development')\n  .devtool(isProd ? 'false' : 'eval-source-map')\n  .entry('app')\n    .add('./vue/main.js')\n    .end()\n  .output\n    .path(isProd ? path.resolve(__dirname, '../dist/vue') : undefined)\n    .publicPath(isProd ? '/examples/' : '/')\n    .filename(isProd ? 'static/js/[name].[chunkhash].js' : '[name].js')\n    .chunkFilename(isProd ? 'static/js/[id].[chunkhash].js' : '[name].js')\n    .end()\n  .resolve\n    .alias\n      .set('vue-example', resolve('vue'))\n      .set('common', resolve('common'))\n      .end()\n    .extensions\n      .add('.js')\n      .add('.vue')\n      .add('.json')\n      .add('.ts')\n      .end()\n    .end()\n  .module\n    .rule('compileVue')\n      .test(/\\.vue$/)\n      .use('vue')\n        .loader('vue-loader')\n        .end()\n      .end()\n    .rule('transformJs')\n      .test(/\\.js$/)\n      .use('babel')\n        .loader('babel-loader')\n        .options({\n          presets: [\"@babel/preset-env\"]\n        })\n        .end()\n      .include\n        .add(resolve('common'))\n        .add(resolve('vue'))\n        .end()\n      .end()\n    .rule('url')\n      .test(/\\.(png|jpe?g|gif|svg|webp)(\\?.*)?$/)\n      .use('url')\n        .loader('url-loader')\n        .options({\n          esModule: false,\n          limit: 10000,\n          name: 'static/img/[name].[hash:7].[ext]'\n        })\n        .end()\n      .end()\n    .rule('ts')\n      .test(/\\.ts$/)\n      .use('ts')\n        .loader('ts-loader')\n        .options({\n          transpileOnly: true\n        })\n        .end()\n      .end()\n    .rule('css')\n      .test(/\\.css$/)\n      .use('style-loader')\n        .loader(\n          process.env.NODE_ENV !== 'production' ?\n          'vue-style-loader' :\n          MiniCssExtractPlugin.loader\n        )\n        .end()\n      .use('css-loader')\n        .loader('css-loader')\n        .end()\n      .end()\n    .rule('stylus')\n      .test(/.styl(us)?$/)\n      .use('style-loader')\n        .loader(\n          process.env.NODE_ENV !== 'production' ?\n          'vue-style-loader' :\n          MiniCssExtractPlugin.loader\n        )\n        .end()\n      .use('css-loader')\n        .loader('css-loader')\n        .end()\n      .use('postcss-loader')\n        .loader('postcss-loader')\n        .end()\n      .use('stylus-loader')\n        .loader('stylus-loader')\n        .end()\n      .end()\n    .end()\n  .plugin('VueLoaderPlugin')\n    .use(VueLoaderPlugin)\n    .end()\n  .plugin('WebpackBar')\n    .use(webpackBar)\n    .end()\n  .plugin('MiniCssExtractPlugin')\n    .use(MiniCssExtractPlugin, [{\n      filename: 'static/css/[name].[contenthash].css'\n    }])\n    .end()\n  .when(!isProd, () => {\n    webpackConfig\n      .devServer\n        .open(true)\n        .hot(true)\n        .compress(true)\n        .end()\n      .plugin('HotModuleReplacementPlugin')\n        .use(webpack.HotModuleReplacementPlugin)\n        .end()\n      .plugin('NoEmitOnErrorsPlugin')\n        .use(webpack.NoEmitOnErrorsPlugin)\n        .end()\n      .plugin('HtmlWebpackPlugin')\n        .use(HtmlWebpackPlugin, [{\n          filename: 'index.html',\n          template: './vue/index.html',\n          inject: true\n        }])\n        .end()\n      .plugin('FriendlyErrorsPlugin')\n        .use(FriendlyErrorsPlugin)\n        .end()\n  }, () => {\n    webpackConfig\n      .optimization\n        .minimizer('TerserPlugin')\n          .use(TerserPlugin)\n        .end()\n      .end()\n      .plugin('OptimizeCSSPlugin')\n        .use(OptimizeCSSPlugin, [{\n          cssProcessorOptions: {\n            safe: true\n          }\n        }])\n        .end()\n      .plugin('HtmlWebpackPlugin')\n        .use(HtmlWebpackPlugin, [{\n          filename: 'index.html',\n          template: './vue/index.html',\n          inject: true,\n          minify: {\n            removeComments: true,\n            collapseWhitespace: true,\n            removeAttributeQuotes: true\n            // more options:\n            // https://github.com/kangax/html-minifier#options-quick-reference\n          },\n          // necessary to consistently work with multiple chunks via CommonsChunkPlugin\n          chunksSortMode: 'dependency'\n        }])\n        .end()\n      .plugin('CopyWebpackPlugin')\n        .use(CopyWebpackPlugin, [[{\n          from: path.resolve(__dirname, '../static'),\n          to: 'static',\n          ignore: ['.*']\n        }]])\n        .end()\n  })\nfunction getPackagesName() {\n  let ret\n  let all = fs.readdirSync(resolve('../../packages'))\n\n  // drop hidden file whose name is startWidth '.'\n  // drop packages which would not be published(eg: examples and vuepress-docs)\n  ret = all\n          .filter(name => {\n            const isHiddenFile = /^\\./g.test(name)\n            return !isHiddenFile\n          })\n          .filter(name => {\n            const isPrivatePackages = require(resolve(`../../packages/${name}/package.json`)).private\n            return !isPrivatePackages\n          })\n          .map((name) => {\n            return require(resolve(`../../packages/${name}/package.json`)).name\n          })\n\n  return ret\n}\n\n// add alias\ngetPackagesName().forEach((name) => {\n  webpackConfig.resolve.alias.set(`${name}$`, `${name}/src/index.ts`)\n})\n\nlet config = { ...webpackConfig.toConfig(), devServer: { host: '0.0.0.0', disableHostCheck: true }}\n// run test e2e\nif (e2e) {\n  config.devServer.setup = (app, server) => {\n    server.middleware.waitUntilValid(async () => {\n      // back to src directory\n      const cwd = path.join(__dirname, '../../')\n\n      await execa('yarn', ['test:e2e'], { stdio: 'inherit', cwd })\n    })\n  }\n}\n\nmodule.exports = config\n"
  },
  {
    "path": "packages/examples/package.json",
    "content": "{\n    \"name\": \"examples\",\n    \"version\": \"2.5.1\",\n    \"description\": \"Examples of BetterScroll\",\n    \"author\": {\n        \"name\": \"jizhi\",\n        \"email\": \"theniceangel@163.com\"\n    },\n    \"private\": true,\n    \"scripts\": {\n        \"vue:dev\": \"cross-env NODE_ENV=development webpack-dev-server --config ./build/vue-webpack.conf.js --host 0.0.0.0 --port 8932 --colors\",\n        \"vue:test:e2e\": \"yarn vue:dev --e2e\",\n        \"vue:build\": \"cross-env NODE_ENV=production node build/vue-example-build.js\",\n        \"vue:release\": \"sh vue-release.sh\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n    },\n    \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n    \"keywords\": [\n        \"scroll\",\n        \"iscroll\",\n        \"javascript\",\n        \"typescript\",\n        \"ios\",\n        \"better-scroll examples\"\n    ],\n    \"license\": \"MIT\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n        \"directory\": \"packages/examples\"\n    },\n    \"devDependencies\": {\n        \"@babel/polyfill\": \"^7.4.4\",\n        \"babel-loader\": \"8.1.0\",\n        \"copy-webpack-plugin\": \"^5.0.2\",\n        \"friendly-errors-webpack-plugin\": \"^1.7.0\",\n        \"html-webpack-plugin\": \"^3.2.0\",\n        \"mini-css-extract-plugin\": \"^0.5.0\",\n        \"ora\": \"^3.4.0\",\n        \"terser-webpack-plugin\": \"^4.1.0\",\n        \"uglifyjs-webpack-plugin\": \"^2.1.2\",\n        \"vue-loader\": \"15.9.6\",\n        \"webpack-cli\": \"3.3.0\",\n        \"webpackbar\": \"3.2.0\"\n    }\n}\n"
  },
  {
    "path": "packages/examples/postcss.config.js",
    "content": "module.exports = {\n  plugins: [\n    require('autoprefixer')({\n      browsers: require('./package.json').browserslist\n    })\n  ]\n}\n"
  },
  {
    "path": "packages/examples/static/css/github-light.css",
    "content": "/*\n   Copyright 2014 GitHub Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n*/\n\n.pl-c /* comment */ {\n  color: #969896;\n}\n\n.pl-c1      /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,\n.pl-s .pl-v /* string variable */ {\n  color: #0086b3;\n}\n\n.pl-e  /* entity */,\n.pl-en /* entity.name */ {\n  color: #795da3;\n}\n\n.pl-s .pl-s1 /* string source */,\n.pl-smi      /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {\n  color: #333;\n}\n\n.pl-ent /* entity.name.tag */ {\n  color: #63a35c;\n}\n\n.pl-k /* keyword, storage, storage.type */ {\n  color: #a71d5d;\n}\n\n.pl-pds              /* punctuation.definition.string, string.regexp.character-class */,\n.pl-s                /* string */,\n.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,\n.pl-sr               /* string.regexp */,\n.pl-sr .pl-cce       /* string.regexp constant.character.escape */,\n.pl-sr .pl-sra       /* string.regexp string.regexp.arbitrary-repitition */,\n.pl-sr .pl-sre       /* string.regexp source.ruby.embedded */ {\n  color: #183691;\n}\n\n.pl-v /* variable */ {\n  color: #ed6a43;\n}\n\n.pl-id /* invalid.deprecated */ {\n  color: #b52a1d;\n}\n\n.pl-ii /* invalid.illegal */ {\n  background-color: #b52a1d;\n  color: #f8f8f8;\n}\n\n.pl-sr .pl-cce /* string.regexp constant.character.escape */ {\n  color: #63a35c;\n  font-weight: bold;\n}\n\n.pl-ml /* markup.list */ {\n  color: #693a17;\n}\n\n.pl-mh        /* markup.heading */,\n.pl-mh .pl-en /* markup.heading entity.name */,\n.pl-ms        /* meta.separator */ {\n  color: #1d3e81;\n  font-weight: bold;\n}\n\n.pl-mq /* markup.quote */ {\n  color: #008080;\n}\n\n.pl-mi /* markup.italic */ {\n  color: #333;\n  font-style: italic;\n}\n\n.pl-mb /* markup.bold */ {\n  color: #333;\n  font-weight: bold;\n}\n\n.pl-md /* markup.deleted, meta.diff.header.from-file */ {\n  background-color: #ffecec;\n  color: #bd2c00;\n}\n\n.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {\n  background-color: #eaffea;\n  color: #55a532;\n}\n\n.pl-mdr /* meta.diff.range */ {\n  color: #795da3;\n  font-weight: bold;\n}\n\n.pl-mo /* meta.output */ {\n  color: #1d3e81;\n}\n\n"
  },
  {
    "path": "packages/examples/static/css/normalize.css",
    "content": "/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n/**\n * 1. Set default font family to sans-serif.\n * 2. Prevent iOS text size adjust after orientation change, without disabling\n *    user zoom.\n */\n\nhtml {\n  font-family: sans-serif; /* 1 */\n  -ms-text-size-adjust: 100%; /* 2 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/**\n * Remove default margin.\n */\n\nbody {\n  margin: 0;\n}\n\n/* HTML5 display definitions\n   ========================================================================== */\n\n/**\n * Correct `block` display not defined for any HTML5 element in IE 8/9.\n * Correct `block` display not defined for `details` or `summary.md` in IE 10/11\n * and Firefox.\n * Correct `block` display not defined for `main` in IE 11.\n */\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\n\n/**\n * 1. Correct `inline-block` display not defined in IE 8/9.\n * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n */\n\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block; /* 1 */\n  vertical-align: baseline; /* 2 */\n}\n\n/**\n * Prevent modern browsers from displaying `audio` without controls.\n * Remove excess height in iOS 5 devices.\n */\n\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n\n/**\n * Address `[hidden]` styling not present in IE 8/9/10.\n * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n */\n\n[hidden],\ntemplate {\n  display: none;\n}\n\n/* Links\n   ========================================================================== */\n\n/**\n * Remove the gray background color from active links in IE 10.\n */\n\na {\n  background-color: transparent;\n}\n\n/**\n * Improve readability when focused and also mouse hovered in all browsers.\n */\n\na:active,\na:hover {\n  outline: 0;\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n */\n\nabbr[title] {\n  border-bottom: 1px dotted;\n}\n\n/**\n * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n */\n\nb,\nstrong {\n  font-weight: bold;\n}\n\n/**\n * Address styling not present in Safari and Chrome.\n */\n\ndfn {\n  font-style: italic;\n}\n\n/**\n * Address variable `h1` font-size and margin within `section` and `article`\n * contexts in Firefox 4+, Safari, and Chrome.\n */\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/**\n * Address styling not present in IE 8/9.\n */\n\nmark {\n  background: #ff0;\n  color: #000;\n}\n\n/**\n * Address inconsistent and variable font size in all browsers.\n */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` affecting `line-height` in all browsers.\n */\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsup {\n  top: -0.5em;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove border when inside `a` element in IE 8/9/10.\n */\n\nimg {\n  border: 0;\n}\n\n/**\n * Correct overflow not hidden in IE 9/10/11.\n */\n\nsvg:not(:root) {\n  overflow: hidden;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * Address margin not present in IE 8/9 and Safari.\n */\n\nfigure {\n  margin: 1em 40px;\n}\n\n/**\n * Address differences between Firefox and other browsers.\n */\n\nhr {\n  box-sizing: content-box;\n  height: 0;\n}\n\n/**\n * Contain overflow in all browsers.\n */\n\npre {\n  overflow: auto;\n}\n\n/**\n * Address odd `em`-unit font size rendering in all browsers.\n */\n\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * Known limitation: by default, Chrome and Safari on OS X allow very limited\n * styling of `select`, unless a `border` property is set.\n */\n\n/**\n * 1. Correct color not being inherited.\n *    Known issue: affects color of disabled elements.\n * 2. Correct font properties not being inherited.\n * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  color: inherit; /* 1 */\n  font: inherit; /* 2 */\n  margin: 0; /* 3 */\n}\n\n/**\n * Address `overflow` set to `hidden` in IE 8/9/10/11.\n */\n\nbutton {\n  overflow: visible;\n}\n\n/**\n * Address inconsistent `text-transform` inheritance for `button` and `select`.\n * All other form control elements do not inherit `text-transform` values.\n * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n * Correct `select` style inheritance in Firefox.\n */\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n/**\n * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n *    and `video` controls.\n * 2. Correct inability to style clickable `input` types in iOS.\n * 3. Improve usability and consistency of cursor style between image-type\n *    `input` and others.\n */\n\nbutton,\nhtml input[type=\"button\"], /* 1 */\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button; /* 2 */\n  cursor: pointer; /* 3 */\n}\n\n/**\n * Re-set default cursor for disabled elements.\n */\n\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\n\n/**\n * Remove inner padding and border in Firefox 4+.\n */\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\n\n/**\n * Address Firefox 4+ setting `line-height` on `input` using `!important` in\n * the UA stylesheet.\n */\n\ninput {\n  line-height: normal;\n}\n\n/**\n * It's recommended that you don't attempt to style these elements.\n * Firefox's implementation doesn't respect box-sizing, padding, or width.\n *\n * 1. Address box sizing set to `content-box` in IE 8/9/10.\n * 2. Remove excess padding in IE 8/9/10.\n */\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Fix the cursor style for Chrome's increment/decrement buttons. For certain\n * `font-size` values of the `input`, it causes the cursor style of the\n * decrement button to change from `default` to `text`.\n */\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n * 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n * 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n *    (include `-moz` to future-proof).\n */\n\ninput[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */ /* 2 */\n  box-sizing: content-box;\n}\n\n/**\n * Remove inner padding and search cancel button in Safari and Chrome on OS X.\n * Safari (but not Chrome) clips the cancel button when the search input has\n * padding (and `textfield` appearance).\n */\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n * Define consistent border, margin, and padding.\n */\n\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\n\n/**\n * 1. Correct `color` not being inherited in IE 8/9/10/11.\n * 2. Remove padding so people aren't caught out if they zero out fieldsets.\n */\n\nlegend {\n  border: 0; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Remove default vertical scrollbar in IE 8/9/10/11.\n */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n * Don't inherit the `font-weight` (applied by a rule above).\n * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n */\n\noptgroup {\n  font-weight: bold;\n}\n\n/* Tables\n   ========================================================================== */\n\n/**\n * Remove most spacing between table cells.\n */\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  padding: 0;\n}\n"
  },
  {
    "path": "packages/examples/static/css/reset.css",
    "content": "/**\n * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)\n * http://cssreset.com\n */\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video, input {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    font-size: 100%;\n    font-weight: normal;\n    vertical-align: baseline;\n}\n\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, menu, nav, section {\n    display: block;\n}\n\nbody {\n    line-height: 1;\n}\n\nblockquote, q {\n    quotes: none;\n}\n\nblockquote:before, blockquote:after,\nq:before, q:after {\n    content: none;\n}\n\ntable {\n    border-collapse: collapse;\n    border-spacing: 0;\n}\n\n/* custom */\n\na {\n    color: #7e8c8d;\n    -webkit-backface-visibility: hidden;\n}\n\nli {\n    list-style: none;\n}\n\n::-webkit-scrollbar {\n    width: 5px;\n    height: 5px;\n}\n\n::-webkit-scrollbar-track-piece {\n    background-color: rgba(0, 0, 0, 0.2);\n    -webkit-border-radius: 6px;\n}\n\n::-webkit-scrollbar-thumb:vertical {\n    height: 5px;\n    background-color: rgba(125, 125, 125, 0.7);\n    -webkit-border-radius: 6px;\n}\n\n::-webkit-scrollbar-thumb:horizontal {\n    width: 5px;\n    background-color: rgba(125, 125, 125, 0.7);\n    -webkit-border-radius: 6px;\n}\n\nhtml, body {\n    width: 100%;\n    /* height: 100%; */\n}\n\nbody {\n    -webkit-text-size-adjust: none;\n    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}"
  },
  {
    "path": "packages/examples/static/css/stylesheet.css",
    "content": "* {\n  box-sizing: border-box; }\n\nbody {\n  padding: 0;\n  margin: 0;\n  font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 16px;\n  line-height: 1.5;\n  background-color: #fff;\n  color: #606c71; }\n\na {\n  color: #1e6bb8;\n  text-decoration: none; }\n  a:hover {\n    text-decoration: underline; }\n\n.btn {\n  display: inline-block;\n  margin-bottom: 1rem;\n  padding: 0.75rem 1rem;\n  color: rgba(255, 255, 255, 0.8);\n  background-color: rgba(255, 255, 255, 0.1);\n  border-color: rgba(255, 255, 255, 0.2);\n  border-style: solid;\n  border-width: 1px;\n  border-radius: 0.3rem;\n  transition: color 0.2s, background-color 0.2s, border-color 0.2s; }\n  .btn + .btn {\n    margin-left: 1rem; }\n\n.btn:hover {\n  color: rgba(255, 255, 255, 0.9);\n  text-decoration: none;\n  background-color: rgba(255, 255, 255, 0.2);\n  border-color: rgba(255, 255, 255, 0.3); }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .btn {\n    font-size: 0.9rem; } }\n\n@media screen and (max-width: 42em) {\n  .btn {\n    display: block;\n    width: 100%;\n    padding: 0.75rem;\n    font-size: 0.9rem; }\n    .btn + .btn {\n      margin-top: 1rem;\n      margin-left: 0; } }\n\n.page-header {\n  color: #fff;\n  text-align: center;\n  font-family: 'Patrick Hand', cursive;\n  background-color: rgba(39,80,255, .7)}\n\n@media screen and (min-width: 64em) {\n  .page-header {\n    padding: 1.5rem 6rem 3rem 6rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .page-header {\n    padding: 1.5rem 4rem 2rem 4rem; } }\n\n@media screen and (max-width: 42em) {\n  .page-header {\n    padding: 1.5rem 1rem 1rem 1rem; } }\n\n.project-name {\n  margin-top: 0;\n  margin-bottom: 0.1rem; }\n\n@media screen and (min-width: 64em) {\n  .project-name {\n    font-size: 3.25rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .project-name {\n    font-size: 2.25rem; } }\n\n@media screen and (max-width: 42em) {\n  .project-name {\n    font-size: 1.75rem; } }\n\n.project-tagline {\n  margin-bottom: 1rem;\n  font-weight: normal;\n  opacity: 0.7; }\n\n@media screen and (min-width: 64em) {\n  .project-tagline {\n    font-size: 1.25rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .project-tagline {\n    font-size: 1.15rem; } }\n\n@media screen and (max-width: 42em) {\n  .project-tagline {\n    font-size: 1rem; } }\n\n.main-content :first-child {\n  margin-top: 0; }\n.main-content img {\n  max-width: 100%; }\n.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {\n  margin-top: 1rem;\n  margin-bottom: 1rem; }\n.main-content code {\n  padding: 2px 4px;\n  font-family: Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 0.9rem;\n  color: #383e41;\n  background-color: #f3f6fa;\n  border-radius: 0.3rem; }\n.main-content pre {\n  padding: 0.8rem;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  font: 1rem Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  color: #567482;\n  word-wrap: normal;\n  background-color: #f3f6fa;\n  border: solid 1px #dce6f0;\n  border-radius: 0.3rem; }\n  .main-content pre > code {\n    padding: 0;\n    margin: 0;\n    font-size: 0.9rem;\n    color: #567482;\n    word-break: normal;\n    white-space: pre;\n    background: transparent;\n    border: 0; }\n.main-content .highlight {\n  margin-bottom: 1rem; }\n  .main-content .highlight pre {\n    margin-bottom: 0;\n    word-break: normal; }\n.main-content .highlight pre, .main-content pre {\n  padding: 0.8rem;\n  overflow: auto;\n  font-size: 0.9rem;\n  line-height: 1.45;\n  border-radius: 0.3rem; }\n.main-content pre code, .main-content pre tt {\n  display: inline;\n  max-width: initial;\n  padding: 0;\n  margin: 0;\n  overflow: initial;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0; }\n  .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {\n    content: normal; }\n.main-content ul, .main-content ol {\n  margin-top: 0; }\n.main-content blockquote {\n  padding: 0 1rem;\n  margin-left: 0;\n  color: #819198;\n  border-left: 0.3rem solid #dce6f0; }\n  .main-content blockquote > :first-child {\n    margin-top: 0; }\n  .main-content blockquote > :last-child {\n    margin-bottom: 0; }\n.main-content table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n  word-break: normal;\n  word-break: keep-all; }\n  .main-content table th {\n    font-weight: bold; }\n  .main-content table th, .main-content table td {\n    padding: 0.5rem 1rem;\n    border: 1px solid #e9ebec; }\n.main-content dl {\n  padding: 0; }\n  .main-content dl dt {\n    padding: 0;\n    margin-top: 1rem;\n    font-size: 1rem;\n    font-weight: bold; }\n  .main-content dl dd {\n    padding: 0;\n    margin-bottom: 1rem; }\n.main-content hr {\n  height: 2px;\n  padding: 0;\n  margin: 1rem 0;\n  background-color: #eff0f1;\n  border: 0; }\n\n@media screen and (min-width: 64em) {\n  .main-content {\n    padding: 2rem 8rem;\n    margin: 0 auto;\n    font-size: 1.1rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .main-content {\n    padding: 2rem 2rem;\n    font-size: 1.1rem; } }\n\n@media screen and (max-width: 42em) {\n  .main-content {\n    padding: 1rem 1rem 2rem 1rem;\n    font-size: 1rem; }\n  iframe {\n    display: none\n  }\n}\n\n.site-footer {\n  padding-top: 2rem;\n  margin-top: 2rem;\n  border-top: solid 1px #d7d7d7; }\n\n.site-footer-owner {\n  display: block;\n  font-weight: bold; }\n\n.site-footer-credits {\n  color: #819198; }\n\n@media screen and (min-width: 64em) {\n  .site-footer {\n    font-size: 1rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .site-footer {\n    font-size: 1rem; } }\n\n@media screen and (max-width: 42em) {\n  .site-footer {\n    font-size: 0.9rem; } }\n"
  },
  {
    "path": "packages/examples/vue/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <section class=\"page-header\">\n      <h1 class=\"project-name\">BetterScroll</h1>\n      <h2 class=\"project-tagline\">inspired by iscroll, and it has a better scroll perfermance</h2>\n    </section>\n    <section class=\"main-content\">\n      <div class=\"example\">\n        <ul class=\"example-list\">\n          <li\n            :key=\"index\"\n            @click=\"goPage(item.path)\"\n            class=\"example-item\"\n            v-for=\"(item, index) in examples\"\n          >\n            <span>{{item.name}}</span>\n          </li>\n          <li class=\"example-item placeholder\"></li>\n        </ul>\n      </div>\n    </section>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nconst examples = [\n  {\n    name: 'core scroll',\n    path: '/core/'\n  },\n  {\n    name: 'observe-dom',\n    path: '/observe-dom/'\n  },\n  {\n    name: 'observe-image',\n    path: '/observe-image/'\n  },\n  {\n    name: 'slide',\n    path: '/slide/'\n  },\n  {\n    name: 'zoom',\n    path: '/zoom/'\n  },\n  {\n    name: 'picker',\n    path: '/picker/'\n  },\n  {\n    name: 'pullup',\n    path: '/pullup/'\n  },\n  {\n    name: 'pulldown',\n    path: '/pulldown/'\n  },\n  {\n    name: 'scrollbar',\n    path: '/scrollbar/'\n  },\n  {\n    name: 'indicators',\n    path: '/indicators/'\n  },\n  {\n    name: 'infinity',\n    path: '/infinity/'\n  },\n  {\n    name: 'form',\n    path: '/form/'\n  },\n  {\n    name: 'nested-scroll',\n    path: '/nested-scroll/'\n  },\n  {\n    name: 'mouse-wheel',\n    path: '/mouse-wheel/'\n  },\n  {\n    name: 'movable',\n    path: '/movable/'\n  },\n  {\n    name: 'compose plugins',\n    path: '/compose/'\n  }\n]\nexport default {\n  data() {\n    return {\n      examples\n    }\n  },\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n.page-header\n  h1\n    @media screen and (min-width 42rem)\n      margin-bottom 1rem\n\n    @media screen and (max-width 42rem)\n      margin-bottom 0.5rem\n\n.main-content\n  .site-footer\n    text-align center\n\n    @media screen and (max-width 42rem)\n      margin-top -1rem\n\n.example-list\n  display flex\n  justify-content space-between\n  flex-wrap wrap\n\n  @media screen and (min-width 42rem)\n    margin 2rem 0 2rem 0\n\n  @media screen and (max-width 42rem)\n    margin 1rem 0\n\n  .example-item\n    background-color white\n    padding 0.8rem\n    border 1px solid rgba(0, 0, 0, 0.1)\n    box-shadow 0 1px 2px 0 rgba(0, 0, 0, 0.1)\n    text-align center\n    margin-bottom 1rem\n\n    &.placeholder\n      visibility hidden\n      height 0\n      margin 0\n      padding 0\n\n    @media screen and (min-width 42rem)\n      flex 0 1 28%\n\n    @media screen and (max-width 42rem)\n      flex 0 1 100%\n      margin-bottom 1rem\n\n.view\n  position fixed\n  top 0\n  left 0\n  bottom 0\n  right 0\n  z-index 1\n  padding 20px\n  background white\n  transform translate3d(0, 0, 0)\n\n  &.move-enter, &.move-leave-active\n    transform translate3d(100%, 0, 0)\n\n  &.move-enter-active, &.move-leave-active\n    transition transform 0.3s\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/compose/pullup-pulldown-outnested.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div ref=\"outerScroll\" class=\"outer-wrapper\">\n      <div class=\"outer-content\">\n        <div class=\"pulldown-wrapper\">\n          <div v-show=\"beforePullDown\">\n            <span>Pull Down and refresh</span>\n          </div>\n          <div v-show=\"!beforePullDown\">\n            <div v-show=\"isPullingDown\">\n              <span>Loading...</span>\n            </div>\n            <div v-show=\"!isPullingDown\">\n              <span>Refresh success</span>\n            </div>\n          </div>\n        </div>\n        <ul>\n          <li class=\"outer-list-item\" v-for=\"(item, index) in topOutItems\" :key=\"index\">{{item}}</li>\n        </ul>\n        <div ref=\"innerScroll\" class=\"inner-wrapper\">\n          <ul class=\"inner-content\">\n            <li class=\"inner-list-item\" v-for=\"(item, index) in innerItems\" :key=\"index\">{{item}}</li>\n          </ul>\n        </div>\n        <ul>\n          <li class=\"outer-list-item2\" v-for=\"(item, index) in bottomOutItems\" :key=\"index\">{{item}}</li>\n        </ul>\n        <div class=\"pullup-wrapper\">\n          <div v-show=\"!isPullingUp\">\n            <span>Pull Up and load</span>\n          </div>\n          <div v-show=\"isPullingUp\">\n            <span>Loading...</span>\n          </div>\n        </div>\n      </div>\n\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport PullUp from '@better-scroll/pull-up'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(NestedScroll)\nBScroll.use(PullUp)\nBScroll.use(PullDown)\n\nconst _data1 = [\n  '😀  😁   😂  🤣   😃  🙃',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '🙂  🤔   😄  🤨   😐  🙃',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️',\n  '🙂  🤔  😄  🤨   😐  🙃 ',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️ '\n]\n\nconst _data2 = [\n  '😀  😁   😂  🤣   😃  🙃',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '🙂  🤔   😄  🤨   😐  🙃',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️',\n  '🙂  🤔  😄  🤨   😐  🙃 ',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️ '\n]\n\nconst _data3 = [\n  'The Mountain top of Inner',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  'The Mountain foot of Inner',\n]\n\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 3000\nconst THRESHOLD = 70\nconst STOP = 56\n\nexport default {\n  data() {\n    return {\n      topOutItems: _data1,\n      bottomOutItems:_data2,\n      innerItems: _data3,\n      beforePullDown: true,\n      isPullingDown: false,\n      isPullingUp: false,\n    }\n  },\n  mounted () {\n    this.initBScroll()\n  },\n  methods: {\n    initBScroll () {\n      // outer\n      this.outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'pullup-pullldown'\n        },\n        bounceTime:TIME_BOUNCE,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: THRESHOLD\n        }\n      })\n      // inner\n      this.innerScroll = new BScroll(this.$refs.innerScroll, {\n        nestedScroll: {\n          groupId: 'pullup-pullldown'\n        },\n        // close bounce effects\n        bounce: {\n          top: false,\n          bottom: false\n        }\n      })\n      this.outerScroll.on('pullingDown', this.pullingDownHandler)\n      this.outerScroll.on('pullingUp', this.pullingUpHandler)\n      this.outerScroll.on('scroll', this.scrollHandler)\n    },\n    // scroll event handler\n    scrollHandler(pos) {\n      console.log(pos.y)\n    },\n    // pullingDown event handler\n    async pullingDownHandler() {\n      this.beforePullDown = false\n      this.isPullingDown = true\n      await this.requestData('refresh')\n      this.isPullingDown = false\n      this.$nextTick(() => {\n        this.outerScroll.finishPullDown()\n        this.beforePullDown = true\n        this.outerScroll.refresh()\n      })\n    },\n    // pullingUp event handler\n    async pullingUpHandler() {\n      this.isPullingUp = true\n      await this.requestData('load')\n      this.isPullingUp = false\n      this.$nextTick(() => {\n        this.outerScroll.finishPullUp()\n        this.outerScroll.refresh()\n      })\n    },\n    async requestData(type) {\n      try {\n        const {topOutItems, bottomOutItems} = await this.ajaxGet(/* url */)\n        if (type === 'load') {\n          this.bottomOutItems = this.bottomOutItems.concat(bottomOutItems)\n        } else {\n          this.topOutItems = topOutItems\n          this.bottomOutItems = bottomOutItems\n        }\n      } catch(err) {\n        console.log(err)\n      }\n    },\n    ajaxGet(/* url */) {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          resolve({\n            topOutItems: _data1,\n            bottomOutItems: _data2\n          })\n        }, REQUEST_TIME)\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n.container\n  height: 100%\n.outer-wrapper\n.inner-wrapper\n  border: 2px solid #62B791\n  border-radius: 5px\n  transform: rotate(0deg)\n  position: relative\n  overflow: hidden\n.outer-wrapper\n  height: 100%\n  border: 1px solid rgba(0, 0, 0, .1)\n.inner-wrapper\n  height: 240px\n  background-color: rgba(98,183,145, 0.2)\n\n.inner-list-item\n  height: 50px\n  line-height: 50px\n  text-align: center\n  list-style: none\n  \n.outer-list-item2,\n.outer-list-item\n  height: 40px\n  line-height: 40px\n  text-align: center\n  list-style: none\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n\n.pullup-wrapper\n  padding 20px\n  text-align center\n  color #999\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/compose/pullup-pulldown-slide.vue",
    "content": "<template>\n  <div class=\"pullup-down-slide-wrapper\">\n    <!-- pulldown -->\n    <div\n      class=\"pulldown-wrapper\"\n      ref=\"pulldown\"\n    >\n      <div v-show=\"beforePullDown\">\n        <span>Pull Down and refresh</span>\n      </div>\n      <div v-show=\"!beforePullDown\">\n        <div v-show=\"isPullingDown\">\n          <span>Loading...</span>\n        </div>\n        <div v-show=\"!isPullingDown\">\n          <span>Refresh success</span>\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"pullup-pulldown-slide-bswrapper\"\n      ref=\"bsWrapper\"\n    >\n      <div class=\"pullup-pulldown-slide-scroller\">\n        <!-- slide item -->\n        <div\n          :key=\"idx\"\n          class=\"pullup-pulldown-slide-item\"\n          :class=\"{['page' + idx % 4 ]: true}\"\n          v-for=\"(item, idx) of dataList\"\n        >{{ `Page ${idx} ` }}</div>\n\n      </div>\n    </div>\n    <!-- pollup -->\n    <div class=\"pullup-wrapper\" ref=\"pullup\">\n      <div v-show=\"!isPullingUp\">\n        <span>Pull Up and load</span>\n      </div>\n      <div v-show=\"isPullingUp\">\n        <span>Loading...</span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport PullUp from '@better-scroll/pull-up'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(PullDown)\nBScroll.use(PullUp)\nBScroll.use(Slide)\n\nconst BASE = 2\nconst TIME_BOUNCE = 700\nconst REQUEST_TIME = 1000\nconst THRESHOLD = 50\nconst STOP = 56\n\nexport default {\n  data() {\n    return {\n      beforePullDown: true,\n      isPullingDown: false,\n      isPullingUp: false,\n      dataList: new Array(BASE),\n      stopPullup: false\n    }\n  },\n  created() {\n    this.bscroll = null\n  },\n  mounted() {\n    this.initBscroll()\n  },\n  methods: {\n    initBscroll() {\n      this.bscroll = new BScroll(this.$refs.bsWrapper, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        momentum: false,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: -THRESHOLD\n        },\n        // slide options\n        slide: {\n          threshold: 5,\n          disableSetHeight: true,\n          autoplay: false,\n          loop: false\n        }\n      })\n      // listening evnets\n      this.bscroll.on('pullingDown', this.pullingDownHandler)\n      this.bscroll.on('pullingUp', this.pullingUpHandler)\n      this.bscroll.on('scroll', this.scrollHandler)\n    },\n    // scroll event handler\n    scrollHandler(pos) {\n      if (pos.y >= 0) {\n        const pullDownEle = this.$refs.pulldown\n        const { height: pulldownH } = getComputedStyle(pullDownEle, null)\n        pullDownEle.style.transform = `translateY(${-parseInt(pulldownH) +\n          pos.y}px) translateZ(0)`\n      }\n      const pullupThreshold = -30\n      const maxScrollY = this.bscroll.maxScrollY\n      if (pos.y - maxScrollY <= pullupThreshold && this.isPullingUp) {\n        this.stopPullup = true\n      }\n      if (pos.y - maxScrollY <= 0 && !this.stopPullup) {\n        const pullUpEle = this.$refs.pullup\n        const { height: pullupH } = getComputedStyle(pullUpEle, null)\n        pullUpEle.style.transform = `translateY(${pos.y - maxScrollY}px) translateZ(0)`\n      }\n    },\n    // pullingDown event handler\n    async pullingDownHandler() {\n      this.beforePullDown = false\n      this.isPullingDown = true\n      this.bscroll.enabled = false\n      await this.requestData('refresh')\n      this.isPullingDown = false\n      this.$nextTick(() => {\n        this.bscroll.finishPullDown()\n        this.beforePullDown = true\n        this.bscroll.enabled = true\n        this.bscroll.refresh()\n      })\n    },\n    // pullingUp event handler\n    async pullingUpHandler() {\n      this.isPullingUp = true\n      this.bscroll.enabled = false\n      await this.requestData('load')\n      this.$nextTick(() => {\n        this.isPullingUp = false\n        this.bscroll.finishPullUp()\n        this.bscroll.enabled = true\n        this.bscroll.refresh()\n        this.resetPullupPos()\n      })\n    },\n    async requestData(type) {\n      try {\n        const newData = await this.ajaxGet(/* url */)\n        if (type === 'load') {\n          this.dataList = newData.concat(this.dataList)\n        } else {\n          this.dataList = newData\n        }\n      } catch (err) {\n        // handle err\n        console.log(err)\n      }\n    },\n    ajaxGet(/* url */) {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          const dataList = new Array(BASE)\n          resolve(dataList)\n        }, REQUEST_TIME)\n      })\n    },\n    resetPullupPos() {\n      const pullUpEle = this.$refs.pullup\n      pullUpEle.style.transform = `translateY(0px) translateZ(0)`\n      pullUpEle.style.transition = 'transform 0.5s'\n      this.stopPullup = false\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.pullup-down-slide-wrapper\n  height 100%\n  overflow hidden\n\n.pullup-pulldown-slide-bswrapper\n  position relative\n  height 100%\n  overflow hidden\n\n.pullup-down-list\n  padding 0\n\n.pullup-pulldown-slide-item\n  list-style none\n  // border-bottom 1px solid #ccc\n  width 100%\n  line-height 200px\n  text-align center\n  font-size 26px\n  transform translate3d(0,0,0)\n  backface-visibility hidden\n  box-sizing: border-box\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n\n.pullup-wrapper\n  height 40px !important\n  padding 20px\n  text-align center\n  color #999\n.page1\n  background-color #D6EADF\n.page2\n  background-color #DDA789\n.page3\n  background-color #C3D899\n.page0\n  background-color #F2D4A7\n</style>"
  },
  {
    "path": "packages/examples/vue/components/compose/pullup-pulldown.vue",
    "content": "<template>\n  <div class=\"pullup-down\">\n    <div\n      class=\"pullup-down-bswrapper\"\n      ref=\"bsWrapper\"\n    >\n      <div class=\"pulldown-scroller\">\n        <div class=\"pulldown-wrapper\">\n          <div v-show=\"beforePullDown\">\n            <span>Pull Down and refresh</span>\n          </div>\n          <div v-show=\"!beforePullDown\">\n            <div v-show=\"isPullingDown\">\n              <span>Loading...</span>\n            </div>\n            <div v-show=\"!isPullingDown\">\n              <span>Refresh success</span>\n            </div>\n          </div>\n        </div>\n        <ul class=\"pullup-down-list\">\n          <li\n            :key=\"idx\"\n            class=\"pullup-down-list-item\"\n            v-for=\"(item, idx) of dataList\"\n          >{{ `I am item ${idx} ` }}</li>\n        </ul>\n        <div class=\"pullup-wrapper\">\n          <div v-show=\"!isPullingUp\">\n            <span>Pull Up and load</span>\n          </div>\n          <div v-show=\"isPullingUp\">\n            <span>Loading...</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport PullUp from '@better-scroll/pull-up'\n\nBScroll.use(PullDown)\nBScroll.use(PullUp)\n\nconst BASE = 30\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 3000\nconst THRESHOLD = 70\nconst STOP = 56\n\nexport default {\n  data() {\n    return {\n      beforePullDown: true,\n      isPullingDown: false,\n      isPullingUp: false,\n      dataList: new Array(BASE)\n    }\n  },\n  created() {\n    this.bscroll = null\n  },\n  mounted() {\n    this.initBscroll()\n  },\n  methods: {\n    initBscroll() {\n      this.bscroll = new BScroll(this.$refs.bsWrapper, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: THRESHOLD\n        }\n      })\n      // listening evnets\n      this.bscroll.on('pullingDown', this.pullingDownHandler)\n      this.bscroll.on('pullingUp', this.pullingUpHandler)\n      this.bscroll.on('scroll', this.scrollHandler)\n    },\n    // scroll event handler\n    scrollHandler(pos) {\n      console.log(pos.y)\n    },\n    // pullingDown event handler\n    async pullingDownHandler() {\n      this.beforePullDown = false\n      this.isPullingDown = true\n      await this.requestData('refresh')\n      this.isPullingDown = false\n      this.$nextTick(() => {\n        this.bscroll.finishPullDown()\n        this.beforePullDown = true\n        this.bscroll.refresh()\n      })\n    },\n    // pullingUp event handler\n    async pullingUpHandler() {\n      this.isPullingUp = true\n      await this.requestData('load')\n      this.isPullingUp = false\n      this.$nextTick(() => {\n        this.bscroll.finishPullUp()\n        this.bscroll.refresh()\n      })\n    },\n    async requestData(type) {\n      try {\n        const newData = await this.ajaxGet(/* url */)\n        if (type === 'load') {\n          this.dataList = newData.concat(this.dataList)\n        } else {\n          this.dataList = newData\n        }\n      } catch (err) {\n        // handle err\n        console.log(err)\n      }\n    },\n    ajaxGet(/* url */) {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          const dataList = new Array(BASE)\n          resolve(dataList)\n        }, REQUEST_TIME)\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.pullup-down\n  height 100%\n\n.pullup-down-bswrapper\n  position relative\n  height 100%\n  padding 0 10px\n  border 1px solid #ccc\n  overflow hidden\n\n.pullup-down-list\n  padding 0\n\n.pullup-down-list-item\n  padding 10px 0\n  list-style none\n  border-bottom 1px solid #ccc\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n\n.pullup-wrapper\n  padding 20px\n  text-align center\n  color #999\n</style>"
  },
  {
    "path": "packages/examples/vue/components/compose/slide-nested.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div ref=\"outerScroll\" class=\"outer-wrapper\">\n      <div class=\"outer-content\">\n        <ul>\n          <li class=\"outer-list-item\" v-for=\"(item, index) in items1\" :key=\"index\">{{item}}</li>\n        </ul>\n        <div ref=\"innerScroll\" class=\"inner-wrapper\">\n          <div class=\"slide-banner-content\">\n          <div class=\"slide-item page1\">page 1</div>\n          <div class=\"slide-item page2\">page 2</div>\n          <div class=\"slide-item page3\">page 3</div>\n          <div class=\"slide-item page4\">page 4</div>\n        </div>\n        </div>\n        <ul>\n          <li class=\"outer-list-item\" v-for=\"(item, index) in items1\" :key=\"index\">{{item}}</li>\n        </ul>\n      </div>\n\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(NestedScroll)\nBScroll.use(Slide)\n\nconst _data1 = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ '\n]\n\nconst _data2 = [\n  'The Mountain top of Inner',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  'The Mountain foot of Inner',\n]\n\nexport default {\n  data() {\n    return {\n      items1: _data1,\n      items2: _data2\n    }\n  },\n  mounted () {\n    this.initBScroll()\n  },\n  beforeDestroy () {\n    this.outerScroll.destroy()\n    this.innerScroll.destroy()\n  },\n  methods: {\n    initBScroll () {\n      // outer\n      this.outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'slide-nested'\n        }\n      })\n      // inner\n      this.innerScroll = new BScroll(this.$refs.innerScroll, {\n        nestedScroll: {\n          groupId: 'slide-nested'\n        },\n        scrollX: true,\n        scrollY: false,\n        momentum: false,\n        // close bounce effects\n        bounce: {\n          top: false,\n          bottom: false\n        },\n        slide: {\n          loop: true,\n          autoplay: false\n        }\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n.container\n  height: 100%\n.outer-wrapper\n.inner-wrapper\n  \n  border-radius: 5px\n  transform: rotate(0deg)\n  position: relative\n  overflow: hidden\n  box-sizing: border-box\n.outer-wrapper\n  height: 100%\n  border: 1px solid rgba(0, 0, 0, .1)\n  border: 1px solid #62B791\n.inner-wrapper\n  height: 200px\n\n.outer-list-item\n  height: 40px\n  line-height: 40px\n  text-align: center\n  list-style: none\n\n.slide-banner-content\n  height 200px\n  white-space nowrap\n  width: 100%\n  font-size 0\n  .slide-item\n    display inline-block\n    height 200px\n    width 100%\n    line-height 200px\n    text-align center\n    font-size 26px\n    &.page1\n      background-color #95B8D1\n    &.page2\n      background-color #DDA789\n    &.page3\n      background-color #C3D899\n    &.page4\n      background-color #F2D4A7\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/default.vue",
    "content": "<template>\n  <div class=\"core-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"(item, index) in emojis\" :key=\"index\" @click=\"clickHandler(item)\">{{item}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        emojis: [\n          '😀 😁 😂 🤣 😃',\n          '😄 😅 😆 😉 😊',\n          '😫 😴 😌 😛 😜',\n          '👆🏻 😒 😓 😔 👇🏻',\n          '😑 😶 🙄 😏 😣',\n          '😞 😟 😤 😢 😭',\n          '🤑 😲 🙄 🙁 😖',\n          '👍 👎 👊 ✊ 🤛',\n          '🙄 ✋ 🤚 🖐 🖖',\n          '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n          '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n          '🌖 🌗 🌘 🌑 🌒',\n          '💫 💥 💢 💦 💧',\n          '🐠 🐟 🐬 🐳 🐋',\n          '😬 😐 😕 😯 😶',\n          '😇 😏 😑 😓 😵',\n          '🐥 🐣 🐔 🐛 🐤',\n          '💪 ✨ 🔔 ✊ ✋',\n          '👇 👊 👍 👈 👆',\n          '💛 👐 👎 👌 💘',\n          '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n          '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n          '🌖 🌗 🌘 🌑 🌒',\n          '💫 💥 💢 💦 💧',\n          '🐠 🐟 🐬 🐳 🐋',\n          '😬 😐 😕 😯 😶',\n          '😇 😏 😑 😓 😵',\n          '🐥 🐣 🐔 🐛 🐤',\n          '💪 ✨ 🔔 ✊ ✋',\n          '👇 👊 👍 👈 👆',\n          '💛 👐 👎 👌 💘'\n        ]\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          probeType: 3,\n          click: true\n        })\n        this.bs.on('scrollStart', () => {\n          console.log('scrollStart-')\n        })\n        this.bs.on('scroll', ({ y }) => {\n          console.log('scrolling-')\n        })\n        this.bs.on('scrollEnd', (pos) => {\n          console.log(pos)\n        })\n      },\n      clickHandler (item) {\n        window.alert(item)\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.core-container\n  .scroll-wrapper\n    height 400px\n    position relative\n    overflow hidden\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/dynamic-content.vue",
    "content": "<template>\n  <div class=\"core-dynamic-content-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content c1\" key=\"1\" v-if=\"!switcher\">\n        <div class=\"scroll-item\" v-for=\"n in nums1\" :key=\"n\">{{n}}</div>\n      </div>\n      <div class=\"scroll-content c2\" key=\"2\" v-else>\n        <div class=\"scroll-item\" v-for=\"n in nums2\" :key=\"n\">{{nums2 - n + 1}}</div>\n      </div>\n    </div>\n    <button class=\"btn\" @click=\"handleClick\">switch content element</button>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        nums1: 30,\n        nums2: 60,\n        switcher: false\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      handleClick() {\n        this.switcher = !this.switcher\n        // wait for Vue rerender\n        this.$nextTick(() => {\n          this.bs.refresh()\n        })\n      },\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          probeType: 3\n        })\n        this.bs.on('contentChanged', (content) => {\n          console.log('--- newContent ---')\n          console.log(content)\n        })\n        this.bs.on('scroll', () => {\n          console.log('scrolling-')\n        })\n        this.bs.on('scrollEnd', () => {\n          console.log('scrollingEnd')\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" scoped>\n\n.core-dynamic-content-container\n  text-align center\n  .scroll-wrapper\n    height 300px\n    overflow hidden\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\t.btn\n\t\tmargin 40px auto\n\t\tpadding 10px\n\t\tcolor #fff\n\t\tborder-radius 4px\n\t\tfont-size 20px\n\t\tbackground-color #666    \n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/freescroll.vue",
    "content": "<template>\n  <div class=\"free-scroll-container\">\n    <div class=\"free-scroll-wrapper\">\n      <div class=\"scroll-wrapper\" ref=\"wrapper\">\n        <div class=\"scroll-content\">\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\n  import BScroll from '@better-scroll/core'\n  export default {\n    mounted () {\n      this.init()\n    },\n    methods: {\n      init () {\n        this.bs = new BScroll(this.$refs.wrapper, {\n          freeScroll: true,\n          bounce: {\n            bottom: false,\n            left: false,\n            right: false,\n            top: false\n          }\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" scoped>\n.free-scroll-container\n  position: relative\n  width: 100%\n  height: 100%\n  .free-scroll-wrapper\n    position: relative\n    width: 100%\n    height: 100%\n    border: 1px solid rgb(96, 108, 113)\n    box-sizing: border-box\n    .scroll-wrapper\n      position: absolute\n      top: 0\n      left: 0\n      right: 0\n      bottom: 0\n      overflow: hidden\n      .scroll-content\n        background-color: #efeff4\n        width: 1500px\n        height: 1000px\n        p\n          font-size: 16px\n          padding: 20px\n          line-height: 200%\n          margin: 0\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/horizontal-rotated.vue",
    "content": "<template>\n  <div class=\"horizontal-rotated-container\">\n    <div class=\"description\">Flipping layout via CSS</div>\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"num in nums\" :key=\"num\">{{num}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        nums: 8\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          scrollX: true,\n          scrollY: false,\n          // v2.3.0\n          quadrant: 3 // rotate180\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n/* flipping by CSS */\n.scroll-wrapper\n  transform rotate(180deg)\n.horizontal-rotated-container\n  .description\n    margin-bottom 40px\n    text-align center\n  .scroll-wrapper\n    height 100px\n    width 250px\n    position relative\n    overflow hidden\n    white-space nowrap\n    margin 0 auto\n    border 1px solid #ccc\n    .scroll-content\n      display inline-block\n    .scroll-item\n      height 100px\n      width 100px\n      line-height 100px\n      font-size 24px\n      font-weight bold\n      text-align center\n      display inline-block\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/horizontal.vue",
    "content": "<template>\n  <div class=\"horizontal-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"(item, index) in emojis\" :key=\"index\">{{item}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        emojis: [\n          '👉🏼 😁 😂 🤣 👈🏼',\n          '😄 😅 😆 😉 😊',\n          '😫 😴 😌 😛 😜',\n          '👆🏻 😒 😓 😔 👇🏻',\n          '😑 😶 🙄 😏 😣',\n          '😞 😟 😤 😢 😭',\n          '🤑 😲 ☹️ 🙁 😖',\n          '👍 👎 👊 ✊ 🤛',\n          '☝️ ✋ 🤚 🖐 🖖',\n          '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n          '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n          '🌖 🌗 🌘 🌑 🌒'\n        ]\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          scrollX: true,\n          probeType: 3 // listening scroll event\n        })\n        this.bs.on('scrollStart', () => {\n          console.log('scrollStart-')\n        })\n        this.bs.on('scroll', ({ y }) => {\n          console.log('scrolling-')\n        })\n        this.bs.on('scrollEnd', () => {\n          console.log('scrollingEnd')\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.horizontal-container\n  .scroll-wrapper\n    position relative\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      display inline-block\n      text-align center\n      padding 0 10px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/specified-content.vue",
    "content": "<template>\n  <div class=\"core-specified-content-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"ignore-content\">\n        The Blue area is not taken as BetterScroll's content\n      </div>\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"n in nums\" :key=\"n\">{{n}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        nums: 30\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        window.bs = this.bs = new BScroll(this.$refs.scroll, {\n          specifiedIndexAsContent: 1,\n          probeType: 3\n        })\n        this.bs.on('scroll', () => {\n          console.log('scrolling-')\n        })\n        this.bs.on('scrollEnd', () => {\n          console.log('scrollingEnd')\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" scoped>\n\n.core-specified-content-container\n  text-align center\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    border 1px solid #42b983\n    .ignore-content\n      padding 20px\n      color white\n      font-size 20px\n      font-weight bold\n      background-color #2c3e50\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/core/vertical-rotated.vue",
    "content": "<template>\n  <div class=\"vertical-rotated-container\">\n    <div class=\"description\">Horizontal layout via CSS</div>\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"num in nums\" :key=\"num\">{{num}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n\n  export default {\n    data () {\n      return {\n        nums: 8\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          // v2.3.0\n          quadrant: 2 // rotate90\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n/* horizontal by CSS */\n.scroll-wrapper\n  transform rotate(90deg)\n.vertical-rotated-container\n  .description\n    text-align center\n  .scroll-wrapper\n    height 250px\n    width 100px\n    position relative\n    overflow hidden\n    margin 0 auto\n    border 1px solid #ccc\n    .scroll-item\n      height 100px\n      width 100px\n      line-height 100px\n      font-size 24px\n      font-weight bold\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/form/textarea.vue",
    "content": "<template>\n  <div class=\"textarea-container\">\n    <div ref=\"scroller\" class=\"textarea-wrapper\">\n      <div class=\"textarea-scroller\">\n        <ul class=\"textarea-list\">\n          <li v-for=\"i of data1\" :key=\"i\" class=\"textarea-list-item\">\n          </li>\n          <textarea class=\"textarea-text\" name=\"\" id=\"\" cols=\"30\" rows=\"10\">Manipulating this area can not make BetterScroll work.</textarea>\n          <li v-for=\"i of data2\" :key=\"i + 10\" class=\"textarea-list-item\">\n          </li>\n        </ul>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import Pullup from '@better-scroll/pull-up'\n\n  BScroll.use(Pullup)\n\n  export default {\n    data() {\n      return {\n        isPullUpLoad: false,\n        data1: 10,\n        data2: 10\n      }\n    },\n    mounted() {\n      this.initBscroll()\n    },\n    methods: {\n      initBscroll() {\n        this.bscroll = new BScroll(this.$refs.scroller, {\n          autoBlur: true // for blur\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.textarea-container\n  height: 100%\n.textarea-wrapper\n  height: 100%\n  padding: 0 10px\n  border: 1px solid #ccc\n  overflow: hidden\n.textarea-list\n  padding: 0\n  text-align: center\n.textarea-list-item\n  padding: 10px 0\n  height: 40px\n  list-style: none\n  border-bottom: 1px solid #ccc\n.textarea-text\n  border: 2px solid #62B791\n  border-radius: 5px\n  margin: 40px auto 0\n  line-height: 2\n  font-size: 20px\n  height: 200px\n</style>"
  },
  {
    "path": "packages/examples/vue/components/indicators/minimap.vue",
    "content": "<template>\n  <div class=\"minimap-container\">\n    <div class=\"scroll-wrapper\" ref=\"wrapper\">\n      <!-- maxWidth is used to overwrite vuepress default theme style -->\n      <!-- because this component is used in vuepress markdown as a demo -->\n      <img :style=\"{ maxWidth: 'none' }\" class=\"scroll-content\" :src=\"dinnerLink\" />\n    </div>\n    <div class=\"scroll-indicator\" ref=\"indicatorWrapper\">\n      <img class=\"scroll-indicator-bg\" :src=\"dinnerLink\">\n      <div class=\"scroll-indicator-handle\"></div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\nimport dinnerLink from './dinner.jpg'\n\nBScroll.use(Indicators)\n\nexport default {\n  created () {\n    this.dinnerLink = dinnerLink\n  },\n  mounted () {\n    this.initScroll()\n  },\n  methods: {\n    initScroll () {\n      this.scroll = new BScroll(this.$refs.wrapper, {\n        startX: -50,\n        startY: -50,\n        freeScroll: true,\n        bounce: false,\n        indicators: [\n          {\n            relationElement: this.$refs.indicatorWrapper,\n            // choose div.scroll-indicator-handle as indicatorHandle\n            relationElementHandleElementIndex: 1\n          }\n        ]\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.minimap-container\n  .scroll-wrapper\n    width 320px\n    height 180px\n    overflow hidden\n  .scroll-content\n    width 1920px\n    height 1080px\n  .scroll-indicator\n    margin-top 15px\n    width 320px\n    height 180px\n    position relative\n  .scroll-indicator-bg\n    position absolute\n    width 100%\n    height 100%\n  .scroll-indicator-handle\n    position absolute\n    border 1px solid white\n    box-shadow 0 0 5px white\n    width 64px\n    height 36px\n    z-index 1\n    background-color rgba(255, 255, 255, 0.3)\n</style>"
  },
  {
    "path": "packages/examples/vue/components/indicators/parallax-scroll.vue",
    "content": "<template>\n  <div class=\"parallax-scroll-container\">\n    <div class=\"parallax-scroll-box\">\n      <div class=\"scroll-wrapper\" ref=\"wrapper\">\n        <div class=\"scroll-content\" />\n      </div>\n      <div class=\"scroll-indicator stars1\" ref=\"indicator1\">\n        <div class=\"star1-bg\"></div>\n      </div>\n      <div class=\"scroll-indicator stars2\" ref=\"indicator2\">\n        <div class=\"star2-bg\"></div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\n\nBScroll.use(Indicators)\n\nexport default {\n  mounted () {\n    this.initScroll()\n  },\n  methods: {\n    initScroll () {\n      this.scroll = new BScroll(this.$refs.wrapper, {\n        freeScroll: true,\n        bounce: false,\n        indicators: [\n          {\n            relationElement: this.$refs.indicator1,\n            interactive: false,\n            ratio: 0.4\n          },\n          {\n            relationElement: this.$refs.indicator2,\n            interactive: false,\n            ratio: 0.2\n          }\n        ]\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.parallax-scroll-container\n  height 100%\n  .parallax-scroll-box\n    position relative\n    width 100%\n    height 100%\n    box-sizing border-box\n    border 1px solid #fe0\n  .scroll-wrapper\n    position absolute\n    z-index 3\n    top 0\n    right 0\n    bottom 0\n    left 0\n    overflow hidden\n  .scroll-content\n    width 100%\n    height 4000px\n    background: url('./galaxies1.png')\n  .scroll-indicator\n    position absolute\n    top 0\n    right 0\n    bottom 0\n    left 0\n    overflow hidden\n    &.stars1\n      z-index 2\n    &.stars2\n      z-index 1\n  .star1-bg\n    height 3000px\n    background: url('./galaxies2.png')\n  .star2-bg\n    height 2000px\n    background: url('./stars.jpg')\n</style>"
  },
  {
    "path": "packages/examples/vue/components/infinity/data/message.json",
    "content": "[\n  \"when you popState and actually being well, we expect it further\",\n  \"But I'm going to take care of ripping out my code in the fact that just something like that\",\n  \"And what we'll createdCallbacks than that you can still read what each one of this should go out\",\n  \"So just return Promise back and do this, the route equals\",\n  \"ah, let's do a clearRoutes it says I'm not going to do\",\n  \"At least trying new Promise\",\n  \"then, and then it's going to check what that\",\n  \"And we zoom in, then you can kind of set, except for a router\",\n  \"Now strictly today\",\n  \"I'm going to just takes an iterable as well be to add a visible\",\n  \"Anyway, so that we'll do a link\",\n  \"So what I'm going to minify this, so I'll just console\",\n  \"log data for now, just sometimes look at that\",\n  \"not then if we wanted to do position from the registerElements primed and red\",\n  \"That isn't get called\",\n  \"At all\",\n  \"No\",\n  \"Interesting that misc here\",\n  \"So what was a regular expression\",\n  \"Because once you get over doing a fancy techniques\",\n  \"And let's see\",\n  \"OK, we broke thing to do\",\n  \"Right\",\n  \"document\",\n  \"&quot; So\",\n  \"Yeah\",\n  \"\",\n  \"which is fine\",\n  \"And that we'll do sc\",\n  \"view\",\n  \"So what you draw the line where is it\",\n  \"Where is being run\",\n  \"I think, a million times look at it and styles an iteration, ES2015 update the content for is this\",\n  \"routes equals Array\",\n  \"from\",\n  \"Hm, that might be a trade\",\n  \"off, because we're just do an animation\",\n  \"in the attached\",\n  \"Look at this push\",\n  \"pull kind of useful to have layout root here is it\",\n  \"That by default, what we going to grab the\",\n  \"Yes\",\n  \"In router, I think, would let's say, for example\",\n  \"So let's make it can be just this the hour mark on the actual contents\",\n  \"We just loads though it was the way, a nice this\",\n  \"Are you would be a little bit more pretty raw, this is a day, dude\",\n  \"Border\",\n  \"radius, that\",\n  \"And I'm going to just do that will take something else\",\n  \"And thank you might now\",\n  \"That is the next time, I'm going to come into misc\",\n  \"And somebody actually not\",\n  \"source equals home\",\n  \"But if I was sending me to resolve where we go\",\n  \"All right\",\n  \"And it makes JavaScript\",\n  \"And I have run again\",\n  \"Normally a massive, as I said, this is always, I'm going to call the different [INAUDIBLE] Hm\",\n  \"Wow\",\n  \"We have happen on screen, and the otherwise, don't want\",\n  \"Yeah, and forth in the new path\",\n  \"So we don't you use that might very wrong\",\n  \"But in a customary bug\",\n  \"Don't forget to hidden or display to none, things like a race when you are actually really long time I want to tell that is where you go\",\n  \"And that work\",\n  \"Yeah, and I'm going to do today\",\n  \"I had misc are all the create one of the performance stuff\",\n  \"But if you had lots of tea\",\n  \"Yeah\",\n  \"Now we're going to come in\",\n  \"But did working as intended it\",\n  \"So we can be able to be watching it straightforward slash\",\n  \"And that, I think that will be all the like since we are valid concept for this, the root of this called HTML5 routing, which I don't know\",\n  \"I just feels OK, but hopefully, and opacity 0, and it's just put a z\",\n  \"index of 1 on that's going to be sort of handling of attachedCallback, and we want to transform scale very well be true for them is amazing, like across from the new one that\",\n  \"You know\",\n  \"Yeah, we could see now, all being we won't do this thing today\",\n  \"And so this is a current view\",\n  \"We have a question ties in\",\n  \"Why not\",\n  \"source equals router, why not\",\n  \"And I think that we'd probably, if we've already to allow it to be the thing\",\n  \"Oh, all right, so we get it, because I have to juggle it all\",\n  \"No\",\n  \"I feel I agree\",\n  \"It would actually get it, because otherwise, we still have this\",\n  \"routes\",\n  \"keys\",\n  \"So this is a layout boundary\",\n  \"It's the cause\",\n  \"Yeah, 3 pixels\",\n  \"OK\",\n  \"So since that's true\",\n  \"And this stuff\",\n  \"And that work\",\n  \"Good point, or strict, and then the URL, changed\",\n  \"But I'm going to, let's see, what we're any\",\n  \"So the new view, think about\",\n  \"And then we've defer, why not\",\n  \"Let's fail\",\n  \"So this newView, newView is never watching is I was that\",\n  \"so that it's a compass\",\n  \"Oh\",\n  \"North, east, south, we called, all be no ES\",\n  \"anything\",\n  \"What I'm curious about your question here\",\n  \"And I'm going to say\",\n  \"so let's see\",\n  \"So let's see\",\n  \"So we'll say from this animations that we want to do this so that this point\",\n  \"So we want us to cover next week\",\n  \"We can actually\",\n  \"But that they've all been set it\",\n  \"Yeah\",\n  \"And at the top and misc here\",\n  \"But it will be run into a bit different sections\",\n  \"And I think you'd want each of there's no DOM tree reason\",\n  \"Well, yeah\",\n  \"OK, so we have a couple of click for clicks\",\n  \"And so if we see about this\",\n  \"So what I think things that I really good start\",\n  \"script tags at home, kids\",\n  \"Don't do this file to actually\",\n  \"Woo\",\n  \"I made, sir\",\n  \"So again, particular line of the\",\n  \"let's call it sc for Supercharged\",\n  \"There's no\",\n  \"It's a compass\",\n  \"Oh\",\n  \"right\",\n  \"newView, newView is the simplicity at this one anything below 2015, right\",\n  \"It broke\",\n  \"OK, let's see\",\n  \"So we're going to removeEventListener\",\n  \"You are the nicest\",\n  \"something that you know, we'll create that doesn't necessarily end up with something new to these pages\",\n  \"In router\",\n  \"And certainly, as I said, you could usually just delete the constructor but createdCallback\",\n  \"Oh, well, let link of the\",\n  \"Yes\",\n  \"If we had to do is I want us to come up writing apps, it can actually, this push\",\n  \"pull kind of data, which version of something\",\n  \"So what they can be about view or something that have a thing to do a trade\",\n  \"off because you've got memory constraints and all these function\",\n  \"So let's see if\",\n  \"oh, do we wanted to do this\",\n  \"If you're attach, what we'd want to know\",\n  \"That is important think in so that goes to control of [\",\n  \"UI \",\n  \"] transitions, particular expression\",\n  \"Right, so the otherwise, it should also work on the layout, which might because we're actually remind yourselves that I can do it\",\n  \"Yeah\",\n  \"So that, in theory, place all the content as well when that have new ideas\",\n  \"So this should be a class list, we'll create one of these, what we'll do is I want to do\",\n  \"All right, bottom, left\",\n  \"Do you have definitely\",\n  \"So when the mindset off chaining [INAUDIBLE] out of the same index HTML elements\",\n  \"Views\",\n  \"Yeah\",\n  \"So I'm going to createRoutes, wee, clearRoutes equals static\",\n  \"Let's do this, status is generally work\",\n  \"So that's why I was building the nicest\",\n  \"I'll tell you what we want to come into the panels\",\n  \"On all of ES2015 updates on the path name\",\n  \"Because it's an iterate what they see\",\n  \"I'm going to do\",\n  \"We'll do that\",\n  \"And hopefully, you're here in slash about view but we're going to be whichever view was the new view is that\",\n  \"so that the event that isn't get called, all subscribing to do today\",\n  \"And then we're just delete the JavaScript language\",\n  \"Yeah, and we need to extends HTMLElement\",\n  \"And we app where we actually uncanny valid concept for the out animation\",\n  \"duration\",\n  \"count in one tends HTML, I think, would then we've defer, why not\",\n  \"Let's see what's good on here\",\n  \"So if you say layout, for example\",\n  \"Yes, so one of its scope\",\n  \"What we want to do, I supposed to find out\",\n  \"The defer mean to your Custom Elements JavaScript says we don't have\",\n  \"We don't want to say this, so one that you click back to then dot the even though it\",\n  \"So there a createdCallback, so we never being us\",\n  \"That doesn't it\",\n  \"Right\",\n  \"All right\",\n  \"That should\",\n  \"Oh no, Array\",\n  \"from\",\n  \"Hm, it shouldNotMakeMoreOutPromises\",\n  \"And then let's do that is purely for simplicity at this\",\n  \"I don't takes too longer and I will say this\",\n  \"routes\",\n  \"because it matches the current ones will now needs to be run against that going to say const view back\",\n  \"And then what the createRoute\",\n  \"That's what I think\",\n  \"So we have to transitions, particular if branch of this, you're giving us way too much better\",\n  \"So since the layout, OK\",\n  \"I think we'll create objects anymore\",\n  \"You let us know what I'm going to do is I'm going to do is let's just find out\",\n  \"createdCallbacks\",\n  \"So if view\",\n  \"I could do if we don't want to make a nav\",\n  \"So I'm going to do that\",\n  \"Super\",\n  \"route\",\n  \"So for this, right now, all the like shouldNotMakeMoreOutPromise\",\n  \"resolve\",\n  \"Same for the power of Promise, right\",\n  \"Because why not\",\n  \"Let's give it or not\",\n  \"The defer also means that the state by selecting the view\",\n  \"No\",\n  \"Interesting\",\n  \"So the brand\",\n  \"new thing\",\n  \"So let's see, so we do that\",\n  \"All being well, we end with an actually hoping I will be remove this\",\n  \"Are you this\",\n  \"So we want to do that, actually just kind of amazing\",\n  \"You know\",\n  \"Yeah\",\n  \"\",\n  \"which is the current view was the new one that's a layout\",\n  \"I don't you ask the question ties in it is when it's like a progressive to deal with, with contain strict\",\n  \"now here\",\n  \"And I'm going to us\",\n  \"So onChanged\",\n  \"Yeah\",\n  \"Because of the this\",\n  \"is\",\n  \"the\",\n  \"active\",\n  \"view\",\n  \"And we are building the routes equals this\",\n  \"But when the view first time we create that isn't it\",\n  \"Right\",\n  \"Yeah, that is amazing\",\n  \"And I think, a more bugs\",\n  \"Yeah, I want it to updating to do that I have new view, and some Promise, we can actually can do here\",\n  \"This is Paul\",\n  \"Hi\",\n  \"This time I write bugs, don't like this is actual lifecycle called ES6\",\n  \"ES2016 was doing that's why I wanted to say\",\n  \"currentView will be fast because\",\n  \"You know what, in the back to the current view\",\n  \"And then we'll say return\",\n  \"One of the panels\",\n  \"OK\",\n  \"Come of that stuff out\",\n  \"Should that the evaluation from 100\",\n  \"no, should add that kind of got allowing that back out, right\",\n  \"newView, newView, what we're kind of got these views that you, very wrong\",\n  \"But if you about using there\",\n  \"Because the nav has disappear ago, it was the keyword for all the regular expression and execution of a router\",\n  \"Now you know, over that, in there\",\n  \"Let's do that there we already got ourselves some of the way to go\",\n  \"And it matches the new one for that\",\n  \"Yeah\",\n  \"And certain time gaps, think it's an animating to put a route for some reason\",\n  \"view\",\n  \"Figure out things simplicity at this point\",\n  \"So what we're being a little bit of a pickle over right now we've deep\",\n  \"linked that could want it to be that\",\n  \"So let's just feels very interactions back in so this\",\n  \"newView\",\n  \"Yeah\",\n  \"And apparent, what we'd want each one of all the debugger standard one\",\n  \"So this way, it should add the visible\",\n  \"And we're pretty raw, there will be find out notionally, the code, it's fine, it's fail\",\n  \"So the question\",\n  \"Yeah, so we could see now them to makes Jav\"\n]\n"
  },
  {
    "path": "packages/examples/vue/components/infinity/default.vue",
    "content": "<template>\n  <div class=\"infinity\">\n    <div class=\"template\">\n      <li ref=\"message\" class=\"infinity-item\">\n        <img class=\"infinity-avatar\" width=\"48\" height=\"48\">\n        <div class=\"infinity-bubble\">\n          <p></p>\n          <img width=\"300\" height=\"300\">\n          <div class=\"infinity-meta\">\n            <time class=\"infinity-posted-date\"></time>\n          </div>\n        </div>\n      </li>\n      <li ref=\"tombstone\" class=\"infinity-item tombstone\">\n        <img class=\"infinity-avatar\" width=\"48\" height=\"48\" :src=\"require('./image/unknown.jpg')\">\n        <div class=\"infinity-bubble\">\n          <p></p>\n          <p></p>\n          <p></p>\n          <div class=\"infinity-meta\">\n            <time class=\"infinity-posted-date\"></time>\n          </div>\n        </div>\n      </li>\n    </div>\n    <div ref=\"chat\" class=\"infinity-timeline\">\n      <ul>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import InfinityScroll from '@better-scroll/infinity'\n  import message from './data/message.json'\n\n  BScroll.use(InfinityScroll)\n\n  const NUM_AVATARS = 4\n  const NUM_IMAGES = 77\n  const INIT_TIME = new Date().getTime()\n\n  function getItem(id) {\n    function pickRandom(a) {\n      return a[Math.floor(Math.random() * a.length)]\n    }\n\n    return new Promise(function (resolve) {\n      let item = {\n        id: id,\n        avatar: Math.floor(Math.random() * NUM_AVATARS),\n        self: Math.random() < 0.1,\n        image: Math.random() < 1.0 / 20 ? Math.floor(Math.random() * NUM_IMAGES) : '',\n        time: new Date(Math.floor(INIT_TIME + id * 20 * 1000 + Math.random() * 20 * 1000)),\n        message: pickRandom(message)\n      }\n      if (item.image === '') {\n        resolve(item)\n      } else {\n        let image = new Image()\n        image.src = require(`./image/image${item.image}.jpg`)\n        image.addEventListener('load', function () {\n          item.image = image\n          resolve(item)\n        })\n        image.addEventListener('error', function () {\n          item.image = ''\n          resolve(item)\n        })\n      }\n    })\n  }\n\n  export default {\n    name: 'infinity',\n    created() {\n      this.nextItem = 0\n      this.pageNum = 0\n    },\n    mounted() {\n      this.createInfinityScroll()\n    },\n    methods: {\n      createInfinityScroll() {\n        this.scroll = new BScroll(this.$refs.chat, {\n          infinity: {\n            render: (item, div) => {\n              div = div || this.$refs.message.cloneNode(true)\n              div.dataset.id = item.id\n              div.querySelector('.infinity-avatar').src = require(`./image/avatar${item.avatar}.jpg`)\n              div.querySelector('.infinity-bubble p').textContent = item.id + '  ' + item.message\n              div.querySelector('.infinity-bubble .infinity-posted-date').textContent = item.time.toString()\n\n              let img = div.querySelector('.infinity-bubble img')\n              if (item.image !== '') {\n                img.style.display = ''\n                img.src = item.image.src\n                img.width = item.image.width\n                img.height = item.image.height\n              } else {\n                img.src = ''\n                img.style.display = 'none'\n              }\n\n              if (item.self) {\n                div.classList.add('infinity-from-me')\n              } else {\n                div.classList.remove('infinity-from-me')\n              }\n              return div\n            },\n            createTombstone: () => {\n              return this.$refs.tombstone.cloneNode(true)\n            },\n            fetch: (count) => {\n              // Fetch at least 30 or count more objects for display.\n              count = Math.max(30, count)\n              return new Promise((resolve, reject) => {\n                // Assume 50 ms per item.\n                setTimeout(() => {\n                  if (++this.pageNum > 20) {\n                    resolve(false)\n                  } else {\n                    console.log('pageNum', this.pageNum)\n                    let items = []\n                    for (let i = 0; i < Math.abs(count); i++) {\n                      items[i] = getItem(this.nextItem++)\n                    }\n                    resolve(Promise.all(items))\n                  }\n                }, 500)\n              })\n            }\n          }\n        })\n        this.scroll.on('scroll', () => {\n          console.log('is scrolling')\n        })\n        this.scroll.on('scrollEnd', () => {\n          console.log('scrollEnd')\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n  .infinity\n    height: 100%\n    .template\n      display: none\n\n  .infinity-timeline\n    position: relative\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n    will-change: transform\n    background-color: #efeff5\n\n  .infinity-timeline > ul\n    position: relative\n    -webkit-backface-visibility: hidden\n    -webkit-transform-style: flat\n\n  .infinity-item\n    display: flex\n    left: 0\n    padding: 10px 0\n    width: 100%\n    contain: layout\n    will-change: transform\n    list-style: none\n  \n  .infinity-avatar\n    border-radius: 500px\n    margin-left: 20px\n    margin-right: 6px\n    min-width: 48px\n\n  .infinity-item\n    p\n      margin: 0\n      word-wrap: break-word\n      font-size: 13px\n\n  .infinity-item.tombstone\n    p\n      width: 100%\n      height: 0.5em\n      background-color: #ccc\n      margin: 0.5em 0\n\n  .infinity-bubble img \n    max-width: 100%\n    height: auto\n\n  .infinity-bubble \n    padding: 7px 10px\n    color: #333\n    background: #fff\n    /*box-shadow: 0 3px 2px rgba(0, 0, 0, 0.1)*/\n    position: relative\n    max-width: 420px\n    min-width: 80px\n    margin: 0 5px\n\n  .infinity-bubble::before \n    content: ''\n    border-style: solid\n    border-width: 0 10px 10px 0\n    border-color: transparent #fff transparent transparent\n    position: absolute\n    top: 0\n    left: -10px\n\n  .infinity-meta \n    font-size: 0.8rem\n    color: #999\n    margin-top: 3px\n\n  .infinity-from-me \n    justify-content: flex-end\n\n  .infinity-from-me .infinity-avatar \n    order: 1\n    margin-left: 6px\n    margin-right: 20px\n\n  .infinity-from-me .infinity-bubble \n    background: #F9D7FF\n\n  .infinity-from-me .infinity-bubble::before \n    left: 100%\n    border-width: 10px 10px 0 0\n    /*border-color: #F9D7FF transparent transparent transparent*/\n\n  .infinity-state \n    display: none\n\n  .infinity-invisible \n    display: none\n  \n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/horizontal-scroll.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-horizontal-scroll\">\n    <div class=\"mouse-wheel-wrapper\" ref=\"scroll\">\n      <div class=\"mouse-wheel-content\">\n        <div class=\"mouse-wheel-item\" v-for=\"n in 100\" :key=\"n\">{{n}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n\n  BScroll.use(MouseWheel)\n\n  export default {\n    mounted() {\n      this.init()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          scrollX: true,\n          scrollY: false,\n          mouseWheel: true\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" scoped>\n\n.mouse-wheel-horizontal-scroll\n  .mouse-wheel-wrapper\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .mouse-wheel-content\n      display inline-block\n      .mouse-wheel-item\n        height 50px\n        line-height 50px\n        font-size 24px\n        display inline-block\n        text-align center\n        padding 0 20px\n        &:nth-child(2n)\n          background-color #C3D899\n        &:nth-child(2n+1)\n          background-color #F2D4A7\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/horizontal-slide.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-horizontal-slide\">\n    <div class=\"slide-container\">\n      <div class=\"slide-wrapper\" ref=\"slide\">\n        <div class=\"slide-content\">\n          <div class=\"slide-page page1\">page 1</div>\n          <div class=\"slide-page page2\">page 2</div>\n          <div class=\"slide-page page3\">page 3</div>\n          <div class=\"slide-page page4\">page 4</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"(item, index) in 4\"\n          :key=\"index\"\n          :class=\"{'active': currentPageIndex === index}\"></span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n\n  BScroll.use(Slide)\n  BScroll.use(MouseWheel)\n\n  export default {\n    data() {\n      return {\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        this.slide = new BScroll(this.$refs.slide, {\n          scrollX: true,\n          scrollY: false,\n          slide: {\n            loop: true,\n            threshold: 100\n          },\n          useTransition: false,\n          momentum: false,\n          bounce: false,\n          stopPropagation: true,\n          mouseWheel: {\n            speed: 2,\n            invert: false,\n            easeTime: 300\n          }\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n      },\n      _onScrollEnd() {\n        let pageIndex = this.slide.getCurrentPage().pageX\n        this.currentPageIndex = pageIndex\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.mouse-wheel-horizontal-slide\n  .slide-container\n    position relative\n  .slide-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/picker.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-picker\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"show\">\n          <span class=\"open\">{{selectedText}}</span>\n      </li>\n    </ul>\n    <transition name=\"picker-fade\">\n      <div class=\"picker\" v-show=\"state===1\" @touchmove.prevent @click=\"_cancel\">\n        <transition name=\"picker-move\">\n          <div class=\"picker-panel\" v-show=\"state===1\" @click.stop>\n            <div class=\"picker-choose border-bottom-1px\">\n              <span class=\"cancel\" @click=\"_cancel\">Cancel</span>\n              <span class=\"confirm\" @click=\"_confirm\">Confirm</span>\n              <h1 class=\"picker-title\">Title</h1>\n            </div>\n            <div class=\"picker-content\">\n              <div class=\"mask-top border-bottom-1px\"></div>\n              <div class=\"mask-bottom border-top-1px\"></div>\n              <div class=\"wheel-wrapper\" ref=\"wheelWrapper\">\n                <div class=\"wheel\">\n                  <ul class=\"wheel-scroll\">\n                    <li\n                      v-for=\"(item, index) in pickerData\" :key=\"index\"\n                      :class=\"{'wheel-disabled-item':item.disabled}\"\n                      class=\"wheel-item\">{{item.text}}</li>\n                  </ul>\n                </div>\n              </div>\n            </div>\n            <div class=\"picker-footer\"></div>\n          </div>\n        </transition>\n      </div>\n    </transition>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  BScroll.use(Wheel)\n  BScroll.use(MouseWheel)\n\n  const STATE_HIDE = 0\n  const STATE_SHOW = 1\n\n  const COMPONENT_NAME = 'picker'\n  const EVENT_SELECT = 'select'\n  const EVENT_CANCEL = 'cancel'\n  const EVENT_CHANGE = 'change'\n\n  const DATA = [\n    {\n      text: 'Venomancer',\n      value: 1,\n      disabled: 'wheel-disabled-item'\n    }, {\n      text: 'Nerubian Weaver',\n      value: 2\n    },\n    {\n      text: 'Spectre',\n      value: 3\n    },\n    {\n      text: 'Juggernaut',\n      value: 4\n    },\n    {\n      text: 'Karl',\n      value: 5\n    },\n    {\n      text: 'Zeus',\n      value: 6\n    },\n    {\n      text: 'Witch Doctor',\n      value: 7\n    }, {\n      text: 'Lich',\n      value: 8\n    },\n    {\n      text: 'Oracle',\n      value: 9\n    },\n    {\n      text: 'Earthshaker',\n      value: 10\n    }\n  ]\n\n  export default {\n    name: COMPONENT_NAME,\n    data() {\n      return {\n        state: STATE_HIDE,\n        selectedIndex: 2,\n        selectedText: 'open',\n        pickerData: DATA\n      }\n    },\n    methods: {\n      _confirm() {\n        if (this._isMoving()) {\n          return\n        }\n        this.hide()\n\n        const currentSelectedIndex = this.wheel.getSelectedIndex()\n        this.selectedIndex = currentSelectedIndex\n        this.selectedText = this.pickerData[this.selectedIndex].text\n        this.$emit(EVENT_SELECT, currentSelectedIndex)\n      },\n      _cancel() {\n        this.hide()\n        this.$emit(EVENT_CANCEL)\n      },\n      _isMoving() {\n        return this.wheel.pending\n      },\n      show() {\n        if (this.state === STATE_SHOW) {\n          return\n        }\n        this.state = STATE_SHOW\n        if (!this.wheel) {\n          // waiting for DOM rendered\n          this.$nextTick(() => {\n            const wrapper = this.$refs.wheelWrapper.children[0]\n            this._createWheel(wrapper)\n          })\n        } else {\n          this.wheel.enable()\n          this.wheel.wheelTo(this.selectedIndex)\n        }\n      },\n      hide() {\n        this.state = STATE_HIDE\n        // if wheel is in animation, clear timer in it\n        this.wheel.disable()\n      },\n      refresh() {\n        this.$nextTick(() => {\n          this.wheel.refresh()\n        })\n      },\n      _createWheel(wheelWrapper) {\n        if (!this.wheel) {\n          this.wheel = new BScroll(wheelWrapper, {\n            mouseWheel: true,\n            wheel: {\n              selectedIndex: this.selectedIndex,\n              wheelWrapperClass: 'wheel-scroll',\n              wheelItemClass: 'wheel-item',\n              wheelDisabledItemClass: 'wheel-disabled-item'\n            },\n            probeType: 3,\n            useTransition: true,\n          })\n          this.wheel.on('scrollEnd', () => {\n            this.$emit(EVENT_CHANGE, this.wheel.getSelectedIndex())\n          })\n        } else {\n          this.wheel.refresh()\n        }\n        return this.wheel\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n  /* reset */\n  ul\n    list-style none\n    padding 0\n\n  .example-list\n    display: flex\n    justify-content: space-between\n    flex-wrap: wrap\n    margin: 2rem\n\n    .example-item\n      background-color white\n      padding: 0.8rem\n      border: 1px solid rgba(0, 0, 0, .1)\n      box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n      text-align: center\n      margin-bottom: 1rem\n      flex: 1\n      &.placeholder\n        visibility: hidden\n        height: 0\n        margin: 0\n        padding: 0\n\n  .picker\n    position: fixed\n    left: 0\n    top: 0\n    z-index: 100\n    width: 100%\n    height: 100%\n    overflow: hidden\n    text-align: center\n    font-size: 14px\n    background-color: rgba(37, 38, 45, .4)\n    &.picker-fade-enter, &.picker-fade-leave-active\n      opacity: 0\n    &.picker-fade-enter-active, &.picker-fade-leave-active\n      transition: all .3s ease-in-out\n\n    .picker-panel\n      position: absolute\n      z-index: 600\n      bottom: 0\n      width: 100%\n      height: 273px\n      background: white\n      &.picker-move-enter, &.picker-move-leave-active\n        transform: translate3d(0, 273px, 0)\n      &.picker-move-enter-active, &.picker-move-leave-active\n        transition: all .3s ease-in-out\n      .picker-choose\n        position: relative\n        height: 60px\n        color: #999\n        .picker-title\n          margin: 0\n          line-height: 60px\n          font-weight: normal\n          text-align: center\n          font-size: 18px\n          color: #333\n        .confirm, .cancel\n          position: absolute\n          top: 6px\n          padding: 16px\n          font-size: 14px\n        .confirm\n          right: 0\n          color: #007bff\n          &:active\n            color: #5aaaff\n        .cancel\n          left: 0\n          &:active\n            color: #c2c2c2\n      .picker-content\n        position: relative\n        top: 20px\n        .mask-top, .mask-bottom\n          z-index: 10\n          width: 100%\n          height: 68px\n          pointer-events: none\n          transform: translateZ(0)\n        .mask-top\n          position: absolute\n          top: 0\n          background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n        .mask-bottom\n          position: absolute\n          bottom: 1px\n          background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .wheel-wrapper\n        display: flex\n        padding: 0 16px\n        .wheel\n          -ms-flex: 1 1 0.000000001px\n          -webkit-box-flex: 1\n          -webkit-flex: 1\n          flex: 1\n          -webkit-flex-basis: 0.000000001px\n          flex-basis: 0.000000001px\n          width: 1%\n          height: 173px\n          overflow: hidden\n          font-size: 18px\n          .wheel-scroll\n            padding: 0\n            margin-top: 68px\n            line-height: 36px\n            list-style: none\n            .wheel-item\n              list-style: none\n              height: 36px\n              overflow: hidden\n              white-space: nowrap\n              color: #333\n              &.wheel-disabled-item\n                opacity: .2;\n    .picker-footer\n      height: 20px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/pulldown.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-pulldown\">\n    <div ref=\"scroll\" class=\"pulldown-wrapper\">\n      <div class=\"pulldown-content\">\n        <div class=\"pulldown-tips\">\n          <div v-show=\"beforePullDown\">\n            <span>Pull Down and refresh</span>\n          </div>\n          <div v-show=\"!beforePullDown\">\n            <div v-show=\"isPullingDown\">\n              <span>Loading...</span>\n            </div>\n            <div v-show=\"!isPullingDown\"><span>Refresh success</span></div>\n          </div>\n        </div>\n        <ul class=\"pulldown-list\">\n          <li v-for=\"i of dataList\" :key=\"i\" class=\"pulldown-list-item\">\n            {{ `I am item ${i} ` }}\n          </li>\n        </ul>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import PullDown from '@better-scroll/pull-down'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n\n  BScroll.use(PullDown)\n  BScroll.use(MouseWheel)\n\n  function generateData() {\n    const BASE = 30\n    const begin = BASE * STEP\n    const end = BASE * (STEP + 1)\n    let ret = []\n    for(let i = end; i > begin; i--) {\n      ret.push(i)\n    }\n    return ret\n  }\n\n  const TIME_BOUNCE = 800\n  const TIME_STOP = 600\n  const REQUEST_TIME = 1000\n  const THRESHOLD = 70\n  const STOP = 56\n  let STEP = 0\n\n  export default {\n    data() {\n      return {\n        beforePullDown: true,\n        isPullingDown: false,\n        dataList: generateData()\n      }\n    },\n    mounted() {\n      this.initBscroll()\n    },\n    methods: {\n      initBscroll() {\n        this.bscroll = new BScroll(this.$refs.scroll, {\n          scrollY: true,\n          bounceTime: TIME_BOUNCE,\n          mouseWheel: true,\n          pullDownRefresh: {\n            threshold: THRESHOLD,\n            stop: STOP\n          }\n        })\n\n        this.bscroll.on('pullingDown', this.pullingDownHandler)\n        this.bscroll.on('scroll', this.scrollHandler)\n        this.bscroll.on('scrollEnd', (e) => {\n          console.log('scrollEnd')\n        })\n      },\n      scrollHandler(pos) {\n        console.log(pos.y)\n      },\n      async pullingDownHandler() {\n        console.log('trigger pullDown')\n        this.beforePullDown = false\n        this.isPullingDown = true\n        STEP += 1\n\n        await this.requestData()\n\n        this.isPullingDown = false\n        this.finishPullDown()\n      },\n      async finishPullDown() {\n        const stopTime = TIME_STOP\n        await new Promise(resolve => {\n          setTimeout(() => {\n            this.bscroll.finishPullDown()\n            resolve()\n          }, stopTime)\n        })\n        setTimeout(() => {\n          this.beforePullDown = true\n          this.bscroll.refresh()\n        }, TIME_BOUNCE)\n      },\n      async requestData() {\n        try {\n          const newData = await this.ajaxGet(/* url */)\n          this.dataList = newData.concat(this.dataList)\n        } catch (err) {\n          // handle err\n          console.log(err)\n        }\n      },\n      ajaxGet(/* url */) {\n        return new Promise(resolve => {\n          setTimeout(() => {\n            const dataList = generateData()\n            resolve(dataList)\n          }, REQUEST_TIME)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.mouse-wheel-pulldown\n  height: 100%\n  .pulldown-wrapper\n    position: relative\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pulldown-list\n    padding: 0\n  .pulldown-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pulldown-tips\n    position: absolute\n    width: 100%\n    padding: 20px\n    box-sizing: border-box\n    transform: translateY(-100%) translateZ(0)\n    text-align: center\n    color: #999\n</style>"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/pullup.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-pullup\">\n    <div ref=\"scroll\" class=\"pullup-wrapper\">\n      <div class=\"pullup-content\">\n        <ul class=\"pullup-list\">\n          <li v-for=\"i of data\" :key=\"i\" class=\"pullup-list-item\">\n            {{ i % 5 === 0 ? 'use your mousewheel please 👆🏻' : `I am item ${i} `}}\n          </li>\n        </ul>\n        <div class=\"pullup-tips\">\n          <div v-if=\"!isPullUpLoad\" class=\"before-trigger\">\n            <span class=\"pullup-txt\">mousewheel trigger pullingup and load more</span>\n          </div>\n          <div v-else class=\"after-trigger\">\n            <span class=\"pullup-txt\">Loading...</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import Pullup from '@better-scroll/pull-up'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n\n  BScroll.use(Pullup)\n  BScroll.use(MouseWheel)\n\n  export default {\n    data() {\n      return {\n        isPullUpLoad: false,\n        data: 30\n      }\n    },\n    mounted() {\n      this.initBscroll()\n    },\n    methods: {\n      initBscroll() {\n        this.scroll = new BScroll(this.$refs.scroll, {\n          probeType: 3,\n          pullUpLoad: true,\n          mouseWheel: true\n        })\n\n        this.scroll.on('pullingUp', this.pullingUpHandler)\n      },\n      async pullingUpHandler() {\n        this.isPullUpLoad = true\n\n        await this.requestData()\n\n        this.scroll.finishPullUp()\n        this.scroll.refresh()\n        this.isPullUpLoad = false\n      },\n      async requestData() {\n        try {\n          const newData = await this.ajaxGet(/* url */)\n          this.data += newData\n        } catch (err) {\n          // handle err\n          console.log(err)\n        }\n      },\n      ajaxGet(/* url */) {\n        return new Promise(resolve => {\n          setTimeout(() => {\n            resolve(20)\n          }, 1000)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.mouse-wheel-pullup\n  height: 100%\n  .pullup-wrapper\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pullup-list\n    padding: 0\n  .pullup-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pullup-tips\n    padding: 20px\n    text-align: center\n    color: #999\n</style>"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/vertical-scroll.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-vertical-scroll\">\n    <div class=\"mouse-wheel-wrapper\" ref=\"scroll\">\n      <div class=\"mouse-wheel-content\">\n        <div class=\"mouse-wheel-item\" v-for=\"n in 100\" :key=\"n\">{{n}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  BScroll.use(MouseWheel)\n\n  export default {\n    mounted() {\n      this.init()\n    },\n    methods: {\n      init() {\n        this.scroll = new BScroll(this.$refs.scroll, {\n          mouseWheel: true\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.mouse-wheel-vertical-scroll\n  .mouse-wheel-wrapper\n    height 400px\n    overflow hidden\n    .mouse-wheel-item\n      height 50px\n      line-height 50px\n      font-size 20px\n      font-weight bold\n      text-align center\n      &:nth-child(2n)\n        background-color #C3D899\n      &:nth-child(2n+1)\n        background-color #F2D4A7\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/mouse-wheel/vertical-slide.vue",
    "content": "<template>\n  <div class=\"mouse-wheel-slide-vertical\">\n    <div class=\"slide-container\">\n      <div class=\"slide-wrapper\" ref=\"slide\">\n        <div class=\"slide-content\">\n          <div class=\"slide-page page1\">page 1</div>\n          <div class=\"slide-page page2\">page 2</div>\n          <div class=\"slide-page page3\">page 3</div>\n          <div class=\"slide-page page4\">page 4</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"(item, index) in 4\"\n          :key=\"index\"\n          :class=\"{'active': currentPageIndex === index}\"></span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  BScroll.use(MouseWheel)\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        this.slide = new BScroll(this.$refs.slide, {\n          scrollX: false,\n          scrollY: true,\n          slide: {\n            loop: true,\n            threshold: 100\n          },\n          mouseWheel: true,\n          momentum: false,\n          bounce: false,\n          stopPropagation: true\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n      },\n      _onScrollEnd() {\n        let pageIndex = this.slide.getCurrentPage().pageY\n        this.currentPageIndex = pageIndex\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.mouse-wheel-slide-vertical\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .slide-container\n    position relative\n    height 100%\n    font-size 0\n  .slide-wrapper\n    height 100%\n    overflow hidden\n    .slide-page\n      display inline-block\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #D6EADF\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    right 4px\n    top 50%\n    transform translateY(-50%)\n    .dot\n      display block\n      margin 4px 0\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        height  20px\n        border-radius 5px\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/movable/default.vue",
    "content": "<template>\n  <div class=\"movable-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"(item, index) in emojis\" :key=\"index\">{{item}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n\n  BScroll.use(Movable)\n\n  export default {\n    data () {\n      return {\n        emojis: [\n          '😀 😁 😂 🤣 😃',\n          '😄 😅 😆 😉 😊',\n          '😫 😴 😌 😛 😜',\n          '👆🏻 😒 😓 😔 👇🏻'\n        ]\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          startX: 20,\n          startY: 20\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.movable-container\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      width 220px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/movable/multi-content-scale.vue",
    "content": "<template>\n  <div class=\"movable-multi-content-scale-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content content1\">\n        <figure>\n          <figcaption>Swordsman</figcaption>\n          <img class=\"picture\" :src=\"swordsmanLink\"\n         alt=\"ftstr\">\n        </figure>\n      </div>\n      <div class=\"scroll-content content2\">\n        <figure>\n          <figcaption>The Witch</figcaption>\n          <img class=\"picture\" :src=\"witchLink\"\n         alt=\"qos_crop\">\n        </figure>\n      </div>\n    </div>\n    <button class=\"btn\" @click=\"handleClick\">Put The Witch at right-bottom corner</button>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n  import Zoom from '@better-scroll/zoom'\n  import SwordsmanLink from './ftstr.png'\n  import WitchLink from './qos_crop.png'\n\n  BScroll.use(Movable)\n  BScroll.use(Zoom)\n\n  export default {\n    created() {\n      this.swordsmanLink = SwordsmanLink\n      this.witchLink = WitchLink\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs1.destroy()\n      this.bs2.destroy()\n    },\n    methods: {\n      handleClick() {\n        this.bs2.putAt('right', 'bottom', 500)\n      },\n      init() {\n        this.bs1 = new BScroll(this.$refs.scroll, {\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          zoom: {\n            start: 1.2,\n            min: 0.5,\n            max: 3\n          }\n        })\n        this.bs1.putAt('center', 'center', 0)\n        this.bs2 = new BScroll(this.$refs.scroll, {\n          specifiedIndexAsContent: 1,\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          startY: 150,\n          zoom: {\n            start: 1,\n            min: 0.5,\n            max: 3\n          }\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.movable-multi-content-scale-container\n\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    position relative\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      position absolute\n      top 0\n      left 0\n      width 200px\n      figure\n        margin 0\n      figcaption\n        font-weight bold\n        margin-bottom 5px\n        text-align center\n        color #ea4c89\n      .picture\n        width 200px\n        height 150px\n        border-radius 10px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n  .btn\n    margin 40px auto\n    padding 10px\n    color #fff\n    border-radius 4px\n    font-size 20px\n    background-color #666\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/movable/multi-content.vue",
    "content": "<template>\n  <div class=\"movable-multi-content-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content content1\">\n        <figure>\n          <figcaption>Cold Oasis</figcaption>\n          <img class=\"picture\" :src=\"picture1\"\n         alt=\"Cold Oasis\">\n        </figure>\n      </div>\n      <div class=\"scroll-content content2\">\n        <figure>\n          <figcaption>Warm Oasis</figcaption>\n          <img class=\"picture\" :src=\"picture2\"\n         alt=\"Warm Oasis\">\n        </figure>\n      </div>\n    </div>\n    <button class=\"btn\" @click=\"handleClick\">Put The Warm at center position</button>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n  import picture1 from './oasis_one.png'\n  import picture2 from './oasis_two.png'\n\n  BScroll.use(Movable)\n\n  export default {\n    created() {\n      this.picture1 = picture1\n      this.picture2 = picture2\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs1.destroy()\n      this.bs2.destroy()\n    },\n    methods: {\n      init() {\n        this.bs1 = new BScroll(this.$refs.scroll, {\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          startX: 10,\n          startY: 10\n        })\n        this.bs2 = new BScroll(this.$refs.scroll, {\n          // use wrapper.children[1] as content\n          specifiedIndexAsContent: 1,\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          startX: 0,\n          startY: 170\n        })\n      },\n      handleClick() {\n        this.bs2.putAt('center', 'center')\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.movable-multi-content-container\n\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    position relative\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      position absolute\n      top 0\n      left 0\n      width 200px\n      figure\n        margin 0\n      figcaption\n        font-weight bold\n        margin-bottom 5px\n        text-align center\n        color #ea4c89\n      .picture\n        width 200px\n        height 150px\n        border-radius 10px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n  .btn\n    margin 40px auto\n    padding 10px\n    color #fff\n    border-radius 4px\n    font-size 20px\n    background-color #666\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/movable/scale.vue",
    "content": "<template>\n  <div class=\"core-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"(item, index) in emojis\" :key=\"index\">{{item}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Movable)\n  BScroll.use(Zoom)\n\n  export default {\n    data () {\n      return {\n        emojis: [\n          '😀 😁 😂 🤣 😃',\n          '😄 😅 😆 😉 😊',\n          '😫 😴 😌 😛 😜',\n          '👆🏻 😒 😓 😔 👇🏻'\n        ]\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          bindToTarget: true,\n          scrollX: true,\n          scrollY: true,\n          freeScroll: true,\n          movable: true,\n          zoom: {\n            start: 1,\n            min: 0.5,\n            max: 3\n          }\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.core-container\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      width 220px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/nested-scroll/horizontal-in-vertical.vue",
    "content": "<template>\n  <div class=\"horizontal-in-vertical-container\">\n    <div class=\"vertical-wrapper\" ref=\"outerScroll\">\n      <div class=\"vertical-content\">\n        <div class=\"vertical-item\" @click=\"handleOuterClick\" v-for=\"(item, idx) in list1\" :key=\"idx\">{{item}}</div>\n        <div class=\"horizontal-wrapper\" ref=\"innerScroll\">\n          <div class=\"slide-banner-content\" @click=\"handleInnerClick\">\n              <div class=\"slide-item page1\">horizontal scroll 1</div>\n              <div class=\"slide-item page2\">horizontal scroll 2</div>\n              <div class=\"slide-item page3\">horizontal scroll 3</div>\n              <div class=\"slide-item page4\">horizontal scroll 4</div>\n          </div>\n        </div>\n        <div class=\"vertical-item\" @click=\"handleOuterClick\" v-for=\"(item, idx) in list2\" :key=\"idx + 10\">{{item}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core';\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(NestedScroll)\nBScroll.use(Slide)\n\nconst LIST1 = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n]\nconst LIST2 = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣'\n]\nexport default {\n  data () {\n    return {\n      list1: LIST1,\n      list2: LIST2\n    }\n  },\n  mounted () {\n    this.initScroll()\n  },\n  methods: {\n    handleOuterClick () {\n      window.alert('clicked outer item')\n    },\n    handleInnerClick () {\n      window.alert('clicked inner item')\n    },\n    initScroll () {\n      let outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'mixed-nested-scroll'\n        },\n        click: true\n      })\n      let innerScroll = new BScroll(this.$refs.innerScroll, {\n        nestedScroll: {\n          groupId: 'mixed-nested-scroll'\n        },\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          loop: false,\n          autoplay: false,\n          threshold: 100\n        },\n        momentum: false,\n        bounce: false,\n        click: true\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n  .horizontal-in-vertical-container\n      height: 100%\n    .vertical-wrapper\n      height: 100%\n      border: 1px solid rgba(0, 0, 0, .1)\n      position: relative\n      overflow: hidden\n      .vertical-item\n        line-height: 40px\n        text-align: center\n    .slide-banner-content\n      height: 120px\n      white-space: nowrap\n      font-size: 0\n      .slide-item\n        display: inline-block\n        height: 120px\n        width: 100%\n        line-height: 120px\n        text-align: center\n        font-size: 26px\n        &.page1\n          background-color: #95B8D1\n        &.page2\n          background-color: #DDA789\n        &.page3\n          background-color: #C3D899\n        &.page4\n          background-color: #F2D4A7\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/nested-scroll/horizontal.vue",
    "content": "<template>\n  <div class=\"container\">\n      <div ref=\"outerScroll\" class=\"outer-wrapper\">\n        <ul class=\"outer-content\">\n          <li v-for=\"(item, idx) in items1\" @click=\"handleOuterClick\" :key=\"idx\" class=\"list-item\">{{ item }}</li>\n          <li class=\"list-item inner-list-item\">\n            <div\n              ref=\"innerScroll\"\n              class=\"inner-wrapper\">\n              <ul class=\"inner-content\">\n                <li v-for=\"(item, idx) in items2\" @click=\"handleInnerClick\" :key=\"idx\" class=\"list-item\">{{ item }}</li>\n              </ul>\n            </div>\n          </li>\n          <li v-for=\"(item, idx) in items1\" @click=\"handleOuterClick\" :key=\"idx + 100\" class=\"list-item\">{{ item }}</li>\n        </ul>\n      </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nBScroll.use(NestedScroll)\n\nconst _data1 = [\n  '👈🏻  outer 👉🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 '\n]\n\nconst _data2 = [\n  '👈🏻  inner 👉🏻  ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👈🏻  inner 👉🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '👈🏻  inner 👉🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👈🏻  inner 👉🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 '\n]\n\nexport default {\n  data() {\n    return {\n      items1: _data1,\n      items2: _data2\n    }\n  },\n  mounted () {\n    this.initBScroll()\n  },\n  beforeDestroy () {\n    this.outerScroll.destroy()\n    this.innerScroll.destroy()\n  },\n  methods: {\n    handleOuterClick () {\n      window.alert('clicked outer item')\n    },\n    handleInnerClick () {\n      window.alert('clicked inner item')\n    },\n    initBScroll () {\n      // outer\n      this.outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'horizontal-nested-scroll'  // groupId is a string or number\n        },\n        scrollX: true,\n        scrollY: false,\n        click: true\n      })\n      // inner\n      this.innerScroll = new BScroll(this.$refs.innerScroll, {\n        // please keep the same groupId as above\n        // outerScroll and innerScroll will be controlled by the same nestedScroll instance\n        nestedScroll: {\n          groupId: 'horizontal-nested-scroll'\n        },\n        scrollX: true,\n        scrollY: false,\n        click: true\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n  .outer-wrapper\n    border: 1px solid rgba(0, 0, 0, 0.1)\n    border-radius: 5px\n    transform: rotate(0deg)\n    margin-top: 50px\n    position: relative\n    overflow: hidden\n    .outer-content\n      display: inline-block\n      vertical-align: top\n      white-space: nowrap\n\n  .inner-wrapper\n    border: 2px solid #62B791\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    width: 200px\n    overflow: hidden\n    .inner-content\n      display: inline-block\n      vertical-align: top\n\n  .list-item\n    display: inline-block\n    line-height: 60px\n\n  .inner-list-item\n    vertical-align: top // important\n    background-color: rgba(98,183,145, 0.2)\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/nested-scroll/triple-vertical.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div ref=\"outerScroll\" class=\"outer-wrapper\">\n      <div class=\"outer-content\">\n        <ul>\n          <li class=\"outer-list-item\" @click=\"handleOuterClick\" v-for=\"(item, index) in outerOpenData\" :key=\"index\">{{item}}</li>\n        </ul>\n        <div ref=\"middleScroll\"  class=\"middle-wrapper\">\n          <div class=\"middle-content\">\n            <ul>\n              <li class=\"middle-list-item\" @click=\"handleMiddleClick\" v-for=\"(item, index) in middleOpenData\" :key=\"index\">{{item}}</li>\n            </ul>\n            <div ref=\"innerScroll\" class=\"inner-wrapper\">\n              <ul class=\"inner-content\">\n                <li class=\"inner-list-item\" v-for=\"(item, index) in innerData\" @click=\"handleInnerClick\" :key=\"index\">{{item}}</li>\n              </ul>\n            </div>\n            <ul>\n              <li class=\"middle-list-item\" @click=\"handleMiddleClick\" v-for=\"(item, index) in middleCloseData\" :key=\"index + 100\">{{item}}</li>\n            </ul>\n          </div>\n        </div>\n        <ul>\n          <li class=\"outer-list-item\" @click=\"handleOuterClick\" v-for=\"(item, index) in outerCloseData\" :key=\"index + 100\">{{item}}</li>\n        </ul>\n      </div>\n\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nBScroll.use(NestedScroll)\n\nconst outerOpenData = [\n  '----Outer Start----',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 '\n]\n\nconst outerCloseData = [\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '----Outer End----',\n]\n\nconst middleOpenData = [\n  '----Middle Start----',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n]\n\nconst middleCloseData = [\n  '👆🏻 middle scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '----Middle End----',\n]\n\nconst innerData = [\n  '------Inner Start-----',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '-----Inner End-----'\n]\n\nexport default {\n  data() {\n    return {\n      outerOpenData,\n      outerCloseData,\n      middleOpenData,\n      middleCloseData,\n      innerData,\n    }\n  },\n  mounted () {\n    this.initBScroll()\n  },\n  beforeDestroy () {\n    this.outerScroll.destroy()\n    this.middleScroll.destroy()\n    this.innerScroll.destroy()\n  },\n  methods: {\n    handleOuterClick () {\n      window.alert('clicked outer item')\n    },\n    handleMiddleClick () {\n      window.alert('clicked middle item')\n    },\n    handleInnerClick () {\n      window.alert('clicked inner item')\n    },\n    initBScroll () {\n      // outer\n      this.outerScroll = this.outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'triple-nested-scroll' // groupId is a string or number\n        },\n        click: true\n      })\n\n      // inner\n      this.innerScroll = new BScroll(this.$refs.innerScroll, {\n        // please keep the same groupId as above\n        // all scrolls will be controlled by the same nestedScroll instance\n        nestedScroll: {\n          groupId: 'triple-nested-scroll'\n        },\n        probeType:2,\n        click: true\n      })\n\n      this.innerScroll.on('scroll', () => {\n        console.log('innerScroll scroll')\n      })\n      this.innerScroll.on('scrollEnd', () => {\n        console.log('innerScroll scrollEnd')\n      })\n\n      // middle\n      this.middleScroll = new BScroll(this.$refs.middleScroll, {\n        nestedScroll: {\n          groupId: 'triple-nested-scroll' // groupId is a string or number\n        },\n        probeType: 2,\n        click: true\n      })\n\n      this.middleScroll.on('scroll', () => {\n        console.log('middleScroll scroll')\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n.container\n  height: 100%\n.outer-wrapper\n.middle-wrapper\n.inner-wrapper\n  border: 2px solid #62B791\n  border-radius: 5px\n  transform: rotate(0deg)\n  position: relative\n  overflow: hidden\n.outer-wrapper\n  height: 100%\n  border: 1px solid rgba(0, 0, 0, .1)\n.middle-wrapper\n  height: 480px\n  background-color rgba(240,65,85, 0.2)  \n  border: 2px solid #f04155\n.inner-wrapper\n  height: 240px\n  background-color rgb(98,183,145)\n.middle-list-item\n.inner-list-item\n  height: 50px\n  line-height: 50px\n  text-align: center\n  list-style: none\n  color: white\n\n.middle-list-item\n  color: #894e06\n\n.outer-list-item\n  height: 40px\n  line-height: 40px\n  text-align: center\n  list-style: none\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/nested-scroll/vertical.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div ref=\"outerScroll\" class=\"outer-wrapper\">\n      <div class=\"outer-content\">\n        <ul>\n          <li class=\"outer-list-item\" @click=\"handleOuterClick\" v-for=\"(item, index) in outerOpenData\" :key=\"index\">{{item}}</li>\n        </ul>\n        <div ref=\"innerScroll\" class=\"inner-wrapper\">\n          <ul class=\"inner-content\">\n            <li class=\"inner-list-item\" v-for=\"(item, index) in innerData\" @click=\"handleInnerClick\" :key=\"index\">{{item}}</li>\n          </ul>\n        </div>\n        <ul>\n          <li class=\"outer-list-item\" @click=\"handleOuterClick\" v-for=\"(item, index) in outerCloseData\" :key=\"index\">{{item}}</li>\n        </ul>\n      </div>\n\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nBScroll.use(NestedScroll)\n\nconst outerOpenData = [\n  '----Outer Start----',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 '\n]\n\nconst outerCloseData = [\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '----Outer End----',\n]\n\nconst innerData = [\n  '------Inner Start-----',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '-----Inner End-----'\n]\n\nexport default {\n  data() {\n    return {\n      outerOpenData,\n      outerCloseData,\n      innerData,\n    }\n  },\n  mounted () {\n    this.initBScroll()\n  },\n  beforeDestroy () {\n    this.outerScroll.destroy()\n    this.innerScroll.destroy()\n  },\n  methods: {\n    handleOuterClick () {\n      window.alert('clicked outer item')\n    },\n    handleInnerClick () {\n      window.alert('clicked inner item')\n    },\n    initBScroll () {\n      // outer\n      this.outerScroll = new BScroll(this.$refs.outerScroll, {\n        nestedScroll: {\n          groupId: 'vertical-nested-scroll' // groupId is a string or number\n        },\n        click: true\n      })\n\n      // inner\n      this.innerScroll = new BScroll(this.$refs.innerScroll, {\n        // please keep the same groupId as above\n        // outerScroll and innerScroll will be controlled by the same nestedScroll instance\n        nestedScroll: {\n          groupId: 'vertical-nested-scroll'\n        },\n        click: true\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n.container\n  height: 100%\n.outer-wrapper\n.inner-wrapper\n  border: 2px solid #62B791\n  border-radius: 5px\n  transform: rotate(0deg)\n  position: relative\n  overflow: hidden\n.outer-wrapper\n  height: 100%\n  border: 1px solid rgba(0, 0, 0, .1)\n.inner-wrapper\n  height: 240px\n  background-color rgba(98,183,145, 0.2)\n.inner-list-item\n  height: 50px\n  line-height: 50px\n  text-align: center\n  list-style: none\n\n.outer-list-item\n  height: 40px\n  line-height: 40px\n  text-align: center\n  list-style: none\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/observe-dom/default.vue",
    "content": "<template>\n  <div class=\"observe-dom-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"num in nums\" :key=\"num\">{{nums - num + 1}}</div>\n      </div>\n    </div>\n\t\t<button class=\"btn\" @click=\"handleClick\">append two children element</button>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import ObserveDOM from '@better-scroll/observe-dom'\n\n  BScroll.use(ObserveDOM)\n  export default {\n    data () {\n      return {\n        nums: 10\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          observeDOM: true,\n          scrollX: true,\n          scrollY: false\n        })\n      },\n      handleClick() {\n        // observe-dom plugin will refresh bs\n        this.nums += 2\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.observe-dom-container\n  text-align center\n  .scroll-wrapper\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n      .scroll-item\n        height 50px\n        line-height 50px\n        font-size 24px\n        display inline-block\n        text-align center\n        padding 0 20px\n        &:nth-child(2n)\n          background-color #C3D899\n        &:nth-child(2n+1)\n          background-color #F2D4A7\n\t.btn\n\t\tmargin 40px auto\n\t\tpadding 10px\n\t\tcolor #fff\n\t\tborder-radius 4px\n\t\tfont-size 20px\n\t\tbackground-color #666   \t\t\t\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/observe-image/default.vue",
    "content": "<template>\n  <div class=\"observe-image-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <img width=\"100%\" v-for=\"(url, index) in images\" :src=\"url\" alt=\"\" :key=\"index\">\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import ObserveImage from '@better-scroll/observe-image'\n\n  BScroll.use(ObserveImage)\n  export default {\n    data () {\n      return {\n        images: [\n          'https://dpubstatic.udache.com/static/dpubimg/dEswI1MVy6/zoo.png',\n          'https://dpubstatic.udache.com/static/dpubimg/BYb_wPak21/home.png',\n          'https://dpubstatic.udache.com/static/dpubimg/B6q1pWB0sB/cabin.png',\n          'https://dpubstatic.udache.com/static/dpubimg/76n1ilzf4R/stone.png'\n        ]\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.bs.destroy()\n    },\n    methods: {\n      init() {\n        this.bs = new BScroll(this.$refs.scroll, {\n          observeImage: true\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.observe-image-container\n  text-align center\n  .scroll-wrapper\n    position relative\n    width 300px\n    height 300px\n    margin 20px auto\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n  .scroll-content\n    font-size 0\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/picker/double-column.vue",
    "content": "<template>\n  <div class=\"container\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"show\">\n          <span class=\"open\">{{selectedText}}</span>\n      </li>\n    </ul>\n    <transition name=\"picker-fade\">\n      <div class=\"picker\" v-show=\"state===1\" @touchmove.prevent @click=\"_cancel\">\n        <transition name=\"picker-move\">\n          <div class=\"picker-panel\" v-show=\"state===1\" @click.stop>\n            <div class=\"picker-choose border-bottom-1px\">\n              <span class=\"cancel\" @click=\"_cancel\">Cancel</span>\n              <span class=\"confirm\" @click=\"_confirm\">Confirm</span>\n              <h1 class=\"picker-title\">Title</h1>\n            </div>\n            <div class=\"picker-content\">\n              <div class=\"mask-top border-bottom-1px\"></div>\n              <div class=\"mask-bottom border-top-1px\"></div>\n              <div class=\"wheel-wrapper\" ref=\"wheelWrapper\">\n                <div class=\"wheel\" v-for=\"(data, index) in pickerData\" :key=\"index\">\n                  <ul class=\"wheel-scroll\">\n                    <li\n                      v-for=\"item in data\" :key=\"item.value\"\n                      class=\"wheel-item\">{{item.text}}</li>\n                  </ul>\n                </div>\n              </div>\n            </div>\n            <div class=\"picker-footer\"></div>\n          </div>\n        </transition>\n      </div>\n    </transition>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n  BScroll.use(Wheel)\n  const STATE_HIDE = 0\n  const STATE_SHOW = 1\n\n  const COMPONENT_NAME = 'picker'\n  const EVENT_SELECT = 'select'\n  const EVENT_CANCEL = 'cancel'\n  const EVENT_CHANGE = 'change'\n\n  const DATA1 = [\n    {\n      text: 'Venomancer',\n      value: 1\n    }, {\n      text: 'Nerubian Weaver',\n      value: 2\n    },\n    {\n      text: 'Spectre',\n      value: 3\n    },\n    {\n      text: 'Juggernaut',\n      value: 4\n    },\n    {\n      text: 'Karl',\n      value: 5\n    },\n    {\n      text: 'Zeus',\n      value: 6\n    },\n    {\n      text: 'Witch Doctor',\n      value: 7\n    }, {\n      text: 'Lich',\n      value: 8\n    },\n    {\n      text: 'Oracle',\n      value: 9\n    },\n    {\n      text: 'Earthshaker',\n      value: 10\n    }\n  ]\n\n  const DATA2 = [\n    {\n      text: 'Durable',\n      value: 'a'\n    },\n    {\n      text: 'Pusher',\n      value: 'b'\n    },\n    {\n      text: 'Carry',\n      value: 'c'\n    },\n    {\n      text: 'Nuker',\n      value: 'd'\n    },\n    {\n      text: 'Support',\n      value: 'e'\n    },\n    {\n      text: 'Jungle',\n      value: 'f'\n    },\n    {\n      text: 'Escape',\n      value: 'g'\n    }, {\n      text: 'Initiator',\n      value: 'h'\n    }\n  ]\n\n  export default {\n    name: COMPONENT_NAME,\n    data() {\n      return {\n        state: STATE_HIDE,\n        selectedIndexPair: [0, 0],\n        selectedText: 'open',\n        pickerData: [DATA1, DATA2]\n      }\n    },\n    methods: {\n      _confirm() {\n        this.wheels.forEach(wheel => {\n          /*\n          * if bs is scrolling, force it stop at the nearest wheel-item\n          * or you can use 'restorePosition' method as the below\n          */\n          // wheel.stop()\n          /*\n          * if bs is scrolling, restore it to the start position\n          * it is same with iOS picker and web Select element implementation\n          * supported at v2.1.0\n          */\n          wheel.restorePosition()\n        })\n        this.hide()\n\n        const currentSelectedIndexPair = this.selectedIndexPair = this.wheels.map(wheel => {\n          return wheel.getSelectedIndex()\n        })\n\n        this.selectedText = this.pickerData.map((data, i) => {\n          const index = currentSelectedIndexPair[i]\n          return `${data[index].text}-${index}`\n        }).join('__')\n        this.$emit(EVENT_SELECT, currentSelectedIndexPair)\n      },\n      _cancel() {\n        /*\n         * if bs is scrolling, restore it to the start position\n         * it is same with iOS picker and web Select element implementation\n         * supported at v2.1.0\n        */\n        this.wheels.forEach(wheel => {\n          wheel.restorePosition()\n        })\n        this.hide()\n        this.$emit(EVENT_CANCEL)\n      },\n      show() {\n        if (this.state === STATE_SHOW) {\n          return\n        }\n        this.state = STATE_SHOW\n\n        if (!this.wheels) {\n          // waiting for DOM rendered\n          this.$nextTick(() => {\n            this.wheels = []\n            let wheelWrapper = this.$refs.wheelWrapper\n            for (let i = 0; i < this.pickerData.length; i++) {\n              this._createWheel(wheelWrapper, i)\n            }\n          })\n        }\n      },\n      hide() {\n        this.state = STATE_HIDE\n      },\n      _createWheel(wheelWrapper, i) {\n        if (!this.wheels[i]) {\n          this.wheels[i] = new BScroll(wheelWrapper.children[i], {\n            wheel: {\n              selectedIndex: this.selectedIndexPair[i],\n              wheelWrapperClass: 'wheel-scroll',\n              wheelItemClass: 'wheel-item'\n            },\n            probeType: 3\n          })\n          this.wheels[i].on('scrollEnd', () => {\n            this.$emit(EVENT_CHANGE, i, this.wheels[i].getSelectedIndex())\n          })\n        } else {\n          this.wheels[i].refresh()\n        }\n\n        return this.wheels[i]\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n  /* reset */\n  ul\n    list-style none\n    padding 0\n\n  .border-bottom-1px, .border-top-1px\n    position: relative\n    &:before, &:after\n      content: \"\"\n      display: block\n      position: absolute\n      transform-origin: 0 0\n  .border-bottom-1px\n    &:after\n      border-bottom: 1px solid #ebebeb\n      left: 0\n      bottom: 0\n      width: 100%\n      transform-origin: 0 bottom\n  .border-top-1px\n    &:before\n      border-top: 1px solid #ebebeb\n      left: 0\n      top: 0\n      width: 100%\n      transform-origin: 0 top\n  @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)\n    .border-top-1px\n      &:before\n        width: 200%\n        transform: scale(.5) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 200%\n        transform: scale(.5) translateZ(0)\n\n  @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)\n    .border-top-1px\n      &:before\n        width: 300%\n        transform: scale(.333) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 300%\n        transform: scale(.333) translateZ(0)\n\n  .example-list\n    display: flex\n    justify-content: space-between\n    flex-wrap: wrap\n    margin: 2rem\n\n    .example-item\n      background-color white\n      padding: 0.8rem\n      border: 1px solid rgba(0, 0, 0, .1)\n      box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n      text-align: center\n      margin-bottom: 1rem\n      flex: 1\n      &.placeholder\n        visibility: hidden\n        height: 0\n        margin: 0\n        padding: 0\n\n  .picker\n    position: fixed\n    left: 0\n    top: 0\n    z-index: 100\n    width: 100%\n    height: 100%\n    overflow: hidden\n    text-align: center\n    font-size: 14px\n    background-color: rgba(37, 38, 45, .4)\n    &.picker-fade-enter, &.picker-fade-leave-active\n      opacity: 0\n    &.picker-fade-enter-active, &.picker-fade-leave-active\n      transition: all .3s ease-in-out\n\n    .picker-panel\n      position: absolute\n      z-index: 600\n      bottom: 0\n      width: 100%\n      height: 273px\n      background: white\n      &.picker-move-enter, &.picker-move-leave-active\n        transform: translate3d(0, 273px, 0)\n      &.picker-move-enter-active, &.picker-move-leave-active\n        transition: all .3s ease-in-out\n      .picker-choose\n        position: relative\n        height: 60px\n        color: #999\n        .picker-title\n          margin: 0\n          line-height: 60px\n          font-weight: normal\n          text-align: center\n          font-size: 18px\n          color: #333\n        .confirm, .cancel\n          position: absolute\n          top: 6px\n          padding: 16px\n          font-size: 14px\n        .confirm\n          right: 0\n          color: #007bff\n          &:active\n            color: #5aaaff\n        .cancel\n          left: 0\n          &:active\n            color: #c2c2c2\n      .picker-content\n        position: relative\n        top: 20px\n        .mask-top, .mask-bottom\n          z-index: 10\n          width: 100%\n          height: 68px\n          pointer-events: none\n          transform: translateZ(0)\n        .mask-top\n          position: absolute\n          top: 0\n          background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n        .mask-bottom\n          position: absolute\n          bottom: 1px\n          background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .wheel-wrapper\n        display: flex\n        padding: 0 16px\n        .wheel\n          flex: 1\n          width: 1%\n          height: 173px\n          overflow: hidden\n          font-size: 18px\n          .wheel-scroll\n            padding: 0\n            margin-top: 68px\n            line-height: 36px\n            list-style: none\n            .wheel-item\n              list-style: none\n              height: 36px\n              overflow: hidden\n              white-space: nowrap\n              color: #333\n              &.wheel-disabled-item\n                opacity: .2;\n    .picker-footer\n      height: 20px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/picker/linkage-column.vue",
    "content": "<template>\n  <div class=\"container\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"show\">\n          <span class=\"open\">{{selectedText}}</span>\n      </li>\n    </ul>\n    <transition name=\"picker-fade\">\n      <div class=\"picker\" v-show=\"state===1\" @touchmove.prevent @click=\"_cancel\">\n        <transition name=\"picker-move\">\n          <div class=\"picker-panel\" v-show=\"state===1\" @click.stop>\n            <div class=\"picker-choose border-bottom-1px\">\n              <span class=\"cancel\" @click=\"_cancel\">Cancel</span>\n              <span class=\"confirm\" @click=\"_confirm\">Confirm</span>\n              <h1 class=\"picker-title\">Title</h1>\n            </div>\n            <div class=\"picker-content\">\n              <div class=\"mask-top border-bottom-1px\"></div>\n              <div class=\"mask-bottom border-top-1px\"></div>\n              <div class=\"wheel-wrapper\" ref=\"wheelWrapper\">\n                <div class=\"wheel\" v-for=\"(data, index) in pickerData\" :key=\"index\">\n                  <ul class=\"wheel-scroll\">\n                    <li\n                      v-for=\"item in data\" :key=\"item.value\"\n                      :class=\"{'wheel-disabled-item':item.disabled}\"\n                      class=\"wheel-item\">{{item.text}}</li>\n                  </ul>\n                </div>\n              </div>\n            </div>\n            <div class=\"picker-footer\"></div>\n          </div>\n        </transition>\n      </div>\n    </transition>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n  BScroll.use(Wheel)\n\n  const STATE_HIDE = 0\n  const STATE_SHOW = 1\n\n  const COMPONENT_NAME = 'picker'\n  const EVENT_SELECT = 'select'\n  const EVENT_CANCEL = 'cancel'\n  const EVENT_CHANGE = 'change'\n\n  const DATA = [\n    {\n      text: '北京市',\n      value: '110000',\n      children: [\n        {\n          text: \"北京市\",\n          value: '110100'\n        }\n      ]\n    },\n    {\n      text: '天津市',\n      value: '120000',\n      children: [\n        {\n          text: \"天津市\",\n          value: '120000'\n        }\n      ]\n    },\n    {\n      text: '河北省',\n      value: '130000',\n      children: [\n        {\n          text: '石家庄市',\n          value: '130100'\n        },\n        {\n          text: '唐山市',\n          value: '130200'\n        },\n        {\n          text: '秦皇岛市',\n          value: '130300'\n        },\n        {\n          text: '邯郸市',\n          value: '130400'\n        },\n        {\n          text: '邢台市',\n          value: '130500'\n        },\n        {\n          text: '保定市',\n          value: '130600'\n        },\n        {\n          text: '张家口市',\n          value: '130700'\n        },\n        {\n          text: '承德市',\n          value: '130800'\n        }\n      ]\n    },\n    {\n      text: '山西省',\n      value: '140000',\n      children: [\n        {\n          text: '太原市',\n          value: '140100'\n        },\n        {\n          text: '大同市',\n          value: '140200'\n        },\n        {\n          text: '阳泉市',\n          value: '140300'\n        },\n        {\n          text: '长治市',\n          value: '140400'\n        },\n        {\n          text: '晋城市',\n          value: '140500'\n        },\n        {\n          text: '朔州市',\n          value: '140600'\n        },\n        {\n          text: '晋中市',\n          value: '140700'\n        }\n      ]\n    }\n  ]\n\n  export default {\n    name: COMPONENT_NAME,\n    data() {\n      return {\n        state: STATE_HIDE,\n        selectedIndexPair: [0, 0],\n        selectedText: 'open',\n        pickerData: []\n      }\n    },\n    created () {\n      // generate data\n      this._loadPickerData(this.selectedIndexPair, undefined /* no prevSelectedIndex due to instantiating */)\n    },\n    methods: {\n      _loadPickerData (newIndexPair, oldIndexPair) {\n        let provinces\n        let cities\n        // first instantiated\n        if (!oldIndexPair) {\n          provinces = DATA.map(({ value, text }) => ({ value, text }))\n          cities = DATA[newIndexPair[0]].children\n          this.pickerData = [provinces, cities]\n        } else {\n          // provinces'index changed, refresh cities data\n          if (newIndexPair[0] !== oldIndexPair[0]) {\n            cities = DATA[newIndexPair[0]].children\n            this.pickerData.splice(1, 1, cities)\n            // Since cities data changed\n            // refresh better-scroll to recaculate scrollHeight\n            this.$nextTick(() => {\n              this.wheels[1].refresh()\n            })\n          }\n        }\n      },\n      _confirm() {\n        this.wheels.forEach(wheel => {\n          /*\n          * if bs is scrolling, force it stop at the nearest wheel-item\n          * or you can use 'restorePosition' method as the below\n          */\n          // wheel.stop()\n          /*\n          * if bs is scrolling, restore it to the start position\n          * it is same with iOS picker and web Select element implementation\n          * supported at v2.1.0\n          */\n          wheel.restorePosition()\n        })\n        this.hide()\n\n        const currentSelectedIndexPair = this.selectedIndexPair = this.wheels.map(wheel => {\n          return wheel.getSelectedIndex()\n        })\n\n        this.selectedText = this.pickerData.map((data, i) => {\n          const index = currentSelectedIndexPair[i]\n          return `${data[index].text}-${index}`\n        }).join('__')\n        this.$emit(EVENT_SELECT, currentSelectedIndexPair)\n      },\n      _cancel() {\n        /*\n         * if bs is scrolling, restore it to the start position\n         * it is same with iOS picker and web Select element implementation\n         * supported at v2.1.0\n        */\n        this.wheels.forEach(wheel => {\n          wheel.restorePosition()\n        })\n        this.hide()\n        this.$emit(EVENT_CANCEL)\n      },\n      show() {\n        if (this.state === STATE_SHOW) {\n          return\n        }\n        this.state = STATE_SHOW\n        if (!this.wheels) {\n          this.$nextTick(() => {\n            this.wheels = []\n            let wheelWrapper = this.$refs.wheelWrapper\n            for (let i = 0; i < this.pickerData.length; i++) {\n              this._createWheel(wheelWrapper, i)\n            }\n          })\n        }\n      },\n      hide() {\n        this.state = STATE_HIDE\n      },\n      _createWheel(wheelWrapper, i) {\n        const wheels = this.wheels\n        if (!wheels[i]) {\n          wheels[i] = new BScroll(wheelWrapper.children[i], {\n            wheel: {\n              selectedIndex: this.selectedIndexPair[i],\n              wheelWrapperClass: 'wheel-scroll',\n              wheelItemClass: 'wheel-item'\n            },\n            probeType: 3\n          })\n          // when any of wheels'scrolling ended , refresh data\n          let prevSelectedIndexPair = this.selectedIndexPair\n          wheels[i].on('scrollEnd', () => {\n            const currentSelectedIndexPair = wheels.map(wheel => wheel.getSelectedIndex())\n            this._loadPickerData(currentSelectedIndexPair, prevSelectedIndexPair)\n            prevSelectedIndexPair = currentSelectedIndexPair\n            this.$emit(EVENT_CHANGE, i, this.wheels[i].getSelectedIndex())\n          })\n        } else {\n          wheels[i].refresh()\n        }\n        return wheels[i]\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n  /* reset */\n  ul\n    list-style none\n    padding 0\n\n  .border-bottom-1px, .border-top-1px\n    position: relative\n    &:before, &:after\n      content: \"\"\n      display: block\n      position: absolute\n      transform-origin: 0 0\n  .border-bottom-1px\n    &:after\n      border-bottom: 1px solid #ebebeb\n      left: 0\n      bottom: 0\n      width: 100%\n      transform-origin: 0 bottom\n  .border-top-1px\n    &:before\n      border-top: 1px solid #ebebeb\n      left: 0\n      top: 0\n      width: 100%\n      transform-origin: 0 top\n  @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)\n    .border-top-1px\n      &:before\n        width: 200%\n        transform: scale(.5) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 200%\n        transform: scale(.5) translateZ(0)\n\n  @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)\n    .border-top-1px\n      &:before\n        width: 300%\n        transform: scale(.333) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 300%\n        transform: scale(.333) translateZ(0)\n\n  .example-list\n    display: flex\n    justify-content: space-between\n    flex-wrap: wrap\n    margin: 2rem\n\n    .example-item\n      background-color white\n      padding: 0.8rem\n      border: 1px solid rgba(0, 0, 0, .1)\n      box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n      text-align: center\n      margin-bottom: 1rem\n      flex: 1\n      &.placeholder\n        visibility: hidden\n        height: 0\n        margin: 0\n        padding: 0\n\n  .picker\n    position: fixed\n    left: 0\n    top: 0\n    z-index: 100\n    width: 100%\n    height: 100%\n    overflow: hidden\n    text-align: center\n    font-size: 14px\n    background-color: rgba(37, 38, 45, .4)\n    &.picker-fade-enter, &.picker-fade-leave-active\n      opacity: 0\n    &.picker-fade-enter-active, &.picker-fade-leave-active\n      transition: all .3s ease-in-out\n\n    .picker-panel\n      position: absolute\n      z-index: 600\n      bottom: 0\n      width: 100%\n      height: 273px\n      background: white\n      &.picker-move-enter, &.picker-move-leave-active\n        transform: translate3d(0, 273px, 0)\n      &.picker-move-enter-active, &.picker-move-leave-active\n        transition: all .3s ease-in-out\n      .picker-choose\n        position: relative\n        height: 60px\n        color: #999\n        .picker-title\n          margin: 0\n          line-height: 60px\n          font-weight: normal\n          text-align: center\n          font-size: 18px\n          color: #333\n        .confirm, .cancel\n          position: absolute\n          top: 6px\n          padding: 16px\n          font-size: 14px\n        .confirm\n          right: 0\n          color: #007bff\n          &:active\n            color: #5aaaff\n        .cancel\n          left: 0\n          &:active\n            color: #c2c2c2\n      .picker-content\n        position: relative\n        top: 20px\n        .mask-top, .mask-bottom\n          z-index: 10\n          width: 100%\n          height: 68px\n          pointer-events: none\n          transform: translateZ(0)\n        .mask-top\n          position: absolute\n          top: 0\n          background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n        .mask-bottom\n          position: absolute\n          bottom: 1px\n          background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .wheel-wrapper\n        display: flex\n        padding: 0 16px\n        .wheel\n          flex: 1\n          width: 1%\n          height: 173px\n          overflow: hidden\n          font-size: 18px\n          .wheel-scroll\n            padding: 0\n            margin-top: 68px\n            line-height: 36px\n            list-style: none\n            .wheel-item\n              list-style: none\n              height: 36px\n              overflow: hidden\n              white-space: nowrap\n              color: #333\n              &.wheel-disabled-item\n                opacity: .2;\n    .picker-footer\n      height: 20px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/picker/one-column.vue",
    "content": "<template>\n  <div class=\"container\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"show\">\n          <span class=\"open\">{{selectedText}}</span>\n      </li>\n    </ul>\n    <transition name=\"picker-fade\">\n      <div class=\"picker\" v-show=\"state===1\" @touchmove.prevent @click=\"_cancel\">\n        <transition name=\"picker-move\">\n          <div class=\"picker-panel\" v-show=\"state===1\" @click.stop>\n            <div class=\"picker-choose border-bottom-1px\">\n              <span class=\"cancel\" @click=\"_cancel\">Cancel</span>\n              <span class=\"confirm\" @click=\"_confirm\">Confirm</span>\n              <h1 class=\"picker-title\">Title</h1>\n            </div>\n            <div class=\"picker-content\">\n              <div class=\"mask-top border-bottom-1px\"></div>\n              <div class=\"mask-bottom border-top-1px\"></div>\n              <div class=\"wheel-wrapper\" ref=\"wheelWrapper\">\n                <div class=\"wheel\">\n                  <ul class=\"wheel-scroll\">\n                    <li\n                      v-for=\"(item, index) in pickerData\" :key=\"index\"\n                      :class=\"{'wheel-disabled-item':item.disabled}\"\n                      class=\"wheel-item\">{{item.text}}</li>\n                  </ul>\n                </div>\n              </div>\n            </div>\n            <div class=\"picker-footer\"></div>\n          </div>\n        </transition>\n      </div>\n    </transition>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n  BScroll.use(Wheel)\n\n  const STATE_HIDE = 0\n  const STATE_SHOW = 1\n\n  const COMPONENT_NAME = 'picker'\n  const EVENT_SELECT = 'select'\n  const EVENT_CANCEL = 'cancel'\n  const EVENT_CHANGE = 'change'\n  const WHEEL_INDEX_CHANGED = 'wheelIndexChanged'\n\n  const DATA = [\n    {\n      text: 'Venomancer',\n      value: 1,\n      disabled: 'wheel-disabled-item'\n    }, {\n      text: 'Nerubian Weaver',\n      value: 2\n    },\n    {\n      text: 'Spectre',\n      value: 3\n    },\n    {\n      text: 'Juggernaut',\n      value: 4\n    },\n    {\n      text: 'Karl',\n      value: 5\n    },\n    {\n      text: 'Zeus',\n      value: 6\n    },\n    {\n      text: 'Witch Doctor',\n      value: 7\n    }, {\n      text: 'Lich',\n      value: 8\n    },\n    {\n      text: 'Oracle',\n      value: 9\n    },\n    {\n      text: 'Earthshaker',\n      value: 10\n    }\n  ]\n\n  export default {\n    name: COMPONENT_NAME,\n    data() {\n      return {\n        state: STATE_HIDE,\n        selectedIndex: 2,\n        selectedText: 'open',\n        pickerData: DATA\n      }\n    },\n    methods: {\n      _confirm() {\n        /*\n         * if bs is scrolling, force it stop at the nearest wheel-item\n         * or you can use 'restorePosition' method as the below\n        */\n        this.wheel.stop()\n        /*\n         * if bs is scrolling, restore it to the start position\n         * it is same with iOS picker and web Select element implementation\n         * supported at v2.1.0\n        */\n        // this.wheel.restorePosition()\n\n        this.hide()\n        const currentSelectedIndex = this.selectedIndex = this.wheel.getSelectedIndex()\n        this.selectedText = `${this.pickerData[currentSelectedIndex].text}-${currentSelectedIndex}`\n        this.$emit(EVENT_SELECT, currentSelectedIndex)\n      },\n      _cancel() {\n        /*\n         * if bs is scrolling, restore it to the start position\n         * it is same with iOS picker and web Select element implementation\n         * supported at v2.1.0\n        */\n        this.wheel.restorePosition()\n        this.hide()\n        this.$emit(EVENT_CANCEL)\n      },\n      show() {\n        if (this.state === STATE_SHOW) {\n          return\n        }\n        this.state = STATE_SHOW\n        if (!this.wheel) {\n          // waiting for DOM rendered\n          this.$nextTick(() => {\n            const wrapper = this.$refs.wheelWrapper.children[0]\n            this._createWheel(wrapper)\n          })\n        }\n      },\n      hide() {\n        this.state = STATE_HIDE\n      },\n      _createWheel(wheelWrapper) {\n        if (!this.wheel) {\n          this.wheel = new BScroll(wheelWrapper, {\n            wheel: {\n              selectedIndex: this.selectedIndex,\n              wheelWrapperClass: 'wheel-scroll',\n              wheelItemClass: 'wheel-item',\n              wheelDisabledItemClass: 'wheel-disabled-item'\n            },\n            useTransition: false,\n            probeType: 3\n          })\n          // < v2.1.0\n          this.wheel.on('scrollEnd', () => {\n            this.$emit(EVENT_CHANGE, this.wheel.getSelectedIndex())\n          })\n          // v2.1.0, only when selectedIndex changed\n          this.wheel.on(WHEEL_INDEX_CHANGED, (index) => {\n            console.log(index)\n          })\n        } else {\n          this.wheel.refresh()\n        }\n        return this.wheel\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n  /* reset */\n  ul\n    list-style none\n    padding 0\n\n  .border-bottom-1px, .border-top-1px\n    position: relative\n    &:before, &:after\n      content: \"\"\n      display: block\n      position: absolute\n      transform-origin: 0 0\n  .border-bottom-1px\n    &:after\n      border-bottom: 1px solid #ebebeb\n      left: 0\n      bottom: 0\n      width: 100%\n      transform-origin: 0 bottom\n  .border-top-1px\n    &:before\n      border-top: 1px solid #ebebeb\n      left: 0\n      top: 0\n      width: 100%\n      transform-origin: 0 top\n  @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)\n    .border-top-1px\n      &:before\n        width: 200%\n        transform: scale(.5) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 200%\n        transform: scale(.5) translateZ(0)\n\n  @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)\n    .border-top-1px\n      &:before\n        width: 300%\n        transform: scale(.333) translateZ(0)\n    .border-bottom-1px\n      &:after\n        width: 300%\n        transform: scale(.333) translateZ(0)\n  .example-list\n    display: flex\n    justify-content: space-between\n    flex-wrap: wrap\n    margin: 2rem\n\n    .example-item\n      background-color white\n      padding: 0.8rem\n      border: 1px solid rgba(0, 0, 0, .1)\n      box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n      text-align: center\n      margin-bottom: 1rem\n      flex: 1\n      &.placeholder\n        visibility: hidden\n        height: 0\n        margin: 0\n        padding: 0\n\n  .picker\n    position: fixed\n    left: 0\n    top: 0\n    z-index: 100\n    width: 100%\n    height: 100%\n    overflow: hidden\n    text-align: center\n    font-size: 14px\n    background-color: rgba(37, 38, 45, .4)\n    &.picker-fade-enter, &.picker-fade-leave-active\n      opacity: 0\n    &.picker-fade-enter-active, &.picker-fade-leave-active\n      transition: all .3s ease-in-out\n\n    .picker-panel\n      position: absolute\n      z-index: 600\n      bottom: 0\n      width: 100%\n      height: 273px\n      background: white\n      &.picker-move-enter, &.picker-move-leave-active\n        transform: translate3d(0, 273px, 0)\n      &.picker-move-enter-active, &.picker-move-leave-active\n        transition: all .3s ease-in-out\n      .picker-choose\n        position: relative\n        height: 60px\n        color: #999\n        .picker-title\n          margin: 0\n          line-height: 60px\n          font-weight: normal\n          text-align: center\n          font-size: 18px\n          color: #333\n        .confirm, .cancel\n          position: absolute\n          top: 6px\n          padding: 16px\n          font-size: 14px\n        .confirm\n          right: 0\n          color: #007bff\n          &:active\n            color: #5aaaff\n        .cancel\n          left: 0\n          &:active\n            color: #c2c2c2\n      .picker-content\n        position: relative\n        top: 20px\n        .mask-top, .mask-bottom\n          z-index: 10\n          width: 100%\n          height: 68px\n          pointer-events: none\n          transform: translateZ(0)\n        .mask-top\n          position: absolute\n          top: 0\n          background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n        .mask-bottom\n          position: absolute\n          bottom: 1px\n          background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .wheel-wrapper\n        display: flex\n        padding: 0 16px\n        .wheel\n          flex: 1\n          width: 1%\n          height: 173px\n          overflow: hidden\n          font-size: 18px\n          .wheel-scroll\n            padding: 0\n            margin-top: 68px\n            line-height: 36px\n            list-style: none\n            .wheel-item\n              list-style: none\n              height: 36px\n              overflow: hidden\n              white-space: nowrap\n              color: #333\n              &.wheel-disabled-item\n                opacity: .2;\n    .picker-footer\n      height: 20px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/pulldown/default.vue",
    "content": "<template>\n  <div class=\"pulldown\">\n    <div\n      class=\"pulldown-bswrapper\"\n      ref=\"bsWrapper\"\n    >\n      <div class=\"pulldown-scroller\">\n        <div class=\"pulldown-wrapper\">\n          <div v-show=\"beforePullDown\">\n            <span>Pull Down and refresh</span>\n          </div>\n          <div v-show=\"!beforePullDown\">\n            <div v-show=\"isPullingDown\">\n              <span>Loading...</span>\n            </div>\n            <div v-show=\"!isPullingDown\">\n              <span>Refresh success</span>\n            </div>\n          </div>\n        </div>\n        <ul class=\"pulldown-list\">\n          <li\n            :key=\"i\"\n            class=\"pulldown-list-item\"\n            v-for=\"i of dataList\"\n          >{{ `I am item ${i} ` }}</li>\n        </ul>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nfunction generateData() {\n  const BASE = 30\n  const begin = BASE * STEP\n  const end = BASE * (STEP + 1)\n  let ret = []\n  for (let i = end; i > begin; i--) {\n    ret.push(i)\n  }\n  return ret\n}\n\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 1000\nconst THRESHOLD = 70\nconst STOP = 56\nlet STEP = 0\n\nexport default {\n  data() {\n    return {\n      beforePullDown: true,\n      isPullingDown: false,\n      dataList: generateData()\n    }\n  },\n  mounted() {\n    this.initBscroll()\n  },\n  methods: {\n    initBscroll() {\n      this.bscroll = new BScroll(this.$refs.bsWrapper, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        useTransition: false,\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP\n        }\n      })\n\n      this.bscroll.on('pullingDown', this.pullingDownHandler)\n      this.bscroll.on('scroll', this.scrollHandler)\n      this.bscroll.on('scrollEnd', e => {\n        console.log('scrollEnd')\n      })\n    },\n    scrollHandler(pos) {\n      console.log(pos.y)\n    },\n    async pullingDownHandler() {\n      console.log('trigger pullDown')\n      this.beforePullDown = false\n      this.isPullingDown = true\n      STEP += 1\n\n      await this.requestData()\n\n      this.isPullingDown = false\n      this.finishPullDown()\n    },\n    async finishPullDown() {\n      this.bscroll.finishPullDown()\n      setTimeout(() => {\n        this.beforePullDown = true\n        this.bscroll.refresh()\n      }, TIME_BOUNCE + 100)\n    },\n    async requestData() {\n      try {\n        const newData = await this.ajaxGet(/* url */)\n        this.dataList = newData.concat(this.dataList)\n      } catch (err) {\n        // handle err\n        console.log(err)\n      }\n    },\n    ajaxGet(/* url */) {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          const dataList = generateData()\n          resolve(dataList)\n        }, REQUEST_TIME)\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.pulldown\n  height 100%\n\n.pulldown-bswrapper\n  position relative\n  height 100%\n  padding 0 10px\n  border 1px solid #ccc\n  overflow hidden\n\n.pulldown-list\n  padding 0\n\n.pulldown-list-item\n  padding 10px 0\n  list-style none\n  border-bottom 1px solid #ccc\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/pulldown/sina-weibo.vue",
    "content": "<template>\n  <div class=\"pulldown-sina\">\n    <div\n      class=\"pulldown-bswrapper\"\n      ref=\"bsWrapper\"\n    >\n      <div class=\"pulldown-scroller\">\n        <div class=\"pulldown-wrapper\">\n          <div v-html=\"tipText\"></div>\n        </div>\n        <ul class=\"pulldown-list\">\n          <li\n            :key=\"i\"\n            class=\"pulldown-list-item\"\n            v-for=\"i of dataList\"\n          >{{ `I am item ${i} ` }}</li>\n        </ul>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nfunction generateData() {\n  const BASE = 30\n  const begin = BASE * STEP\n  const end = BASE * (STEP + 1)\n  let ret = []\n  for (let i = end; i > begin; i--) {\n    ret.push(i)\n  }\n  return ret\n}\n\n// pulldownRefresh state\nconst PHASE = {\n  moving: {\n    enter: 'enter',\n    leave: 'leave'\n  },\n  fetching: 'fetching',\n  succeed: 'succeed'\n}\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 2000\nconst THRESHOLD = 70\nconst STOP = 56\nlet STEP = 0\nconst ARROW_BOTTOM = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"M367.997 338.75l-95.998 95.997V17.503h-32v417.242l-95.996-95.995l-22.627 22.627L256 496l134.624-134.623l-22.627-22.627z\"></path></svg>'\nconst ARROW_UP = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"M390.624 150.625L256 16L121.376 150.625l22.628 22.627l95.997-95.998v417.982h32V77.257l95.995 95.995l22.628-22.627z\"></path></svg>'\n\nexport default {\n  data() {\n    return {\n      tipText: '',\n      isPullingDown: false,\n      dataList: generateData()\n    }\n  },\n  mounted() {\n    this.initBscroll()\n  },\n  methods: {\n    initBscroll() {\n      this.bscroll = new BScroll(this.$refs.bsWrapper, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        useTransition: false,\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP\n        }\n      })\n\n      this.bscroll.on('pullingDown', this.pullingDownHandler)\n      this.bscroll.on('scrollEnd', e => {\n        console.log('scrollEnd')\n      })\n      // v2.4.0 supported\n      this.bscroll.on('enterThreshold', () => {\n        this.setTipText(PHASE.moving.enter)\n      })\n      this.bscroll.on('leaveThreshold', () => {\n        this.setTipText(PHASE.moving.leave)\n      })\n    },\n    async pullingDownHandler() {\n      this.setTipText(PHASE.fetching)\n      STEP += 1\n      await this.getData()\n\n      this.setTipText(PHASE.succeed)\n      // tell BetterScroll to finish pull down\n      this.bscroll.finishPullDown()\n      // waiting for BetterScroll's bounceAnimation then refresh size\n      setTimeout(() => {\n        this.bscroll.refresh()\n      }, TIME_BOUNCE + 50)\n    },\n    async getData() {\n      const newData = await this.mockFetchData()\n      this.dataList = newData.concat(this.dataList)\n    },\n    mockFetchData() {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          const dataList = generateData()\n          resolve(dataList)\n        }, REQUEST_TIME)\n      })\n    },\n    setTipText(phase = PHASE.default) {\n      const TEXTS_MAP = {\n        'enter': `${ARROW_BOTTOM} Pull down`,\n        'leave': `${ARROW_UP} Release`,\n        'fetching': 'Loading...',\n        'succeed': 'Refresh succeed'\n      }\n      this.tipText = TEXTS_MAP[phase]\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\" scoped>\n.pulldown-sina\n  height 100%\n\n.pulldown-bswrapper\n  position relative\n  height 100%\n  padding 0 10px\n  border 1px solid #ccc\n  overflow hidden\n\n.pulldown-list\n  padding 0\n\n.pulldown-list-item\n  padding 10px 0\n  list-style none\n  border-bottom 1px solid #ccc\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/pullup/default.vue",
    "content": "<template>\n  <div class=\"pullup\">\n    <div ref=\"scroll\" class=\"pullup-wrapper\">\n      <div class=\"pullup-content\">\n        <ul class=\"pullup-list\">\n          <li v-for=\"i of data\" :key=\"i\" class=\"pullup-list-item\">\n            {{ i % 5 === 0 ? 'scroll up 👆🏻' : `I am item ${i} `}}\n          </li>\n        </ul>\n        <div class=\"pullup-tips\">\n          <div v-if=\"!isPullUpLoad\" class=\"before-trigger\">\n            <span class=\"pullup-txt\">Pull up and load more</span>\n          </div>\n          <div v-else class=\"after-trigger\">\n            <span class=\"pullup-txt\">Loading...</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import Pullup from '@better-scroll/pull-up'\n\n  BScroll.use(Pullup)\n\n  export default {\n    data() {\n      return {\n        isPullUpLoad: false,\n        data: 30\n      }\n    },\n    mounted() {\n      this.initBscroll()\n    },\n    methods: {\n      initBscroll() {\n        this.bscroll = new BScroll(this.$refs.scroll, {\n          pullUpLoad: true\n        })\n\n        this.bscroll.on('pullingUp', this.pullingUpHandler)\n      },\n      async pullingUpHandler() {\n        this.isPullUpLoad = true\n\n        await this.requestData()\n\n        this.bscroll.finishPullUp()\n        this.bscroll.refresh()\n        this.isPullUpLoad = false\n      },\n      async requestData() {\n        try {\n          const newData = await this.ajaxGet(/* url */)\n          this.data += newData\n        } catch (err) {\n          // handle err\n          console.log(err)\n        }\n      },\n      ajaxGet(/* url */) {\n        return new Promise(resolve => {\n          setTimeout(() => {\n            resolve(20)\n          }, 1000)\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.pullup\n  height: 100%\n  .pullup-wrapper\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pullup-list\n    padding: 0\n  .pullup-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pullup-tips\n    padding: 20px\n    text-align: center\n    color: #999\n</style>"
  },
  {
    "path": "packages/examples/vue/components/scrollbar/custom.vue",
    "content": "<template>\n  <div class=\"custom-scrollbar-container\">\n    <div ref=\"wrapper\" class=\"custom-scrollbar-wrapper\">\n      <img @load=\"onload\" class=\"custom-scrollbar-content\" :src=\"girlImageLink\" alt=\"\">\n      <!-- custom-vertical-scrollbar-->\n      <div class=\"custom-vertical-scrollbar\" ref=\"vertical\">\n        <div class=\"custom-vertical-indicator\"></div>\n      </div>\n      <!-- custom-horizontal-scrollbar-->\n      <div class=\"custom-horizontal-scrollbar\" ref=\"horizontal\">\n        <div class=\"custom-horizontal-indicator\"></div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import ScrollBar from '@better-scroll/scroll-bar'\n  import girlImage from './girl.jpg'\n\n  BScroll.use(ScrollBar)\n\n  export default {\n    data () {\n      return {\n        girlImageLink : girlImage\n      }\n    },\n    methods: {\n      initBscroll() {\n        this.scroll = new BScroll(this.$refs.wrapper, {\n          freeScroll: true,\n          click: true,\n          scrollbar: {\n            customElements: [this.$refs.horizontal, this.$refs.vertical],\n            fade: false,\n            interactive: true,\n            scrollbarTrackClickable: true\n          }\n        })\n      },\n      onload () {\n        this.initBscroll()\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.custom-scrollbar-container\n  .custom-scrollbar-wrapper\n    position relative\n    width 280px\n    height 280px\n    overflow hidden\n  .custom-scrollbar-content\n    max-width none\n  .custom-vertical-scrollbar\n    position absolute\n    top 50%\n    right 10px\n    height 100px\n    width 7px\n    border-radius 6px\n    transform translateY(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-vertical-indicator\n    width 100%\n    height 20px\n    border-radius 6px\n    background-color #db8090\n  .custom-horizontal-scrollbar\n    position absolute\n    left 50%\n    bottom 10px\n    width 100px\n    height 7px\n    border-radius 6px\n    transform translateX(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-horizontal-indicator\n    height 100%\n    width 20px\n    border-radius 6px\n    background-color #db8090\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/scrollbar/horizontal.vue",
    "content": "<template>\n  <div class=\"horizontal-scrollbar-container\">\n    <div class=\"scroll-wrapper\" ref=\"scroll\">\n      <div class=\"scroll-content\">\n        <div class=\"scroll-item\" v-for=\"index in num\" :key=\"index\">{{index}}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Scrollbar from '@better-scroll/scroll-bar'\n\n  BScroll.use(Scrollbar)\n\n  export default {\n    data () {\n      return {\n        num: 12\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.scroll.destroy()\n    },\n    methods: {\n      init() {\n        this.scroll = new BScroll(this.$refs.scroll, {\n          scrollX: true,\n          scrollY: false,\n          click: true,\n          probeType: 1,\n          scrollbar: {\n            fade: false,\n            interactive: true,\n            scrollbarTrackClickable: true,\n            scrollbarTrackOffsetType: 'clickedPoint' // can use 'step'\n          }\n        })\n        this.scroll.on('scrollEnd', () => {\n          console.log('scrollEnd')\n        })\n        this.scroll.on('scrollStart', () => {\n          console.log('scrollStart')\n        })\n        this.scroll.on('scroll', () => {\n          console.log('scroll')\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\" scoped>\n\n.horizontal-scrollbar-container\n  .scroll-wrapper\n    position relative\n    display flex\n    align-content center\n    width 90%\n    height 100px\n    margin 80px auto\n    white-space nowrap\n    border 3px solid rgba(30, 80, 255, 0.3)\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n      align-self center\n    .scroll-item\n      opacity  0.6\n      color white\n      box-sizing border-box\n      height 50px\n      width 50px\n      line-height 50px\n      border-radius 50%\n      font-size 18px\n      display inline-block\n      text-align center\n      padding 0 10px\n      margin 0 10px\n      &:nth-child(4n)\n        background-color #06F\n      &:nth-child(4n+1)\n        background-color #0f9d00\n      &:nth-child(4n+2)\n        background-color #F00\n      &:nth-child(4n+3)\n        background-color #ffea00\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/scrollbar/mousewheel.vue",
    "content": "<template>\n  <div class=\"mousewheel-scrollbar-container\">\n    <div ref=\"wrapper\" class=\"custom-scrollbar-wrapper\">\n      <div class=\"custom-scrollbar-content\">\n        <img @load=\"onload\" :src=\"girlImageLink\" alt=\"\">\n      </div>\n      <!-- custom-horizontal-scrollbar-->\n      <div class=\"custom-horizontal-scrollbar\" ref=\"horizontal\">\n        <div class=\"custom-horizontal-indicator\"></div>\n      </div>\n    </div>\n    <div class=\"tip\">please use your mouse-wheel</div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import ScrollBar from '@better-scroll/scroll-bar'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  import girlImage from './sad-girl.jpg'\n\n  BScroll.use(ScrollBar)\n  BScroll.use(MouseWheel)\n\n  export default {\n    data () {\n      return {\n        girlImageLink : girlImage\n      }\n    },\n    methods: {\n      initBscroll() {\n        this.scroll = new BScroll(this.$refs.wrapper, {\n          scrollX: true,\n          scrollY: false,\n          click: true,\n          mouseWheel: true,\n          scrollbar: {\n            customElements: [this.$refs.horizontal],\n            fade: true,\n            interactive: true,\n            scrollbarTrackClickable: true\n          }\n        })\n      },\n      onload () {\n        this.initBscroll()\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\" scoped>\n.mousewheel-scrollbar-container\n  .custom-scrollbar-wrapper\n    position relative\n    width 280px\n    height 280px\n    overflow hidden\n  .custom-scrollbar-content\n    display inline-block\n    height 280px\n    > img\n      max-width none\n  .custom-horizontal-scrollbar\n    position absolute\n    left 50%\n    bottom 10px\n    width 100px\n    height 7px\n    border-radius 6px\n    transform translateX(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-horizontal-indicator\n    height 100%\n    width 20px\n    border-radius 6px\n    background-color #db8090\n  .tip\n    text-align center\n    margin-top 10px\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/scrollbar/vertical.vue",
    "content": "<template>\n  <div class=\"scrollbar\">\n    <div ref=\"wrapper\" class=\"scrollbar-wrapper\">\n      <ul class=\"scrollbar-content\">\n        <li v-for=\"i of 40\" :key=\"i\" class=\"scrollbar-content-item\">\n          {{ `I am item ${i} `}}\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script>\n  import BScroll from '@better-scroll/core'\n  import ScrollBar from '@better-scroll/scroll-bar'\n\n  BScroll.use(ScrollBar)\n\n  export default {\n    mounted() {\n      this.initBscroll()\n    },\n    methods: {\n      initBscroll() {\n        this.scroll = new BScroll(this.$refs.wrapper, {\n          scrollY: true,\n          scrollbar: true\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"stylus\">\n.scrollbar\n  height: 100%\n.scrollbar-wrapper\n  position: relative\n  height: 100%\n  padding: 0 10px\n  border: 1px solid #ccc\n  overflow: hidden\n.scrollbar-content-item\n  padding: 10px 0\n  list-style: none\n  border-bottom: 1px solid #ccc\n  text-align: left\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/slide/banner.vue",
    "content": "<template>\n  <div class=\"slide-banner\">\n    <div class=\"banner-wrapper\">\n      <div class=\"slide-banner-wrapper\" ref=\"slide\">\n        <div class=\"slide-banner-content\">\n          <div v-for=\"num in nums\" class=\"slide-page\" :class=\"'page' + num\" :key=\"num\">page {{num}}</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"num in nums\"\n          :key=\"num\"\n          :class=\"{'active': currentPageIndex === (num - 1)}\"></span>\n      </div>\n    </div>\n    <div class=\"btn-wrap\">\n      <button class=\"next\" @click=\"nextPage\">nextPage</button>\n      <button class=\"prev\" @click=\"prePage\">prePage</button>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        nums: 4,\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        this.slide = new BScroll(this.$refs.slide, {\n          scrollX: true,\n          scrollY: false,\n          slide: true,\n          momentum: false,\n          bounce: false,\n          probeType: 3\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n\n        this.slide.on('slideWillChange', (page) => {\n          this.currentPageIndex = page.pageX\n        })\n\n        // v2.1.0\n        this.slide.on('slidePageChanged', (page) => {\n          console.log('CurrentPage changed to => ', page)\n        })\n      },\n      _onScrollEnd () {\n        console.log('CurrentPage => ', this.slide.getCurrentPage())\n      },\n      nextPage() {\n        this.slide.next()\n      },\n      prePage() {\n        this.slide.prev()\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.slide-banner\n  .banner-wrapper\n    position relative\n  .slide-banner-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-banner-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/slide/dynamic.vue",
    "content": "<template>\n  <div class=\"dynamic-slide-banner\">\n    <div class=\"banner-wrapper\">\n      <div class=\"slide-banner-wrapper\" ref=\"slide\">\n        <div class=\"slide-banner-content\">\n          <div v-for=\"num in nums\" class=\"slide-page\" :class=\"'page' + num\" :key=\"num\">page {{num}}</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"num in nums\"\n          :key=\"num\"\n          :class=\"{'active': currentPageIndex === (num - 1)}\"></span>\n      </div>\n    </div>\n    <div class=\"btn-wrap\">\n      <button @click=\"increase\" class=\"increase\">increase</button>\n      <button @click=\"decrease\" class=\"decrease\">decrease</button>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        nums: 1,\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      increase() {\n        this.nums += 1\n        this.$nextTick(() => {\n          this.slide.refresh()\n        })\n      },\n      decrease() {\n        this.nums -= 1\n        this.nums = Math.max(1, this.nums) // at least one page\n        this.$nextTick(() => {\n          this.slide.refresh()\n        })\n      },\n      init() {\n        window.slide = this.slide = new BScroll(this.$refs.slide, {\n          scrollX: true,\n          scrollY: false,\n          slide: {\n            autoplay: false,\n            loop: true\n          },\n          momentum: false,\n          bounce: false,\n          probeType: 3\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n\n        this.slide.on('slideWillChange', (page) => {\n          console.log('【slideWillChange】CurrentPage =>', page)\n          this.currentPageIndex = page.pageX\n        })\n\n        // v2.1.0\n        this.slide.on('slidePageChanged', (page) => {\n          console.log('【slidePageChanged】CurrentPage =>', page)\n        })\n      },\n      _onScrollEnd () {\n        console.log('【scrollEnd】CurrentPage =>', this.slide.getCurrentPage())\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n.dynamic-slide-banner\n  .banner-wrapper\n    position relative\n  .slide-banner-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-banner-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n      &.page5\n        background-color #E71D36\n      &.page6\n        background-color #2EC4B6\n      &.page7\n        background-color #EFFFE9\n      &.page8\n        background-color #011627\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/slide/fullpage.vue",
    "content": "<template>\n  <div class=\"slide-fullpage\">\n    <div class=\"banner-wrapper\">\n      <div class=\"slide-banner-wrapper\" ref=\"slide\">\n        <div class=\"slide-banner-content\">\n          <div v-for=\"num in nums\" :key=\"num\" class=\"slide-page\" :class=\"'page' + num\">page {{num}}</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"num in nums\"\n          :key=\"num\"\n          :class=\"{'active': currentPageIndex === (num - 1)}\"></span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        nums: 4,\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        window.slide = this.slide = new BScroll(this.$refs.slide, {\n          scrollX: true,\n          scrollY: false,\n          slide: {\n            threshold: 100,\n            loop: false,\n            autoplay: false\n          },\n          useTransition: false,\n          momentum: false,\n          bounce: false,\n          stopPropagation: true\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n      },\n      nextPage() {\n        this.slide.next()\n      },\n      prePage() {\n        this.slide.prev()\n      },\n      _onScrollEnd() {\n        let pageIndex = this.slide.getCurrentPage().pageX\n        this.currentPageIndex = pageIndex\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.slide-fullpage\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .banner-wrapper\n    position relative\n    height 100%\n  .slide-banner-wrapper\n    height 100%\n    overflow hidden\n  .slide-banner-content\n    height 100%\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 100%\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/slide/specified-index.vue",
    "content": "<template>\n  <div class=\"slide-specified-index\">\n    <div class=\"banner-wrapper\">\n      <div class=\"slide-specified-wrapper\" ref=\"slide\">\n        <div class=\"slide-specified-content\">\n          <div v-for=\"num in nums\" class=\"slide-page\" :class=\"'page' + num\" :key=\"num\">page {{num}}</div>\n        </div>\n      </div>\n    </div>\n    <div v-if=\"slideCreated\" class=\"description\">currentPageIndex is {{slide.getCurrentPage().pageX}}</div>\n    <div class=\"btn-wrap\">\n      <button class=\"next\" @click=\"nextPage\">nextPage</button>\n      <button class=\"prev\" @click=\"prePage\">prePage</button>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        slideCreated: false,\n        nums: 4,\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n      this.slideCreated = true\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        this.slide = new BScroll(this.$refs.slide, {\n          scrollX: true,\n          scrollY: false,\n          slide: {\n            autoplay: false,\n            loop: true,\n            startPageXIndex: 2 // v2.3.0\n          },\n          momentum: false,\n          bounce: false,\n          probeType: 3\n        })\n        // v2.1.0\n        this.slide.on('slidePageChanged', (page) => {\n          console.log('CurrentPage changed to => ', page)\n          // always get the currentPageIndex, because slide is not reactive\n          this.$forceUpdate()\n        })\n      },\n      nextPage() {\n        this.slide.next()\n      },\n      prePage() {\n        this.slide.prev()\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.slide-specified-index\n  .banner-wrapper\n    position relative\n  .slide-specified-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-specified-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .description\n    text-align center\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/slide/vertical.vue",
    "content": "<template>\n  <div class=\"slide-vertical\">\n    <div class=\"vertical-wrapper\">\n      <div class=\"slide-vertical-wrapper\" ref=\"slide\">\n        <div class=\"slide-vertical-content\">\n          <div v-for=\"num in nums\" :key=\"num\" class=\"slide-page\" :class=\"'page' + num\">page {{num}}</div>\n        </div>\n      </div>\n      <div class=\"dots-wrapper\">\n        <span\n          class=\"dot\"\n          v-for=\"num in nums\"\n          :key=\"num\"\n          :class=\"{'active': currentPageIndex === (num - 1)}\"></span>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n\n  export default {\n    data() {\n      return {\n        nums: 4,\n        currentPageIndex: 0\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    beforeDestroy() {\n      this.slide.destroy()\n    },\n    methods: {\n      init() {\n        this.slide = new BScroll(this.$refs.slide, {\n          scrollX: false,\n          scrollY: true,\n          slide: {\n            threshold: 100\n          },\n          useTransition: true,\n          momentum: false,\n          bounce: false,\n          stopPropagation: true\n        })\n        this.slide.on('scrollEnd', this._onScrollEnd)\n      },\n      _onScrollEnd() {\n        let pageIndex = this.slide.getCurrentPage().pageY\n        this.currentPageIndex = pageIndex\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.slide-vertical\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .vertical-wrapper\n    position relative\n    height 100%\n    font-size 0\n  .slide-vertical-wrapper\n    height 100%\n    overflow hidden\n    .slide-page\n      display inline-block\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #D6EADF\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    right 4px\n    top 50%\n    transform translateY(-50%)\n    .dot\n      display block\n      margin 4px 0\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        height  20px\n        border-radius 5px\n\n</style>\n"
  },
  {
    "path": "packages/examples/vue/components/zoom/default.vue",
    "content": "<template>\n  <div class=\"zoom-default\">\n    <div class=\"zoom-wrapper\" ref=\"zoom\">\n      <div class=\"zoom-items\">\n        <div class=\"grid-item\" v-for=\"num in nums\" :key=\"num\">{{num}}</div>\n      </div>\n    </div>\n    <div class=\"btn-wrap\">\n      <button class=\"zoom-half\" @click=\"zoomTo(0.5)\">zoomTo:0.5</button>\n      <button class=\"zoom-original\" @click=\"zoomTo(1)\">zoomTo:1</button>\n      <button class=\"zoom-double\" @click=\"zoomTo(2)\">zoomTo:2</button>\n    </div>\n    <div class=\"linkwork-wrap\">\n      <p>changing with zooming action</p>\n      <div class=\"linkwork-block\" :style=\"{transform: linkworkTransform}\"></div>\n    </div>\n  </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import BScroll from '@better-scroll/core'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Zoom)\n\n  const COMPONENT_NAME = 'zoom'\n\n  export default {\n    name: COMPONENT_NAME,\n    data() {\n      return {\n        nums: 16,\n        linkworkTransform: 'scale(1)'\n      }\n    },\n    mounted() {\n      this.init()\n    },\n    methods: {\n      init() {\n        this.zoom = new BScroll(this.$refs.zoom, {\n          freeScroll: true,\n          scrollX: true,\n          scrollY: true,\n          disableMouse: true,\n          useTransition: true,\n          zoom: {\n            start: 1.5,\n            min: 0.5,\n            max: 3,\n            initialOrigin: ['center', 'center']\n          }\n        })\n\n        this.zoom.on('zooming', ({ scale }) => {\n          this.linkworkTransform = `scale(${scale})`\n        })\n\n        this.zoom.on('zoomEnd', ({ scale }) => {\n          console.log(scale)\n        })\n      },\n      zoomTo(value) {\n        this.zoom.zoomTo(value, 'center', 'center')\n      }\n    }\n  }\n</script>\n<style lang=\"stylus\" rel=\"stylesheet/stylus\">\n\n.zoom-default\n  .zoom-wrapper\n    width 100%\n    overflow hidden\n    .zoom-items\n      display flex\n      flex-direction row\n      flex-wrap wrap\n      align-content space-between\n      .grid-item\n        flex 1 1 25%\n        box-sizing border-box\n        height 52px\n        line-height 52px\n        border 1px solid #eee\n        text-align center\n        &:nth-child(2n)\n          background-color #b3d4a8\n        &:nth-child(2n+1)\n          background-color #b6b7a3\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n  .linkwork-wrap\n    margin-top 50px\n    p\n      margin 10px 0\n      font-size 16px\n      font-weight bold\n      text-align center\n  .linkwork-block\n    margin 10px auto\n    width 60px\n    height 60px\n    border-radius 50%\n    background-image url(\"./good.svg\")\n    background-size 100%\n</style>\n"
  },
  {
    "path": "packages/examples/vue/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>BetterScroll Examples</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\"\n        content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\">\n  <link rel=\"shortcut icon\" href=\"static/bs.ico\" type=\"images/x-icon\">\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"static/css/reset.css\">\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"static/css/normalize.css\" media=\"screen\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n  <link href=\"https://fonts.googleapis.com/css2?family=Patrick+Hand&display=swap\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"static/css/stylesheet.css\" media=\"screen\">\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"static/css/github-light.css\" media=\"screen\">\n</head>\n<body>\n<div id=\"app\"></div>\n</body>\n</html>\n"
  },
  {
    "path": "packages/examples/vue/main.js",
    "content": "import '@babel/polyfill'\nimport Vue from 'vue'\nimport router from './router'\nimport App from './App.vue'\n\n// /* eslint-disable no-unused-vars */\n// import VConsole from 'vconsole' // develop console\n// /* eslint-disable no-new */\n// new VConsole()\n\n/* eslint-disable no-new */\nnew Vue({\n  el: '#app',\n  router,\n  render: h => h(App)\n})\n"
  },
  {
    "path": "packages/examples/vue/pages/compose-entry.vue",
    "content": "<template>\n  <div class=\"compose\">\n    <ul class=\"example-list\">\n      <li\n        @click=\"goPage('/compose/pullup-pulldown')\"\n        class=\"example-item\"\n      >\n        <span>pullup-pulldown</span>\n      </li>\n      <li\n        @click=\"goPage('/compose/pullup-pulldown-slide')\"\n        class=\"example-item\"\n      >\n        <span>pullup-pulldown-slide</span>\n      </li>\n      <li\n        @click=\"goPage('/compose/pullup-pulldown-outnested')\"\n        class=\"example-item\"\n      >\n        <span>pullup-pulldown-outnested</span>\n      </li>\n      <li\n        @click=\"goPage('/compose/slide-nested')\"\n        class=\"example-item\"\n      >\n        <span>slide-nested</span>\n      </li>\n      \n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n.free-scroll-container\n  &.view\n    position fixed !important\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/core-entry.vue",
    "content": "<template>\n  <div class=\"core\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/core/default')\">\n        <span>vertical</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/horizontal')\">\n        <span>horizontal</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/dynamic-content')\">\n        <span>dynamic-content</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/specified-content')\">\n        <span>specified-content</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/freescroll')\">\n        <span>freescroll</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/vertical-rotated')\">\n        <span>vertical rotated(v2.3.0)</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/core/horizontal-rotated')\">\n        <span>horizontal rotated(v2.3.0)</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n.free-scroll-container\n  &.view\n    position fixed!important\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/form-entry.vue",
    "content": "<template>\n  <div class=\"core\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/form/textarea')\">\n        <span>textarea</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n.free-scroll-container\n  &.view\n    position fixed!important\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/indicators-entry.vue",
    "content": "<template>\n  <div class=\"indicators\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/indicators/minimap')\">\n        <span>minimap</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/indicators/parallax-scroll')\">\n        <span>parallax scroll</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "packages/examples/vue/pages/infinity-entry.vue",
    "content": "<template>\n  <infinity />\n</template>\n\n<script>\nimport Infinity from 'vue-example/components/infinity/default.vue'\nexport default {\n  components: {\n    Infinity\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n\n</style>"
  },
  {
    "path": "packages/examples/vue/pages/mouse-wheel-entry.vue",
    "content": "<template>\n  <div class=\"mouse-wheel\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/vertical-scroll')\">\n        <span>vertical scroll</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/horizontal-scroll')\">\n        <span>horizontal scroll</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/vertical-slide')\">\n        <span>vertical slide</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/horizontal-slide')\">\n        <span>horizontal slide</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/pullup')\">\n        <span>pull up load</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/pulldown')\">\n        <span>pull down refresh</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/mouse-wheel/picker')\">\n        <span>picker</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n.free-scroll-container\n  &.view\n    position fixed!important\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/movable-entry.vue",
    "content": "<template>\n  <div class=\"movable\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/movable/default')\">\n        <span>default</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/movable/scale')\">\n        <span>scale</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/movable/multi-content')\">\n        <span>multi-content</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/movable/multi-content-scale')\">\n        <span>multi-content-scale</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/nested-scroll-entry.vue",
    "content": "<template>\n  <div class=\"core\">\n    <ul class=\"example-list\">\n      <li\n        class=\"example-item\"\n        :key=\"index\"\n        v-for=\"(route, index) in routes\"\n        @click=\"goPage(route.path)\">\n        <span>{{route.name}}</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  data () {\n    return {\n      routes: [\n        {\n          path: '/nested-scroll/vertical',\n          name: 'vertical'\n        },\n        {\n          path: '/nested-scroll/horizontal',\n          name: 'horizontal'\n        },\n        {\n          path: '/nested-scroll/horizontal-in-vertical',\n          name: 'horizontal-in-vertical'\n        },\n        {\n          path: '/nested-scroll/triple-vertical',\n          name: 'triple-vertical'\n        }\n      ]\n    }\n  },\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "packages/examples/vue/pages/observe-dom-entry.vue",
    "content": "<template>\n  <observe-dom />\n</template>\n\n<script>\nimport ObserveDom from 'vue-example/components/observe-dom/default.vue'\nexport default {\n  components: {\n    ObserveDom\n  }\n}\n</script>"
  },
  {
    "path": "packages/examples/vue/pages/observe-image-entry.vue",
    "content": "<template>\n  <observe-image />\n</template>\n\n<script>\nimport ObserveImage from 'vue-example/components/observe-image/default.vue'\nexport default {\n  components: {\n    ObserveImage\n  }\n}\n</script>"
  },
  {
    "path": "packages/examples/vue/pages/picker-entry.vue",
    "content": "<template>\n  <div class=\"container\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/picker/one-column')\">\n          <span>One Column Picker</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/picker/double-column')\">\n          <span>Double Column Picker</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/picker/linkage-column')\">\n          <span>Linkage Column Picker</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/pulldown-entry.vue",
    "content": "<template>\n  <div class=\"container\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/pulldown/default')\">\n          <span>Default</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/pulldown/sina')\">\n          <span>Sina-Weibo(v2.4.0)</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "packages/examples/vue/pages/pullup-entry.vue",
    "content": "<template>\n  <pullup />\n</template>\n\n<script>\nimport Pullup from 'vue-example/components/pullup/default.vue'\nexport default {\n  components: {\n    Pullup\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n\n</style>"
  },
  {
    "path": "packages/examples/vue/pages/scrollbar-entry.vue",
    "content": "<template>\n  <div class=\"scroll-bar\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/scrollbar/vertical')\">\n        <span>vertical</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/scrollbar/horizontal')\">\n        <span>horizontal</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/scrollbar/custom')\">\n        <span>custom</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/scrollbar/mousewheel')\">\n        <span>use mousewheel</span>\n      </li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n\n</style>"
  },
  {
    "path": "packages/examples/vue/pages/slide-entry.vue",
    "content": "<template>\n  <div class=\"slide\">\n    <ul class=\"example-list\">\n      <li class=\"example-item\" @click=\"goPage('/slide/banner')\">\n          <span>banner slide</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/slide/fullpage')\">\n          <span>page slide</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/slide/vertical')\">\n          <span>vertical slide</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/slide/dynamic')\">\n          <span>dynamic slide（v2.1.0）</span>\n      </li>\n      <li class=\"example-item\" @click=\"goPage('/slide/specified')\">\n          <span>specified index slide（v2.3.0）</span>\n      </li>\n      <li class=\"example-item placeholder\"></li>\n    </ul>\n    <transition name=\"move\">\n      <router-view class=\"view\"></router-view>\n    </transition>\n  </div>\n</template>\n<script>\nexport default {\n  methods: {\n    goPage(path) {\n      this.$router.push(path)\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n</style>\n"
  },
  {
    "path": "packages/examples/vue/pages/zoom-entry.vue",
    "content": "<template>\n  <zoom />\n</template>\n\n<script type=\"text/ecmascript-6\">\n  import Zoom from 'vue-example/components/zoom/default.vue'\n  export default {\n    components: {\n      Zoom\n    }\n  }\n</script>\n"
  },
  {
    "path": "packages/examples/vue/router/index.js",
    "content": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nimport CoreEntry from 'vue-example/pages/core-entry'\nimport ObserveDOMEntry from 'vue-example/pages/observe-dom-entry'\nimport ZoomEntry from 'vue-example/pages/zoom-entry'\nimport SlideEntry from 'vue-example/pages/slide-entry'\nimport PickerEntry from 'vue-example/pages/picker-entry'\nimport PullupEntry from 'vue-example/pages/pullup-entry'\nimport PullDownEntry from 'vue-example/pages/pulldown-entry'\nimport ScrollBarEntry from 'vue-example/pages/scrollbar-entry'\nimport InfinityScrollEntry from 'vue-example/pages/infinity-entry'\nimport FormEntry from 'vue-example/pages/form-entry'\nimport NestedScrollEntry from 'vue-example/pages/nested-scroll-entry'\nimport MovableEntry from 'vue-example/pages/movable-entry'\nimport MouseWheelEntry from 'vue-example/pages/mouse-wheel-entry'\nimport ComposeEntry from 'vue-example/pages/compose-entry'\nimport ObserveImageEntry from 'vue-example/pages/observe-image-entry'\nimport IndicatorsEntry from 'vue-example/pages/indicators-entry'\n\nimport ScrollbarVertical from 'vue-example/components/scrollbar/vertical'\nimport ScrollbarHorizontal from 'vue-example/components/scrollbar/horizontal'\nimport ScrollbarCustom from 'vue-example/components/scrollbar/custom'\nimport ScrollbarMouseWheel from 'vue-example/components/scrollbar/mousewheel'\n\nimport MouseWheelVerticalScroll from 'vue-example/components/mouse-wheel/vertical-scroll'\nimport MouseWheelHorizontalScroll from 'vue-example/components/mouse-wheel/horizontal-scroll'\nimport MouseWheelVerticalSlide from 'vue-example/components/mouse-wheel/vertical-slide'\nimport MouseWheelHorizontalSlide from 'vue-example/components/mouse-wheel/horizontal-slide'\nimport MouseWheelPullUp from 'vue-example/components/mouse-wheel/pullup'\nimport MouseWheelPullDown from 'vue-example/components/mouse-wheel/pulldown'\nimport MouseWheelPicker from 'vue-example/components/mouse-wheel/picker'\n\nimport BannerSlide from 'vue-example/components/slide/banner'\nimport PageSlide from 'vue-example/components/slide/fullpage'\nimport VerticalSlide from 'vue-example/components/slide/vertical'\nimport DynamicSlide from 'vue-example/components/slide/dynamic'\nimport SpecifiedIndexSlide from 'vue-example/components/slide/specified-index'\n\nimport VerticalScroll from 'vue-example/components/core/default'\nimport HorizontalScroll from 'vue-example/components/core/horizontal'\nimport DynamicContentScroll from 'vue-example/components/core/dynamic-content'\nimport SpecifiedContentScroll from 'vue-example/components/core/specified-content'\nimport Freescroll from 'vue-example/components/core/freescroll'\nimport VerticalRotatedScroll from 'vue-example/components/core/vertical-rotated'\nimport HorizontalRotatedScroll from 'vue-example/components/core/horizontal-rotated'\n\nimport OneColumnPicker from 'vue-example/components/picker/one-column'\nimport DoubleColumnPicker from 'vue-example/components/picker/double-column'\nimport LinkageColumnPicker from 'vue-example/components/picker/linkage-column'\n\nimport FormTextarea from 'vue-example/components/form/textarea'\n\nimport NestedVerticalScroll from 'vue-example/components/nested-scroll/vertical'\nimport NestedTripleVerticalScroll from 'vue-example/components/nested-scroll/triple-vertical'\nimport NestedHorizontalScroll from 'vue-example/components/nested-scroll/horizontal'\nimport NestedHorizontalInVertical from 'vue-example/components/nested-scroll/horizontal-in-vertical'\n\nimport Movable from 'vue-example/components/movable/default'\nimport MovableMultiContent from 'vue-example/components/movable/multi-content'\nimport MovableScale from 'vue-example/components/movable/scale'\nimport MovableMultiContentScale from 'vue-example/components/movable/multi-content-scale'\n\nimport ComposePullUpPullDown from 'vue-example/components/compose/pullup-pulldown'\nimport ComposePullUpPullDownSlide from 'vue-example/components/compose/pullup-pulldown-slide'\nimport ComposePullUpPullDownNested from 'vue-example/components/compose/pullup-pulldown-outnested'\nimport ComposeSlideNested from 'vue-example/components/compose/slide-nested'\n\nimport IndicatorsMinimap from 'vue-example/components/indicators/minimap'\nimport IndicatorsParallaxScroll from 'vue-example/components/indicators/parallax-scroll'\n\nimport PulldownDefault from 'vue-example/components/pulldown/default'\nimport PulldownSinaWeibo from 'vue-example/components/pulldown/sina-weibo'\nVue.use(Router)\n\nexport default new Router({\n  routes: [\n    {\n      path: '/zoom',\n      component: ZoomEntry,\n    },\n    {\n      path: '/observe-dom',\n      component: ObserveDOMEntry\n    },\n    {\n      path: '/slide',\n      component: SlideEntry,\n      children: [\n        {\n          path: 'banner',\n          component: BannerSlide,\n        },\n        {\n          path: 'fullpage',\n          component: PageSlide,\n        },\n        {\n          path: 'vertical',\n          component: VerticalSlide,\n        },\n        {\n          path: 'dynamic',\n          component: DynamicSlide,\n        },\n        {\n          path: 'specified',\n          component: SpecifiedIndexSlide\n        }\n      ],\n    },\n    {\n      path: '/core',\n      component: CoreEntry,\n      children: [\n        {\n          path: 'default',\n          component: VerticalScroll,\n        },\n        {\n          path: 'horizontal',\n          component: HorizontalScroll,\n        },\n        {\n          path: 'dynamic-content',\n          component: DynamicContentScroll\n        },\n        {\n          path: 'specified-content',\n          component: SpecifiedContentScroll\n        },\n        {\n          path: 'freescroll',\n          component: Freescroll,\n        },\n        {\n          path: 'vertical-rotated',\n          component: VerticalRotatedScroll\n        },\n        {\n          path: 'horizontal-rotated',\n          component: HorizontalRotatedScroll\n        }\n      ],\n    },\n    {\n      path: '/mouse-wheel',\n      component: MouseWheelEntry,\n      children: [\n        {\n          path: 'vertical-scroll',\n          component: MouseWheelVerticalScroll,\n        },\n        {\n          path: 'horizontal-scroll',\n          component: MouseWheelHorizontalScroll,\n        },\n        {\n          path: 'vertical-slide',\n          component: MouseWheelVerticalSlide,\n        },\n        {\n          path: 'horizontal-slide',\n          component: MouseWheelHorizontalSlide,\n        },\n        {\n          path: 'pullup',\n          component: MouseWheelPullUp,\n        },\n        {\n          path: 'pulldown',\n          component: MouseWheelPullDown,\n        },\n        {\n          path: 'picker',\n          component: MouseWheelPicker,\n        },\n      ],\n    },\n    {\n      path: '/picker',\n      component: PickerEntry,\n      children: [\n        {\n          path: 'one-column',\n          component: OneColumnPicker,\n        },\n        {\n          path: 'double-column',\n          component: DoubleColumnPicker,\n        },\n        {\n          path: 'linkage-column',\n          component: LinkageColumnPicker,\n        },\n      ],\n    },\n    {\n      path: '/pullup',\n      component: PullupEntry,\n    },\n    {\n      path: '/pulldown',\n      component: PullDownEntry,\n      children: [\n        {\n          path: 'default',\n          component: PulldownDefault\n        },\n        {\n          path: 'sina',\n          component: PulldownSinaWeibo\n        }\n      ]\n    },\n    {\n      path: '/scrollbar',\n      component: ScrollBarEntry,\n      children: [\n        {\n          path: 'vertical',\n          component: ScrollbarVertical\n        },\n        {\n          path: 'horizontal',\n          component: ScrollbarHorizontal\n        },\n        {\n          path: 'custom',\n          component: ScrollbarCustom\n        },\n        {\n          path: 'mousewheel',\n          component: ScrollbarMouseWheel\n        }\n      ]\n    },\n    {\n      path: '/infinity',\n      component: InfinityScrollEntry,\n    },\n    {\n      path: '/form',\n      component: FormEntry,\n      children: [\n        {\n          path: 'textarea',\n          component: FormTextarea,\n        },\n      ],\n    },\n    {\n      path: '/nested-scroll',\n      component: NestedScrollEntry,\n      children: [\n        {\n          path: 'vertical',\n          component: NestedVerticalScroll,\n        },\n        {\n          path: 'horizontal',\n          component: NestedHorizontalScroll,\n        },\n        {\n          path: 'horizontal-in-vertical',\n          component: NestedHorizontalInVertical,\n        },\n        {\n          path: 'triple-vertical',\n          component: NestedTripleVerticalScroll,\n        }\n      ],\n    },\n    {\n      path: '/movable',\n      component: MovableEntry,\n      children: [\n        {\n          path: 'default',\n          component: Movable,\n        },\n        {\n          path: 'scale',\n          component: MovableScale,\n        },\n        {\n          path: 'multi-content',\n          component: MovableMultiContent\n        },\n        {\n          path: 'multi-content-scale',\n          component: MovableMultiContentScale\n        }\n      ],\n    },\n    {\n      path: '/compose',\n      component: ComposeEntry,\n      children: [\n        {\n          path: 'pullup-pulldown',\n          component: ComposePullUpPullDown,\n        },\n        {\n          path: 'pullup-pulldown-slide',\n          component: ComposePullUpPullDownSlide,\n        },\n        {\n          path: 'pullup-pulldown-outnested',\n          component: ComposePullUpPullDownNested\n        },\n        {\n          path: 'slide-nested',\n          component: ComposeSlideNested\n        }\n      ],\n    },\n    {\n      path: '/observe-image',\n      component: ObserveImageEntry\n    },\n    {\n      path: '/indicators',\n      component: IndicatorsEntry,\n      children: [\n        {\n          path: 'minimap',\n          component: IndicatorsMinimap\n        },\n        {\n          path: 'parallax-scroll',\n          component: IndicatorsParallaxScroll\n        }\n      ]\n    }\n  ],\n})\n"
  },
  {
    "path": "packages/examples/vue-release.sh",
    "content": "#!/usr/bin/env sh\n\nset -e\n\nyarn run vue:build\n\ncd dist/vue\n\ngit init\ngit add -A\ngit commit -m 'update examples'\n\ngit push -f git@github.com:better-scroll/examples.git master:gh-pages\n\ncd -\n"
  },
  {
    "path": "packages/indicators/README.md",
    "content": "# @better-scroll/indicators\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/indicators/README_zh-CN.md)\n\nIndicator can be used to achieve magnifying glass, parallax scrolling and other effects.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\nBScroll.use(Indicators)\n\nconst bs = new BScroll('.wrapper', {\n  indicators: [\n    relationElement: someHTMLElement\n  ]\n})\n```\n\n```ts\ninterface IndicatorOptions {\n  interactive?: boolean\n  ratio?: Ratio\n  relationElementHandleElementIndex?: number\n  relationElement: HTMLElement\n}\n```"
  },
  {
    "path": "packages/indicators/README_zh-CN.md",
    "content": "# @better-scroll/indicators\n\n指示器，可用来实现放大镜、视觉滚动等效果。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\nBScroll.use(Indicators)\n\nconst bs = new BScroll('.wrapper', {\n  indicators: [\n    relationElement: someHTMLElement\n  ]\n})\n```\n\n```ts\ninterface IndicatorOptions {\n  interactive?: boolean\n  ratio?: Ratio\n  relationElementHandleElementIndex?: number\n  relationElement: HTMLElement\n}\n```\n"
  },
  {
    "path": "packages/indicators/package.json",
    "content": "{\n  \"name\": \"@better-scroll/indicators\",\n  \"version\": \"2.5.1\",\n  \"description\": \"used as parallax scrolling, magnifier effects\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/indicators.min.js\",\n  \"module\": \"dist/indicators.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"scrollbar\",\n    \"minimap\",\n    \"magnifier\",\n    \"parallax scroll\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/indicators\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/indicators/src/__mocks__/indicator.ts",
    "content": "const mockIndicator = jest\n  .fn()\n  .mockImplementation(function IndicatorMockFn(scroll: any, options: any) {\n    return {\n      destroy: jest.fn(),\n    }\n  })\n\nexport default mockIndicator\n"
  },
  {
    "path": "packages/indicators/src/__tests__/index.spec.ts",
    "content": "import Indicator from '../indicator'\nimport BScroll from '@better-scroll/core'\n\njest.mock('@better-scroll/core')\njest.mock('../indicator')\n\nimport Indicators from '../index'\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\nconst createIndicatorElement = () => {\n  // indicators DOM\n  const indicatorWrapper = document.createElement('div')\n  const indicatorEl = document.createElement('div')\n  indicatorWrapper.appendChild(indicatorEl)\n  return {\n    indicatorWrapper,\n  }\n}\n\ndescribe('Indicators unit tests', () => {\n  let scroll: BScroll\n\n  beforeEach(() => {\n    // BScroll DOM\n    const wrapper = document.createElement('div')\n    const content = document.createElement('div')\n    wrapper.appendChild(content)\n\n    scroll = new BScroll(wrapper, {})\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  describe('constructor', () => {\n    it('throw error when pass wrong options', () => {\n      expect(() => {\n        new Indicators(scroll)\n      }).toThrow()\n      expect(() => {\n        addProperties(scroll.options, {\n          indicators: [\n            {\n              relationElement: null,\n            },\n          ],\n        })\n        new Indicators(scroll)\n      }).toThrow()\n    })\n\n    it('should create indicator elements', () => {\n      const { indicatorWrapper } = createIndicatorElement()\n      addProperties(scroll.options, {\n        indicators: [\n          {\n            relationElement: indicatorWrapper,\n          },\n        ],\n      })\n      const indicatorsInstance = new Indicators(scroll)\n      expect(indicatorsInstance.indicators.length).toBe(1)\n    })\n\n    it('destroy hook', () => {\n      const { indicatorWrapper } = createIndicatorElement()\n      addProperties(scroll.options, {\n        indicators: [\n          {\n            relationElement: indicatorWrapper,\n          },\n        ],\n      })\n      const indicatorsInstance = new Indicators(scroll)\n      scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n      for (let indicator of indicatorsInstance.indicators) {\n        expect(indicator.destroy).toBeCalled()\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "packages/indicators/src/__tests__/indicator.spec.ts",
    "content": "import Indicator from '../indicator'\nimport BScroll from '@better-scroll/core'\nimport { IndicatorOptions } from '../types'\nimport {\n  dispatchTouchStart,\n  dispatchTouchMove,\n  dispatchTouchEnd,\n} from '@better-scroll/core/src/__tests__/__utils__/event'\n\njest.mock('@better-scroll/core')\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\nconst createIndicatorElement = () => {\n  // indicators DOM\n  const indicatorWrapper = document.createElement('div')\n  const indicatorEl = document.createElement('div')\n  indicatorWrapper.appendChild(indicatorEl)\n  return {\n    indicatorWrapper,\n  }\n}\n\ndescribe('Indicator unit tests', () => {\n  let scroll: BScroll\n  let indicatorOption: IndicatorOptions\n\n  beforeEach(() => {\n    // BScroll DOM\n    const wrapper = document.createElement('div')\n    const content = document.createElement('div')\n    wrapper.appendChild(content)\n\n    scroll = new BScroll(wrapper, {})\n\n    indicatorOption = {\n      relationElement: createIndicatorElement().indicatorWrapper,\n    }\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('refresh hook', () => {\n    const indicator = new Indicator(scroll, indicatorOption)\n    addProperties(scroll, {\n      hasVerticalScroll: true,\n      hasHorizontalScroll: true,\n      maxScrollX: -1,\n      maxScrollY: -1,\n    })\n    addProperties(indicatorOption, {\n      ratio: {\n        x: 1,\n        y: 1,\n      },\n    })\n    scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n    expect(indicator.currentPos).toMatchObject({\n      x: 0,\n      y: 0,\n    })\n    expect(indicator.indicatorEl.style.transform).toBe(\n      'translateX(0px) translateY(0px)  translateZ(0)'\n    )\n  })\n\n  it('translater hook', () => {\n    const indicator = new Indicator(scroll, indicatorOption)\n    addProperties(indicator, {\n      ratioY: 2,\n      translateYSign: 1,\n    })\n    const translaterHooks = scroll.scroller.translater.hooks\n    translaterHooks.trigger(translaterHooks.eventTypes.translate, {\n      x: 0,\n      y: -20,\n    })\n    expect(indicator.currentPos).toMatchObject({\n      x: 0,\n      y: -40,\n    })\n  })\n\n  it('animater hook', () => {\n    const indicator = new Indicator(scroll, indicatorOption)\n    const animaterHooks = scroll.scroller.animater.hooks\n    animaterHooks.trigger(\n      animaterHooks.eventTypes.timeFunction,\n      'cubic-bezier(0.25, 0.46, 0.45, 0.94)'\n    )\n    animaterHooks.trigger(animaterHooks.eventTypes.time, 200)\n    const style = indicator.indicatorEl.style as any\n    expect(style['transition-timing-function']).toBe(\n      'cubic-bezier(0.25, 0.46, 0.45, 0.94)'\n    )\n    expect(style['transition-duration']).toBe('200ms')\n  })\n\n  it('touch hooks', async () => {\n    addProperties(scroll.options, {\n      probeType: 3,\n      disableMouse: false,\n    })\n    addProperties(scroll, {\n      hasHorizontalScroll: true,\n    })\n    const indicator = new Indicator(scroll, indicatorOption)\n    const beforeStartMockFn = jest.fn()\n    const startMockFn = jest.fn()\n    const moveMockFn = jest.fn()\n    const endMockFn = jest.fn()\n    const scroller = scroll.scroller\n    scroller.hooks.on(\n      scroller.hooks.eventTypes.beforeScrollStart,\n      beforeStartMockFn\n    )\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollStart, startMockFn)\n    scroller.hooks.on(scroller.hooks.eventTypes.scroll, moveMockFn)\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollEnd, endMockFn)\n\n    // scroll is disabled\n    scroll.enabled = false\n    dispatchTouchStart(indicatorOption.relationElement.children[0], [\n      {\n        pageX: 0,\n        pageY: -20,\n      },\n    ])\n    expect(beforeStartMockFn).not.toBeCalled()\n\n    // scroll is enabled\n    scroll.enabled = true\n    dispatchTouchStart(indicatorOption.relationElement.children[0], [\n      {\n        pageX: 0,\n        pageY: -20,\n      },\n    ])\n    dispatchTouchMove(window, [\n      {\n        pageX: 0,\n        pageY: -40,\n      },\n    ])\n\n    dispatchTouchEnd(window, [])\n\n    expect(beforeStartMockFn).toBeCalled()\n    expect(startMockFn).toBeCalled()\n    expect(moveMockFn).toBeCalled()\n    expect(endMockFn).toBeCalled()\n\n    // dispatch scroll in interval time\n    addProperties(scroll.options, {\n      probeType: 1,\n    })\n    const moveMockFn2 = jest.fn()\n    scroller.hooks.on(scroller.hooks.eventTypes.scroll, moveMockFn2)\n    dispatchTouchStart(indicatorOption.relationElement.children[0], [\n      {\n        pageX: 0,\n        pageY: -20,\n      },\n    ])\n    await new Promise((resolve) => {\n      setTimeout(resolve, 400)\n    })\n    dispatchTouchMove(window, [\n      {\n        pageX: 0,\n        pageY: -40,\n      },\n    ])\n    expect(moveMockFn2).toBeCalled()\n  })\n\n  it('destroy', () => {\n    addProperties(indicatorOption, {\n      ratio: 1,\n    })\n    const indicator = new Indicator(scroll, indicatorOption)\n    scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n    indicator.destroy()\n    expect(indicator.hooksFn.length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "packages/indicators/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport Indicator from './indicator'\nimport { IndicatorOptions } from './types'\nimport { assert } from '@better-scroll/shared-utils'\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    indicators?: IndicatorOptions[]\n  }\n}\n\nexport default class Indicators {\n  static pluginName = 'indicators'\n  options: IndicatorOptions[] = []\n  indicators: Indicator[] = []\n  constructor(public scroll: BScroll) {\n    this.handleOptions()\n    this.handleHooks()\n  }\n\n  private handleOptions() {\n    const UserIndicatorsOptions = this.scroll.options.indicators!\n    assert(\n      Array.isArray(UserIndicatorsOptions),\n      `'indicators' must be an array.`\n    )\n    for (const indicatorOptions of UserIndicatorsOptions) {\n      assert(\n        !!indicatorOptions.relationElement,\n        `'relationElement' must be a HTMLElement.`\n      )\n      this.createIndicators(indicatorOptions)\n    }\n  }\n\n  private createIndicators(options: IndicatorOptions) {\n    this.indicators.push(new Indicator(this.scroll, options))\n  }\n\n  private handleHooks() {\n    const scrollHooks = this.scroll.hooks\n    scrollHooks.on(scrollHooks.eventTypes.destroy, () => {\n      for (const indicator of this.indicators) {\n        indicator.destroy()\n      }\n      this.indicators = []\n    })\n  }\n}\n"
  },
  {
    "path": "packages/indicators/src/indicator.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { IndicatorOptions, Ratio, Postion, ValueSign } from './types'\nimport {\n  EventRegister,\n  EventEmitter,\n  getRect,\n  getClientSize,\n  getNow,\n  between,\n  Probe,\n  TouchEvent,\n  style,\n  maybePrevent,\n} from '@better-scroll/shared-utils'\nconst resolveRatioOption = (ratioConfig?: Ratio) => {\n  let ret = {\n    ratioX: 0,\n    ratioY: 0,\n  }\n  /* istanbul ignore if  */\n  if (!ratioConfig) {\n    return ret\n  }\n  if (typeof ratioConfig === 'number') {\n    ret.ratioX = ret.ratioY = ratioConfig\n  } else if (typeof ratioConfig === 'object' && ratioConfig) {\n    ret.ratioX = ratioConfig.x || 0\n    ret.ratioY = ratioConfig.y || 0\n  }\n  return ret\n}\n\nconst handleBubbleAndCancelable = (e: TouchEvent) => {\n  maybePrevent(e)\n  e.stopPropagation()\n}\nexport default class Indicator {\n  wrapper: HTMLElement\n  indicatorEl: HTMLElement\n  maxScrollX: number\n  minScrollX: number\n  ratioX: number\n  maxScrollY: number\n  minScrollY: number\n  translateXSign: ValueSign\n  translateYSign: ValueSign\n  ratioY: number\n  currentPos: Postion = {\n    x: 0,\n    y: 0,\n  }\n  moved: boolean\n  startTime: number\n  initiated: boolean\n  lastPointX: number\n  lastPointY: number\n  startEventRegister: EventRegister\n  moveEventRegister: EventRegister\n  endEventRegister: EventRegister\n  hooksFn: [EventEmitter, string, Function][] = []\n  constructor(public scroll: BScroll, public options: IndicatorOptions) {\n    this.handleDOM()\n    this.handleHooks()\n    this.handleInteractive()\n  }\n\n  private handleDOM() {\n    const { relationElement, relationElementHandleElementIndex = 0 } =\n      this.options\n    this.wrapper = relationElement\n    this.indicatorEl = this.wrapper.children[\n      relationElementHandleElementIndex\n    ] as HTMLElement\n  }\n\n  private handleHooks() {\n    const scroll = this.scroll\n    const scrollHooks = scroll.hooks\n    const translaterHooks = scroll.scroller.translater.hooks\n    const animaterHooks = scroll.scroller.animater.hooks\n\n    this.registerHooks(\n      scrollHooks,\n      scrollHooks.eventTypes.refresh,\n      this.refresh\n    )\n\n    this.registerHooks(\n      translaterHooks,\n      translaterHooks.eventTypes.translate,\n      (pos: Postion) => {\n        this.updatePosition(pos)\n      }\n    )\n\n    this.registerHooks(\n      animaterHooks,\n      animaterHooks.eventTypes.time,\n      this.transitionTime\n    )\n\n    this.registerHooks(\n      animaterHooks,\n      animaterHooks.eventTypes.timeFunction,\n      this.transitionTimingFunction\n    )\n  }\n\n  private transitionTime(time: number = 0) {\n    this.indicatorEl.style[style.transitionDuration as any] = time + 'ms'\n  }\n\n  private transitionTimingFunction(easing: string) {\n    this.indicatorEl.style[style.transitionTimingFunction as any] = easing\n  }\n\n  private handleInteractive() {\n    if (this.options.interactive !== false) {\n      this.registerEvents()\n    }\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private registerEvents() {\n    const { disableMouse, disableTouch } = this.scroll.options\n    const startEvents = []\n    const moveEvents = []\n    const endEvents = []\n\n    if (!disableMouse) {\n      startEvents.push({\n        name: 'mousedown',\n        handler: this.start.bind(this),\n      })\n\n      moveEvents.push({\n        name: 'mousemove',\n        handler: this.move.bind(this),\n      })\n\n      endEvents.push({\n        name: 'mouseup',\n        handler: this.end.bind(this),\n      })\n    }\n\n    if (!disableTouch) {\n      startEvents.push({\n        name: 'touchstart',\n        handler: this.start.bind(this),\n      })\n\n      moveEvents.push({\n        name: 'touchmove',\n        handler: this.move.bind(this),\n      })\n\n      endEvents.push(\n        {\n          name: 'touchend',\n          handler: this.end.bind(this),\n        },\n        {\n          name: 'touchcancel',\n          handler: this.end.bind(this),\n        }\n      )\n    }\n\n    this.startEventRegister = new EventRegister(this.indicatorEl, startEvents)\n    this.moveEventRegister = new EventRegister(window, moveEvents)\n    this.endEventRegister = new EventRegister(window, endEvents)\n  }\n\n  refresh() {\n    const {\n      x,\n      y,\n      hasHorizontalScroll,\n      hasVerticalScroll,\n      maxScrollX: maxBScrollX,\n      maxScrollY: maxBScrollY,\n    } = this.scroll\n    const { ratioX, ratioY } = resolveRatioOption(this.options.ratio)\n    const { width: wrapperWidth, height: wrapperHeight } = getClientSize(\n      this.wrapper\n    )\n    const { width: indicatorWidth, height: indicatorHeight } = getRect(\n      this.indicatorEl\n    )\n    if (hasHorizontalScroll) {\n      this.maxScrollX = wrapperWidth - indicatorWidth\n      this.translateXSign =\n        this.maxScrollX > 0 ? ValueSign.Positive : ValueSign.NotPositive\n      this.minScrollX = 0\n      // ensure positive\n      this.ratioX = ratioX ? ratioX : Math.abs(this.maxScrollX / maxBScrollX)\n    }\n\n    if (hasVerticalScroll) {\n      this.maxScrollY = wrapperHeight - indicatorHeight\n      this.translateYSign =\n        this.maxScrollY > 0 ? ValueSign.Positive : ValueSign.NotPositive\n      this.minScrollY = 0\n      this.ratioY = ratioY ? ratioY : Math.abs(this.maxScrollY / maxBScrollY)\n    }\n\n    this.updatePosition({\n      x,\n      y,\n    })\n  }\n\n  private start(e: TouchEvent) {\n    if (this.BScrollIsDisabled()) {\n      return\n    }\n    let point = (e.touches ? e.touches[0] : e) as Touch\n\n    handleBubbleAndCancelable(e)\n\n    this.initiated = true\n    this.moved = false\n    this.lastPointX = point.pageX\n    this.lastPointY = point.pageY\n    this.startTime = getNow()\n    this.scroll.scroller.hooks.trigger(\n      this.scroll.scroller.hooks.eventTypes.beforeScrollStart\n    )\n  }\n\n  private BScrollIsDisabled() {\n    return !this.scroll.enabled\n  }\n\n  private move(e: TouchEvent) {\n    if (!this.initiated) {\n      return\n    }\n    let point = (e.touches ? e.touches[0] : e) as Touch\n    const pointX = point.pageX\n    const pointY = point.pageY\n\n    handleBubbleAndCancelable(e)\n\n    let deltaX = pointX - this.lastPointX\n    let deltaY = pointY - this.lastPointY\n    this.lastPointX = pointX\n    this.lastPointY = pointY\n    if (!this.moved && !this.indicatorNotMoved(deltaX, deltaY)) {\n      this.moved = true\n      this.scroll.scroller.hooks.trigger(\n        this.scroll.scroller.hooks.eventTypes.scrollStart\n      )\n    }\n\n    if (this.moved) {\n      const newPos = this.getBScrollPosByRatio(this.currentPos, deltaX, deltaY)\n      this.syncBScroll(newPos)\n    }\n  }\n\n  private end(e: TouchEvent) {\n    if (!this.initiated) {\n      return\n    }\n    this.initiated = false\n\n    handleBubbleAndCancelable(e)\n\n    if (this.moved) {\n      const { x, y } = this.scroll\n      this.scroll.scroller.hooks.trigger(\n        this.scroll.scroller.hooks.eventTypes.scrollEnd,\n        {\n          x,\n          y,\n        }\n      )\n    }\n  }\n\n  private getBScrollPosByRatio(\n    currentPos: Postion,\n    deltaX: number,\n    deltaY: number\n  ) {\n    const { x: currentX, y: currentY } = currentPos\n\n    const {\n      hasHorizontalScroll,\n      hasVerticalScroll,\n      minScrollX: BScrollMinScrollX,\n      maxScrollX: BScrollMaxScrollX,\n      minScrollY: BScrollMinScrollY,\n      maxScrollY: BScrollMaxScrollY,\n    } = this.scroll\n\n    let { x, y } = this.scroll\n\n    if (hasHorizontalScroll) {\n      const newPosX = between(\n        currentX + deltaX,\n        Math.min(this.minScrollX, this.maxScrollX),\n        Math.max(this.minScrollX, this.maxScrollX)\n      )\n      const roundX = Math.round((newPosX / this.ratioX) * this.translateXSign)\n      x = between(roundX, BScrollMaxScrollX, BScrollMinScrollX)\n    }\n\n    if (hasVerticalScroll) {\n      const newPosY = between(\n        currentY + deltaY,\n        Math.min(this.minScrollY, this.maxScrollY),\n        Math.max(this.minScrollY, this.maxScrollY)\n      )\n      const roundY = Math.round((newPosY / this.ratioY) * this.translateYSign)\n      y = between(roundY, BScrollMaxScrollY, BScrollMinScrollY)\n    }\n    return { x, y }\n  }\n\n  private indicatorNotMoved(deltaX: number, deltaY: number): boolean {\n    const { x, y } = this.currentPos\n    const xNotMoved =\n      (x === this.minScrollX && deltaX <= 0) ||\n      (x === this.maxScrollX && deltaX >= 0)\n    const yNotMoved =\n      (y === this.minScrollY && deltaY <= 0) ||\n      (y === this.maxScrollY && deltaY >= 0)\n    return xNotMoved && yNotMoved\n  }\n\n  private syncBScroll(newPos: Postion) {\n    const timestamp = getNow()\n    const { options, scroller } = this.scroll\n    const { probeType, momentumLimitTime } = options\n    scroller.translater.translate(newPos)\n\n    // dispatch scroll in interval time\n    if (timestamp - this.startTime > momentumLimitTime) {\n      this.startTime = timestamp\n      if (probeType === Probe.Throttle) {\n        scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, newPos)\n      }\n    }\n\n    // dispatch scroll all the time\n    if (probeType > Probe.Throttle) {\n      scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, newPos)\n    }\n  }\n\n  updatePosition(BScrollPos: Postion) {\n    const newIndicatorPos = this.getIndicatorPosByRatio(BScrollPos)\n    this.applyTransformProperty(newIndicatorPos)\n    this.currentPos = { ...newIndicatorPos }\n  }\n\n  private applyTransformProperty(pos: Postion) {\n    const translateZ = this.scroll.options.translateZ\n    const transformProperties = [\n      `translateX(${pos.x}px)`,\n      `translateY(${pos.y}px)`,\n      `${translateZ}`,\n    ]\n    this.indicatorEl.style[style.transform as any] =\n      transformProperties.join(' ')\n  }\n\n  private getIndicatorPosByRatio(BScrollPos: Postion) {\n    const { x, y } = BScrollPos\n    const { hasHorizontalScroll, hasVerticalScroll } = this.scroll\n    const position = { ...this.currentPos }\n    if (hasHorizontalScroll) {\n      const roundX = Math.round(this.ratioX * x * this.translateXSign)\n      // maybe maxScrollX is negative\n      position.x = between(\n        roundX,\n        Math.min(this.minScrollX, this.maxScrollX),\n        Math.max(this.minScrollX, this.maxScrollX)\n      )\n    }\n\n    if (hasVerticalScroll) {\n      const roundY = Math.round(this.ratioY * y * this.translateYSign)\n      // maybe maxScrollY is negative\n      position.y = between(\n        roundY,\n        Math.min(this.minScrollY, this.maxScrollY),\n        Math.max(this.minScrollY, this.maxScrollY)\n      )\n    }\n\n    return position\n  }\n\n  destroy() {\n    if (this.options.interactive !== false) {\n      this.startEventRegister.destroy()\n      this.moveEventRegister.destroy()\n      this.endEventRegister.destroy()\n    }\n\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n    this.hooksFn.length = 0\n  }\n}\n"
  },
  {
    "path": "packages/indicators/src/types.ts",
    "content": "export type Ratio = number | RatioOfDirection\nexport type RatioOfDirection = {\n  x: number\n  y: number\n}\nexport interface IndicatorOptions {\n  interactive?: boolean\n  ratio?: Ratio\n  relationElementHandleElementIndex?: number\n  relationElement: HTMLElement\n}\n\nexport const enum Direction {\n  Vertical = 'vertical',\n  Horizontal = 'horizontal',\n}\n\nexport type Postion = {\n  x: number\n  y: number\n}\n\nexport const enum ValueSign {\n  Positive = -1,\n  NotPositive = 1,\n}\n"
  },
  {
    "path": "packages/infinity/README.md",
    "content": "# @better-scroll/infinity\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/infinity/README_zh-CN.md)\n\nThe ability to inject a infinity load for BetterScroll.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport InfinityScroll from '@better-scroll/infinity'\nBScroll.use(InfinityScroll)\n\nconst bs = new BScroll('.wrapper', {\n  infinity: {\n    fetch(count) {\n      // Fetch data that is larger than count, the function is asynchronous, and it needs to return a Promise.。\n      // After you have successfully fetch the data, you need resolve an array of data (or resolve Promise).\n      // Each element of the array is list data, which will be rendered when the render method executes。\n      // If there is no data, you can resolve (false) to tell the infinite scroll list that there is no more data。\n    }\n    render(item, div) {\n      // Rendering each element node, item is data, and div is a container for wrapping element nodes.\n      // The function needs to return to the rendered DOM node.\n    },\n    createTombstone() {\n      // Returns a tombstone DOM node.。\n    }\n  }\n})\n```\n"
  },
  {
    "path": "packages/infinity/README_zh-CN.md",
    "content": "# @better-scroll/infinity\n\n为 BetterScroll 注入上拉加载的能力。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport InfinityScroll from '@better-scroll/infinity'\nBScroll.use(InfinityScroll)\n\nconst bs = new BScroll('.wrapper', {\n  infinity: {\n    fetch(count) {\n      // 获取大于 count 数量的数据，该函数是异步的，它需要返回一个 Promise。\n      // 成功获取数据后，你需要 resolve 数据数组（也可以 resolve 一个 Promise）。\n      // 数组的每一个元素是列表数据，在 render 方法执行的时候会传递这个数据渲染。\n      // 如果没有数据的时候，你可以 resolve(false)，来告诉无限滚动列表已经没有更多数据了。\n    }\n    render(item, div) {\n      // 渲染每一个元素节点，item 是数据，div 是包裹元素节点的容器。\n      // 该函数需要返回渲染后的 DOM 节点。\n    },\n    createTombstone() {\n      // 返回一个墓碑 DOM 节点。\n    }\n  }\n})\n```\n"
  },
  {
    "path": "packages/infinity/package.json",
    "content": "{\n  \"name\": \"@better-scroll/infinity\",\n  \"version\": \"2.5.1\",\n  \"description\": \"The ability to inject a infinity load for BetterScroll.\",\n  \"author\": \"fengweiyao <fengweiyao0317@hotmail.com>\",\n  \"main\": \"dist/infinity.min.js\",\n  \"module\": \"dist/infinity.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"infinity\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/infinity\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/infinity/src/DataManager.ts",
    "content": "class ListItem {\n  data: any | null\n  dom: HTMLElement | null\n  tombstone: HTMLElement | null\n  width: number\n  height: number\n  pos: number\n\n  constructor() {\n    this.data = null\n    this.dom = null\n    this.tombstone = null\n    this.width = 0\n    this.height = 0\n    this.pos = 0\n  }\n}\n\nexport type pListItem = Partial<ListItem>\n\nexport default class DataManager {\n  public loadedNum = 0\n  private fetching = false\n  private hasMore = true\n  private list: Array<pListItem>\n  constructor(\n    list: Array<pListItem>,\n    private fetchFn: (len: number) => Promise<Array<any> | boolean>,\n    private onFetchFinish: (list: Array<pListItem>, hasMore: boolean) => number\n  ) {\n    this.list = list || []\n  }\n\n  async update(end: number): Promise<void> {\n    if (!this.hasMore) {\n      end = Math.min(end, this.list.length)\n    }\n\n    // add data placeholder\n    if (end > this.list.length) {\n      const len = end - this.list.length\n      this.addEmptyData(len)\n    }\n\n    // tslint:disable-next-line: no-floating-promises\n    return this.checkToFetch(end)\n  }\n\n  add(data: Array<any>): Array<pListItem> {\n    for (let i = 0; i < data.length; i++) {\n      if (!this.list[this.loadedNum]) {\n        this.list[this.loadedNum] = { data: data[i] }\n      } else {\n        this.list[this.loadedNum] = {\n          ...this.list[this.loadedNum],\n          ...{ data: data[i] },\n        }\n      }\n      this.loadedNum++\n    }\n    return this.list\n  }\n\n  addEmptyData(len: number): Array<pListItem> {\n    for (let i = 0; i < len; i++) {\n      this.list.push(new ListItem())\n    }\n    return this.list\n  }\n\n  async fetch(len: number): Promise<Array<any> | boolean> {\n    if (this.fetching) {\n      return []\n    }\n    this.fetching = true\n    const data = await this.fetchFn(len)\n    this.fetching = false\n    return data\n  }\n\n  async checkToFetch(end: number): Promise<void> {\n    if (!this.hasMore) {\n      return\n    }\n\n    if (end <= this.loadedNum) {\n      return\n    }\n\n    const min = end - this.loadedNum\n    const newData = await this.fetch(min)\n    if (newData instanceof Array && newData.length) {\n      this.add(newData)\n\n      const currentEnd = this.onFetchFinish(this.list, true)\n\n      return this.checkToFetch(currentEnd)\n    } else if (typeof newData === 'boolean' && newData === false) {\n      this.hasMore = false\n      this.list.splice(this.loadedNum)\n\n      this.onFetchFinish(this.list, false)\n    }\n  }\n\n  getList() {\n    return this.list\n  }\n\n  resetState() {\n    this.loadedNum = 0\n    this.fetching = false\n    this.hasMore = true\n    this.list = []\n  }\n}\n"
  },
  {
    "path": "packages/infinity/src/DomManager.ts",
    "content": "import { pListItem } from './DataManager'\nimport Tombstone from './Tombstone'\nimport { style, cssVendor } from '@better-scroll/shared-utils'\n\nconst ANIMATION_DURATION_MS = 200\n\nexport default class DomManager {\n  private content: HTMLElement\n  private unusedDom: HTMLElement[] = []\n  private timers: Array<number> = []\n\n  constructor(\n    content: HTMLElement,\n    private renderFn: (data: any, div?: HTMLElement) => HTMLElement,\n    private tombstone: Tombstone\n  ) {\n    this.setContent(content)\n  }\n\n  update(\n    list: Array<pListItem>,\n    start: number,\n    end: number\n  ): {\n    start: number\n    end: number\n    startPos: number\n    startDelta: number\n    endPos: number\n  } {\n    if (start >= list.length) {\n      start = list.length - 1\n    }\n\n    if (end > list.length) {\n      end = list.length\n    }\n\n    this.collectUnusedDom(list, start, end)\n    this.createDom(list, start, end)\n    this.cacheHeight(list, start, end)\n\n    const { startPos, startDelta, endPos } = this.positionDom(list, start, end)\n\n    return {\n      start,\n      startPos,\n      startDelta,\n      end,\n      endPos,\n    }\n  }\n\n  private collectUnusedDom(\n    list: Array<pListItem>,\n    start: number,\n    end: number\n  ): Array<any> {\n    // TODO optimise\n    for (let i = 0; i < list.length; i++) {\n      if (i === start) {\n        i = end - 1\n        continue\n      }\n\n      if (list[i].dom) {\n        const dom = list[i].dom as HTMLElement\n        if (Tombstone.isTombstone(dom)) {\n          this.tombstone.recycleOne(dom)\n          dom.style.display = 'none'\n        } else {\n          this.unusedDom.push(dom)\n        }\n        list[i].dom = null\n      }\n    }\n\n    return list\n  }\n\n  private createDom(list: Array<pListItem>, start: number, end: number): void {\n    for (let i = start; i < end; i++) {\n      let dom = list[i].dom\n      const data = list[i].data\n      if (dom) {\n        if (Tombstone.isTombstone(dom) && data) {\n          list[i].tombstone = dom\n          list[i].dom = null\n        } else {\n          continue\n        }\n      }\n      dom = data\n        ? this.renderFn(data, this.unusedDom.pop())\n        : this.tombstone.getOne()\n      dom.style.position = 'absolute'\n      list[i].dom = dom\n      list[i].pos = -1\n      this.content.appendChild(dom)\n    }\n  }\n\n  private cacheHeight(\n    list: Array<pListItem>,\n    start: number,\n    end: number\n  ): void {\n    for (let i = start; i < end; i++) {\n      if (list[i].data && !list[i].height) {\n        list[i].height = list[i].dom!.offsetHeight\n      }\n    }\n  }\n\n  private positionDom(\n    list: Array<pListItem>,\n    start: number,\n    end: number\n  ): { startPos: number; startDelta: number; endPos: number } {\n    const tombstoneEles: Array<HTMLElement> = []\n    const { start: startPos, delta: startDelta } = this.getStartPos(\n      list,\n      start,\n      end\n    )\n    let pos = startPos\n\n    for (let i = start; i < end; i++) {\n      const tombstone = list[i].tombstone\n      if (tombstone) {\n        const tombstoneStyle = tombstone.style as any\n        tombstoneStyle[\n          style.transition\n        ] = `${cssVendor}transform ${ANIMATION_DURATION_MS}ms, opacity ${ANIMATION_DURATION_MS}ms`\n        tombstoneStyle[style.transform] = `translateY(${pos}px)`\n        tombstoneStyle.opacity = '0'\n\n        list[i].tombstone = null\n        tombstoneEles.push(tombstone)\n      }\n\n      if (list[i].dom && list[i].pos !== pos) {\n        list[i].dom!.style[style.transform as any] = `translateY(${pos}px)`\n        list[i].pos = pos\n      }\n      pos += list[i].height || this.tombstone.height\n    }\n\n    const timerId = window.setTimeout(() => {\n      this.tombstone.recycle(tombstoneEles)\n    }, ANIMATION_DURATION_MS)\n    this.timers.push(timerId)\n\n    return {\n      startPos,\n      startDelta,\n      endPos: pos,\n    }\n  }\n\n  private getStartPos(\n    list: Array<any>,\n    start: number,\n    end: number\n  ): { start: number; delta: number } {\n    if (list[start] && list[start].pos !== -1) {\n      return {\n        start: list[start].pos,\n        delta: 0,\n      }\n    }\n    // TODO optimise\n    let pos = list[0].pos === -1 ? 0 : list[0].pos\n    for (let i = 0; i < start; i++) {\n      pos += list[i].height || this.tombstone.height\n    }\n    let originPos = pos\n\n    let i\n    for (i = start; i < end; i++) {\n      if (!Tombstone.isTombstone(list[i].dom) && list[i].pos !== -1) {\n        pos = list[i].pos\n        break\n      }\n    }\n    let x = i\n    if (x < end) {\n      while (x > start) {\n        pos -= list[x - 1].height\n        x--\n      }\n    }\n    const delta = originPos - pos\n\n    return {\n      start: pos,\n      delta: delta,\n    }\n  }\n\n  removeTombstone(): void {\n    const tombstones = this.content.querySelectorAll('.tombstone')\n    for (let i = tombstones.length - 1; i >= 0; i--) {\n      this.content.removeChild(tombstones[i])\n    }\n  }\n\n  setContent(content: HTMLElement) {\n    if (content !== this.content) {\n      this.content = content\n    }\n  }\n\n  destroy(): void {\n    this.removeTombstone()\n    this.timers.forEach((id) => {\n      clearTimeout(id)\n    })\n  }\n  resetState() {\n    this.destroy()\n    this.timers = []\n    this.unusedDom = []\n  }\n}\n"
  },
  {
    "path": "packages/infinity/src/IndexCalculator.ts",
    "content": "export const PRE_NUM = 10\nexport const POST_NUM = 30\n\nconst enum DIRECTION {\n  UP,\n  DOWN,\n}\n\nexport default class IndexCalculator {\n  private lastDirection = DIRECTION.DOWN\n  private lastPos = 0\n\n  constructor(public wrapperHeight: number, private tombstoneHeight: number) {}\n\n  calculate(pos: number, list: Array<any>): { start: number; end: number } {\n    let offset = pos - this.lastPos\n    this.lastPos = pos\n\n    const direction = this.getDirection(offset)\n\n    // important! start index is much more important than end index.\n    let start = this.calculateIndex(0, pos, list)\n\n    let end = this.calculateIndex(start, pos + this.wrapperHeight, list)\n\n    if (direction === DIRECTION.DOWN) {\n      start -= PRE_NUM\n      end += POST_NUM\n    } else {\n      start -= POST_NUM\n      end += PRE_NUM\n    }\n\n    if (start < 0) {\n      start = 0\n    }\n\n    return {\n      start,\n      end,\n    }\n  }\n\n  private getDirection(offset: number): DIRECTION {\n    let direction\n    if (offset > 0) {\n      direction = DIRECTION.DOWN\n    } else if (offset < 0) {\n      direction = DIRECTION.UP\n    } else {\n      return this.lastDirection\n    }\n    this.lastDirection = direction\n    return direction\n  }\n\n  private calculateIndex(\n    start: number,\n    offset: number,\n    list: Array<any>\n  ): number {\n    if (offset <= 0) {\n      return start\n    }\n\n    let i = start\n    let startPos = list[i] && list[i].pos !== -1 ? list[i].pos : 0\n    let lastPos = startPos\n    let tombstone = 0\n    while (i < list.length && list[i].pos < offset) {\n      lastPos = list[i].pos\n      i++\n    }\n\n    if (i === list.length) {\n      tombstone = Math.floor((offset - lastPos) / this.tombstoneHeight)\n    }\n\n    i += tombstone\n\n    return i\n  }\n\n  resetState() {\n    this.lastDirection = DIRECTION.DOWN\n    this.lastPos = 0\n  }\n}\n"
  },
  {
    "path": "packages/infinity/src/Tombstone.ts",
    "content": "import { style } from '@better-scroll/shared-utils'\n\nexport default class Tombstone {\n  private cached: Array<HTMLElement> = []\n  public width = 0\n  public height = 0\n\n  private initialed = false\n\n  constructor(private create: () => HTMLElement) {\n    this.getSize()\n  }\n\n  static isTombstone(el: HTMLElement): boolean {\n    if (el && el.classList) {\n      return el.classList.contains('tombstone')\n    }\n    return false\n  }\n\n  private getSize(): void {\n    if (!this.initialed) {\n      let tombstone = this.create()\n      tombstone.style.position = 'absolute'\n      document.body.appendChild(tombstone)\n\n      tombstone.style.display = ''\n      this.height = tombstone.offsetHeight\n      this.width = tombstone.offsetWidth\n\n      document.body.removeChild(tombstone)\n\n      this.cached.push(tombstone)\n    }\n  }\n\n  getOne(): HTMLElement {\n    let tombstone = this.cached.pop()\n    if (tombstone) {\n      const tombstoneStyle = tombstone.style as any\n\n      tombstoneStyle.display = ''\n      tombstoneStyle.opacity = '1'\n      tombstoneStyle[style.transform] = ''\n      tombstoneStyle[style.transition] = ''\n      return tombstone\n    }\n    return this.create()\n  }\n\n  recycle(tombstones: Array<HTMLElement>): Array<HTMLElement> {\n    for (let tombstone of tombstones) {\n      tombstone.style.display = 'none'\n      this.cached.push(tombstone)\n    }\n    return this.cached\n  }\n\n  recycleOne(tombstone: HTMLElement) {\n    this.cached.push(tombstone)\n\n    return this.cached\n  }\n}\n"
  },
  {
    "path": "packages/infinity/src/__tests__/DataManager.spec.ts",
    "content": "import DataManager from '../DataManager'\n\ndescribe('DataManager unit test', () => {\n  const NEW_LEN = 10\n  let list: Array<any>\n  let dataManager: DataManager\n\n  let fetchFn = jest.fn()\n  let onFetchFinish = jest.fn().mockReturnValue(0)\n\n  beforeEach(() => {\n    list = []\n\n    dataManager = new DataManager(list, fetchFn, onFetchFinish)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should fetch new data when loadedNum < list.length', () => {\n    // given\n    let end = NEW_LEN\n    const DATA = 1\n    const newData = new Array(NEW_LEN).fill(DATA)\n    fetchFn.mockReturnValue(Promise.resolve(newData))\n    // when\n    // tslint:disable-next-line: no-floating-promises\n    dataManager.update(end)\n    // then\n    // create empty data firstly, and then, go to fetch data\n    expect(dataManager.getList().length).not.toBeLessThan(NEW_LEN)\n    expect(fetchFn).toBeCalledWith(NEW_LEN)\n  })\n\n  it('should call onFetchFinish with hasMore equal true when loaded new data', async () => {\n    // given\n    const start = 0\n    const end = NEW_LEN\n    const DATA = 1\n    const newData = new Array(NEW_LEN).fill(DATA)\n    fetchFn.mockReturnValue(Promise.resolve(newData))\n    // when\n    await dataManager.update(end)\n    // then\n    expect(onFetchFinish).toBeCalledWith(dataManager.getList(), true)\n  })\n\n  it('should call onFetchFinish with hasMore equal false when no more data', async () => {\n    // given\n    const start = 0\n    const end = NEW_LEN\n    fetchFn.mockReturnValue(Promise.resolve(false))\n    // when\n    await dataManager.update(end)\n    // then\n    expect(onFetchFinish).toBeCalledWith(dataManager.getList(), false)\n  })\n})\n"
  },
  {
    "path": "packages/infinity/src/__tests__/IndexCalculator.spec.ts",
    "content": "import IndexCalculator, { PRE_NUM, POST_NUM } from '../IndexCalculator'\n\nimport FakeList from './__utils__/FakeList'\n\nimport { WRAPPER_HEIGHT, TOMBSTONE_HEIGHT } from './__utils__/constans'\n\ndescribe('IndexCalculator unit test', () => {\n  let indexCalculator: IndexCalculator\n  const VISIBLE_CNT = WRAPPER_HEIGHT / TOMBSTONE_HEIGHT\n\n  beforeEach(() => {\n    indexCalculator = new IndexCalculator(WRAPPER_HEIGHT, TOMBSTONE_HEIGHT)\n  })\n\n  it('should get start equal to 0 when pos is 0', () => {\n    // given\n    const list = new FakeList(0).getList()\n    // when\n    const { start, end } = indexCalculator.calculate(0, list)\n    // then\n    expect(start).toBe(0)\n    expect(end).toBe(VISIBLE_CNT + POST_NUM)\n  })\n\n  it('should get start equal to 0 when first rendered index < PRE_NUM', () => {\n    // given\n    const list = new FakeList(100)\n      .fillDom(0)\n      .fillPos()\n      .getList()\n    const INDEX = 5\n    const POSITION = INDEX * TOMBSTONE_HEIGHT\n    // when\n    const { start, end } = indexCalculator.calculate(POSITION, list)\n    // then\n    expect(start).toBe(0)\n  })\n\n  it('should get start equal to index-PRE_NUM when first visilbe index > PRE_NUM', () => {\n    // given\n    const list = new FakeList(100)\n      .fillDom(0)\n      .fillPos()\n      .getList()\n    const INDEX = 15\n    const POSITION = INDEX * TOMBSTONE_HEIGHT\n    // when\n    const { start } = indexCalculator.calculate(POSITION, list)\n    // then\n    expect(start).toBe(INDEX - PRE_NUM)\n  })\n\n  it('should get correct val when scroll up and down', () => {\n    // given\n    const list = new FakeList(100)\n      .fillDom(0)\n      .fillPos()\n      .getList()\n    const FIRST_INDEX = 50\n    const FIRST_POSITION = FIRST_INDEX * TOMBSTONE_HEIGHT\n    const SECOND_INDEX = 40\n    const SECOND_POSITION = SECOND_INDEX * TOMBSTONE_HEIGHT\n    // when\n    indexCalculator.calculate(FIRST_POSITION, list)\n    const { start } = indexCalculator.calculate(SECOND_POSITION, list)\n    // then\n    expect(start).toBe(SECOND_INDEX - POST_NUM)\n  })\n})\n"
  },
  {
    "path": "packages/infinity/src/__tests__/__utils__/FakeList.ts",
    "content": "import { mockDomOffset } from '@better-scroll/core/src/__tests__/__utils__/layout'\nimport { TOMBSTONE_HEIGHT } from './constans'\n\nexport default class FakeList {\n  private list: any[]\n  constructor(size: number) {\n    this.list = Array.from({ length: size }).map(i => {\n      return {}\n    })\n  }\n\n  fill(val: any, start: number = 0, end?: number): this {\n    if (!end) {\n      end = this.list.length\n    }\n\n    for (let i = start; i < end; i++) {\n      Object.assign(this.list[i], val)\n    }\n\n    return this\n  }\n\n  fillPos(end?: number): this {\n    if (!end) {\n      end = this.list.length\n    }\n\n    let startPos = 0\n\n    for (let i = 0; i < end; i++) {\n      Object.assign(this.list[i], { pos: startPos })\n      const height = this.list[i].height\n      startPos += height\n    }\n\n    return this\n  }\n\n  fillDom(start: number, end?: number, height = TOMBSTONE_HEIGHT): this {\n    if (!end) {\n      end = this.list.length\n    }\n\n    for (let i = start; i < end; i++) {\n      const dom = document.createElement('div')\n      mockDomOffset(dom, { height })\n      Object.assign(this.list[i], { dom, height, pos: -1 })\n    }\n\n    return this\n  }\n\n  syncDomTo(content: HTMLElement): this {\n    for (let i = 0; i < this.list.length; i++) {\n      if (this.list[i].dom) {\n        content.appendChild(this.list[i].dom)\n      }\n    }\n    return this\n  }\n\n  getList(): any[] {\n    return this.list\n  }\n}\n"
  },
  {
    "path": "packages/infinity/src/__tests__/__utils__/constans.ts",
    "content": "const TOMBSTONE_HEIGHT = 37 // 元素默认的高度\n\nconst WRAPPER_HEIGHT = 370 // 滚动窗口的高度\n\nconst VISIBLE_CNT = 10 // 滚动窗口内能显示的元素\n\nexport { TOMBSTONE_HEIGHT, WRAPPER_HEIGHT, VISIBLE_CNT }\n"
  },
  {
    "path": "packages/infinity/src/index.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\nimport { Probe, warn } from '@better-scroll/shared-utils'\nimport IndexCalculator from './IndexCalculator'\nimport DataManager from './DataManager'\nimport DomManager from './DomManager'\nimport Tombstone from './Tombstone'\n\nexport interface InfinityOptions {\n  fetch: (count: number) => Promise<Array<any> | false>\n  render: (item: any, div?: HTMLElement) => HTMLElement\n  createTombstone: () => HTMLElement\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    infinity?: InfinityOptions\n  }\n}\n\nconst EXTRA_SCROLL_Y = -2000\n\nexport default class InfinityScroll {\n  static pluginName = 'infinity'\n  start: number = 0\n  end: number = 0\n  options: InfinityOptions\n  private tombstone: Tombstone\n  private domManager: DomManager\n  private dataManager: DataManager\n  private indexCalculator: IndexCalculator\n\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  init() {\n    this.handleOptions()\n\n    const {\n      fetch: fetchFn,\n      render: renderFn,\n      createTombstone: createTombstoneFn,\n    } = this.options\n\n    this.tombstone = new Tombstone(createTombstoneFn)\n    this.indexCalculator = new IndexCalculator(\n      this.scroll.scroller.scrollBehaviorY.wrapperSize,\n      this.tombstone.height\n    )\n    this.domManager = new DomManager(\n      this.scroll.scroller.content,\n      renderFn,\n      this.tombstone\n    )\n    this.dataManager = new DataManager(\n      [],\n      fetchFn,\n      this.onFetchFinish.bind(this)\n    )\n\n    this.scroll.on(this.scroll.eventTypes.destroy, this.destroy, this)\n    this.scroll.on(this.scroll.eventTypes.scroll, this.update, this)\n\n    this.scroll.on(\n      this.scroll.eventTypes.contentChanged,\n      (content: HTMLElement) => {\n        this.domManager.setContent(content)\n        this.indexCalculator.resetState()\n        this.domManager.resetState()\n        this.dataManager.resetState()\n        this.update({ y: 0 })\n      }\n    )\n    const { scrollBehaviorY } = this.scroll.scroller\n\n    scrollBehaviorY.hooks.on(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      this.modifyBoundary,\n      this\n    )\n\n    this.update({ y: 0 })\n  }\n\n  private modifyBoundary(boundary: Boundary) {\n    // manually set position to allow scroll\n    boundary.maxScrollPos = EXTRA_SCROLL_Y\n  }\n\n  private handleOptions() {\n    // narrow down type to an object\n    const infinityOptions = this.scroll.options.infinity\n    if (infinityOptions) {\n      if (typeof infinityOptions.fetch !== 'function') {\n        warn('Infinity plugin need fetch Function to new data.')\n      }\n      if (typeof infinityOptions.render !== 'function') {\n        warn('Infinity plugin need render Function to render each item.')\n      }\n      if (typeof infinityOptions.render !== 'function') {\n        warn(\n          'Infinity plugin need createTombstone Function to create tombstone.'\n        )\n      }\n      this.options = infinityOptions\n    }\n\n    this.scroll.options.probeType = Probe.Realtime\n  }\n\n  update(pos: { y: number }): void {\n    const position = Math.round(-pos.y)\n    // important! calculate start/end index to render\n    const { start, end } = this.indexCalculator.calculate(\n      position,\n      this.dataManager.getList()\n    )\n    this.start = start\n    this.end = end\n    // tslint:disable-next-line: no-floating-promises\n    this.dataManager.update(end)\n    this.updateDom(this.dataManager.getList())\n  }\n\n  private onFetchFinish(list: Array<any>, hasMore: boolean) {\n    const { end } = this.updateDom(list)\n    if (!hasMore) {\n      this.domManager.removeTombstone()\n      this.scroll.scroller.animater.stop()\n      this.scroll.resetPosition()\n    }\n    // tslint:disable-next-line: no-floating-promises\n    return end\n  }\n\n  private updateDom(\n    list: Array<any>\n  ): { end: number; startPos: number; endPos: number } {\n    const { end, startPos, endPos, startDelta } = this.domManager.update(\n      list,\n      this.start,\n      this.end\n    )\n\n    if (startDelta) {\n      this.scroll.minScrollY = startDelta\n    }\n\n    if (endPos > this.scroll.maxScrollY) {\n      this.scroll.maxScrollY = -(\n        endPos - this.scroll.scroller.scrollBehaviorY.wrapperSize\n      )\n    }\n\n    return {\n      end,\n      startPos,\n      endPos,\n    }\n  }\n\n  destroy() {\n    const { content, scrollBehaviorY } = this.scroll.scroller\n    while (content.firstChild) {\n      content.removeChild(content.firstChild)\n    }\n    this.domManager.destroy()\n    this.scroll.off('scroll', this.update)\n    this.scroll.off('destroy', this.destroy)\n    scrollBehaviorY.hooks.off(scrollBehaviorY.hooks.eventTypes.computeBoundary)\n  }\n}\n"
  },
  {
    "path": "packages/mouse-wheel/README.md",
    "content": "# @better-scroll/mouse-wheel\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/mouse-wheel/README_zh-CN.md)\n\nAllow the mouse wheel to manipulate scrolling behavior.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\nBScroll.use(MouseWheel)\n\nconst bs = new BScroll('.wrapper', {\n  // ...\n  mouseWheel: {\n    speed: 2,\n    invert: false,\n    easeTime: 300,\n  }\n})\n```\n"
  },
  {
    "path": "packages/mouse-wheel/README_zh-CN.md",
    "content": "# @better-scroll/mouse-wheel\n\n允许鼠标滚轮来操纵滚动行为。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\nBScroll.use(MouseWheel)\n\nconst bs = new BScroll('.wrapper', {\n  // ...\n  mouseWheel: {\n    speed: 2,\n    invert: false,\n    easeTime: 300,\n  }\n})\n```\n"
  },
  {
    "path": "packages/mouse-wheel/package.json",
    "content": "{\n  \"name\": \"@better-scroll/mouse-wheel\",\n  \"version\": \"2.5.1\",\n  \"description\": \"support for MouseWheel in PC\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/mouse-wheel.min.js\",\n  \"module\": \"dist/mouse-wheel.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/mouse-wheel\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/mouse-wheel/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\njest.mock('@better-scroll/core')\n\nimport MouseWheel from '../index'\nimport { createEvent } from '@better-scroll/core/src/__tests__/__utils__/event'\n\ninterface CustomMouseWheel extends Event {\n  deltaX: number\n  deltaY: number\n  pageX: number\n  pageY: number\n  [key: string]: any\n}\n\nconst createMouseWheelElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\nfunction dispatchMouseWheel(\n  target: EventTarget,\n  name = 'wheel',\n  data: { [key: string]: any } = {}\n): void {\n  const event = <CustomMouseWheel>createEvent('', name)\n  Object.assign(event, data)\n  target.dispatchEvent(event)\n}\n\ndescribe('mouse-wheel plugin', () => {\n  const DISCRETE_TIME = 400\n  let scroll: BScroll\n  let mouseWheel: MouseWheel\n  jest.useFakeTimers()\n\n  beforeEach(() => {\n    const { wrapper } = createMouseWheelElements()\n    scroll = new BScroll(wrapper, {\n      stopPropagation: true,\n    })\n\n    scroll.scroller.scrollBehaviorX.performDampingAlgorithm = jest\n      .fn()\n      .mockImplementation((arg1) => {\n        return arg1\n      })\n    scroll.scroller.scrollBehaviorY.performDampingAlgorithm = jest\n      .fn()\n      .mockImplementation((arg1) => {\n        return arg1\n      })\n\n    mouseWheel = new MouseWheel(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n    jest.clearAllTimers()\n  })\n\n  it('should proxy hooks to BScroll instance', () => {\n    expect(scroll.registerType).toHaveBeenCalledWith([\n      'alterOptions',\n      'mousewheelStart',\n      'mousewheelMove',\n      'mousewheelEnd',\n    ])\n  })\n\n  it('should handle default options and user options', () => {\n    // case 1\n    scroll.options.mouseWheel = true\n    mouseWheel = new MouseWheel(scroll)\n\n    expect(mouseWheel.mouseWheelOpt).toMatchObject({\n      speed: 20,\n      invert: false,\n      easeTime: 300,\n      discreteTime: 400,\n      throttleTime: 0,\n      dampingFactor: 0.1,\n    })\n\n    // case 2\n    scroll.options.mouseWheel = {\n      dampingFactor: 1,\n      throttleTime: 50,\n    }\n    mouseWheel = new MouseWheel(scroll)\n\n    expect(mouseWheel.mouseWheelOpt).toMatchObject({\n      speed: 20,\n      invert: false,\n      easeTime: 300,\n      discreteTime: 400,\n      throttleTime: 50,\n      dampingFactor: 1,\n    })\n  })\n\n  it('should trigger mousewheel(start|move|end) when moved ', () => {\n    const onStart = jest.fn()\n    const onMove = jest.fn()\n    const onEnd = jest.fn()\n    const onAlterOptions = jest.fn()\n\n    scroll.on('mousewheelStart', onStart)\n    scroll.on('mousewheelMove', onMove)\n    scroll.on('mousewheelEnd', onEnd)\n    scroll.on('alterOptions', onAlterOptions)\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel')\n    expect(onStart).toBeCalledTimes(1)\n    expect(onMove).toBeCalledTimes(1)\n    expect(onAlterOptions).toBeCalledTimes(1)\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel')\n    jest.advanceTimersByTime(DISCRETE_TIME)\n    expect(onMove).toBeCalledTimes(2)\n\n    expect(onEnd).toBeCalledTimes(1)\n\n    // forbid moving\n    scroll.enabled = false\n    dispatchMouseWheel(scroll.wrapper, 'wheel')\n    expect(onStart).toBeCalledTimes(1)\n    expect(onMove).toBeCalledTimes(2)\n    expect(onEnd).toBeCalledTimes(1)\n  })\n\n  it('should support throttle when throttleTime > 0', () => {\n    mouseWheel.mouseWheelOpt.throttleTime = 50\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 10,\n    })\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 20,\n    })\n    jest.advanceTimersByTime(51)\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 30,\n    })\n    expect(scroll.scrollTo).toBeCalledTimes(2)\n  })\n\n  it('should warn when easeTime is invalid', () => {\n    const spyFn = jest.spyOn(console, 'error')\n    mouseWheel.mouseWheelOpt.easeTime = 50\n    dispatchMouseWheel(scroll.wrapper, 'wheel')\n    expect(spyFn).toBeCalled()\n  })\n\n  it('should preventDefault & stopProgation if they are set', () => {\n    const mockStopPropagation = jest.fn()\n    const mockPreventDefault = jest.fn()\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      preventDefault: mockPreventDefault,\n      stopPropagation: mockStopPropagation,\n    })\n    expect(mockPreventDefault).toBeCalled()\n    expect(mockStopPropagation).not.toBe(0)\n    jest.advanceTimersByTime(400)\n\n    mockPreventDefault.mockClear()\n    mockStopPropagation.mockClear()\n\n    // preventDefaultException work\n    scroll.options.preventDefaultException = {\n      tagName: /^(DIV)$/,\n    }\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      preventDefault: mockPreventDefault,\n      stopPropagation: mockStopPropagation,\n    })\n    expect(mockPreventDefault).not.toBeCalled()\n  })\n\n  it('should forbid scrollTo when mousewheelMove hook return true', () => {\n    const onStart = jest.fn()\n    const onMove = jest.fn().mockImplementation(() => {\n      return true\n    })\n    const onEnd = jest.fn()\n\n    scroll.on('mousewheelStart', onStart)\n    scroll.on('mousewheelMove', onMove)\n    scroll.on('mousewheelEnd', onEnd)\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel')\n    expect(onStart).toBeCalledTimes(1)\n    expect(onMove).toBeCalledTimes(1)\n\n    expect(scroll.scrollTo).not.toBeCalled()\n  })\n\n  it('should get right postion when move with deltaMode = 0', () => {\n    const onEnd = jest.fn()\n    scroll.on('mousewheelEnd', onEnd)\n\n    // x direction\n    scroll.hasVerticalScroll = false\n    scroll.hasHorizontalScroll = true\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 10,\n      deltaMode: 0,\n    })\n\n    expect(scroll.scrollTo).toBeCalledWith(-10, 0, 300)\n    jest.advanceTimersByTime(410)\n    expect(onEnd).toBeCalledWith({\n      x: -10,\n      y: 0,\n      directionX: 1,\n      directionY: 0,\n    })\n\n    // y direction\n    scroll.hasVerticalScroll = true\n    scroll.hasHorizontalScroll = false\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 10,\n      deltaMode: 0,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(0, -10, 300)\n    jest.advanceTimersByTime(410)\n    expect(onEnd).toBeCalledWith({\n      x: 0,\n      y: -10,\n      directionX: 0,\n      directionY: 1,\n    })\n  })\n\n  it('should get right postion when move with deltaMode = 1', () => {\n    // x direction\n    scroll.hasVerticalScroll = false\n    scroll.hasHorizontalScroll = true\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 2,\n      deltaMode: 1,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(-40, 0, 300)\n\n    // y direction\n    scroll.hasVerticalScroll = true\n    scroll.hasHorizontalScroll = false\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 2,\n      deltaMode: 1,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(0, -40, 300)\n  })\n\n  it('should get right postion when move with wheelDeltaX and wheelDeltaY', () => {\n    scroll.hasVerticalScroll = true\n    scroll.hasHorizontalScroll = true\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      wheelDeltaX: -120,\n      wheelDeltaY: -240,\n      deltaMode: 0,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(-20, -40, 300)\n  })\n\n  it('should get right postion when move with wheelDelta', () => {\n    scroll.hasVerticalScroll = true\n    scroll.hasHorizontalScroll = true\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      wheelDelta: -120,\n      deltaMode: 0,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(-20, -20, 300)\n  })\n\n  it('should get right postion when move with detail', () => {\n    scroll.hasVerticalScroll = true\n    scroll.hasHorizontalScroll = true\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      detail: 60,\n      deltaMode: 0,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(-400, -400, 300)\n  })\n\n  it('should get right postion when move with invert = true', () => {\n    // x direction\n    scroll.hasVerticalScroll = false\n    scroll.hasHorizontalScroll = true\n\n    mouseWheel.mouseWheelOpt.invert = true\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 2,\n      deltaMode: 1,\n    })\n    expect(scroll.scrollTo).toBeCalledWith(40, 0, 300)\n  })\n\n  it('should work with dampingFactor', () => {\n    mouseWheel.mouseWheelOpt.dampingFactor = 0.1\n\n    scroll.scroller.scrollBehaviorX.performDampingAlgorithm = jest\n      .fn()\n      .mockImplementation((distance, factor) => {\n        return distance * factor\n      })\n    scroll.scroller.scrollBehaviorY.performDampingAlgorithm = jest\n      .fn()\n      .mockImplementation((distance, factor) => {\n        return distance * factor\n      })\n\n    dispatchMouseWheel(scroll.wrapper, 'wheel', {\n      deltaX: 0,\n      deltaY: 2,\n      deltaMode: 1,\n    })\n\n    expect(scroll.scrollTo).toBeCalledWith(0, -4, 300)\n    // improve coverage\n    mouseWheel.destroy()\n  })\n})\n"
  },
  {
    "path": "packages/mouse-wheel/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport {\n  warn,\n  preventDefaultExceptionFn,\n  EventRegister,\n  EventEmitter,\n  Direction,\n  ApplyOrder,\n  extend,\n  maybePrevent,\n} from '@better-scroll/shared-utils'\n\nexport type MouseWheelOptions = Partial<MouseWheelConfig> | true\n\nexport interface MouseWheelConfig {\n  speed: number\n  invert: boolean\n  easeTime: number\n  discreteTime: number\n  throttleTime: number\n  dampingFactor: number\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    mouseWheel?: MouseWheelOptions\n  }\n}\n\ninterface CompatibleWheelEvent extends WheelEvent {\n  pageX: number\n  pageY: number\n  readonly wheelDeltaX: number\n  readonly wheelDeltaY: number\n  readonly wheelDelta: number\n}\n\ninterface WheelDelta {\n  x: number\n  y: number\n  directionX: Direction\n  directionY: Direction\n}\n\nexport default class MouseWheel {\n  static pluginName = 'mouseWheel'\n  static applyOrder = ApplyOrder.Pre\n  mouseWheelOpt: MouseWheelConfig\n  private eventRegister: EventRegister\n  private wheelEndTimer: number = 0\n  private wheelMoveTimer: number = 0\n  private wheelStart = false\n  private deltaCache: { x: number; y: number }[]\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  private init() {\n    this.handleBScroll()\n    this.handleOptions()\n    this.handleHooks()\n\n    this.registerEvent()\n  }\n\n  private handleBScroll() {\n    this.scroll.registerType([\n      'alterOptions',\n      'mousewheelStart',\n      'mousewheelMove',\n      'mousewheelEnd',\n    ])\n  }\n\n  private handleOptions() {\n    const userOptions = (\n      this.scroll.options.mouseWheel === true\n        ? {}\n        : this.scroll.options.mouseWheel\n    ) as Partial<MouseWheelConfig>\n    const defaultOptions: MouseWheelConfig = {\n      speed: 20,\n      invert: false,\n      easeTime: 300,\n      discreteTime: 400,\n      throttleTime: 0,\n      dampingFactor: 0.1,\n    }\n    this.mouseWheelOpt = extend(defaultOptions, userOptions)\n  }\n\n  private handleHooks() {\n    this.hooksFn = []\n    this.registerHooks(this.scroll.hooks, 'destroy', this.destroy)\n  }\n\n  private registerEvent() {\n    this.eventRegister = new EventRegister(this.scroll.scroller.wrapper, [\n      {\n        name: 'wheel',\n        handler: this.wheelHandler.bind(this),\n      },\n      {\n        name: 'mousewheel',\n        handler: this.wheelHandler.bind(this),\n      },\n      {\n        name: 'DOMMouseScroll', // FireFox\n        handler: this.wheelHandler.bind(this),\n      },\n    ])\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private wheelHandler(e: CompatibleWheelEvent) {\n    if (!this.scroll.enabled) {\n      return\n    }\n    this.beforeHandler(e)\n\n    // start\n    if (!this.wheelStart) {\n      this.wheelStartHandler(e)\n      this.wheelStart = true\n    }\n\n    // move\n    const delta = this.getWheelDelta(e)\n    this.wheelMoveHandler(delta)\n\n    // end\n    this.wheelEndDetector(delta)\n  }\n\n  private wheelStartHandler(e: CompatibleWheelEvent) {\n    this.cleanCache()\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n\n    scrollBehaviorX.setMovingDirection(Direction.Default)\n    scrollBehaviorY.setMovingDirection(Direction.Default)\n    scrollBehaviorX.setDirection(Direction.Default)\n    scrollBehaviorY.setDirection(Direction.Default)\n\n    this.scroll.trigger(this.scroll.eventTypes.alterOptions, this.mouseWheelOpt)\n    this.scroll.trigger(this.scroll.eventTypes.mousewheelStart)\n  }\n\n  private cleanCache() {\n    this.deltaCache = []\n  }\n\n  private wheelMoveHandler(delta: {\n    x: number\n    y: number\n    directionX: number\n    directionY: number\n  }) {\n    const { throttleTime, dampingFactor } = this.mouseWheelOpt\n    if (throttleTime && this.wheelMoveTimer) {\n      this.deltaCache.push(delta)\n    } else {\n      const cachedDelta = this.deltaCache.reduce(\n        (prev, current) => {\n          return {\n            x: prev.x + current.x,\n            y: prev.y + current.y,\n          }\n        },\n        { x: 0, y: 0 }\n      )\n\n      this.cleanCache()\n\n      const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n\n      scrollBehaviorX.setMovingDirection(-delta.directionX)\n      scrollBehaviorY.setMovingDirection(-delta.directionY)\n      scrollBehaviorX.setDirection(delta.x)\n      scrollBehaviorY.setDirection(delta.y)\n\n      // when out of boundary, perform a damping scroll\n      const newX = scrollBehaviorX.performDampingAlgorithm(\n        Math.round(delta.x) + cachedDelta.x,\n        dampingFactor\n      )\n      const newY = scrollBehaviorY.performDampingAlgorithm(\n        Math.round(delta.y) + cachedDelta.x,\n        dampingFactor\n      )\n\n      if (\n        !this.scroll.trigger(this.scroll.eventTypes.mousewheelMove, {\n          x: newX,\n          y: newY,\n        })\n      ) {\n        const easeTime = this.getEaseTime()\n        if (newX !== this.scroll.x || newY !== this.scroll.y) {\n          this.scroll.scrollTo(newX, newY, easeTime)\n        }\n      }\n      if (throttleTime) {\n        this.wheelMoveTimer = window.setTimeout(() => {\n          this.wheelMoveTimer = 0\n        }, throttleTime)\n      }\n    }\n  }\n\n  private wheelEndDetector(delta: WheelDelta) {\n    window.clearTimeout(this.wheelEndTimer)\n    this.wheelEndTimer = window.setTimeout(() => {\n      this.wheelStart = false\n      window.clearTimeout(this.wheelMoveTimer)\n      this.wheelMoveTimer = 0\n\n      this.scroll.trigger(this.scroll.eventTypes.mousewheelEnd, delta)\n    }, this.mouseWheelOpt.discreteTime)\n  }\n\n  private getWheelDelta(e: CompatibleWheelEvent): WheelDelta {\n    const { speed, invert } = this.mouseWheelOpt\n    let wheelDeltaX = 0\n    let wheelDeltaY = 0\n    let direction = invert ? Direction.Negative : Direction.Positive\n    switch (true) {\n      case 'deltaX' in e:\n        if (e.deltaMode === 1) {\n          wheelDeltaX = -e.deltaX * speed\n          wheelDeltaY = -e.deltaY * speed\n        } else {\n          wheelDeltaX = -e.deltaX\n          wheelDeltaY = -e.deltaY\n        }\n        break\n      case 'wheelDeltaX' in e:\n        wheelDeltaX = (e.wheelDeltaX / 120) * speed\n        wheelDeltaY = (e.wheelDeltaY / 120) * speed\n        break\n      case 'wheelDelta' in e:\n        wheelDeltaX = wheelDeltaY = (e.wheelDelta / 120) * speed\n        break\n      case 'detail' in e:\n        wheelDeltaX = wheelDeltaY = (-e.detail / 3) * speed\n        break\n    }\n    wheelDeltaX *= direction\n    wheelDeltaY *= direction\n\n    if (!this.scroll.hasVerticalScroll) {\n      if (Math.abs(wheelDeltaY) > Math.abs(wheelDeltaX)) {\n        wheelDeltaX = wheelDeltaY\n      }\n      wheelDeltaY = 0\n    }\n    if (!this.scroll.hasHorizontalScroll) {\n      wheelDeltaX = 0\n    }\n\n    const directionX =\n      wheelDeltaX > 0\n        ? Direction.Negative\n        : wheelDeltaX < 0\n        ? Direction.Positive\n        : Direction.Default\n    const directionY =\n      wheelDeltaY > 0\n        ? Direction.Negative\n        : wheelDeltaY < 0\n        ? Direction.Positive\n        : Direction.Default\n    return {\n      x: wheelDeltaX,\n      y: wheelDeltaY,\n      directionX,\n      directionY,\n    }\n  }\n\n  private beforeHandler(e: CompatibleWheelEvent) {\n    const { preventDefault, stopPropagation, preventDefaultException } =\n      this.scroll.options\n    if (\n      preventDefault &&\n      !preventDefaultExceptionFn(e.target, preventDefaultException)\n    ) {\n      maybePrevent(e)\n    }\n    if (stopPropagation) {\n      e.stopPropagation()\n    }\n  }\n\n  private getEaseTime() {\n    const SAFE_EASETIME = 100\n    const easeTime = this.mouseWheelOpt.easeTime\n    // scrollEnd event will be triggered in every calling of scrollTo when easeTime is too small\n    // easeTime needs to be greater than 100\n    if (easeTime < SAFE_EASETIME) {\n      warn(\n        `easeTime should be greater than 100.` +\n          `If mouseWheel easeTime is too small,` +\n          `scrollEnd will be triggered many times.`\n      )\n    }\n    return Math.max(easeTime, SAFE_EASETIME)\n  }\n\n  destroy() {\n    this.eventRegister.destroy()\n\n    window.clearTimeout(this.wheelEndTimer)\n    window.clearTimeout(this.wheelMoveTimer)\n\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n  }\n}\n"
  },
  {
    "path": "packages/movable/README.md",
    "content": "# @better-scroll/movable\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/movable/README.md)\n\nMovable area plugin.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\n\nBScroll.use(Movable)\n\nconst bs = new BScroll('.wrapper', {\n  movable: true\n})\n```\n"
  },
  {
    "path": "packages/movable/README_zh-CN.md",
    "content": "# @better-scroll/movable\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/movable/README_zh-CN.md)\n\nMovable 可移动区域插件。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\n\nBScroll.use(Movable)\n\nconst bs = new BScroll('.wrapper', {\n  movable: true\n})\n```\n"
  },
  {
    "path": "packages/movable/package.json",
    "content": "{\n  \"name\": \"@better-scroll/movable\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Movable plugin for BetterScroll\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/movable.min.js\",\n  \"module\": \"dist/movable.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/observe-dom\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  }\n}\n"
  },
  {
    "path": "packages/movable/src/__tests__/index.spec.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\njest.mock('@better-scroll/core')\nimport { createDiv } from '@better-scroll/core/src/__tests__/__utils__/layout'\nimport Movable from '../index'\n\nconst createMovableEls = () => {\n  const wrapper = createDiv(100, 100, 0, 0)\n  const content = createDiv(100, 100, 0, 0)\n  wrapper.appendChild(content)\n\n  return {\n    wrapper,\n    content,\n  }\n}\ndescribe('movable plugin', () => {\n  let scroll: BScroll\n  let movable: Movable\n\n  beforeEach(() => {\n    // create DOM\n    const { wrapper } = createMovableEls()\n    scroll = new BScroll(wrapper)\n    movable = new Movable(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to BScroll instance', () => {\n    expect(scroll.proxy).toBeCalled()\n    expect(scroll.proxy).toHaveBeenLastCalledWith([\n      {\n        key: 'putAt',\n        sourceKey: 'plugins.movable.putAt',\n      },\n    ])\n  })\n\n  it('should modify boundary', () => {\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n    scrollBehaviorX.options.scrollable = true\n    scrollBehaviorY.options.scrollable = true\n\n    scrollBehaviorX.wrapperSize = 200\n    scrollBehaviorX.contentSize = 100\n    scrollBehaviorY.wrapperSize = 400\n    scrollBehaviorY.contentSize = 200\n\n    let boundaryX: Boundary = { minScrollPos: 0, maxScrollPos: 1 }\n    let boundaryY: Boundary = { minScrollPos: 0, maxScrollPos: 1 }\n\n    scrollBehaviorX.hooks.trigger(\n      scrollBehaviorX.hooks.eventTypes.computeBoundary,\n      boundaryX\n    )\n    scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      boundaryY\n    )\n\n    expect(boundaryX).toMatchObject({\n      minScrollPos: 100,\n      maxScrollPos: 0,\n    })\n\n    expect(boundaryY).toMatchObject({\n      minScrollPos: 200,\n      maxScrollPos: 0,\n    })\n  })\n\n  it('should register ignoreHasScroll hook', () => {\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n    const retX = scrollBehaviorX.hooks.trigger(\n      scrollBehaviorX.hooks.eventTypes.ignoreHasScroll\n    )\n    const retY = scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.ignoreHasScroll\n    )\n\n    expect(retX).toBe(true)\n    expect(retY).toBe(true)\n  })\n\n  it('should work well when call putAt()', () => {\n    // integer\n    movable.putAt(20, 20)\n    expect(scroll.scrollTo).toBeCalledWith(20, 20, 800, expect.anything())\n\n    // simulate minScrollPos\n    scroll.scroller.scrollBehaviorX.minScrollPos = 300\n    scroll.scroller.scrollBehaviorY.minScrollPos = 300\n\n    // [left, bottom]\n    movable.putAt('left', 'bottom')\n    expect(scroll.scrollTo).toBeCalledWith(0, 300, 800, expect.anything())\n\n    // [right, top]\n    movable.putAt('right', 'top')\n    expect(scroll.scrollTo).toBeCalledWith(300, 0, 800, expect.anything())\n\n    // [center, center]\n    movable.putAt('center', 'center')\n    expect(scroll.scrollTo).toBeCalledWith(150, 150, 800, expect.anything())\n  })\n\n  it('should destroy all events', () => {\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n    scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n    expect(scrollBehaviorX.hooks.events['computeBoundary'].length).toBe(0)\n    expect(scrollBehaviorX.hooks.events['ignoreHasScroll'].length).toBe(0)\n\n    expect(scrollBehaviorY.hooks.events['computeBoundary'].length).toBe(0)\n    expect(scrollBehaviorY.hooks.events['ignoreHasScroll'].length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "packages/movable/src/index.ts",
    "content": "import BScroll, { Behavior, Boundary } from '@better-scroll/core'\nimport {\n  ease,\n  EventEmitter,\n  ApplyOrder,\n  EaseItem,\n} from '@better-scroll/shared-utils'\nimport propertiesConfig from './propertiesConfig'\n\ntype PositionX = number | 'left' | 'right' | 'center'\ntype PositionY = number | 'top' | 'bottom' | 'center'\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    movable?: true\n  }\n  interface CustomAPI {\n    movable: PluginAPI\n  }\n}\n\ninterface PluginAPI {\n  putAt(x: PositionX, y: PositionY, time?: number, easing?: EaseItem): void\n}\n\nexport default class Movable implements PluginAPI {\n  static pluginName = 'movable'\n  static applyOrder = ApplyOrder.Pre\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  constructor(public scroll: BScroll) {\n    this.handleBScroll()\n    this.handleHooks()\n  }\n\n  private handleBScroll() {\n    this.scroll.proxy(propertiesConfig)\n  }\n\n  private handleHooks() {\n    this.hooksFn = []\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n\n    const computeBoundary = (boundary: Boundary, behavior: Behavior) => {\n      if (boundary.maxScrollPos > 0) {\n        // content is smaller than wrapper\n        boundary.minScrollPos = behavior.wrapperSize - behavior.contentSize\n        boundary.maxScrollPos = 0\n      }\n    }\n\n    this.registerHooks(\n      scrollBehaviorX.hooks,\n      scrollBehaviorX.hooks.eventTypes.ignoreHasScroll,\n      () => true\n    )\n    this.registerHooks(\n      scrollBehaviorX.hooks,\n      scrollBehaviorX.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        computeBoundary(boundary, scrollBehaviorX)\n      }\n    )\n\n    this.registerHooks(\n      scrollBehaviorY.hooks,\n      scrollBehaviorY.hooks.eventTypes.ignoreHasScroll,\n      () => true\n    )\n    this.registerHooks(\n      scrollBehaviorY.hooks,\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        computeBoundary(boundary, scrollBehaviorY)\n      }\n    )\n\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.destroy,\n      () => {\n        this.destroy()\n      }\n    )\n  }\n\n  putAt(\n    x: PositionX,\n    y: PositionY,\n    time = this.scroll.options.bounceTime,\n    easing = ease.bounce\n  ) {\n    const position = this.resolvePostion(x, y)\n    this.scroll.scrollTo(position.x, position.y, time, easing)\n  }\n\n  private resolvePostion(x: PositionX, y: PositionY): { x: number; y: number } {\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    const resolveFormula = {\n      left() {\n        return 0\n      },\n      top() {\n        return 0\n      },\n      right() {\n        return scrollBehaviorX.minScrollPos\n      },\n      bottom() {\n        return scrollBehaviorY.minScrollPos\n      },\n      center(index: number) {\n        const baseSize =\n          index === 0\n            ? scrollBehaviorX.minScrollPos\n            : scrollBehaviorY.minScrollPos\n        return baseSize / 2\n      },\n    }\n    return {\n      x: typeof x === 'number' ? x : resolveFormula[x](0),\n      y: typeof y === 'number' ? y : resolveFormula[y](1),\n    }\n  }\n\n  destroy() {\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n    this.hooksFn.length = 0\n  }\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n}\n"
  },
  {
    "path": "packages/movable/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.movable'\nconst propertiesMap = [\n  {\n    key: 'putAt',\n    name: 'putAt',\n  },\n]\nexport default propertiesMap.map((item) => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`,\n  }\n})\n"
  },
  {
    "path": "packages/nested-scroll/README.md",
    "content": "# better-scroll\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/nested-scroll/README_zh-CN.md)\n\nnestedScroll is a plugin which helps you solve the trouble of nested Scroll\n\n## Usage\n\n```js\n  import BScroll from 'better-scroll'\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  BScroll.use(NestedScroll)\n\n  // parent bs\n  new BScroll('.outerWrapper', {\n    nestedScroll: true\n  })\n  // child bs\n  new BScroll('.innerWrapper', {\n    nestedScroll: true\n  })\n```\n"
  },
  {
    "path": "packages/nested-scroll/package.json",
    "content": "{\n  \"name\": \"@better-scroll/nested-scroll\",\n  \"version\": \"2.5.1\",\n  \"description\": \"make your nested scrolls reconciliation\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"main\": \"dist/nested-scroll.js\",\n  \"module\": \"dist/nested-scroll.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"scripts\": {},\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/nested-scroll\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/nested-scroll/src/BScrollFamily.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { EventEmitter, findIndex } from '@better-scroll/shared-utils'\n\n// second element is used to discribe the distance between two bs instance's wrapper DOM\nexport type BScrollFamilyTuple = [BScrollFamily, number]\n\nexport default class BScrollFamily {\n  static create(scroll: BScroll) {\n    return new BScrollFamily(scroll)\n  }\n  ancestors: BScrollFamilyTuple[] = []\n  descendants: BScrollFamilyTuple[] = []\n  hooksManager: [EventEmitter, string, Function][] = []\n  selfScroll: BScroll\n  analyzed: boolean = false\n  constructor(scroll: BScroll) {\n    this.selfScroll = scroll\n  }\n\n  hasAncestors(bscrollFamily: BScrollFamily) {\n    const index = findIndex(this.ancestors, ([item]) => {\n      return item === bscrollFamily\n    })\n    return index > -1\n  }\n\n  hasDescendants(bscrollFamily: BScrollFamily) {\n    const index = findIndex(this.descendants, ([item]) => {\n      return item === bscrollFamily\n    })\n    return index > -1\n  }\n\n  addAncestor(bscrollFamily: BScrollFamily, distance: number) {\n    const ancestors = this.ancestors\n    ancestors.push([bscrollFamily, distance])\n    // by ascend\n    ancestors.sort((a, b) => {\n      return a[1] - b[1]\n    })\n  }\n\n  addDescendant(bscrollFamily: BScrollFamily, distance: number) {\n    const descendants = this.descendants\n    descendants.push([bscrollFamily, distance])\n    // by ascend\n    descendants.sort((a, b) => {\n      return a[1] - b[1]\n    })\n  }\n\n  removeAncestor(bscrollFamily: BScrollFamily) {\n    const ancestors = this.ancestors\n    if (ancestors.length) {\n      const index = findIndex(this.ancestors, ([item]) => {\n        return item === bscrollFamily\n      })\n      if (index > -1) {\n        return ancestors.splice(index, 1)\n      }\n    }\n  }\n\n  removeDescendant(bscrollFamily: BScrollFamily) {\n    const descendants = this.descendants\n    if (descendants.length) {\n      const index = findIndex(this.descendants, ([item]) => {\n        return item === bscrollFamily\n      })\n      if (index > -1) {\n        return descendants.splice(index, 1)\n      }\n    }\n  }\n\n  registerHooks(hook: EventEmitter, eventType: string, handler: Function) {\n    hook.on(eventType, handler)\n    this.hooksManager.push([hook, eventType, handler])\n  }\n\n  setAnalyzed(flag = false) {\n    this.analyzed = flag\n  }\n\n  purge() {\n    // remove self from graph\n    this.ancestors.forEach(([bscrollFamily]) => {\n      bscrollFamily.removeDescendant(this)\n    })\n    this.descendants.forEach(([bscrollFamily]) => {\n      bscrollFamily.removeAncestor(this)\n    })\n\n    // remove all hook handlers\n    this.hooksManager.forEach(([hooks, eventType, handler]) => {\n      hooks.off(eventType, handler)\n    })\n    this.hooksManager = []\n  }\n}\n"
  },
  {
    "path": "packages/nested-scroll/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\njest.mock('@better-scroll/core')\n\nimport NestedScroll, { DEFAUL_GROUP_ID } from '../index'\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\ndescribe('NestedScroll tests', () => {\n  let parentWrapper: HTMLElement\n  let parentContent: HTMLElement\n  let childWrapper: HTMLElement\n  let childContent: HTMLElement\n  let grandsonWrapper: HTMLElement\n  let grandsonContent: HTMLElement\n  beforeEach(() => {\n    parentWrapper = document.createElement('div')\n    parentContent = document.createElement('div')\n\n    childWrapper = document.createElement('div')\n    childContent = document.createElement('div')\n\n    grandsonWrapper = document.createElement('div')\n    grandsonContent = document.createElement('div')\n\n    parentWrapper.appendChild(parentContent)\n    parentContent.appendChild(childWrapper)\n    childWrapper.appendChild(childContent)\n    childContent.appendChild(grandsonWrapper)\n    grandsonWrapper.appendChild(grandsonContent)\n  })\n\n  afterEach(() => {\n    NestedScroll.purgeAllNestedScrolls()\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to scroll instance', () => {\n    const scroll = new BScroll(parentWrapper, {\n      nestedScroll: true,\n    })\n    new NestedScroll(scroll)\n    expect(scroll.proxy).toBeCalledWith([\n      {\n        key: 'purgeNestedScroll',\n        sourceKey: 'plugins.nestedScroll.purgeNestedScroll',\n      },\n    ])\n  })\n\n  it('nestedScroll.gropuId should be string or number ', () => {\n    const spyFn = jest.spyOn(console, 'error')\n    const nestedScroll1 = new NestedScroll(\n      new BScroll(parentWrapper, {\n        nestedScroll: {\n          groupId: {} as any,\n        },\n      })\n    )\n\n    expect(spyFn).toBeCalledWith(\n      '[BScroll warn]: groupId must be string or number for NestedScroll plugin'\n    )\n  })\n\n  it('should allocate default groupId when nestedScroll options is \"true\"', () => {\n    const nestedScroll1 = new NestedScroll(\n      new BScroll(parentWrapper, {\n        nestedScroll: true,\n      })\n    )\n    const nestedScroll2 = new NestedScroll(\n      new BScroll(childWrapper, {\n        nestedScroll: true,\n      })\n    )\n\n    expect(nestedScroll1).toEqual(nestedScroll2)\n    expect(NestedScroll.instancesMap[DEFAUL_GROUP_ID]).toBe(nestedScroll1)\n  })\n\n  it('should share same nestedScroll when groupId is equal', () => {\n    const nestedScroll1 = new NestedScroll(\n      new BScroll(parentWrapper, {\n        nestedScroll: {\n          groupId: 0,\n        },\n      })\n    )\n    const nestedScroll2 = new NestedScroll(\n      new BScroll(childWrapper, {\n        nestedScroll: {\n          groupId: 0,\n        },\n      })\n    )\n\n    expect(nestedScroll1).toEqual(nestedScroll2)\n  })\n\n  it('should store BScroll instance', () => {\n    const nestedScroll1 = new NestedScroll(new BScroll(parentWrapper, {}))\n    const nestedScroll2 = new NestedScroll(new BScroll(childWrapper, {}))\n\n    expect(nestedScroll1.store.length).toBe(2)\n    expect(nestedScroll1).toBe(nestedScroll2)\n  })\n\n  it('should build BScrollGraph', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'BScrollGraph',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'BScrollGraph',\n      },\n    })\n    // ns1 === ns2\n    const ns1 = new NestedScroll(parentScroll)\n    const ns2 = new NestedScroll(childScroll)\n    const parentBScrollFamily = ns1.store[0]\n    const childBScrollFamily = ns1.store[1]\n\n    expect(parentBScrollFamily.ancestors.length).toBe(0)\n    expect(\n      parentBScrollFamily.descendants.findIndex(([bf]) => {\n        return bf === childBScrollFamily\n      }) > -1\n    ).toBe(true)\n    expect(\n      childBScrollFamily.ancestors.findIndex(([bf]) => {\n        return bf === parentBScrollFamily\n      }) > -1\n    ).toBe(true)\n    expect(childBScrollFamily.descendants.length).toBe(0)\n  })\n\n  it('should has different nestedScroll instance in NestedScroll class', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'BScrollGraph1',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'BScrollGraph2',\n      },\n    })\n\n    const nestedScroll1 = new NestedScroll(parentScroll)\n    const nestedScroll2 = new NestedScroll(childScroll)\n\n    expect(nestedScroll1).not.toBe(nestedScroll2)\n    expect(NestedScroll.getAllNestedScrolls().length).toBe(2)\n  })\n\n  it('disable the ancestors scroll when self is scrolling', () => {\n    // vertical\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'vertical',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'vertical',\n      },\n    })\n    const grandsonScroll = new BScroll(grandsonWrapper, {\n      nestedScroll: {\n        groupId: 'vertical',\n      },\n    })\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n    new NestedScroll(grandsonScroll)\n\n    addProperties(parentScroll, {\n      pending: true,\n    })\n    addProperties(childScroll, {\n      pending: true,\n    })\n    grandsonScroll.trigger(grandsonScroll.eventTypes.beforeScrollStart)\n\n    expect(parentScroll.stop).toBeCalled()\n    expect(parentScroll.resetPosition).toBeCalled()\n    expect(childScroll.stop).toBeCalled()\n    expect(childScroll.resetPosition).toBeCalled()\n\n    // horizontal\n    const parentScrollH = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'horizontal',\n      },\n      scrollY: false,\n      scrollX: true,\n    })\n    const childScrollH = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'horizontal',\n      },\n      scrollY: false,\n      scrollX: true,\n    })\n    const grandsonScrollH = new BScroll(grandsonWrapper, {\n      nestedScroll: {\n        groupId: 'horizontal',\n      },\n      scrollY: false,\n      scrollX: true,\n    })\n    new NestedScroll(parentScrollH)\n    new NestedScroll(childScrollH)\n    new NestedScroll(grandsonScrollH)\n\n    addProperties(parentScrollH, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n    })\n    addProperties(childScrollH, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n    })\n    addProperties(grandsonScrollH, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n    })\n    grandsonScrollH.trigger(grandsonScrollH.eventTypes.beforeScrollStart)\n\n    expect(parentScrollH.disable).toBeCalled()\n    expect(parentScrollH.scroller.actions.startTime).toBeTruthy()\n    expect(childScrollH.disable).toBeCalled()\n    expect(childScrollH.scroller.actions.startTime).toBeTruthy()\n  })\n\n  it('should delete scroll from nestedScroll graph when scroll is destroyed', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'deleteScroll',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'deleteScroll',\n      },\n    })\n\n    const ns = new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    expect(ns.store.length).toBe(2)\n    expect(childScroll.hooks.events.destroy.length).toBe(1)\n    childScroll.hooks.trigger(childScroll.hooks.eventTypes.destroy)\n    expect(ns.store.length).toBe(1)\n    expect(childScroll.hooks.events.destroy.length).toBe(0)\n  })\n\n  it('should force ancestors and descendants stop when self will start scrolling', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'force-stop',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'force-stop',\n      },\n    })\n\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    addProperties(childScroll, {\n      pending: true,\n    })\n\n    parentScroll.trigger(parentScroll.eventTypes.beforeScrollStart)\n    expect(childScroll.stop).toBeCalled()\n    expect(childScroll.resetPosition).toBeCalled()\n  })\n\n  it('should force self resetting potisition', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'force-stop',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'force-stop',\n      },\n    })\n\n    addProperties(parentScroll, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n    })\n    addProperties(childScroll, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n      x: 1,\n    })\n\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    childScroll.trigger(childScroll.eventTypes.beforeScrollStart)\n    expect(childScroll.scroller.reflow).toBeCalled()\n    expect(childScroll.resetPosition).toBeCalled()\n  })\n\n  it('detectMovingDirection hook', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'enable-others',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'enable-others',\n      },\n    })\n\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    // one is moved, all ancestors should be disabled\n    childScroll.scroller.actions.contentMoved = true\n    const selfActionsHooks = childScroll.scroller.actions.hooks\n    selfActionsHooks.trigger(selfActionsHooks.eventTypes.detectMovingDirection)\n    expect(parentScroll.disable).toBeCalled()\n  })\n\n  it('should enable ancestors and descendants when self touchended', () => {\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'touchend',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'touchend',\n      },\n    })\n\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    childScroll.trigger(childScroll.eventTypes.touchEnd)\n    expect(parentScroll.enable).toBeCalled()\n    parentScroll.trigger(parentScroll.eventTypes.touchEnd)\n    expect(childScroll.enable).toBeCalled()\n  })\n\n  it('should only allow top-scroll has bounce effect', () => {\n    // vertical\n    const parentScroll = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'vertical-bounce-effect',\n      },\n    })\n    const childScroll = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'vertical-bounce-effect',\n      },\n    })\n\n    new NestedScroll(parentScroll)\n    new NestedScroll(childScroll)\n\n    childScroll.movingDirectionY = -1\n\n    const selfActionsHooks = childScroll.scroller.actions.hooks\n    selfActionsHooks.trigger(selfActionsHooks.eventTypes.detectMovingDirection)\n\n    expect(childScroll.disable).toBeCalled()\n    expect(parentScroll.enable).toBeCalled()\n\n    // horizontal\n\n    const parentScrollH = new BScroll(parentWrapper, {\n      nestedScroll: {\n        groupId: 'horizontal-bounce-effect',\n      },\n    })\n    const childScrollH = new BScroll(childWrapper, {\n      nestedScroll: {\n        groupId: 'horizontal-bounce-effect',\n      },\n    })\n\n    addProperties(childScrollH, {\n      hasVerticalScroll: false,\n      hasHorizontalScroll: true,\n    })\n\n    new NestedScroll(parentScrollH)\n    new NestedScroll(childScrollH)\n\n    childScrollH.movingDirectionX = -1\n\n    const selfActionsHooksH = childScrollH.scroller.actions.hooks\n    selfActionsHooksH.trigger(\n      selfActionsHooksH.eventTypes.detectMovingDirection\n    )\n\n    expect(childScrollH.disable).toBeCalled()\n    expect(parentScrollH.enable).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/nested-scroll/src/index.ts",
    "content": "import BScroll, { MountedBScrollHTMLElement } from '@better-scroll/core'\nimport {\n  Direction,\n  EventEmitter,\n  extend,\n  warn,\n  findIndex,\n} from '@better-scroll/shared-utils'\nimport BScrollFamily from './BScrollFamily'\nimport propertiesConfig from './propertiesConfig'\n\nexport const DEFAUL_GROUP_ID = 'INTERNAL_NESTED_SCROLL'\n\nexport type NestedScrollGroupId = string | number\n\nexport interface NestedScrollConfig {\n  groupId: NestedScrollGroupId\n}\n\nexport type NestedScrollOptions = NestedScrollConfig | true\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    nestedScroll?: NestedScrollOptions\n  }\n  interface CustomAPI {\n    nestedScroll: PluginAPI\n  }\n}\n\ninterface PluginAPI {\n  purgeNestedScroll(groupId: NestedScrollGroupId): void\n}\n\ninterface NestedScrollInstancesMap {\n  [key: string]: NestedScroll\n  [index: number]: NestedScroll\n}\n\nconst forceScrollStopHandler = (scrolls: BScroll[]) => {\n  scrolls.forEach((scroll) => {\n    if (scroll.pending) {\n      scroll.stop()\n      scroll.resetPosition()\n    }\n  })\n}\n\nconst enableScrollHander = (scrolls: BScroll[]) => {\n  scrolls.forEach((scroll) => {\n    scroll.enable()\n  })\n}\n\nconst disableScrollHander = (scrolls: BScroll[], currentScroll: BScroll) => {\n  scrolls.forEach((scroll) => {\n    if (\n      scroll.hasHorizontalScroll === currentScroll.hasHorizontalScroll ||\n      scroll.hasVerticalScroll === currentScroll.hasVerticalScroll\n    ) {\n      scroll.disable()\n    }\n  })\n}\n\nconst syncTouchstartData = (scrolls: BScroll[]) => {\n  scrolls.forEach((scroll) => {\n    const { actions, scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n\n    // prevent click triggering many times\n    actions.fingerMoved = true\n    actions.contentMoved = false\n\n    actions.directionLockAction.reset()\n\n    scrollBehaviorX.start()\n    scrollBehaviorY.start()\n\n    scrollBehaviorX.resetStartPos()\n    scrollBehaviorY.resetStartPos()\n\n    actions.startTime = +new Date()\n  })\n}\n\nconst isOutOfBoundary = (scroll: BScroll): boolean => {\n  const {\n    hasHorizontalScroll,\n    hasVerticalScroll,\n    x,\n    y,\n    minScrollX,\n    maxScrollX,\n    minScrollY,\n    maxScrollY,\n    movingDirectionX,\n    movingDirectionY,\n  } = scroll\n  let ret = false\n\n  const outOfLeftBoundary =\n    x >= minScrollX && movingDirectionX === Direction.Negative\n  const outOfRightBoundary =\n    x <= maxScrollX && movingDirectionX === Direction.Positive\n  const outOfTopBoundary =\n    y >= minScrollY && movingDirectionY === Direction.Negative\n  const outOfBottomBoundary =\n    y <= maxScrollY && movingDirectionY === Direction.Positive\n\n  if (hasVerticalScroll) {\n    ret = outOfTopBoundary || outOfBottomBoundary\n  } else if (hasHorizontalScroll) {\n    ret = outOfLeftBoundary || outOfRightBoundary\n  }\n\n  return ret\n}\n\nconst isResettingPosition = (scroll: BScroll): boolean => {\n  const {\n    hasHorizontalScroll,\n    hasVerticalScroll,\n    x,\n    y,\n    minScrollX,\n    maxScrollX,\n    minScrollY,\n    maxScrollY,\n  } = scroll\n  let ret = false\n\n  const outOfLeftBoundary = x > minScrollX\n  const outOfRightBoundary = x < maxScrollX\n  const outOfTopBoundary = y > minScrollY\n  const outOfBottomBoundary = y < maxScrollY\n\n  if (hasVerticalScroll) {\n    ret = outOfTopBoundary || outOfBottomBoundary\n  } else if (hasHorizontalScroll) {\n    ret = outOfLeftBoundary || outOfRightBoundary\n  }\n\n  return ret\n}\n\nconst resetPositionHandler = (scroll: BScroll) => {\n  scroll.scroller.reflow()\n  scroll.resetPosition(0 /* Immediately */)\n}\n\nconst calculateDistance = (\n  childNode: HTMLElement,\n  parentNode: HTMLElement\n): number => {\n  let distance = 0\n  let parent = childNode.parentNode\n  while (parent && parent !== parentNode) {\n    distance++\n    parent = parent.parentNode\n  }\n  return distance\n}\n\nexport default class NestedScroll implements PluginAPI {\n  static pluginName = 'nestedScroll'\n  static instancesMap: NestedScrollInstancesMap = {}\n  store: BScrollFamily[]\n  options: NestedScrollConfig\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  constructor(scroll: BScroll) {\n    const groupId = this.handleOptions(scroll)\n    let instance = NestedScroll.instancesMap[groupId]\n    if (!instance) {\n      instance = NestedScroll.instancesMap[groupId] = this\n      instance.store = []\n      instance.hooksFn = []\n    }\n\n    instance.init(scroll)\n\n    return instance\n  }\n\n  static getAllNestedScrolls(): NestedScroll[] {\n    const instancesMap = NestedScroll.instancesMap\n    return Object.keys(instancesMap).map((key) => instancesMap[key])\n  }\n\n  static purgeAllNestedScrolls() {\n    const nestedScrolls = NestedScroll.getAllNestedScrolls()\n    nestedScrolls.forEach((ns) => ns.purgeNestedScroll())\n  }\n\n  private handleOptions(scroll: BScroll): number | string {\n    const userOptions = (scroll.options.nestedScroll === true\n      ? {}\n      : scroll.options.nestedScroll) as NestedScrollConfig\n    const defaultOptions: NestedScrollConfig = {\n      groupId: DEFAUL_GROUP_ID,\n    }\n    this.options = extend(defaultOptions, userOptions)\n\n    const groupIdType = typeof this.options.groupId\n    if (groupIdType !== 'string' && groupIdType !== 'number') {\n      warn('groupId must be string or number for NestedScroll plugin')\n    }\n\n    return this.options.groupId\n  }\n\n  private init(scroll: BScroll) {\n    scroll.proxy(propertiesConfig)\n    this.addBScroll(scroll)\n    this.buildBScrollGraph()\n    this.analyzeBScrollGraph()\n    this.ensureEventInvokeSequence()\n    this.handleHooks(scroll)\n  }\n\n  private handleHooks(scroll: BScroll) {\n    this.registerHooks(scroll.hooks, scroll.hooks.eventTypes.destroy, () => {\n      this.deleteScroll(scroll)\n    })\n  }\n\n  deleteScroll(scroll: BScroll) {\n    const wrapper = scroll.wrapper as MountedBScrollHTMLElement\n    wrapper.isBScrollContainer = undefined\n    const store = this.store\n    const hooksFn = this.hooksFn\n    const i = findIndex(store, (bscrollFamily) => {\n      return bscrollFamily.selfScroll === scroll\n    })\n    if (i > -1) {\n      const bscrollFamily = store[i]\n      bscrollFamily.purge()\n      store.splice(i, 1)\n    }\n    const k = findIndex(hooksFn, ([hooks]) => {\n      return hooks === scroll.hooks\n    })\n    if (k > -1) {\n      const [hooks, eventType, handler] = hooksFn[k]\n      hooks.off(eventType, handler)\n      hooksFn.splice(k, 1)\n    }\n  }\n\n  addBScroll(scroll: BScroll) {\n    this.store.push(BScrollFamily.create(scroll))\n  }\n\n  private buildBScrollGraph() {\n    const store = this.store\n\n    let bf1: BScrollFamily\n    let bf2: BScrollFamily\n    let wrapper1: MountedBScrollHTMLElement\n    let wrapper2: MountedBScrollHTMLElement\n    let len = this.store.length\n\n    // build graph\n    for (let i = 0; i < len; i++) {\n      bf1 = store[i]\n      wrapper1 = bf1.selfScroll.wrapper\n      for (let j = 0; j < len; j++) {\n        bf2 = store[j]\n        wrapper2 = bf2.selfScroll.wrapper\n\n        // same bs\n        if (bf1 === bf2) continue\n\n        if (!wrapper1.contains(wrapper2)) continue\n\n        // bs1 contains bs2\n        const distance = calculateDistance(wrapper2, wrapper1)\n        if (!bf1.hasDescendants(bf2)) {\n          bf1.addDescendant(bf2, distance)\n        }\n\n        if (!bf2.hasAncestors(bf1)) {\n          bf2.addAncestor(bf1, distance)\n        }\n      }\n    }\n  }\n\n  private analyzeBScrollGraph() {\n    this.store.forEach((bscrollFamily) => {\n      if (bscrollFamily.analyzed) {\n        return\n      }\n\n      const {\n        ancestors,\n        descendants,\n        selfScroll: currentScroll,\n      } = bscrollFamily\n\n      const beforeScrollStartHandler = () => {\n        // always get the latest scroll\n        const ancestorScrolls = ancestors.map(\n          ([bscrollFamily]) => bscrollFamily.selfScroll\n        )\n        const descendantScrolls = descendants.map(\n          ([bscrollFamily]) => bscrollFamily.selfScroll\n        )\n        forceScrollStopHandler([...ancestorScrolls, ...descendantScrolls])\n\n        if (isResettingPosition(currentScroll)) {\n          resetPositionHandler(currentScroll)\n        }\n        syncTouchstartData(ancestorScrolls)\n        disableScrollHander(ancestorScrolls, currentScroll)\n      }\n\n      const touchEndHandler = () => {\n        const ancestorScrolls = ancestors.map(\n          ([bscrollFamily]) => bscrollFamily.selfScroll\n        )\n        const descendantScrolls = descendants.map(\n          ([bscrollFamily]) => bscrollFamily.selfScroll\n        )\n        enableScrollHander([...ancestorScrolls, ...descendantScrolls])\n      }\n\n      bscrollFamily.registerHooks(\n        currentScroll,\n        currentScroll.eventTypes.beforeScrollStart,\n        beforeScrollStartHandler\n      )\n      bscrollFamily.registerHooks(\n        currentScroll,\n        currentScroll.eventTypes.touchEnd,\n        touchEndHandler\n      )\n\n      const selfActionsHooks = currentScroll.scroller.actions.hooks\n      bscrollFamily.registerHooks(\n        selfActionsHooks,\n        selfActionsHooks.eventTypes.detectMovingDirection,\n        () => {\n          const ancestorScrolls = ancestors.map(\n            ([bscrollFamily]) => bscrollFamily.selfScroll\n          )\n          const parentScroll = ancestorScrolls[0]\n          const otherAncestorScrolls = ancestorScrolls.slice(1)\n          const contentMoved = currentScroll.scroller.actions.contentMoved\n          const isTopScroll = ancestorScrolls.length === 0\n          if (contentMoved) {\n            disableScrollHander(ancestorScrolls, currentScroll)\n          } else if (!isTopScroll) {\n            if (isOutOfBoundary(currentScroll)) {\n              disableScrollHander([currentScroll], currentScroll)\n              if (parentScroll) {\n                enableScrollHander([parentScroll])\n              }\n              disableScrollHander(otherAncestorScrolls, currentScroll)\n              return true\n            }\n          }\n        }\n      )\n      bscrollFamily.setAnalyzed(true)\n    })\n  }\n  // make sure touchmove|touchend invoke from child to parent\n  private ensureEventInvokeSequence() {\n    const copied = this.store.slice()\n    const sequencedScroll = copied.sort((a, b) => {\n      return a.descendants.length - b.descendants.length\n    })\n    sequencedScroll.forEach((bscrollFamily) => {\n      const scroll = bscrollFamily.selfScroll\n      scroll.scroller.actionsHandler.rebindDOMEvents()\n    })\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  purgeNestedScroll() {\n    const groupId = this.options.groupId\n    this.store.forEach((bscrollFamily) => {\n      bscrollFamily.purge()\n    })\n    this.store = []\n\n    this.hooksFn.forEach(([hooks, eventType, handler]) => {\n      hooks.off(eventType, handler)\n    })\n    this.hooksFn = []\n\n    delete NestedScroll.instancesMap[groupId]\n  }\n}\n"
  },
  {
    "path": "packages/nested-scroll/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.nestedScroll'\n\nconst propertiesMap = [\n  {\n    key: 'purgeNestedScroll',\n    name: 'purgeNestedScroll',\n  },\n]\n\nexport default propertiesMap.map((item) => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`,\n  }\n})\n"
  },
  {
    "path": "packages/observe-dom/README.md",
    "content": "# @better-scroll/observe-dom\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/observe-dom/README_zh-CN.md)\n\nrecaculating BetterScroll's scrollHeight or scrollWidth by `MutationObserver`, with this, you don't care when BetterScroll's scrollHeight or scrollWidth have changed. Plugin has done it for you.\n\n> **if current browser does not surpport `MutationObserver`, it will fallback to `setTimeout` in recursion**\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport ObserveDom from '@better-scroll/observe-dom'\nBScroll.use(ObserveDom)\n\nconst bs = new BScroll('.wrapper', {\n  observeDOM: true\n})\n```\n"
  },
  {
    "path": "packages/observe-dom/README_zh-CN.md",
    "content": "# @better-scroll/observe-dom\n\n动态计算 BetterScroll 的可滚动高度或者宽度，你并不需要自己在高度或者宽度发生变化的时候，手动调用 `refresh()` 方法。插件通过 `MutationObserver` 帮你完成了。\n\n> **如果当前你的浏览器不支持 `MutationObserver`，会降级使用 `setTimeout`。**\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport ObserveDom from '@better-scroll/observe-dom'\nBScroll.use(ObserveDom)\n\nconst bs = new BScroll('.wrapper', {\n  observeDOM: true\n})\n```\n"
  },
  {
    "path": "packages/observe-dom/package.json",
    "content": "{\n  \"name\": \"@better-scroll/observe-dom\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Dynamic recalculating container's size for BetterScroll\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/observe-dom.min.js\",\n  \"module\": \"dist/observe-dom.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/observe-dom\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/observe-dom/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport ObserveDOM from '../index'\nimport {\n  mockDomOffset,\n  CustomHTMLDivElement,\n} from '@better-scroll/core/src/__tests__/__utils__/layout'\n\njest.mock('@better-scroll/core')\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\nconst createObserveDOMElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\ndescribe('observe dom', () => {\n  let scroll: BScroll\n  let observeDOM: ObserveDOM\n  let mockMutationObserver: jest.Mock\n  let mockObserve: jest.Mock\n  let mockDisconnect: jest.Mock\n  let triggerMutation: Function\n\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n\n  beforeEach(() => {\n    mockObserve = jest.fn()\n    mockDisconnect = jest.fn()\n    mockMutationObserver = jest.fn().mockImplementation((cb) => {\n      triggerMutation = (mutations: any, timer: number) => {\n        cb(mutations, timer)\n      }\n      return {\n        observe: mockObserve,\n        disconnect: mockDisconnect,\n      }\n    })\n    Object.defineProperty(window, 'MutationObserver', {\n      get: function () {\n        return mockMutationObserver\n      },\n    })\n\n    const { wrapper } = createObserveDOMElements()\n    scroll = new BScroll(wrapper, {})\n    observeDOM = new ObserveDOM(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n    jest.clearAllTimers()\n  })\n\n  it('observe without MutationObserver', () => {\n    // set MutationObserver to undefined\n    Object.defineProperty(window, 'MutationObserver', {\n      get: function () {\n        return undefined\n      },\n    })\n\n    observeDOM = new ObserveDOM(scroll)\n\n    expect(observeDOM.observer).toBeFalsy()\n\n    mockDomOffset(scroll.scroller.content as CustomHTMLDivElement, {\n      width: 400,\n    })\n    jest.advanceTimersByTime(1000)\n    expect(scroll.refresh).toBeCalled()\n\n    // destroy\n    scroll.hooks.trigger('destroy')\n    mockDomOffset(scroll.scroller.content as CustomHTMLDivElement, {\n      width: 500,\n    })\n    jest.advanceTimersByTime(1000)\n    expect(scroll.refresh).toHaveBeenCalledTimes(1)\n  })\n\n  it('observe with MutationObserver', () => {\n    expect(scroll.hooks.events.enable.length).toBe(1)\n    expect(scroll.hooks.events.disable.length).toBe(1)\n    expect(scroll.hooks.events.destroy.length).toBe(1)\n    expect(mockObserve).toHaveBeenCalledWith(scroll.scroller.content, {\n      attributes: true,\n      childList: true,\n      subtree: true,\n    })\n\n    // init can't be call again when it is observing\n    const init = jest.spyOn(observeDOM, 'init')\n    scroll.hooks.trigger(scroll.hooks.eventTypes.enabled)\n    expect(init).not.toHaveBeenCalled()\n    init.mockReset()\n\n    addProperties(scroll.scroller.scrollBehaviorX, {\n      currentPos: -12,\n      minScrollPos: 0,\n      maxScrollPos: -100,\n    })\n\n    addProperties(scroll.scroller.scrollBehaviorY, {\n      currentPos: -12,\n      minScrollPos: 0,\n      maxScrollPos: -100,\n    })\n\n    addProperties(scroll.scroller.animater, {\n      pending: false,\n    })\n\n    triggerMutation(\n      [\n        {\n          type: 'test',\n        },\n      ],\n      300\n    )\n    expect(scroll.refresh).toBeCalledTimes(1)\n\n    // attributes change & target is scroller.content\n    triggerMutation(\n      [\n        {\n          type: 'attributes',\n          target: scroll.scroller.content,\n        },\n      ],\n      300\n    )\n    expect(scroll.refresh).toBeCalledTimes(1)\n\n    // attributes change & target is not scroller.content\n    triggerMutation(\n      [\n        {\n          type: 'attributes',\n          target: '',\n        },\n      ],\n      300\n    )\n    expect(scroll.refresh).toBeCalledTimes(1)\n    jest.advanceTimersByTime(61)\n    expect(scroll.refresh).toBeCalledTimes(2)\n\n    // pedding\n    addProperties(scroll.scroller.animater, {\n      pending: true,\n    })\n    triggerMutation(\n      [\n        {\n          type: 'test',\n        },\n      ],\n      300\n    )\n    expect(scroll.refresh).toBeCalledTimes(2)\n\n    // out of boundary\n    addProperties(scroll.scroller.scrollBehaviorY, {\n      currentPos: -12,\n      minScrollPos: 0,\n      maxScrollPos: -10,\n    })\n    triggerMutation(\n      [\n        {\n          type: 'test',\n        },\n      ],\n      300\n    )\n    expect(scroll.refresh).toBeCalledTimes(2)\n\n    // destroy\n    scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n    expect(mockDisconnect).toBeCalled()\n    expect(scroll.hooks.events.enable.length).toBe(0)\n    expect(scroll.hooks.events.disable.length).toBe(0)\n    expect(scroll.hooks.events.destroy.length).toBe(0)\n  })\n\n  it('enable/disable', () => {\n    scroll.hooks.trigger(scroll.hooks.eventTypes.disable)\n    expect(mockDisconnect).toBeCalled()\n\n    scroll.hooks.trigger(scroll.hooks.eventTypes.enable)\n    expect(mockObserve).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/observe-dom/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { getRect, EventEmitter } from '@better-scroll/shared-utils'\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    observeDOM?: true\n  }\n}\nexport default class ObserveDOM {\n  static pluginName = 'observeDOM'\n  observer: MutationObserver\n  private stopObserver: boolean = false\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n  init() {\n    this.handleMutationObserver()\n    this.handleHooks()\n  }\n  private handleMutationObserver() {\n    if (typeof MutationObserver !== 'undefined') {\n      let timer = 0\n      this.observer = new MutationObserver((mutations) => {\n        this.mutationObserverHandler(mutations, timer)\n      })\n      this.startObserve(this.observer)\n    } else {\n      this.checkDOMUpdate()\n    }\n  }\n  private handleHooks() {\n    this.hooksFn = []\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.contentChanged,\n      () => {\n        this.stopObserve()\n        // launch a new mutationObserver\n        this.handleMutationObserver()\n      }\n    )\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.enable,\n      () => {\n        if (this.stopObserver) {\n          this.handleMutationObserver()\n        }\n      }\n    )\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.disable,\n      () => {\n        this.stopObserve()\n      }\n    )\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.destroy,\n      () => {\n        this.destroy()\n      }\n    )\n  }\n  private mutationObserverHandler(mutations: MutationRecord[], timer: number) {\n    if (this.shouldNotRefresh()) {\n      return\n    }\n    let immediateRefresh = false\n    let deferredRefresh = false\n    for (let i = 0; i < mutations.length; i++) {\n      const mutation = mutations[i]\n      if (mutation.type !== 'attributes') {\n        immediateRefresh = true\n        break\n      } else {\n        if (mutation.target !== this.scroll.scroller.content) {\n          deferredRefresh = true\n          break\n        }\n      }\n    }\n\n    if (immediateRefresh) {\n      this.scroll.refresh()\n    } else if (deferredRefresh) {\n      // attributes changes too often\n      clearTimeout(timer)\n      timer = window.setTimeout(() => {\n        if (!this.shouldNotRefresh()) {\n          this.scroll.refresh()\n        }\n      }, 60)\n    }\n  }\n\n  private startObserve(observer: MutationObserver) {\n    const config = {\n      attributes: true,\n      childList: true,\n      subtree: true,\n    }\n    observer.observe(this.scroll.scroller.content, config)\n  }\n\n  private shouldNotRefresh() {\n    const { scroller } = this.scroll\n    const { scrollBehaviorX, scrollBehaviorY } = scroller\n    let outsideBoundaries =\n      scrollBehaviorX.currentPos > scrollBehaviorX.minScrollPos ||\n      scrollBehaviorX.currentPos < scrollBehaviorX.maxScrollPos ||\n      scrollBehaviorY.currentPos > scrollBehaviorY.minScrollPos ||\n      scrollBehaviorY.currentPos < scrollBehaviorY.maxScrollPos\n    return scroller.animater.pending || outsideBoundaries\n  }\n\n  private checkDOMUpdate() {\n    const content = this.scroll.scroller.content\n    let contentRect = getRect(content)\n    let oldWidth = contentRect.width\n    let oldHeight = contentRect.height\n\n    const check = () => {\n      if (this.stopObserver) {\n        return\n      }\n      contentRect = getRect(content)\n      let newWidth = contentRect.width\n      let newHeight = contentRect.height\n\n      if (oldWidth !== newWidth || oldHeight !== newHeight) {\n        this.scroll.refresh()\n      }\n      oldWidth = newWidth\n      oldHeight = newHeight\n\n      next()\n    }\n\n    const next = () => {\n      setTimeout(() => {\n        check()\n      }, 1000)\n    }\n\n    next()\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private stopObserve() {\n    this.stopObserver = true\n    if (this.observer) {\n      this.observer.disconnect()\n    }\n  }\n\n  destroy() {\n    this.stopObserve()\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n    this.hooksFn.length = 0\n  }\n}\n"
  },
  {
    "path": "packages/observe-image/README.md",
    "content": "# @better-scroll/observe-image\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/observe-image/README_zh-CN.md)\n\nwhen detecting images is loaded or failed to load, auto refresh BetterScroll's size.\n\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport ObserveImage from '@better-scroll/observe-image'\nBScroll.use(ObserveImage)\n\nconst bs = new BScroll('.wrapper', {\n  observeImage: true\n})\n```\n"
  },
  {
    "path": "packages/observe-image/README_zh-CN.md",
    "content": "# @better-scroll/observe-image\n\n当探测到 BetterScroll content DOM 的子元素是 image 标签，并且加载成功或者失败的时候，自动调用 `refresh()` 方法重新计算可滚动的高度或者宽度，适用于当滚动区域含有高度或者宽度不固定的场景。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport ObserveImage from '@better-scroll/observe-image'\nBScroll.use(ObserveImage)\n\nconst bs = new BScroll('.wrapper', {\n  observeImage: true\n})\n```\n"
  },
  {
    "path": "packages/observe-image/package.json",
    "content": "{\n  \"name\": \"@better-scroll/observe-image\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Observe image loading for BetterScroll\",\n  \"author\": \"theniceangel <theniceangel@163.com>\",\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll/tree/master/packages/observe-image#readme\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"umage\"\n  ],\n  \"main\": \"dist/observe-image.min.js\",\n  \"module\": \"dist/observe-image.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/observe-image\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  }\n}\n"
  },
  {
    "path": "packages/observe-image/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport ObserveImage from '../index'\nimport { createEvent } from '@better-scroll/core/src/__tests__/__utils__/event'\n\njest.mock('@better-scroll/core')\n\nconst createObserveImageElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  return { wrapper, content }\n}\n\ndescribe('observe image', () => {\n  const { wrapper, content } = createObserveImageElements()\n  let scroll: BScroll\n  let observeImage: ObserveImage\n\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n\n  beforeEach(() => {\n    scroll = new BScroll(wrapper, {})\n    observeImage = new ObserveImage(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n    jest.clearAllTimers()\n  })\n\n  it('should capture image load or error event', () => {\n    let img = document.createElement('img')\n    let loadEvent = createEvent('Event', 'load')\n    content.appendChild(img)\n    img.dispatchEvent(loadEvent)\n    jest.advanceTimersByTime(151)\n    expect(scroll.refresh).toBeCalled()\n  })\n\n  it('should trigger bs.refresh in a tick when debounceTime is 0', () => {\n    observeImage.options.debounceTime = 0\n    let img = document.createElement('img')\n    let loadEvent = createEvent('Event', 'load')\n    content.appendChild(img)\n    img.dispatchEvent(loadEvent)\n    expect(scroll.refresh).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/observe-image/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { EventRegister, extend } from '@better-scroll/shared-utils'\n\nexport type ObserveImageOptions = Partial<ObserveImageConfig> | true\n\nexport interface ObserveImageConfig {\n  debounceTime: number\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    observeImage?: ObserveImageOptions\n  }\n}\n\nconst isImageTag = (el: HTMLElement) => {\n  return el.tagName.toLowerCase() === 'img'\n}\n\nexport default class ObserveImage {\n  static pluginName = 'observeImage'\n  imageLoadEventRegister: EventRegister\n  imageErrorEventRegister: EventRegister\n  refreshTimer: number = 0\n  options: ObserveImageConfig\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n  init() {\n    this.handleOptions(this.scroll.options.observeImage)\n    this.bindEventsToWrapper()\n  }\n\n  private handleOptions(userOptions: ObserveImageOptions = {}) {\n    userOptions = (userOptions === true ? {} : userOptions) as Partial<\n      ObserveImageConfig\n    >\n    const defaultOptions: ObserveImageConfig = {\n      debounceTime: 100, // ms\n    }\n    this.options = extend(defaultOptions, userOptions)\n  }\n\n  private bindEventsToWrapper() {\n    const wrapper = this.scroll.scroller.wrapper\n    this.imageLoadEventRegister = new EventRegister(wrapper, [\n      {\n        name: 'load',\n        handler: this.load.bind(this),\n        capture: true,\n      },\n    ])\n    this.imageErrorEventRegister = new EventRegister(wrapper, [\n      {\n        name: 'error',\n        handler: this.load.bind(this),\n        capture: true,\n      },\n    ])\n  }\n\n  private load(e: Event) {\n    const target = e.target as HTMLElement\n    const debounceTime = this.options.debounceTime\n    if (target && isImageTag(target)) {\n      if (debounceTime === 0) {\n        this.scroll.refresh()\n      } else {\n        clearTimeout(this.refreshTimer)\n        this.refreshTimer = window.setTimeout(() => {\n          this.scroll.refresh()\n        }, this.options.debounceTime)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/pull-down/README.md",
    "content": "# @better-scroll/pull-down\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/pull-down/README_zh-CN.md)\n\nThe ability to inject a pull-down refresh for BetterScroll.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pulldown from '@better-scroll/pull-down'\nBScroll.use(Pulldown)\n\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: true\n})\n```\n"
  },
  {
    "path": "packages/pull-down/README_zh-CN.md",
    "content": "# @better-scroll/pull-down\n\n为 BetterScroll 注入下拉刷新的能力。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pulldown from '@better-scroll/pull-down'\nBScroll.use(Pulldown)\n\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: true\n})\n```\n"
  },
  {
    "path": "packages/pull-down/package.json",
    "content": "{\n  \"name\": \"@better-scroll/pull-down\",\n  \"version\": \"2.5.1\",\n  \"description\": \"pull down to refresh, behave likes App list refreshing\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/pull-down.min.js\",\n  \"module\": \"dist/pull-down.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"pull-down\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/pull-down\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/pull-down/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { Probe } from '@better-scroll/shared-utils'\nimport { ease } from '@better-scroll/shared-utils/src/ease'\njest.mock('@better-scroll/core')\n\nimport PullDown from '../index'\n\nconst createPullDownElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\ndescribe('pull down tests', () => {\n  let scroll: BScroll\n  let pullDown: PullDown\n  beforeEach(() => {\n    // create DOM\n    const { wrapper } = createPullDownElements()\n    scroll = new BScroll(wrapper, {})\n    pullDown = new PullDown(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to BScroll instance', () => {\n    new PullDown(scroll)\n\n    expect(scroll.proxy).toBeCalledWith([\n      {\n        key: 'finishPullDown',\n        sourceKey: 'plugins.pullDownRefresh.finishPullDown',\n      },\n      {\n        key: 'openPullDown',\n        sourceKey: 'plugins.pullDownRefresh.openPullDown',\n      },\n      {\n        key: 'closePullDown',\n        sourceKey: 'plugins.pullDownRefresh.closePullDown',\n      },\n      {\n        key: 'autoPullDownRefresh',\n        sourceKey: 'plugins.pullDownRefresh.autoPullDownRefresh',\n      },\n    ])\n  })\n\n  it('should handle default options and user options', () => {\n    // case 1\n    scroll.options.pullDownRefresh = true\n    pullDown = new PullDown(scroll)\n\n    expect(pullDown.options).toMatchObject({\n      threshold: 90,\n      stop: 40,\n    })\n\n    // case 2\n    scroll.options.pullDownRefresh = {\n      threshold: 100,\n      stop: 50,\n    }\n    pullDown = new PullDown(scroll)\n\n    expect(pullDown.options).toMatchObject({\n      threshold: 100,\n      stop: 50,\n    })\n\n    expect(scroll.options.probeType).toBe(Probe.Realtime)\n  })\n\n  it('should cache originalMinScrollY', () => {\n    expect(pullDown.cachedOriginanMinScrollY).toBe(0)\n    expect(pullDown.currentMinScrollY).toBe(0)\n  })\n\n  it('should modify minScrollY when necessary', () => {\n    pullDown.currentMinScrollY = 50\n    const scrollBehaviorY = scroll.scroller.scrollBehaviorY\n    let boundary = {\n      minScrollPos: 0,\n      maxScrollPos: 20,\n    }\n    scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      boundary\n    )\n    expect(boundary).toMatchObject({\n      minScrollPos: 50,\n      maxScrollPos: -1,\n    })\n  })\n\n  it('should checkPullDown', () => {\n    const mockFn = jest.fn()\n    scroll.on(scroll.eventTypes.pullingDown, mockFn)\n\n    scroll.trigger(scroll.eventTypes.scrollStart)\n    // simulate pullUp action\n    scroll.y = -100\n    scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.end)\n    expect(mockFn).toHaveBeenCalledTimes(0)\n\n    // simulate pullDown action\n    scroll.y = 100\n\n    scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.end)\n    expect(mockFn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should checkLocationOfThresholdBoundary', () => {\n    const enterThresholdFn = jest.fn()\n    const leaveThresholdFn = jest.fn()\n    scroll.on(scroll.eventTypes.enterThreshold, enterThresholdFn)\n    scroll.on(scroll.eventTypes.leaveThreshold, leaveThresholdFn)\n    scroll.trigger(scroll.eventTypes.scrollStart)\n\n    // enter threshold boundary\n    scroll.y = 20\n    scroll.trigger(scroll.eventTypes.scroll)\n\n    // leave threshold boundary\n    scroll.y = 100\n    scroll.trigger(scroll.eventTypes.scroll)\n\n    expect(enterThresholdFn).toHaveBeenCalledTimes(1)\n    expect(leaveThresholdFn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should trigger pullingDown once', () => {\n    const mockFn = jest.fn()\n    scroll.on(scroll.eventTypes.pullingDown, mockFn)\n    // when\n    scroll.y = 100\n    scroll.trigger(scroll.eventTypes.scrollStart)\n    scroll.scroller.hooks.trigger('end')\n    scroll.scroller.hooks.trigger('end')\n    // then\n    expect(mockFn).toBeCalledTimes(1)\n  })\n\n  it('should stop at correct position', () => {\n    // when\n    scroll.y = 100\n    scroll.scroller.hooks.trigger('end')\n    expect(scroll.scrollTo).toHaveBeenCalledWith(\n      0,\n      40,\n      scroll.options.bounceTime,\n      ease.bounce\n    )\n  })\n\n  it('should work well when call finishPullDown()', () => {\n    pullDown.pulling = 2\n    pullDown.finishPullDown()\n\n    expect(pullDown.pulling).toBe(0)\n    expect(scroll.scroller.scrollBehaviorY.computeBoundary).toBeCalled()\n    expect(scroll.resetPosition).toBeCalled()\n  })\n\n  it('should work well when call closePullDown()', () => {\n    pullDown.closePullDown()\n\n    expect(pullDown.watching).toBe(false)\n    expect(scroll.scroller.hooks.events.end.length).toBe(0)\n  })\n\n  it('should work well when call openPullDown()', () => {\n    pullDown.closePullDown()\n\n    expect(pullDown.watching).toBe(false)\n    expect(pullDown.options).toMatchObject({\n      threshold: 90,\n      stop: 40,\n    })\n\n    // modify options\n    pullDown.openPullDown({\n      threshold: 200,\n      stop: 80,\n    })\n\n    expect(pullDown.options).toMatchObject({\n      threshold: 200,\n      stop: 80,\n    })\n    expect(pullDown.watching).toBe(true)\n  })\n\n  it('should work well when call autoPullDownRefresh()', () => {\n    const mockFn = jest.fn()\n    scroll.on(scroll.eventTypes.pullingDown, mockFn)\n    pullDown.autoPullDownRefresh()\n    expect(pullDown.watching).toBe(true)\n    expect(pullDown.currentMinScrollY).toBe(40)\n    expect(scroll.scroller.scrollBehaviorY.computeBoundary).toBeCalled()\n    expect(scroll.scrollTo).toHaveBeenCalledTimes(2)\n    expect(scroll.scrollTo).toHaveBeenLastCalledWith(\n      0,\n      40,\n      scroll.options.bounceTime,\n      ease.bounce\n    )\n\n    // closePullDown, and autoPullDownRefresh will not work\n    pullDown.closePullDown()\n    pullDown.autoPullDownRefresh()\n    expect(scroll.scrollTo).toBeCalledTimes(2)\n  })\n\n  it('should call finishPullDown when content DOM changed', () => {\n    // simulate pullDown action\n    pullDown.pulling = 2\n\n    scroll.hooks.trigger(scroll.hooks.eventTypes.contentChanged)\n    expect(scroll.scroller.scrollBehaviorY.computeBoundary).toBeCalled()\n    expect(scroll.resetPosition).toBeCalledWith(800, ease.bounce)\n  })\n\n  it('should work well when integrating with mousewheel', () => {\n    const options = {} as any\n    scroll.trigger(scroll.eventTypes.alterOptions, options)\n\n    expect(options.discreteTime).toBe(300)\n    expect(options.easeTime).toBe(350)\n\n    const mockFn = jest.fn()\n    scroll.scroller.hooks.on(scroll.scroller.hooks.eventTypes.end, mockFn)\n\n    scroll.trigger(scroll.eventTypes.mousewheelEnd)\n    expect(mockFn).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/pull-down/src/index.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\nimport { MouseWheelConfig } from '@better-scroll/mouse-wheel'\nimport { ease, extend, EventEmitter, Probe } from '@better-scroll/shared-utils'\nimport propertiesConfig from './propertiesConfig'\n\nexport type PullDownRefreshOptions = Partial<PullDownRefreshConfig> | true\n\n// pulldownRefresh phase will go through:\n// DEFAULT -> MOVING -> FETCHING\n// or\n// DEFAULT -> MOVING\nconst enum PullDownPhase {\n  DEFAULT,\n  MOVING,\n  FETCHING,\n}\n\nconst enum ThresholdBoundary {\n  DEFAULT,\n  INSIDE,\n  OUTSIDE,\n}\n\nexport interface PullDownRefreshConfig {\n  threshold: number\n  stop: number\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    pullDownRefresh?: PullDownRefreshOptions\n  }\n  interface CustomAPI {\n    pullDownRefresh: PluginAPI\n  }\n}\n\ninterface PluginAPI {\n  finishPullDown(): void\n  openPullDown(config?: PullDownRefreshOptions): void\n  closePullDown(): void\n  autoPullDownRefresh(): void\n}\n\nconst PULLING_DOWN_EVENT = 'pullingDown'\nconst ENTER_THRESHOLD_EVENT = 'enterThreshold'\nconst LEAVE_THRESHOLD_EVENT = 'leaveThreshold'\n\nexport default class PullDown implements PluginAPI {\n  static pluginName = 'pullDownRefresh'\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  pulling: PullDownPhase = PullDownPhase.DEFAULT\n  thresholdBoundary: ThresholdBoundary = ThresholdBoundary.DEFAULT\n  watching: boolean\n  options: PullDownRefreshConfig\n  cachedOriginanMinScrollY: number\n  currentMinScrollY: number\n\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  private setPulling(status: PullDownPhase) {\n    this.pulling = status\n  }\n\n  private setThresholdBoundary(boundary: ThresholdBoundary) {\n    this.thresholdBoundary = boundary\n  }\n\n  private init() {\n    this.handleBScroll()\n\n    this.handleOptions(this.scroll.options.pullDownRefresh)\n\n    this.handleHooks()\n\n    this.watch()\n  }\n\n  private handleBScroll() {\n    this.scroll.registerType([\n      PULLING_DOWN_EVENT,\n      ENTER_THRESHOLD_EVENT,\n      LEAVE_THRESHOLD_EVENT,\n    ])\n\n    this.scroll.proxy(propertiesConfig)\n  }\n\n  private handleOptions(userOptions: PullDownRefreshOptions = {}) {\n    userOptions = (\n      userOptions === true ? {} : userOptions\n    ) as Partial<PullDownRefreshConfig>\n    const defaultOptions: PullDownRefreshConfig = {\n      threshold: 90,\n      stop: 40,\n    }\n    this.options = extend(defaultOptions, userOptions)\n\n    this.scroll.options.probeType = Probe.Realtime\n  }\n\n  private handleHooks() {\n    this.hooksFn = []\n    const scroller = this.scroll.scroller\n    const scrollBehaviorY = scroller.scrollBehaviorY\n    this.currentMinScrollY = this.cachedOriginanMinScrollY =\n      scrollBehaviorY.minScrollPos\n\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.contentChanged,\n      () => {\n        this.finishPullDown()\n      }\n    )\n\n    this.registerHooks(\n      scrollBehaviorY.hooks,\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        // content is smaller than wrapper\n        if (boundary.maxScrollPos > 0) {\n          // allow scrolling when content is not full of wrapper\n          boundary.maxScrollPos = -1\n        }\n        boundary.minScrollPos = this.currentMinScrollY\n      }\n    )\n\n    // integrate with mousewheel\n    if (this.hasMouseWheelPlugin()) {\n      this.registerHooks(\n        this.scroll,\n        this.scroll.eventTypes.alterOptions,\n        (mouseWheelOptions: MouseWheelConfig) => {\n          const SANE_DISCRETE_TIME = 300\n          const SANE_EASE_TIME = 350\n          mouseWheelOptions.discreteTime = SANE_DISCRETE_TIME\n          // easeTime > discreteTime ensure goInto checkPullDown function\n          mouseWheelOptions.easeTime = SANE_EASE_TIME\n        }\n      )\n\n      this.registerHooks(\n        this.scroll,\n        this.scroll.eventTypes.mousewheelEnd,\n        () => {\n          // mouseWheel need trigger checkPullDown manually\n          scroller.hooks.trigger(scroller.hooks.eventTypes.end)\n        }\n      )\n    }\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private hasMouseWheelPlugin() {\n    return !!this.scroll.eventTypes.alterOptions\n  }\n\n  private watch() {\n    const scroller = this.scroll.scroller\n    this.watching = true\n    this.registerHooks(\n      scroller.hooks,\n      scroller.hooks.eventTypes.end,\n      this.checkPullDown\n    )\n\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.scrollStart,\n      this.resetStateBeforeScrollStart\n    )\n\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.scroll,\n      this.checkLocationOfThresholdBoundary\n    )\n\n    if (this.hasMouseWheelPlugin()) {\n      this.registerHooks(\n        this.scroll,\n        this.scroll.eventTypes.mousewheelStart,\n        this.resetStateBeforeScrollStart\n      )\n    }\n  }\n\n  private resetStateBeforeScrollStart() {\n    // current fetching pulldownRefresh has ended\n    if (!this.isFetchingStatus()) {\n      this.setPulling(PullDownPhase.MOVING)\n      this.setThresholdBoundary(ThresholdBoundary.DEFAULT)\n    }\n  }\n\n  private checkLocationOfThresholdBoundary() {\n    // pulldownRefresh is in the phase of Moving\n    if (this.pulling === PullDownPhase.MOVING) {\n      const scroll = this.scroll\n      // enter threshold boundary\n      const enteredThresholdBoundary =\n        this.thresholdBoundary !== ThresholdBoundary.INSIDE &&\n        this.locateInsideThresholdBoundary()\n      // leave threshold boundary\n      const leftThresholdBoundary =\n        this.thresholdBoundary !== ThresholdBoundary.OUTSIDE &&\n        !this.locateInsideThresholdBoundary()\n      if (enteredThresholdBoundary) {\n        this.setThresholdBoundary(ThresholdBoundary.INSIDE)\n        scroll.trigger(ENTER_THRESHOLD_EVENT)\n      }\n      if (leftThresholdBoundary) {\n        this.setThresholdBoundary(ThresholdBoundary.OUTSIDE)\n        scroll.trigger(LEAVE_THRESHOLD_EVENT)\n      }\n    }\n  }\n\n  private locateInsideThresholdBoundary() {\n    return this.scroll.y <= this.options.threshold\n  }\n\n  private unwatch() {\n    const scroll = this.scroll\n    const scroller = scroll.scroller\n    this.watching = false\n    scroller.hooks.off(scroller.hooks.eventTypes.end, this.checkPullDown)\n    scroll.off(scroll.eventTypes.scrollStart, this.resetStateBeforeScrollStart)\n    scroll.off(scroll.eventTypes.scroll, this.checkLocationOfThresholdBoundary)\n    if (this.hasMouseWheelPlugin()) {\n      scroll.off(\n        scroll.eventTypes.mousewheelStart,\n        this.resetStateBeforeScrollStart\n      )\n    }\n  }\n\n  private checkPullDown() {\n    const { threshold, stop } = this.options\n    // check if a real pull down action\n    if (this.scroll.y < threshold) {\n      return false\n    }\n\n    if (this.pulling === PullDownPhase.MOVING) {\n      this.modifyBehaviorYBoundary(stop)\n\n      this.setPulling(PullDownPhase.FETCHING)\n\n      this.scroll.trigger(PULLING_DOWN_EVENT)\n    }\n\n    this.scroll.scrollTo(\n      this.scroll.x,\n      stop,\n      this.scroll.options.bounceTime,\n      ease.bounce\n    )\n\n    return this.isFetchingStatus()\n  }\n\n  private isFetchingStatus() {\n    return this.pulling === PullDownPhase.FETCHING\n  }\n\n  private modifyBehaviorYBoundary(stopDistance: number) {\n    const scrollBehaviorY = this.scroll.scroller.scrollBehaviorY\n    // manually modify minScrollPos for a hang animation\n    // to prevent from resetPosition\n    this.cachedOriginanMinScrollY = scrollBehaviorY.minScrollPos\n    this.currentMinScrollY = stopDistance\n    scrollBehaviorY.computeBoundary()\n  }\n\n  finishPullDown() {\n    if (this.isFetchingStatus()) {\n      const scrollBehaviorY = this.scroll.scroller.scrollBehaviorY\n      // restore minScrollY since the hang animation has ended\n      this.currentMinScrollY = this.cachedOriginanMinScrollY\n      scrollBehaviorY.computeBoundary()\n      this.setPulling(PullDownPhase.DEFAULT)\n      this.scroll.resetPosition(this.scroll.options.bounceTime, ease.bounce)\n    }\n  }\n\n  // allow 'true' type is compat for beta version implements\n  openPullDown(config: PullDownRefreshOptions = {}) {\n    this.handleOptions(config)\n    if (!this.watching) {\n      this.watch()\n    }\n  }\n\n  closePullDown() {\n    this.unwatch()\n  }\n\n  autoPullDownRefresh() {\n    const { threshold, stop } = this.options\n\n    if (this.isFetchingStatus() || !this.watching) {\n      return\n    }\n\n    this.modifyBehaviorYBoundary(stop)\n\n    this.scroll.trigger(this.scroll.eventTypes.scrollStart)\n    this.scroll.scrollTo(this.scroll.x, threshold)\n\n    this.setPulling(PullDownPhase.FETCHING)\n    this.scroll.trigger(PULLING_DOWN_EVENT)\n    this.scroll.scrollTo(\n      this.scroll.x,\n      stop,\n      this.scroll.options.bounceTime,\n      ease.bounce\n    )\n  }\n}\n"
  },
  {
    "path": "packages/pull-down/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.pullDownRefresh'\n\nconst propertiesMap = [\n  {\n    key: 'finishPullDown',\n    name: 'finishPullDown'\n  },\n  {\n    key: 'openPullDown',\n    name: 'openPullDown'\n  },\n  {\n    key: 'closePullDown',\n    name: 'closePullDown'\n  },\n  {\n    key: 'autoPullDownRefresh',\n    name: 'autoPullDownRefresh'\n  }\n]\n\nexport default propertiesMap.map(item => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`\n  }\n})\n"
  },
  {
    "path": "packages/pull-up/README.md",
    "content": "# @better-scroll/pull-up\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/pull-up/README_zh-CN.md)\n\nThe ability to inject a pull-up load for BetterScroll.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullUp from '@better-scroll/pull-up'\nBScroll.use(PullUp)\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n```\n"
  },
  {
    "path": "packages/pull-up/README_zh-CN.md",
    "content": "# @better-scroll/pull-up\n\n为 BetterScroll 注入上拉加载的能力。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pullup from '@better-scroll/pull-up'\nBScroll.use(Pullup)\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n```\n"
  },
  {
    "path": "packages/pull-up/package.json",
    "content": "{\n  \"name\": \"@better-scroll/pull-up\",\n  \"version\": \"2.5.1\",\n  \"description\": \"pull up to load more data\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/pull-up.min.js\",\n  \"module\": \"dist/pull-up.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"pull-up\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/pull-up\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/pull-up/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\njest.mock('@better-scroll/core')\n\nimport PullUp from '@better-scroll/pull-up'\nimport { Probe } from '@better-scroll/shared-utils'\n\nconst createPullUpElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\ndescribe('pullUp plugins', () => {\n  let scroll: BScroll\n  let pullUp: PullUp\n\n  beforeEach(() => {\n    // create DOM\n    const { wrapper } = createPullUpElements()\n    scroll = new BScroll(wrapper, {})\n    pullUp = new PullUp(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to scroll instance', () => {\n    expect(scroll.proxy).toBeCalledWith([\n      {\n        key: 'finishPullUp',\n        sourceKey: 'plugins.pullUpLoad.finishPullUp',\n      },\n      {\n        key: 'openPullUp',\n        sourceKey: 'plugins.pullUpLoad.openPullUp',\n      },\n      {\n        key: 'closePullUp',\n        sourceKey: 'plugins.pullUpLoad.closePullUp',\n      },\n      {\n        key: 'autoPullUpLoad',\n        sourceKey: 'plugins.pullUpLoad.autoPullUpLoad',\n      },\n    ])\n  })\n\n  it('should handle default options and user options', () => {\n    // case 1\n    scroll.options.pullUpLoad = true\n    pullUp = new PullUp(scroll)\n\n    expect(pullUp.options).toMatchObject({\n      threshold: 0,\n    })\n\n    // case 2\n    scroll.options.pullUpLoad = {\n      threshold: 40,\n    }\n    pullUp = new PullUp(scroll)\n\n    expect(pullUp.options).toMatchObject({\n      threshold: 40,\n    })\n\n    // case 3\n    scroll.options.pullUpLoad = {\n      threshold: -40,\n    }\n    pullUp = new PullUp(scroll)\n\n    expect(pullUp.options).toMatchObject({\n      threshold: -40,\n    })\n\n    expect(scroll.options.probeType).toBe(Probe.Realtime)\n  })\n\n  it('should modify maxScrollY when content is full of wrapper', () => {\n    const scrollBehaviorY = scroll.scroller.scrollBehaviorY\n    let boundary = {\n      minScrollPos: 0,\n      maxScrollPos: 20,\n    }\n    scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      boundary\n    )\n    expect(boundary).toMatchObject({\n      minScrollPos: 0,\n      maxScrollPos: -1,\n    })\n  })\n\n  it('should checkPullUp', () => {\n    const mockFn = jest.fn()\n    scroll.on(scroll.eventTypes.pullingUp, mockFn)\n\n    const pos1 = {\n      x: 0,\n      y: 100,\n    }\n    // simulate pullDown action\n    scroll.movingDirectionY = -1\n\n    scroll.trigger(scroll.eventTypes.scroll, pos1)\n    expect(mockFn).toHaveBeenCalledTimes(0)\n\n    // simulate pullUp action\n    const pos2 = {\n      x: 0,\n      y: -100,\n    }\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos2)\n    expect(mockFn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should trigger pullingUp once', () => {\n    const mockFn = jest.fn()\n    const pos = {\n      x: 0,\n      y: -100,\n    }\n    scroll.on(scroll.eventTypes.pullingUp, mockFn)\n    // when\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n    // then\n    expect(mockFn).toBeCalledTimes(1)\n  })\n\n  it('should work well when call finishPullUp()', () => {\n    // simulate pullUp action\n    const pos = {\n      x: 0,\n      y: -100,\n    }\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n\n    pullUp.finishPullUp()\n\n    expect(scroll.scroller.scrollBehaviorY.setMovingDirection).toBeCalledWith(0)\n    expect(scroll.events.scrollEnd.length).toBe(2)\n    expect(pullUp.watching).toBe(false)\n  })\n\n  it('should work well when call closePullUp()', () => {\n    pullUp.closePullUp()\n\n    expect(pullUp.watching).toBe(false)\n    expect(scroll.events.scroll.length).toBe(0)\n  })\n\n  it('should work well when call openPullUp()', () => {\n    pullUp.closePullUp()\n\n    expect(pullUp.watching).toBe(false)\n    expect(pullUp.options).toMatchObject({\n      threshold: 0,\n    })\n\n    // modify options\n    pullUp.openPullUp({\n      threshold: 200,\n    })\n\n    expect(pullUp.options).toMatchObject({\n      threshold: 200,\n    })\n    expect(pullUp.watching).toBe(true)\n  })\n\n  it('should reset pulling when scrollEnd triggered', () => {\n    // simulate pullUp action\n    const pos = {\n      x: 0,\n      y: -100,\n    }\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n\n    expect(pullUp.pulling).toBe(true)\n\n    scroll.trigger(scroll.eventTypes.scrollEnd)\n\n    expect(pullUp.pulling).toBe(false)\n  })\n\n  it('should call watch() in scrollEnd hooks when pullingUp', () => {\n    const pullUpMockFn = jest.fn()\n    // simulate pullUp action\n    const pos = {\n      x: 0,\n      y: -100,\n    }\n    scroll.on(scroll.eventTypes.pullingUp, pullUpMockFn)\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n\n    expect(pullUpMockFn).toBeCalledTimes(1)\n    expect(pullUp.pulling).toBe(true)\n\n    pullUp.finishPullUp()\n    // because pulling is true, won't trigger pullingUp\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n    expect(pullUpMockFn).toBeCalledTimes(1)\n    expect(pullUp.watching).toBe(false)\n\n    // register another watch in scrollEnd\n    scroll.trigger(scroll.eventTypes.scrollEnd)\n\n    scroll.movingDirectionY = 1\n    scroll.trigger(scroll.eventTypes.scroll, pos)\n    expect(pullUpMockFn).toBeCalledTimes(2)\n  })\n\n  it('should work well when call autoPullUpLoad()', () => {\n    pullUp.autoPullUpLoad()\n\n    const outOfBoundaryPos = -1\n    expect(scroll.scroller.scrollBehaviorY.setMovingDirection).toBeCalledWith(\n      -1\n    )\n    expect(scroll.scrollTo).toBeCalledWith(0, outOfBoundaryPos, 800)\n    expect(scroll.scrollTo).toBeCalledTimes(1)\n\n    // closePullUp, and autoPullUpLoad will not work\n    pullUp.closePullUp()\n    pullUp.autoPullUpLoad()\n    expect(scroll.scrollTo).toBeCalledTimes(1)\n  })\n\n  it('should call finishPullUp when content DOM changed', () => {\n    scroll.hooks.trigger(scroll.hooks.eventTypes.contentChanged)\n    expect(scroll.scroller.scrollBehaviorY.setMovingDirection).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/pull-up/src/index.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\nimport {\n  Probe,\n  Direction,\n  extend,\n  EventEmitter,\n} from '@better-scroll/shared-utils'\nimport propertiesConfig from './propertiesConfig'\n\nexport type PullUpLoadOptions = Partial<PullUpLoadConfig> | true\nexport interface PullUpLoadConfig {\n  threshold: number\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    pullUpLoad?: PullUpLoadOptions\n  }\n  interface CustomAPI {\n    pullUpLoad: PluginAPI\n  }\n}\ninterface PluginAPI {\n  finishPullUp(): void\n  openPullUp(config?: PullUpLoadOptions): void\n  closePullUp(): void\n  autoPullUpLoad(): void\n}\n\nconst PULL_UP_HOOKS_NAME = 'pullingUp'\n\nexport default class PullUp implements PluginAPI {\n  static pluginName = 'pullUpLoad'\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  pulling: boolean = false\n  watching: boolean = false\n  options: PullUpLoadConfig\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  private init() {\n    this.handleBScroll()\n\n    this.handleOptions(this.scroll.options.pullUpLoad)\n\n    this.handleHooks()\n\n    this.watch()\n  }\n\n  private handleBScroll() {\n    this.scroll.registerType([PULL_UP_HOOKS_NAME])\n\n    this.scroll.proxy(propertiesConfig)\n  }\n\n  private handleOptions(userOptions: PullUpLoadOptions = {}) {\n    userOptions = (userOptions === true ? {} : userOptions) as Partial<\n      PullUpLoadConfig\n    >\n    const defaultOptions: PullUpLoadConfig = {\n      threshold: 0,\n    }\n    this.options = extend(defaultOptions, userOptions)\n\n    this.scroll.options.probeType = Probe.Realtime\n  }\n\n  private handleHooks() {\n    this.hooksFn = []\n    const { scrollBehaviorY } = this.scroll.scroller\n\n    this.registerHooks(\n      this.scroll.hooks,\n      this.scroll.hooks.eventTypes.contentChanged,\n      () => {\n        this.finishPullUp()\n      }\n    )\n\n    this.registerHooks(\n      scrollBehaviorY.hooks,\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        // content is smaller than wrapper\n        if (boundary.maxScrollPos > 0) {\n          // allow scrolling when content is not full of wrapper\n          boundary.maxScrollPos = -1\n        }\n      }\n    )\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private watch() {\n    if (this.watching) {\n      return\n    }\n    this.watching = true\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.scroll,\n      this.checkPullUp\n    )\n  }\n\n  private unwatch() {\n    this.watching = false\n    this.scroll.off(this.scroll.eventTypes.scroll, this.checkPullUp)\n  }\n\n  private checkPullUp(pos: { x: number; y: number }) {\n    const { threshold } = this.options\n\n    if (\n      this.scroll.movingDirectionY === Direction.Positive &&\n      pos.y <= this.scroll.maxScrollY + threshold\n    ) {\n      this.pulling = true\n      // must reset pulling after scrollEnd\n      this.scroll.once(this.scroll.eventTypes.scrollEnd, () => {\n        this.pulling = false\n      })\n      this.unwatch()\n      this.scroll.trigger(PULL_UP_HOOKS_NAME)\n    }\n  }\n\n  finishPullUp() {\n    // reset Direction, fix #936\n    this.scroll.scroller.scrollBehaviorY.setMovingDirection(Direction.Default)\n    if (this.pulling) {\n      this.scroll.once(this.scroll.eventTypes.scrollEnd, () => {\n        this.watch()\n      })\n    } else {\n      this.watch()\n    }\n  }\n\n  // allow 'true' type is compat for beta version implements\n  openPullUp(config: PullUpLoadOptions = {}) {\n    this.handleOptions(config)\n    this.watch()\n  }\n\n  closePullUp() {\n    this.unwatch()\n  }\n\n  autoPullUpLoad() {\n    const { threshold } = this.options\n    const { scrollBehaviorY } = this.scroll.scroller\n\n    if (this.pulling || !this.watching) {\n      return\n    }\n\n    // simulate a pullUp action\n    const NEGATIVE_VALUE = -1\n    const outOfBoundaryPos =\n      scrollBehaviorY.maxScrollPos + threshold + NEGATIVE_VALUE\n    this.scroll.scroller.scrollBehaviorY.setMovingDirection(NEGATIVE_VALUE)\n    this.scroll.scrollTo(\n      this.scroll.x,\n      outOfBoundaryPos,\n      this.scroll.options.bounceTime\n    )\n  }\n}\n"
  },
  {
    "path": "packages/pull-up/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.pullUpLoad'\n\nconst propertiesMap = [\n  {\n    key: 'finishPullUp',\n    name: 'finishPullUp'\n  },\n  {\n    key: 'openPullUp',\n    name: 'openPullUp'\n  },\n  {\n    key: 'closePullUp',\n    name: 'closePullUp'\n  },\n  {\n    key: 'autoPullUpLoad',\n    name: 'autoPullUpLoad'\n  }\n]\n\nexport default propertiesMap.map(item => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`\n  }\n})\n"
  },
  {
    "path": "packages/react-examples/README.md",
    "content": "# react-examples\n\nBetterScroll example in different React scenarios.\n"
  },
  {
    "path": "packages/react-examples/config-overrides.js",
    "content": "const {\n  override,\n  addWebpackAlias,\n  removeModuleScopePlugin,\n} = require('customize-cra')\nconst path = require('path')\n\nconst resolve = (dir) => {\n  return path.resolve(__dirname, dir)\n}\n\nconst customWebpackConfig = (config) => {\n  const oneOf = config.module.rules.find((rule) => rule.oneOf).oneOf\n\n  // 修改 file-loader 配置\n  oneOf.map((rule) => {\n    if (\n      Array.isArray(rule.test) &&\n      rule.options.name === 'static/media/[name].[hash:8].[ext]'\n    ) {\n      rule.options.esModule = false\n    }\n    return rule\n  })\n\n  // 添加 stylus-loader\n  const stylusLoader = {\n    test: /\\.styl$/,\n    use: [\n      {\n        loader: 'style-loader',\n      },\n      {\n        loader: 'css-loader',\n      },\n      {\n        loader: 'stylus-loader',\n      },\n    ],\n  }\n  oneOf.unshift(stylusLoader)\n\n  return config\n}\n\nmodule.exports = override(\n  addWebpackAlias({\n    '@': resolve('src'),\n    common: resolve('common'),\n  }),\n  removeModuleScopePlugin(),\n  customWebpackConfig\n)\n"
  },
  {
    "path": "packages/react-examples/package.json",
    "content": "{\n  \"name\": \"react-examples\",\n  \"version\": \"2.5.1\",\n  \"description\": \"Examples of BetterScroll\",\n  \"private\": true,\n  \"author\": \"maomao1996 <1714487678@qq.com>\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ustbhuangyi/better-scroll.git\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"scripts\": {\n    \"dev\": \"react-app-rewired start\",\n    \"build\": \"react-app-rewired build\",\n    \"test\": \"react-app-rewired test\",\n    \"eject\": \"react-scripts eject\"\n  },\n  \"dependencies\": {\n    \"@testing-library/jest-dom\": \"^5.11.10\",\n    \"@testing-library/react\": \"^11.2.6\",\n    \"@testing-library/user-event\": \"^12.8.3\",\n    \"better-scroll\": \"^2.5.1\",\n    \"classnames\": \"^2.3.1\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-router-dom\": \"^5.2.0\",\n    \"react-scripts\": \"4.0.3\",\n    \"react-transition-group\": \"^4.4.1\",\n    \"web-vitals\": \"^1.1.1\"\n  },\n  \"eslintConfig\": {\n    \"extends\": [\n      \"react-app\",\n      \"react-app/jest\"\n    ]\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"devDependencies\": {\n    \"customize-cra\": \"^1.0.0\",\n    \"react-app-rewired\": \"^2.1.8\",\n    \"stylus\": \"^0.54.8\",\n    \"stylus-loader\": \"^3.0.2\"\n  }\n}\n"
  },
  {
    "path": "packages/react-examples/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\">\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"%PUBLIC_URL%/static/css/reset.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"%PUBLIC_URL%/static/css/normalize.css\" media=\"screen\">\n    <link href=\"https://fonts.googleapis.com/css2?family=Patrick+Hand&display=swap\" rel=\"stylesheet\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"%PUBLIC_URL%/static/css/stylesheet.css\" media=\"screen\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"%PUBLIC_URL%/static/css/github-light.css\" media=\"screen\">\n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>BetterScroll</title>\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "packages/react-examples/public/static/css/github-light.css",
    "content": "/*\n   Copyright 2014 GitHub Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n*/\n\n.pl-c /* comment */ {\n  color: #969896;\n}\n\n.pl-c1      /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,\n.pl-s .pl-v /* string variable */ {\n  color: #0086b3;\n}\n\n.pl-e  /* entity */,\n.pl-en /* entity.name */ {\n  color: #795da3;\n}\n\n.pl-s .pl-s1 /* string source */,\n.pl-smi      /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {\n  color: #333;\n}\n\n.pl-ent /* entity.name.tag */ {\n  color: #63a35c;\n}\n\n.pl-k /* keyword, storage, storage.type */ {\n  color: #a71d5d;\n}\n\n.pl-pds              /* punctuation.definition.string, string.regexp.character-class */,\n.pl-s                /* string */,\n.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,\n.pl-sr               /* string.regexp */,\n.pl-sr .pl-cce       /* string.regexp constant.character.escape */,\n.pl-sr .pl-sra       /* string.regexp string.regexp.arbitrary-repitition */,\n.pl-sr .pl-sre       /* string.regexp source.ruby.embedded */ {\n  color: #183691;\n}\n\n.pl-v /* variable */ {\n  color: #ed6a43;\n}\n\n.pl-id /* invalid.deprecated */ {\n  color: #b52a1d;\n}\n\n.pl-ii /* invalid.illegal */ {\n  background-color: #b52a1d;\n  color: #f8f8f8;\n}\n\n.pl-sr .pl-cce /* string.regexp constant.character.escape */ {\n  color: #63a35c;\n  font-weight: bold;\n}\n\n.pl-ml /* markup.list */ {\n  color: #693a17;\n}\n\n.pl-mh        /* markup.heading */,\n.pl-mh .pl-en /* markup.heading entity.name */,\n.pl-ms        /* meta.separator */ {\n  color: #1d3e81;\n  font-weight: bold;\n}\n\n.pl-mq /* markup.quote */ {\n  color: #008080;\n}\n\n.pl-mi /* markup.italic */ {\n  color: #333;\n  font-style: italic;\n}\n\n.pl-mb /* markup.bold */ {\n  color: #333;\n  font-weight: bold;\n}\n\n.pl-md /* markup.deleted, meta.diff.header.from-file */ {\n  background-color: #ffecec;\n  color: #bd2c00;\n}\n\n.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {\n  background-color: #eaffea;\n  color: #55a532;\n}\n\n.pl-mdr /* meta.diff.range */ {\n  color: #795da3;\n  font-weight: bold;\n}\n\n.pl-mo /* meta.output */ {\n  color: #1d3e81;\n}\n\n"
  },
  {
    "path": "packages/react-examples/public/static/css/normalize.css",
    "content": "/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n/**\n * 1. Set default font family to sans-serif.\n * 2. Prevent iOS text size adjust after orientation change, without disabling\n *    user zoom.\n */\n\nhtml {\n  font-family: sans-serif; /* 1 */\n  -ms-text-size-adjust: 100%; /* 2 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/**\n * Remove default margin.\n */\n\nbody {\n  margin: 0;\n}\n\n/* HTML5 display definitions\n   ========================================================================== */\n\n/**\n * Correct `block` display not defined for any HTML5 element in IE 8/9.\n * Correct `block` display not defined for `details` or `summary.md` in IE 10/11\n * and Firefox.\n * Correct `block` display not defined for `main` in IE 11.\n */\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\n\n/**\n * 1. Correct `inline-block` display not defined in IE 8/9.\n * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n */\n\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block; /* 1 */\n  vertical-align: baseline; /* 2 */\n}\n\n/**\n * Prevent modern browsers from displaying `audio` without controls.\n * Remove excess height in iOS 5 devices.\n */\n\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n\n/**\n * Address `[hidden]` styling not present in IE 8/9/10.\n * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n */\n\n[hidden],\ntemplate {\n  display: none;\n}\n\n/* Links\n   ========================================================================== */\n\n/**\n * Remove the gray background color from active links in IE 10.\n */\n\na {\n  background-color: transparent;\n}\n\n/**\n * Improve readability when focused and also mouse hovered in all browsers.\n */\n\na:active,\na:hover {\n  outline: 0;\n}\n\n/* Text-level semantics\n   ========================================================================== */\n\n/**\n * Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n */\n\nabbr[title] {\n  border-bottom: 1px dotted;\n}\n\n/**\n * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n */\n\nb,\nstrong {\n  font-weight: bold;\n}\n\n/**\n * Address styling not present in Safari and Chrome.\n */\n\ndfn {\n  font-style: italic;\n}\n\n/**\n * Address variable `h1` font-size and margin within `section` and `article`\n * contexts in Firefox 4+, Safari, and Chrome.\n */\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/**\n * Address styling not present in IE 8/9.\n */\n\nmark {\n  background: #ff0;\n  color: #000;\n}\n\n/**\n * Address inconsistent and variable font size in all browsers.\n */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` affecting `line-height` in all browsers.\n */\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsup {\n  top: -0.5em;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\n/* Embedded content\n   ========================================================================== */\n\n/**\n * Remove border when inside `a` element in IE 8/9/10.\n */\n\nimg {\n  border: 0;\n}\n\n/**\n * Correct overflow not hidden in IE 9/10/11.\n */\n\nsvg:not(:root) {\n  overflow: hidden;\n}\n\n/* Grouping content\n   ========================================================================== */\n\n/**\n * Address margin not present in IE 8/9 and Safari.\n */\n\nfigure {\n  margin: 1em 40px;\n}\n\n/**\n * Address differences between Firefox and other browsers.\n */\n\nhr {\n  box-sizing: content-box;\n  height: 0;\n}\n\n/**\n * Contain overflow in all browsers.\n */\n\npre {\n  overflow: auto;\n}\n\n/**\n * Address odd `em`-unit font size rendering in all browsers.\n */\n\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\n/* Forms\n   ========================================================================== */\n\n/**\n * Known limitation: by default, Chrome and Safari on OS X allow very limited\n * styling of `select`, unless a `border` property is set.\n */\n\n/**\n * 1. Correct color not being inherited.\n *    Known issue: affects color of disabled elements.\n * 2. Correct font properties not being inherited.\n * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  color: inherit; /* 1 */\n  font: inherit; /* 2 */\n  margin: 0; /* 3 */\n}\n\n/**\n * Address `overflow` set to `hidden` in IE 8/9/10/11.\n */\n\nbutton {\n  overflow: visible;\n}\n\n/**\n * Address inconsistent `text-transform` inheritance for `button` and `select`.\n * All other form control elements do not inherit `text-transform` values.\n * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n * Correct `select` style inheritance in Firefox.\n */\n\nbutton,\nselect {\n  text-transform: none;\n}\n\n/**\n * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n *    and `video` controls.\n * 2. Correct inability to style clickable `input` types in iOS.\n * 3. Improve usability and consistency of cursor style between image-type\n *    `input` and others.\n */\n\nbutton,\nhtml input[type=\"button\"], /* 1 */\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button; /* 2 */\n  cursor: pointer; /* 3 */\n}\n\n/**\n * Re-set default cursor for disabled elements.\n */\n\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\n\n/**\n * Remove inner padding and border in Firefox 4+.\n */\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  border: 0;\n  padding: 0;\n}\n\n/**\n * Address Firefox 4+ setting `line-height` on `input` using `!important` in\n * the UA stylesheet.\n */\n\ninput {\n  line-height: normal;\n}\n\n/**\n * It's recommended that you don't attempt to style these elements.\n * Firefox's implementation doesn't respect box-sizing, padding, or width.\n *\n * 1. Address box sizing set to `content-box` in IE 8/9/10.\n * 2. Remove excess padding in IE 8/9/10.\n */\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Fix the cursor style for Chrome's increment/decrement buttons. For certain\n * `font-size` values of the `input`, it causes the cursor style of the\n * decrement button to change from `default` to `text`.\n */\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n * 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n * 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n *    (include `-moz` to future-proof).\n */\n\ninput[type=\"search\"] {\n  -webkit-appearance: textfield; /* 1 */ /* 2 */\n  box-sizing: content-box;\n}\n\n/**\n * Remove inner padding and search cancel button in Safari and Chrome on OS X.\n * Safari (but not Chrome) clips the cancel button when the search input has\n * padding (and `textfield` appearance).\n */\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n * Define consistent border, margin, and padding.\n */\n\nfieldset {\n  border: 1px solid #c0c0c0;\n  margin: 0 2px;\n  padding: 0.35em 0.625em 0.75em;\n}\n\n/**\n * 1. Correct `color` not being inherited in IE 8/9/10/11.\n * 2. Remove padding so people aren't caught out if they zero out fieldsets.\n */\n\nlegend {\n  border: 0; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n * Remove default vertical scrollbar in IE 8/9/10/11.\n */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n * Don't inherit the `font-weight` (applied by a rule above).\n * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n */\n\noptgroup {\n  font-weight: bold;\n}\n\n/* Tables\n   ========================================================================== */\n\n/**\n * Remove most spacing between table cells.\n */\n\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\ntd,\nth {\n  padding: 0;\n}\n"
  },
  {
    "path": "packages/react-examples/public/static/css/reset.css",
    "content": "/**\n * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)\n * http://cssreset.com\n */\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video, input {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    font-size: 100%;\n    font-weight: normal;\n    vertical-align: baseline;\n}\n\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, menu, nav, section {\n    display: block;\n}\n\nbody {\n    line-height: 1;\n}\n\nblockquote, q {\n    quotes: none;\n}\n\nblockquote:before, blockquote:after,\nq:before, q:after {\n    content: none;\n}\n\ntable {\n    border-collapse: collapse;\n    border-spacing: 0;\n}\n\n/* custom */\n\na {\n    color: #7e8c8d;\n    -webkit-backface-visibility: hidden;\n}\n\nli {\n    list-style: none;\n}\n\n::-webkit-scrollbar {\n    width: 5px;\n    height: 5px;\n}\n\n::-webkit-scrollbar-track-piece {\n    background-color: rgba(0, 0, 0, 0.2);\n    -webkit-border-radius: 6px;\n}\n\n::-webkit-scrollbar-thumb:vertical {\n    height: 5px;\n    background-color: rgba(125, 125, 125, 0.7);\n    -webkit-border-radius: 6px;\n}\n\n::-webkit-scrollbar-thumb:horizontal {\n    width: 5px;\n    background-color: rgba(125, 125, 125, 0.7);\n    -webkit-border-radius: 6px;\n}\n\nhtml, body {\n    width: 100%;\n    /* height: 100%; */\n}\n\nbody {\n    -webkit-text-size-adjust: none;\n    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}"
  },
  {
    "path": "packages/react-examples/public/static/css/stylesheet.css",
    "content": "* {\n  box-sizing: border-box; }\n\nbody {\n  padding: 0;\n  margin: 0;\n  font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 16px;\n  line-height: 1.5;\n  background-color: #fff;\n  color: #606c71; }\n\na {\n  color: #1e6bb8;\n  text-decoration: none; }\n  a:hover {\n    text-decoration: underline; }\n\n.btn {\n  display: inline-block;\n  margin-bottom: 1rem;\n  padding: 0.75rem 1rem;\n  color: rgba(255, 255, 255, 0.8);\n  background-color: rgba(255, 255, 255, 0.1);\n  border-color: rgba(255, 255, 255, 0.2);\n  border-style: solid;\n  border-width: 1px;\n  border-radius: 0.3rem;\n  transition: color 0.2s, background-color 0.2s, border-color 0.2s; }\n  .btn + .btn {\n    margin-left: 1rem; }\n\n.btn:hover {\n  color: rgba(255, 255, 255, 0.9);\n  text-decoration: none;\n  background-color: rgba(255, 255, 255, 0.2);\n  border-color: rgba(255, 255, 255, 0.3); }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .btn {\n    font-size: 0.9rem; } }\n\n@media screen and (max-width: 42em) {\n  .btn {\n    display: block;\n    width: 100%;\n    padding: 0.75rem;\n    font-size: 0.9rem; }\n    .btn + .btn {\n      margin-top: 1rem;\n      margin-left: 0; } }\n\n.page-header {\n  color: #fff;\n  text-align: center;\n  font-family: 'Patrick Hand', cursive;\n  background-color: rgba(39,80,255, .7)}\n\n@media screen and (min-width: 64em) {\n  .page-header {\n    padding: 1.5rem 6rem 3rem 6rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .page-header {\n    padding: 1.5rem 4rem 2rem 4rem; } }\n\n@media screen and (max-width: 42em) {\n  .page-header {\n    padding: 1.5rem 1rem 1rem 1rem; } }\n\n.project-name {\n  margin-top: 0;\n  margin-bottom: 0.1rem; }\n\n@media screen and (min-width: 64em) {\n  .project-name {\n    font-size: 3.25rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .project-name {\n    font-size: 2.25rem; } }\n\n@media screen and (max-width: 42em) {\n  .project-name {\n    font-size: 1.75rem; } }\n\n.project-tagline {\n  margin-bottom: 1rem;\n  font-weight: normal;\n  opacity: 0.7; }\n\n@media screen and (min-width: 64em) {\n  .project-tagline {\n    font-size: 1.25rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .project-tagline {\n    font-size: 1.15rem; } }\n\n@media screen and (max-width: 42em) {\n  .project-tagline {\n    font-size: 1rem; } }\n\n.main-content :first-child {\n  margin-top: 0; }\n.main-content img {\n  max-width: 100%; }\n.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {\n  margin-top: 1rem;\n  margin-bottom: 1rem; }\n.main-content code {\n  padding: 2px 4px;\n  font-family: Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 0.9rem;\n  color: #383e41;\n  background-color: #f3f6fa;\n  border-radius: 0.3rem; }\n.main-content pre {\n  padding: 0.8rem;\n  margin-top: 0;\n  margin-bottom: 1rem;\n  font: 1rem Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  color: #567482;\n  word-wrap: normal;\n  background-color: #f3f6fa;\n  border: solid 1px #dce6f0;\n  border-radius: 0.3rem; }\n  .main-content pre > code {\n    padding: 0;\n    margin: 0;\n    font-size: 0.9rem;\n    color: #567482;\n    word-break: normal;\n    white-space: pre;\n    background: transparent;\n    border: 0; }\n.main-content .highlight {\n  margin-bottom: 1rem; }\n  .main-content .highlight pre {\n    margin-bottom: 0;\n    word-break: normal; }\n.main-content .highlight pre, .main-content pre {\n  padding: 0.8rem;\n  overflow: auto;\n  font-size: 0.9rem;\n  line-height: 1.45;\n  border-radius: 0.3rem; }\n.main-content pre code, .main-content pre tt {\n  display: inline;\n  max-width: initial;\n  padding: 0;\n  margin: 0;\n  overflow: initial;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0; }\n  .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {\n    content: normal; }\n.main-content ul, .main-content ol {\n  margin-top: 0; }\n.main-content blockquote {\n  padding: 0 1rem;\n  margin-left: 0;\n  color: #819198;\n  border-left: 0.3rem solid #dce6f0; }\n  .main-content blockquote > :first-child {\n    margin-top: 0; }\n  .main-content blockquote > :last-child {\n    margin-bottom: 0; }\n.main-content table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n  word-break: normal;\n  word-break: keep-all; }\n  .main-content table th {\n    font-weight: bold; }\n  .main-content table th, .main-content table td {\n    padding: 0.5rem 1rem;\n    border: 1px solid #e9ebec; }\n.main-content dl {\n  padding: 0; }\n  .main-content dl dt {\n    padding: 0;\n    margin-top: 1rem;\n    font-size: 1rem;\n    font-weight: bold; }\n  .main-content dl dd {\n    padding: 0;\n    margin-bottom: 1rem; }\n.main-content hr {\n  height: 2px;\n  padding: 0;\n  margin: 1rem 0;\n  background-color: #eff0f1;\n  border: 0; }\n\n@media screen and (min-width: 64em) {\n  .main-content {\n    padding: 2rem 8rem;\n    margin: 0 auto;\n    font-size: 1.1rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .main-content {\n    padding: 2rem 2rem;\n    font-size: 1.1rem; } }\n\n@media screen and (max-width: 42em) {\n  .main-content {\n    padding: 1rem 1rem 2rem 1rem;\n    font-size: 1rem; }\n  iframe {\n    display: none\n  }\n}\n\n.site-footer {\n  padding-top: 2rem;\n  margin-top: 2rem;\n  border-top: solid 1px #d7d7d7; }\n\n.site-footer-owner {\n  display: block;\n  font-weight: bold; }\n\n.site-footer-credits {\n  color: #819198; }\n\n@media screen and (min-width: 64em) {\n  .site-footer {\n    font-size: 1rem; } }\n\n@media screen and (min-width: 42em) and (max-width: 64em) {\n  .site-footer {\n    font-size: 1rem; } }\n\n@media screen and (max-width: 42em) {\n  .site-footer {\n    font-size: 0.9rem; } }\n"
  },
  {
    "path": "packages/react-examples/src/App.js",
    "content": "import { useHistory } from 'react-router-dom'\n\nimport Router from './router'\n\nconst examples = [\n  {\n    name: 'core scroll',\n    path: '/core/',\n  },\n  {\n    name: 'observe-dom',\n    path: '/observe-dom/',\n  },\n  {\n    name: 'observe-image',\n    path: '/observe-image/',\n  },\n  {\n    name: 'slide',\n    path: '/slide',\n  },\n  {\n    name: 'zoom',\n    path: '/zoom/',\n  },\n  {\n    name: 'picker',\n    path: '/picker/',\n  },\n  {\n    name: 'pullup',\n    path: '/pullup/',\n  },\n  {\n    name: 'pulldown',\n    path: '/pulldown/',\n  },\n  {\n    name: 'scrollbar',\n    path: '/scrollbar',\n  },\n  {\n    name: 'indicators',\n    path: '/indicators',\n  },\n  {\n    name: 'infinity',\n    path: '/infinity',\n  },\n  {\n    name: 'form',\n    path: '/form',\n  },\n  {\n    name: 'nested-scroll',\n    path: '/nested-scroll',\n  },\n  {\n    name: 'mouse-wheel',\n    path: '/mouse-wheel',\n  },\n  {\n    name: 'movable',\n    path: '/movable',\n  },\n  {\n    name: 'compose plugins',\n    path: '/compose',\n  },\n]\n\nconst App = () => {\n  const history = useHistory()\n\n  const goPage = (path) => {\n    history.push(path)\n  }\n\n  return (\n    <>\n      <section className=\"page-header\">\n        <h1 className=\"project-name\">BetterScroll</h1>\n        <h2 className=\"project-tagline\">\n          inspired by iscroll, and it has a better scroll perfermance\n        </h2>\n      </section>\n      <section className=\"main-content\">\n        <div className=\"example\">\n          <ul className=\"example-list\">\n            {examples.map((item) => (\n              <li\n                key={item.name}\n                className=\"example-item\"\n                onClick={() => goPage(item.path)}\n              >\n                <span>{item.name}</span>\n              </li>\n            ))}\n\n            <li className=\"example-item placeholder\"></li>\n          </ul>\n        </div>\n      </section>\n      <Router />\n    </>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "packages/react-examples/src/index.js",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport { HashRouter } from 'react-router-dom'\n\nimport reportWebVitals from './reportWebVitals'\nimport App from './App'\n\nimport './index.styl'\n\nReactDOM.render(\n  <HashRouter>\n    <App />\n  </HashRouter>,\n  document.getElementById('root')\n)\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals()\n"
  },
  {
    "path": "packages/react-examples/src/index.styl",
    "content": ".page-header\n  h1\n    @media screen and (min-width 42rem)\n      margin-bottom 1rem\n\n    @media screen and (max-width 42rem)\n      margin-bottom 0.5rem\n\n.main-content\n  .site-footer\n    text-align center\n\n    @media screen and (max-width 42rem)\n      margin-top -1rem\n\n.example-list\n  display flex\n  justify-content space-between\n  flex-wrap wrap\n\n  @media screen and (min-width 42rem)\n    margin 2rem 0 2rem 0\n\n  @media screen and (max-width 42rem)\n    margin 1rem 0\n\n  .example-item\n    background-color $color-white\n    padding 0.8rem\n    border 1px solid rgba(0, 0, 0, 0.1)\n    box-shadow 0 1px 2px 0 rgba(0, 0, 0, 0.1)\n    text-align center\n    margin-bottom 1rem\n\n    &.placeholder\n      visibility hidden\n      height 0\n      margin 0\n      padding 0\n\n    @media screen and (min-width 42rem)\n      flex 0 1 28%\n\n    @media screen and (max-width 42rem)\n      flex 0 1 100%\n      margin-bottom 1rem\n\n.view\n  position fixed\n  top 0\n  left 0\n  bottom 0\n  right 0\n  z-index 1\n  padding 20px\n  background #fff\n  transform translate3d(0, 0, 0)\n\n  &.move-enter, &.move-leave-active\n    transform translate3d(100%, 0, 0)\n\n  &.move-enter-active, &.move-leave-active\n    transition transform 0.3s\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/components/pullup-pulldown-outnested.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport PullUp from '@better-scroll/pull-up'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(PullDown)\nBScroll.use(PullUp)\nBScroll.use(NestedScroll)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 3000\nconst THRESHOLD = 70\nconst STOP = 56\n\nconst TOP_OUT_ITEMS = [\n  '😀  😁   😂  🤣   😃  🙃',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '🙂  🤔   😄  🤨   😐  🙃',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️',\n  '🙂  🤔  😄  🤨   😐  🙃 ',\n  '👆🏻 Pull Down and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️ ',\n]\n\nconst BOTTOM_OUT_ITEMS = [\n  '😀  😁   😂  🤣   😃  🙃',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '🙂  🤔   😄  🤨   😐  🙃',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️',\n  '🙂  🤔  😄  🤨   😐  🙃 ',\n  '👆🏻 Pull Up and refresh👆🏻',\n  '😔  😕   🙃  🤑   😲  ☹️ ',\n]\n\nconst INNER_ITEMS = [\n  'The Mountain top of Inner',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  'The Mountain foot of Inner',\n]\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve({\n        topOutItems: TOP_OUT_ITEMS,\n        bottomOutItems: BOTTOM_OUT_ITEMS,\n      })\n    }, REQUEST_TIME)\n  })\n}\n\nconst PullUpPullDownNestedScroll = () => {\n  const [beforePullDown, setBeforePullDown] = useState(true)\n  const [isPullingDown, setIsPullingDown] = useState(false)\n  const [isPullUpLoad, setIsPullUpLoad] = useState(false)\n  const [topOutItems, setTopOutItems] = useState(TOP_OUT_ITEMS)\n  const [bottomOutItems, setBottomOutItems] = useState(BOTTOM_OUT_ITEMS)\n\n  const wrapperRef = useRef(null)\n  const innerRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async (type) => {\n    try {\n      const { topOutItems, bottomOutItems } = await ajaxGet(/* url */)\n      if (type === 'load') {\n        setBottomOutItems((prev) => bottomOutItems.concat(prev))\n      } else {\n        setTopOutItems(topOutItems)\n        setBottomOutItems(bottomOutItems)\n      }\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n  const finishPullDown = () => {\n    scrollRef.current.finishPullDown()\n    setTimeout(() => {\n      setBeforePullDown(true)\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 100)\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    setBeforePullDown(false)\n    setIsPullingDown(true)\n    await requestData('refresh')\n    setIsPullingDown(false)\n    finishPullDown()\n  })\n\n  const pullingUpHandler = useStableCallback(async () => {\n    setIsPullUpLoad(true)\n    await requestData('load')\n    scrollRef.current.finishPullUp()\n    scrollRef.current.refresh()\n    setIsPullUpLoad(false)\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        nestedScroll: {\n          groupId: 'pullup-pullldown',\n        },\n        bounceTime: TIME_BOUNCE,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP,\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: THRESHOLD,\n        },\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n      BS.on('pullingUp', pullingUpHandler)\n      BS.on('scroll', (pos) => {\n        console.log(pos.y)\n      })\n\n      new BScroll(innerRef.current, {\n        nestedScroll: {\n          groupId: 'pullup-pullldown',\n        },\n        // close bounce effects\n        bounce: {\n          top: false,\n          bottom: false,\n        },\n      })\n    }\n  }, [pullingDownHandler, pullingUpHandler])\n\n  return (\n    <div className=\"pullup-pulldown-outnested view\">\n      <div className=\"outer-wrapper\" ref={wrapperRef}>\n        <div className=\"outer-content\">\n          <div className=\"pulldown-wrapper\">\n            <div style={{ display: beforePullDown ? '' : 'none' }}>\n              <span>Pull Down and refresh</span>\n            </div>\n            <div style={{ display: beforePullDown ? 'none' : '' }}>\n              <div v-show=\"isPullingDown\">\n                <span>Loading...</span>\n              </div>\n              <div style={{ display: isPullingDown ? 'none' : '' }}>\n                <span>Refresh success</span>\n              </div>\n            </div>\n          </div>\n          <ul>\n            {topOutItems.map((item, index) => (\n              <li key={index} className=\"outer-list-item\">\n                {item}\n              </li>\n            ))}\n          </ul>\n          <div className=\"inner-wrapper\" ref={innerRef}>\n            <ul className=\"inner-content\">\n              {INNER_ITEMS.map((item, index) => (\n                <li key={index} className=\"inner-list-item\">\n                  {item}\n                </li>\n              ))}\n            </ul>\n          </div>\n          <ul>\n            {bottomOutItems.map((item, index) => (\n              <li key={index} className=\"outer-list-item2\">\n                {item}\n              </li>\n            ))}\n          </ul>\n          <div className=\"pullup-wrapper\">\n            {isPullUpLoad ? (\n              <div>\n                <span>Loading...</span>\n              </div>\n            ) : (\n              <div>\n                <span>Pull Up and load</span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default PullUpPullDownNestedScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/components/pullup-pulldown-slide.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport PullUp from '@better-scroll/pull-up'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(PullDown)\nBScroll.use(PullUp)\nBScroll.use(Slide)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst TIME_BOUNCE = 700\nconst REQUEST_TIME = 1000\nconst THRESHOLD = 50\nconst STOP = 56\n\nfunction generateData() {\n  return Array.from({ length: 2 }, (_, i) => i)\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(generateData())\n    }, REQUEST_TIME)\n  })\n}\n\nconst PullUpPullDownSlide = () => {\n  const [beforePullDown, setBeforePullDown] = useState(true)\n  const [isPullingDown, setIsPullingDown] = useState(false)\n  const [isPullUpLoad, setIsPullUpLoad] = useState(false)\n  const [data, setData] = useState(generateData())\n\n  const stopPullup = useRef(false)\n\n  const wrapperRef = useRef(null)\n  const pulldownRef = useRef(null)\n  const pullupRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async (type) => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      if (type === 'load') {\n        setData((prev) => newData.concat(prev))\n      } else {\n        setData(newData)\n      }\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const resetPullUpPos = () => {\n    const pullUpEle = pullupRef.current\n    pullUpEle.style.transform = `translateY(0px) translateZ(0)`\n    pullUpEle.style.transition = 'transform 0.5s'\n    stopPullup.current = false\n  }\n\n  const finishPullDown = () => {\n    scrollRef.current.finishPullDown()\n    scrollRef.current.enabled = true\n    setTimeout(() => {\n      setBeforePullDown(true)\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 100)\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    setBeforePullDown(false)\n    setIsPullingDown(true)\n    scrollRef.current.enabled = false\n    await requestData('refresh')\n    setIsPullingDown(false)\n    finishPullDown()\n  })\n\n  const pullingUpHandler = useStableCallback(async () => {\n    setIsPullUpLoad(true)\n    scrollRef.current.enabled = false\n    await requestData('load')\n    scrollRef.current.finishPullUp()\n    scrollRef.current.enabled = true\n    scrollRef.current.refresh()\n    setIsPullUpLoad(false)\n    resetPullUpPos()\n  })\n\n  const scrollHandler = useStableCallback((pos) => {\n    if (pos.y >= 0) {\n      const pullDownEle = pulldownRef.current\n      const { height: pulldownH } = getComputedStyle(pullDownEle, null)\n      pullDownEle.style.transform = `translateY(${\n        -parseInt(pulldownH) + pos.y\n      }px) translateZ(0)`\n    }\n    const pullupThreshold = -30\n    const maxScrollY = scrollRef.current.maxScrollY\n    if (pos.y - maxScrollY <= pullupThreshold && isPullUpLoad) {\n      stopPullup.current = true\n    }\n    if (pos.y - maxScrollY <= 0 && !stopPullup.current) {\n      const pullUpEle = pullupRef.current\n      pullUpEle.style.transform = `translateY(${\n        pos.y - maxScrollY\n      }px) translateZ(0)`\n    }\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        momentum: false,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP,\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: -THRESHOLD,\n        },\n        // slide options\n        slide: {\n          threshold: 5,\n          disableSetHeight: true,\n          autoplay: false,\n          loop: false,\n        },\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n      BS.on('pullingUp', pullingUpHandler)\n      BS.on('scroll', scrollHandler)\n    }\n  }, [pullingDownHandler, pullingUpHandler, scrollHandler])\n\n  return (\n    <div className=\"pullup-down-slide-wrapper view\">\n      {/* pull down */}\n      <div className=\"pulldown-wrapper\" ref={pulldownRef}>\n        <div style={{ display: beforePullDown ? '' : 'none' }}>\n          <span>Pull Down and refresh</span>\n        </div>\n        <div style={{ display: beforePullDown ? 'none' : '' }}>\n          <div v-show=\"isPullingDown\">\n            <span>Loading...</span>\n          </div>\n          <div style={{ display: isPullingDown ? 'none' : '' }}>\n            <span>Refresh success</span>\n          </div>\n        </div>\n      </div>\n      {/* slide item */}\n      <div className=\"pullup-pulldown-slide-bswrapper\" ref={wrapperRef}>\n        <div className=\"pullup-pulldown-slide-scroller\">\n          {data.map((_, index) => (\n            <div\n              key={index}\n              className={`pullup-pulldown-slide-item page${index % 4}`}\n            >\n              {`Page ${index} `}\n            </div>\n          ))}\n        </div>\n      </div>\n      {/* pollup */}\n      <div className=\"pullup-wrapper\" ref={pullupRef}>\n        {isPullUpLoad ? (\n          <div>\n            <span>Loading...</span>\n          </div>\n        ) : (\n          <div>\n            <span>Pull Up and load</span>\n          </div>\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport default PullUpPullDownSlide\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/components/pullup-pulldown.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport PullUp from '@better-scroll/pull-up'\n\nBScroll.use(PullDown)\nBScroll.use(PullUp)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 3000\nconst THRESHOLD = 70\nconst STOP = 56\n\nfunction generateData() {\n  return Array.from({ length: 30 }, (_, i) => i)\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(generateData())\n    }, REQUEST_TIME)\n  })\n}\n\nconst PullUpPullDown = () => {\n  const [beforePullDown, setBeforePullDown] = useState(true)\n  const [isPullingDown, setIsPullingDown] = useState(false)\n  const [isPullUpLoad, setIsPullUpLoad] = useState(false)\n  const [data, setData] = useState(generateData())\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async (type) => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      if (type === 'load') {\n        setData((prev) => newData.concat(prev))\n      } else {\n        setData(newData)\n      }\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const finishPullDown = () => {\n    scrollRef.current.finishPullDown()\n    setTimeout(() => {\n      setBeforePullDown(true)\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 100)\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    setBeforePullDown(false)\n    setIsPullingDown(true)\n    await requestData('refresh')\n    setIsPullingDown(false)\n    finishPullDown()\n  })\n\n  const pullingUpHandler = useStableCallback(async () => {\n    setIsPullUpLoad(true)\n    await requestData('load')\n    scrollRef.current.finishPullUp()\n    scrollRef.current.refresh()\n    setIsPullUpLoad(false)\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        // pullDown options\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP,\n        },\n        // pullUp options\n        pullUpLoad: {\n          threshold: THRESHOLD,\n        },\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n      BS.on('pullingUp', pullingUpHandler)\n      BS.on('scroll', (pos) => {\n        console.log(pos.y)\n      })\n    }\n  }, [pullingDownHandler, pullingUpHandler])\n\n  return (\n    <div className=\"pullup-down view\">\n      <div className=\"pullup-down-bswrapper\" ref={wrapperRef}>\n        <div className=\"pulldown-scroller\">\n          <div className=\"pulldown-wrapper\">\n            <div style={{ display: beforePullDown ? '' : 'none' }}>\n              <span>Pull Down and refresh</span>\n            </div>\n            <div style={{ display: beforePullDown ? 'none' : '' }}>\n              <div v-show=\"isPullingDown\">\n                <span>Loading...</span>\n              </div>\n              <div style={{ display: isPullingDown ? 'none' : '' }}>\n                <span>Refresh success</span>\n              </div>\n            </div>\n          </div>\n          <ul className=\"pullup-down-list\">\n            {data.map((_, index) => (\n              <li key={index} className=\"pullup-down-list-item\">\n                {`I am item ${index} `}\n              </li>\n            ))}\n          </ul>\n          <div className=\"pullup-wrapper\">\n            {isPullUpLoad ? (\n              <div>\n                <span>Loading...</span>\n              </div>\n            ) : (\n              <div>\n                <span>Pull Up and load</span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default PullUpPullDown\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/components/slide-nested.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(NestedScroll)\nBScroll.use(Slide)\n\nconst DATA = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n]\n\nconst NestedScrollPage = () => {\n  const outerWrapperRef = useRef(null)\n  const innerWrapperRef = useRef(null)\n\n  useEffect(() => {\n    const outerScrollRef = new BScroll(outerWrapperRef.current, {\n      nestedScroll: {\n        groupId: 'slide-nested',\n      },\n    })\n\n    const innerScrollRef = new BScroll(innerWrapperRef.current, {\n      nestedScroll: {\n        groupId: 'slide-nested',\n      },\n      scrollX: true,\n      scrollY: false,\n      momentum: false,\n      // close bounce effects\n      bounce: {\n        top: false,\n        bottom: false,\n      },\n      slide: {\n        loop: true,\n        autoplay: false,\n      },\n    })\n\n    return () => {\n      outerScrollRef.destroy()\n      innerScrollRef.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"slide-nested view\">\n      <div className=\"outer-wrapper\" ref={outerWrapperRef}>\n        <div className=\"outer-content\">\n          <ul>\n            {DATA.map((item, index) => (\n              <li key={index} className=\"outer-list-item\">\n                {item}\n              </li>\n            ))}\n          </ul>\n          <div className=\"inner-wrapper\" ref={innerWrapperRef}>\n            <div className=\"slide-banner-content\">\n              <div className=\"slide-item page1\">page 1</div>\n              <div className=\"slide-item page2\">page 2</div>\n              <div className=\"slide-item page3\">page 3</div>\n              <div className=\"slide-item page4\">page 4</div>\n            </div>\n          </div>\n          <ul>\n            {DATA.map((item, index) => (\n              <li key={index} className=\"outer-list-item\">\n                {item}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default NestedScrollPage\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/compose/pullup-pulldown',\n    name: 'pullup-pulldown',\n  },\n  {\n    path: '/compose/pullup-pulldown-slide',\n    name: 'pullup-pulldown-slide',\n  },\n  {\n    path: '/compose/pullup-pulldown-outnested',\n    name: 'pullup-pulldown-outnested',\n  },\n  {\n    path: '/compose/slide-nested',\n    name: 'slide-nested',\n  },\n]\n\nconst Compose = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Compose\n"
  },
  {
    "path": "packages/react-examples/src/pages/compose/index.styl",
    "content": ".pullup-down\n  height 100%\n  .pullup-down-bswrapper\n    position relative\n    height 100%\n    padding 0 10px\n    border 1px solid #ccc\n    overflow hidden\n  .pullup-down-list\n    padding 0\n  .pullup-down-list-item\n    padding 10px 0\n    list-style none\n    border-bottom 1px solid #ccc\n  .pulldown-wrapper\n    position absolute\n    width 100%\n    padding 20px\n    box-sizing border-box\n    transform translateY(-100%) translateZ(0)\n    text-align center\n    color #999\n  .pullup-wrapper\n    padding 20px\n    text-align center\n    color #999\n\n.pullup-down-slide-wrapper\n  height 100%\n  overflow hidden\n  .pullup-pulldown-slide-bswrapper\n    position relative\n    height 100%\n    overflow hidden\n  .pullup-down-list\n    padding 0\n  .pullup-pulldown-slide-item\n    list-style none\n    width 100%\n    line-height 200px\n    text-align center\n    font-size 26px\n    transform translate3d(0,0,0)\n    backface-visibility hidden\n    box-sizing: border-box\n  .pulldown-wrapper\n    position absolute\n    width 100%\n    padding 20px\n    box-sizing border-box\n    transform translateY(-100%) translateZ(0)\n    text-align center\n    color #999\n  .pullup-wrapper\n    height 40px !important\n    padding 20px\n    text-align center\n    color #999\n  .page1\n    background-color #D6EADF\n  .page2\n    background-color #DDA789\n  .page3\n    background-color #C3D899\n  .page0\n    background-color #F2D4A7\n\n.pullup-pulldown-outnested\n  height: 100%\n  .outer-wrapper\n  .inner-wrapper\n    border: 2px solid #62B791\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    overflow: hidden\n  .outer-wrapper\n    height: 100%\n    border: 1px solid rgba(0, 0, 0, .1)\n  .inner-wrapper\n    height: 240px\n    background-color: rgba(98,183,145, 0.2)\n  .inner-list-item\n    height: 50px\n    line-height: 50px\n    text-align: center\n    list-style: none\n  .outer-list-item2,\n  .outer-list-item\n    height: 40px\n    line-height: 40px\n    text-align: center\n    list-style: none\n  .pulldown-wrapper\n    position absolute\n    width 100%\n    padding 20px\n    box-sizing border-box\n    transform translateY(-100%) translateZ(0)\n    text-align center\n    color #999\n  .pullup-wrapper\n    padding 20px\n    text-align center\n    color #999\n\n.slide-nested\n  height: 100%\n  .outer-wrapper\n  .inner-wrapper\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    overflow: hidden\n    box-sizing: border-box\n  .outer-wrapper\n    height: 100%\n    border: 1px solid rgba(0, 0, 0, .1)\n    border: 1px solid #62B791\n  .inner-wrapper\n    height: 200px\n  .outer-list-item\n    height: 40px\n    line-height: 40px\n    text-align: center\n    list-style: none\n  .slide-banner-content\n    height 200px\n    white-space nowrap\n    width: 100%\n    font-size 0\n    .slide-item\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/default.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst emojis = [\n  '😀 😁 😂 🤣 😃',\n  '😄 😅 😆 😉 😊',\n  '😫 😴 😌 😛 😜',\n  '👆🏻 😒 😓 😔 👇🏻',\n  '😑 😶 🙄 😏 😣',\n  '😞 😟 😤 😢 😭',\n  '🤑 😲 🙄 🙁 😖',\n  '👍 👎 👊 ✊ 🤛',\n  '🙄 ✋ 🤚 🖐 🖖',\n  '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n  '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n  '🌖 🌗 🌘 🌑 🌒',\n  '💫 💥 💢 💦 💧',\n  '🐠 🐟 🐬 🐳 🐋',\n  '😬 😐 😕 😯 😶',\n  '😇 😏 😑 😓 😵',\n  '🐥 🐣 🐔 🐛 🐤',\n  '💪 ✨ 🔔 ✊ ✋',\n  '👇 👊 👍 👈 👆',\n  '💛 👐 👎 👌 💘',\n  '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n  '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n  '🌖 🌗 🌘 🌑 🌒',\n  '💫 💥 💢 💦 💧',\n  '🐠 🐟 🐬 🐳 🐋',\n  '😬 😐 😕 😯 😶',\n  '😇 😏 😑 😓 😵',\n  '🐥 🐣 🐔 🐛 🐤',\n  '💪 ✨ 🔔 ✊ ✋',\n  '👇 👊 👍 👈 👆',\n  '💛 👐 👎 👌 💘',\n]\n\nconst handleClick = (item) => {\n  window.alert(item)\n}\n\nconst Default = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        probeType: 3,\n        click: true,\n      }))\n\n      BS.on('scrollStart', () => {\n        console.log('scrollStart-')\n      })\n      BS.on('scroll', ({ y }) => {\n        console.log('scrolling-')\n      })\n      BS.on('scrollEnd', (pos) => {\n        console.log(pos)\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"core-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {emojis.map((item, index) => (\n            <div\n              key={index}\n              className=\"scroll-item\"\n              onClick={() => handleClick(item)}\n            >\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/dynamic-content.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums1 = createArray(30)\nconst nums2 = createArray(60)\n\nconst DynamicContent = () => {\n  const [switcher, setSwitcher] = useState(false)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        probeType: 3,\n      }))\n\n      BS.on('contentChanged', (content) => {\n        console.log('--- newContent ---')\n        console.log(content)\n      })\n      BS.on('scroll', () => {\n        console.log('scrolling-')\n      })\n      BS.on('scrollEnd', () => {\n        console.log('scrollingEnd')\n      })\n    }\n  }, [])\n\n  useEffect(() => {\n    if (scrollRef.current) {\n      scrollRef.current.refresh()\n    }\n  }, [switcher])\n\n  const handleClick = () => {\n    setSwitcher((value) => !value)\n  }\n\n  return (\n    <div className=\"core-dynamic-content-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        {switcher ? (\n          <div className=\"scroll-content c2\" key=\"c2\">\n            {nums2.map((item) => (\n              <div key={item} className=\"scroll-item\">\n                {nums2.length - item + 1}\n              </div>\n            ))}\n          </div>\n        ) : (\n          <div className=\"scroll-content c1\" key=\"c1\">\n            {nums1.map((item) => (\n              <div key={item} className=\"scroll-item\">\n                {item}\n              </div>\n            ))}\n          </div>\n        )}\n      </div>\n      <button className=\"btn\" onClick={handleClick}>\n        switch content element\n      </button>\n    </div>\n  )\n}\n\nexport default DynamicContent\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/freescroll.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst FreeScroll = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        freeScroll: true,\n        bounce: {\n          bottom: false,\n          left: false,\n          right: false,\n          top: false,\n        },\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"free-scroll-container view\">\n      <div className=\"free-scroll-wrapper\">\n        <div className=\"scroll-wrapper\" ref={wrapperRef}>\n          <div className=\"scroll-content\">\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n            </p>\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n            </p>\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n            </p>\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n            </p>\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut\n              enim ad minim veniam, quis nostrud exercitation ullamco laboris\n              nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in\n              reprehenderit in voluptate velit esse cillum dolore eu fugiat\n              nulla pariatur. Excepteur sint occaecat cupidatat non proident,\n              sunt in culpa qui officia deserunt mollit anim id est laborum.\n            </p>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default FreeScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/horizontal-rotated.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(8)\n\nconst HorizontalRotated = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        // v2.3.0\n        quadrant: 3, // rotate 180\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"horizontal-rotated-container view\">\n      <div className=\"description\">Flipping layout via CSS</div>\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {nums.map((item, index) => (\n            <div key={index} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default HorizontalRotated\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/horizontal.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst emojis = [\n  '👉🏼 😁 😂 🤣 👈🏼',\n  '😄 😅 😆 😉 😊',\n  '😫 😴 😌 😛 😜',\n  '👆🏻 😒 😓 😔 👇🏻',\n  '😑 😶 🙄 😏 😣',\n  '😞 😟 😤 😢 😭',\n  '🤑 😲 ☹️ 🙁 😖',\n  '👍 👎 👊 ✊ 🤛',\n  '☝️ ✋ 🤚 🖐 🖖',\n  '👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼',\n  '☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽',\n  '🌖 🌗 🌘 🌑 🌒',\n]\n\nconst Horizontal = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        probeType: 3, // listening scroll event\n      }))\n\n      BS.on('scrollStart', () => {\n        console.log('scrollStart-')\n      })\n      BS.on('scroll', ({ y }) => {\n        console.log('scrolling-')\n      })\n      BS.on('scrollEnd', (pos) => {\n        console.log(pos)\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"horizontal-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {emojis.map((item, index) => (\n            <div key={index} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Horizontal\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/specified-content.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(30)\n\nconst SpecifiedContent = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        specifiedIndexAsContent: 1,\n        probeType: 3,\n      }))\n\n      BS.on('scroll', () => {\n        console.log('scrolling-')\n      })\n      BS.on('scrollEnd', () => {\n        console.log('scrollingEnd')\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"core-specified-content-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"ignore-content\">\n          The Blue area is not taken as BetterScroll's content\n        </div>\n        <div className=\"scroll-content\">\n          {nums.map((item) => (\n            <div key={item} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default SpecifiedContent\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/components/vertical-rotated.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(8)\n\nconst VerticalRotated = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        // v2.3.0\n        quadrant: 2, // rotate 90\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"vertical-rotated-container view\">\n      <div className=\"description\">Horizontal layout via CSS</div>\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {nums.map((item, index) => (\n            <div key={index} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default VerticalRotated\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/core/default',\n    name: 'vertical',\n  },\n  {\n    path: '/core/horizontal',\n    name: 'horizontal',\n  },\n  {\n    path: '/core/dynamic-content',\n    name: 'dynamic-content',\n  },\n  {\n    path: '/core/specified-content',\n    name: 'specified-content',\n  },\n  {\n    path: '/core/freescroll',\n    name: 'freescroll',\n  },\n  {\n    path: '/core/vertical-rotated',\n    name: 'vertical-rotated(v2.3.0)',\n  },\n  {\n    path: '/core/horizontal-rotated',\n    name: 'horizontal-rotated(v2.3.0)',\n  },\n]\n\nconst Core = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Core\n"
  },
  {
    "path": "packages/react-examples/src/pages/core/index.styl",
    "content": ".free-scroll-container\n  &.view\n    position fixed!important\n\n.core-container\n  .scroll-wrapper\n    height 400px\n    position relative\n    overflow hidden\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n.horizontal-container\n  .scroll-wrapper\n    position relative\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      display inline-block\n      text-align center\n      padding 0 10px\n\n\n.core-dynamic-content-container\n  text-align center\n  .scroll-wrapper\n    height 300px\n    overflow hidden\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\t.btn\n\t\tmargin 40px auto\n\t\tpadding 10px\n\t\tcolor #fff!important\n\t\tborder-radius 4px\n\t\tfont-size 20px\n\t\tbackground-color #666!important\n\n.core-specified-content-container\n  text-align center\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    border 1px solid #42b983\n    .ignore-content\n      padding 20px\n      color white\n      font-size 20px\n      font-weight bold\n      background-color #2c3e50\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n.free-scroll-container\n  position: relative\n  width: 100%\n  height: 100%\n  .free-scroll-wrapper\n    position: relative\n    width: 100%\n    height: 100%\n    border: 1px solid rgb(96, 108, 113)\n    box-sizing: border-box\n    .scroll-wrapper\n      position: absolute\n      top: 0\n      left: 0\n      right: 0\n      bottom: 0\n      overflow: hidden\n      .scroll-content\n        background-color: #efeff4\n        width: 1500px\n        height: 1000px\n        p\n          font-size: 16px\n          padding: 20px\n          line-height: 200%\n          margin: 0\n\n.vertical-rotated-container\n  .description\n    text-align center\n  .scroll-wrapper\n    height 250px\n    width 100px\n    position relative\n    overflow hidden\n    margin 0 auto\n    border 1px solid #ccc\n    transform rotate(90deg)\n    .scroll-item\n      height 100px\n      width 100px\n      line-height 100px\n      font-size 24px\n      font-weight bold\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n.horizontal-rotated-container\n  .description\n    margin-bottom 40px\n    text-align center\n  .scroll-wrapper\n    height 100px\n    width 250px\n    position relative\n    overflow hidden\n    white-space nowrap\n    margin 0 auto\n    border 1px solid #ccc\n    transform rotate(180deg)\n    .scroll-content\n      display inline-block\n    .scroll-item\n      height 100px\n      width 100px\n      line-height 100px\n      font-size 24px\n      font-weight bold\n      text-align center\n      display inline-block\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n"
  },
  {
    "path": "packages/react-examples/src/pages/form/components/textarea.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst data = createArray(10)\n\nconst Textarea = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        autoBlur: true, // for blur\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"textarea-container view\">\n      <div className=\"textarea-wrapper\" ref={wrapperRef}>\n        <div className=\"textarea-scroller\">\n          <ul className=\"textarea-list\">\n            {data.map((item) => (\n              <li key={item} className=\"textarea-list-item\"></li>\n            ))}\n            <textarea\n              className=\"textarea-text\"\n              name=\"\"\n              id=\"\"\n              cols=\"30\"\n              rows=\"10\"\n              defaultValue=\"Manipulating this area can not make BetterScroll work.\"\n            ></textarea>\n            {data.map((item) => (\n              <li key={item} className=\"textarea-list-item\"></li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Textarea\n"
  },
  {
    "path": "packages/react-examples/src/pages/form/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/form/textarea',\n    name: 'textarea',\n  },\n]\n\nconst Form = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Form\n"
  },
  {
    "path": "packages/react-examples/src/pages/form/index.styl",
    "content": ".textarea-container\n  height: 100%\n.textarea-wrapper\n  height: 100%\n  padding: 0 10px\n  border: 1px solid #ccc\n  overflow: hidden\n.textarea-list\n  padding: 0\n  text-align: center\n.textarea-list-item\n  padding: 10px 0\n  height: 40px\n  list-style: none\n  border-bottom: 1px solid #ccc\n.textarea-text\n  border: 2px solid #62B791\n  border-radius: 5px\n  margin: 40px auto 0\n  line-height: 2\n  font-size: 20px\n  height: 200px\n"
  },
  {
    "path": "packages/react-examples/src/pages/indicators/components/minimap.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\n\nimport dinnerLink from './dinner.jpg'\n\nBScroll.use(Indicators)\n\nconst Minimap = () => {\n  const wrapperRef = useRef(null)\n  const indicatorWrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        startX: -50,\n        startY: -50,\n        freeScroll: true,\n        bounce: false,\n        indicators: [\n          {\n            relationElement: indicatorWrapperRef.current,\n            // choose div.scroll-indicator-handle as indicatorHandle\n            relationElementHandleElementIndex: 1,\n          },\n        ],\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"minimap-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        {/* maxWidth is used to overwrite vuepress default theme style */}\n        {/* because this component is used in vuepress markdown as a demo */}\n        <img className=\"scroll-content\" src={dinnerLink} alt=\"custom\" />\n      </div>\n      <div className=\"scroll-indicator\" ref={indicatorWrapperRef}>\n        <img className=\"scroll-indicator-bg\" src={dinnerLink} alt=\"custom\" />\n        <div className=\"scroll-indicator-handle\"></div>\n      </div>\n    </div>\n  )\n}\n\nexport default Minimap\n"
  },
  {
    "path": "packages/react-examples/src/pages/indicators/components/parallax-scroll.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\n\nBScroll.use(Indicators)\n\nconst ParallaxScroll = () => {\n  const wrapperRef = useRef(null)\n  const indicator1Ref = useRef(null)\n  const indicator2Ref = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        freeScroll: true,\n        bounce: false,\n        indicators: [\n          {\n            relationElement: indicator1Ref.current,\n            interactive: false,\n            ratio: 0.4,\n          },\n          {\n            relationElement: indicator2Ref.current,\n            interactive: false,\n            ratio: 0.2,\n          },\n        ],\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"parallax-scroll-container view\">\n      <div className=\"parallax-scroll-box\">\n        <div className=\"scroll-wrapper\" ref={wrapperRef}>\n          <div className=\"scroll-content\"></div>\n        </div>\n        <div className=\"scroll-indicator stars1\" ref={indicator1Ref}>\n          <div className=\"star1-bg\"></div>\n        </div>\n        <div className=\"scroll-indicator stars2\" ref={indicator2Ref}>\n          <div className=\"star2-bg\"></div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default ParallaxScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/indicators/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/indicators/minimap',\n    name: 'minimap',\n  },\n  {\n    path: '/indicators/parallax-scroll',\n    name: 'parallax scroll',\n  },\n]\n\nconst Indicators = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Indicators\n"
  },
  {
    "path": "packages/react-examples/src/pages/indicators/index.styl",
    "content": ".minimap-container\n  .scroll-wrapper\n    width 320px\n    height 180px\n    overflow hidden\n  .scroll-content\n    width 1920px\n    height 1080px\n  .scroll-indicator\n    margin-top 15px\n    width 320px\n    height 180px\n    position relative\n  .scroll-indicator-bg\n    position absolute\n    width 100%\n    height 100%\n  .scroll-indicator-handle\n    position absolute\n    border 1px solid white\n    box-shadow 0 0 5px white\n    width 64px\n    height 36px\n    z-index 1\n    background-color rgba(255, 255, 255, 0.3)\n\n.parallax-scroll-container\n  height 100%\n  .parallax-scroll-box\n    position relative\n    width 100%\n    height 100%\n    box-sizing border-box\n    border 1px solid #fe0\n  .scroll-wrapper\n    position absolute\n    z-index 3\n    top 0\n    right 0\n    bottom 0\n    left 0\n    overflow hidden\n  .scroll-content\n    width 100%\n    height 4000px\n    background: url('./components/galaxies1.png')\n  .scroll-indicator\n    position absolute\n    top 0\n    right 0\n    bottom 0\n    left 0\n    overflow hidden\n    &.stars1\n      z-index 2\n    &.stars2\n      z-index 1\n  .star1-bg\n    height 3000px\n    background: url('./components/galaxies2.png')\n  .star2-bg\n    height 2000px\n    background: url('./components/stars.jpg')\n"
  },
  {
    "path": "packages/react-examples/src/pages/infinity/data/message.json",
    "content": "[\n  \"when you popState and actually being well, we expect it further\",\n  \"But I'm going to take care of ripping out my code in the fact that just something like that\",\n  \"And what we'll createdCallbacks than that you can still read what each one of this should go out\",\n  \"So just return Promise back and do this, the route equals\",\n  \"ah, let's do a clearRoutes it says I'm not going to do\",\n  \"At least trying new Promise\",\n  \"then, and then it's going to check what that\",\n  \"And we zoom in, then you can kind of set, except for a router\",\n  \"Now strictly today\",\n  \"I'm going to just takes an iterable as well be to add a visible\",\n  \"Anyway, so that we'll do a link\",\n  \"So what I'm going to minify this, so I'll just console\",\n  \"log data for now, just sometimes look at that\",\n  \"not then if we wanted to do position from the registerElements primed and red\",\n  \"That isn't get called\",\n  \"At all\",\n  \"No\",\n  \"Interesting that misc here\",\n  \"So what was a regular expression\",\n  \"Because once you get over doing a fancy techniques\",\n  \"And let's see\",\n  \"OK, we broke thing to do\",\n  \"Right\",\n  \"document\",\n  \"&quot; So\",\n  \"Yeah\",\n  \"\",\n  \"which is fine\",\n  \"And that we'll do sc\",\n  \"view\",\n  \"So what you draw the line where is it\",\n  \"Where is being run\",\n  \"I think, a million times look at it and styles an iteration, ES2015 update the content for is this\",\n  \"routes equals Array\",\n  \"from\",\n  \"Hm, that might be a trade\",\n  \"off, because we're just do an animation\",\n  \"in the attached\",\n  \"Look at this push\",\n  \"pull kind of useful to have layout root here is it\",\n  \"That by default, what we going to grab the\",\n  \"Yes\",\n  \"In router, I think, would let's say, for example\",\n  \"So let's make it can be just this the hour mark on the actual contents\",\n  \"We just loads though it was the way, a nice this\",\n  \"Are you would be a little bit more pretty raw, this is a day, dude\",\n  \"Border\",\n  \"radius, that\",\n  \"And I'm going to just do that will take something else\",\n  \"And thank you might now\",\n  \"That is the next time, I'm going to come into misc\",\n  \"And somebody actually not\",\n  \"source equals home\",\n  \"But if I was sending me to resolve where we go\",\n  \"All right\",\n  \"And it makes JavaScript\",\n  \"And I have run again\",\n  \"Normally a massive, as I said, this is always, I'm going to call the different [INAUDIBLE] Hm\",\n  \"Wow\",\n  \"We have happen on screen, and the otherwise, don't want\",\n  \"Yeah, and forth in the new path\",\n  \"So we don't you use that might very wrong\",\n  \"But in a customary bug\",\n  \"Don't forget to hidden or display to none, things like a race when you are actually really long time I want to tell that is where you go\",\n  \"And that work\",\n  \"Yeah, and I'm going to do today\",\n  \"I had misc are all the create one of the performance stuff\",\n  \"But if you had lots of tea\",\n  \"Yeah\",\n  \"Now we're going to come in\",\n  \"But did working as intended it\",\n  \"So we can be able to be watching it straightforward slash\",\n  \"And that, I think that will be all the like since we are valid concept for this, the root of this called HTML5 routing, which I don't know\",\n  \"I just feels OK, but hopefully, and opacity 0, and it's just put a z\",\n  \"index of 1 on that's going to be sort of handling of attachedCallback, and we want to transform scale very well be true for them is amazing, like across from the new one that\",\n  \"You know\",\n  \"Yeah, we could see now, all being we won't do this thing today\",\n  \"And so this is a current view\",\n  \"We have a question ties in\",\n  \"Why not\",\n  \"source equals router, why not\",\n  \"And I think that we'd probably, if we've already to allow it to be the thing\",\n  \"Oh, all right, so we get it, because I have to juggle it all\",\n  \"No\",\n  \"I feel I agree\",\n  \"It would actually get it, because otherwise, we still have this\",\n  \"routes\",\n  \"keys\",\n  \"So this is a layout boundary\",\n  \"It's the cause\",\n  \"Yeah, 3 pixels\",\n  \"OK\",\n  \"So since that's true\",\n  \"And this stuff\",\n  \"And that work\",\n  \"Good point, or strict, and then the URL, changed\",\n  \"But I'm going to, let's see, what we're any\",\n  \"So the new view, think about\",\n  \"And then we've defer, why not\",\n  \"Let's fail\",\n  \"So this newView, newView is never watching is I was that\",\n  \"so that it's a compass\",\n  \"Oh\",\n  \"North, east, south, we called, all be no ES\",\n  \"anything\",\n  \"What I'm curious about your question here\",\n  \"And I'm going to say\",\n  \"so let's see\",\n  \"So let's see\",\n  \"So we'll say from this animations that we want to do this so that this point\",\n  \"So we want us to cover next week\",\n  \"We can actually\",\n  \"But that they've all been set it\",\n  \"Yeah\",\n  \"And at the top and misc here\",\n  \"But it will be run into a bit different sections\",\n  \"And I think you'd want each of there's no DOM tree reason\",\n  \"Well, yeah\",\n  \"OK, so we have a couple of click for clicks\",\n  \"And so if we see about this\",\n  \"So what I think things that I really good start\",\n  \"script tags at home, kids\",\n  \"Don't do this file to actually\",\n  \"Woo\",\n  \"I made, sir\",\n  \"So again, particular line of the\",\n  \"let's call it sc for Supercharged\",\n  \"There's no\",\n  \"It's a compass\",\n  \"Oh\",\n  \"right\",\n  \"newView, newView is the simplicity at this one anything below 2015, right\",\n  \"It broke\",\n  \"OK, let's see\",\n  \"So we're going to removeEventListener\",\n  \"You are the nicest\",\n  \"something that you know, we'll create that doesn't necessarily end up with something new to these pages\",\n  \"In router\",\n  \"And certainly, as I said, you could usually just delete the constructor but createdCallback\",\n  \"Oh, well, let link of the\",\n  \"Yes\",\n  \"If we had to do is I want us to come up writing apps, it can actually, this push\",\n  \"pull kind of data, which version of something\",\n  \"So what they can be about view or something that have a thing to do a trade\",\n  \"off because you've got memory constraints and all these function\",\n  \"So let's see if\",\n  \"oh, do we wanted to do this\",\n  \"If you're attach, what we'd want to know\",\n  \"That is important think in so that goes to control of [\",\n  \"UI \",\n  \"] transitions, particular expression\",\n  \"Right, so the otherwise, it should also work on the layout, which might because we're actually remind yourselves that I can do it\",\n  \"Yeah\",\n  \"So that, in theory, place all the content as well when that have new ideas\",\n  \"So this should be a class list, we'll create one of these, what we'll do is I want to do\",\n  \"All right, bottom, left\",\n  \"Do you have definitely\",\n  \"So when the mindset off chaining [INAUDIBLE] out of the same index HTML elements\",\n  \"Views\",\n  \"Yeah\",\n  \"So I'm going to createRoutes, wee, clearRoutes equals static\",\n  \"Let's do this, status is generally work\",\n  \"So that's why I was building the nicest\",\n  \"I'll tell you what we want to come into the panels\",\n  \"On all of ES2015 updates on the path name\",\n  \"Because it's an iterate what they see\",\n  \"I'm going to do\",\n  \"We'll do that\",\n  \"And hopefully, you're here in slash about view but we're going to be whichever view was the new view is that\",\n  \"so that the event that isn't get called, all subscribing to do today\",\n  \"And then we're just delete the JavaScript language\",\n  \"Yeah, and we need to extends HTMLElement\",\n  \"And we app where we actually uncanny valid concept for the out animation\",\n  \"duration\",\n  \"count in one tends HTML, I think, would then we've defer, why not\",\n  \"Let's see what's good on here\",\n  \"So if you say layout, for example\",\n  \"Yes, so one of its scope\",\n  \"What we want to do, I supposed to find out\",\n  \"The defer mean to your Custom Elements JavaScript says we don't have\",\n  \"We don't want to say this, so one that you click back to then dot the even though it\",\n  \"So there a createdCallback, so we never being us\",\n  \"That doesn't it\",\n  \"Right\",\n  \"All right\",\n  \"That should\",\n  \"Oh no, Array\",\n  \"from\",\n  \"Hm, it shouldNotMakeMoreOutPromises\",\n  \"And then let's do that is purely for simplicity at this\",\n  \"I don't takes too longer and I will say this\",\n  \"routes\",\n  \"because it matches the current ones will now needs to be run against that going to say const view back\",\n  \"And then what the createRoute\",\n  \"That's what I think\",\n  \"So we have to transitions, particular if branch of this, you're giving us way too much better\",\n  \"So since the layout, OK\",\n  \"I think we'll create objects anymore\",\n  \"You let us know what I'm going to do is I'm going to do is let's just find out\",\n  \"createdCallbacks\",\n  \"So if view\",\n  \"I could do if we don't want to make a nav\",\n  \"So I'm going to do that\",\n  \"Super\",\n  \"route\",\n  \"So for this, right now, all the like shouldNotMakeMoreOutPromise\",\n  \"resolve\",\n  \"Same for the power of Promise, right\",\n  \"Because why not\",\n  \"Let's give it or not\",\n  \"The defer also means that the state by selecting the view\",\n  \"No\",\n  \"Interesting\",\n  \"So the brand\",\n  \"new thing\",\n  \"So let's see, so we do that\",\n  \"All being well, we end with an actually hoping I will be remove this\",\n  \"Are you this\",\n  \"So we want to do that, actually just kind of amazing\",\n  \"You know\",\n  \"Yeah\",\n  \"\",\n  \"which is the current view was the new one that's a layout\",\n  \"I don't you ask the question ties in it is when it's like a progressive to deal with, with contain strict\",\n  \"now here\",\n  \"And I'm going to us\",\n  \"So onChanged\",\n  \"Yeah\",\n  \"Because of the this\",\n  \"is\",\n  \"the\",\n  \"active\",\n  \"view\",\n  \"And we are building the routes equals this\",\n  \"But when the view first time we create that isn't it\",\n  \"Right\",\n  \"Yeah, that is amazing\",\n  \"And I think, a more bugs\",\n  \"Yeah, I want it to updating to do that I have new view, and some Promise, we can actually can do here\",\n  \"This is Paul\",\n  \"Hi\",\n  \"This time I write bugs, don't like this is actual lifecycle called ES6\",\n  \"ES2016 was doing that's why I wanted to say\",\n  \"currentView will be fast because\",\n  \"You know what, in the back to the current view\",\n  \"And then we'll say return\",\n  \"One of the panels\",\n  \"OK\",\n  \"Come of that stuff out\",\n  \"Should that the evaluation from 100\",\n  \"no, should add that kind of got allowing that back out, right\",\n  \"newView, newView, what we're kind of got these views that you, very wrong\",\n  \"But if you about using there\",\n  \"Because the nav has disappear ago, it was the keyword for all the regular expression and execution of a router\",\n  \"Now you know, over that, in there\",\n  \"Let's do that there we already got ourselves some of the way to go\",\n  \"And it matches the new one for that\",\n  \"Yeah\",\n  \"And certain time gaps, think it's an animating to put a route for some reason\",\n  \"view\",\n  \"Figure out things simplicity at this point\",\n  \"So what we're being a little bit of a pickle over right now we've deep\",\n  \"linked that could want it to be that\",\n  \"So let's just feels very interactions back in so this\",\n  \"newView\",\n  \"Yeah\",\n  \"And apparent, what we'd want each one of all the debugger standard one\",\n  \"So this way, it should add the visible\",\n  \"And we're pretty raw, there will be find out notionally, the code, it's fine, it's fail\",\n  \"So the question\",\n  \"Yeah, so we could see now them to makes Jav\"\n]\n"
  },
  {
    "path": "packages/react-examples/src/pages/infinity/index.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport InfinityScroll from '@better-scroll/infinity'\n\nimport message from './data/message.json'\n\nimport './index.styl'\n\nBScroll.use(InfinityScroll)\n\nconst NUM_AVATARS = 4\nconst NUM_IMAGES = 77\nconst INIT_TIME = new Date().getTime()\n\nfunction getItem(id) {\n  function pickRandom(a) {\n    return a[Math.floor(Math.random() * a.length)]\n  }\n\n  return new Promise((resolve) => {\n    const item = {\n      id: id,\n      avatar: Math.floor(Math.random() * NUM_AVATARS),\n      self: Math.random() < 0.1,\n      image:\n        Math.random() < 1.0 / 20 ? Math.floor(Math.random() * NUM_IMAGES) : '',\n      time: new Date(\n        Math.floor(INIT_TIME + id * 20 * 1000 + Math.random() * 20 * 1000)\n      ),\n      message: pickRandom(message),\n    }\n    if (item.image === '') {\n      resolve(item)\n    } else {\n      let image = new Image()\n      image.src = require(`./image/image${item.image}.jpg`)\n      image.addEventListener('load', function () {\n        item.image = image\n        resolve(item)\n      })\n      image.addEventListener('error', function () {\n        item.image = ''\n        resolve(item)\n      })\n    }\n  })\n}\n\nlet nextItem = 0\nlet pageNum = 0\n\nconst InfinityPage = () => {\n  const messageRef = useRef(null)\n  const tombstoneRef = useRef(null)\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        infinity: {\n          render: (item, div) => {\n            div = div || messageRef.current.cloneNode(true)\n            div.dataset.id = item.id\n            div.querySelector(\n              '.infinity-avatar'\n            ).src = require(`./image/avatar${item.avatar}.jpg`)\n            div.querySelector('.infinity-bubble p').textContent =\n              item.id + '  ' + item.message\n            div.querySelector(\n              '.infinity-bubble .infinity-posted-date'\n            ).textContent = item.time.toString()\n\n            let img = div.querySelector('.infinity-bubble img')\n            if (item.image !== '') {\n              img.style.display = ''\n              img.src = item.image.src\n              img.width = item.image.width\n              img.height = item.image.height\n            } else {\n              img.src = ''\n              img.style.display = 'none'\n            }\n\n            if (item.self) {\n              div.classList.add('infinity-from-me')\n            } else {\n              div.classList.remove('infinity-from-me')\n            }\n            return div\n          },\n          createTombstone: () => tombstoneRef.current.cloneNode(true),\n          fetch: (count) => {\n            count = Math.max(30, count)\n            return new Promise((resolve, reject) => {\n              // Assume 50 ms per item.\n              setTimeout(() => {\n                if (++pageNum > 20) {\n                  resolve(false)\n                } else {\n                  console.log('pageNum', pageNum)\n                  let items = []\n                  for (let i = 0; i < Math.abs(count); i++) {\n                    items[i] = getItem(nextItem++)\n                  }\n                  resolve(Promise.all(items))\n                }\n              }, 500)\n            })\n          },\n        },\n      }))\n\n      BS.on('scroll', () => {\n        console.log('is scrolling')\n      })\n      BS.on('scrollEnd', () => {\n        console.log('scrollEnd')\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"infinity view\">\n      <div className=\"template\">\n        <li ref={messageRef} className=\"infinity-item\">\n          <img className=\"infinity-avatar\" width=\"48\" height=\"48\" alt=\"\" />\n          <div className=\"infinity-bubble\">\n            <p></p>\n            <img width=\"300\" height=\"300\" alt=\"\" />\n            <div className=\"infinity-meta\">\n              <time className=\"infinity-posted-date\"></time>\n            </div>\n          </div>\n        </li>\n        <li ref={tombstoneRef} className=\"infinity-item tombstone\">\n          <img\n            className=\"infinity-avatar\"\n            width=\"48\"\n            height=\"48\"\n            src={require('./image/unknown.jpg')}\n            alt=\"\"\n          />\n          <div className=\"infinity-bubble\">\n            <p></p>\n            <p></p>\n            <p></p>\n            <div className=\"infinity-meta\">\n              <time className=\"infinity-posted-date\"></time>\n            </div>\n          </div>\n        </li>\n      </div>\n      <div ref={wrapperRef} className=\"infinity-timeline\">\n        <ul></ul>\n      </div>\n    </div>\n  )\n}\n\nexport default InfinityPage\n"
  },
  {
    "path": "packages/react-examples/src/pages/infinity/index.styl",
    "content": ".infinity\n  height: 100%\n  .template\n    display: none\n\n.infinity-timeline\n  position: relative\n  height: 100%\n  padding: 0 10px\n  border: 1px solid #ccc\n  overflow: hidden\n  will-change: transform\n  background-color: #efeff5\n\n.infinity-timeline > ul\n  position: relative\n  -webkit-backface-visibility: hidden\n  -webkit-transform-style: flat\n\n.infinity-item\n  display: flex\n  left: 0\n  padding: 10px 0\n  width: 100%\n  contain: layout\n  will-change: transform\n  list-style: none\n\n.infinity-avatar\n  border-radius: 500px\n  margin-left: 20px\n  margin-right: 6px\n  min-width: 48px\n\n.infinity-item\n  p\n    margin: 0\n    word-wrap: break-word\n    font-size: 13px\n\n.infinity-item.tombstone\n  p\n    width: 100%\n    height: 0.5em\n    background-color: #ccc\n    margin: 0.5em 0\n\n.infinity-bubble img\n  max-width: 100%\n  height: auto\n\n.infinity-bubble\n  padding: 7px 10px\n  color: #333\n  background: #fff\n  /*box-shadow: 0 3px 2px rgba(0, 0, 0, 0.1)*/\n  position: relative\n  max-width: 420px\n  min-width: 80px\n  margin: 0 5px\n\n.infinity-bubble::before\n  content: ''\n  border-style: solid\n  border-width: 0 10px 10px 0\n  border-color: transparent #fff transparent transparent\n  position: absolute\n  top: 0\n  left: -10px\n\n.infinity-meta\n  font-size: 0.8rem\n  color: #999\n  margin-top: 3px\n\n.infinity-from-me\n  justify-content: flex-end\n\n.infinity-from-me .infinity-avatar\n  order: 1\n  margin-left: 6px\n  margin-right: 20px\n\n.infinity-from-me .infinity-bubble\n  background: #F9D7FF\n\n.infinity-from-me .infinity-bubble::before\n  left: 100%\n  border-width: 10px 10px 0 0\n  /*border-color: #F9D7FF transparent transparent transparent*/\n\n.infinity-state\n  display: none\n\n.infinity-invisible\n  display: none\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/horizontal-scroll.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(MouseWheel)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst data = createArray(100)\n\nconst HorizontalScroll = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    scrollRef.current = new BScroll(wrapperRef.current, {\n      scrollX: true,\n      scrollY: false,\n      mouseWheel: true,\n    })\n  }, [])\n\n  return (\n    <div className=\"mouse-wheel-horizontal-scroll view\">\n      <div className=\"mouse-wheel-wrapper\" ref={wrapperRef}>\n        <div className=\"mouse-wheel-content\">\n          {data.map((item, index) => (\n            <div key={index} className=\"mouse-wheel-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default HorizontalScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/horizontal-slide.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(Slide)\nBScroll.use(MouseWheel)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst HorizontalSlide = () => {\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          loop: true,\n          threshold: 100,\n        },\n        useTransition: false,\n        momentum: false,\n        bounce: false,\n        stopPropagation: true,\n        mouseWheel: {\n          speed: 2,\n          invert: false,\n          easeTime: 300,\n        },\n      }))\n\n      BS.on('scrollEnd', () => {\n        setCurrentPageIndex(BS.getCurrentPage().pageY)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"mouse-wheel-horizontal-slide view\">\n      <div className=\"slide-container\">\n        <div className=\"slide-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {nums.map((num) => (\n            <span\n              key={num}\n              className={classNames('dot', {\n                active: currentPageIndex === num - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default HorizontalSlide\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/picker.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport { CSSTransition } from 'react-transition-group'\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(Wheel)\nBScroll.use(MouseWheel)\n\nconst DATA = [\n  {\n    text: 'Venomancer',\n    value: 1,\n    disabled: 'wheel-disabled-item',\n  },\n  {\n    text: 'Nerubian Weaver',\n    value: 2,\n  },\n  {\n    text: 'Spectre',\n    value: 3,\n  },\n  {\n    text: 'Juggernaut',\n    value: 4,\n  },\n  {\n    text: 'Karl',\n    value: 5,\n  },\n  {\n    text: 'Zeus',\n    value: 6,\n  },\n  {\n    text: 'Witch Doctor',\n    value: 7,\n  },\n  {\n    text: 'Lich',\n    value: 8,\n  },\n  {\n    text: 'Oracle',\n    value: 9,\n  },\n  {\n    text: 'Earthshaker',\n    value: 10,\n  },\n]\n\nconst stopPropagation = (e) => {\n  e.stopPropagation()\n}\n\nconst preventDefault = (e) => {\n  e.preventDefault()\n}\n\nconst OneColumn = () => {\n  const [visible, setVisible] = useState(false)\n  const [selectedIndex, setSelectedIndex] = useState(2)\n  const [selectedText, setSelectedText] = useState('open')\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (visible) {\n      if (!scrollRef.current) {\n        const wrapper = wrapperRef.current.children[0]\n        const BS = (scrollRef.current = new BScroll(wrapper, {\n          wheel: {\n            wheelWrapperClass: 'wheel-scroll',\n            wheelItemClass: 'wheel-item',\n            wheelDisabledItemClass: 'wheel-disabled-item',\n            selectedIndex,\n          },\n          useTransition: false,\n          probeType: 3,\n        }))\n\n        BS.on('scrollEnd', () => {\n          console.log('BS.getSelectedIndex()', BS.getSelectedIndex())\n        })\n      } else {\n        scrollRef.current.refresh()\n      }\n    }\n  }, [visible, selectedIndex])\n\n  const handleShow = () => {\n    if (visible) {\n      return\n    }\n    setVisible(true)\n  }\n\n  const handleHide = () => {\n    setVisible(false)\n  }\n\n  const handleConfirm = () => {\n    scrollRef.current.stop()\n    handleHide()\n    const currentSelectedIndex = scrollRef.current.getSelectedIndex()\n    setSelectedIndex(currentSelectedIndex)\n    setSelectedText(`${DATA[currentSelectedIndex].text}`)\n  }\n\n  const handleCancel = () => {\n    scrollRef.current.restorePosition()\n    handleHide()\n  }\n\n  return (\n    <div className=\"container view\">\n      <div className=\"container-btn\" onClick={handleShow}>\n        {selectedText}\n      </div>\n      <CSSTransition\n        in={visible}\n        classNames=\"picker-fade\"\n        timeout={300}\n        onEnter={(node) => {\n          node.style.display = 'block'\n        }}\n        onExited={(node) => {\n          node.style.display = ''\n        }}\n      >\n        <div\n          className=\"picker\"\n          onClick={handleCancel}\n          onTouchMove={preventDefault}\n        >\n          <CSSTransition in={visible} classNames=\"picker-move\" timeout={300}>\n            <div className=\"picker-panel\" onClick={stopPropagation}>\n              <div className=\"picker-choose border-bottom-1px\">\n                <span className=\"cancel\" onClick={handleCancel}>\n                  Cancel\n                </span>\n                <span className=\"confirm\" onClick={handleConfirm}>\n                  Confirm\n                </span>\n                <h1 className=\"picker-title\">Title</h1>\n              </div>\n              <div className=\"picker-content\">\n                <div className=\"mask-top border-bottom-1px\"></div>\n                <div className=\"mask-bottom border-top-1px\"></div>\n                <div className=\"wheel-wrapper\" ref={wrapperRef}>\n                  <div className=\"wheel\">\n                    <ul className=\"wheel-scroll\">\n                      {DATA.map((item, index) => (\n                        <li\n                          key={index}\n                          className={classNames([\n                            'wheel-item',\n                            { 'wheel-disabled-item': item.disabled },\n                          ])}\n                        >\n                          {item.text}\n                        </li>\n                      ))}\n                    </ul>\n                  </div>\n                </div>\n              </div>\n              <div className=\"picker-footer\"></div>\n            </div>\n          </CSSTransition>\n        </div>\n      </CSSTransition>\n    </div>\n  )\n}\n\nexport default OneColumn\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/pulldown.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(PullDown)\nBScroll.use(MouseWheel)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const dataList = generateData()\n      resolve(dataList)\n    }, 1000)\n  })\n}\n\nconst TIME_BOUNCE = 800\nlet STEP = 0\n\nfunction generateData() {\n  const BASE = 30\n  const begin = BASE * STEP\n  const end = BASE * (STEP + 1)\n  let ret = []\n  for (let i = end; i > begin; i--) {\n    ret.push(i)\n  }\n  return ret\n}\n\nconst Pulldown = () => {\n  const [beforePullDown, setBeforePullDown] = useState(true)\n  const [isPullingDown, setIsPullingDown] = useState(false)\n  const [data, setData] = useState(generateData())\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async () => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      setData((prev) => newData.concat(prev))\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const finishPullDown = () => {\n    scrollRef.current.finishPullDown()\n    setTimeout(() => {\n      setBeforePullDown(true)\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 100)\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    setBeforePullDown(false)\n    setIsPullingDown(true)\n    STEP += 1\n    await requestData()\n    setIsPullingDown(false)\n    finishPullDown()\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        pullDownRefresh: {\n          threshold: 70,\n          stop: 56,\n        },\n        mouseWheel: true,\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n\n      BS.on('scroll', (pos) => {\n        console.log(pos.y)\n      })\n\n      BS.on('scrollEnd', () => {\n        console.log('scrollEnd')\n      })\n    }\n  }, [pullingDownHandler])\n\n  return (\n    <div className=\"mouse-wheel-pulldown view\">\n      <div className=\"pulldown-wrapper\" ref={wrapperRef}>\n        <div className=\"pulldown-content\">\n          <div className=\"pulldown-tips\">\n            <div style={{ display: beforePullDown ? '' : 'none' }}>\n              <span>Pull Down and refresh</span>\n            </div>\n            <div style={{ display: beforePullDown ? 'none' : '' }}>\n              <div v-show=\"isPullingDown\">\n                <span>Loading...</span>\n              </div>\n              <div style={{ display: isPullingDown ? 'none' : '' }}>\n                <span>Refresh success</span>\n              </div>\n            </div>\n          </div>\n          <ul className=\"pulldown-list\">\n            {data.map((item, index) => (\n              <li key={index} className=\"pulldown-list-item\">\n                {`I am item ${item} `}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Pulldown\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/pullup.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Pullup from '@better-scroll/pull-up'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(Pullup)\nBScroll.use(MouseWheel)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(20)\n    }, 1000)\n  })\n}\n\nconst PullupPage = () => {\n  const [isPullUpLoad, setIsPullUpLoad] = useState(false)\n  const [num, setNum] = useState(30)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async () => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      setNum((n) => n + newData)\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const pullingUpHandler = useStableCallback(async () => {\n    setIsPullUpLoad(true)\n    await requestData()\n    scrollRef.current.finishPullUp()\n    scrollRef.current.refresh()\n    setIsPullUpLoad(false)\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        probeType: 3,\n        pullUpLoad: true,\n        mouseWheel: true,\n      }))\n\n      BS.on('pullingUp', pullingUpHandler)\n    }\n  }, [pullingUpHandler])\n\n  return (\n    <div className=\"mouse-wheel-pullup view\">\n      <div className=\"pullup-wrapper\" ref={wrapperRef}>\n        <div className=\"pullup-content\">\n          <ul className=\"pullup-list\">\n            {createArray(num).map((item, index) => (\n              <li key={index} className=\"pullup-list-item\">\n                {item % 5 === 0 ? 'scroll up 👆🏻' : `I am item ${item} `}\n              </li>\n            ))}\n          </ul>\n          <div className=\"pullup-tips\">\n            {isPullUpLoad ? (\n              <div className=\"after-trigger\">\n                <span className=\"pullup-txt\">Loading...</span>\n              </div>\n            ) : (\n              <div className=\"before-trigger\">\n                <span className=\"pullup-txt\">Pull up and load more</span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default PullupPage\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/vertical-scroll.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(MouseWheel)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst data = createArray(100)\n\nconst VerticalScroll = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    scrollRef.current = new BScroll(wrapperRef.current, {\n      mouseWheel: true,\n    })\n  }, [])\n\n  return (\n    <div className=\"mouse-wheel-vertical-scroll view\">\n      <div className=\"mouse-wheel-wrapper\" ref={wrapperRef}>\n        <div className=\"mouse-wheel-content\">\n          {data.map((item, index) => (\n            <div key={index} className=\"mouse-wheel-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default VerticalScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/components/vertical-slide.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nBScroll.use(Slide)\nBScroll.use(MouseWheel)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst VerticalSlide = () => {\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: false,\n        scrollY: true,\n        slide: {\n          loop: true,\n          threshold: 100,\n        },\n        mouseWheel: true,\n        momentum: false,\n        bounce: false,\n        stopPropagation: true,\n      }))\n\n      BS.on('scrollEnd', () => {\n        setCurrentPageIndex(BS.getCurrentPage().pageY)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"mouse-wheel-slide-vertical view\">\n      <div className=\"slide-container\">\n        <div className=\"slide-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {nums.map((num) => (\n            <span\n              key={num}\n              className={classNames('dot', {\n                active: currentPageIndex === num - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default VerticalSlide\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/mouse-wheel/vertical-scroll',\n    name: 'vertical scroll',\n  },\n  {\n    path: '/mouse-wheel/horizontal-scroll',\n    name: 'horizontal scroll',\n  },\n  {\n    path: '/mouse-wheel/vertical-slide',\n    name: 'vertical slide',\n  },\n  {\n    path: '/mouse-wheel/horizontal-slide',\n    name: 'horizontal slide',\n  },\n  {\n    path: '/mouse-wheel/pullup',\n    name: 'pull up load',\n  },\n  {\n    path: '/mouse-wheel/pulldown',\n    name: 'pull down refresh',\n  },\n  {\n    path: '/mouse-wheel/picker',\n    name: 'picker',\n  },\n]\n\nconst MouseWheel = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default MouseWheel\n"
  },
  {
    "path": "packages/react-examples/src/pages/mouse-wheel/index.styl",
    "content": ".mouse-wheel-vertical-scroll\n  .mouse-wheel-wrapper\n    height 400px\n    overflow hidden\n    .mouse-wheel-item\n      height 50px\n      line-height 50px\n      font-size 20px\n      font-weight bold\n      text-align center\n      &:nth-child(2n)\n        background-color #C3D899\n      &:nth-child(2n+1)\n        background-color #F2D4A7\n\n.mouse-wheel-horizontal-scroll\n  .mouse-wheel-wrapper\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .mouse-wheel-content\n      display inline-block\n      .mouse-wheel-item\n        height 50px\n        line-height 50px\n        font-size 24px\n        display inline-block\n        text-align center\n        padding 0 20px\n        &:nth-child(2n)\n          background-color #C3D899\n        &:nth-child(2n+1)\n          background-color #F2D4A7\n\n.mouse-wheel-slide-vertical\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .slide-container\n    position relative\n    height 100%\n    font-size 0\n  .slide-wrapper\n    height 100%\n    overflow hidden\n    .slide-page\n      display inline-block\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #D6EADF\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    right 4px\n    top 50%\n    transform translateY(-50%)\n    .dot\n      display block\n      margin 4px 0\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        height  20px\n        border-radius 5px\n\n.mouse-wheel-horizontal-slide\n  .slide-container\n    position relative\n  .slide-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n\n.mouse-wheel-pullup\n  height: 100%\n  .pullup-wrapper\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pullup-list\n    padding: 0\n  .pullup-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pullup-tips\n    padding: 20px\n    text-align: center\n    color: #999\n\n.mouse-wheel-pulldown\n  height: 100%\n  .pulldown-wrapper\n    position: relative\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pulldown-list\n    padding: 0\n  .pulldown-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pulldown-tips\n    position: absolute\n    width: 100%\n    padding: 20px\n    box-sizing: border-box\n    transform: translateY(-100%) translateZ(0)\n    text-align: center\n    color: #999\n\n/* reset */\nul\n  list-style none\n  padding 0\n\n.border-bottom-1px, .border-top-1px\n  position: relative\n  &:before, &:after\n    content: \"\"\n    display: block\n    position: absolute\n    transform-origin: 0 0\n.border-bottom-1px\n  &:after\n    border-bottom: 1px solid #ebebeb\n    left: 0\n    bottom: 0\n    width: 100%\n    transform-origin: 0 bottom\n.border-top-1px\n  &:before\n    border-top: 1px solid #ebebeb\n    left: 0\n    top: 0\n    width: 100%\n    transform-origin: 0 top\n@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)\n  .border-top-1px\n    &:before\n      width: 200%\n      transform: scale(.5) translateZ(0)\n  .border-bottom-1px\n    &:after\n      width: 200%\n      transform: scale(.5) translateZ(0)\n\n@media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)\n  .border-top-1px\n    &:before\n      width: 300%\n      transform: scale(.333) translateZ(0)\n  .border-bottom-1px\n    &:after\n      width: 300%\n      transform: scale(.333) translateZ(0)\n.container-btn\n  margin: 2rem\n  background-color white\n  padding: 0.8rem\n  border: 1px solid rgba(0, 0, 0, .1)\n  box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n  text-align: center\n\n.picker\n  display: none\n  position: fixed\n  left: 0\n  top: 0\n  z-index: 100\n  width: 100%\n  height: 100%\n  overflow: hidden\n  text-align: center\n  font-size: 14px\n  background-color: rgba(37, 38, 45, .4)\n  &.picker-fade-enter\n    opacity: 0\n  &.picker-fade-enter-active\n    opacity: 1\n    transition: all .3s ease-in-out\n  &.picker-fade-exit-active\n    opacity: 0;\n    transition: all .3s ease-in-out\n\n\n  .picker-panel\n    position: absolute\n    z-index: 600\n    bottom: 0\n    width: 100%\n    height: 273px\n    background: white\n    &.picker-move-enter\n      transform: translate3d(0, 273px, 0)\n    &.picker-move-enter-active\n      transform: translate3d(0, 0, 0)\n      transition: all .3s ease-in-out\n    &.picker-move-exit-active\n      transform: translate3d(0, 273px, 0)\n      transition: all .3s ease-in-out\n    .picker-choose\n      position: relative\n      height: 60px\n      color: #999\n      .picker-title\n        margin: 0\n        line-height: 60px\n        font-weight: normal\n        text-align: center\n        font-size: 18px\n        color: #333\n      .confirm, .cancel\n        position: absolute\n        top: 6px\n        padding: 16px\n        font-size: 14px\n      .confirm\n        right: 0\n        color: #007bff\n        &:active\n          color: #5aaaff\n      .cancel\n        left: 0\n        &:active\n          color: #c2c2c2\n    .picker-content\n      position: relative\n      top: 20px\n      .mask-top, .mask-bottom\n        z-index: 10\n        width: 100%\n        height: 68px\n        pointer-events: none\n        transform: translateZ(0)\n      .mask-top\n        position: absolute\n        top: 0\n        background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .mask-bottom\n        position: absolute\n        bottom: 1px\n        background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n    .wheel-wrapper\n      display: flex\n      padding: 0 16px\n      .wheel\n        flex: 1\n        width: 1%\n        height: 173px\n        overflow: hidden\n        font-size: 18px\n        .wheel-scroll\n          padding: 0\n          margin-top: 68px\n          line-height: 36px\n          list-style: none\n          .wheel-item\n            list-style: none\n            height: 36px\n            overflow: hidden\n            white-space: nowrap\n            color: #333\n            &.wheel-disabled-item\n              opacity: .2;\n  .picker-footer\n    height: 20px\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/components/default.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\n\nBScroll.use(Movable)\n\nconst emojis = [\n  '😀 😁 😂 🤣 😃',\n  '😄 😅 😆 😉 😊',\n  '😫 😴 😌 😛 😜',\n  '👆🏻 😒 😓 😔 👇🏻',\n]\n\nconst Default = () => {\n  const wrapperRef = useRef(null)\n\n  useEffect(() => {\n    const BS = new BScroll(wrapperRef.current, {\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      startX: 20,\n      startY: 20,\n    })\n\n    return () => {\n      BS.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"movable-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {emojis.map((item, index) => (\n            <div key={index} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/components/multi-content-scale.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\nimport Zoom from '@better-scroll/zoom'\n\nimport SwordsmanLink from './ftstr.png'\nimport WitchLink from './qos_crop.png'\n\nBScroll.use(Movable)\nBScroll.use(Zoom)\n\nconst MultiContentScale = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef2 = useRef(null)\n\n  useEffect(() => {\n    const BS1 = new BScroll(wrapperRef.current, {\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      zoom: {\n        start: 1.2,\n        min: 0.5,\n        max: 3,\n      },\n    })\n    BS1.putAt('center', 'center', 0)\n\n    const BS2 = (scrollRef2.current = new BScroll(wrapperRef.current, {\n      // use wrapper.children[1] as content\n      specifiedIndexAsContent: 1,\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      startY: 150,\n      zoom: {\n        start: 1,\n        min: 0.5,\n        max: 3,\n      },\n    }))\n\n    return () => {\n      BS1.destroy()\n      BS2.destroy()\n    }\n  }, [])\n\n  const handleClick = () => {\n    scrollRef2.current.putAt('right', 'bottom', 500)\n  }\n\n  return (\n    <div className=\"movable-multi-content-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content content1\">\n          <figure>\n            <figcaption>Cold Oasis</figcaption>\n            <img className=\"picture\" src={SwordsmanLink} alt=\"ftstr\" />\n          </figure>\n        </div>\n        <div className=\"scroll-content content2\">\n          <figure>\n            <figcaption>Warm Oasis</figcaption>\n            <img className=\"picture\" src={WitchLink} alt=\"qos_crop\" />\n          </figure>\n        </div>\n      </div>\n      <button className=\"btn\" onClick={handleClick}>\n        Put The Warm at center position\n      </button>\n    </div>\n  )\n}\n\nexport default MultiContentScale\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/components/multi-content.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\n\nimport picture1 from './oasis_one.png'\nimport picture2 from './oasis_two.png'\n\nBScroll.use(Movable)\n\nconst MultiContent = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef2 = useRef(null)\n\n  useEffect(() => {\n    const BS1 = new BScroll(wrapperRef.current, {\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      startX: 10,\n      startY: 10,\n    })\n\n    const BS2 = (scrollRef2.current = new BScroll(wrapperRef.current, {\n      // use wrapper.children[1] as content\n      specifiedIndexAsContent: 1,\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      startX: 0,\n      startY: 170,\n    }))\n\n    return () => {\n      BS1.destroy()\n      BS2.destroy()\n    }\n  }, [])\n\n  const handleClick = () => {\n    scrollRef2.current.putAt('center', 'center')\n  }\n\n  return (\n    <div className=\"movable-multi-content-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content content1\">\n          <figure>\n            <figcaption>Cold Oasis</figcaption>\n            <img className=\"picture\" src={picture1} alt=\"Cold Oasis\" />\n          </figure>\n        </div>\n        <div className=\"scroll-content content2\">\n          <figure>\n            <figcaption>Warm Oasis</figcaption>\n            <img className=\"picture\" src={picture2} alt=\"Warm Oasis\" />\n          </figure>\n        </div>\n      </div>\n      <button className=\"btn\" onClick={handleClick}>\n        Put The Warm at center position\n      </button>\n    </div>\n  )\n}\n\nexport default MultiContent\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/components/scale.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Movable from '@better-scroll/movable'\nimport Zoom from '@better-scroll/zoom'\n\nBScroll.use(Movable)\nBScroll.use(Zoom)\n\nconst emojis = [\n  '😀 😁 😂 🤣 😃',\n  '😄 😅 😆 😉 😊',\n  '😫 😴 😌 😛 😜',\n  '👆🏻 😒 😓 😔 👇🏻',\n]\n\nconst Scale = () => {\n  const wrapperRef = useRef(null)\n\n  useEffect(() => {\n    const BS = new BScroll(wrapperRef.current, {\n      bindToTarget: true,\n      scrollX: true,\n      scrollY: true,\n      freeScroll: true,\n      movable: true,\n      zoom: {\n        start: 1,\n        min: 0.5,\n        max: 3,\n      },\n    })\n\n    return () => {\n      BS.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"movable-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {emojis.map((item, index) => (\n            <div key={index} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Scale\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/movable/default',\n    name: 'default',\n  },\n  {\n    path: '/movable/scale',\n    name: 'scale',\n  },\n  {\n    path: '/movable/multi-content',\n    name: 'multi-content',\n  },\n  {\n    path: '/movable/multi-content-scale',\n    name: 'multi-content-scale',\n  },\n]\n\nconst Movable = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Movable\n"
  },
  {
    "path": "packages/react-examples/src/pages/movable/index.styl",
    "content": ".movable-container\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      width 220px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n\n.movable-multi-content-container\n  .scroll-wrapper\n    height 400px\n    overflow hidden\n    position relative\n    box-shadow 0 0 3px rgba(0, 0, 0, .3)\n    .scroll-content\n      position absolute\n      top 0\n      left 0\n      width 200px\n      figure\n        margin 0\n      figcaption\n        font-weight bold\n        margin-bottom 5px\n        text-align center\n        color #ea4c89\n      .picture\n        width 200px\n        height 150px\n        border-radius 10px\n    .scroll-item\n      height 50px\n      line-height 50px\n      font-size 24px\n      font-weight bold\n      border-bottom 1px solid #eee\n      text-align center\n      &:nth-child(2n)\n        background-color #f3f5f7\n      &:nth-child(2n+1)\n        background-color #42b983\n  .btn\n    margin 40px auto\n    padding 10px\n    color #fff\n    border-radius 4px\n    font-size 20px\n    background-color #666\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/components/horizontal-in-vertical.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(NestedScroll)\nBScroll.use(Slide)\n\nconst outerOpenData = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n]\n\nconst outerCloseData = [\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 vertical scroll 👇🏻 ',\n  '😔 😕 🙃 🤣 😲 🙃 🤣',\n]\n\nconst handleOuterClick = () => {\n  alert('clicked outer item')\n}\n\nconst handleInnerClick = () => {\n  alert('clicked inner item')\n}\n\nconst HorizontalInVertical = () => {\n  const outerWrapperRef = useRef(null)\n  const outerScrollRef = useRef(null)\n  const innerWrapperRef = useRef(null)\n  const innerScrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!outerScrollRef.current) {\n      outerScrollRef.current = new BScroll(outerWrapperRef.current, {\n        nestedScroll: {\n          groupId: 'mixed-nested-scroll',\n        },\n        click: true,\n      })\n    }\n    if (!innerScrollRef.current) {\n      innerScrollRef.current = new BScroll(innerWrapperRef.current, {\n        nestedScroll: {\n          groupId: 'mixed-nested-scroll',\n        },\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          loop: false,\n          autoplay: false,\n          threshold: 100,\n        },\n        momentum: false,\n        bounce: false,\n        click: true,\n      })\n    }\n\n    return () => {\n      outerScrollRef.current?.destroy()\n      innerScrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"horizontal-in-vertical-container view\">\n      <div className=\"vertical-wrapper\" ref={outerWrapperRef}>\n        <div className=\"vertical-content\">\n          {outerOpenData.map((item, index) => (\n            <li\n              key={index}\n              className=\"vertical-item\"\n              onClick={handleOuterClick}\n            >\n              {item}\n            </li>\n          ))}\n          <div className=\"horizontal-wrapper\" ref={innerWrapperRef}>\n            <div className=\"slide-banner-content\" onClick={handleInnerClick}>\n              <div className=\"slide-item page1\">horizontal scroll 1</div>\n              <div className=\"slide-item page2\">horizontal scroll 2</div>\n              <div className=\"slide-item page3\">horizontal scroll 3</div>\n              <div className=\"slide-item page4\">horizontal scroll 4</div>\n            </div>\n          </div>\n          {outerCloseData.map((item, index) => (\n            <div\n              key={index}\n              className=\"vertical-item\"\n              onClick={handleOuterClick}\n            >\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default HorizontalInVertical\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/components/horizontal.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(NestedScroll)\n\nconst outerOpenData = ['👈🏻  outer 👉🏻 ', '🙂 🤔 😄 🤨 😐 🙃 ']\n\nconst outerCloseData = ['😔 😕 🙃 🤑 😲 😲 ', '👈🏻  outer 👉🏻 ']\n\nconst innerData = [\n  '👈🏻  inner 👉🏻  ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👈🏻  inner 👉🏻 ',\n  '😔 😕 🙃 🤑 😲 ☹️ ',\n  '👈🏻  inner 👉🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👈🏻  inner 👉🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n]\n\nconst handleOuterClick = () => {\n  alert('clicked outer item')\n}\n\nconst handleInnerClick = () => {\n  alert('clicked inner item')\n}\n\nconst Horizontal = () => {\n  const outerWrapperRef = useRef(null)\n  const outerScrollRef = useRef(null)\n  const innerWrapperRef = useRef(null)\n  const innerScrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!outerScrollRef.current) {\n      outerScrollRef.current = new BScroll(outerWrapperRef.current, {\n        nestedScroll: {\n          groupId: 'horizontal-nested-scroll', // groupId is a string or number\n        },\n        scrollX: true,\n        scrollY: false,\n        click: true,\n      })\n    }\n    if (!innerScrollRef.current) {\n      innerScrollRef.current = new BScroll(innerWrapperRef.current, {\n        // please keep the same groupId as above\n        // outerScroll and innerScroll will be controlled by the same nestedScroll instance\n        nestedScroll: {\n          groupId: 'horizontal-nested-scroll',\n        },\n        scrollX: true,\n        scrollY: false,\n        click: true,\n      })\n    }\n\n    return () => {\n      outerScrollRef.current?.destroy()\n      innerScrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"view\">\n      <div className=\"horizontal\">\n        <div className=\"outer-wrapper\" ref={outerWrapperRef}>\n          <div className=\"outer-content\">\n            <ul>\n              {outerOpenData.map((item, index) => (\n                <li\n                  key={index}\n                  className=\"list-item\"\n                  onClick={handleOuterClick}\n                >\n                  {item}\n                </li>\n              ))}\n              <li className=\"list-item inner-list-item\">\n                <div className=\"inner-wrapper\" ref={innerWrapperRef}>\n                  <ul className=\"inner-content\">\n                    {innerData.map((item, index) => (\n                      <li\n                        key={index}\n                        className=\"list-item\"\n                        onClick={handleInnerClick}\n                      >\n                        {item}\n                      </li>\n                    ))}\n                  </ul>\n                </div>\n              </li>\n              {outerCloseData.map((item, index) => (\n                <li\n                  key={index}\n                  className=\"list-item\"\n                  onClick={handleOuterClick}\n                >\n                  {item}\n                </li>\n              ))}\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Horizontal\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/components/triple-vertical.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(NestedScroll)\n\nconst outerOpenData = [\n  '----Outer Start----',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n]\n\nconst outerCloseData = [\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '----Outer End----',\n]\n\nconst middleOpenData = [\n  '----Middle Start----',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n]\n\nconst middleCloseData = [\n  '👆🏻 middle scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 middle scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '----Middle End----',\n]\n\nconst innerData = [\n  '------Inner Start-----',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '-----Inner End-----',\n]\n\nconst handleOuterClick = () => {\n  alert('clicked outer item')\n}\n\nconst handleMiddleClick = () => {\n  alert('clicked middle item')\n}\n\nconst handleInnerClick = () => {\n  alert('clicked inner item')\n}\n\nconst TripleVertical = () => {\n  const outerWrapperRef = useRef(null)\n  const middleWrapperRef = useRef(null)\n  const innerWrapperRef = useRef(null)\n\n  useEffect(() => {\n    const outerScroll = new BScroll(outerWrapperRef.current, {\n      nestedScroll: {\n        groupId: 'triple-nested-scroll', // groupId is a string or number\n      },\n      click: true,\n    })\n    const middleScroll = new BScroll(middleWrapperRef.current, {\n      nestedScroll: {\n        groupId: 'triple-nested-scroll', // groupId is a string or number\n      },\n      probeType: 2,\n      click: true,\n    })\n\n    middleScroll.on('scroll', () => {\n      console.log('middleScroll scroll')\n    })\n\n    const innerScroll = new BScroll(innerWrapperRef.current, {\n      // please keep the same groupId as above\n      // all scrolls will be controlled by the same nestedScroll instance\n      nestedScroll: {\n        groupId: 'triple-nested-scroll',\n      },\n      probeType: 2,\n      click: true,\n    })\n\n    innerScroll.on('scroll', () => {\n      console.log('innerScroll scroll')\n    })\n    innerScroll.on('scrollEnd', () => {\n      console.log('innerScroll scrollEnd')\n    })\n\n    return () => {\n      outerScroll.destroy()\n      middleScroll.destroy()\n      innerScroll.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"triple-vertical view\">\n      <div className=\"outer-wrapper\" ref={outerWrapperRef}>\n        <div className=\"outer-content\">\n          <ul>\n            {outerOpenData.map((item, index) => (\n              <li\n                key={index}\n                className=\"outer-list-item\"\n                onClick={handleOuterClick}\n              >\n                {item}\n              </li>\n            ))}\n          </ul>\n\n          <div className=\"middle-wrapper\" ref={middleWrapperRef}>\n            <div className=\"middle-content\">\n              <ul>\n                {middleOpenData.map((item, index) => (\n                  <li\n                    key={index}\n                    className=\"middle-list-item\"\n                    onClick={handleMiddleClick}\n                  >\n                    {item}\n                  </li>\n                ))}\n              </ul>\n              <div className=\"inner-wrapper\" ref={innerWrapperRef}>\n                <div className=\"inner-content\">\n                  <ul>\n                    {innerData.map((item, index) => (\n                      <li\n                        key={index}\n                        className=\"middle-list-item\"\n                        onClick={handleInnerClick}\n                      >\n                        {item}\n                      </li>\n                    ))}\n                  </ul>\n                </div>\n              </div>\n              <ul>\n                {middleCloseData.map((item, index) => (\n                  <li\n                    key={index}\n                    className=\"middle-list-item\"\n                    onClick={handleMiddleClick}\n                  >\n                    {item}\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </div>\n          <ul>\n            {outerCloseData.map((item, index) => (\n              <li\n                key={index}\n                className=\"outer-list-item\"\n                onClick={handleOuterClick}\n              >\n                {item}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default TripleVertical\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/components/vertical.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(NestedScroll)\n\nconst outerOpenData = [\n  '----Outer Start----',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n]\n\nconst outerCloseData = [\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😲 ',\n  '🙂 🤔 😄 🤨  😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 outer scroll 👇🏻 ',\n  '----Outer End----',\n]\n\nconst innerData = [\n  '------Inner Start-----',\n  '😀 😁 😂 🤣 😃 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙂 🤔 😄 🤨 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '😔 😕 🙃 🤑 😲 😐 🙃 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐣 🐣 🐣 🐣 🐣 🐣 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🐥 🐥 🐥 🐥 🐥 🐥 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🤓 🤓 🤓 🤓 🤓 🤓 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🦔 🦔 🦔 🦔 🦔 🦔 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🙈 🙈 🙈 🙈 🙈 🙈 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '🚖 🚖 🚖 🚖 🚖 🚖 ',\n  '👆🏻 inner scroll 👇🏻 ',\n  '✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ✌🏻 ',\n  '-----Inner End-----',\n]\n\nconst handleOuterClick = () => {\n  alert('clicked outer item')\n}\n\nconst handleInnerClick = () => {\n  alert('clicked inner item')\n}\n\nconst Vertical = () => {\n  const outerWrapperRef = useRef(null)\n  const outerScrollRef = useRef(null)\n  const innerWrapperRef = useRef(null)\n  const innerScrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!outerScrollRef.current) {\n      outerScrollRef.current = new BScroll(outerWrapperRef.current, {\n        nestedScroll: {\n          groupId: 'vertical-nested-scroll', // groupId is a string or number\n        },\n        click: true,\n      })\n    }\n    if (!innerScrollRef.current) {\n      innerScrollRef.current = new BScroll(innerWrapperRef.current, {\n        // please keep the same groupId as above\n        // outerScroll and innerScroll will be controlled by the same nestedScroll instance\n        nestedScroll: {\n          groupId: 'vertical-nested-scroll',\n        },\n        click: true,\n      })\n    }\n\n    return () => {\n      outerScrollRef.current?.destroy()\n      innerScrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"vertical view\">\n      <div className=\"outer-wrapper\" ref={outerWrapperRef}>\n        <div className=\"outer-content\">\n          <ul>\n            {outerOpenData.map((item, index) => (\n              <li\n                key={index}\n                className=\"outer-list-item\"\n                onClick={handleOuterClick}\n              >\n                {item}\n              </li>\n            ))}\n            <div className=\"inner-wrapper\" ref={innerWrapperRef}>\n              <ul className=\"inner-content\">\n                {innerData.map((item, index) => (\n                  <li\n                    key={index}\n                    className=\"inner-list-item\"\n                    onClick={handleInnerClick}\n                  >\n                    {item}\n                  </li>\n                ))}\n              </ul>\n            </div>\n            {outerCloseData.map((item, index) => (\n              <li\n                key={index}\n                className=\"outer-list-item\"\n                onClick={handleOuterClick}\n              >\n                {item}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Vertical\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/nested-scroll/vertical',\n    name: 'vertical',\n  },\n  {\n    path: '/nested-scroll/horizontal',\n    name: 'horizontal',\n  },\n  {\n    path: '/nested-scroll/horizontal-in-vertical',\n    name: 'horizontal-in-vertical',\n  },\n  {\n    path: '/nested-scroll/triple-vertical',\n    name: 'triple-vertical',\n  },\n]\n\nconst NestedScroll = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default NestedScroll\n"
  },
  {
    "path": "packages/react-examples/src/pages/nested-scroll/index.styl",
    "content": ".vertical\n  .outer-wrapper\n  .inner-wrapper\n    border: 2px solid #62B791\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    overflow: hidden\n  .outer-wrapper\n    height: 100%\n    border: 1px solid rgba(0, 0, 0, .1)\n  .inner-wrapper\n    height: 240px\n    background-color rgba(98,183,145, 0.2)\n  .inner-list-item\n    height: 50px\n    line-height: 50px\n    text-align: center\n    list-style: none\n  .outer-list-item\n    height: 40px\n    line-height: 40px\n    text-align: center\n    list-style: none\n\n.horizontal\n  .outer-wrapper\n    border: 1px solid rgba(0, 0, 0, 0.1)\n    border-radius: 5px\n    transform: rotate(0deg)\n    margin-top: 50px\n    position: relative\n    overflow: hidden\n    .outer-content\n      display: inline-block\n      vertical-align: top\n      white-space: nowrap\n  .inner-wrapper\n    border: 2px solid #62B791\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    width: 200px\n    overflow: hidden\n    .inner-content\n      display: inline-block\n      vertical-align: top\n  .list-item\n    display: inline-block\n    line-height: 60px\n  .inner-list-item\n    vertical-align: top // important\n    background-color: rgba(98,183,145, 0.2)\n\n.horizontal-in-vertical-container\n    height: 100%\n  .vertical-wrapper\n    height: 100%\n    border: 1px solid rgba(0, 0, 0, .1)\n    position: relative\n    overflow: hidden\n    .vertical-item\n      line-height: 40px\n      text-align: center\n  .slide-banner-content\n    height: 120px\n    white-space: nowrap\n    font-size: 0\n    .slide-item\n      display: inline-block\n      height: 120px\n      width: 100%\n      line-height: 120px\n      text-align: center\n      font-size: 26px\n      &.page1\n        background-color: #95B8D1\n      &.page2\n        background-color: #DDA789\n      &.page3\n        background-color: #C3D899\n      &.page4\n        background-color: #F2D4A7\n\n.triple-vertical\n  .outer-wrapper\n  .middle-wrapper\n  .inner-wrapper\n    border: 2px solid #62B791\n    border-radius: 5px\n    transform: rotate(0deg)\n    position: relative\n    overflow: hidden\n  .outer-wrapper\n    height: 100%\n    border: 1px solid rgba(0, 0, 0, .1)\n  .middle-wrapper\n    height: 480px\n    background-color rgba(240,65,85, 0.2)\n    border: 2px solid #f04155\n  .inner-wrapper\n    height: 240px\n    background-color rgb(98,183,145)\n  .middle-list-item\n  .inner-list-item\n    height: 50px\n    line-height: 50px\n    text-align: center\n    list-style: none\n    color: white\n\n  .middle-list-item\n    color: #894e06\n\n  .outer-list-item\n    height: 40px\n    line-height: 40px\n    text-align: center\n    list-style: none\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-dom/components/default.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ObserveDOM from '@better-scroll/observe-dom'\n\nBScroll.use(ObserveDOM)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\n\nconst Default = () => {\n  const [num, setNum] = useState(10)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        observeDOM: true,\n        scrollX: true,\n        scrollY: false,\n      })\n    }\n  }, [])\n\n  const handleClick = () => {\n    setNum((n) => n + 2)\n  }\n\n  return (\n    <div className=\"observe-dom-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {createArray(num).map((item, index) => (\n            <div\n              key={index}\n              className=\"scroll-item\"\n              onClick={() => handleClick(item)}\n            >\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n      <button className=\"btn\" onClick={handleClick}>\n        append two children element\n      </button>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-dom/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst ObserveDom = (props) => <>{props.children}</>\n\nexport default ObserveDom\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-dom/index.styl",
    "content": ".observe-dom-container\n  text-align center\n  .scroll-wrapper\n    width 90%\n    margin 80px auto\n    white-space nowrap\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n      .scroll-item\n        height 50px\n        line-height 50px\n        font-size 24px\n        display inline-block\n        text-align center\n        padding 0 20px\n        &:nth-child(2n)\n          background-color #C3D899\n        &:nth-child(2n+1)\n          background-color #F2D4A7\n\t.btn\n\t\tmargin 40px auto\n\t\tpadding 10px\n\t\tcolor #fff!important\n\t\tborder-radius 4px\n\t\tfont-size 20px\n\t\tbackground-color #666!important\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-image/components/default.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ObserveImage from '@better-scroll/observe-image'\n\nBScroll.use(ObserveImage)\n\nconst images = [\n  'https://dpubstatic.udache.com/static/dpubimg/dEswI1MVy6/zoo.png',\n  'https://dpubstatic.udache.com/static/dpubimg/BYb_wPak21/home.png',\n  'https://dpubstatic.udache.com/static/dpubimg/B6q1pWB0sB/cabin.png',\n  'https://dpubstatic.udache.com/static/dpubimg/76n1ilzf4R/stone.png',\n]\n\nconst Default = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        observeImage: true,\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"observe-image-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {images.map((item, index) => (\n            <img width=\"100%\" src={item} alt=\"\" key={index} />\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-image/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst ObserveImage = (props) => <>{props.children}</>\n\nexport default ObserveImage\n"
  },
  {
    "path": "packages/react-examples/src/pages/observe-image/index.styl",
    "content": ".observe-image-container\n  text-align center\n  .scroll-wrapper\n    position relative\n    width 300px\n    height 300px\n    margin 20px auto\n    border 3px solid #42b983\n    border-radius 5px\n    overflow hidden\n  .scroll-content\n    font-size 0\n"
  },
  {
    "path": "packages/react-examples/src/pages/picker/components/double-column.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport { CSSTransition } from 'react-transition-group'\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n\nconst DATA1 = [\n  {\n    text: 'Venomancer',\n    value: 1,\n  },\n  {\n    text: 'Nerubian Weaver',\n    value: 2,\n  },\n  {\n    text: 'Spectre',\n    value: 3,\n  },\n  {\n    text: 'Juggernaut',\n    value: 4,\n  },\n  {\n    text: 'Karl',\n    value: 5,\n  },\n  {\n    text: 'Zeus',\n    value: 6,\n  },\n  {\n    text: 'Witch Doctor',\n    value: 7,\n  },\n  {\n    text: 'Lich',\n    value: 8,\n  },\n  {\n    text: 'Oracle',\n    value: 9,\n  },\n  {\n    text: 'Earthshaker',\n    value: 10,\n  },\n]\n\nconst DATA2 = [\n  {\n    text: 'Durable',\n    value: 'a',\n  },\n  {\n    text: 'Pusher',\n    value: 'b',\n  },\n  {\n    text: 'Carry',\n    value: 'c',\n  },\n  {\n    text: 'Nuker',\n    value: 'd',\n  },\n  {\n    text: 'Support',\n    value: 'e',\n  },\n  {\n    text: 'Jungle',\n    value: 'f',\n  },\n  {\n    text: 'Escape',\n    value: 'g',\n  },\n  {\n    text: 'Initiator',\n    value: 'h',\n  },\n]\n\nconst stopPropagation = (e) => {\n  e.stopPropagation()\n}\n\nconst preventDefault = (e) => {\n  e.preventDefault()\n}\n\nconst pickerData = [DATA1, DATA2]\n\nconst DoubleColumn = () => {\n  const [visible, setVisible] = useState(false)\n  const [selectedIndexPair, setSelectedIndexPair] = useState([0, 0])\n  const [selectedText, setSelectedText] = useState('open')\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef([])\n\n  useEffect(() => {\n    if (visible) {\n      const createWheel = (wheelWrapper, i) => {\n        if (scrollRef.current[i]) {\n          scrollRef.current[i].refresh()\n        } else {\n          const BS = (scrollRef.current[i] = new BScroll(\n            wheelWrapper.children[i],\n            {\n              wheel: {\n                wheelWrapperClass: 'wheel-scroll',\n                wheelItemClass: 'wheel-item',\n                selectedIndex: selectedIndexPair[i],\n              },\n              useTransition: false,\n              probeType: 3,\n            }\n          ))\n\n          // < v2.1.0\n          BS.on('scrollEnd', () => {\n            console.log('BS.getSelectedIndex()', BS.getSelectedIndex())\n          })\n        }\n      }\n\n      const wrapper = wrapperRef.current\n      for (let i = 0; i < pickerData.length; i++) {\n        createWheel(wrapper, i)\n      }\n    }\n  }, [visible, selectedIndexPair])\n\n  const handleShow = () => {\n    if (visible) {\n      return\n    }\n    setVisible(true)\n  }\n\n  const handleHide = () => {\n    setVisible(false)\n  }\n\n  const handleConfirm = () => {\n    scrollRef.current.forEach((wheel) => {\n      /*\n       * if bs is scrolling, force it stop at the nearest wheel-item\n       * or you can use 'restorePosition' method as the below\n       */\n      // wheel.stop()\n      /*\n       * if bs is scrolling, restore it to the start position\n       * it is same with iOS picker and web Select element implementation\n       * supported at v2.1.0\n       */\n      wheel.restorePosition()\n    })\n    handleHide()\n    const currentSelectedIndexPair = scrollRef.current.map((wheel) =>\n      wheel.getSelectedIndex()\n    )\n    setSelectedIndexPair(currentSelectedIndexPair)\n    setSelectedText(\n      pickerData\n        .map((data, i) => {\n          const index = currentSelectedIndexPair[i]\n          return `${data[index].text}-${index}`\n        })\n        .join('__')\n    )\n  }\n\n  const handleCancel = () => {\n    /*\n     * if bs is scrolling, restore it to the start position\n     * it is same with iOS picker and web Select element implementation\n     * supported at v2.1.0\n     */\n    scrollRef.current.forEach((wheel) => {\n      wheel.restorePosition()\n    })\n    handleHide()\n  }\n\n  return (\n    <div className=\"container view\">\n      <div className=\"container-btn\" onClick={handleShow}>\n        {selectedText}\n      </div>\n      <CSSTransition\n        in={visible}\n        classNames=\"picker-fade\"\n        timeout={300}\n        onEnter={(node) => {\n          node.style.display = 'block'\n        }}\n        onExited={(node) => {\n          node.style.display = ''\n        }}\n      >\n        <div\n          className=\"picker\"\n          onClick={handleCancel}\n          onTouchMove={preventDefault}\n        >\n          <CSSTransition in={visible} classNames=\"picker-move\" timeout={300}>\n            <div className=\"picker-panel\" onClick={stopPropagation}>\n              <div className=\"picker-choose border-bottom-1px\">\n                <span className=\"cancel\" onClick={handleCancel}>\n                  Cancel\n                </span>\n                <span className=\"confirm\" onClick={handleConfirm}>\n                  Confirm\n                </span>\n                <h1 className=\"picker-title\">Title</h1>\n              </div>\n              <div className=\"picker-content\">\n                <div className=\"mask-top border-bottom-1px\"></div>\n                <div className=\"mask-bottom border-top-1px\"></div>\n                <div className=\"wheel-wrapper\" ref={wrapperRef}>\n                  {pickerData.map((data, index) => (\n                    <div className=\"wheel\" key={index}>\n                      <ul className=\"wheel-scroll\">\n                        {data.map((item, index) => (\n                          <li key={index} className=\"wheel-item\">\n                            {item.text}\n                          </li>\n                        ))}\n                      </ul>\n                    </div>\n                  ))}\n                </div>\n              </div>\n              <div className=\"picker-footer\"></div>\n            </div>\n          </CSSTransition>\n        </div>\n      </CSSTransition>\n    </div>\n  )\n}\n\nexport default DoubleColumn\n"
  },
  {
    "path": "packages/react-examples/src/pages/picker/components/linkage-column.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport { CSSTransition } from 'react-transition-group'\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n\nconst DATA = [\n  {\n    text: '北京市',\n    value: '110000',\n    children: [\n      {\n        text: '北京市',\n        value: '110100',\n      },\n    ],\n  },\n  {\n    text: '天津市',\n    value: '120000',\n    children: [\n      {\n        text: '天津市',\n        value: '120000',\n      },\n    ],\n  },\n  {\n    text: '河北省',\n    value: '130000',\n    children: [\n      {\n        text: '石家庄市',\n        value: '130100',\n      },\n      {\n        text: '唐山市',\n        value: '130200',\n      },\n      {\n        text: '秦皇岛市',\n        value: '130300',\n      },\n      {\n        text: '邯郸市',\n        value: '130400',\n      },\n      {\n        text: '邢台市',\n        value: '130500',\n      },\n      {\n        text: '保定市',\n        value: '130600',\n      },\n      {\n        text: '张家口市',\n        value: '130700',\n      },\n      {\n        text: '承德市',\n        value: '130800',\n      },\n    ],\n  },\n  {\n    text: '山西省',\n    value: '140000',\n    children: [\n      {\n        text: '太原市',\n        value: '140100',\n      },\n      {\n        text: '大同市',\n        value: '140200',\n      },\n      {\n        text: '阳泉市',\n        value: '140300',\n      },\n      {\n        text: '长治市',\n        value: '140400',\n      },\n      {\n        text: '晋城市',\n        value: '140500',\n      },\n      {\n        text: '朔州市',\n        value: '140600',\n      },\n      {\n        text: '晋中市',\n        value: '140700',\n      },\n    ],\n  },\n]\n\nconst stopPropagation = (e) => {\n  e.stopPropagation()\n}\n\nconst preventDefault = (e) => {\n  e.preventDefault()\n}\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst LinkageColumn = () => {\n  const [visible, setVisible] = useState(false)\n  const [pickerData, setPickerData] = useState(() => [])\n  const [selectedIndexPair, setSelectedIndexPair] = useState([0, 0])\n  const [selectedText, setSelectedText] = useState('open')\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef([])\n\n  const loadPickerData = useStableCallback((newIndexPair, oldIndexPair) => {\n    let provinces\n    let cities\n    // first instantiated\n    if (!oldIndexPair) {\n      provinces = DATA.map(({ value, text }) => ({ value, text }))\n      cities = DATA[newIndexPair[0]].children\n      setPickerData([provinces, cities])\n    } else {\n      // provinces'index changed, refresh cities data\n      if (newIndexPair[0] !== oldIndexPair[0]) {\n        cities = DATA[newIndexPair[0]].children.slice()\n        setPickerData((prev) => {\n          const next = [...prev]\n          next.splice(1, 1, cities)\n          return next\n        })\n        // Since cities data changed\n        // refresh better-scroll to recaculate scrollHeight\n        scrollRef.current[1].refresh()\n      }\n    }\n  })\n\n  const createWheels = useStableCallback(() => {\n    const createWheel = (wheelWrapper, i) => {\n      if (scrollRef.current[i]) {\n        scrollRef.current[i].refresh()\n      } else {\n        const BS = (scrollRef.current[i] = new BScroll(\n          wheelWrapper.children[i],\n          {\n            wheel: {\n              wheelWrapperClass: 'wheel-scroll',\n              wheelItemClass: 'wheel-item',\n              selectedIndex: selectedIndexPair[i],\n            },\n            useTransition: false,\n            probeType: 3,\n          }\n        ))\n\n        // when any of wheels'scrolling ended , refresh data\n        let prevSelectedIndexPair = selectedIndexPair\n        BS.on('scrollEnd', () => {\n          const currentSelectedIndexPair = scrollRef.current.map((wheel) =>\n            wheel.getSelectedIndex()\n          )\n          loadPickerData(currentSelectedIndexPair, prevSelectedIndexPair)\n          prevSelectedIndexPair = currentSelectedIndexPair\n        })\n      }\n    }\n\n    const wrapper = wrapperRef.current\n    for (let i = 0; i < pickerData.length; i++) {\n      createWheel(wrapper, i)\n    }\n  })\n\n  useEffect(() => {\n    loadPickerData(selectedIndexPair)\n  }, [loadPickerData, selectedIndexPair])\n\n  useEffect(() => {\n    if (visible) {\n      createWheels()\n    }\n  }, [visible, createWheels])\n\n  const handleShow = () => {\n    if (visible) {\n      return\n    }\n    setVisible(true)\n  }\n\n  const handleHide = () => {\n    setVisible(false)\n  }\n\n  const handleConfirm = () => {\n    scrollRef.current.forEach((wheel) => {\n      /*\n       * if bs is scrolling, force it stop at the nearest wheel-item\n       * or you can use 'restorePosition' method as the below\n       */\n      // wheel.stop()\n      /*\n       * if bs is scrolling, restore it to the start position\n       * it is same with iOS picker and web Select element implementation\n       * supported at v2.1.0\n       */\n      wheel.restorePosition()\n    })\n    handleHide()\n    const currentSelectedIndexPair = scrollRef.current.map((wheel) =>\n      wheel.getSelectedIndex()\n    )\n    setSelectedIndexPair(currentSelectedIndexPair)\n    setSelectedText(\n      pickerData\n        .map((data, i) => {\n          const index = currentSelectedIndexPair[i]\n          return `${data[index].text}-${index}`\n        })\n        .join('__')\n    )\n  }\n\n  const handleCancel = () => {\n    /*\n     * if bs is scrolling, restore it to the start position\n     * it is same with iOS picker and web Select element implementation\n     * supported at v2.1.0\n     */\n    scrollRef.current.forEach((wheel) => {\n      wheel.restorePosition()\n    })\n    handleHide()\n  }\n\n  return (\n    <div className=\"container view\">\n      <div className=\"container-btn\" onClick={handleShow}>\n        {selectedText}\n      </div>\n      <CSSTransition\n        in={visible}\n        classNames=\"picker-fade\"\n        timeout={300}\n        onEnter={(node) => {\n          node.style.display = 'block'\n        }}\n        onExited={(node) => {\n          node.style.display = ''\n        }}\n      >\n        <div\n          className=\"picker\"\n          onClick={handleCancel}\n          onTouchMove={preventDefault}\n        >\n          <CSSTransition in={visible} classNames=\"picker-move\" timeout={300}>\n            <div className=\"picker-panel\" onClick={stopPropagation}>\n              <div className=\"picker-choose border-bottom-1px\">\n                <span className=\"cancel\" onClick={handleCancel}>\n                  Cancel\n                </span>\n                <span className=\"confirm\" onClick={handleConfirm}>\n                  Confirm\n                </span>\n                <h1 className=\"picker-title\">Title</h1>\n              </div>\n              <div className=\"picker-content\">\n                <div className=\"mask-top border-bottom-1px\"></div>\n                <div className=\"mask-bottom border-top-1px\"></div>\n                <div className=\"wheel-wrapper\" ref={wrapperRef}>\n                  {pickerData.map((data, index) => (\n                    <div className=\"wheel\" key={index}>\n                      <ul className=\"wheel-scroll\">\n                        {data.map((item) => (\n                          <li key={item.value} className=\"wheel-item\">\n                            {item.text}\n                          </li>\n                        ))}\n                      </ul>\n                    </div>\n                  ))}\n                </div>\n              </div>\n              <div className=\"picker-footer\"></div>\n            </div>\n          </CSSTransition>\n        </div>\n      </CSSTransition>\n    </div>\n  )\n}\n\nexport default LinkageColumn\n"
  },
  {
    "path": "packages/react-examples/src/pages/picker/components/one-column.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport { CSSTransition } from 'react-transition-group'\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n\nconst DATA = [\n  {\n    text: 'Venomancer',\n    value: 1,\n    disabled: 'wheel-disabled-item',\n  },\n  {\n    text: 'Nerubian Weaver',\n    value: 2,\n  },\n  {\n    text: 'Spectre',\n    value: 3,\n  },\n  {\n    text: 'Juggernaut',\n    value: 4,\n  },\n  {\n    text: 'Karl',\n    value: 5,\n  },\n  {\n    text: 'Zeus',\n    value: 6,\n  },\n  {\n    text: 'Witch Doctor',\n    value: 7,\n  },\n  {\n    text: 'Lich',\n    value: 8,\n  },\n  {\n    text: 'Oracle',\n    value: 9,\n  },\n  {\n    text: 'Earthshaker',\n    value: 10,\n  },\n]\n\nconst stopPropagation = (e) => {\n  e.stopPropagation()\n}\n\nconst preventDefault = (e) => {\n  e.preventDefault()\n}\n\nconst OneColumn = () => {\n  const [visible, setVisible] = useState(false)\n  const [selectedIndex, setSelectedIndex] = useState(2)\n  const [selectedText, setSelectedText] = useState('open')\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (visible) {\n      if (!scrollRef.current) {\n        const wrapper = wrapperRef.current.children[0]\n        const BS = (scrollRef.current = new BScroll(wrapper, {\n          wheel: {\n            wheelWrapperClass: 'wheel-scroll',\n            wheelItemClass: 'wheel-item',\n            wheelDisabledItemClass: 'wheel-disabled-item',\n            selectedIndex,\n          },\n          useTransition: false,\n          probeType: 3,\n        }))\n\n        // < v2.1.0\n        BS.on('scrollEnd', () => {\n          console.log('BS.getSelectedIndex()', BS.getSelectedIndex())\n        })\n        // v2.1.0, only when selectedIndex changed\n        BS.on('wheelIndexChanged', (index) => {\n          console.log(index)\n        })\n      } else {\n        scrollRef.current.refresh()\n      }\n    }\n  }, [visible, selectedIndex])\n\n  const handleShow = () => {\n    if (visible) {\n      return\n    }\n    setVisible(true)\n  }\n\n  const handleHide = () => {\n    setVisible(false)\n  }\n\n  const handleConfirm = () => {\n    /*\n     * if bs is scrolling, force it stop at the nearest wheel-item\n     * or you can use 'restorePosition' method as the below\n     */\n    scrollRef.current.stop()\n    handleHide()\n    const currentSelectedIndex = scrollRef.current.getSelectedIndex()\n    setSelectedIndex(currentSelectedIndex)\n    setSelectedText(\n      `${DATA[currentSelectedIndex].text}-${currentSelectedIndex}`\n    )\n  }\n\n  const handleCancel = () => {\n    /*\n     * if bs is scrolling, restore it to the start position\n     * it is same with iOS picker and web Select element implementation\n     * supported at v2.1.0\n     */\n    scrollRef.current.restorePosition()\n    handleHide()\n  }\n\n  return (\n    <div className=\"container view\">\n      <div className=\"container-btn\" onClick={handleShow}>\n        {selectedText}\n      </div>\n      <CSSTransition\n        in={visible}\n        classNames=\"picker-fade\"\n        timeout={300}\n        onEnter={(node) => {\n          node.style.display = 'block'\n        }}\n        onExited={(node) => {\n          node.style.display = ''\n        }}\n      >\n        <div\n          className=\"picker\"\n          onClick={handleCancel}\n          onTouchMove={preventDefault}\n        >\n          <CSSTransition in={visible} classNames=\"picker-move\" timeout={300}>\n            <div className=\"picker-panel\" onClick={stopPropagation}>\n              <div className=\"picker-choose border-bottom-1px\">\n                <span className=\"cancel\" onClick={handleCancel}>\n                  Cancel\n                </span>\n                <span className=\"confirm\" onClick={handleConfirm}>\n                  Confirm\n                </span>\n                <h1 className=\"picker-title\">Title</h1>\n              </div>\n              <div className=\"picker-content\">\n                <div className=\"mask-top border-bottom-1px\"></div>\n                <div className=\"mask-bottom border-top-1px\"></div>\n                <div className=\"wheel-wrapper\" ref={wrapperRef}>\n                  <div className=\"wheel\">\n                    <ul className=\"wheel-scroll\">\n                      {DATA.map((item, index) => (\n                        <li\n                          key={index}\n                          className={classNames([\n                            'wheel-item',\n                            { 'wheel-disabled-item': item.disabled },\n                          ])}\n                        >\n                          {item.text}\n                        </li>\n                      ))}\n                    </ul>\n                  </div>\n                </div>\n              </div>\n              <div className=\"picker-footer\"></div>\n            </div>\n          </CSSTransition>\n        </div>\n      </CSSTransition>\n    </div>\n  )\n}\n\nexport default OneColumn\n"
  },
  {
    "path": "packages/react-examples/src/pages/picker/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/picker/one-column',\n    name: 'One Column Picker',\n  },\n  {\n    path: '/picker/double-column',\n    name: 'Double Column Picker',\n  },\n  {\n    path: '/picker/linkage-column',\n    name: 'Linkage Column Picker',\n  },\n]\n\nconst Picker = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Picker\n"
  },
  {
    "path": "packages/react-examples/src/pages/picker/index.styl",
    "content": "/* reset */\nul\n  list-style none\n  padding 0\n\n.border-bottom-1px, .border-top-1px\n  position: relative\n  &:before, &:after\n    content: \"\"\n    display: block\n    position: absolute\n    transform-origin: 0 0\n.border-bottom-1px\n  &:after\n    border-bottom: 1px solid #ebebeb\n    left: 0\n    bottom: 0\n    width: 100%\n    transform-origin: 0 bottom\n.border-top-1px\n  &:before\n    border-top: 1px solid #ebebeb\n    left: 0\n    top: 0\n    width: 100%\n    transform-origin: 0 top\n@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)\n  .border-top-1px\n    &:before\n      width: 200%\n      transform: scale(.5) translateZ(0)\n  .border-bottom-1px\n    &:after\n      width: 200%\n      transform: scale(.5) translateZ(0)\n\n@media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)\n  .border-top-1px\n    &:before\n      width: 300%\n      transform: scale(.333) translateZ(0)\n  .border-bottom-1px\n    &:after\n      width: 300%\n      transform: scale(.333) translateZ(0)\n.container-btn\n  margin: 2rem\n  background-color white\n  padding: 0.8rem\n  border: 1px solid rgba(0, 0, 0, .1)\n  box-shadow: 0 1px 2px 0 rgba(0,0,0,0.1)\n  text-align: center\n\n.picker\n  display: none\n  position: fixed\n  left: 0\n  top: 0\n  z-index: 100\n  width: 100%\n  height: 100%\n  overflow: hidden\n  text-align: center\n  font-size: 14px\n  background-color: rgba(37, 38, 45, .4)\n  &.picker-fade-enter\n    opacity: 0\n  &.picker-fade-enter-active\n    opacity: 1\n    transition: all .3s ease-in-out\n  &.picker-fade-exit-active\n    opacity: 0;\n    transition: all .3s ease-in-out\n\n\n  .picker-panel\n    position: absolute\n    z-index: 600\n    bottom: 0\n    width: 100%\n    height: 273px\n    background: white\n    &.picker-move-enter\n      transform: translate3d(0, 273px, 0)\n    &.picker-move-enter-active\n      transform: translate3d(0, 0, 0)\n      transition: all .3s ease-in-out\n    &.picker-move-exit-active\n      transform: translate3d(0, 273px, 0)\n      transition: all .3s ease-in-out\n    .picker-choose\n      position: relative\n      height: 60px\n      color: #999\n      .picker-title\n        margin: 0\n        line-height: 60px\n        font-weight: normal\n        text-align: center\n        font-size: 18px\n        color: #333\n      .confirm, .cancel\n        position: absolute\n        top: 6px\n        padding: 16px\n        font-size: 14px\n      .confirm\n        right: 0\n        color: #007bff\n        &:active\n          color: #5aaaff\n      .cancel\n        left: 0\n        &:active\n          color: #c2c2c2\n    .picker-content\n      position: relative\n      top: 20px\n      .mask-top, .mask-bottom\n        z-index: 10\n        width: 100%\n        height: 68px\n        pointer-events: none\n        transform: translateZ(0)\n      .mask-top\n        position: absolute\n        top: 0\n        background: linear-gradient(to top, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n      .mask-bottom\n        position: absolute\n        bottom: 1px\n        background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.8))\n    .wheel-wrapper\n      display: flex\n      padding: 0 16px\n      .wheel\n        flex: 1\n        width: 1%\n        height: 173px\n        overflow: hidden\n        font-size: 18px\n        .wheel-scroll\n          padding: 0\n          margin-top: 68px\n          line-height: 36px\n          list-style: none\n          .wheel-item\n            list-style: none\n            height: 36px\n            overflow: hidden\n            white-space: nowrap\n            color: #333\n            &.wheel-disabled-item\n              opacity: .2;\n  .picker-footer\n    height: 20px\n"
  },
  {
    "path": "packages/react-examples/src/pages/pulldown/components/default.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const dataList = generateData()\n      resolve(dataList)\n    }, 1000)\n  })\n}\n\nconst TIME_BOUNCE = 800\nlet STEP = 0\n\nfunction generateData() {\n  const BASE = 30\n  const begin = BASE * STEP\n  const end = BASE * (STEP + 1)\n  let ret = []\n  for (let i = end; i > begin; i--) {\n    ret.push(i)\n  }\n  return ret\n}\n\nconst Default = () => {\n  const [beforePullDown, setBeforePullDown] = useState(true)\n  const [isPullingDown, setIsPullingDown] = useState(false)\n  const [data, setData] = useState(generateData())\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async () => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      setData((prev) => newData.concat(prev))\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const finishPullDown = () => {\n    scrollRef.current.finishPullDown()\n    setTimeout(() => {\n      setBeforePullDown(true)\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 100)\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    setBeforePullDown(false)\n    setIsPullingDown(true)\n    STEP += 1\n    await requestData()\n    setIsPullingDown(false)\n    finishPullDown()\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        useTransition: false,\n        pullDownRefresh: {\n          threshold: 70,\n          stop: 56,\n        },\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n\n      BS.on('scroll', (pos) => {\n        console.log(pos.y)\n      })\n\n      BS.on('scrollEnd', () => {\n        console.log('scrollEnd')\n      })\n    }\n  }, [pullingDownHandler])\n\n  return (\n    <div className=\"pulldown view\">\n      <div className=\"pulldown-bswrapper\" ref={wrapperRef}>\n        <div className=\"pulldown-scroller\">\n          <div className=\"pulldown-wrapper\">\n            <div style={{ display: beforePullDown ? '' : 'none' }}>\n              <span>Pull Down and refresh</span>\n            </div>\n            <div style={{ display: beforePullDown ? 'none' : '' }}>\n              <div v-show=\"isPullingDown\">\n                <span>Loading...</span>\n              </div>\n              <div style={{ display: isPullingDown ? 'none' : '' }}>\n                <span>Refresh success</span>\n              </div>\n            </div>\n          </div>\n          <ul className=\"pulldown-list\">\n            {data.map((item, index) => (\n              <li key={index} className=\"pulldown-list-item\">\n                {`I am item ${item} `}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/pulldown/components/sina-weibo.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nfunction generateData() {\n  const BASE = 30\n  const begin = BASE * STEP\n  const end = BASE * (STEP + 1)\n  let ret = []\n  for (let i = end; i > begin; i--) {\n    ret.push(i)\n  }\n  return ret\n}\n\n// pulldownRefresh state\nconst PHASE = {\n  moving: {\n    enter: 'enter',\n    leave: 'leave',\n  },\n  fetching: 'fetching',\n  succeed: 'succeed',\n}\nconst TIME_BOUNCE = 800\nconst REQUEST_TIME = 2000\nconst THRESHOLD = 70\nconst STOP = 56\nlet STEP = 0\nconst ARROW_BOTTOM =\n  '<svg width=\"16\" height=\"16\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"M367.997 338.75l-95.998 95.997V17.503h-32v417.242l-95.996-95.995l-22.627 22.627L256 496l134.624-134.623l-22.627-22.627z\"></path></svg>'\nconst ARROW_UP =\n  '<svg width=\"16\" height=\"16\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"M390.624 150.625L256 16L121.376 150.625l22.628 22.627l95.997-95.998v417.982h32V77.257l95.995 95.995l22.628-22.627z\"></path></svg>'\n\nconst mockFetchData = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const dataList = generateData()\n      resolve(dataList)\n    }, REQUEST_TIME)\n  })\n}\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst Sina = () => {\n  const [tipText, setTipText] = useState('')\n  const [data, setData] = useState(generateData())\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const getData = async () => {\n    try {\n      const newData = await mockFetchData()\n      setData((prev) => newData.concat(prev))\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const pullingDownHandler = useStableCallback(async () => {\n    updateTipText(PHASE.fetching)\n    STEP += 1\n    await getData()\n    updateTipText(PHASE.succeed)\n    scrollRef.current.finishPullDown()\n    setTimeout(() => {\n      scrollRef.current.refresh()\n    }, TIME_BOUNCE + 50)\n  })\n\n  const updateTipText = useStableCallback((phase = PHASE.default) => {\n    const TEXTS_MAP = {\n      enter: `${ARROW_BOTTOM} Pull down`,\n      leave: `${ARROW_UP} Release`,\n      fetching: 'Loading...',\n      succeed: 'Refresh succeed',\n    }\n    setTipText(TEXTS_MAP[phase])\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        bounceTime: TIME_BOUNCE,\n        useTransition: false,\n        pullDownRefresh: {\n          threshold: THRESHOLD,\n          stop: STOP,\n        },\n      }))\n\n      BS.on('pullingDown', pullingDownHandler)\n\n      BS.on('scrollEnd', () => {\n        console.log('scrollEnd')\n      })\n\n      // v2.4.0 supported\n      BS.on('enterThreshold', () => {\n        updateTipText(PHASE.moving.enter)\n      })\n\n      BS.on('leaveThreshold', () => {\n        updateTipText(PHASE.moving.leave)\n      })\n    }\n  }, [pullingDownHandler, updateTipText])\n\n  return (\n    <div className=\"pulldown view\">\n      <div className=\"pulldown-bswrapper\" ref={wrapperRef}>\n        <div className=\"pulldown-scroller\">\n          <div className=\"pulldown-wrapper\">\n            <div dangerouslySetInnerHTML={{ __html: tipText }}></div>\n          </div>\n          <ul className=\"pulldown-list\">\n            {data.map((item, index) => (\n              <li key={index} className=\"pulldown-list-item\">\n                {`I am item ${item} `}\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Sina\n"
  },
  {
    "path": "packages/react-examples/src/pages/pulldown/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/pulldown/default',\n    name: 'Default',\n  },\n  {\n    path: '/pulldown/sina',\n    name: 'Sina-Weibo(v2.4.0)',\n  },\n]\n\nconst Pulldown = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Pulldown\n"
  },
  {
    "path": "packages/react-examples/src/pages/pulldown/index.styl",
    "content": ".pulldown\n  height 100%\n\n.pulldown-bswrapper\n  position relative\n  height 100%\n  padding 0 10px\n  border 1px solid #ccc\n  overflow hidden\n\n.pulldown-list\n  padding 0\n\n.pulldown-list-item\n  padding 10px 0\n  list-style none\n  border-bottom 1px solid #ccc\n\n.pulldown-wrapper\n  position absolute\n  width 100%\n  padding 20px\n  box-sizing border-box\n  transform translateY(-100%) translateZ(0)\n  text-align center\n  color #999\n"
  },
  {
    "path": "packages/react-examples/src/pages/pullup/components/default.js",
    "content": "import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Pullup from '@better-scroll/pull-up'\n\nBScroll.use(Pullup)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\n\nconst useStableCallback = (callback) => {\n  const callbackRef = useRef(callback)\n\n  useEffect(() => {\n    callbackRef.current = callback\n  })\n\n  return useCallback((...args) => callbackRef.current(...args), [])\n}\n\nconst ajaxGet = (/* url */) => {\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      resolve(20)\n    }, 1000)\n  })\n}\n\nconst Default = () => {\n  const [isPullUpLoad, setIsPullUpLoad] = useState(false)\n  const [num, setNum] = useState(20)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const requestData = async () => {\n    try {\n      const newData = await ajaxGet(/* url */)\n      setNum((n) => n + newData)\n    } catch (err) {\n      // handle err\n      console.log(err)\n    }\n  }\n\n  const pullingUpHandler = useStableCallback(async () => {\n    setIsPullUpLoad(true)\n    await requestData()\n    scrollRef.current.finishPullUp()\n    scrollRef.current.refresh()\n    setIsPullUpLoad(false)\n  })\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        pullUpLoad: true,\n      }))\n\n      BS.on('pullingUp', pullingUpHandler)\n    }\n  }, [pullingUpHandler])\n\n  return (\n    <div className=\"pullup view\">\n      <div className=\"pullup-wrapper\" ref={wrapperRef}>\n        <div className=\"pullup-content\">\n          <ul className=\"pullup-list\">\n            {createArray(num).map((item, index) => (\n              <li key={index} className=\"pullup-list-item\">\n                {item % 5 === 0 ? 'scroll up 👆🏻' : `I am item ${item} `}\n              </li>\n            ))}\n          </ul>\n          <div className=\"pullup-tips\">\n            {isPullUpLoad ? (\n              <div className=\"after-trigger\">\n                <span className=\"pullup-txt\">Loading...</span>\n              </div>\n            ) : (\n              <div className=\"before-trigger\">\n                <span className=\"pullup-txt\">Pull up and load more</span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/pullup/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst Pullup = (props) => <>{props.children}</>\n\nexport default Pullup\n"
  },
  {
    "path": "packages/react-examples/src/pages/pullup/index.styl",
    "content": ".pullup\n  height: 100%\n  .pullup-wrapper\n    height: 100%\n    padding: 0 10px\n    border: 1px solid #ccc\n    overflow: hidden\n  .pullup-list\n    padding: 0\n  .pullup-list-item\n    padding: 10px 0\n    list-style: none\n    border-bottom: 1px solid #ccc\n  .pullup-tips\n    padding: 20px\n    text-align: center\n    color: #999\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/components/custom.js",
    "content": "import React, { useRef } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ScrollBar from '@better-scroll/scroll-bar'\n\nimport girlImageLink from './girl.jpg'\n\nBScroll.use(ScrollBar)\n\nconst Custom = () => {\n  const wrapperRef = useRef(null)\n  const verticalRef = useRef(null)\n  const horizontalRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const onLoad = () => {\n    scrollRef.current = new BScroll(wrapperRef.current, {\n      freeScroll: true,\n      click: true,\n      scrollbar: {\n        customElements: [horizontalRef.current, verticalRef.current],\n        fade: false,\n        interactive: true,\n        scrollbarTrackClickable: true,\n      },\n    })\n  }\n\n  return (\n    <div className=\"custom-scrollbar-container view\">\n      <div className=\"custom-scrollbar-wrapper\" ref={wrapperRef}>\n        <img\n          onLoad={onLoad}\n          className=\"custom-scrollbar-content\"\n          src={girlImageLink}\n          alt=\"custom\"\n        />\n        {/* custom-vertical-scrollbar */}\n        <div className=\"custom-vertical-scrollbar\" ref={verticalRef}>\n          <div className=\"custom-vertical-indicator\"></div>\n        </div>\n        {/* custom-horizontal-scrollbar */}\n        <div className=\"custom-horizontal-scrollbar\" ref={horizontalRef}>\n          <div className=\"custom-horizontal-indicator\"></div>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Custom\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/components/horizontal.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ScrollBar from '@better-scroll/scroll-bar'\n\nBScroll.use(ScrollBar)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst data = createArray(40)\n\nconst Horizontal = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        click: true,\n        probeType: 1,\n        scrollbar: {\n          fade: false,\n          interactive: true,\n          scrollbarTrackClickable: true,\n          scrollbarTrackOffsetType: 'clickedPoint', // can use 'step'\n        },\n      }))\n\n      BS.on('scrollEnd', () => {\n        console.log('scrollEnd')\n      })\n      BS.on('scrollStart', () => {\n        console.log('scrollStart')\n      })\n      BS.on('scroll', () => {\n        console.log('scroll')\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"horizontal-scrollbar-container view\">\n      <div className=\"scroll-wrapper\" ref={wrapperRef}>\n        <div className=\"scroll-content\">\n          {data.map((item) => (\n            <div key={item} className=\"scroll-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Horizontal\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/components/mousewheel.js",
    "content": "import React, { useRef } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ScrollBar from '@better-scroll/scroll-bar'\nimport MouseWheel from '@better-scroll/mouse-wheel'\n\nimport girlImageLink from './sad-girl.jpg'\n\nBScroll.use(ScrollBar)\nBScroll.use(MouseWheel)\n\nconst Mousewheel = () => {\n  const wrapperRef = useRef(null)\n  const horizontalRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const onLoad = () => {\n    scrollRef.current = new BScroll(wrapperRef.current, {\n      scrollX: true,\n      scrollY: false,\n      click: true,\n      mouseWheel: true,\n      scrollbar: {\n        customElements: [horizontalRef.current],\n        fade: true,\n        interactive: true,\n        scrollbarTrackClickable: true,\n      },\n    })\n  }\n\n  return (\n    <div className=\"mousewheel-scrollbar-container view\">\n      <div className=\"custom-scrollbar-wrapper\" ref={wrapperRef}>\n        <div class=\"custom-scrollbar-content\">\n          <img onLoad={onLoad} src={girlImageLink} alt=\"custom\" />\n        </div>\n        {/* custom-horizontal-scrollbar */}\n        <div className=\"custom-horizontal-scrollbar\" ref={horizontalRef}>\n          <div className=\"custom-horizontal-indicator\"></div>\n        </div>\n      </div>\n      <div className=\"tip\">please use your mouse-wheel</div>\n    </div>\n  )\n}\n\nexport default Mousewheel\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/components/vertical.js",
    "content": "import React, { useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport ScrollBar from '@better-scroll/scroll-bar'\n\nBScroll.use(ScrollBar)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst data = createArray(40)\n\nconst Vertical = () => {\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollY: true,\n        scrollbar: true,\n      })\n    }\n  }, [])\n\n  return (\n    <div className=\"scrollbar view\">\n      <div className=\"scrollbar-wrapper\" ref={wrapperRef}>\n        <div className=\"scrollbar-content\">\n          {data.map((item) => (\n            <div key={item} className=\"scrollbar-content-item\">\n              {`I am item ${item} `}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Vertical\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/scrollbar/vertical',\n    name: 'vertical',\n  },\n  {\n    path: '/scrollbar/horizontal',\n    name: 'horizontal',\n  },\n  {\n    path: '/scrollbar/custom',\n    name: 'custom',\n  },\n  {\n    path: '/scrollbar/mousewheel',\n    name: 'use mousewheel',\n  },\n]\n\nconst Core = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view core\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Core\n"
  },
  {
    "path": "packages/react-examples/src/pages/scrollbar/index.styl",
    "content": ".scrollbar\n  height: 100%\n.scrollbar-wrapper\n  position: relative\n  height: 100%\n  padding: 0 10px\n  border: 1px solid #ccc\n  overflow: hidden\n.scrollbar-content-item\n  padding: 10px 0\n  list-style: none\n  border-bottom: 1px solid #ccc\n  text-align: left\n\n.horizontal-scrollbar-container\n  .scroll-wrapper\n    position relative\n    display flex\n    align-content center\n    width 90%\n    height 100px\n    margin 80px auto\n    white-space nowrap\n    border 3px solid rgba(30, 80, 255, 0.3)\n    border-radius 5px\n    overflow hidden\n    .scroll-content\n      display inline-block\n      align-self center\n    .scroll-item\n      opacity  0.6\n      color white\n      box-sizing border-box\n      height 50px\n      width 50px\n      line-height 50px\n      border-radius 50%\n      font-size 18px\n      display inline-block\n      text-align center\n      padding 0 10px\n      margin 0 10px\n      &:nth-child(4n)\n        background-color #06F\n      &:nth-child(4n+1)\n        background-color #0f9d00\n      &:nth-child(4n+2)\n        background-color #F00\n      &:nth-child(4n+3)\n        background-color #ffea00\n\n.custom-scrollbar-container\n  .custom-scrollbar-wrapper\n    position relative\n    width 280px\n    height 280px\n    overflow hidden\n  .custom-scrollbar-content\n    max-width none\n  .custom-vertical-scrollbar\n    position absolute\n    top 50%\n    right 10px\n    height 100px\n    width 7px\n    border-radius 6px\n    transform translateY(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-vertical-indicator\n    width 100%\n    height 20px\n    border-radius 6px\n    background-color #db8090\n  .custom-horizontal-scrollbar\n    position absolute\n    left 50%\n    bottom 10px\n    width 100px\n    height 7px\n    border-radius 6px\n    transform translateX(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-horizontal-indicator\n    height 100%\n    width 20px\n    border-radius 6px\n    background-color #db8090\n\n.mousewheel-scrollbar-container\n  .custom-scrollbar-wrapper\n    position relative\n    width 280px\n    height 280px\n    overflow hidden\n  .custom-scrollbar-content\n    display inline-block\n    height 280px\n    > img\n      max-width none\n  .custom-horizontal-scrollbar\n    position absolute\n    left 50%\n    bottom 10px\n    width 100px\n    height 7px\n    border-radius 6px\n    transform translateX(-50%) translateZ(0)\n    background-color rgb(200, 200, 200, 0.3)\n  .custom-horizontal-indicator\n    height 100%\n    width 20px\n    border-radius 6px\n    background-color #db8090\n  .tip\n    text-align center\n    margin-top 10px\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/components/banner.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst Banner = () => {\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        slide: true,\n        momentum: false,\n        bounce: false,\n        probeType: 3,\n      }))\n\n      BS.on('scrollEnd', () => {\n        console.log('CurrentPage => ', BS.getCurrentPage())\n      })\n      BS.on('slideWillChange', (page) => {\n        setCurrentPageIndex(page.pageX)\n      })\n      // v2.1.0\n      BS.on('slidePageChanged', (page) => {\n        console.log('CurrentPage changed to => ', page)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  const handleNextPage = () => {\n    scrollRef.current.next()\n  }\n  const handlePrePage = () => {\n    scrollRef.current.prev()\n  }\n\n  return (\n    <div className=\"slide-banner view\">\n      <div className=\"banner-wrapper\">\n        <div className=\"slide-banner-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-banner-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {nums.map((num) => (\n            <span\n              key={num}\n              className={classNames('dot', {\n                active: currentPageIndex === num - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n      <div className=\"btn-wrap\">\n        <button className=\"next\" onClick={handleNextPage}>\n          nextPage\n        </button>\n        <button className=\"prev\" onClick={handlePrePage}>\n          prePage\n        </button>\n      </div>\n    </div>\n  )\n}\n\nexport default Banner\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/components/dynamic.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\n\nconst Dynamic = () => {\n  const [num, setNum] = useState(1)\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          autoplay: false,\n          loop: true,\n        },\n        momentum: false,\n        bounce: false,\n        probeType: 3,\n      }))\n\n      BS.on('scrollEnd', () => {\n        console.log('【scrollEnd】CurrentPage => ', BS.getCurrentPage())\n      })\n      BS.on('slideWillChange', (page) => {\n        console.log('【slideWillChange】CurrentPage =>', page)\n        setCurrentPageIndex(page.pageX)\n      })\n      // v2.1.0\n      BS.on('slidePageChanged', (page) => {\n        console.log('【slidePageChanged】CurrentPage =>', page)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  useEffect(() => {\n    scrollRef.current?.refresh()\n  }, [num])\n\n  const handleIncrease = () => {\n    setNum((n) => n + 1)\n  }\n  const handleDecrease = () => {\n    setNum((n) => Math.max(1, n - 1))\n  }\n\n  return (\n    <div className=\"dynamic-slide-banner view\">\n      <div className=\"banner-wrapper\">\n        <div className=\"slide-banner-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-banner-content\">\n            {createArray(num).map((n) => (\n              <div key={n} className={`slide-page page${n}`}>\n                page {n}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {createArray(num).map((n) => (\n            <span\n              key={n}\n              className={classNames('dot', {\n                active: currentPageIndex === n - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n      <div className=\"btn-wrap\">\n        <button onClick={handleIncrease} className=\"increase\">\n          increase\n        </button>\n        <button onClick={handleDecrease} className=\"decrease\">\n          decrease\n        </button>\n      </div>\n    </div>\n  )\n}\n\nexport default Dynamic\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/components/fullpage.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst FullPage = () => {\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          threshold: 100,\n          loop: false,\n          autoplay: false,\n        },\n        useTransition: false,\n        momentum: false,\n        bounce: false,\n        stopPropagation: true,\n      }))\n\n      BS.on('scrollEnd', () => {\n        setCurrentPageIndex(BS.getCurrentPage().pageX)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"slide-fullpage view\">\n      <div className=\"banner-wrapper\">\n        <div className=\"slide-banner-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-banner-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {nums.map((num) => (\n            <span\n              key={num}\n              className={classNames('dot', {\n                active: currentPageIndex === num - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default FullPage\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/components/specified-index.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst SpecifiedIndex = () => {\n  const [slideCreated, setSlideCreated] = useState(false)\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: true,\n        scrollY: false,\n        slide: {\n          autoplay: false,\n          loop: true,\n          startPageXIndex: 2, // v2.3.0\n        },\n        momentum: false,\n        bounce: false,\n        probeType: 3,\n      }))\n\n      setCurrentPageIndex(BS.getCurrentPage().pageX)\n      setSlideCreated(true)\n\n      // v2.1.0\n      BS.on('slidePageChanged', (page) => {\n        console.log('CurrentPage changed to => ', page)\n        setCurrentPageIndex(page.pageX)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  const handleNextPage = () => {\n    scrollRef.current.next()\n  }\n  const handlePrePage = () => {\n    scrollRef.current.prev()\n  }\n\n  return (\n    <div className=\"slide-specified-index view\">\n      <div className=\"banner-wrapper\">\n        <div className=\"slide-specified-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-specified-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n      {slideCreated && (\n        <div className=\"description\">\n          currentPageIndex is {currentPageIndex}\n        </div>\n      )}\n\n      <div className=\"btn-wrap\">\n        <button className=\"next\" onClick={handleNextPage}>\n          nextPage\n        </button>\n        <button className=\"prev\" onClick={handlePrePage}>\n          prePage\n        </button>\n      </div>\n    </div>\n  )\n}\n\nexport default SpecifiedIndex\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/components/vertical.js",
    "content": "import React, { useState, useRef, useEffect } from 'react'\nimport classNames from 'classnames'\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(4)\n\nconst Vertical = () => {\n  const [currentPageIndex, setCurrentPageIndex] = useState(0)\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        scrollX: false,\n        scrollY: true,\n        slide: {\n          threshold: 100,\n        },\n        useTransition: true,\n        momentum: false,\n        bounce: false,\n        stopPropagation: true,\n      }))\n\n      BS.on('scrollEnd', () => {\n        setCurrentPageIndex(BS.getCurrentPage().pageY)\n      })\n    }\n\n    return () => {\n      scrollRef.current?.destroy()\n    }\n  }, [])\n\n  return (\n    <div className=\"slide-vertical view\">\n      <div className=\"vertical-wrapper\">\n        <div className=\"slide-vertical-wrapper\" ref={wrapperRef}>\n          <div className=\"slide-vertical-content\">\n            {nums.map((num) => (\n              <div key={num} className={`slide-page page${num}`}>\n                page {num}\n              </div>\n            ))}\n          </div>\n        </div>\n        <div className=\"dots-wrapper\">\n          {nums.map((num) => (\n            <span\n              key={num}\n              className={classNames('dot', {\n                active: currentPageIndex === num - 1,\n              })}\n            ></span>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Vertical\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst examples = [\n  {\n    path: '/slide/banner',\n    name: 'banner slide',\n  },\n  {\n    path: '/slide/fullpage',\n    name: 'page slide',\n  },\n  {\n    path: '/slide/vertical',\n    name: 'vertical slide',\n  },\n  {\n    path: '/slide/dynamic',\n    name: 'dynamic slide（v2.1.0）',\n  },\n  {\n    path: '/slide/specified',\n    name: 'specified index slide（v2.3.0）',\n  },\n]\n\nconst Core = (props) => {\n  const goPage = (path) => {\n    props.history.push(path)\n  }\n\n  return (\n    <div className=\"view slide\">\n      <ul className=\"example-list\">\n        {examples.map((item) => (\n          <li\n            className=\"example-item\"\n            onClick={() => goPage(item.path)}\n            key={item.path}\n          >\n            <span>{item.name}</span>\n          </li>\n        ))}\n      </ul>\n      {props.children}\n    </div>\n  )\n}\n\nexport default Core\n"
  },
  {
    "path": "packages/react-examples/src/pages/slide/index.styl",
    "content": ".slide-banner\n  .banner-wrapper\n    position relative\n  .slide-banner-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-banner-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n\n.slide-fullpage\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .banner-wrapper\n    position relative\n    height 100%\n  .slide-banner-wrapper\n    height 100%\n    overflow hidden\n  .slide-banner-content\n    height 100%\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 100%\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n\n.slide-vertical\n  height 100%\n  &.view\n    padding 0\n    height 100%\n  .vertical-wrapper\n    position relative\n    height 100%\n    font-size 0\n  .slide-vertical-wrapper\n    height 100%\n    overflow hidden\n    .slide-page\n      display inline-block\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      transform translate3d(0,0,0)\n      backface-visibility hidden\n      &.page1\n        background-color #D6EADF\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .dots-wrapper\n    position absolute\n    right 4px\n    top 50%\n    transform translateY(-50%)\n    .dot\n      display block\n      margin 4px 0\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        height  20px\n        border-radius 5px\n\n.dynamic-slide-banner\n  .banner-wrapper\n    position relative\n  .slide-banner-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-banner-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n      &.page5\n        background-color #E71D36\n      &.page6\n        background-color #2EC4B6\n      &.page7\n        background-color #EFFFE9\n      &.page8\n        background-color #011627\n  .dots-wrapper\n    position absolute\n    bottom 4px\n    left 50%\n    transform translateX(-50%)\n    .dot\n      display inline-block\n      margin 0 4px\n      width 8px\n      height 8px\n      border-radius 50%\n      background #eee\n      &.active\n        width 20px\n        border-radius 5px\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n\n.slide-specified-index\n  .banner-wrapper\n    position relative\n  .slide-specified-wrapper\n    min-height 1px\n    overflow hidden\n  .slide-specified-content\n    height 200px\n    white-space nowrap\n    font-size 0\n    .slide-page\n      display inline-block\n      height 200px\n      width 100%\n      line-height 200px\n      text-align center\n      font-size 26px\n      &.page1\n        background-color #95B8D1\n      &.page2\n        background-color #DDA789\n      &.page3\n        background-color #C3D899\n      &.page4\n        background-color #F2D4A7\n  .description\n    text-align center\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n"
  },
  {
    "path": "packages/react-examples/src/pages/zoom/components/default.js",
    "content": "import React, { useRef, useState, useEffect } from 'react'\nimport BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\n\nimport good from './good.svg'\n\nBScroll.use(Zoom)\n\nconst createArray = (length) => Array.from({ length }, (_v, i) => i + 1)\nconst nums = createArray(16)\n\nconst Default = () => {\n  const [linkworkTransform, setLinkworkTransform] = useState('scale(1)')\n\n  const wrapperRef = useRef(null)\n  const scrollRef = useRef(null)\n\n  const handleZoomTo = (value) => {\n    scrollRef.current.zoomTo(value, 'center', 'center')\n  }\n\n  useEffect(() => {\n    if (!scrollRef.current) {\n      const BS = (scrollRef.current = new BScroll(wrapperRef.current, {\n        freeScroll: true,\n        scrollX: true,\n        scrollY: true,\n        disableMouse: true,\n        useTransition: true,\n        zoom: {\n          start: 1.5,\n          min: 0.5,\n          max: 3,\n          initialOrigin: ['center', 'center'],\n        },\n      }))\n\n      BS.on('zooming', ({ scale }) => {\n        setLinkworkTransform(`scale(${scale})`)\n      })\n\n      BS.on('zoomEnd', ({ scale }) => {\n        console.log(scale)\n      })\n\n      return () => {\n        scrollRef.current?.destroy()\n      }\n    }\n  }, [])\n\n  return (\n    <div className=\"zoom-default view\">\n      <div className=\"zoom-wrapper\" ref={wrapperRef}>\n        <div className=\"zoom-items\">\n          {nums.map((item, index) => (\n            <div key={index} className=\"grid-item\">\n              {item}\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className=\"btn-wrap\">\n        <button className=\"zoom-half\" onClick={() => handleZoomTo(0.5)}>\n          zoomTo:0.5\n        </button>\n        <button className=\"zoom-original\" onClick={() => handleZoomTo(1)}>\n          zoomTo:1\n        </button>\n        <button className=\"zoom-double\" onClick={() => handleZoomTo(2)}>\n          zoomTo:2\n        </button>\n      </div>\n      <div className=\"linkwork-wrap\">\n        <p>changing with zooming action</p>\n        <div\n          className=\"linkwork-block\"\n          style={{ transform: linkworkTransform }}\n        >\n          <img src={good} alt=\"\" />\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default Default\n"
  },
  {
    "path": "packages/react-examples/src/pages/zoom/index.js",
    "content": "import React from 'react'\n\nimport './index.styl'\n\nconst Zoom = (props) => <>{props.children}</>\n\nexport default Zoom\n"
  },
  {
    "path": "packages/react-examples/src/pages/zoom/index.styl",
    "content": ".zoom-default\n  .zoom-wrapper\n    width 100%\n    overflow hidden\n    .zoom-items\n      display flex\n      flex-direction row\n      flex-wrap wrap\n      align-content space-between\n      .grid-item\n        flex 1 1 25%\n        box-sizing border-box\n        height 52px\n        line-height 52px\n        border 1px solid #eee\n        text-align center\n        &:nth-child(2n)\n          background-color #b3d4a8\n        &:nth-child(2n+1)\n          background-color #b6b7a3\n  .btn-wrap\n    margin-top 20px\n    display flex\n    justify-content center\n    button\n      margin 0 10px\n      padding 10px\n      color #fff\n      border-radius 4px\n      background-color #666\n  .linkwork-wrap\n    margin-top 50px\n    p\n      margin 10px 0\n      font-size 16px\n      font-weight bold\n      text-align center\n  .linkwork-block\n    margin 10px auto\n    width 60px\n    height 60px\n    border-radius 50%\n    img {\n      width 100%\n    }\n"
  },
  {
    "path": "packages/react-examples/src/reportWebVitals.js",
    "content": "const reportWebVitals = onPerfEntry => {\n  if (onPerfEntry && onPerfEntry instanceof Function) {\n    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n      getCLS(onPerfEntry);\n      getFID(onPerfEntry);\n      getFCP(onPerfEntry);\n      getLCP(onPerfEntry);\n      getTTFB(onPerfEntry);\n    });\n  }\n};\n\nexport default reportWebVitals;\n"
  },
  {
    "path": "packages/react-examples/src/router.js",
    "content": "import React, { lazy, Suspense } from 'react'\nimport { Switch, Route } from 'react-router-dom'\n\nconst ROUTES = [\n  {\n    path: '/core',\n    component: lazy(() => import('./pages/core')),\n    routes: [\n      {\n        path: '/core/default',\n        component: lazy(() => import('./pages/core/components/default')),\n      },\n      {\n        path: '/core/horizontal',\n        component: lazy(() => import('./pages/core/components/horizontal')),\n      },\n      {\n        path: '/core/dynamic-content',\n        component: lazy(() =>\n          import('./pages/core/components/dynamic-content')\n        ),\n      },\n      {\n        path: '/core/specified-content',\n        component: lazy(() =>\n          import('./pages/core/components/specified-content')\n        ),\n      },\n      {\n        path: '/core/freescroll',\n        component: lazy(() => import('./pages/core/components/freescroll')),\n      },\n      {\n        path: '/core/vertical-rotated',\n        component: lazy(() =>\n          import('./pages/core/components/vertical-rotated')\n        ),\n      },\n      {\n        path: '/core/horizontal-rotated',\n        component: lazy(() =>\n          import('./pages/core/components/horizontal-rotated')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/observe-dom',\n    component: lazy(() => import('./pages/observe-dom')),\n    routes: [\n      {\n        path: '/observe-dom/',\n        component: lazy(() => import('./pages/observe-dom/components/default')),\n      },\n    ],\n  },\n  {\n    path: '/observe-image',\n    component: lazy(() => import('./pages/observe-image')),\n    routes: [\n      {\n        path: '/observe-image/',\n        component: lazy(() =>\n          import('./pages/observe-image/components/default')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/slide',\n    component: lazy(() => import('./pages/slide')),\n    routes: [\n      {\n        path: '/slide/banner',\n        component: lazy(() => import('./pages/slide/components/banner')),\n      },\n      {\n        path: '/slide/fullpage',\n        component: lazy(() => import('./pages/slide/components/fullpage')),\n      },\n      {\n        path: '/slide/vertical',\n        component: lazy(() => import('./pages/slide/components/vertical')),\n      },\n      {\n        path: '/slide/dynamic',\n        component: lazy(() => import('./pages/slide/components/dynamic')),\n      },\n      {\n        path: '/slide/specified',\n        component: lazy(() =>\n          import('./pages/slide/components/specified-index')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/zoom',\n    component: lazy(() => import('./pages/zoom')),\n    routes: [\n      {\n        path: '/zoom/',\n        component: lazy(() => import('./pages/zoom/components/default')),\n      },\n    ],\n  },\n  {\n    path: '/picker',\n    component: lazy(() => import('./pages/picker')),\n    routes: [\n      {\n        path: '/picker/one-column',\n        component: lazy(() => import('./pages/picker/components/one-column')),\n      },\n      {\n        path: '/picker/double-column',\n        component: lazy(() =>\n          import('./pages/picker/components/double-column')\n        ),\n      },\n      {\n        path: '/picker/linkage-column',\n        component: lazy(() =>\n          import('./pages/picker/components/linkage-column')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/pullup',\n    component: lazy(() => import('./pages/pullup')),\n    routes: [\n      {\n        path: '/pullup/',\n        component: lazy(() => import('./pages/pullup/components/default')),\n      },\n    ],\n  },\n  {\n    path: '/pulldown',\n    component: lazy(() => import('./pages/pulldown')),\n    routes: [\n      {\n        path: '/pulldown/default',\n        component: lazy(() => import('./pages/pulldown/components/default')),\n      },\n      {\n        path: '/pulldown/sina',\n        component: lazy(() => import('./pages/pulldown/components/sina-weibo')),\n      },\n    ],\n  },\n  {\n    path: '/scrollbar',\n    component: lazy(() => import('./pages/scrollbar')),\n    routes: [\n      {\n        path: '/scrollbar/vertical',\n        component: lazy(() => import('./pages/scrollbar/components/vertical')),\n      },\n      {\n        path: '/scrollbar/horizontal',\n        component: lazy(() =>\n          import('./pages/scrollbar/components/horizontal')\n        ),\n      },\n      {\n        path: '/scrollbar/custom',\n        component: lazy(() => import('./pages/scrollbar/components/custom')),\n      },\n      {\n        path: '/scrollbar/mousewheel',\n        component: lazy(() =>\n          import('./pages/scrollbar/components/mousewheel')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/indicators',\n    component: lazy(() => import('./pages/indicators')),\n    routes: [\n      {\n        path: '/indicators/minimap',\n        component: lazy(() => import('./pages/indicators/components/minimap')),\n      },\n      {\n        path: '/indicators/parallax-scroll',\n        component: lazy(() =>\n          import('./pages/indicators/components/parallax-scroll')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/infinity',\n    component: lazy(() => import('./pages/infinity')),\n  },\n  {\n    path: '/form',\n    component: lazy(() => import('./pages/form')),\n    routes: [\n      {\n        path: '/form/textarea',\n        component: lazy(() => import('./pages/form/components/textarea')),\n      },\n    ],\n  },\n  {\n    path: '/nested-scroll',\n    component: lazy(() => import('./pages/nested-scroll')),\n    routes: [\n      {\n        path: '/nested-scroll/vertical',\n        component: lazy(() =>\n          import('./pages/nested-scroll/components/vertical')\n        ),\n      },\n      {\n        path: '/nested-scroll/horizontal',\n        component: lazy(() =>\n          import('./pages/nested-scroll/components/horizontal')\n        ),\n      },\n      {\n        path: '/nested-scroll/horizontal-in-vertical',\n        component: lazy(() =>\n          import('./pages/nested-scroll/components/horizontal-in-vertical')\n        ),\n      },\n      {\n        path: '/nested-scroll/triple-vertical',\n        component: lazy(() =>\n          import('./pages/nested-scroll/components/triple-vertical')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/mouse-wheel',\n    component: lazy(() => import('./pages/mouse-wheel')),\n    routes: [\n      {\n        path: '/mouse-wheel/vertical-scroll',\n        component: lazy(() =>\n          import('./pages/mouse-wheel/components/vertical-scroll')\n        ),\n      },\n      {\n        path: '/mouse-wheel/horizontal-scroll',\n        component: lazy(() =>\n          import('./pages/mouse-wheel/components/horizontal-scroll')\n        ),\n      },\n      {\n        path: '/mouse-wheel/vertical-slide',\n        component: lazy(() =>\n          import('./pages/mouse-wheel/components/vertical-slide')\n        ),\n      },\n      {\n        path: '/mouse-wheel/horizontal-slide',\n        component: lazy(() =>\n          import('./pages/mouse-wheel/components/horizontal-slide')\n        ),\n      },\n      {\n        path: '/mouse-wheel/pullup',\n        component: lazy(() => import('./pages/mouse-wheel/components/pullup')),\n      },\n      {\n        path: '/mouse-wheel/pulldown',\n        component: lazy(() =>\n          import('./pages/mouse-wheel/components/pulldown')\n        ),\n      },\n      {\n        path: '/mouse-wheel/picker',\n        component: lazy(() => import('./pages/mouse-wheel/components/picker')),\n      },\n    ],\n  },\n  {\n    path: '/movable',\n    component: lazy(() => import('./pages/movable')),\n    routes: [\n      {\n        path: '/movable/default',\n        component: lazy(() => import('./pages/movable/components/default')),\n      },\n      {\n        path: '/movable/scale',\n        component: lazy(() => import('./pages/movable/components/scale')),\n      },\n      {\n        path: '/movable/multi-content',\n        component: lazy(() =>\n          import('./pages/movable/components/multi-content')\n        ),\n      },\n      {\n        path: '/movable/multi-content-scale',\n        component: lazy(() =>\n          import('./pages/movable/components/multi-content-scale')\n        ),\n      },\n    ],\n  },\n  {\n    path: '/compose',\n    component: lazy(() => import('./pages/compose')),\n    routes: [\n      {\n        path: '/compose/pullup-pulldown',\n        component: lazy(() =>\n          import('./pages/compose/components/pullup-pulldown')\n        ),\n      },\n      {\n        path: '/compose/pullup-pulldown-slide',\n        component: lazy(() =>\n          import('./pages/compose/components/pullup-pulldown-slide')\n        ),\n      },\n      {\n        path: '/compose/pullup-pulldown-outnested',\n        component: lazy(() =>\n          import('./pages/compose/components/pullup-pulldown-outnested')\n        ),\n      },\n      {\n        path: '/compose/slide-nested',\n        component: lazy(() =>\n          import('./pages/compose/components/slide-nested')\n        ),\n      },\n    ],\n  },\n]\n\nconst renderRoute = (route) => {\n  const { component: Component, ...rest } = route\n  return (\n    <Route\n      {...rest}\n      key={route.path}\n      render={(props) => (\n        <Component {...props}>\n          <Router routes={route.routes}></Router>\n        </Component>\n      )}\n    />\n  )\n}\n\nconst Router = ({ routes }) => {\n  if (!routes?.length) {\n    return null\n  }\n  return <Switch>{routes.map((route) => renderRoute(route))}</Switch>\n}\n\nconst RouterView = () => (\n  <Suspense fallback={null}>\n    <Router routes={ROUTES} />\n  </Suspense>\n)\n\nexport default RouterView\n"
  },
  {
    "path": "packages/react-examples/src/setupTests.js",
    "content": "// jest-dom adds custom jest matchers for asserting on DOM nodes.\n// allows you to do things like:\n// expect(element).toHaveTextContent(/react/i)\n// learn more: https://github.com/testing-library/jest-dom\nimport '@testing-library/jest-dom';\n"
  },
  {
    "path": "packages/scroll-bar/README.md",
    "content": "# @better-scroll/scroll-bar\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/scroll-bar/README_zh-CN.md)\n\nAn elegant and beautiful scroll bar.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Scrollbar from '@better-scroll/scroll-bar'\nBScroll.use(Scrollbar)\n\nconst bs = new BScroll('.wrapper', {\n  scrollbar: true\n})\n```\n"
  },
  {
    "path": "packages/scroll-bar/README_zh-CN.md",
    "content": "# @better-scroll/scroll-bar\n\n一个优雅美丽的滚动条.\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Scrollbar from '@better-scroll/scroll-bar'\nBScroll.use(Scrollbar)\n\nconst bs = new BScroll('.wrapper', {\n  scrollbar: true\n})\n```\n"
  },
  {
    "path": "packages/scroll-bar/package.json",
    "content": "{\n  \"name\": \"@better-scroll/scroll-bar\",\n  \"version\": \"2.5.1\",\n  \"description\": \"scrollbar is used to BetterScroll, which behaves like browser scrollbar\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/scroll-bar.min.js\",\n  \"module\": \"dist/scroll-bar.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"scrollbar\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/scroll-bar\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/scroll-bar/src/__mocks__/event-handler.ts",
    "content": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst EventHandler = jest.fn().mockImplementation(() => {\n  return {\n    hooks: new EventEmitter(['touchStart', 'touchMove', 'touchEnd']),\n    destroy: jest.fn(),\n  }\n})\n\nexport default EventHandler\n"
  },
  {
    "path": "packages/scroll-bar/src/__mocks__/indicator.ts",
    "content": "const mockIndicator = jest\n  .fn()\n  .mockImplementation(function IndicatorMockFn(scroll: any, options: any) {\n    return {\n      wrapper: options.wrapper,\n      destroy: jest.fn(),\n    }\n  })\n\nexport default mockIndicator\n"
  },
  {
    "path": "packages/scroll-bar/src/__tests__/__snapshots__/index.spec.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`scroll-bar unit tests constructor should create indicator elements 1`] = `\n<div>\n  <div />\n  <div\n    class=\"bscroll-horizontal-scrollbar\"\n    style=\"position: absolute; z-index: 9999; overflow: hidden; height: 7px; left: 2px; right: 2px; bottom: 0px; pointer-events: none;\"\n  >\n    <div\n      class=\"bscroll-indicator\"\n      style=\"box-sizing: border-box; position: absolute; background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(255,255,255,0.9); border-radius: 3px; height: 100%;\"\n    />\n  </div>\n  <div\n    class=\"bscroll-vertical-scrollbar\"\n    style=\"position: absolute; z-index: 9999; overflow: hidden; width: 7px; bottom: 2px; top: 2px; right: 1px; pointer-events: none;\"\n  >\n    <div\n      class=\"bscroll-indicator\"\n      style=\"box-sizing: border-box; position: absolute; background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(255,255,255,0.9); border-radius: 3px; width: 100%;\"\n    />\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "packages/scroll-bar/src/__tests__/event-handler.spec.ts",
    "content": "import Indicator, { IndicatorDirection, OffsetType } from '../indicator'\nimport BScroll from '@better-scroll/core'\nimport EventHandler from '../event-handler'\nimport {\n  dispatchTouchStart,\n  dispatchTouchMove,\n  dispatchTouchEnd,\n} from '@better-scroll/core/src/__tests__/__utils__/event'\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\ndescribe('scroll-bar indicator tests', () => {\n  let scroll: BScroll\n  let indicator: Indicator\n  let wrapper = document.createElement('div')\n  let indicatorEl = document.createElement('div')\n  wrapper.appendChild(indicatorEl)\n  let indicatorOptions = {\n    wrapper,\n    direction: IndicatorDirection.Vertical,\n    fade: true,\n    fadeInTime: 250,\n    fadeOutTime: 500,\n    interactive: false,\n    minSize: 8,\n    isCustom: false,\n    scrollbarTrackClickable: false,\n    scrollbarTrackOffsetType: OffsetType.Step,\n    scrollbarTrackOffsetTime: 300,\n  }\n  let EventHandlerOptions = {\n    disableMouse: false,\n    disableTouch: false,\n  }\n\n  beforeEach(() => {\n    // create Dom\n    const wrapper = document.createElement('div')\n    const content = document.createElement('div')\n    wrapper.appendChild(content)\n    scroll = new BScroll(wrapper, {})\n    indicator = new Indicator(scroll, indicatorOptions)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('touchStart | touchMove | touchEnd hooks', () => {\n    const eventHandler = new EventHandler(indicator, EventHandlerOptions)\n    expect(Object.keys(eventHandler.hooks.eventTypes)).toMatchObject([\n      'touchStart',\n      'touchMove',\n      'touchEnd',\n    ])\n\n    const touchStartMockFn = jest.fn()\n    const touchMoveMockFn = jest.fn()\n    const touchEndMockFn = jest.fn()\n    const eventHandlerHooks = eventHandler.hooks\n    eventHandlerHooks.on(\n      eventHandlerHooks.eventTypes.touchStart,\n      touchStartMockFn\n    )\n    eventHandlerHooks.on(\n      eventHandlerHooks.eventTypes.touchMove,\n      touchMoveMockFn\n    )\n    eventHandlerHooks.on(eventHandlerHooks.eventTypes.touchEnd, touchEndMockFn)\n\n    // scroll is disabled\n    addProperties(scroll, {\n      enabled: false,\n    })\n    dispatchTouchStart(indicatorEl, [\n      {\n        pageX: 0,\n        pageY: 0,\n      },\n    ])\n    dispatchTouchMove(window, [\n      {\n        pageX: 10,\n        pageY: 10,\n      },\n    ])\n    dispatchTouchEnd(window, [\n      {\n        pageX: 20,\n        pageY: 20,\n      },\n    ])\n    expect(touchStartMockFn).not.toBeCalled()\n    expect(touchMoveMockFn).not.toBeCalled()\n    expect(touchEndMockFn).not.toBeCalled()\n\n    // scroll is enabled\n    addProperties(scroll, {\n      enabled: true,\n    })\n    dispatchTouchStart(indicatorEl, [\n      {\n        pageX: 0,\n        pageY: 0,\n      },\n    ])\n    dispatchTouchMove(window, [\n      {\n        pageX: 10,\n        pageY: 10,\n      },\n    ])\n    dispatchTouchEnd(window, [\n      {\n        pageX: 20,\n        pageY: 20,\n      },\n    ])\n    expect(touchStartMockFn).toBeCalled()\n    expect(touchMoveMockFn).toBeCalled()\n    expect(touchEndMockFn).toBeCalled()\n  })\n\n  it('destroy', () => {\n    const eventHandler = new EventHandler(indicator, EventHandlerOptions)\n    eventHandler.destroy()\n  })\n})\n"
  },
  {
    "path": "packages/scroll-bar/src/__tests__/index.spec.ts",
    "content": "import Indicator from '../indicator'\nimport BScroll, { Options } from '@better-scroll/core'\n\njest.mock('@better-scroll/core')\njest.mock('../indicator')\n\nimport ScrollBar from '../index'\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\ndescribe('scroll-bar unit tests', () => {\n  let scroll: BScroll\n  let options: Partial<Options>\n\n  beforeAll(() => {\n    // create Dom\n    const wrapper = document.createElement('div')\n    const content = document.createElement('div')\n    wrapper.appendChild(content)\n    // mock bscroll\n    options = {\n      scrollbar: true,\n      scrollX: true,\n      scrollY: true,\n    }\n    scroll = new BScroll(wrapper, options)\n  })\n\n  beforeEach(() => {\n    jest.clearAllMocks()\n  })\n\n  describe('constructor', () => {\n    it('should create indicator elements', () => {\n      const scrollbar = new ScrollBar(scroll)\n      // then\n      expect(scroll.wrapper).toMatchSnapshot()\n      expect(scrollbar.options).toMatchObject({\n        fade: true,\n        interactive: false,\n        customElements: [],\n        minSize: 8,\n        scrollbarTrackClickable: false,\n        scrollbarTrackOffsetType: 'step',\n        scrollbarTrackOffsetTime: 300,\n      })\n    })\n\n    it('custom scrollbar', () => {\n      const customHScrollbar = document.createElement('div')\n      addProperties(scroll.options, {\n        scrollX: true,\n        scrollY: false,\n        scrollbar: {\n          customElements: [customHScrollbar],\n        },\n      })\n      const scrollbar = new ScrollBar(scroll)\n      expect(scrollbar.indicators[0].wrapper).toBe(customHScrollbar)\n    })\n\n    it('destroy hook', () => {\n      const scrollbar = new ScrollBar(scroll)\n      scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n      for (let indicator of scrollbar.indicators) {\n        expect(indicator.destroy).toBeCalled()\n      }\n    })\n  })\n})\n"
  },
  {
    "path": "packages/scroll-bar/src/__tests__/indicator.spec.ts",
    "content": "import Indicator, { IndicatorDirection, OffsetType } from '../indicator'\nimport EventHandler from '../event-handler'\nimport BScroll from '@better-scroll/core'\nimport { dispatchClick } from '@better-scroll/core/src/__tests__/__utils__/event'\n\njest.mock('@better-scroll/core')\njest.mock('../event-handler')\n\nconst addProperties = <T extends Object, K extends Object>(\n  target: T,\n  source: K\n) => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target\n}\n\ndescribe('scroll-bar indicator tests', () => {\n  let scroll: BScroll\n  let indicator: Indicator\n  let wrapper = document.createElement('div')\n  let indicatorEl = document.createElement('div')\n  wrapper.appendChild(indicatorEl)\n  let indicatorOptions = {\n    wrapper,\n    direction: IndicatorDirection.Vertical,\n    fade: true,\n    fadeInTime: 250,\n    fadeOutTime: 500,\n    interactive: false,\n    minSize: 8,\n    isCustom: false,\n    scrollbarTrackClickable: false,\n    scrollbarTrackOffsetType: OffsetType.Step,\n    scrollbarTrackOffsetTime: 300,\n  }\n\n  beforeEach(() => {\n    // create Dom\n    const wrapper = document.createElement('div')\n    const content = document.createElement('div')\n    wrapper.appendChild(content)\n    scroll = new BScroll(wrapper, {})\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should have corrent key', () => {\n    indicator = new Indicator(scroll, indicatorOptions)\n    expect(indicator.keysMap).toMatchObject({\n      hasScroll: 'hasVerticalScroll',\n      size: 'height',\n      wrapperSize: 'clientHeight',\n      scrollerSize: 'scrollerHeight',\n      maxScrollPos: 'maxScrollY',\n      pos: 'y',\n      point: 'pageY',\n      translateProperty: 'translateY',\n      domRect: 'top',\n    })\n    expect(wrapper.style.opacity).toEqual('0')\n  })\n\n  it('refresh hook', () => {\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    addProperties(scroll, {\n      hasHorizontalScroll: true,\n      maxScrollX: 8,\n    })\n    scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n    expect(indicator.currentPos).toBe(-8)\n  })\n\n  it('translate hook', () => {\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    addProperties(scroll, {\n      hasHorizontalScroll: true,\n      maxScrollX: 8,\n    })\n    const translaterHooks = scroll.scroller.translater.hooks\n    scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n    translaterHooks.trigger(translaterHooks.eventTypes.translate, {\n      x: 10,\n      y: 0,\n    })\n    expect(indicator.currentPos).toBe(0)\n  })\n\n  it('transitionTime and transitionTimingFunction hook', () => {\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    const animaterHooks = scroll.scroller.animater.hooks\n    animaterHooks.trigger(animaterHooks.eventTypes.time)\n    animaterHooks.trigger(\n      animaterHooks.eventTypes.timeFunction,\n      'cubic-bezier(0.23, 1, 0.32, 1)'\n    )\n    expect(indicator.indicatorEl.style.transitionDuration).toBe('0ms')\n    expect(indicator.indicatorEl.style.transitionTimingFunction).toBe(\n      'cubic-bezier(0.23, 1, 0.32, 1)'\n    )\n  })\n\n  it(\"about scrolling's hook\", () => {\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    scroll.trigger(scroll.eventTypes.scrollStart)\n    expect(indicator.wrapper.style.opacity).toBe('1')\n    scroll.trigger(scroll.eventTypes.scrollEnd)\n    expect(indicator.wrapper.style.opacity).toBe('0')\n  })\n\n  it(\"about mouse-wheel scrolling's hook\", () => {\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    scroll.registerType(['mousewheelStart', 'mousewheelMove', 'mousewheelEnd'])\n    scroll.trigger(scroll.eventTypes.mousewheelStart)\n    expect(indicator.wrapper.style.opacity).toBe('1')\n    scroll.trigger(scroll.eventTypes.mousewheelEnd)\n    expect(indicator.wrapper.style.opacity).toBe('0')\n    scroll.trigger(scroll.eventTypes.mousewheelMove)\n    expect(indicator.wrapper.style.opacity).toBe('1')\n  })\n\n  it('interactive option', () => {\n    // horizontal\n    addProperties(scroll.options, {\n      probeType: 3,\n    })\n    addProperties(scroll, {\n      hasHorizontalScroll: true,\n      maxScrollX: 8,\n    })\n    Object.assign(indicatorOptions, {\n      direction: IndicatorDirection.Horizontal,\n      interactive: true,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    indicator.refresh()\n    const beforeStartMockFn = jest.fn()\n    const startMockFn = jest.fn()\n    const moveMockFn = jest.fn()\n    const endMockFn = jest.fn()\n    const scroller = scroll.scroller\n    scroller.hooks.on(\n      scroller.hooks.eventTypes.beforeScrollStart,\n      beforeStartMockFn\n    )\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollStart, startMockFn)\n    scroller.hooks.on(scroller.hooks.eventTypes.scroll, moveMockFn)\n    scroller.hooks.on(scroller.hooks.eventTypes.scrollEnd, endMockFn)\n\n    const eventHandlerHooks = indicator.eventHandler.hooks\n    indicator.scrollInfo.maxScrollPos = 10\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchStart)\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchMove, 2)\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchEnd)\n    expect(beforeStartMockFn).toBeCalled()\n    expect(startMockFn).toBeCalled()\n    expect(moveMockFn).toBeCalled()\n    expect(endMockFn).toBeCalled()\n    expect(scroll.scroller.translater.translate).toBeCalled()\n\n    // vertical\n    addProperties(scroll.options, {\n      probeType: 1,\n    })\n    addProperties(scroll, {\n      hasHorizontalScroll: false,\n      hasVerticalScroll: true,\n      maxScrollX: 0,\n      maxScrollY: 8,\n    })\n    addProperties(indicator, {\n      direction: IndicatorDirection.Vertical,\n    })\n\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchStart)\n    indicator.startTime = indicator.startTime - 400\n    indicator.scrollInfo.maxScrollPos = 10\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchMove, 2)\n    eventHandlerHooks.trigger(eventHandlerHooks.eventTypes.touchEnd)\n\n    expect(beforeStartMockFn).toBeCalledTimes(2)\n    expect(startMockFn).toBeCalledTimes(2)\n    expect(moveMockFn).toBeCalledTimes(2)\n    expect(endMockFn).toBeCalledTimes(2)\n    expect(scroll.scroller.translater.translate).toBeCalledTimes(2)\n  })\n\n  it('updatePosition', () => {\n    addProperties(scroll, {\n      maxScrollY: -8,\n    })\n    addProperties(indicatorOptions, {\n      direction: IndicatorDirection.Vertical,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    indicator.refresh()\n    indicator.updatePosition({\n      x: 0,\n      y: -2,\n    })\n    expect(indicator.currentPos).toBe(0)\n\n    addProperties(scroll, {\n      maxScrollY: 8,\n    })\n    addProperties(indicator.options, {\n      isCustom: true,\n    })\n    indicator.refresh()\n    indicator.updatePosition({\n      x: 0,\n      y: 2,\n    })\n    expect(indicator.currentPos).toBe(0)\n  })\n\n  it('click', () => {\n    addProperties(scroll, {\n      maxScrollY: -8,\n    })\n    addProperties(indicatorOptions, {\n      direction: IndicatorDirection.Vertical,\n      scrollbarTrackClickable: true,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    indicator.refresh()\n    dispatchClick(indicator.wrapper, 'click')\n    expect(scroll.scrollTo).toBeCalled()\n\n    addProperties(indicator, {\n      direction: IndicatorDirection.Horizontal,\n    })\n    addProperties(indicator.options, {\n      scrollbarTrackClickable: true,\n      scrollbarTrackOffsetType: OffsetType.Point,\n    })\n    dispatchClick(indicator.wrapper, 'click')\n    expect(scroll.scrollTo).toBeCalled()\n  })\n\n  it('destroy', () => {\n    const parentNode = document.createElement('div')\n    parentNode.appendChild(wrapper)\n    addProperties(indicatorOptions, {\n      direction: IndicatorDirection.Vertical,\n      scrollbarTrackClickable: true,\n      isCustom: false,\n    })\n    indicator = new Indicator(scroll, indicatorOptions)\n    indicator.destroy()\n    expect(indicator.eventHandler.destroy).toBeCalled()\n    expect(indicator.hooksFn.length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "packages/scroll-bar/src/event-handler.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport {\n  TouchEvent,\n  EventRegister,\n  EventEmitter,\n  maybePrevent,\n} from '@better-scroll/shared-utils'\nimport Indicator from './indicator'\n\ninterface EventHandlerOptions {\n  disableMouse: boolean\n  disableTouch: boolean\n}\n\nexport default class EventHandler {\n  startEventRegister: EventRegister\n  moveEventRegister: EventRegister\n  endEventRegister: EventRegister\n  initiated: boolean\n  lastPoint: number\n  scroll: BScroll\n  hooks: EventEmitter\n\n  constructor(\n    public indicator: Indicator,\n    public options: EventHandlerOptions\n  ) {\n    this.hooks = new EventEmitter(['touchStart', 'touchMove', 'touchEnd'])\n    this.registerEvents()\n  }\n  private registerEvents() {\n    const { disableMouse, disableTouch } = this.options\n    const startEvents = []\n    const moveEvents = []\n    const endEvents = []\n\n    if (!disableMouse) {\n      startEvents.push({\n        name: 'mousedown',\n        handler: this.start.bind(this),\n      })\n\n      moveEvents.push({\n        name: 'mousemove',\n        handler: this.move.bind(this),\n      })\n\n      endEvents.push({\n        name: 'mouseup',\n        handler: this.end.bind(this),\n      })\n    }\n\n    if (!disableTouch) {\n      startEvents.push({\n        name: 'touchstart',\n        handler: this.start.bind(this),\n      })\n\n      moveEvents.push({\n        name: 'touchmove',\n        handler: this.move.bind(this),\n      })\n\n      endEvents.push(\n        {\n          name: 'touchend',\n          handler: this.end.bind(this),\n        },\n        {\n          name: 'touchcancel',\n          handler: this.end.bind(this),\n        }\n      )\n    }\n\n    this.startEventRegister = new EventRegister(\n      this.indicator.indicatorEl,\n      startEvents\n    )\n    this.moveEventRegister = new EventRegister(window, moveEvents)\n    this.endEventRegister = new EventRegister(window, endEvents)\n  }\n\n  private BScrollIsDisabled() {\n    return !this.indicator.scroll.enabled\n  }\n\n  private start(e: TouchEvent) {\n    if (this.BScrollIsDisabled()) {\n      return\n    }\n    let point = (e.touches ? e.touches[0] : e) as Touch\n\n    maybePrevent(e)\n    e.stopPropagation()\n\n    this.initiated = true\n    this.lastPoint = point[this.indicator.keysMap.point]\n    this.hooks.trigger(this.hooks.eventTypes.touchStart)\n  }\n\n  private move(e: TouchEvent) {\n    if (!this.initiated) {\n      return\n    }\n    let point = (e.touches ? e.touches[0] : e) as Touch\n    const pointPos = point[this.indicator.keysMap.point]\n\n    maybePrevent(e)\n    e.stopPropagation()\n\n    let delta = pointPos - this.lastPoint\n    this.lastPoint = pointPos\n    this.hooks.trigger(this.hooks.eventTypes.touchMove, delta)\n  }\n\n  private end(e: TouchEvent) {\n    if (!this.initiated) {\n      return\n    }\n    this.initiated = false\n\n    maybePrevent(e)\n    e.stopPropagation()\n\n    this.hooks.trigger(this.hooks.eventTypes.touchEnd)\n  }\n\n  destroy() {\n    this.startEventRegister.destroy()\n    this.moveEventRegister.destroy()\n    this.endEventRegister.destroy()\n  }\n}\n"
  },
  {
    "path": "packages/scroll-bar/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport Indicator, {\n  IndicatorOptions,\n  IndicatorDirection,\n  OffsetType,\n} from './indicator'\nimport { extend } from '@better-scroll/shared-utils'\n\nexport type ScrollbarOptions = Partial<ScrollbarConfig> | true\n\nexport interface ScrollbarConfig {\n  fade: boolean\n  fadeInTime: number\n  fadeOutTime: number\n  interactive: boolean\n  customElements: HTMLElement[]\n  minSize: number\n  scrollbarTrackClickable: boolean\n  scrollbarTrackOffsetType: OffsetType\n  scrollbarTrackOffsetTime: number\n}\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    scrollbar?: ScrollbarOptions\n  }\n}\n\nexport default class ScrollBar {\n  static pluginName = 'scrollbar'\n  options: ScrollbarConfig\n  indicators: Indicator[]\n\n  constructor(public scroll: BScroll) {\n    this.handleOptions()\n    this.createIndicators()\n    this.handleHooks()\n  }\n\n  private handleHooks() {\n    const scroll = this.scroll\n    scroll.hooks.on(scroll.hooks.eventTypes.destroy, () => {\n      for (let indicator of this.indicators) {\n        indicator.destroy()\n      }\n    })\n  }\n\n  private handleOptions() {\n    const userOptions = (this.scroll.options.scrollbar === true\n      ? {}\n      : this.scroll.options.scrollbar) as Partial<ScrollbarConfig>\n\n    const defaultOptions: ScrollbarConfig = {\n      fade: true,\n      fadeInTime: 250,\n      fadeOutTime: 500,\n      interactive: false,\n      customElements: [],\n      minSize: 8,\n      scrollbarTrackClickable: false,\n      scrollbarTrackOffsetType: OffsetType.Step,\n      scrollbarTrackOffsetTime: 300,\n    }\n    this.options = extend(defaultOptions, userOptions)\n  }\n\n  private createIndicators() {\n    let indicatorOptions: IndicatorOptions\n    const scroll: BScroll = this.scroll\n    const indicators: Indicator[] = []\n    const scrollDirectionConfigKeys = ['scrollX', 'scrollY']\n    const indicatorDirections = [\n      IndicatorDirection.Horizontal,\n      IndicatorDirection.Vertical,\n    ]\n    const customScrollbarEls = this.options.customElements\n    for (let i = 0; i < scrollDirectionConfigKeys.length; i++) {\n      const key = scrollDirectionConfigKeys[i]\n      // wanna scroll in specified direction\n      if (scroll.options[key]) {\n        const customElement = customScrollbarEls.shift()\n        const direction = indicatorDirections[i]\n        let isCustom = false\n        let scrollbarWrapper = customElement\n          ? customElement\n          : this.createScrollbarElement(direction)\n        // internal scrollbar\n        if (scrollbarWrapper !== customElement) {\n          scroll.wrapper.appendChild(scrollbarWrapper)\n        } else {\n          // custom scrollbar passed by users\n          isCustom = true\n        }\n        indicatorOptions = {\n          wrapper: scrollbarWrapper,\n          direction,\n          ...this.options,\n          isCustom,\n        }\n        indicators.push(new Indicator(scroll, indicatorOptions))\n      }\n    }\n    this.indicators = indicators\n  }\n\n  private createScrollbarElement(\n    direction: IndicatorDirection,\n    scrollbarTrackClickable = this.options.scrollbarTrackClickable\n  ) {\n    let scrollbarWrapperEl: HTMLDivElement = document.createElement('div')\n    let scrollbarIndicatorEl: HTMLDivElement = document.createElement('div')\n\n    scrollbarWrapperEl.style.cssText =\n      'position:absolute;z-index:9999;overflow:hidden;'\n    scrollbarIndicatorEl.style.cssText =\n      'box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px;'\n\n    scrollbarIndicatorEl.className = 'bscroll-indicator'\n    if (direction === IndicatorDirection.Horizontal) {\n      scrollbarWrapperEl.style.cssText +=\n        'height:7px;left:2px;right:2px;bottom:0;'\n      scrollbarIndicatorEl.style.height = '100%'\n      scrollbarWrapperEl.className = 'bscroll-horizontal-scrollbar'\n    } else {\n      scrollbarWrapperEl.style.cssText +=\n        'width:7px;bottom:2px;top:2px;right:1px;'\n      scrollbarIndicatorEl.style.width = '100%'\n      scrollbarWrapperEl.className = 'bscroll-vertical-scrollbar'\n    }\n\n    if (!scrollbarTrackClickable) {\n      scrollbarWrapperEl.style.cssText += 'pointer-events:none;'\n    }\n    scrollbarWrapperEl.appendChild(scrollbarIndicatorEl)\n    return scrollbarWrapperEl\n  }\n}\n"
  },
  {
    "path": "packages/scroll-bar/src/indicator.ts",
    "content": "import BScroll, { TranslaterPoint } from '@better-scroll/core'\nimport {\n  style,\n  EventEmitter,\n  between,\n  getNow,\n  Probe,\n  EventRegister,\n} from '@better-scroll/shared-utils'\nimport EventHandler from './event-handler'\n\nexport const enum IndicatorDirection {\n  Horizontal = 'horizontal',\n  Vertical = 'vertical',\n}\n\nconst enum ScrollTo {\n  Up = -1,\n  Down = 1,\n}\n\nexport const enum OffsetType {\n  Step = 'step',\n  Point = 'clickedPoint',\n}\n\nexport interface IndicatorOptions {\n  wrapper: HTMLElement\n  direction: IndicatorDirection\n  fade: boolean\n  fadeInTime: number\n  fadeOutTime: number\n  interactive: boolean\n  minSize: number\n  isCustom: boolean\n  scrollbarTrackClickable: boolean\n  scrollbarTrackOffsetType: OffsetType\n  scrollbarTrackOffsetTime: number\n}\n\ninterface KeysMap {\n  hasScroll: 'hasVerticalScroll' | 'hasHorizontalScroll'\n  size: 'height' | 'width'\n  wrapperSize: 'clientHeight' | 'clientWidth'\n  scrollerSize: 'scrollerHeight' | 'scrollerWidth'\n  maxScrollPos: 'maxScrollY' | 'maxScrollX'\n  pos: 'y' | 'x'\n  point: 'pageX' | 'pageY'\n  translateProperty: 'translateY' | 'translateX'\n  domRect: 'top' | 'left'\n}\n\ninterface ScrollInfo {\n  maxScrollPos: number\n  minScrollPos: number\n  sizeRatio: number\n  baseSize: number\n}\n\nexport default class Indicator {\n  wrapper: HTMLElement\n  wrapperRect: DOMRect\n  indicatorEl: HTMLElement\n  direction: IndicatorDirection\n  scrollInfo: ScrollInfo\n  currentPos: number\n  moved: boolean\n  startTime: number\n  keysMap: KeysMap\n  eventHandler: EventHandler\n  clickEventRegister: EventRegister\n  hooksFn: [EventEmitter, string, Function][] = []\n\n  constructor(public scroll: BScroll, public options: IndicatorOptions) {\n    this.wrapper = options.wrapper\n    this.direction = options.direction\n    this.indicatorEl = this.wrapper.children[0] as HTMLElement\n    this.keysMap = this.getKeysMap()\n\n    this.handleFade()\n\n    this.handleHooks()\n  }\n\n  private handleFade() {\n    if (this.options.fade) {\n      this.wrapper.style.opacity = '0'\n    }\n  }\n\n  private handleHooks() {\n    const { fade, interactive, scrollbarTrackClickable } = this.options\n    const scroll = this.scroll\n    const scrollHooks = scroll.hooks\n    const translaterHooks = scroll.scroller.translater.hooks\n    const animaterHooks = scroll.scroller.animater.hooks\n\n    this.registerHooks(\n      scrollHooks,\n      scrollHooks.eventTypes.refresh,\n      this.refresh\n    )\n\n    this.registerHooks(\n      translaterHooks,\n      translaterHooks.eventTypes.translate,\n      (pos: { x: number; y: number }) => {\n        const { hasScroll: hasScrollKey } = this.keysMap\n        if (this.scroll[hasScrollKey]) {\n          this.updatePosition(pos)\n        }\n      }\n    )\n\n    this.registerHooks(\n      animaterHooks,\n      animaterHooks.eventTypes.time,\n      this.transitionTime\n    )\n\n    this.registerHooks(\n      animaterHooks,\n      animaterHooks.eventTypes.timeFunction,\n      this.transitionTimingFunction\n    )\n\n    if (fade) {\n      this.registerHooks(scroll, scroll.eventTypes.scrollEnd, () => {\n        this.fade()\n      })\n\n      this.registerHooks(scroll, scroll.eventTypes.scrollStart, () => {\n        this.fade(true)\n      })\n\n      // for mousewheel event\n      if (\n        scroll.eventTypes.mousewheelStart &&\n        scroll.eventTypes.mousewheelEnd\n      ) {\n        this.registerHooks(scroll, scroll.eventTypes.mousewheelStart, () => {\n          this.fade(true)\n        })\n\n        this.registerHooks(scroll, scroll.eventTypes.mousewheelMove, () => {\n          this.fade(true)\n        })\n\n        this.registerHooks(scroll, scroll.eventTypes.mousewheelEnd, () => {\n          this.fade()\n        })\n      }\n    }\n\n    if (interactive) {\n      const { disableMouse, disableTouch } = this.scroll.options\n      this.eventHandler = new EventHandler(this, {\n        disableMouse,\n        disableTouch,\n      })\n      const eventHandlerHooks = this.eventHandler.hooks\n      this.registerHooks(\n        eventHandlerHooks,\n        eventHandlerHooks.eventTypes.touchStart,\n        this.startHandler\n      )\n      this.registerHooks(\n        eventHandlerHooks,\n        eventHandlerHooks.eventTypes.touchMove,\n        this.moveHandler\n      )\n      this.registerHooks(\n        eventHandlerHooks,\n        eventHandlerHooks.eventTypes.touchEnd,\n        this.endHandler\n      )\n    }\n\n    if (scrollbarTrackClickable) {\n      this.bindClick()\n    }\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  private bindClick() {\n    const wrapper = this.wrapper\n    this.clickEventRegister = new EventRegister(wrapper, [\n      {\n        name: 'click',\n        handler: this.handleClick.bind(this),\n      },\n    ])\n  }\n\n  private handleClick(e: MouseEvent) {\n    const newPos = this.calculateclickOffsetPos(e)\n    let { x, y } = this.scroll\n    x = this.direction === IndicatorDirection.Horizontal ? newPos : x\n    y = this.direction === IndicatorDirection.Vertical ? newPos : y\n    this.scroll.scrollTo(x, y, this.options.scrollbarTrackOffsetTime)\n  }\n\n  private calculateclickOffsetPos(e: MouseEvent) {\n    const { point: poinKey, domRect: domRectKey } = this.keysMap\n    const { scrollbarTrackOffsetType } = this.options\n    const clickPointOffset = e[poinKey] - this.wrapperRect[domRectKey]\n    const scrollToWhere =\n      clickPointOffset < this.currentPos ? ScrollTo.Up : ScrollTo.Down\n    let delta = 0\n    let currentPos = this.currentPos\n    if (scrollbarTrackOffsetType === OffsetType.Step) {\n      delta = this.scrollInfo.baseSize * scrollToWhere\n    } else {\n      delta = 0\n      currentPos = clickPointOffset\n    }\n    return this.newPos(currentPos, delta, this.scrollInfo)\n  }\n\n  getKeysMap(): KeysMap {\n    if (this.direction === IndicatorDirection.Vertical) {\n      return {\n        hasScroll: 'hasVerticalScroll',\n        size: 'height',\n        wrapperSize: 'clientHeight',\n        scrollerSize: 'scrollerHeight',\n        maxScrollPos: 'maxScrollY',\n        pos: 'y',\n        point: 'pageY',\n        translateProperty: 'translateY',\n        domRect: 'top',\n      }\n    }\n    return {\n      hasScroll: 'hasHorizontalScroll',\n      size: 'width',\n      wrapperSize: 'clientWidth',\n      scrollerSize: 'scrollerWidth',\n      maxScrollPos: 'maxScrollX',\n      pos: 'x',\n      point: 'pageX',\n      translateProperty: 'translateX',\n      domRect: 'left',\n    }\n  }\n\n  fade(visible?: boolean) {\n    const { fadeInTime, fadeOutTime } = this.options\n    const time = visible ? fadeInTime : fadeOutTime\n    const wrapper = this.wrapper\n    wrapper.style[style.transitionDuration as any] = time + 'ms'\n    wrapper.style.opacity = visible ? '1' : '0'\n  }\n\n  refresh() {\n    const { hasScroll: hasScrollKey } = this.keysMap\n    const scroll = this.scroll\n    const { x, y } = scroll\n    this.wrapperRect = this.wrapper.getBoundingClientRect()\n    if (this.canScroll(scroll[hasScrollKey])) {\n      let {\n        wrapperSize: wrapperSizeKey,\n        scrollerSize: scrollerSizeKey,\n        maxScrollPos: maxScrollPosKey,\n      } = this.keysMap\n\n      this.scrollInfo = this.refreshScrollInfo(\n        this.wrapper[wrapperSizeKey],\n        scroll[scrollerSizeKey],\n        scroll[maxScrollPosKey],\n        this.indicatorEl[wrapperSizeKey]\n      )\n\n      this.updatePosition({\n        x,\n        y,\n      })\n    }\n  }\n\n  transitionTime(time: number = 0) {\n    this.indicatorEl.style[style.transitionDuration as any] = time + 'ms'\n  }\n\n  transitionTimingFunction(easing: string) {\n    this.indicatorEl.style[style.transitionTimingFunction as any] = easing\n  }\n\n  private canScroll(hasScroll: boolean): boolean {\n    this.wrapper.style.display = hasScroll ? 'block' : 'none'\n    return hasScroll\n  }\n\n  private refreshScrollInfo(\n    wrapperSize: number,\n    scrollerSize: number,\n    maxScrollPos: number,\n    indicatorElSize: number\n  ): ScrollInfo {\n    let baseSize = Math.max(\n      Math.round(\n        (wrapperSize * wrapperSize) / (scrollerSize || wrapperSize || 1)\n      ),\n      this.options.minSize\n    )\n\n    if (this.options.isCustom) {\n      baseSize = indicatorElSize\n    }\n\n    const maxIndicatorScrollPos = wrapperSize - baseSize\n    // sizeRatio is negative\n    let sizeRatio = maxIndicatorScrollPos / maxScrollPos\n\n    return {\n      baseSize,\n      maxScrollPos: maxIndicatorScrollPos,\n      minScrollPos: 0,\n      sizeRatio,\n    }\n  }\n\n  updatePosition(point: TranslaterPoint) {\n    const { pos, size } = this.caculatePosAndSize(point, this.scrollInfo)\n    this.refreshStyle(size, pos)\n    this.currentPos = pos\n  }\n\n  private caculatePosAndSize(\n    point: TranslaterPoint,\n    scrollInfo: ScrollInfo\n  ): { pos: number; size: number } {\n    const { pos: posKey } = this.keysMap\n    const { sizeRatio, baseSize, maxScrollPos, minScrollPos } = scrollInfo\n    const minSize = this.options.minSize\n\n    let pos = Math.round(sizeRatio * point[posKey])\n    let size\n    // when out of boundary, slow down size reduction\n    if (pos < minScrollPos) {\n      size = Math.max(baseSize + pos * 3, minSize)\n      pos = minScrollPos\n    } else if (pos > maxScrollPos) {\n      size = Math.max(baseSize - (pos - maxScrollPos) * 3, minSize)\n      pos = maxScrollPos + baseSize - size\n    } else {\n      size = baseSize\n    }\n\n    return {\n      pos,\n      size,\n    }\n  }\n\n  private refreshStyle(size: number, pos: number) {\n    const {\n      translateProperty: translatePropertyKey,\n      size: sizeKey,\n    } = this.keysMap\n    const translateZ = this.scroll.options.translateZ\n\n    this.indicatorEl.style[sizeKey] = `${size}px`\n\n    this.indicatorEl.style[\n      style.transform as any\n    ] = `${translatePropertyKey}(${pos}px)${translateZ}`\n  }\n\n  startHandler() {\n    this.moved = false\n    this.startTime = getNow()\n    this.transitionTime()\n    this.scroll.scroller.hooks.trigger(\n      this.scroll.scroller.hooks.eventTypes.beforeScrollStart\n    )\n  }\n\n  moveHandler(delta: number) {\n    if (!this.moved && !this.indicatorNotMoved(delta)) {\n      this.moved = true\n      this.scroll.scroller.hooks.trigger(\n        this.scroll.scroller.hooks.eventTypes.scrollStart\n      )\n    }\n    if (this.moved) {\n      const newPos = this.newPos(this.currentPos, delta, this.scrollInfo)\n      this.syncBScroll(newPos)\n    }\n  }\n\n  endHandler() {\n    if (this.moved) {\n      const { x, y } = this.scroll\n      this.scroll.scroller.hooks.trigger(\n        this.scroll.scroller.hooks.eventTypes.scrollEnd,\n        {\n          x,\n          y,\n        }\n      )\n    }\n  }\n\n  private indicatorNotMoved(delta: number): boolean {\n    const currentPos = this.currentPos\n    const { maxScrollPos, minScrollPos } = this.scrollInfo\n    const notMoved =\n      (currentPos === minScrollPos && delta <= 0) ||\n      (currentPos === maxScrollPos && delta >= 0)\n    return notMoved\n  }\n\n  private syncBScroll(newPos: number) {\n    const timestamp = getNow()\n    const {\n      x,\n      y,\n      options,\n      scroller,\n      maxScrollY,\n      minScrollY,\n      maxScrollX,\n      minScrollX,\n    } = this.scroll\n    const { probeType, momentumLimitTime } = options\n    const position = { x, y }\n    if (this.direction === IndicatorDirection.Vertical) {\n      position.y = between(newPos, maxScrollY, minScrollY)\n    } else {\n      position.x = between(newPos, maxScrollX, minScrollX)\n    }\n    scroller.translater.translate(position)\n\n    // dispatch scroll in interval time\n    if (timestamp - this.startTime > momentumLimitTime) {\n      this.startTime = timestamp\n      if (probeType === Probe.Throttle) {\n        scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, position)\n      }\n    }\n\n    // dispatch scroll all the time\n    if (probeType > Probe.Throttle) {\n      scroller.hooks.trigger(scroller.hooks.eventTypes.scroll, position)\n    }\n  }\n\n  private newPos(\n    currentPos: number,\n    delta: number,\n    scrollInfo: ScrollInfo\n  ): number {\n    const { maxScrollPos, sizeRatio, minScrollPos } = scrollInfo\n    let newPos = currentPos + delta\n\n    newPos = between(newPos, minScrollPos, maxScrollPos)\n\n    return Math.round(newPos / sizeRatio)\n  }\n\n  destroy() {\n    const { interactive, scrollbarTrackClickable, isCustom } = this.options\n    if (interactive) {\n      this.eventHandler.destroy()\n    }\n    if (scrollbarTrackClickable) {\n      this.clickEventRegister.destroy()\n    }\n    if (!isCustom) {\n      this.wrapper.parentNode!.removeChild(this.wrapper)\n    }\n\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n    this.hooksFn.length = 0\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/README.md",
    "content": "# @better-scroll/shared-utils\n\nshared-utils for BetterScroll.\n"
  },
  {
    "path": "packages/shared-utils/package.json",
    "content": "{\n\t\"name\": \"@better-scroll/shared-utils\",\n\t\"version\": \"2.5.1\",\n\t\"description\": \"shared-utils for BetterScroll\",\n\t\"author\": {\n\t\t\"name\": \"jizhi\",\n\t\t\"email\": \"theniceangel@163.com\"\n\t},\n\t\"main\": \"dist/shared-utils.min.js\",\n\t\"module\": \"dist/shared-utils.esm.js\",\n\t\"typings\": \"dist/types/index.d.ts\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n\t\"keywords\": [\n\t\t\"scroll\",\n\t\t\"iscroll\",\n\t\t\"javascript\",\n\t\t\"typescript\",\n\t\t\"ios\"\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n\t\t\"directory\": \"packages/shared-utils\"\n\t},\n\t\"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/shared-utils/src/Touch.ts",
    "content": "interface TouchList {\n  length: number\n  [index: number]: Touch\n  item: (index: number) => Touch\n}\n\ninterface Touch {\n  identifier: number\n  target: EventTarget\n  screenX: number\n  screenY: number\n  clientX: number\n  clientY: number\n  pageX: number\n  pageY: number\n}\n\nexport interface TouchEvent extends UIEvent {\n  touches: TouchList\n  targetTouches: TouchList\n  changedTouches: TouchList\n  altKey: boolean\n  metaKey: boolean\n  ctrlKey: boolean\n  shiftKey: boolean\n  rotation: number\n  scale: number\n  button: number\n  _constructed?: boolean\n}\n"
  },
  {
    "path": "packages/shared-utils/src/__mocks__/dom.ts",
    "content": "export const style = {\n  transform: 'transform',\n  transition: 'transition',\n  transitionTimingFunction: 'transitionTimingFunction',\n  transitionDuration: 'transitionDuration',\n  transitionDelay: 'transitionDelay',\n  transformOrigin: 'transformOrigin',\n  transitionEnd: 'transitionEnd'\n}\n"
  },
  {
    "path": "packages/shared-utils/src/__mocks__/ease.ts",
    "content": "export const ease = {\n  // easeOutQuint\n  swipe: {\n    style: 'cubic-bezier(0.23, 1, 0.32, 1)',\n    fn: () => {}\n  },\n  // easeOutQuard\n  swipeBounce: {\n    style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',\n    fn: () => {}\n  },\n  // easeOutQuart\n  bounce: {\n    style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',\n    fn: () => {}\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/debug.spec.ts",
    "content": "import { warn, assert } from '../debug'\n\ndescribe('debug', () => {\n  it('should work well when call warn()', () => {\n    const spyFn = jest.spyOn(console, 'error')\n    warn('Error occured')\n\n    expect(spyFn).toBeCalledWith('[BScroll warn]: Error occured')\n  })\n\n  it('should work well when call assert()', () => {\n    const a = 1 + Math.random()\n    const b = 2\n    expect(() => {\n      assert(a > b, '')\n    }).toThrow()\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/dom.spec.ts",
    "content": "import {\n  prepend,\n  removeChild,\n  addClass,\n  removeClass,\n  tap,\n  dblclick,\n  click,\n} from '../dom'\n\ndescribe('dom', () => {\n  it('prepend', () => {\n    // append operation\n    const target1 = document.createElement('div')\n    const el1 = document.createElement('p')\n    prepend(el1, target1)\n\n    expect(target1.children[0]).toBe(el1)\n\n    // prepend operation\n    const target2 = document.createElement('div')\n    const child = document.createElement('div')\n    target2.appendChild(child)\n    const el2 = document.createElement('p')\n    prepend(el2, target2)\n\n    expect(target2.children[0]).toBe(el2)\n    expect(target2.children[1]).toBe(child)\n  })\n\n  it('removeChild', () => {\n    // append operation\n    const target = document.createElement('div')\n    const el = document.createElement('p')\n    prepend(el, target)\n\n    expect(target.children[0]).toBe(el)\n\n    removeChild(target, el)\n    expect(target.children.length).toBe(0)\n  })\n\n  it('addClass & removeClass', () => {\n    const target = document.createElement('div')\n    addClass(target, 'test')\n    expect(target.className).toBe(' test')\n\n    // same classname\n    addClass(target, 'test')\n\n    addClass(target, 'test2')\n    expect(target.className).toBe(' test test2')\n\n    // exclude unexisted classname\n    removeClass(target, 'biz')\n    expect(target.className).toBe(' test test2')\n\n    removeClass(target, 'test test2')\n    expect(target.className).toBe(' ')\n  })\n\n  it('tap & dblclick', () => {\n    const mockFn1 = jest.fn()\n    const mockFn2 = jest.fn()\n    const target = document.createElement('div')\n    document.body.appendChild(target)\n\n    let e = { target } as any\n    window.addEventListener('tap', mockFn1)\n    window.addEventListener('dblclick', mockFn2)\n    tap(e, 'tap')\n    expect(mockFn1).toBeCalled()\n\n    dblclick(e)\n    expect(mockFn2).toBeCalled()\n  })\n\n  it('click ', () => {\n    const mockFn1 = jest.fn()\n    const target = document.createElement('div')\n    document.body.appendChild(target)\n\n    let e = { target, type: 'mouseup' } as any\n    window.addEventListener('click', mockFn1)\n    click(e)\n\n    expect(mockFn1).toBeCalled()\n\n    // fallback to createEvent\n    Object.defineProperty(window, 'MouseEvent', {\n      get() {\n        return undefined\n      },\n    })\n    click(Object.assign(e, { type: 'touchend', changedTouches: [{}] }))\n    expect(mockFn1).toBeCalledTimes(2)\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/ease.spec.ts",
    "content": "import { ease } from '../ease'\n\ndescribe('ease', () => {\n  it('swipe fn', () => {\n    const handler = ease.swipe.fn\n    const time1 = handler(0.2)\n    const time2 = handler(0.4)\n    const time3 = handler(0.5)\n    const time4 = handler(0.6)\n    const time5 = handler(0.8)\n    const time6 = handler(1)\n\n    expect(time1).toBeCloseTo(0.6723)\n    expect(time2).toBeCloseTo(0.92224)\n    expect(time3).toBeCloseTo(0.96875)\n    expect(time4).toBeCloseTo(0.98976)\n    expect(time5).toBeCloseTo(0.99968)\n    expect(time6).toBeCloseTo(1)\n  })\n\n  it('swipeBounce fn', () => {\n    const handler = ease.swipeBounce.fn\n    const time1 = handler(0.2)\n    const time2 = handler(0.4)\n    const time3 = handler(0.5)\n    const time4 = handler(0.6)\n    const time5 = handler(0.8)\n    const time6 = handler(1)\n\n    expect(time1).toBeCloseTo(0.36)\n    expect(time2).toBeCloseTo(0.64)\n    expect(time3).toBeCloseTo(0.75)\n    expect(time4).toBeCloseTo(0.84)\n    expect(time5).toBeCloseTo(0.96)\n    expect(time6).toBeCloseTo(1)\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/events.spec.ts",
    "content": "import { EventEmitter, EventRegister } from '../events'\n\ndescribe('events', () => {\n  describe('EventEmitter', () => {\n    let eventEmitter: EventEmitter\n\n    beforeEach(() => {\n      eventEmitter = new EventEmitter(['test1'])\n    })\n    afterEach(() => {\n      jest.clearAllMocks()\n    })\n\n    it('should register handler successfully', () => {\n      eventEmitter.on('test1', () => {})\n\n      expect(eventEmitter.eventTypes.test1).toBeTruthy()\n      expect(eventEmitter.events.test1).not.toBeUndefined()\n    })\n\n    it('should trigger handler', () => {\n      let mockHandler = jest.fn((x) => x + 1)\n      eventEmitter.on('test1', mockHandler)\n      eventEmitter.trigger('test1', 1)\n\n      expect(mockHandler.mock.calls.length).toBe(1)\n      expect(mockHandler.mock.calls[0][0]).toBe(1)\n      expect(mockHandler.mock.results[0].value).toBe(2)\n    })\n\n    it('should trigger handler only once', () => {\n      let mockHandler = jest.fn((x) => x + 1)\n      eventEmitter.once('test1', mockHandler)\n      eventEmitter.trigger('test1', 1)\n      eventEmitter.trigger('test1', 1)\n\n      expect(mockHandler.mock.calls.length).toBe(1)\n    })\n\n    it('should tear down handler when invoking off()', () => {\n      let mockHandler = jest.fn((x) => x + 1)\n      eventEmitter.once('test1', mockHandler)\n      eventEmitter.off('test1', mockHandler)\n\n      expect(eventEmitter.events.test1.length).toBe(0)\n    })\n\n    it('should register eventTypes when invoking registerType()', () => {\n      eventEmitter.registerType(['test2'])\n\n      expect(eventEmitter.eventTypes.test2).toBe('test2')\n    })\n\n    it('should warn about unregistered event when invoking off()', () => {\n      const spyFn = jest.spyOn(console, 'error')\n      eventEmitter.off('test2')\n\n      expect(spyFn).toBeCalled()\n    })\n\n    it('should keep chainable call when invoking off()', () => {\n      const ret = eventEmitter.off('test1', () => {})\n      const ret2 = eventEmitter.off()\n\n      expect(ret).toBe(eventEmitter)\n      expect(ret2).toBe(eventEmitter)\n    })\n\n    it('should support cancelable callback', () => {\n      const mockHandler1 = jest.fn().mockImplementation(() => true)\n      const mockHandler2 = jest.fn()\n      eventEmitter.on('test1', mockHandler1)\n      eventEmitter.on('test1', mockHandler2)\n\n      const ret = eventEmitter.trigger('test1')\n      expect(mockHandler1).toBeCalled()\n      expect(mockHandler2).not.toBeCalled()\n      expect(ret).toBe(true)\n    })\n\n    it('should support cancelable once callback', () => {\n      const mockHandler1 = jest.fn()\n      const mockHandler2 = jest.fn().mockImplementation(() => true)\n      const mockHandler3 = jest.fn()\n\n      eventEmitter.on('test1', mockHandler1)\n      eventEmitter.once('test1', mockHandler2)\n      eventEmitter.on('test1', mockHandler3)\n\n      const ret = eventEmitter.trigger('test1')\n\n      expect(mockHandler1).toBeCalled()\n      expect(mockHandler2).toBeCalled()\n      expect(mockHandler3).not.toBeCalled()\n      expect(ret).toBe(true)\n    })\n  })\n\n  describe('EventRegister', () => {\n    let eventRegister: EventRegister\n    let fakeNode: HTMLElement\n    let mockHandler: any\n\n    beforeEach(() => {\n      fakeNode = document.createElement('div')\n      mockHandler = jest.fn(() => 'it is a test handler ')\n      eventRegister = new EventRegister(fakeNode, [\n        {\n          name: 'test',\n          handler: mockHandler as any,\n        },\n      ])\n    })\n    afterEach(() => {\n      jest.clearAllMocks()\n    })\n\n    it('should trigger handler when dispatch touch event', () => {\n      const evt = document.createEvent('Event')\n      const evtType = 'test'\n      evt.initEvent(evtType, false, false)\n\n      fakeNode.dispatchEvent(evt)\n      expect(mockHandler.mock.calls.length).toBe(1)\n    })\n\n    it('should remove dom events when destroy', () => {\n      eventRegister.destroy()\n      const evt = document.createEvent('Event')\n      const evtType = 'test'\n      evt.initEvent(evtType, false, false)\n\n      fakeNode.dispatchEvent(evt)\n      expect(mockHandler.mock.calls.length).toBe(0)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/lang.spec.ts",
    "content": "import { findIndex } from '../lang'\n\ndescribe('lang', () => {\n  it('findIndex', () => {\n    // hide ES6 findIndex\n    // @ts-ignore\n    Array.prototype.findIndex = undefined\n    const array = [1, 2]\n    const ret = findIndex(array, (item) => item % 2 === 0)\n    expect(ret).toBe(1)\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/propertiesProxy.spec.ts",
    "content": "import { propertiesProxy } from '../propertiesProxy'\n\ndescribe('propertiesProxy', () => {\n  it('should proxy property correctly', () => {\n    let obj = {\n      a: {\n        b: {\n          c: 1,\n        },\n      },\n    } as any\n    propertiesProxy(obj, 'a.b.c', 'c')\n    expect(obj.c).toBe(1)\n    obj.c = 3\n    expect(obj.a.b.c).toBe(3)\n  })\n\n  it('should prevent error when string path is wrong', () => {\n    let obj = {\n      a: {\n        b: {\n          d: 1,\n        },\n      },\n    } as any\n    propertiesProxy(obj, 'a.c.d', 'c')\n    expect(obj.c).toBeFalsy()\n    obj.c = 2\n    expect(obj.a.c.d).toBe(2)\n  })\n\n  it('should change context when proxying method', () => {\n    let obj = {\n      a: {\n        b: {\n          c() {\n            return (this as any).d\n          },\n          d: 1,\n        },\n      },\n    } as any\n    propertiesProxy(obj, 'a.b.c', 'c')\n    expect(obj.c()).toBe(1)\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/__tests__/raf.spec.ts",
    "content": "import { requestAnimationFrame, cancelAnimationFrame } from '../raf'\n\njest.mock('../env', () => {\n  const windowCompat = window as any\n  windowCompat.requestAnimationFrame = null\n  windowCompat.webkitRequestAnimationFrame = null\n  windowCompat.mozRequestAnimationFrame = null\n  windowCompat.oRequestAnimationFrame = null\n\n  windowCompat.cancelAnimationFrame = null\n  windowCompat.webkitCancelAnimationFrame = null\n  windowCompat.mozCancelAnimationFrame = null\n  windowCompat.oCancelAnimationFrame = null\n  return {\n    inBrowser: true,\n  }\n})\ndescribe('raf', () => {\n  it('should fallback setTimeout or clearTimeout', () => {\n    jest.useFakeTimers()\n    const spySetTimeout = jest.spyOn(window, 'setTimeout')\n    const spyClearTimeout = jest.spyOn(window, 'clearTimeout')\n    const mockFn = jest.fn()\n\n    const timer = requestAnimationFrame(mockFn)\n    jest.advanceTimersByTime(17)\n\n    expect(mockFn).toBeCalled()\n    expect(spySetTimeout).toBeCalled()\n    cancelAnimationFrame(timer)\n    expect(spyClearTimeout).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "packages/shared-utils/src/debug.ts",
    "content": "export function warn(msg: string) {\n  console.error(`[BScroll warn]: ${msg}`)\n}\n\nexport function assert(condition: string | boolean, msg: string) {\n  if (!condition) {\n    throw new Error('[BScroll] ' + msg)\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/dom.ts",
    "content": "import { inBrowser, isWeChatDevTools, supportsPassive } from './env'\nimport { extend } from './lang'\n\nexport type safeCSSStyleDeclaration = {\n  [key: string]: string\n} & CSSStyleDeclaration\nexport interface DOMRect {\n  left: number\n  top: number\n  width: number\n  height: number\n  [key: string]: number\n}\n\nlet elementStyle = (inBrowser &&\n  document.createElement('div').style) as safeCSSStyleDeclaration\n\nlet vendor = (() => {\n  /* istanbul ignore if  */\n  if (!inBrowser) {\n    return false\n  }\n  const transformNames = [\n    {\n      key: 'standard',\n      value: 'transform',\n    },\n    {\n      key: 'webkit',\n      value: 'webkitTransform',\n    },\n    {\n      key: 'Moz',\n      value: 'MozTransform',\n    },\n    {\n      key: 'O',\n      value: 'OTransform',\n    },\n    {\n      key: 'ms',\n      value: 'msTransform',\n    },\n  ]\n  for (let obj of transformNames) {\n    if (elementStyle[obj.value] !== undefined) {\n      return obj.key\n    }\n  }\n  /* istanbul ignore next  */\n  return false\n})()\n\n/* istanbul ignore next  */\nfunction prefixStyle(style: string): string {\n  if (vendor === false) {\n    return style\n  }\n\n  if (vendor === 'standard') {\n    if (style === 'transitionEnd') {\n      return 'transitionend'\n    }\n    return style\n  }\n\n  return vendor + style.charAt(0).toUpperCase() + style.substr(1)\n}\n\nexport function getElement(el: HTMLElement | string) {\n  return (\n    typeof el === 'string' ? document.querySelector(el) : el\n  ) as HTMLElement\n}\n\nexport function addEvent(\n  el: HTMLElement,\n  type: string,\n  fn: EventListenerOrEventListenerObject,\n  capture?: AddEventListenerOptions\n) {\n  const useCapture = supportsPassive\n    ? {\n        passive: false,\n        capture: !!capture,\n      }\n    : !!capture\n  el.addEventListener(type, fn, useCapture)\n}\n\nexport function removeEvent(\n  el: HTMLElement,\n  type: string,\n  fn: EventListenerOrEventListenerObject,\n  capture?: EventListenerOptions\n) {\n  el.removeEventListener(type, fn, {\n    capture: !!capture,\n  })\n}\n\nexport function maybePrevent(e: Event) {\n  if (e.cancelable) {\n    e.preventDefault()\n  }\n}\n\nexport function offset(el: HTMLElement | null) {\n  let left = 0\n  let top = 0\n\n  while (el) {\n    left -= el.offsetLeft\n    top -= el.offsetTop\n    el = el.offsetParent as HTMLElement\n  }\n\n  return {\n    left,\n    top,\n  }\n}\n\nexport function offsetToBody(el: HTMLElement) {\n  let rect = el.getBoundingClientRect()\n\n  return {\n    left: -(rect.left + window.pageXOffset),\n    top: -(rect.top + window.pageYOffset),\n  }\n}\n\nexport const cssVendor =\n  vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : ''\n\nlet transform = prefixStyle('transform')\nlet transition = prefixStyle('transition')\n\nexport const hasPerspective =\n  inBrowser && prefixStyle('perspective') in elementStyle\n// fix issue #361\nexport const hasTouch =\n  inBrowser && ('ontouchstart' in window || isWeChatDevTools)\nexport const hasTransition = inBrowser && transition in elementStyle\n\nexport const style = {\n  transform,\n  transition,\n  transitionTimingFunction: prefixStyle('transitionTimingFunction'),\n  transitionDuration: prefixStyle('transitionDuration'),\n  transitionDelay: prefixStyle('transitionDelay'),\n  transformOrigin: prefixStyle('transformOrigin'),\n  transitionEnd: prefixStyle('transitionEnd'),\n  transitionProperty: prefixStyle('transitionProperty'),\n}\n\nexport const eventTypeMap: {\n  [key: string]: number\n  touchstart: number\n  touchmove: number\n  touchend: number\n  touchcancel: number\n  mousedown: number\n  mousemove: number\n  mouseup: number\n} = {\n  touchstart: 1,\n  touchmove: 1,\n  touchend: 1,\n  touchcancel: 1,\n\n  mousedown: 2,\n  mousemove: 2,\n  mouseup: 2,\n}\n\nexport function getRect(el: HTMLElement): DOMRect {\n  /* istanbul ignore if  */\n  if (el instanceof (window as any).SVGElement) {\n    let rect = el.getBoundingClientRect()\n    return {\n      top: rect.top,\n      left: rect.left,\n      width: rect.width,\n      height: rect.height,\n    }\n  } else {\n    return {\n      top: el.offsetTop,\n      left: el.offsetLeft,\n      width: el.offsetWidth,\n      height: el.offsetHeight,\n    }\n  }\n}\n\nexport function preventDefaultExceptionFn(\n  el: any,\n  exceptions: {\n    tagName?: RegExp\n    className?: RegExp\n    [key: string]: any\n  }\n) {\n  for (let i in exceptions) {\n    if (exceptions[i].test(el[i])) {\n      return true\n    }\n  }\n  return false\n}\n\nexport const tagExceptionFn = preventDefaultExceptionFn\n\nexport function tap(e: any, eventName: string) {\n  let ev = document.createEvent('Event') as any\n  ev.initEvent(eventName, true, true)\n  ev.pageX = e.pageX\n  ev.pageY = e.pageY\n  e.target.dispatchEvent(ev)\n}\n\nexport function click(e: any, event = 'click') {\n  let eventSource\n  if (e.type === 'mouseup') {\n    eventSource = e\n  } else if (e.type === 'touchend' || e.type === 'touchcancel') {\n    eventSource = e.changedTouches[0]\n  }\n  let posSrc: {\n    screenX?: number\n    screenY?: number\n    clientX?: number\n    clientY?: number\n  } = {}\n  if (eventSource) {\n    posSrc.screenX = eventSource.screenX || 0\n    posSrc.screenY = eventSource.screenY || 0\n    posSrc.clientX = eventSource.clientX || 0\n    posSrc.clientY = eventSource.clientY || 0\n  }\n  let ev: any\n  const bubbles = true\n  const cancelable = true\n  const { ctrlKey, shiftKey, altKey, metaKey } = e\n  const pressedKeysMap = {\n    ctrlKey,\n    shiftKey,\n    altKey,\n    metaKey,\n  }\n  if (typeof MouseEvent !== 'undefined') {\n    try {\n      ev = new MouseEvent(\n        event,\n        extend(\n          {\n            bubbles,\n            cancelable,\n            ...pressedKeysMap,\n          },\n          posSrc\n        )\n      )\n    } catch (e) {\n      /* istanbul ignore next */\n      createEvent()\n    }\n  } else {\n    createEvent()\n  }\n\n  function createEvent() {\n    ev = document.createEvent('Event')\n    ev.initEvent(event, bubbles, cancelable)\n    extend(ev, posSrc)\n  }\n\n  // forwardedTouchEvent set to true in case of the conflict with fastclick\n  ev.forwardedTouchEvent = true\n  ev._constructed = true\n  e.target.dispatchEvent(ev)\n}\n\nexport function dblclick(e: Event) {\n  click(e, 'dblclick')\n}\n\nexport function prepend(el: HTMLElement, target: HTMLElement) {\n  const firstChild = target.firstChild as HTMLElement\n  if (firstChild) {\n    before(el, firstChild)\n  } else {\n    target.appendChild(el)\n  }\n}\n\nexport function before(el: HTMLElement, target: HTMLElement) {\n  const parentNode = target.parentNode as HTMLElement\n  parentNode.insertBefore(el, target)\n}\n\nexport function removeChild(el: HTMLElement, child: HTMLElement) {\n  el.removeChild(child)\n}\n\nexport function hasClass(el: HTMLElement, className: string) {\n  let reg = new RegExp('(^|\\\\s)' + className + '(\\\\s|$)')\n  return reg.test(el.className)\n}\n\nexport function addClass(el: HTMLElement, className: string) {\n  if (hasClass(el, className)) {\n    return\n  }\n\n  let newClass = el.className.split(' ')\n  newClass.push(className)\n  el.className = newClass.join(' ')\n}\n\nexport function removeClass(el: HTMLElement, className: string) {\n  if (!hasClass(el, className)) {\n    return\n  }\n\n  let reg = new RegExp('(^|\\\\s)' + className + '(\\\\s|$)', 'g')\n  el.className = el.className.replace(reg, ' ')\n}\n\nexport function HTMLCollectionToArray(el: HTMLCollection) {\n  return Array.prototype.slice.call(el, 0)\n}\n\nexport function getClientSize(el: HTMLElement) {\n  return {\n    width: el.clientWidth,\n    height: el.clientHeight,\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/ease.ts",
    "content": "export interface EaseItem {\n  style: string\n  fn: EaseFn\n}\ninterface EaseMap {\n  [key: string]: EaseItem\n}\n\nexport interface EaseFn {\n  (t: number): number\n}\n\nexport const ease: EaseMap = {\n  // easeOutQuint\n  swipe: {\n    style: 'cubic-bezier(0.23, 1, 0.32, 1)',\n    fn: function(t: number) {\n      return 1 + --t * t * t * t * t\n    }\n  },\n  // easeOutQuard\n  swipeBounce: {\n    style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',\n    fn: function(t: number) {\n      return t * (2 - t)\n    }\n  },\n  // easeOutQuart\n  bounce: {\n    style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',\n    fn: function(t: number) {\n      return 1 - --t * t * t * t\n    }\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/enums.ts",
    "content": "export const enum DirectionLock {\n  Default = '',\n  Horizontal = 'horizontal',\n  Vertical = 'vertical',\n  None = 'none',\n}\n\nexport const enum Direction {\n  // fingers move from bottom to top or right to left\n  Positive = 1,\n  // on the contrary as above\n  Negative = -1,\n  Default = 0,\n}\n\nexport const enum ApplyOrder {\n  Pre = 'pre',\n  Post = 'post',\n}\n\nexport const enum EventPassthrough {\n  None = '',\n  Horizontal = 'horizontal',\n  Vertical = 'vertical',\n}\n\nexport const enum EventType {\n  Touch = 1,\n  Mouse = 2,\n}\n\nexport const enum MouseButton {\n  Left,\n  Middle,\n  Right,\n}\n\nexport const enum Probe {\n  Default,\n  Throttle,\n  Normal,\n  Realtime,\n}\n\nexport const enum Quadrant {\n  First = 1,\n  Second,\n  Third,\n  Forth,\n}\n"
  },
  {
    "path": "packages/shared-utils/src/env.ts",
    "content": "// ssr support\nexport const inBrowser = typeof window !== 'undefined'\nexport const ua = inBrowser && navigator.userAgent.toLowerCase()\nexport const isWeChatDevTools = !!(ua && /wechatdevtools/.test(ua))\nexport const isAndroid = ua && ua.indexOf('android') > 0\n\n/* istanbul ignore next */\nexport const isIOSBadVersion: boolean = (() => {\n  if (typeof ua === 'string') {\n    const regex = /os (\\d\\d?_\\d(_\\d)?)/\n    const matches = regex.exec(ua)\n    if (!matches) return false\n    const parts = matches[1].split('_').map(function (item) {\n      return parseInt(item, 10)\n    })\n    // ios version >= 13.4 issue 982\n    return !!(parts[0] === 13 && parts[1] >= 4)\n  }\n  return false\n})()\n\n/* istanbul ignore next */\nexport let supportsPassive = false\n/* istanbul ignore next */\nif (inBrowser) {\n  const EventName = 'test-passive' as any\n  try {\n    const opts = {}\n    Object.defineProperty(opts, 'passive', {\n      get() {\n        supportsPassive = true\n      },\n    }) // https://github.com/facebook/flow/issues/285\n    window.addEventListener(EventName, () => {}, opts)\n  } catch (e) {}\n}\n"
  },
  {
    "path": "packages/shared-utils/src/events.ts",
    "content": "import { warn } from './debug'\nimport { addEvent, removeEvent } from './dom'\n\ninterface Events {\n  [name: string]: [WithFnFunction, Object][]\n}\n\ninterface EventTypes {\n  [type: string]: string\n}\n\ninterface WithFnFunction extends Function {\n  fn?: Function\n}\n\nexport class EventEmitter {\n  events: Events\n  eventTypes: EventTypes\n  constructor(names: string[]) {\n    this.events = {}\n    this.eventTypes = {}\n    this.registerType(names)\n  }\n\n  on(type: string, fn: Function, context: Object = this) {\n    this.hasType(type)\n    if (!this.events[type]) {\n      this.events[type] = []\n    }\n\n    this.events[type].push([fn, context])\n    return this\n  }\n\n  once(type: string, fn: Function, context: Object = this) {\n    this.hasType(type)\n    const magic = (...args: any[]) => {\n      this.off(type, magic)\n      const ret = fn.apply(context, args)\n      if (ret === true) {\n        return ret\n      }\n    }\n    magic.fn = fn\n\n    this.on(type, magic)\n    return this\n  }\n\n  off(type?: string, fn?: Function) {\n    if (!type && !fn) {\n      this.events = {}\n      return this\n    }\n\n    if (type) {\n      this.hasType(type)\n      if (!fn) {\n        this.events[type] = []\n        return this\n      }\n\n      let events = this.events[type]\n      if (!events) {\n        return this\n      }\n\n      let count = events.length\n      while (count--) {\n        if (\n          events[count][0] === fn ||\n          (events[count][0] && events[count][0].fn === fn)\n        ) {\n          events.splice(count, 1)\n        }\n      }\n\n      return this\n    }\n  }\n\n  trigger(type: string, ...args: any[]) {\n    this.hasType(type)\n    let events = this.events[type]\n    if (!events) {\n      return\n    }\n\n    let len = events.length\n    let eventsCopy = [...events]\n    let ret\n    for (let i = 0; i < len; i++) {\n      let event = eventsCopy[i]\n      let [fn, context] = event\n      if (fn) {\n        ret = fn.apply(context, args)\n        if (ret === true) {\n          return ret\n        }\n      }\n    }\n  }\n  registerType(names: string[]) {\n    names.forEach((type: string) => {\n      this.eventTypes[type] = type\n    })\n  }\n\n  destroy() {\n    this.events = {}\n    this.eventTypes = {}\n  }\n\n  private hasType(type: string) {\n    const types = this.eventTypes\n    const isType = types[type] === type\n    if (!isType) {\n      warn(\n        `EventEmitter has used unknown event type: \"${type}\", should be oneof [` +\n          `${Object.keys(types).map((_) => JSON.stringify(_))}` +\n          `]`\n      )\n    }\n  }\n}\n\ninterface EventData {\n  name: string\n  handler(e: UIEvent): void\n  capture?: boolean\n}\n\nexport class EventRegister {\n  constructor(\n    public wrapper: HTMLElement | Window,\n    public events: EventData[]\n  ) {\n    this.addDOMEvents()\n  }\n\n  destroy() {\n    this.removeDOMEvents()\n    this.events = []\n  }\n\n  private addDOMEvents() {\n    this.handleDOMEvents(addEvent)\n  }\n\n  private removeDOMEvents() {\n    this.handleDOMEvents(removeEvent)\n  }\n\n  private handleDOMEvents(eventOperation: Function) {\n    const wrapper = this.wrapper\n\n    this.events.forEach((event: EventData) => {\n      eventOperation(wrapper, event.name, this, !!event.capture)\n    })\n  }\n\n  private handleEvent(e: UIEvent) {\n    const eventType = e.type\n    this.events.some((event: EventData) => {\n      if (event.name === eventType) {\n        event.handler(e)\n        return true\n      }\n      return false\n    })\n  }\n}\n"
  },
  {
    "path": "packages/shared-utils/src/index.ts",
    "content": "export * from './debug'\nexport * from './dom'\nexport * from './ease'\nexport * from './env'\nexport * from './lang'\nexport * from './raf'\nexport * from './Touch'\nexport * from './propertiesProxy'\nexport * from './enums'\nexport * from './events'\nexport * from './types'\n"
  },
  {
    "path": "packages/shared-utils/src/lang.ts",
    "content": "export function getNow() {\n  return window.performance &&\n    window.performance.now &&\n    window.performance.timing\n    ? window.performance.now() + window.performance.timing.navigationStart\n    : +new Date()\n}\n\nexport const extend = <T extends object, U extends object>(\n  target: T,\n  source: U\n): T & U => {\n  for (const key in source) {\n    ;(target as any)[key] = source[key]\n  }\n  return target as T & U\n}\n\nexport function isUndef(v: any): boolean {\n  return v === undefined || v === null\n}\n\nexport function getDistance(x: number, y: number) {\n  return Math.sqrt(x * x + y * y)\n}\nexport function between(x: number, min: number, max: number) {\n  if (x < min) {\n    return min\n  }\n  if (x > max) {\n    return max\n  }\n  return x\n}\n\nexport function findIndex<T>(\n  ary: T[],\n  fn: (value: T, index: number, arr?: T[]) => boolean\n) {\n  if (ary.findIndex) {\n    return ary.findIndex(fn)\n  }\n  let index = -1\n  ary.some(function (item, i, ary) {\n    const ret = fn(item, i, ary)\n    if (ret) {\n      index = i\n      return ret\n    }\n  })\n  return index\n}\n"
  },
  {
    "path": "packages/shared-utils/src/propertiesProxy.ts",
    "content": "/* istanbul ignore next */\nconst noop = function (val?: any) {}\n\ninterface TraversedObject {\n  [key: string]: any\n}\nconst sharedPropertyDefinition: PropertyDescriptor = {\n  enumerable: true,\n  configurable: true,\n  get: noop,\n  set: noop,\n}\n\nconst getProperty = (obj: TraversedObject, key: string) => {\n  const keys = key.split('.')\n  for (let i = 0; i < keys.length - 1; i++) {\n    obj = obj[keys[i]]\n    if (typeof obj !== 'object' || !obj) return\n  }\n  const lastKey = keys.pop() as string\n  if (typeof obj[lastKey] === 'function') {\n    return function () {\n      return obj[lastKey].apply(obj, arguments)\n    }\n  } else {\n    return obj[lastKey]\n  }\n}\n\nconst setProperty = (obj: TraversedObject, key: string, value: any) => {\n  const keys = key.split('.')\n  let temp\n  for (let i = 0; i < keys.length - 1; i++) {\n    temp = keys[i]\n    if (!obj[temp]) obj[temp] = {}\n    obj = obj[temp]\n  }\n  obj[keys.pop() as string] = value\n}\n\nexport function propertiesProxy(\n  target: Object,\n  sourceKey: string,\n  key: string\n) {\n  sharedPropertyDefinition.get = function proxyGetter() {\n    return getProperty(this, sourceKey)\n  }\n  sharedPropertyDefinition.set = function proxySetter(val) {\n    setProperty(this, sourceKey, val)\n  }\n  Object.defineProperty(target, key, sharedPropertyDefinition)\n}\n"
  },
  {
    "path": "packages/shared-utils/src/raf.ts",
    "content": "import { inBrowser } from './env'\n\ninterface DelayedHandler {\n  (): void\n  interval: number\n}\n\nconst DEFAULT_INTERVAL = 1000 / 60\nconst windowCompat = inBrowser && (window as any)\n\n/* istanbul ignore next */\nfunction noop() {}\n\nexport const requestAnimationFrame = (() => {\n  /* istanbul ignore if  */\n  if (!inBrowser) {\n    return noop\n  }\n  return (\n    windowCompat.requestAnimationFrame ||\n    windowCompat.webkitRequestAnimationFrame ||\n    windowCompat.mozRequestAnimationFrame ||\n    windowCompat.oRequestAnimationFrame ||\n    // if all else fails, use setTimeout\n    function (callback: DelayedHandler) {\n      return window.setTimeout(callback, callback.interval || DEFAULT_INTERVAL) // make interval as precise as possible.\n    }\n  )\n})()\n\nexport const cancelAnimationFrame = (() => {\n  /* istanbul ignore if  */\n  if (!inBrowser) {\n    return noop\n  }\n  return (\n    windowCompat.cancelAnimationFrame ||\n    windowCompat.webkitCancelAnimationFrame ||\n    windowCompat.mozCancelAnimationFrame ||\n    windowCompat.oCancelAnimationFrame ||\n    function (id: number) {\n      window.clearTimeout(id)\n    }\n  )\n})()\n"
  },
  {
    "path": "packages/shared-utils/src/types.ts",
    "content": "export type Position = {\n  x: number\n  y: number\n}\n"
  },
  {
    "path": "packages/slide/README.md",
    "content": "# @better-scroll/slide\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/slide/README_zh-CN.md)\n\nThe ability to inject a Carousel effect for BetterScroll.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\nBScroll.use(Slide)\n\nconst bs = new BScroll('.div', {\n  scrollX: false,\n  scrollY: true,\n  slide: {\n    loop: true,\n    threshold: 100\n  },\n  useTransition: true,\n  momentum: false,\n  bounce: false,\n  stopPropagation: true\n})\n```\n"
  },
  {
    "path": "packages/slide/README_zh-CN.md",
    "content": "# @better-scroll/slide\n\n实现轮播图的效果。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\nBScroll.use(Slide)\n\nconst bs = new BScroll('.div', {\n  scrollX: false,\n  scrollY: true,\n  slide: {\n    loop: true,\n    threshold: 100\n  },\n  useTransition: true,\n  momentum: false,\n  bounce: false,\n  stopPropagation: true\n})\n```\n"
  },
  {
    "path": "packages/slide/package.json",
    "content": "{\n\t\"name\": \"@better-scroll/slide\",\n\t\"version\": \"2.5.1\",\n\t\"description\": \"a carousel effect triggered by BetterScroll\",\n\t\"author\": {\n\t\t\"name\": \"jizhi\",\n\t\t\"email\": \"theniceangel@163.com\"\n\t},\n\t\"main\": \"dist/slide.min.js\",\n\t\"module\": \"dist/slide.esm.js\",\n\t\"typings\": \"dist/types/index.d.ts\",\n\t\"publishConfig\": {\n\t\t\"access\": \"public\"\n\t},\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n\t\"keywords\": [\n\t\t\"scroll\",\n\t\t\"iscroll\",\n\t\t\"javascript\",\n\t\t\"typescript\",\n\t\t\"ios\"\n\t],\n\t\"license\": \"MIT\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+ssh://git@github.com/ustbhuangyi/better-scroll.git\",\n\t\t\"directory\": \"packages/slide\"\n\t},\n\t\"dependencies\": {\n\t\t\"@better-scroll/core\": \"^2.5.1\"\n\t},\n\t\"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/slide/src/PagesMatrix.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport { PageIndex } from './SlidePages'\nimport { DEFAULT_PAGE_STATS } from './constants'\n\nexport interface PageStats {\n  x: number\n  y: number\n  width: number\n  height: number\n  cx: number // center position of every page\n  cy: number\n}\n\nexport default class PagesMatrix {\n  pages: Array<Array<PageStats>>\n  pageLengthOfX: number\n  pageLengthOfY: number\n  private wrapperWidth: number\n  private wrapperHeight: number\n  private scrollerWidth: number\n  private scrollerHeight: number\n  constructor(private scroll: BScroll) {\n    this.init()\n  }\n  init() {\n    const scroller = this.scroll.scroller\n    const { scrollBehaviorX, scrollBehaviorY } = scroller\n    this.wrapperWidth = scrollBehaviorX.wrapperSize\n    this.wrapperHeight = scrollBehaviorY.wrapperSize\n    this.scrollerHeight = scrollBehaviorY.contentSize\n    this.scrollerWidth = scrollBehaviorX.contentSize\n    this.pages = this.buildPagesMatrix(this.wrapperWidth, this.wrapperHeight)\n    this.pageLengthOfX = this.pages ? this.pages.length : 0\n    this.pageLengthOfY = this.pages && this.pages[0] ? this.pages[0].length : 0\n  }\n\n  getPageStats(pageX: number, pageY: number): PageStats {\n    // Returns the default Stats when no Stats are retrieved\n    // Scene: When the `content` element is an empty bounding box, the return value of the `width/height` function of el.getBoundingClientRect is 0 and pages will be calculated as an empty array.\n    // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect\n    return this.pages[pageX] && this.pages[pageX][pageY]\n      ? this.pages[pageX][pageY]\n      : DEFAULT_PAGE_STATS\n  }\n\n  getNearestPageIndex(x: number, y: number): PageIndex {\n    let pageX = 0\n    let pageY = 0\n    let l = this.pages.length\n    for (; pageX < l - 1; pageX++) {\n      if (x >= this.pages[pageX][0].cx) {\n        break\n      }\n    }\n    l = this.pages[pageX] ? this.pages[pageX].length : 0\n    for (; pageY < l - 1; pageY++) {\n      if (y >= this.pages[0][pageY].cy) {\n        break\n      }\n    }\n    return {\n      pageX,\n      pageY,\n    }\n  }\n\n  // (n x 1) matrix for horizontal scroll or\n  // (1 * n) matrix for vertical scroll\n  private buildPagesMatrix(\n    stepX: number,\n    stepY: number\n  ): Array<Array<PageStats>> {\n    let pages: Array<Array<PageStats>> = []\n    let x = 0\n    let y\n    let cx\n    let cy\n    let i = 0\n    let l\n    const maxScrollPosX = this.scroll.scroller.scrollBehaviorX.maxScrollPos\n    const maxScrollPosY = this.scroll.scroller.scrollBehaviorY.maxScrollPos\n    cx = Math.round(stepX / 2)\n    cy = Math.round(stepY / 2)\n\n    while (x > -this.scrollerWidth) {\n      pages[i] = []\n      l = 0\n      y = 0\n      while (y > -this.scrollerHeight) {\n        pages[i][l] = {\n          x: Math.max(x, maxScrollPosX),\n          y: Math.max(y, maxScrollPosY),\n          width: stepX,\n          height: stepY,\n          cx: x - cx,\n          cy: y - cy,\n        }\n\n        y -= stepY\n        l++\n      }\n      x -= stepX\n      i++\n    }\n    return pages\n  }\n}\n"
  },
  {
    "path": "packages/slide/src/SlidePages.ts",
    "content": "import { between, extend, warn } from '@better-scroll/shared-utils'\nimport BScroll from '@better-scroll/core'\nimport { SlideConfig } from './index'\nimport PagesMatrix, { PageStats } from './PagesMatrix'\nimport { BASE_PAGE } from './constants'\n\nexport interface PageIndex {\n  pageX: number\n  pageY: number\n}\nexport interface Position {\n  x: number\n  y: number\n}\n\nexport type Page = PageIndex & Position\n\nconst enum Direction {\n  Positive = 'positive',\n  Negative = 'negative',\n}\n\nexport default class SlidePages {\n  loopX: boolean\n  loopY: boolean\n  slideX: boolean = false\n  slideY: boolean = false\n  wannaLoop: boolean\n  pagesMatrix: PagesMatrix\n  currentPage: Page\n  constructor(public scroll: BScroll, private slideOptions: SlideConfig) {\n    this.currentPage = extend({}, BASE_PAGE)\n  }\n\n  refresh() {\n    this.pagesMatrix = new PagesMatrix(this.scroll)\n    this.checkSlideLoop()\n    this.currentPage = this.getAdjustedCurrentPage()\n  }\n\n  getAdjustedCurrentPage(): Page {\n    let { pageX, pageY } = this.currentPage\n    // page index should be handled\n    // because page counts may reduce\n    pageX = Math.min(pageX, this.pagesMatrix.pageLengthOfX - 1)\n    pageY = Math.min(pageY, this.pagesMatrix.pageLengthOfY - 1)\n    // loop scene should also be respected\n    // because clonedNode will cause pageLength increasing\n    if (this.loopX) {\n      pageX = Math.min(pageX, this.pagesMatrix.pageLengthOfX - 2)\n    }\n    if (this.loopY) {\n      pageY = Math.min(pageY, this.pagesMatrix.pageLengthOfY - 2)\n    }\n    const { x, y } = this.pagesMatrix.getPageStats(pageX, pageY)\n    return { pageX, pageY, x, y }\n  }\n\n  setCurrentPage(newPage: Page) {\n    this.currentPage = newPage\n  }\n\n  getInternalPage(pageX: number, pageY: number): Page {\n    if (pageX >= this.pagesMatrix.pageLengthOfX) {\n      pageX = this.pagesMatrix.pageLengthOfX - 1\n    } else if (pageX < 0) {\n      pageX = 0\n    }\n\n    if (pageY >= this.pagesMatrix.pageLengthOfY) {\n      pageY = this.pagesMatrix.pageLengthOfY - 1\n    } else if (pageY < 0) {\n      pageY = 0\n    }\n\n    let { x, y } = this.pagesMatrix.getPageStats(pageX, pageY)\n\n    return {\n      pageX,\n      pageY,\n      x,\n      y,\n    }\n  }\n\n  getInitialPage(\n    showFirstPage: boolean = false,\n    firstInitialised: boolean = false\n  ): Page {\n    const { startPageXIndex, startPageYIndex } = this.slideOptions\n    let firstPageX = this.loopX ? 1 : 0\n    let firstPageY = this.loopY ? 1 : 0\n\n    let pageX = showFirstPage ? firstPageX : this.currentPage.pageX\n    let pageY = showFirstPage ? firstPageY : this.currentPage.pageY\n\n    if (firstInitialised) {\n      pageX = this.loopX ? startPageXIndex + 1 : startPageXIndex\n      pageY = this.loopY ? startPageYIndex + 1 : startPageYIndex\n    } else {\n      pageX = showFirstPage ? firstPageX : this.currentPage.pageX\n      pageY = showFirstPage ? firstPageY : this.currentPage.pageY\n    }\n\n    const { x, y } = this.pagesMatrix.getPageStats(pageX, pageY)\n\n    return {\n      pageX,\n      pageY,\n      x,\n      y,\n    }\n  }\n\n  getExposedPage(page: Page): Page {\n    let exposedPage = extend({}, page)\n    // only pageX or pageY need fix\n    if (this.loopX) {\n      exposedPage.pageX = this.fixedPage(\n        exposedPage.pageX,\n        this.pagesMatrix.pageLengthOfX - 2\n      )\n    }\n    if (this.loopY) {\n      exposedPage.pageY = this.fixedPage(\n        exposedPage.pageY,\n        this.pagesMatrix.pageLengthOfY - 2\n      )\n    }\n    return exposedPage\n  }\n\n  getExposedPageByPageIndex(pageIndexX: number, pageIndexY: number): Page {\n    const page = {\n      pageX: pageIndexX,\n      pageY: pageIndexY,\n    }\n    if (this.loopX) {\n      page.pageX = pageIndexX + 1\n    }\n    if (this.loopY) {\n      page.pageY = pageIndexY + 1\n    }\n    const { x, y } = this.pagesMatrix.getPageStats(page.pageX, page.pageY)\n    return {\n      x,\n      y,\n      pageX: pageIndexX,\n      pageY: pageIndexY,\n    }\n  }\n\n  getWillChangedPage(page: Page): Page {\n    page = extend({}, page)\n    // Page need fix\n    if (this.loopX) {\n      page.pageX = this.fixedPage(\n        page.pageX,\n        this.pagesMatrix.pageLengthOfX - 2\n      )\n      page.x = this.pagesMatrix.getPageStats(page.pageX + 1, 0).x\n    }\n    if (this.loopY) {\n      page.pageY = this.fixedPage(\n        page.pageY,\n        this.pagesMatrix.pageLengthOfY - 2\n      )\n      page.y = this.pagesMatrix.getPageStats(0, page.pageY + 1).y\n    }\n    return page\n  }\n\n  private fixedPage(page: number, realPageLen: number): number {\n    const pageIndex = []\n    for (let i = 0; i < realPageLen; i++) {\n      pageIndex.push(i)\n    }\n    pageIndex.unshift(realPageLen - 1)\n    pageIndex.push(0)\n    return pageIndex[page]\n  }\n\n  getPageStats(): PageStats {\n    return this.pagesMatrix.getPageStats(\n      this.currentPage.pageX,\n      this.currentPage.pageY\n    )\n  }\n\n  getValidPageIndex(x: number, y: number): PageIndex {\n    let lastX = this.pagesMatrix.pageLengthOfX - 1\n    let lastY = this.pagesMatrix.pageLengthOfY - 1\n    let firstX = 0\n    let firstY = 0\n    if (this.loopX) {\n      x += 1\n      firstX = firstX + 1\n      lastX = lastX - 1\n    }\n    if (this.loopY) {\n      y += 1\n      firstY = firstY + 1\n      lastY = lastY - 1\n    }\n    x = between(x, firstX, lastX)\n    y = between(y, firstY, lastY)\n\n    return {\n      pageX: x,\n      pageY: y,\n    }\n  }\n\n  nextPageIndex(): PageIndex {\n    return this.getPageIndexByDirection(Direction.Positive)\n  }\n\n  prevPageIndex(): PageIndex {\n    return this.getPageIndexByDirection(Direction.Negative)\n  }\n\n  getNearestPage(x: number, y: number): Page {\n    const pageIndex = this.pagesMatrix.getNearestPageIndex(x, y)\n    let { pageX, pageY } = pageIndex\n\n    let newX = this.pagesMatrix.getPageStats(pageX, 0).x\n    let newY = this.pagesMatrix.getPageStats(0, pageY).y\n\n    return {\n      x: newX,\n      y: newY,\n      pageX,\n      pageY,\n    }\n  }\n\n  getPageByDirection(page: Page, directionX: number, directionY: number): Page {\n    let { pageX, pageY } = page\n    if (pageX === this.currentPage.pageX) {\n      pageX = between(pageX + directionX, 0, this.pagesMatrix.pageLengthOfX - 1)\n    }\n    if (pageY === this.currentPage.pageY) {\n      pageY = between(pageY + directionY, 0, this.pagesMatrix.pageLengthOfY - 1)\n    }\n\n    const x = this.pagesMatrix.getPageStats(pageX, 0).x\n    const y = this.pagesMatrix.getPageStats(0, pageY).y\n\n    return {\n      x,\n      y,\n      pageX,\n      pageY,\n    }\n  }\n\n  resetLoopPage(): PageIndex | undefined {\n    if (this.loopX) {\n      if (this.currentPage.pageX === 0) {\n        return {\n          pageX: this.pagesMatrix.pageLengthOfX - 2,\n          pageY: this.currentPage.pageY,\n        }\n      }\n      if (this.currentPage.pageX === this.pagesMatrix.pageLengthOfX - 1) {\n        return {\n          pageX: 1,\n          pageY: this.currentPage.pageY,\n        }\n      }\n    }\n    if (this.loopY) {\n      if (this.currentPage.pageY === 0) {\n        return {\n          pageX: this.currentPage.pageX,\n          pageY: this.pagesMatrix.pageLengthOfY - 2,\n        }\n      }\n      if (this.currentPage.pageY === this.pagesMatrix.pageLengthOfY - 1) {\n        return {\n          pageX: this.currentPage.pageX,\n          pageY: 1,\n        }\n      }\n    }\n  }\n\n  private getPageIndexByDirection(direction: Direction): PageIndex {\n    let x = this.currentPage.pageX\n    let y = this.currentPage.pageY\n    if (this.slideX) {\n      x = direction === Direction.Negative ? x - 1 : x + 1\n    }\n    if (this.slideY) {\n      y = direction === Direction.Negative ? y - 1 : y + 1\n    }\n    return {\n      pageX: x,\n      pageY: y,\n    }\n  }\n\n  private checkSlideLoop() {\n    this.wannaLoop = this.slideOptions.loop\n    if (this.pagesMatrix.pageLengthOfX > 1) {\n      this.slideX = true\n    } else {\n      this.slideX = false\n    }\n    if (this.pagesMatrix.pages[0] && this.pagesMatrix.pageLengthOfY > 1) {\n      this.slideY = true\n    } else {\n      this.slideY = false\n    }\n    this.loopX = this.wannaLoop && this.slideX\n    this.loopY = this.wannaLoop && this.slideY\n\n    if (this.slideX && this.slideY) {\n      warn('slide does not support two direction at the same time.')\n    }\n  }\n}\n"
  },
  {
    "path": "packages/slide/src/__mocks__/PagesMatrix.ts",
    "content": "const PagesMatrix = jest.fn().mockImplementation((scroll) => {\n  const loopX = scroll.options.scrollX\n  const loopY = scroll.options.scrollY\n  const pages = loopX\n    ? [\n        [\n          {\n            pageX: 0,\n            pageY: 0,\n            x: 0,\n            y: 0,\n          },\n        ],\n        [\n          {\n            pageX: 1,\n            pageY: 0,\n            x: 20,\n            y: 0,\n          },\n        ],\n        [\n          {\n            pageX: 2,\n            pageY: 0,\n            x: 40,\n            y: 0,\n          },\n        ],\n        [\n          {\n            pageX: 3,\n            pageY: 0,\n            x: 60,\n            y: 0,\n          },\n        ],\n      ]\n    : loopY\n    ? [\n        [\n          {\n            pageX: 0,\n            pageY: 0,\n            x: 0,\n            y: 0,\n          },\n          {\n            pageX: 0,\n            pageY: 1,\n            x: 0,\n            y: 20,\n          },\n          {\n            pageX: 0,\n            pageY: 2,\n            x: 0,\n            y: 40,\n          },\n          {\n            pageX: 0,\n            pageY: 3,\n            x: 0,\n            y: 60,\n          },\n        ],\n      ]\n    : []\n  return {\n    pages,\n    pageLengthOfX: loopX ? 4 : 1,\n    pageLengthOfY: loopY ? 4 : 1,\n    wrapperWidth: 0,\n    wrapperHeight: 0,\n    scrollerWidth: 0,\n    scrollerHeight: 0,\n    init: jest.fn(),\n    getPageStats: jest.fn().mockImplementation(() => {\n      return {\n        x: 0,\n        y: 0,\n        width: 100,\n        height: 100,\n        cx: 50, // center position of every page\n        cy: 50,\n      }\n    }),\n    getNearestPageIndex: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 1,\n        pageY: 0,\n      }\n    }),\n    buildPagesMatrix: jest.fn(),\n  }\n})\n\nexport default PagesMatrix\n"
  },
  {
    "path": "packages/slide/src/__mocks__/SlidePages.ts",
    "content": "import PagesMatrix from '../PagesMatrix'\njest.mock('../PagesMatrix')\n\nconst SlidePage = jest.fn().mockImplementation(() => {\n  return {\n    currentPage: {\n      pageX: 0,\n      pageY: 0,\n      x: 0,\n      y: 0,\n    },\n    loopX: true,\n    loopY: false,\n    slideX: true,\n    slideY: false,\n    needLoop: true,\n    pagesMatrix: new PagesMatrix({\n      options: {},\n    } as any),\n    refresh: jest.fn(),\n    checkSlideLoop: jest.fn(),\n    setCurrentPage: jest.fn(),\n    getInternalPage: jest.fn().mockImplementation((pageX, pageY) => {\n      return {\n        pageX,\n        pageY,\n        x: 20,\n        y: 20,\n      }\n    }),\n    getExposedPageByPageIndex: jest.fn(),\n    getInitialPage: jest.fn().mockImplementation((flag, firstInit) => {\n      if (firstInit) {\n        return {\n          pageX: 1,\n          pageY: 0,\n          x: 10,\n          y: 10,\n        }\n      }\n      return {\n        pageX: 0,\n        pageY: 0,\n        x: 10,\n        y: 10,\n      }\n    }),\n    getExposedPage: jest.fn().mockImplementation((page) => {\n      return (\n        page || {\n          pageX: 0,\n          pageY: 0,\n          x: 0,\n          y: 0,\n        }\n      )\n    }),\n    getWillChangedPage: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      }\n    }),\n    fixedPage: jest.fn(),\n    getPageStats: jest.fn().mockImplementation(() => {\n      return {\n        width: 0,\n        height: 0,\n      }\n    }),\n    getValidPageIndex: jest.fn().mockImplementation((pageX, pageY) => {\n      return {\n        pageX,\n        pageY,\n      }\n    }),\n    nextPageIndex: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n      }\n    }),\n    prevPageIndex: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n      }\n    }),\n    getNearestPage: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      }\n    }),\n    resetLoopPage: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n      }\n    }),\n    getPageIndexByDirection: jest.fn(),\n    getPageByDirection: jest.fn().mockImplementation(() => {\n      return {\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      }\n    }),\n  }\n})\n\nexport default SlidePage\n"
  },
  {
    "path": "packages/slide/src/__tests__/PagesMatrix.spec.ts",
    "content": "import PagesMatrix from '../PagesMatrix'\nimport BScroll from '@better-scroll/core'\nimport { DEFAULT_PAGE_STATS } from '../constants'\n\nconst createSlideElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  for (let i = 0; i < 3; i++) {\n    content.appendChild(document.createElement('p'))\n  }\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\ndescribe('slide test for PagesMatrix class', () => {\n  let pageMatrix: PagesMatrix\n  let scroll: BScroll\n\n  beforeEach(() => {\n    const { wrapper } = createSlideElements()\n    scroll = new BScroll(wrapper, {})\n    scroll.scroller.scrollBehaviorX.wrapperSize = 100\n    scroll.scroller.scrollBehaviorX.contentSize = 400\n    scroll.scroller.scrollBehaviorX.maxScrollPos = -300\n    scroll.scroller.scrollBehaviorY.wrapperSize = 100\n    scroll.scroller.scrollBehaviorY.contentSize = 100\n    scroll.scroller.scrollBehaviorY.maxScrollPos = 0\n    pageMatrix = new PagesMatrix(scroll)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should create 4 * 1 matrix page in X direction', () => {\n    expect(pageMatrix.pages.length).toBe(4)\n    expect(pageMatrix.pages[0].length).toBe(1)\n    expect(pageMatrix.pageLengthOfX).toBe(4)\n    expect(pageMatrix.pageLengthOfY).toBe(1)\n\n    const pageIndex1 = pageMatrix.getNearestPageIndex(0, 0)\n\n    expect(pageIndex1).toMatchObject({\n      pageX: 0,\n      pageY: 0,\n    })\n\n    const pageIndex2 = pageMatrix.getNearestPageIndex(-175, 0)\n\n    expect(pageIndex2).toMatchObject({\n      pageX: 2,\n      pageY: 0,\n    })\n  })\n\n  it('should create 1 * 4 matrix page in Y direction', () => {\n    const { wrapper } = createSlideElements()\n    scroll = new BScroll(wrapper, {})\n    scroll.scroller.scrollBehaviorX.wrapperSize = 100\n    scroll.scroller.scrollBehaviorX.contentSize = 100\n    scroll.scroller.scrollBehaviorX.maxScrollPos = 0\n    scroll.scroller.scrollBehaviorY.wrapperSize = 100\n    scroll.scroller.scrollBehaviorY.contentSize = 400\n    scroll.scroller.scrollBehaviorY.maxScrollPos = -300\n    pageMatrix = new PagesMatrix(scroll)\n\n    expect(pageMatrix.pages.length).toBe(1)\n    expect(pageMatrix.pages[0].length).toBe(4)\n    expect(pageMatrix.pageLengthOfX).toBe(1)\n    expect(pageMatrix.pageLengthOfY).toBe(4)\n\n    const pageIndex1 = pageMatrix.getNearestPageIndex(0, 0)\n\n    expect(pageIndex1).toMatchObject({\n      pageX: 0,\n      pageY: 0,\n    })\n\n    const pageIndex2 = pageMatrix.getNearestPageIndex(0, -175)\n\n    expect(pageIndex2).toMatchObject({\n      pageX: 0,\n      pageY: 2,\n    })\n  })\n\n  it('should work well with getPageStats()', () => {\n    const pageStats1 = pageMatrix.getPageStats(1, 0)\n\n    expect(pageStats1).toMatchObject({\n      x: -100,\n      y: 0,\n      width: 100,\n      height: 100,\n      cx: -150,\n      cy: -50,\n    })\n\n    const pageStats2 = pageMatrix.getPageStats(2, 0)\n    expect(pageStats2).toMatchObject({\n      x: -200,\n      y: 0,\n      width: 100,\n      height: 100,\n      cx: -250,\n      cy: -50,\n    })\n  })\n\n  it('The pages calculation fails to access PageStats should return the default value', () => {\n    const { wrapper } = createSlideElements()\n    scroll = new BScroll(wrapper, {})\n    scroll.scroller.scrollBehaviorX.wrapperSize = 100\n    scroll.scroller.scrollBehaviorX.contentSize = 0\n    scroll.scroller.scrollBehaviorX.maxScrollPos = 100\n    scroll.scroller.scrollBehaviorY.wrapperSize = 100\n    scroll.scroller.scrollBehaviorY.contentSize = 0\n    scroll.scroller.scrollBehaviorY.maxScrollPos = 100\n    pageMatrix = new PagesMatrix(scroll)\n\n    expect(pageMatrix.pages.length).toBe(0)\n    expect(pageMatrix.pageLengthOfX).toBe(0)\n    expect(pageMatrix.pageLengthOfY).toBe(0)\n\n    const pagesIdx: [number, number][] = [\n      [1, 0],\n      [-1, 0],\n      [1, 1],\n      [-1, -1],\n      [0, 1],\n      [0, -1],\n    ]\n    for (const idx of pagesIdx) {\n      expect(pageMatrix.getPageStats(idx[0], idx[1])).toMatchObject(\n        DEFAULT_PAGE_STATS\n      )\n    }\n\n    const pagesLoc: [number, number][] = [\n      [-100, 0],\n      [0, -100],\n    ]\n\n    for (const loc of pagesLoc) {\n      expect(pageMatrix.getNearestPageIndex(loc[0], loc[1])).toMatchObject({\n        pageX: 0,\n        pageY: 0,\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "packages/slide/src/__tests__/SlidePages.spec.ts",
    "content": "import SlidePages from '../SlidePages'\nimport PageMatrix from '../PagesMatrix'\nimport BScroll from '@better-scroll/core'\nimport { ease } from '@better-scroll/shared-utils'\n\njest.mock('@better-scroll/core')\njest.mock('../PagesMatrix')\n\nconst createSlideElements = () => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  for (let i = 0; i < 3; i++) {\n    content.appendChild(document.createElement('p'))\n  }\n  wrapper.appendChild(content)\n  return { wrapper }\n}\n\ndescribe('slide test for SlidePages class', () => {\n  let slidePages: SlidePages\n  let scroll: BScroll\n  const BASE_PAGE = {\n    pageX: 0,\n    pageY: 0,\n    x: 0,\n    y: 0,\n  }\n  const slideOptions = {\n    loop: true,\n    threshold: 0.1,\n    speed: 400,\n    easing: ease.bounce,\n    listenFlick: true,\n    autoplay: true,\n    interval: 3000,\n    startPageXIndex: 0,\n    startPageYIndex: 0,\n  }\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  describe('loopX', () => {\n    beforeEach(() => {\n      const { wrapper } = createSlideElements()\n      scroll = new BScroll(wrapper, {\n        scrollX: true,\n        scrollY: false,\n      })\n      slidePages = new SlidePages(scroll, slideOptions)\n      slidePages.refresh()\n    })\n\n    it('should has base current page', () => {\n      expect(slidePages.currentPage).toMatchObject(BASE_PAGE)\n    })\n\n    it('should set loopX when new SlidePage', () => {\n      expect(slidePages.loopX).toBe(true)\n    })\n\n    it('should work well with getExposedPageByPageIndex()', () => {\n      const ret = slidePages.getExposedPageByPageIndex(1, 0)\n      expect(ret).toMatchObject({\n        x: 0,\n        y: 0,\n        pageX: 1,\n        pageY: 0,\n      })\n    })\n\n    it('should work well with setCurrentPage()', () => {\n      const currentPage = {\n        pageX: 1,\n        pageY: 0,\n        x: 200,\n        y: 200,\n      }\n      slidePages.setCurrentPage(currentPage)\n      expect(slidePages.currentPage).toMatchObject(currentPage)\n    })\n\n    it('should work well with getInternalPage()', () => {\n      const page1 = slidePages.getInternalPage(1, 0)\n\n      expect(page1).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n\n      // exceed maxPageX\n      const page2 = slidePages.getInternalPage(4, 0)\n\n      expect(page2).toMatchObject({\n        pageX: 3,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n\n      // less than maxPageX\n      const page3 = slidePages.getInternalPage(-1, 0)\n\n      expect(page3).toMatchObject({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getInitialPage()', () => {\n      const page = slidePages.getInitialPage(true)\n\n      expect(page).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getExposedPage() when loopX is true', () => {\n      const pageX = slidePages.getExposedPage({\n        x: 0,\n        y: 0,\n        pageX: 1,\n        pageY: 0,\n      })\n\n      expect(pageX).toMatchObject({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getWillChangedPage() when loopX is true', () => {\n      const page = slidePages.getWillChangedPage({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n\n      expect(page).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getPageStats()', () => {\n      const pageStats = slidePages.getPageStats()\n\n      expect(pageStats).toMatchObject({\n        x: 0,\n        y: 0,\n        width: 100,\n        height: 100,\n        cx: 50,\n        cy: 50,\n      })\n    })\n\n    it('should work well with getValidPageIndex()', () => {\n      const pageIndex = slidePages.getValidPageIndex(1, 0)\n\n      expect(pageIndex).toMatchObject({\n        pageX: 2,\n        pageY: 0,\n      })\n    })\n\n    it('should work well with nextPageIndex()', () => {\n      const pageIndex = slidePages.nextPageIndex()\n\n      expect(pageIndex).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n      })\n    })\n\n    it('should work well with prevPageIndex()', () => {\n      const pageIndex = slidePages.prevPageIndex()\n\n      expect(pageIndex).toMatchObject({\n        pageX: -1,\n        pageY: 0,\n      })\n    })\n\n    it('should work well with getNearestPage()', () => {\n      const pageIndex = slidePages.getNearestPage(30, 0)\n\n      expect(pageIndex).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with resetLoopPage()', () => {\n      const page1 = slidePages.resetLoopPage()\n\n      expect(page1).toMatchObject({\n        pageX: 2,\n        pageY: 0,\n      })\n\n      slidePages.setCurrentPage({\n        pageX: 3,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n      const page2 = slidePages.resetLoopPage()\n\n      expect(page2).toMatchObject({\n        pageX: 1,\n        pageY: 0,\n      })\n    })\n\n    it('should work well with getPageByDirection()', () => {\n      const page = slidePages.getPageByDirection(\n        {\n          x: 0,\n          y: 0,\n          pageY: 0,\n          pageX: 0,\n        },\n        1,\n        0\n      )\n\n      expect(page.pageX).toBe(1)\n    })\n  })\n\n  describe('loopY', () => {\n    beforeEach(() => {\n      const { wrapper } = createSlideElements()\n      scroll = new BScroll(wrapper, {\n        scrollX: false,\n        scrollY: true,\n      })\n      slidePages = new SlidePages(scroll, slideOptions)\n      slidePages.refresh()\n    })\n\n    it('should set loopY when new SlidePage', () => {\n      expect(slidePages.loopY).toBe(true)\n    })\n\n    it('should work well with getExposedPageByPageIndex()', () => {\n      const ret = slidePages.getExposedPageByPageIndex(0, 1)\n      expect(ret).toMatchObject({\n        x: 0,\n        y: 0,\n        pageX: 0,\n        pageY: 1,\n      })\n    })\n\n    it('should work well with getInitialPage()', () => {\n      const page = slidePages.getInitialPage(true, true)\n\n      expect(page).toMatchObject({\n        pageX: 0,\n        pageY: 1,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getExposedPage() when loopY is true', () => {\n      const pageY = slidePages.getExposedPage({\n        x: 0,\n        y: 0,\n        pageX: 0,\n        pageY: 1,\n      })\n\n      expect(pageY).toMatchObject({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getWillChangedPage() when loopY is true', () => {\n      const page = slidePages.getWillChangedPage({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n\n      expect(page).toMatchObject({\n        pageX: 0,\n        pageY: 1,\n        x: 0,\n        y: 0,\n      })\n    })\n\n    it('should work well with getValidPageIndex()', () => {\n      const pageIndex = slidePages.getValidPageIndex(0, 1)\n\n      expect(pageIndex).toMatchObject({\n        pageX: 0,\n        pageY: 2,\n      })\n    })\n\n    it('should work well with nextPageIndex()', () => {\n      const pageIndex = slidePages.nextPageIndex()\n\n      expect(pageIndex).toMatchObject({\n        pageX: 0,\n        pageY: 1,\n      })\n    })\n\n    it('should work well with prevPageIndex()', () => {\n      const pageIndex = slidePages.prevPageIndex()\n\n      expect(pageIndex).toMatchObject({\n        pageX: 0,\n        pageY: -1,\n      })\n    })\n\n    it('should work well with resetLoopPage()', () => {\n      const page1 = slidePages.resetLoopPage()\n\n      expect(page1).toMatchObject({\n        pageX: 0,\n        pageY: 2,\n      })\n\n      slidePages.setCurrentPage({\n        pageX: 0,\n        pageY: 3,\n        x: 0,\n        y: 0,\n      })\n\n      const page2 = slidePages.resetLoopPage()\n\n      expect(page2).toMatchObject({\n        pageX: 0,\n        pageY: 1,\n      })\n    })\n\n    it('should work well with getPageByDirection()', () => {\n      const page = slidePages.getPageByDirection(\n        {\n          x: 0,\n          y: 0,\n          pageY: 0,\n          pageX: 0,\n        },\n        0,\n        1\n      )\n\n      expect(page.pageY).toBe(1)\n    })\n\n    it('should work well with getInternalPage()', () => {\n      const page1 = slidePages.getInternalPage(0, 1)\n\n      expect(page1).toMatchObject({\n        pageX: 0,\n        pageY: 1,\n        x: 0,\n        y: 0,\n      })\n\n      // exceed maxPageY\n      const page2 = slidePages.getInternalPage(0, 4)\n\n      expect(page2).toMatchObject({\n        pageX: 0,\n        pageY: 3,\n        x: 0,\n        y: 0,\n      })\n\n      // less than maxPageX\n      const page3 = slidePages.getInternalPage(0, -1)\n\n      expect(page3).toMatchObject({\n        pageX: 0,\n        pageY: 0,\n        x: 0,\n        y: 0,\n      })\n    })\n  })\n\n  describe('loopX & loopY', () => {\n    it('should warn when loopX & loopY is true', () => {\n      const spyFn = jest.spyOn(console, 'error')\n      const { wrapper } = createSlideElements()\n      scroll = new BScroll(wrapper, {\n        scrollX: true,\n        scrollY: true,\n      })\n      slidePages = new SlidePages(scroll, slideOptions)\n      slidePages.refresh()\n      expect(spyFn).toHaveBeenCalledTimes(1)\n    })\n  })\n})\n"
  },
  {
    "path": "packages/slide/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport Slide from '../index'\nimport SlidePages from '../SlidePages'\nimport { ease } from '@better-scroll/shared-utils'\n\njest.mock('@better-scroll/core')\njest.mock('../SlidePages')\n\nconst createSlideElements = (len = 3) => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  for (let i = 0; i < len; i++) {\n    content.appendChild(document.createElement('p'))\n  }\n  wrapper.appendChild(content)\n  return { wrapper, content }\n}\n\ndescribe('slide test for SlidePage class', () => {\n  let scroll: BScroll\n  let slide: Slide\n  beforeAll(() => {\n    jest.useFakeTimers()\n  })\n\n  beforeEach(() => {\n    const { wrapper } = createSlideElements()\n    scroll = new BScroll(wrapper, {})\n    slide = new Slide(scroll)\n  })\n\n  afterAll(() => {\n    jest.clearAllMocks()\n    jest.clearAllTimers()\n  })\n\n  it('should fail when slideContent has no children element', () => {\n    const spyFn = jest.spyOn(console, 'error')\n    const { wrapper } = createSlideElements(0)\n    scroll = new BScroll(wrapper, {})\n    slide = new Slide(scroll)\n\n    expect(spyFn).toBeCalled()\n  })\n\n  it('should proxy hooks to BScroll instance', () => {\n    expect(scroll.registerType).toHaveBeenCalledWith([\n      'slideWillChange',\n      'slidePageChanged',\n    ])\n\n    expect(scroll.proxy).toHaveBeenLastCalledWith([\n      {\n        key: 'next',\n        sourceKey: 'plugins.slide.next',\n      },\n      {\n        key: 'prev',\n        sourceKey: 'plugins.slide.prev',\n      },\n      {\n        key: 'goToPage',\n        sourceKey: 'plugins.slide.goToPage',\n      },\n      {\n        key: 'getCurrentPage',\n        sourceKey: 'plugins.slide.getCurrentPage',\n      },\n      {\n        key: 'startPlay',\n        sourceKey: 'plugins.slide.startPlay',\n      },\n      {\n        key: 'pausePlay',\n        sourceKey: 'plugins.slide.pausePlay',\n      },\n    ])\n  })\n\n  it('should handle default options and user options', () => {\n    // case 1\n    scroll.options.slide = true\n    slide = new Slide(scroll)\n\n    expect(slide.options).toMatchObject({\n      loop: true,\n      threshold: 0.1,\n      speed: 400,\n      easing: ease.bounce,\n      listenFlick: true,\n      autoplay: true,\n      interval: 3000,\n    })\n\n    // case 2\n    scroll.options.slide = {\n      loop: false,\n      autoplay: false,\n    }\n    slide = new Slide(scroll)\n\n    expect(slide.options).toMatchObject({\n      loop: false,\n      threshold: 0.1,\n      speed: 400,\n      easing: ease.bounce,\n      listenFlick: true,\n      autoplay: false,\n      interval: 3000,\n    })\n  })\n\n  it('should clone the first and last page when loop is true', () => {\n    const content = scroll.scroller.content\n    scroll.scroller.hooks.trigger(\n      scroll.scroller.hooks.eventTypes.beforeRefresh\n    )\n    expect(content.children.length).toBe(5)\n  })\n\n  it('should not clone the first and last page when loop is false', () => {\n    const { wrapper } = createSlideElements()\n    scroll = new BScroll(wrapper)\n    scroll.options.slide = {\n      loop: false,\n    }\n    slide = new Slide(scroll)\n    scroll.scroller.hooks.trigger(\n      scroll.scroller.hooks.eventTypes.beforeRefresh\n    )\n    const content = scroll.scroller.content\n    expect(content.children.length).toBe(3)\n  })\n\n  it('should failed to initialised slide when only has a child', () => {\n    const { wrapper } = createSlideElements(0)\n    const spyFn = jest.spyOn(console, 'error')\n    scroll = new BScroll(wrapper, {})\n    slide = new Slide(scroll)\n    expect(spyFn).toBeCalled()\n  })\n\n  describe('api', () => {\n    it('goToPage()', () => {\n      slide.goToPage(2, 0)\n\n      expect(scroll.scroller.scrollTo).toBeCalledWith(\n        20,\n        20,\n        400,\n        expect.anything()\n      )\n    })\n\n    it('getCurrentPage()', () => {\n      slide.getCurrentPage()\n\n      expect(slide.pages.getInitialPage).toBeCalled()\n    })\n\n    it('startPlay()', () => {\n      slide.startPlay()\n      jest.advanceTimersByTime(4000)\n      expect(scroll.scroller.scrollTo).toBeCalledWith(\n        20,\n        20,\n        400,\n        expect.anything()\n      )\n    })\n  })\n\n  describe('tap into scroll', () => {\n    it('should pause play when BScroll trigger beforeScrollStart hook', () => {\n      const spyFn = jest.spyOn(Slide.prototype, 'pausePlay')\n      slide = new Slide(scroll)\n      scroll.trigger(scroll.eventTypes.beforeScrollStart)\n\n      expect(spyFn).toBeCalled()\n    })\n\n    it('should call modifyCurrentPage() when BScroll trigger scrollEnd hook', () => {\n      // simulate stopping from animation\n      scroll.scroller.animater.forceStopped = true\n      scroll.trigger(scroll.eventTypes.scrollEnd, { x: 0, y: 0 })\n\n      expect(slide.pages.setCurrentPage).toBeCalled()\n\n      scroll.trigger(scroll.eventTypes.scrollEnd, { x: 0, y: 0 })\n      expect(slide.pages.resetLoopPage).toBeCalledTimes(1)\n    })\n\n    it('slidePageChanged event', () => {\n      const { wrapper } = createSlideElements()\n      const scroll = new BScroll(wrapper, {})\n      const slide = new Slide(scroll)\n      scroll.trigger(scroll.eventTypes.scrollEnd, { x: 0, y: 0 })\n      expect(slide.pages.getExposedPageByPageIndex).toBeCalled()\n    })\n\n    it('should stop mousewheelMove handler chain', () => {\n      scroll.registerType(['mousewheelMove'])\n      slide = new Slide(scroll)\n      const mock = jest.fn()\n      scroll.on(scroll.eventTypes.mousewheelMove, mock)\n      scroll.trigger(scroll.eventTypes.mousewheelMove)\n      expect(mock).not.toBeCalled()\n    })\n\n    it('should call next/prev in mousewheelEnd hook', () => {\n      scroll.registerType(['mousewheelMove', 'mousewheelEnd'])\n      slide = new Slide(scroll)\n      const nextSpyFn = jest.spyOn(Slide.prototype, 'next')\n      const prevSpyFn = jest.spyOn(Slide.prototype, 'prev')\n      const delta1 = {\n        directionX: -1,\n        directionY: -1,\n      }\n      scroll.trigger(scroll.eventTypes.mousewheelEnd, delta1)\n      expect(prevSpyFn).toBeCalled()\n      const delta2 = {\n        directionX: 1,\n        directionY: 1,\n      }\n      scroll.trigger(scroll.eventTypes.mousewheelEnd, delta2)\n      expect(nextSpyFn).toBeCalled()\n    })\n  })\n\n  describe('tap into scroll hooks', () => {\n    it('should call refreshHandler when Bscroll.hooks.refresh triggered', () => {\n      // case 1 content changed\n      let wrapper1 = createSlideElements().wrapper\n      scroll = new BScroll(wrapper1, {\n        slide: {\n          threshold: 100,\n        },\n      })\n      slide = new Slide(scroll)\n      ;(slide as any).initialised = true\n      scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n\n      expect(scroll.scroller.scrollTo).toBeCalledWith(\n        20,\n        20,\n        0,\n        expect.anything()\n      )\n      expect(slide.pages.getInitialPage).toHaveBeenCalled()\n\n      // case 2 content has only no child\n      let { wrapper: wrapper2, content: content2 } = createSlideElements(1)\n      scroll = new BScroll(wrapper2, {})\n      slide = new Slide(scroll)\n      content2.removeChild(content2.children[0])\n      scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n      expect(slide.pages.refresh).not.toBeCalled()\n\n      // case3 common refresh\n      let { wrapper: wrapper3 } = createSlideElements(1)\n      scroll = new BScroll(wrapper3, {})\n      slide = new Slide(scroll)\n      const spyFn2 = jest.spyOn(Slide.prototype, 'startPlay')\n      const position = {}\n      scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n      scroll.hooks.trigger(\n        scroll.hooks.eventTypes.beforeInitialScrollTo,\n        position\n      )\n\n      expect(slide.pages.refresh).toBeCalled()\n      expect(slide.pages.getInitialPage).toBeCalled()\n      expect(position).toMatchObject({\n        x: 10,\n        y: 10,\n      })\n      expect(spyFn2).toBeCalled()\n    })\n  })\n\n  describe('tap into scroller hooks', () => {\n    it('should call modifyScrollMetaHandler when scroller.hooks.momentum triggered', () => {\n      const scrollMeta = {\n        newX: -1,\n        newY: -1,\n        time: 0,\n      }\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.momentum,\n        scrollMeta\n      )\n      expect(scrollMeta.newX).toBe(0)\n      expect(scrollMeta.newY).toBe(0)\n      expect(scrollMeta.time).toBe(400)\n\n      expect(slide.pages.getPageByDirection).toBeCalledWith(\n        {\n          x: 0,\n          y: 0,\n          pageX: 0,\n          pageY: 0,\n        },\n        0,\n        0\n      )\n    })\n    it('should start a new autoPlay timer when scroller.hooks.checkClick triggered', () => {\n      const spyFn = jest.spyOn(Slide.prototype, 'startPlay')\n      slide = new Slide(scroll)\n\n      scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.checkClick)\n      expect(spyFn).toBeCalled()\n    })\n\n    it('should go to next/pre page scroller.hooks.flickHandler triggered', () => {\n      slide = new Slide(scroll)\n\n      scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.flick)\n\n      expect(scroll.scroller.scrollTo).toBeCalledWith(\n        20,\n        20,\n        400,\n        expect.anything()\n      )\n    })\n\n    it('should dispatch slideWillChange event when scroller.hooks.scroll triggered', () => {\n      slide = new Slide(scroll)\n      let pageX = 0\n\n      scroll.on(scroll.eventTypes.slideWillChange, (Page: any) => {\n        pageX = Page.pageX\n      })\n      slide.pages.getWillChangedPage = jest.fn().mockImplementation(() => {\n        return {\n          pageX: 1,\n          pageY: 0,\n          x: 0,\n          y: 0,\n        }\n      })\n      scroll.hooks.trigger(scroll.hooks.eventTypes.refresh)\n      scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.scroll, {\n        x: 0,\n        y: 0,\n      })\n      expect(pageX).toBe(0)\n\n      scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.scroll, {\n        x: 200,\n        y: 0,\n      })\n      expect(pageX).toBe(1)\n    })\n\n    it('scroller.hooks.beforeRefresh', () => {\n      const { wrapper } = createSlideElements()\n      const scroll = new BScroll(wrapper, {})\n      const slide = new Slide(scroll)\n\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.beforeRefresh\n      )\n      expect(scroll.scroller.content.children.length).toBe(5)\n\n      // slideContent changed\n      slide.prevContent = document.createElement('p')\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.beforeRefresh\n      )\n      expect(scroll.scroller.content.children.length).toBe(7)\n\n      // many pages reduce to one page\n      const mockFn1 = jest.fn()\n      slide.initialised = true\n      slide.prevContent = wrapper.children[0] as HTMLElement\n      const childrenEl = [...Array.from(scroll.scroller.content.children)]\n      for (let i = 1; i < 5; i++) {\n        scroll.scroller.content.removeChild(childrenEl[i])\n      }\n      scroll.on(scroll.eventTypes.scrollEnd, mockFn1)\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.beforeRefresh\n      )\n      scroll.trigger(scroll.eventTypes.scrollEnd, { x: 0, y: 0 })\n      expect(scroll.scroller.content.children.length).toBe(1)\n      expect(mockFn1).not.toBeCalled()\n\n      // one page increases to many page\n      const mockFn2 = jest.fn()\n      scroll.scroller.content.appendChild(document.createElement('div'))\n      scroll.on(scroll.eventTypes.scrollEnd, mockFn1)\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.beforeRefresh\n      )\n      scroll.trigger(scroll.eventTypes.scrollEnd, { x: 0, y: 0 })\n      expect(scroll.scroller.content.children.length).toBe(4)\n      expect(mockFn2).not.toBeCalled()\n      while (scroll.scroller.content.children.length) {\n        const len = scroll.scroller.content.children.length\n        scroll.scroller.content.removeChild(\n          scroll.scroller.content.children[len - 1]\n        )\n      }\n      // reset loop changed status\n      scroll.scroller.hooks.trigger(\n        scroll.scroller.hooks.eventTypes.beforeRefresh\n      )\n    })\n  })\n\n  it('should destroy all events', () => {\n    scroll.scroller.hooks.trigger(\n      scroll.scroller.hooks.eventTypes.beforeRefresh\n    )\n    scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n\n    expect(scroll.scroller.content.children.length).toBe(3)\n    expect(scroll.events['beforeScrollStart'].length).toBe(0)\n    expect(scroll.events['scrollEnd'].length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "packages/slide/src/constants.ts",
    "content": "export const BASE_PAGE = {\n  pageX: 0,\n  pageY: 0,\n  x: 0,\n  y: 0,\n}\n\nexport const DEFAULT_PAGE_STATS = {\n  x: 0,\n  y: 0,\n  width: 0,\n  height: 0,\n  cx: 0,\n  cy: 0,\n}\n"
  },
  {
    "path": "packages/slide/src/index.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport {\n  between,\n  prepend,\n  removeChild,\n  ease,\n  extend,\n  EaseItem,\n  Direction,\n  warn,\n  EventEmitter,\n} from '@better-scroll/shared-utils'\nimport SlidePages, { Page, Position } from './SlidePages'\nimport propertiesConfig from './propertiesConfig'\nimport { BASE_PAGE } from './constants'\n\nexport interface SlideConfig {\n  loop: boolean\n  threshold: number\n  speed: number\n  easing: {\n    style: string\n    fn: (t: number) => number\n  }\n  listenFlick: boolean\n  autoplay: boolean\n  interval: number\n  startPageXIndex: number\n  startPageYIndex: number\n}\nexport type SlideOptions = Partial<SlideConfig> | true\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    slide?: SlideOptions\n  }\n  interface CustomAPI {\n    slide: PluginAPI\n  }\n}\n\ninterface PluginAPI {\n  next(time?: number, easing?: EaseItem): void\n  prev(time?: number, easing?: EaseItem): void\n  goToPage(x: number, y: number, time?: number, easing?: EaseItem): void\n  getCurrentPage(): Page\n  startPlay(): void\n  pausePlay(): void\n}\n\nconst samePage = (p1: Page, p2: Page) => {\n  return p1.pageX === p2.pageX && p1.pageY === p2.pageY\n}\n\ntype styleConfiguration = {\n  direction: 'scrollX' | 'scrollY'\n  sizeType: 'offsetWidth' | 'offsetHeight'\n  styleType: 'width' | 'height'\n}\nexport default class Slide implements PluginAPI {\n  static pluginName = 'slide'\n  pages: SlidePages\n  options: SlideConfig\n  initialised: boolean\n  contentChanged: boolean\n  prevContent: HTMLElement\n  exposedPage: Page\n  private cachedClonedPageDOM: HTMLElement[] = []\n  private oneToMorePagesInLoop: boolean\n  private moreToOnePageInLoop: boolean\n  private thresholdX: number\n  private thresholdY: number\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  private resetLooping = false\n  private willChangeToPage: Page\n  private autoplayTimer: number = 0\n  constructor(public scroll: BScroll) {\n    if (!this.satisfyInitialization()) {\n      return\n    }\n    this.init()\n  }\n\n  private satisfyInitialization(): boolean {\n    if (this.scroll.scroller.content.children.length <= 0) {\n      warn(\n        `slide need at least one slide page to be initialised.` +\n          `please check your DOM layout.`\n      )\n      return false\n    }\n    return true\n  }\n\n  init() {\n    this.willChangeToPage = extend({}, BASE_PAGE)\n\n    this.handleBScroll()\n    this.handleOptions()\n    this.handleHooks()\n    this.createPages()\n  }\n\n  private createPages() {\n    this.pages = new SlidePages(this.scroll, this.options)\n  }\n\n  private handleBScroll() {\n    this.scroll.registerType(['slideWillChange', 'slidePageChanged'])\n    this.scroll.proxy(propertiesConfig)\n  }\n\n  private handleOptions() {\n    const userOptions = (this.scroll.options.slide === true\n      ? {}\n      : this.scroll.options.slide) as Partial<SlideConfig>\n    const defaultOptions: SlideConfig = {\n      loop: true,\n      threshold: 0.1,\n      speed: 400,\n      easing: ease.bounce,\n      listenFlick: true,\n      autoplay: true,\n      interval: 3000,\n      startPageXIndex: 0,\n      startPageYIndex: 0,\n    }\n    this.options = extend(defaultOptions, userOptions)\n  }\n\n  private handleLoop(prevSlideContent: HTMLElement) {\n    const { loop } = this.options\n    const slideContent = this.scroll.scroller.content\n    const currentSlidePagesLength = slideContent.children.length\n    // only should respect loop scene\n    if (loop) {\n      if (slideContent !== prevSlideContent) {\n        this.resetLoopChangedStatus()\n        this.removeClonedSlidePage(prevSlideContent)\n        currentSlidePagesLength > 1 &&\n          this.cloneFirstAndLastSlidePage(slideContent)\n      } else {\n        // many pages reduce to one page\n        if (currentSlidePagesLength === 3 && this.initialised) {\n          this.removeClonedSlidePage(slideContent)\n          this.moreToOnePageInLoop = true\n          this.oneToMorePagesInLoop = false\n        } else if (currentSlidePagesLength > 1) {\n          // one page increases to many page\n          if (this.initialised && this.cachedClonedPageDOM.length === 0) {\n            this.oneToMorePagesInLoop = true\n            this.moreToOnePageInLoop = false\n          } else {\n            this.removeClonedSlidePage(slideContent)\n            this.resetLoopChangedStatus()\n          }\n          this.cloneFirstAndLastSlidePage(slideContent)\n        } else {\n          this.resetLoopChangedStatus()\n        }\n      }\n    }\n  }\n\n  private resetLoopChangedStatus() {\n    this.moreToOnePageInLoop = false\n    this.oneToMorePagesInLoop = false\n  }\n\n  private handleHooks() {\n    const scrollHooks = this.scroll.hooks\n    const scrollerHooks = this.scroll.scroller.hooks\n    const { listenFlick } = this.options\n    this.prevContent = this.scroll.scroller.content\n\n    this.hooksFn = []\n    // scroll\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.beforeScrollStart,\n      this.pausePlay\n    )\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.scrollEnd,\n      this.modifyCurrentPage\n    )\n    this.registerHooks(\n      this.scroll,\n      this.scroll.eventTypes.scrollEnd,\n      this.startPlay\n    )\n    // for mousewheel event\n    if (this.scroll.eventTypes.mousewheelMove) {\n      this.registerHooks(\n        this.scroll,\n        this.scroll.eventTypes.mousewheelMove,\n        () => {\n          // prevent default action of mousewheelMove\n          return true\n        }\n      )\n      this.registerHooks(\n        this.scroll,\n        this.scroll.eventTypes.mousewheelEnd,\n        (delta: { directionX: number; directionY: number }) => {\n          if (\n            delta.directionX === Direction.Positive ||\n            delta.directionY === Direction.Positive\n          ) {\n            this.next()\n          }\n          if (\n            delta.directionX === Direction.Negative ||\n            delta.directionY === Direction.Negative\n          ) {\n            this.prev()\n          }\n        }\n      )\n    }\n\n    // scrollHooks\n    this.registerHooks(\n      scrollHooks,\n      scrollHooks.eventTypes.refresh,\n      this.refreshHandler\n    )\n    this.registerHooks(\n      scrollHooks,\n      scrollHooks.eventTypes.destroy,\n      this.destroy\n    )\n\n    // scroller\n    this.registerHooks(\n      scrollerHooks,\n      scrollerHooks.eventTypes.beforeRefresh,\n      () => {\n        this.handleLoop(this.prevContent)\n        this.setSlideInlineStyle()\n      }\n    )\n    this.registerHooks(\n      scrollerHooks,\n      scrollerHooks.eventTypes.momentum,\n      this.modifyScrollMetaHandler\n    )\n    this.registerHooks(\n      scrollerHooks,\n      scrollerHooks.eventTypes.scroll,\n      this.scrollHandler\n    )\n    // a click operation will clearTimer, so restart a new one\n    this.registerHooks(\n      scrollerHooks,\n      scrollerHooks.eventTypes.checkClick,\n      this.startPlay\n    )\n    if (listenFlick) {\n      this.registerHooks(\n        scrollerHooks,\n        scrollerHooks.eventTypes.flick,\n        this.flickHandler\n      )\n    }\n  }\n\n  startPlay() {\n    const { interval, autoplay } = this.options\n    if (autoplay) {\n      clearTimeout(this.autoplayTimer)\n      this.autoplayTimer = window.setTimeout(() => {\n        this.next()\n      }, interval)\n    }\n  }\n\n  pausePlay() {\n    if (this.options.autoplay) {\n      clearTimeout(this.autoplayTimer)\n    }\n  }\n\n  private setSlideInlineStyle() {\n    const styleConfigurations: styleConfiguration[] = [\n      {\n        direction: 'scrollX',\n        sizeType: 'offsetWidth',\n        styleType: 'width',\n      },\n      {\n        direction: 'scrollY',\n        sizeType: 'offsetHeight',\n        styleType: 'height',\n      },\n    ]\n    const {\n      content: slideContent,\n      wrapper: slideWrapper,\n    } = this.scroll.scroller\n    const scrollOptions = this.scroll.options\n\n    styleConfigurations.forEach(({ direction, sizeType, styleType }) => {\n      // wanna scroll in this direction\n      if (scrollOptions[direction]) {\n        const size = slideWrapper[sizeType]\n        const children = slideContent.children\n        const length = children.length\n        for (let i = 0; i < length; i++) {\n          const slidePageDOM = children[i] as HTMLElement\n          slidePageDOM.style[styleType] = size + 'px'\n        }\n        slideContent.style[styleType] = size * length + 'px'\n      }\n    })\n  }\n\n  next(time?: number, easing?: EaseItem) {\n    const { pageX, pageY } = this.pages.nextPageIndex()\n    this.goTo(pageX, pageY, time, easing)\n  }\n  prev(time?: number, easing?: EaseItem) {\n    const { pageX, pageY } = this.pages.prevPageIndex()\n    this.goTo(pageX, pageY, time, easing)\n  }\n\n  goToPage(pageX: number, pageY: number, time?: number, easing?: EaseItem) {\n    const pageIndex = this.pages.getValidPageIndex(pageX, pageY)\n    this.goTo(pageIndex.pageX, pageIndex.pageY, time, easing)\n  }\n\n  getCurrentPage(): Page {\n    return this.exposedPage || this.pages.getInitialPage(false, true)\n  }\n\n  setCurrentPage(page: Page) {\n    this.pages.setCurrentPage(page)\n    this.exposedPage = this.pages.getExposedPage(page)\n  }\n\n  nearestPage(x: number, y: number): Page {\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    const {\n      maxScrollPos: maxScrollPosX,\n      minScrollPos: minScrollPosX,\n    } = scrollBehaviorX\n    const {\n      maxScrollPos: maxScrollPosY,\n      minScrollPos: minScrollPosY,\n    } = scrollBehaviorY\n\n    return this.pages.getNearestPage(\n      between(x, maxScrollPosX, minScrollPosX),\n      between(y, maxScrollPosY, minScrollPosY)\n    )\n  }\n\n  private satisfyThreshold(x: number, y: number): boolean {\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n\n    let satisfied = true\n    if (\n      Math.abs(x - scrollBehaviorX.absStartPos) <= this.thresholdX &&\n      Math.abs(y - scrollBehaviorY.absStartPos) <= this.thresholdY\n    ) {\n      satisfied = false\n    }\n\n    return satisfied\n  }\n\n  private refreshHandler(content: HTMLElement) {\n    if (!this.satisfyInitialization()) {\n      return\n    }\n    this.pages.refresh()\n    this.computeThreshold()\n\n    const contentChanged = (this.contentChanged = this.prevContent !== content)\n    if (contentChanged) {\n      this.prevContent = content\n    }\n    const initPage = this.pages.getInitialPage(\n      this.oneToMorePagesInLoop || this.moreToOnePageInLoop,\n      contentChanged || !this.initialised\n    )\n    if (this.initialised) {\n      this.goTo(initPage.pageX, initPage.pageY, 0)\n    } else {\n      this.registerHooks(\n        this.scroll.hooks,\n        this.scroll.hooks.eventTypes.beforeInitialScrollTo,\n        (position: { x: number; y: number }) => {\n          this.initialised = true\n          position.x = initPage.x\n          position.y = initPage.y\n        }\n      )\n    }\n\n    this.startPlay()\n  }\n\n  private computeThreshold() {\n    const threshold = this.options.threshold\n\n    // Integer\n    if (threshold % 1 === 0) {\n      this.thresholdX = threshold\n      this.thresholdY = threshold\n    } else {\n      // decimal\n      const { width, height } = this.pages.getPageStats()\n      this.thresholdX = Math.round(width * threshold)\n      this.thresholdY = Math.round(height * threshold)\n    }\n  }\n\n  private cloneFirstAndLastSlidePage(slideContent: HTMLElement) {\n    const children = slideContent.children\n    const preprendDOM = children[children.length - 1].cloneNode(\n      true\n    ) as HTMLElement\n    const appendDOM = children[0].cloneNode(true) as HTMLElement\n    prepend(preprendDOM, slideContent)\n    slideContent.appendChild(appendDOM)\n    this.cachedClonedPageDOM = [preprendDOM, appendDOM]\n  }\n\n  private removeClonedSlidePage(slideContent: HTMLElement) {\n    // maybe slideContent has removed from DOM Tree\n    const slidePages = (slideContent && slideContent.children) || []\n    if (slidePages.length) {\n      this.cachedClonedPageDOM.forEach((el) => {\n        removeChild(slideContent, el)\n      })\n    }\n    this.cachedClonedPageDOM = []\n  }\n\n  private modifyCurrentPage(point: Position) {\n    const {\n      pageX: prevExposedPageX,\n      pageY: prevExposedPageY,\n    } = this.getCurrentPage()\n    const newPage = this.nearestPage(point.x, point.y)\n    this.setCurrentPage(newPage)\n\n    /* istanbul ignore if */\n    if (this.contentChanged) {\n      this.contentChanged = false\n      return true\n    }\n\n    const {\n      pageX: currentExposedPageX,\n      pageY: currentExposedPageY,\n    } = this.getCurrentPage()\n    this.pageWillChangeTo(newPage)\n\n    // loop is true, and one page becomes many pages when call bs.refresh\n    if (this.oneToMorePagesInLoop) {\n      this.oneToMorePagesInLoop = false\n      return true\n    }\n\n    // loop is true, and many page becomes one page when call bs.refresh\n    // if prevPage > 0, dispatch slidePageChanged and scrollEnd events\n    /* istanbul ignore if */\n    if (\n      this.moreToOnePageInLoop &&\n      prevExposedPageX === 0 &&\n      prevExposedPageY === 0\n    ) {\n      this.moreToOnePageInLoop = false\n      return true\n    }\n\n    if (\n      prevExposedPageX !== currentExposedPageX ||\n      prevExposedPageY !== currentExposedPageY\n    ) {\n      // only trust pageX & pageY when loop is true\n      const page = this.pages.getExposedPageByPageIndex(\n        currentExposedPageX,\n        currentExposedPageY\n      )\n      this.scroll.trigger(this.scroll.eventTypes.slidePageChanged, page)\n    }\n\n    // triggered by resetLoop\n    if (this.resetLooping) {\n      this.resetLooping = false\n      return\n    }\n\n    const changePage = this.pages.resetLoopPage()\n    if (changePage) {\n      this.resetLooping = true\n      this.goTo(changePage.pageX, changePage.pageY, 0)\n      // stop user's scrollEnd\n      // since it is a seamless scroll\n      return true\n    }\n  }\n\n  private goTo(pageX: number, pageY: number, time?: number, easing?: EaseItem) {\n    const newPage = this.pages.getInternalPage(pageX, pageY)\n    const scrollEasing = easing || this.options.easing || ease.bounce\n    const { x, y } = newPage\n\n    const deltaX = x - this.scroll.scroller.scrollBehaviorX.currentPos\n    const deltaY = y - this.scroll.scroller.scrollBehaviorY.currentPos\n    /* istanbul ignore if */\n    if (!deltaX && !deltaY) {\n      this.scroll.scroller.togglePointerEvents(true)\n      return\n    }\n    time = time === undefined ? this.getEaseTime(deltaX, deltaY) : time\n    this.scroll.scroller.scrollTo(x, y, time, scrollEasing)\n  }\n\n  private flickHandler() {\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    const {\n      currentPos: currentPosX,\n      startPos: startPosX,\n      direction: directionX,\n    } = scrollBehaviorX\n    const {\n      currentPos: currentPosY,\n      startPos: startPosY,\n      direction: directionY,\n    } = scrollBehaviorY\n    const { pageX, pageY } = this.pages.currentPage\n\n    let time = this.getEaseTime(\n      currentPosX - startPosX,\n      currentPosY - startPosY\n    )\n    this.goTo(pageX + directionX, pageY + directionY, time)\n  }\n\n  private getEaseTime(deltaX: number, deltaY: number): number {\n    return (\n      this.options.speed ||\n      Math.max(\n        Math.max(\n          Math.min(Math.abs(deltaX), 1000),\n          Math.min(Math.abs(deltaY), 1000)\n        ),\n        300\n      )\n    )\n  }\n\n  private modifyScrollMetaHandler(scrollMeta: {\n    newX: number\n    newY: number\n    time: number\n    [key: string]: any\n  }) {\n    const { scrollBehaviorX, scrollBehaviorY, animater } = this.scroll.scroller\n    const newX = scrollMeta.newX\n    const newY = scrollMeta.newY\n\n    const newPage =\n      this.satisfyThreshold(newX, newY) || animater.forceStopped\n        ? this.pages.getPageByDirection(\n            this.nearestPage(newX, newY),\n            scrollBehaviorX.direction,\n            scrollBehaviorY.direction\n          )\n        : this.pages.currentPage\n\n    scrollMeta.time = this.getEaseTime(\n      scrollMeta.newX - newPage.x,\n      scrollMeta.newY - newPage.y\n    )\n    scrollMeta.newX = newPage.x\n    scrollMeta.newY = newPage.y\n    scrollMeta.easing = this.options.easing || ease.bounce\n  }\n\n  private scrollHandler({ x, y }: Position) {\n    if (this.satisfyThreshold(x, y)) {\n      const newPage = this.nearestPage(x, y)\n      this.pageWillChangeTo(newPage)\n    }\n  }\n\n  private pageWillChangeTo(newPage: Page) {\n    const changeToPage = this.pages.getWillChangedPage(newPage)\n    if (!samePage(this.willChangeToPage, changeToPage)) {\n      this.willChangeToPage = changeToPage\n      this.scroll.trigger(\n        this.scroll.eventTypes.slideWillChange,\n        this.willChangeToPage\n      )\n    }\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  destroy() {\n    const slideContent = this.scroll.scroller.content\n    const { loop, autoplay } = this.options\n    if (loop) {\n      this.removeClonedSlidePage(slideContent)\n    }\n    if (autoplay) {\n      clearTimeout(this.autoplayTimer)\n    }\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      if (hooks.eventTypes[hooksName]) {\n        hooks.off(hooksName, handlerFn)\n      }\n    })\n    this.hooksFn.length = 0\n  }\n}\n"
  },
  {
    "path": "packages/slide/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.slide'\nconst propertiesMap = [\n  {\n    key: 'next',\n    name: 'next',\n  },\n  {\n    key: 'prev',\n    name: 'prev',\n  },\n  {\n    key: 'goToPage',\n    name: 'goToPage',\n  },\n  {\n    key: 'getCurrentPage',\n    name: 'getCurrentPage',\n  },\n  {\n    key: 'startPlay',\n    name: 'startPlay',\n  },\n  {\n    key: 'pausePlay',\n    name: 'pausePlay',\n  },\n]\n\nexport default propertiesMap.map((item) => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`,\n  }\n})\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/components/demo.vue",
    "content": "<template>\n  <div class=\"demo-wrap\">\n    <div class=\"demo-nav\">\n      <i class=\"demo-nav-btn icon-code\" @click='toggleCode'>\n        <!-- more details in https://github.com/tailwindlabs/heroicons -->\n        <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" :stroke=\"codeIconColor\">\n          <path stroke-linecap=\"round\" stroke-linejoin=\"round\" :stroke-width=\"codeIconWidth\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\" />\n        </svg>\n      </i>\n      <v-popover v-if=\"!hideQrcode\" placement='right' :offset ='10' trigger='click'>\n        <i class=\"demo-nav-btn icon-qrcode\">\n          <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"#666\">\n            <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z\" />\n          </svg>\n        </i>\n        <template slot=\"popover\">\n          <qr-code\n            :url=\"fullQrcodeUrl\"\n            :size=\"100\"\n            error-level=\"L\">\n          </qr-code>\n        </template>\n      </v-popover>\n      <i class=\"demo-nav-btn icon-preview\" @click='toPreview'>\n        <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"#666\">\n          <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\" />\n        </svg>\n      </i>\n    </div>\n    <div class=\"demo-code\" v-show=\"showCode\">\n        <div class=\"demo-code-nav\">\n          <button\n            v-for=\"(config, index) in codeNavConfigs\"\n            :class=\"['demo-code-btn', codeNavIndex === index ? 'active' : '']\"\n            @click=\"codeNavBtnHandler(index)\">{{config.title}}</button>\n        </div>\n        <div class=\"demo-code-content\">\n          <div\n            class=\"demo-code-item\"\n            v-for=\"(config, index) in codeNavConfigs\"\n            v-show=\"codeNavIndex === index\">\n            <slot :name=\"config.slotName\"></slot>\n          </div>\n          <i class=\"demo-code-content-copy\" @click='copyCode'>\n            <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"#ccc\">\n              <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n            </svg>\n          </i>\n          <transition name=\"slide-fade\">\n            <span class=\"demo-code-content-copied\" v-if=\"copied\">Copied</span>\n          </transition>\n        </div>\n    </div>\n    <div class=\"demo-main\">\n      <div class=\"demo-component-wrap\">\n        <slot name=\"demo\"></slot>\n      </div>\n    </div>\n  </div>\n</template>\n<script>\nimport QrCode from './qrcode.vue'\n//TODO replate url when publich 2.0\nconst FALLBACK_URL = 'https://ustbhuangyi.github.io/better-scroll/#/'\nconst BASE_URL = process.env.NODE_ENV === 'production' ?\n  'https://better-scroll.github.io/examples/#/' :\n   `http://${LOCAL_IP}:8932/#/`\n\nexport default {\n  name: 'demo',\n  components: { QrCode },\n  props: {\n    qrcodeUrl: {\n      type: String,\n      default: ''\n    },\n    renderCode: {\n      type: Boolean,\n      default: false\n    },\n    hideQrcode: {\n      type: Boolean,\n      default: false\n    }\n  },\n  computed: {\n    fullQrcodeUrl() {\n      return `${BASE_URL}${this.qrcodeUrl}`\n    },\n    codeIconColor() {\n      return this.showCode ? '#000' : '#666'\n    },\n    codeIconWidth() {\n      return this.showCode ? 3 : 2\n    }\n  },\n  data() {\n    return {\n      showCode: this.renderCode,\n      copied: false,\n      codeNavIndex: 0,\n      codeNavConfigs: []\n    }\n  },\n  created () {\n    // dynamic generating demo code configs\n    this.makeCodeNavConfigs()\n  },\n  methods: {\n    toggleCode() {\n      this.showCode = !this.showCode\n    },\n    toPreview() {\n      window.open(`${BASE_URL}${this.qrcodeUrl}`)\n    },\n    copyCode () {\n      const pre = this.$el.querySelectorAll('pre')[this.codeNavIndex]\n      pre.setAttribute('contenteditable', 'true')\n      pre.focus()\n      document.execCommand('selectAll', false, null)\n      this.copied = document.execCommand('copy')\n      pre.removeAttribute('contenteditable')\n      setTimeout(() => { this.copied = false }, 1000)\n    },\n    codeNavBtnHandler (i) {\n      this.codeNavIndex = i\n    },\n    makeCodeNavConfigs () {\n      const slots = this.$slots\n      const keys = ['code-template', 'code-script', 'code-style']\n      const configs = []\n      let title\n      keys.forEach(key => {\n        if (slots[key]) {\n          title = key.replace('code-', '').replace(/^\\S/, s => s.toUpperCase())\n          configs.push({\n            title,\n            slotName: key\n          })\n        }\n      })\n      this.codeNavConfigs = configs\n    }\n  }\n}\n</script>\n<style lang=\"stylus\">\n.demo-wrap\n  margin: 20px 0\n  box-shadow 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12)\n  text-decoration: none\n  .demo-nav\n    display flex\n    flex-direction row\n    flex-wrap nowrap\n    align-items center\n    height 48px\n    background-color #f5f5f5\n    color rgba(0,0,0,0.87)\n    .demo-nav-btn\n      cursor pointer\n      margin-left 8px\n      font-size 22px\n      font-weight bold\n      align-items center\n      outline 0\n      border 0\n      background-size 100%\n      width 26px\n      height 26px\n      line-height 26px\n      display inline-block\n  .demo-code\n    background-color rgb(45, 45, 45)\n    border-color rgb(45, 45, 45)\n    color #fff\n    pre\n      margin 0!important\n    .demo-code-nav\n      display flex\n      flex-direction row\n      flex-wrap nowrap\n      justify-content flex-start\n      align-items center\n      height 48px\n      border-bottom: 1px solid rgba(255,255,255,0.12)\n      .demo-code-btn\n        display block\n        padding 0 16px\n        margin 0 8px\n        height 32px\n        line-height 32px\n        font-size 16px\n        border-radius 28px\n        outline none\n        cursor pointer\n        background-color transparent\n        color white\n        border none\n        &.active\n          background #f5f5f5\n          color black\n          transition all .3s ease\n    .demo-code-content\n      max-height 350px\n      overflow-y auto\n      position relative\n      .demo-code-content-copy\n        cursor pointer\n        position absolute\n        top 30px\n        right 10px\n        z-index 100\n        width 26px\n        height 26px\n      .demo-code-content-copied\n          position absolute\n          top 30px\n          right 50px\n          z-index 100\n  .demo-main\n    padding 16px\n    background #fff\n    .demo-component-wrap\n      margin 0 auto\n      max-width 350px\n      height 400px\n      background #f1f2f3\n      overflow hidden\n\n.slide-fade-enter-active {\n  transition: all .3s ease;\n}\n.slide-fade-leave-active {\n  transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);\n}\n.slide-fade-enter, .slide-fade-leave-to {\n  transform: translateX(10px);\n  opacity: 0;\n}\n</style>\n\n\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/components/qrcode.vue",
    "content": "<template>\n    <div ref=\"qrcode\"></div>\n</template>\n\n<script>\nexport default {\n  props: {\n      url: {\n        type: String,\n        required: true\n      },\n      size: {\n        type: Number,\n        required: false,\n        default: 256\n      },\n      color: {\n        type: String,\n        required: false,\n        default: '#000'\n      },\n      bgColor: {\n        type: String,\n        required: false,\n        default: '#FFF'\n      },\n      errorLevel: {\n          type: String, \n          validator: function (value) {\n              return value === 'L' || value === 'M' || value === 'Q' || value === 'H'\n          }, \n          required: false, \n          default: 'H'\n      }\n  },\n  watch: {\n      url: () => {\n        this.clear()\n        this.makeCode(this.url)\n      }\n  },\n  data() {\n      return{\n        qrCode: null\n      }\n  },\n  mounted() {\n    import('qrcode-js-package/qrcode.js').then(res => {\n      const QRCode = res.default\n      this.qrCode = new QRCode(this.$refs.qrcode, {\n        text: this.url,\n        width: this.size,\n        height: this.size,\n        colorDark : this.color,\n        colorLight : this.bgColor,\n        correctLevel : QRCode.CorrectLevel[this.errorLevel]\n      })\n    })\n  },\n  methods: {\n    clear() {\n      if (!this.qrcode) {\n        return\n      }\n      this.qrCode.clear()\n    },\n    makeCode(url) {\n      if (!this.qrcode) {\n        return\n      }\n      this.qrCode.makeCode(url)\n    }\n  }\n}\n</script>"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/config.js",
    "content": "const path = require('path')\nconst os = require('os')\nconst fs = require('fs')\n\nfunction resolve(p) {\n  return path.resolve(__dirname, '../../../', p)\n}\n\nmodule.exports = {\n  base: '/docs/',\n  publicPath: '/docs/',\n  cache: false,\n  head: [\n    ['link', { rel: 'shortcut icon', href: '/assets/bs.ico', type: 'images/x-icon' }],\n    ['script', { src: 'https://www.googletagmanager.com/gtag/js?id=G-7E85TW7P27' }],\n    ['script', { type: 'text/javascript' }, `\n      window.dataLayer = window.dataLayer || [];\n      function gtag(){dataLayer.push(arguments);}\n      gtag('js', new Date());\n      gtag('config', 'G-7E85TW7P27');\n    `]\n  ],\n  locales: {\n    '/en-US/': {\n      lang: 'en-US',\n      title: 'BetterScroll 2.0',\n      description: 'Make Scroll Perfect'\n    },\n    '/zh-CN/': {\n      lang: 'zh-CN',\n      title: 'BetterScroll 2.0',\n      description: 'Make Scroll Perfect'\n    }\n  },\n  themeConfig: {\n    repo: 'ustbhuangyi/better-scroll',\n    docsBranch: 'dev',\n    docsDir: 'packages/vuepress-docs/docs',\n    editLinks: true,\n    smoothScroll: true,\n    algolia: {\n      apiKey: '93916bfd4dd5ed93f9b7c0d9c9854404',\n      indexName: 'better-scroll'\n    },\n    logo: 'https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg',\n    locales: {\n      '/zh-CN/': {\n        label: '简体中文',\n        selectText: '选择语言',\n        nav: require('./nav/zh-CN.js'),\n        lastUpdated: '上次更新',\n        editLinkText: '在 GitHub 上编辑此页',\n        sidebar: {\n          '/zh-CN/guide/': require('./sidebar/guide.js')('zh-CN'),\n          '/zh-CN/plugins/': require('./sidebar/plugins.js')('zh-CN'),\n          '/zh-CN/FAQ/': require('./sidebar/FAQ.js')('zh-CN')\n        }\n      },\n      '/en-US/': {\n        label: 'English',\n        selectText: 'Languages',\n        nav: require('./nav/en-US.js'),\n        lastUpdated: 'Last Updated',\n        editLinkText: 'Edit this page on GitHub',\n        sidebar: {\n          '/en-US/guide/': require('./sidebar/guide.js')('en-US'),\n          '/en-US/plugins/': require('./sidebar/plugins.js')('en-US'),\n          '/en-US/FAQ/': require('./sidebar/FAQ.js')('en-US')\n        }\n      }\n    }\n  },\n  configureWebpack: {\n    module: {\n      rules: [\n        {\n          test: /\\.tsx?$/,\n          loader: 'ts-loader',\n          options: {\n            transpileOnly: true\n          }\n        }\n      ]\n    },\n    resolve: {\n      extensions: ['.js', '.vue', '.json', '.ts'],\n      alias: {\n        common: resolve('examples/common')\n      }\n    }\n  },\n  define: {\n    LOCAL_IP: getIp()\n  },\n  plugins: [\n    ['@vuepress/back-to-top', true],\n    [\n      '@vuepress/register-components',\n      {\n        componentsDir: resolve('examples/vue/components')\n      }\n    ],\n    [\n      '@vuepress/medium-zoom',\n      {\n        selector: '[data-zoomable]',\n        // medium-zoom options here\n        // See: https://github.com/francoischalifour/medium-zoom#options\n        options: {\n          margin: 16\n        }\n      }\n    ],\n    require('./plugins/extract-code.js')\n  ]\n}\n\nfunction getIp() {\n  var networks = os.networkInterfaces()\n  var found = '127.0.0.1'\n\n  Object.keys(networks).forEach(function(k) {\n    var n = networks[k]\n    n.forEach(function(addr) {\n      if (addr.family === 'IPv4' && !addr.internal) {\n        found = addr.address\n      }\n    })\n  })\n\n  return found\n}\n\nfunction getPackagesName() {\n  let ret\n  let all = fs.readdirSync(resolve('../packages'))\n  // drop hidden file whose name is startWidth '.'\n  // drop packages which would not be published(eg: examples and docs)\n  ret = all\n    .filter(name => {\n      const isHiddenFile = /^\\./g.test(name)\n      return !isHiddenFile\n    })\n    .filter(name => {\n      const isPrivatePackages = require(resolve(\n        `../packages/${name}/package.json`\n      )).private\n      return !isPrivatePackages\n    })\n    .map(name => {\n      return require(resolve(`../packages/${name}/package.json`)).name\n    })\n\n  return ret\n}\ngetPackagesName().forEach(name => {\n  module.exports.configureWebpack.resolve.alias[\n    name + '$'\n  ] = `${name}/src/index.ts`\n})\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/enhanceApp.js",
    "content": "import './public/assets/stylus/index.styl'\nimport VTooltip from 'v-tooltip'\n\nexport default ({Vue, options, router}) => {\n  // // redirect to /zh-CN/ by default\n  router.addRoutes([{\n    path: '/',\n    redirect: '/zh-CN/'\n  }])\n\n  // TODO Unified management of global components\n  Vue.use(VTooltip)\n}\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/nav/en-US.js",
    "content": "module.exports = [\n  { text: 'Guide', link: '/en-US/guide/' },\n  { text: 'Plugin', link: '/en-US/plugins/' },\n  { text: 'FAQ', link: '/en-US/FAQ/' },\n  { text: 'Discuss', link: 'https://github.com/ustbhuangyi/better-scroll/issues'}\n]\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/nav/zh-CN.js",
    "content": "module.exports = [\n  { text: '指南', link: '/zh-CN/guide/' },\n  { text: '插件', link: '/zh-CN/plugins/' },\n  { text: '常见问题', link: '/zh-CN/FAQ/' },\n  { text: '讨论', link: 'https://github.com/ustbhuangyi/better-scroll/issues'}\n]\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/plugins/extract-code.js",
    "content": "const { fs, path } = require('@vuepress/shared-utils')\n\nfunction extractCodeFromVueSFC(md, options = {}) {\n    const root = options.root || path.join(process.cwd(), '../')\n\n    md.core.ruler.after('block', 'extract-code', function parser(state) {\n        let tokens = state.tokens,\n            tok, i;\n\n        // modify html_block\n        for (i = 0; i < tokens.length; i++) {\n            tok = tokens[i];\n            if (tok.type === 'html_block' && isDemoBlock(tok)) {\n                const handledTokens = getHandledTokens(tok)\n                if (handledTokens.length) {\n                    tokens.splice(i, 1, ...handledTokens)\n                    i += handledTokens.length - 1\n                }\n            }\n        }\n\n        function isDemoBlock(token) {\n            let content = token.content\n            let regex = /<\\/demo>$/\n            return regex.test(content.trim())\n        }\n\n        function getHandledTokens(htmlToken, toks) {\n            const tokens = toks || []\n            let content = htmlToken.content\n\n            let reg = /<<<(.*)(\\?.*)?\\b/\n            let matched = content.match(reg)\n            if (!matched) {\n                return tokens\n            }\n            tokens.length && tokens.pop()\n\n            const contentBefore = content.substr(0, matched.index)\n            const contentAfter = content.substr(matched.index + matched[0].length)\n            let token = createToken('html_block', '', 0)\n            token.content = contentBefore\n            tokens.push(token)\n\n            token = createToken('fence', 'code', 0)\n            const rawPath = matched[1].trim().replace(/^@/, root)\n            const filename = rawPath.split(/\\?/).shift()\n            const partName = rawPath.replace(filename, '').substr(1)\n            token.info = filename.split('.').pop()\n            content = fs.existsSync(filename) ? fs.readFileSync(filename).toString() : 'Not found: ' + filename\n            if (partName) {\n                const partReg = new RegExp(`<${partName}[\\\\s\\\\S]*</${partName}>`)\n                const matched = content.match(partReg)\n                if (matched) {\n                    content = matched[0]\n                }\n                // highlight stylus\n                if (partName === 'style') {\n                    token.info = \"styl\"\n                }\n            }\n\n            token.content = content\n            token.markup = '```'\n            tokens.push(token)\n\n            token = createToken('html_block', '', 0)\n            token.content = contentAfter\n            tokens.push(token)\n\n            return getHandledTokens(token, tokens)\n        }\n\n        function createToken(type, tag, nesting) {\n            let token = new state.Token(type, tag, nesting);\n            token.block = true;\n\n            if (nesting < 0) { this.level--; }\n            token.level = this.level;\n            if (nesting > 0) { this.level++; }\n\n            return token;\n        }\n    })\n}\n\nmodule.exports = {\n    name: 'extract-code-plugin',\n    chainMarkdown(config) {\n        config.plugin('extract-code')\n            .use(extractCodeFromVueSFC)\n    }\n}\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/public/assets/stylus/index.styl",
    "content": "div[class~=\"language-styl\"]:before {\n    content: 'stylus';\n}\n.v-popover {\n  line-height: 1\n}\n// for v-tooltip\n.tooltip {\n  display: block !important;\n  z-index: 10000;\n}\n\n.tooltip .tooltip-inner {\n  background: black;\n  color: white;\n  border-radius: 16px;\n  padding: 5px 10px 4px;\n}\n\n.tooltip .popover .popover-inner {\n      background: #f9f9f9;\n      color: black;\n      padding: 10px;\n      border-radius: 5px;\n      box-shadow: 0 5px 30px rgba(black, .1);\n}\n\n.tooltip .popover .popover-arrow {\n  border-color: #f9f9f9\n}\n\n\n.tooltip .tooltip-arrow {\n  width: 0;\n  height: 0;\n  border-style: solid;\n  position: absolute;\n  margin: 5px;\n  border-color: black;\n  z-index: 1;\n}\n\n.tooltip[x-placement^=\"top\"] {\n  margin-bottom: 5px;\n}\n\n.tooltip[x-placement^=\"top\"] .tooltip-arrow {\n  border-width: 5px 5px 0 5px;\n  border-left-color: transparent !important;\n  border-right-color: transparent !important;\n  border-bottom-color: transparent !important;\n  bottom: -5px;\n  left: calc(50% - 5px);\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.tooltip[x-placement^=\"bottom\"] {\n  margin-top: 5px;\n}\n\n.tooltip[x-placement^=\"bottom\"] .tooltip-arrow {\n  border-width: 0 5px 5px 5px;\n  border-left-color: transparent !important;\n  border-right-color: transparent !important;\n  border-top-color: transparent !important;\n  top: -5px;\n  left: calc(50% - 5px);\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.tooltip[x-placement^=\"right\"] {\n  margin-left: 5px;\n}\n\n.tooltip[x-placement^=\"right\"] .tooltip-arrow {\n  border-width: 5px 5px 5px 0;\n  border-left-color: transparent !important;\n  border-top-color: transparent !important;\n  border-bottom-color: transparent !important;\n  left: -5px;\n  top: calc(50% - 5px);\n  margin-left: 0;\n  margin-right: 0;\n}\n\n.tooltip[x-placement^=\"left\"] {\n  margin-right: 5px;\n}\n\n.tooltip[x-placement^=\"left\"] .tooltip-arrow {\n  border-width: 5px 0 5px 5px;\n  border-top-color: transparent !important;\n  border-right-color: transparent !important;\n  border-bottom-color: transparent !important;\n  right: -5px;\n  top: calc(50% - 5px);\n  margin-left: 0;\n  margin-right: 0;\n}\n\n.tooltip.popover .popover-inner {\n  background: #f9f9f9;\n  color: black;\n  padding:10px;\n  border-radius: 5px;\n  box-shadow: 0 5px 10px rgba(black, .3);\n}\n\n.tooltip.popover .popover-arrow {\n  border-color: #f9f9f9;\n}\n\n.tooltip[aria-hidden='true'] {\n  visibility: hidden;\n  opacity: 0;\n  transition: opacity .15s, visibility .15s;\n}\n\n.tooltip[aria-hidden='false'] {\n  visibility: visible;\n  opacity: 1;\n  transition: opacity .15s;\n}\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/sidebar/FAQ.js",
    "content": "const FAQContent = {\n  'zh-CN': '常见问题',\n  'en-US': 'FAQ'\n}\n\nconst getFAQSideBar = function (lang) {\n  return [\n    {\n      title: FAQContent[lang],\n      collapsable: false,\n      children: [\n        '',\n        'diagnosis'\n      ]\n    }\n  ]\n}\nmodule.exports = getFAQSideBar\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/sidebar/guide.js",
    "content": "const guideContent = {\n  'zh-CN': '指南',\n  'en-US': 'Guide'\n}\nconst baseScrollContent = {\n  'zh-CN': '核心滚动',\n  'en-US': 'Base Scroll'\n}\nconst getGuideSideBar = function (lang) {\n  return [\n    {\n      title: guideContent[lang],\n      collapsable: false,\n      children: [\n        '',\n        'how-to-install',\n        'use'\n      ]\n    },\n    {\n      title: baseScrollContent[lang],\n      collapsable: false,\n      children: [\n        'base-scroll',\n        'base-scroll-options',\n        'base-scroll-api'\n      ]\n    }\n  ]\n}\nmodule.exports = getGuideSideBar\n"
  },
  {
    "path": "packages/vuepress-docs/docs/.vuepress/sidebar/plugins.js",
    "content": "const introContent = {\n  'zh-US': '插件介绍',\n  'en-US': 'Plugins Guide'\n}\nconst pluginContent = {\n  'zh-US': '插件',\n  'en-US': 'Plugins'\n}\nconst highPluginContent = {\n  'zh-US': '插件的高阶使用',\n  'en-US': 'High Level Use Of Plugins'\n}\nconst getPluginsSideBar = function (lang) {\n  return [\n    {\n      title: introContent[lang],\n      collapsable: false,\n      children: [\n        '',\n        'how-to-write',\n      ]\n    },\n    {\n      title: pluginContent[lang],\n      collapsable: false,\n      children: [\n        'mouse-wheel',\n        'observe-dom',\n        'observe-image',\n        'pulldown',\n        'pullup',\n        'scroll-bar',\n        'indicators',\n        'slide',\n        'wheel',\n        'zoom',\n        'nested-scroll',\n        'infinity',\n        'movable'\n      ]\n    },\n    // {\n    //   title: highPluginContent[lang],\n    //   collapsable: false,\n    //   children: [\n    //     'compose-plugins'\n    //   ]\n    // }\n  ]\n}\nmodule.exports = getPluginsSideBar\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/FAQ/README.md",
    "content": "# FAQ\n\n### Why can't BetterScroll scroll is failed when initialization?\n\nBetterScroll scrolling principle is that the height/width of the `content` element exceeds the height/width of the `wrapper` element. Also, if your content element contains images of a non-fixed size, you must call the `refresh()` method to ensure that the height is calculated correctly after the image has been loaded. There is also a situation where the form element exists on the page. After the keyboard is popped up, the palatable height of the page is compressed, causing `bs` to not work properly, and the `refresh()` method is still need to be called.\n\n### Why can't the click event in the BetterScroll area be triggered?\n\nBy default, BetterScroll blocks the browser's native click event. If you want the click event to take effect, BetterScroll dispatches a click event and the `_constructed` of the event parameter is true. The configuration items are as follows:\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  click: true\n})\n```\n\n### Why does my BetterScroll listen for the `scroll` hook and the listener doesn't execute?\n\nBetterScroll uses the `probeType` configuration item to decide whether to dispatch the `scroll` hook because there is some performance penalty. When the `probeType` is `2`, the event will be dispatched in real time. When the `probeType` is `3`, the event will be dispatched during the `momentum` animation. The recommended setting is `3`.\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  probeType: 3\n})\n```\n\n### Slide used horizontal scrolling, found that vertical scrolling in the slide area is invalid?\n\nIf you want to keep your browser's native vertical scrolling, you need the following configuration items:\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  eventPassthrough: 'vertical'\n})\n```\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/FAQ/diagnosis.md",
    "content": "# BetterScroll's \"diagnosis\"\n\n### [Question 1] Why can't my BetterScroll work?\n\nThe problem basically lies in the **Height Calculation Error**. First of all, you must have a clear understanding of the scrolling principle of `BetterScroll`. For vertical scrolling, simply the height of the `wrapper` container is greater than the height of the `content` content, and the `translateY` is modified to achieve the purpose of scrolling. The principle of horizontal scrolling is similar. Then the calculation **Scrollable Height** is the logic necessary for `BetterScroll`. The general logic  is:\n\n  1. **Pictures with uncertain sizes**\n\n  - **Reason**\n\n    When js performs a calculation of the scrollable height, the image has not been rendered.\n\n  - **Solution**\n\n    Call `bs.refresh()` inside the callback function of the image's `onload` to ensure that the correct height of the image is calculated before calculating the **Scrollable Height**.\n\n  2. **Vue's keep-alive component**\n\n  - **Scenes**\n\n    Suppose there are two components of A and B wrapped by `keep-alive`, A component uses BetterScroll, does some operation in A component, pops up input keyboard, then enters B component, then returns to A component, `bs` is unable to scroll.\n\n  - **Reason**\n\n    Because Vue's keep-alive's cache and the input keyboard pops up, it compresses the height of the viewable area, causing the previously calculated scrollable height to be incorrect.\n\n  - **Solution**\n\n    You can call `bs.refresh()` on Vue's `activated` hook to recalculate the height or re-instantiate bs.\n\n### [Question 2] Why do brower's vertical scrolling failed after I use BetterScroll to do horizontal scrolling?\n\nBetterScroll provides a feature of `slide`. If you implement a horizontal scrollin, such as `slide`. do vertical scrolling in the `slide` area, you can't bubble to the browser, so you can't manipulate the scroll bar of the native browser.\n\n- **Reason**\n\n  The internal scrolling calculations of BetterScroll exist in the user's interaction. For example, the mobile terminal is the `touchstart/touchmove/touchend` event. The listeners of these events generally have the line `e.preventDefault()`, which will block the browser's default behavior so that the browser's scrollbar cannot be scrolled.\n\n- **Solution**\n\n  Configure the `eventPassthrough` attribute.\n\n  ```js\n    Let bs = new BScroll('.wrapper', {\n      eventPassthrough: 'vertical' // keep vertical native scrolling\n    })\n  ```\n\n### [Question 3] Why can't I pop up a pop-up window after using BetterScroll.\n\n- **Reason**\n\n  **question 2** has been mentioned, it is caused by `e.preventDefault()` in touchstart.\n\n- **Solution**\n\n  Option 1: Configure the `preventDefaultException` property.\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    preventDefaultException: {\n      className: /(^|\\s)test(\\s|$)/\n    }\n  })\n  ```\n\n  `eventDefaultException` can be used to control the `e.preventDefault()` of the `touchstart` and `touchmove` events. If the above regular expression is used to check if the class name of the currently touched target element contains `test`, if passed, Then `e.preventDefault()` will not be called.\n\n  Option 2: Configure the `preventDefault` property.\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    preventDefault: false\n  })\n  ```\n\n  `preventDefault` is set to `false`, there are some side effects, it is generally recommended to use **Option one**.\n\n  :::warning\n  The side effect is that the touch event may bubble up to the document, causing the document to be dragged. At this point you need to listen for the parent or ancestor element of the `wrapper` element, bind them to the touchmove event, and call `e.preventDefault()`.\n  :::\n\n### [Question 4] Why are the listeners for all click events inside BetterScroll content not triggered?\n\n- **Reason**\n\n  Still caused by `e.preventDefault()`. On the mobile side, if you call `e.preventDefault()` inside the logic of `touchstart/touchmove/touchend`, it will prevent the execution of the click event of it and its child elements. Therefore, BetterScroll internally manages the dispatch of the `click` event, you only need the `click` configuration item.\n\n- **Solution**\n\n  Configure the `click` attribute.\n\n  ```js\n    Let bs = new BScroll('.wrapper', {\n      click: true\n    })\n  ```\n\n### [Question 5] Why is the click event dispatched twice when Nesting BetterScroll?\n\n- **Reason**\n\n  As stated in **Question 4**, the BetterScroll dispatches a `click` event internally, and nested scenes must have two or more bs.\n\n- **Solution**\n\n  You can manage the bubbling of events by instantiating inner BetterScroll's `stopPropagation` configuration item, or by instantiating inner BetterScroll's `click` configuration item to prevent multiple triggers of clicks.\n\n  ```js\n  let innerBS = new BScroll('.wrapper', {\n    stopPropagation: true\n  })\n\n  // or\n  let innerBS = new BScroll('.wrapper', {\n    click: false\n  })\n  ```\n\n### [Question 6] Why do I listen to the scroll event of bs, why not execute the callback?\n\n- **Reason**\n\n  BetterScroll does not dispatch the `scroll` event at any time because there is a performance penalty for getting the scroll position of bs. As for whether or not to distribute, it depends on the `probeType` configuration item.\n\n- **Solution**\n\n  ```js\n    Let bs = new BScroll('.div', {\n      probeType: 3 // real-time dispatch\n    })\n  ```\n\n### [Question 7] In two vertically nested bs scenes, why move the inner bs will cause the outer layer to also be scrolled.\n\n- **Reason**\n\n  The internal logic of BetterScroll is in the body of the listener function of the touch event. Since the touch event of the internal bs is triggered, it will naturally bubble to the outer bs.\n\n- **Solution**\n\n  Since you know the reason, there are corresponding solutions. For example, when you scroll the **inner** `bs`, listen for the `scroll` event and call the **outer** `bs.disable()` to disable the **outer** `bs`. When the **inner** `bs` scrolls to the bottom, it means that you need to scroll the **outer** `bs` at this time. At this time, call the **outer** `bs.enable()` to activate the outer layer and call the **inner** `bs.disable(). ` to forbid inner scrolling. In fact, think about it, this interaction is consistent with the nested scrolling behavior of the `Web browser`, except that the browser handles the various scrolling nesting logic for you, and the BetterScroll requires your own dispatched events and exposed APIs to fulfill.\n\n  > The [scroll](https://didi.github.io/cube-ui/example/#/scroll/v-scrolls) component of `cube-ui` gives a solution to this scenario. [Code is here](https://github.com/didi/cube-ui/blob/dev/src/components/scroll/scroll.vue)\n\n### [Question 8] In the vertical bs nesting horizontal bs scene, why does the vertical movement of the horizontal bs area do not cause vertical scrolling of the outer vertical bs?\n\n- **Reason**\n\n  The reason is similar to **Question 2**, because `e.preventDefault()` affects the default scrolling behavior, causing the outer bs to not trigger the touch event.\n\n- **Solution**\n\n  The solution is to configure the `eventPassthrough` property of the inner bs to keep the default native vertical scrolling.\n\n  ```js\n    Let innerBS = new BScroll('.wrapper', {\n      eventPassthrough: 'vertical' // keep vertical native scrolling\n    })\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/README.md",
    "content": "---\nhome: true\nheroText: BetterScroll 2.0\nactionText: Getting Started →\nactionLink: /en-US/guide/\nfeatures:\n- title: Smooth scrolling effect\n  details:  Aimed at solving scrolling on the mobile side (PC supported already).\n- title: Zero dependence\n  details: Based on native JS implementation, it does not depend on any framework. Perfect for Vue, React and other MVVM frameworks.\n- title: Pluggable\n  details: Plugin support, such as Picker, PullUpLoad, PullDownRefresh, Zoom, Mouse-Wheel, Slide, Movable, Indicators, Parallax Scrolling, Magnifier and so on.\nfooter: MIT Licensed | Copyright © 2018-present ustbhuangyi and theniceangel\n---\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/README.md",
    "content": "# Introduction\n\n## What is BetterScroll ?\n\nBetterScroll is a plugin which is aimed at solving scrolling circumstances on the mobile side (PC supported already). The core is inspired by the implementation of [iscroll](https://github.com/cubiq/iscroll), so the APIs of BetterScroll are compatible with iscroll on the whole. What's more, BetterScroll also extends some features and optimizes for performance based on iscroll.\n\nBetterScroll is implemented with plain JavaScript, which means it's dependency free.\n\n## Demo\n\n<img data-zoomable :src=\"$withBase('/assets/images/qrcode.png')\" alt=\"demo\">\n\n## Getting started\n\nThe most common application scenario of BetterScroll is list scrolling. Let's see its HTML:\n\n```html\n<div class=\"wrapper\">\n  <ul class=\"content\">\n    <li>...</li>\n    <li>...</li>\n    ...\n  </ul>\n  <!-- you can put some other DOMs here, it won't affect the scrolling -->\n</div>\n```\n\nIn the code above, BetterScroll is applied to the outer `wrapper` container, and the scrolling part is `content` element. Pay attention that BetterScroll handles the scroll of the first child element (content) of the container (`wrapper`) by default, which means other elements will be ignored.\n\nThe simplest initialization code is as follow:\n\n```javascript\nimport BScroll from '@better-scroll/core'\nlet wrapper = document.querySelector('.wrapper')\nlet scroll = new BScroll(wrapper)\n```\n\nBetterScroll provides a class whose first parameter is a plain DOM object when instantiated. Certainly, BetterScroll inside would try to use querySelector to get the DOM object.\n\n:::warning\nIn BetterScroll 2.X, we split the 1.X-coupled feature into the plugin to achieve on-demand loading and reduce the volume of the package. Therefore, `@better-scroll/core` only provides the most core scrolling capabilities. If you want to implement the **pull-up load**, **pull-down refresh** function, you need to use the corresponding [plugin] (/en-US/plugins).\n:::\n\n:::tip\nBetterScroll v2.0.4 can use [specifiedIndexAsContent](./base-scroll-options.html#specifiedindexascontent-2-0-4) to specify a child element of the wrapper as BetterScroll's content.\n:::\n\n## The principle of scrolling\n\nMany developers have used BetterScroll, but the most common problem they have met is:\n\n> I have initiated BetterScroll, but the content can't scroll.\n\nThe phenomenon is 'the content can't scroll' and we need to figure out the root cause. Before that, let's take a look at the browser's scrolling principle: everyone can see the browser's scroll bar. When the height of the page content exceeds the viewport height, the vertical scroll bar will appear; When the width of page content exceeds the viewport width, the horizontal bar will appear. That is to say, when the viewport can't display all the content, the browser would guide the user to scroll the screen with scroll bar to see the rest of content.\n\nThe principle of BetterScroll is samed as the browser. We can feel about this more obviously using a picture:\n\n<img data-zoomable :src=\"$withBase('/assets/images/schematic.png')\" alt=\"schematic\">\n\nThe green part is the wrapper, also known as the parent container, which has **fixed height**. The yellow part is the content, which is **the first child element** of the parent container and whose height would grow with the size of its content. Then, when the height of the content doesn't exceed the height of the parent container, the content would not scroll. Once exceeded, the content can be scrolled. That is the principle of BetterScroll.\n\n## Using BetterScroll with MVVM frameworks\n\nI wrote an article [When BetterScroll meets Vue](https://zhuanlan.zhihu.com/p/27407024) (in Chinese). I also hope that developers can contribute to share the experience of using BetterScroll with other frameworks.\n\nA fantastic mobile ui lib implement by Vue: [cube-ui](https://github.com/didi/cube-ui/)\n\n## Using BetterScroll in the real project\n\nIf you want to learn how to use BetterScroll in the real project，you can learn my two practical courses(in Chinese)。\n\n[High imitating starvation takeout practical course base on Vue.js](https://coding.imooc.com/class/74.html)\n\n[Project demo address](http://ustbhuangyi.com/sell/)\n\n![QR Code](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fsell%252F%2523%252Fgoods&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=686203a49c4613080b5b3004323ff977)\n\n[Music App advanced practical course base on Vue.js](http://coding.imooc.com/class/107.html)\n\n[Project demo address](http://ustbhuangyi.com/music/)\n\n![QR Code](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fmusic%252F&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=731bbcc2b490454d2cc604f98539952c)\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/base-scroll-api.md",
    "content": "# API\n\nIf you want to understand BetterScroll thoroughly, you need to understand the common properties of its instances, the flexible methods, and the hooks provided.\n\n## \bProperties\n\nSometimes we want to do some extensions based on BetterScroll, we need to understand some of the properties of BetterScroll. Here are a few common properties.\n\n### x\n  - **Type**: `number`.\n  - **Usage**: scroll horizontal axis coordinate.\n\n### y\n  - **Type**: `number`.\n  - **Usage**: scroll vertical axis coordinate.\n\n### maxScrollX\n  - **Type**: `number`\n  - **Usage**: max scrollable horizontal coordinate.\n  - **Note**: horizontal scroll range is [minScrollX, maxScrollX], and maxScrollX is negative value.\n\n### minScrollX\n  - **Type**: `number`\n  - **Usage**: min scrollable horizontal coordinate.\n  - **Note**: horizontal scroll range is [minScrollX, maxScrollX], and minScrollX is positive value.\n\n### maxScrollY\n  - **Type**: `number`\n  - **Usage**: max scrollable vertical coordinate\n  - **Note**: vertical scroll range is [minScrollY, maxScrollY], and maxScrollY is negative value.\n\n### minScrollY\n  - **Type**: `number`\n  - **Usage**: min scrollable vertical coordinate\n  - **Note**: vertical scroll range is [minScrollY, maxScrollY], and minScrollY is positive value.\n\n### movingDirectionX\n  - **Type**: `number`\n  - **Usage**: estimate the moving direction on horizontal is left or right.\n  - **Note**: -1 means finger moves from left to right, 1 means moving from right to left, 0 means haven't moved.\n\n### movingDirectionY\n  - **Type**: `number`\n  - **Usage**: estimate the moving direction on vertical is up or down during scrolling.\n  - **Note**: -1 means finger moves from up to down, 1 means from down to up, 0 means haven't moved.\n\n### directionX\n  - **Type**: `number`\n  - **Usage**: estimate the moving direction on horizontal between start position and end position is left or right.\n  - **Note**: -1 means finger moves from up to down, 1 means from down to up, 0 means haven't moved.\n\n### directionY\n  - **Type**: `number`\n  - **Usage**: estimate the moving direction on vertical between start position and end position is up or down.\n  - **Note**: -1 means finger moves from up to down, 1 means from down to up, 0 means haven't moved.\n\n### enabled\n  - **Type**: `boolean`,\n  - **Usage**: estimate whether the current scroll is enabled.\n\n### pending\n  - **Type**: `boolean`,\n  - **Usage**: estimate whether the current scroll is animating.\n\n## Methods\n\nBetterScroll provides a lot of flexible APIs, which are used when we implement some features based on BetterScroll, and understanding them will help to meet more complex requirements.\n\n### refresh()\n  - **Arguments**: none.\n  - **Return**: none.\n  - **Usage**: recalculate BetterScroll to ensure scroll work properly when the structure of DOM changes.\n\n### scrollTo(x, y, time, easing, extraTransform)\n   - **Arguments**:\n     - `{number} x`, horizontal axis distance. (unit: px)\n     - `{number} y`, vertical axis distance. (unit: px)\n     - `{number} time`, animation duration. (unit: ms)\n     - `{Object} easing function`, usually don't suggest modifying. If you really need to modify, please refer `packages/shared-utils/src/ease.ts`'s of source code\n     - `{Object} extraTransform`, you only need to pass in this parameter if you want to modify some other properties of the CSS transform. The structure is as follows:\n     ```js\n     let extraTransform = {\n       // \u001dstart point\n       start: {\n         scale: 0\n       },\n       // end poinnt\n       end: {\n         scale: 1.1\n       }\n     }\n     bs.scrollTo(0, -60, 300, undefined, extraTransform)\n     ```\n    - **Return**: none.\n    - **Usage**: scroll to specified position.\n\n### scrollBy(x, y, time, easing)\n   - **Arguments**:\n     - `{number} x`, horizontal axis changed distance. (unit: px)\n     - `{number} y`, vertical axis changed distance. (unit: px)\n     - `{number} time`, animation duration. (unit: ms)\n     - `{Object} easing function`, usually don't suggest modifying. If you really need to modify, please refer `packages/shared-utils/src/ease.ts`.\n   - **Return**: none.\n   - **Usage**: scroll to specified position based on current position.\n\n### scrollToElement(el, time, offsetX, offsetY, easing)\n   - **Arguments**:\n     - `{DOM | string} el`, target element. If the value is a string, we will try to use querySelector get the DOM element.\n     - `{number} time`, animation duration. (unit ms)\n     - `{number | boolean}` offsetX, the x offset to target element，If the value is true, scroll to the center of target element.\n     - `{number | boolean}` offsetY, the y offset to target element，If the value is true, scroll to the center of target element.\n     - `{Object} easing function`, usually don't suggest modifying. If you really need to modify, please refer `packages/shared-utils/src/ease.ts`.\n   - **Return**: none.\n   - **Usage**: scroll to target element.\n\n### stop()\n   - **Arguments**: none.\n   - **Return**: none.\n   - **Usage**: stop the scroll animation immediately.\n\n### enable()\n   - **Arguments**: none.\n   - **Return**: none.\n   - **Usage**: enable BetterScroll. It's enabled by default.\n\n### disable()\n   - **Arguments**: none.\n   - **Return**: none.\n   - **Usage**: disable BetterScroll. And it will make the callbacks of DOM events don't response.\n\n### destroy()\n   - **Arguments**: none.\n   - **Return**: none.\n   - **Usage**: destroy BetterScroll，remove events and free some memory when the scroll is not needed anymore.\n\n### on(type, fn, context)\n   - **Arguments**:\n     - `{string} type`, event\n     - `{Function} fn`, callback\n     - `{Object} context`,default is `this`.\n   - **Return**: none\n   - **Usage**: listen for a hook on the current BScroll, such as \"scroll\", \"scrollEnd\" and so on.\n   - **Example**:\n   ```javascript\n   import BScroll from '@BetterScroll/core'\n   let scroll = new BScroll('.wrapper', {\n     probeType: 3\n   })\n   function onScroll(pos) {\n       console.log(`Now position is x: ${pos.x}, y: ${pos.y}`)\n   }\n   scroll.on('scroll', onScroll)\n   ```\n\n### once(type, fn, context)\n   - **Arguments**:\n     - `{string} type`, event\n     - `{Function} fn`, callback\n     - `{Object} context`, default is `this`.\n   - **Return**: none\n   - **Usage**: listen for a custom event, but only once. The listener will be removed once it triggers for the first time.\n\n### off(type, fn)\n   - **Arguments**:\n     - `{string} type`, event\n     - `{Function} fn`, callback\n   - **Return**: none\n   - **Usage**: remove custom event listener. Only remove the listener for that specific callback.\n   - **Example**:\n   ```javascript\n   import BScroll from '@BetterScroll/core'\n   let scroll = new BScroll('.wrapper', {\n     probeType: 3\n   })\n   function handler() {\n       console.log('bs is scrolling now')\n   }\n   scroll.on('scroll', handler)\n\n   scroll.off('scroll', handler)\n   ```\n\n## Events VS Hooks\n\nBased on the 2.x architecture design and compatibility with 1.x events, we have extended two concepts-\"**Events**\" and \"**Hooks**\". Basically, they are all instances of `EventEmitter`, but they are called differently. Let's explain it from the excerpted source code below:\n\n```typescript\n  export default BScrollCore extends EventEmitter {\n    hooks: EventEmitter\n  }\n```\n\n  - **BScrollCore**\n\n    It inherits EventEmitter itself. we all call it \"**event**\".\n\n    ```js\n      import BScroll from '@better-scroll/core'\n      let bs = new BScroll('.wrapper', {})\n\n      // listen bs scroll event\n      bs.on('scroll', () => {})\n      // listen bs refresh event\n      bs.on('refresh', () => {})\n    ```\n\n  - **BScrollCore.hooks**\n\n    Hooks are also instances of EventEmitter. we all call it \"**hook**\".\n\n    ```js\n      import BScroll from '@better-scroll/core'\n      let bs = new BScroll('.wrapper', {})\n\n      // tap bs refresh hook\n      bs.hooks.on('refresh', () => {})\n      // tap bs enable hook\n      bs.hooks.on('enable', () => {})\n    ```\n\nI believe everyone now has a better distinction between the two. \"**Event**\" is for the compatibility of 1.x. Users generally pay attention to the distribution of events, but if you want to write a plugin, you should focus on \"**hooks**\".\n\n## Events\n\nIn 2.0, BetterScroll events are almost same with 1.x events. Only BetterScroll will dispatch \"**events**\". If you need to expose events when writing plugins, you should also dispatch them through BetterScroll. [details is here](../plugins/how-to-write.html), the current events are divided into the following types:\n\n  - **refresh**\n    - **Trigger timing**: BetterScroll recalculate\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n\n      const bs = new BetterScroll('.wrapper', {})\n\n      bs.on('refresh', () => {})\n    ```\n\n  - **enable**\n    - **Trigger timing**: BetterScroll is enabled and starts to respond to user interaction\n\n    ```js\n      bs.on('enable', () => {})\n    ```\n\n  - **disable**\n    - **Trigger timing**: BetterScroll is disabled and no longer responds to user interaction\n\n    ```js\n      bs.on('disable', () => {})\n    ```\n\n  - **beforeScrollStart**\n    - **Trigger timing**: When the user's finger is placed on the scroll area\n\n    ```js\n      bs.on('beforeScrollStart', () => {})\n    ```\n\n  - **scrollStart**\n    - **Trigger timing**: Content element meets the scrolling conditions and will start scrolling\n\n    ```js\n      bs.on('scrollStart', () => {})\n    ```\n\n  - **scroll**\n    - **Trigger timing**: It is scrolling\n\n    ```js\n      bs.on('scroll', (position) => {\n        console.log(position.x, position.y)\n      })\n    ```\n\n  - **scrollEnd**\n    - **Trigger timing**: End of scrolling, or force a content that is scrolling to stop\n\n    ```js\n      bs.on('scrollEnd', () => {})\n    ```\n\n  - **scrollCancel**\n    - **Trigger timing**: Scroll cancel\n\n    ```js\n      bs.on('scrollCancel', () => {})\n    ```\n\n  - **touchEnd**\n    - **Trigger timing**: User finger leaves the scroll area\n\n    ```js\n      bs.on('touchEnd', () => {})\n    ```\n\n  - **flick**\n    - **Trigger timing**: User triggered flick operation\n\n    ```js\n      bs.on('flick', () => {})\n    ```\n\n  - **destroy**\n    - **Trigger timing**: BetterScroll destroyed\n\n    ```js\n      bs.on('destroy', () => {})\n    ```\n\n  - **contentChanged** <Badge text='2.0.4' />\n    - **Trigger timing**: When calling `bs.refresh()`, it is detected that the content DOM has become other elements\n\n    ```typescript\n      // bs version >= 2.0.4\n      bs.on('contentChanged', (newContent: HTMLElement) => {})\n    ```\n\nThe following events must be registered for the **plugin** in parentheses to be dispatched:\n\n  - **alterOptions(__mouse-wheel__)**\n    - **Trigger timing**: mouse-wheel scroll starts\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import MouseWheel from '@better-scroll/mouse-wheel'\n\n      BetterScroll.use(MouseWheel)\n      const bs = new BetterScroll('.wrapper', {\n        mouseWheel: true\n      })\n\n      bs.on('alterOptions', (mouseWheelOptions) => {\n        /**\n         * mouseWheelOptions.speed\n         * mouseWheelOptions.invert\n         * mouseWheelOptions.easeTime\n         * mouseWheelOptions.discreteTime\n         * mouseWheelOptions.throttleTime\n         * mouseWheelOptions.dampingFactor\n         **/\n\n        // please see details in mouse-wheel plugin doc\n      })\n    ```\n\n  - **mousewheelStart(__mouse-wheel__)**\n    - **Trigger timing**: mouse-wheel scroll starts\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import MouseWheel from '@better-scroll/mouse-wheel'\n\n      BetterScroll.use(MouseWheel)\n      const bs = new BetterScroll('.wrapper', {\n        mouseWheel: true\n      })\n\n      bs.on('mousewheelStart', () => {})\n    ```\n\n  - **mousewheelMove(__mouse-wheel__)**\n    - **Trigger timing**: mouse-wheel is scrolling\n\n    ```js\n      bs.on('mousewheelMove', () => {})\n    ```\n\n  - **mousewheelEnd(__mouse-wheel__)**\n    - **Trigger timing**: mouse-wheel scrollEnd\n\n    ```js\n      bs.on('mousewheelEnd', () => {})\n    ```\n\n  - **pullingDown(__pull-down__)**\n    - **Trigger timing**: When the top pull-down distance exceeds the threshold\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import PullDown from '@better-scroll/pull-down'\n\n      BetterScroll.use(PullDown)\n      const bs = new BetterScroll('.wrapper', {\n        pullDownRefresh: true\n      })\n\n      bs.on('pullingDown', () => {\n        await fetchData()\n        bs.finishPullDown()\n      })\n    ```\n\n  - **pullingUp(__pull-up__)**\n    - **Trigger timing**: When the bottom pull-up distance exceeds the threshold\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import PullUp from '@better-scroll/pull-up'\n\n      BetterScroll.use(PullUp)\n      const bs = new BetterScroll('.wrapper', {\n        pullUpLoad: true\n      })\n\n      bs.on('pullingUp', () => {\n        await fetchData()\n        bs.finishPullUp()\n      })\n    ```\n\n  - **slideWillChange(__slide__)**\n    - **Trigger timing**: The slide is about to switch page\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Slide from '@better-scroll/slide'\n\n      BetterScroll.use(Slide)\n\n      const bs = new BetterScroll('.wrapper', {\n        slide: true,\n        momentum: false,\n        bounce: false,\n        probeType: 2\n      })\n\n      bs.on('slideWillChange', (page) => {\n        // Page about to switch\n        console.log(page.pageX, page.pageY)\n      })\n    ```\n\n  - **beforeZoomStart(__zoom__)**\n    - **Trigger timing**: When two fingers touch the zoom element\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Zoom from '@better-scroll/zoom'\n\n      BetterScroll.use(Zoom)\n\n      const bs = new BetterScroll('.wrapper', {\n        zoom: true\n      })\n\n      bs.on('beforeZoomStart', () => {})\n    ```\n\n  - **zoomStart(__zoom__)**\n    - **Trigger timing**: Two-finger zoom distance exceeds the minimum threshold\n\n    ```js\n      bs.on('zoomStart', () => {})\n    ```\n\n  - **zooming(__zoom__)**\n    - **Trigger timing**: While the two-finger zoom behavior is in progress\n\n    ```js\n      bs.on('zooming', ({ scale }) => {\n        // current scale\n      })\n    ```\n\n  - **zoomEnd(__zoom__)**\n    - **Trigger timing**: After the two-finger zoom action ends\n\n    ```js\n      bs.on('zoomEnd', ({ scale }) => {})\n    ```\n\n## Hooks\n\nA hook is a concept extended from version 2.0. Its essence is the same as an event. It is an instance of EventEmitter, which is a typical subscription publishing model. As the smallest scroll unit, BScrollCore also has many functional classes inside. Each functional class has a property called hooks, which bridges the communication between different classes. If you want to write a complex plugin, hooks must be mastered.\n\n  - **BScrollCore.hooks**\n\n    - **beforeInitialScrollTo**\n      - **Trigger timing**: After initial loading the plugin, you need to scroll to the specified position\n      - **Arguments**: position object\n        - `{ x: number, y: number }`\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('beforeInitialScrollTo', (postion) => {\n          postion.x = 0\n          position.y = -200 // Initialize scroll to -200\n        })\n      ```\n\n    - **refresh**\n      - **Trigger timing**: Recalculate BetterScroll\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('refresh', () => { console.log('refreshed') })\n      ```\n\n    - **enable**\n      - **Trigger timing**: Enable BetterScroll to respond to user behavior\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('enable', () => { console.log('enabled') })\n      ```\n\n    - **disable**\n      - **Trigger timing**: Disable BetterScroll and no longer respond to user behavior\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('disable', () => { console.log('disabled') })\n      ```\n\n    - **destroy**\n      - **Trigger timing**: Destroy BetterScroll\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('destroy', () => { console.log('destroyed') })\n      ```\n\n    - **contentChanged** <Badge text='2.0.4' />\n      - **Trigger timing**：When calling `bs.refresh()`, it is detected that the content DOM has become other elements\n      - **Usage**\n      ```typescript\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        // bs version >= 2.0.4\n        bs.hooks.on('contentChanged', (newContent: HTMLElement) => { console.log(newContent) })\n      ```\n\n\n  - **ActionsHandler.hooks**\n\n    - **beforeStart**\n      - **Trigger timing**: Just respond to the touchstart event, but the position of the finger on the screen has not been recorded\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('beforeStart', (event) => { console.log(event.target) })\n      ```\n\n    - **start**\n      - **Trigger timing**: After recording the position of the finger on the screen, touchmove will be triggered\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('start', (event) => { console.log(event.target) })\n      ```\n\n    - **move**\n      - **Trigger timing**: Responding to the touchmove event, after recording the position of the finger on the screen\n      - **Arguments**: Objects with the following properties\n        - `{ number } deltaX`: x offset\n        - `{ number } deltaY`: y offset\n        - `{ event } e`: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('move', ({ deltaX, deltaY, e }) => {})\n      ```\n\n    - **end**\n      - **Trigger timing**: Responding to touchend event\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('end', (event) => {})\n      ```\n\n    - **click**\n      - **Trigger timing**: Trigger the click event\n      - **Arguments**: event object\n\n  - **ScrollerActions.hooks**\n\n    - **start**\n      - **Trigger timing**: After recording all the scrolling initial information\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('start', (event) => { console.log(event.target) })\n      ```\n\n    - **beforeMove**\n      - **Trigger timing**: Before checking whether it is legal scrolling\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeMove', (event) => { console.log(event.target) })\n      ```\n\n    - **scrollStart**\n      - **Trigger timing**: scroll is abount to start\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('scrollStart', () => {})\n      ```\n\n    - **scroll**\n      - **Trigger timing**: It is scrolling\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('scroll', () => {})\n      ```\n\n    - **beforeEnd**\n      - **Trigger timing**: The touchend event callback has just been executed, but the final position has not been updated\n      - **Arguments**: event object\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeEnd', (event) => { console.log(event) })\n      ```\n\n    - **end**\n      - **Trigger timing**: Just execute the touchend event callback and update the scroll direction\n      - **Arguments**: Two Arguments, the first is the event object, the second is the current position\n        - `{ event } e`: event object\n        - `{ x: number, y: number } postion`: current position\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('end', (e, postion) => { console.log(e) })\n      ```\n\n    - **scrollEnd**\n      - **Trigger timing**: Scrolling is about to end, but you still need to verify whether a scrolling behavior triggers flick and momentum behaviors.\n      - **Arguments**: Two Arguments, the first is the current position, the second is the animation duration\n        - `{ x: number, y: number } postion`: current position\n        - `{ number } duration`: animation duration\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeEnd', (pos, duration) => { console.log(pos) })\n      ```\n\n    - **coordinateTransformation**\n      - **Trigger timing**: After calculating the offset of the user's finger, before scrolling occurs\n      - **Arguments**: transformateDeltaData object\n        - `{ deltaX: number, deltaY: number } transformateDeltaData`\n      - **Usage**\n      ```js\n      let bs = new BScroll('.wrapper', {\n          quadrant: 1 // default value\n      })\n      bs.scroller.actions.hooks.on(\n        'coordinateTransformation',\n        (transformateDeltaData) => {\n          // get user finger moved distance\n          const originDeltaX = transformateDeltaData.deltaX\n          const originDeltaY = transformateDeltaData.deltaY\n\n          // apply transformation\n          transformateDeltaData.deltaX = originDeltaY\n          transformateDeltaData.deltaY = originDeltaX\n\n          // transformateDeltaData.deltaX will be used as content DOM style's translateX\n          // transformateDeltaData.deltaY will be used as content DOM style's translateY\n        }\n      )\n      ```\n\n      This hook is usually to fix the logic of user-defined displacement transformation when the ancestor element of the wrapper DOM of BetterScroll is rotated. In most cases, it only needs to be configured [quadrant](./base-scroll-options.html#quadrant).\n\n  - **Behavior.hooks**\n\n    - **beforeComputeBoundary**\n      - **Trigger timing**: About to calculate the scroll boundary\n      - **Arguments**: boundary object\n        - `{ minScrollPos: number, maxScrollPos: number } boundary`\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('beforeComputeBoundary', () => {})\n      ```\n\n    - **computeBoundary**\n      - **Trigger timing**: Calculate the scroll boundary\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('computeBoundary', (boundary) => {\n          // The maximum value of the upper boundary, the more positive, the greater the pull down\n          console.log(boundary.minScrollPos)\n          // The minimum value of the lower boundary, the more negative, the farther you roll\n          console.log(boundary.maxScrollPos)\n        })\n      ```\n\n    - **momentum**\n      - **Trigger timing**: Meet the conditions for triggering momentum animation, and before calculating distance\n      - **Arguments**: Two Arguments, the first is the momentumData object, the second is the scroll offset\n        - `{ destination: number, duration: number, rate: number} momentumData`: destination is the target position, duration is the easing time, rate is the slope\n        - `{ number } distance`: Scroll offset to trigger momentum\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('momentum', (momentumData, distance) => {})\n      ```\n\n    - **end**\n      - **Trigger timing**: Does not meet the conditions for triggering momentum animation\n      - **Arguments**: momentumInfo object\n        - `{ destination: number, duration: number} momentumInfo`: destination is the target position, duration is the easing time\n      - **Usage**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('end', (momentumInfo) => {\n          console.log(momentumInfo.destination)\n          console.log(momentumInfo.duration)\n        })\n      ```\n\n  - **Animation.hooks(useTransition: false)**\n\n    - **forceStop**\n      - **Trigger timing**: Force a scrolling bs to stop\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **move**\n      - **Trigger timing**: Scrolling\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **end**\n      - **Trigger timing**: Scroll ended\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n  - **Transition.hooks(useTransition: true)**\n\n    - **forceStop**\n      - **Trigger timing**: Force a bs that is doing animation to stop\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **move**\n      - **Trigger timing**: Scrolling\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **end**\n      - **Trigger timing**: Scroll ended\n      - **Arguments**:position object\n        - `{ x: number, y: number } position`\n\n    - **time**\n      - **Trigger timing**: Before the CSS3 transition started, the wheel plugin listened to the hook\n      - **Arguments**: CSS3 transition duration\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.animater.hooks\n          hooks.on('time', (duration) => {\n            console.log(duration) // 800\n          })\n        ```\n\n    - **timeFunction**\n      - **Trigger timing**:  Before the CSS3 transition started, the wheel plugin listened to the hook\n      - **Arguments**: CSS3 transition-timing-function\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.animater.hooks\n          hooks.on('timeFunction', (easing) => {\n            console.log(easing) // cubic-bezier(0.1, 0.7, 1.0, 0.1)\n          })\n        ```\n\n  - **Translater.hooks**\n\n    - **beforeTranslate**\n      - **Trigger timing**: Before modifying the transform style of the content element, the zoom plugin listened to the hook\n      - **Arguments**: The first is the transformStyle array, the second is the point object\n        - `{ ['translateX(0px)'|'translateY(0px)'] } transformStyle`: The property value corresponding to the current transform\n        - `{ x: number, y: number } point`: x corresponds to the value of translateX, y corresponds to the value of translateY\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.translater.hooks\n          hooks.on('beforeTranslate', (transformStyle, point) => {\n            transformStyle.push('scale(1.2)')\n            console.log(transformStyle) // ['translateX(0px)', 'translateY(0px)', 'scale(1.2)']\n            console.log(point) // { x: 0, y: 0 }\n          })\n        ```\n\n    - **translate**\n      - **Trigger timing**: After modifying the transform style of the content element, the wheel plugin listened to the hook\n      - **Arguments**: point object\n        - `{ x: number, y: number } point`: x corresponds to the value of translateX, y corresponds to the value of translateY\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.translater.hooks\n          hooks.on('translate', (point) => {\n            console.log(point) // { x: 0, y: 0 }\n          })\n        ```\n\n  - **Scroller.hooks**\n\n    - **beforeStart**\n      same with `ScrollerActions.hooks.start`\n\n    - **beforeMove**\n      same with `ScrollerActions.hooks.beforeMove`\n\n    - **beforeScrollStart**\n      same with `ScrollerActions.hooks.start`\n\n    - **scrollStart**\n      same with `ScrollerActions.hooks.scrollStart`\n\n    - **scroll**\n      - **Trigger timing**: Scrolling\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **beforeEnd**\n      same with `ScrollerActions.hooks.beforeEnd`\n\n    - **touchEnd**\n      - **Trigger timing**: User finger leaves the scroll area\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('touchEnd', () => {\n          console.log('your finger has leave')\n        })\n      ```\n\n    - **end**\n      - **Trigger timing**: After touchEnd, it is triggered before verifying click. The pull-down plugin is implemented based on this hook\n      - **Arguments**: position object\n       - `{ x: number, y: number } position`\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('end', (position) => {\n          console.log(position.x)\n          console.log(position.y)\n        })\n      ```\n\n    - **scrollEnd**\n      - **Trigger timing**: Scroll ended\n      - **Arguments**: position object\n        - `{ x: number, y: number } position`\n\n    - **resize**\n      - **Trigger timing**: window size changed\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('resize', () => {\n          console.log(\"window's size has changed\")\n        })\n      ```\n\n    - **flick**\n      - **Trigger timing**: Finger flicking detected\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('flick', () => {})\n      ```\n\n    - **scrollCancel**\n      - **Trigger timing**: Scroll canceled or did not happen\n\n    - **momentum**\n      - **Trigger timing**: Momentum displacement is about to begin, and the slide plugin listens to the hook\n      - **Arguments**: scrollMetaData object\n        - `{ time: number, easing: EaseItem, newX: number, newY: number }`: time is the duration of the animation, easing is the easing function, newX and newY are the end points\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('momentum', (scrollMetaData) => {\n          scrollMetaData.newX = 0\n          scrollMetaData.newY = -200\n        })\n      ```\n\n    - **scrollTo**\n      - **Trigger timing**: Triggered when the bs.scrollTo method is called\n      - **Arguments**: endPoint object\n        - `{ x: number, y: number } endPoint`\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('scrollTo', (endPoint) => {\n          console.log(endPoint.x)\n          console.log(endPoint.y)\n        })\n        bs.scrollTo(0, -200)\n      ```\n\n    - **scrollToElement**\n      - **Trigger timing**: Triggered when the bs.scrollToElement method is called, and the wheel plugin listens to the hook\n      - **Arguments**: The first is the target DOM object, the second is the coordinates of the end point\n        - `{ HTMLElment } el`\n        - `{ top: number, left: number } postion`\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('scrollToElement', (el, pos) => {\n          console.log(el)\n          console.log(pos.left)\n          console.log(pos.top)\n        })\n        bs.scrollToElement('.some-item', 300, true, true)\n      ```\n\n    - **beforeRefresh**\n      - **Trigger timing**: Before behavior calculates the boundary, the slide plugin listens to the hook\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('beforeRefresh', () => {})\n      ```\n\n::: tip\nIf you are careful, you will find that some Scroller.hooks have exactly the same functions as ScrollActions.hooks. In fact, we internally use a [hook bubbling](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/core/src/utils/bubbling.ts) strategy to proxy the hooks of the inner function classes to the BetterScroll Instance in the form of bubbling to be compatible with the use of 1.x.\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/base-scroll-options.md",
    "content": "# Options\n\nBetterScroll supports rich options configuration, you can pass them in the second parameter when initializing, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nlet scroll = new BScroll('.wrapper',{\n    scrollY: true,\n    click: true\n})\n```\n\nThis implements a list of vertical clickable scrolling effects. so let's list the parameters supported by BetterScroll.\n\n## startX\n  - **Type**: `number`\n  - **Default**:  `0`\n  - **Details**: Initialize the postion in the horizontal axis direction.\n\n## startY\n  - **Type**: `number`\n  - **Default**:  `0`\n  - **Details**: Initialize the postion in the vertical axis direction.\n\n## scrollX\n  - **Type**: `boolean`\n  - **Default**: `false`\n  - **Usage**: When set to true, horizontal scrolling would be enabled\n  - **Note**: This configuration is invalid when setting [eventPassthrough](./base-scroll-options.html#eventpassthrough) to 'horizontal'.\n\n## scrollY\n  - **Type**: `boolean`\n  - **Default**: `true`\n  - **Usage**: When set to true, vertical scrolling would be enabled\n  - **Note**: This configuration is invalid when setting [eventPassthrough](./base-scroll-options.html#eventpassthrough) to 'vertical'.\n\n## freeScroll\n  - **Type**: `boolean`\n  - **Default**: `false`\n  - **Usage**: By default, because human fingers cannot perform absolute vertical or horizontal movement, there will be horizontal and vertical offsets during a finger operation. The internal default will abandon the smaller offset direction , Keep scrolling in the other direction. But in some scenes, we need to calculate the horizontal and vertical finger offset distances at the same time, instead of only calculating the direction with a larger offset. At this time, we only need to set `freeScroll` to true.\n  - **Note**:  This configuration is invalid when [eventPassthrough](./base-scroll-options.html#eventpassthrough) isn't set to empty.\n  - **Examples**\n  ```js\n  // finger startpoint -> e1: { pageX: 120, pageY: 120 }\n  // finger endpoint -> e2: { pageX: 121, pageY: 140 }\n  // offsetX:  e2.pageX - e1.pageX = 1\n  // offsetY:  e2.pageY - e1.pageY = 20\n  // if freeScroll is false,  due to offsetY > offsetX + directionLockThreshold\n  // offsetX is fixed to be 0, only calculate offsetY, thus do a vertical scroll!\n  ```\n\n## directionLockThreshold\n  - **Type**: `number`\n  - **Default**: `5`\n  - **Usage**: when `freeScroll` is false, we need to lock the scrolling only in one direction,  we calculate the numerical difference between the absolute values of horizontal axis and vertical axis' scrolling distance at the initialization time of scrolling. When the value of the numerical difference is greater than `directionLockThreshold`, the lock direction can be determined.\n  - **Note**: If [eventPassthrough](./base-scroll-options.html#eventpassthrough) is set, `directionLockThreshold` is invalid and will always be 0.\n\n## eventPassthrough\n  - **Type**: `string`\n  - **Default**: `''`\n  - Optional value: `vertical | horizontal`\n  - **Usage**: Sometimes we want to preserve native vertical scroll but being able to add an horizontal BetterScroll (maybe a carousel). Set this to 'vertical' and the BetterScroll area will react to horizontal swipes only. Vertical swipes will naturally scroll the whole page. Contrarily, set this to 'horizontal' when you want to keep natural horizontal scroll.\n  - **Note**: The setting of  `eventPassthrough` will cause some other settings to be invalid, be careful when using it.\n\n## click\n  - **Type**: `boolean`\n  - **Default**: `false`\n  - **Usage**: To override the native scrolling BetterScroll has to inhibit some default browser behaviors, such as mouse clicks. If you want your application to respond to the click event you have to explicitly set this option to `true`. And then BetterScroll will add a private attribute called `_constructed` to the dispatched event whose value is true.\n\n\n## dblclick\n  - **Type**: `boolean | Object`\n  - **Default**: `false`\n  - **Usage**: Send dblclick event. When configured to true, by default the two times click delay is 300 ms. If configured to an object, the `delay` can be modified.\n  ```js\n\tdblclick: {\n\t\tdelay: 300\n\t}\n  ```\n\n## tap\n  - **Type**: `string`\n  - **Default**: `''`\n  - **Details**: Since BetterScroll will block the native click event, we can set tap to 'tap', which will dispatch a tap event when the region is clicked. You can listen to it as if it were listening to native events.\n\n## bounce\n   - **Type**: `boolean | Object`\n   - **Default**: `true`\n   - **Details**: When the content element meets the boundary it performs a small bounce animation. Setting this to true will enable the animation.\n   ```js\n\t\tbounce: {\n\t\t\ttop: true,\n\t\t\tbottom: true,\n\t\t\tleft: true,\n\t\t\tright: true\n\t\t}\n   ```\n   `bounce` can support the effect of closing the back of some edges. You can set the `key` of the corresponding side to `false`.\n\n  :::tip\n  If you want to conveniently set all edges to **true** or **false**, you only need to set `bounce` to **true** or **false**.\n  :::\n\n\n## bounceTime\n   - **Type**: `number`\n   - **Default**: Default: `800` (ms, modification is not recommended)\n   - **Details**: Set the duration in millisecond of the bounce animation.\n\n## momentum\n   - **Type**: `boolean`\n   - **Default**: `true`\n   - **Usage**: If setted to true, you can turn on the momentum animation when the user quickly flicks on screen.\n\n## momentumLimitTime\n   - **Type**: `number`\n   - **Default**: `300` (ms)\n   - **Usage**: Only when the time of the user's flicking on screen is lower than `momentumLimitTime` resulting in the momentum animation.\n\n## momentumLimitDistance\n   - **Type**: `number`\n   - **Default**: `15` (px)\n   - **Usage**: Only when the distance of the user's flicking on screen is greater than `momentumLimitTime` resulting in the momentum animation.\n\n## swipeTime\n   - **Type**: `number`\n   - **Default**: `2500` (ms)\n   - **Usage**: Set the duration in millisecond of the momentum animation.\n\n## swipeBounceTime\n   - **Type**: `number`\n   - **Default**: `500` (ms)\n   - **Usage**: Set the entire bounce animation time when the content element meets the boundary in the case of running a momentum animation.\n\n## deceleration\n   - **Type**: `number`\n   - **Default**: `0.0015`\n   - **Usage**: Represent the deceleration of the momentum animation.\n\n## flickLimitTime\n   - **Type**: `number`\n   - **Default**: `200`\n   - **Usage**: Sometimes we want to cpture the user's flick action (slide a short distance in a short time). Only when the time of the user slide on screen is shorter than `flickLimitTime`, it is considered as a flick action.\n\n## flickLimitDistance\n   - **Type**: `number`\n   - **Default**: `100`\n   - **Usage**: Only when the distance of the user slide on screen is shorter than `flickLimitDistance`, it is considered as a flick action\n\n## resizePolling\n   - **Type**: `number`\n   - **Default**: `60` (ms)\n   - **Usage**: When you resize the window BetterScroll has to recalculate elements position and dimension. This might be a pretty daunting task for the poor little fella. To give it some rest the polling is set to 60 milliseconds and it is reasonable value.\n\n## probeType\n   - **Type**: `number`\n   - **Default**: `0`\n   - **Optional Value**: `1 | 2 | 3`\n   - **Usage**: Deciding whether to dispatch the scroll event, this has an impact on the performance of the page, especially in the mode where `useTransition` is true.\n\n   ```js\n   // There are two scenarios for dispatching scroll:\n   // 1. The finger acts on the scrolling area (content DOM),\n   // 2. Invoke the scrollTo method or trigger the momentum scroll animation (in fact, the implementation is still Invoking the scrollTo method)\n\n   // For the v2.1.0, the probeType has been unified\n\n   // The probeType is:\n   // 0, scroll event will not be dispatched at any time，\n   // 1, and only when the finger is moving on the scroll area, a scroll event is dispatched every momentumLimitTime milliseconds.\n   // 2, and only when the finger is moving on the scroll area, a scroll event is dispatched all the time.\n   // 3, scroll events are dispatched at any time, including invoking scrollTo or triggering momentum\n   ```\n\n## preventDefault\n   - **Type**: `boolean`\n   - **Default**: `true`\n   - **Usage**: Whether or not to `preventDefault()` when events are fired. This should be left `true` unless you really know what you are doing.\n\n## preventDefaultException\n   - **Type**: Object\n   - **Default**: `{ tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/ }`\n   - **Usage**: BetterScroll will inhibit the native scrolling and meanwhile inhibit some native components' default behaviours. In this situation, we can't 'preventDefault' on these elements, so we can configure 'preventDefaultException'. Default `{tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/}` represents that default behaviours of elements with tagnames like 'input', 'textarea', 'button', 'select', 'audio' will not be inhibited\n   - **Note**: This is a pretty powerful option. Its key is the attribute value of DOM elements, the corresponding value can be a regular expression. For example, if we want to configure the element whose class name is 'test', then the configuration is `{className:/(^|\\s)test(\\s|$)/}`.\n\n## tagException\n   - **Type**: `Object`\n   - **Default**: `{ tagName: /^TEXTAREA$/ }`\n   - **Usage**: If BetterScroll nests form elements such as `textarea`, the user's expectation should be that sliding textarea should not cause bs scrolling. If the manipulated DOM (eg:textarea tag) hits the configured rule, `bs` won't scroll.\n   - **Note**: This is a pretty powerful option. Its key is the attribute value of DOM elements, the corresponding value can be a regular expression. For example, if we want to configure the element whose class name is 'test', then the configuration is `{className:/(^|\\s)test(\\s|$)/}`.\n\n## HWCompositing\n   - **Type**: `boolean`\n   - **Default**: `true`\n   - **Usage**: This option tries to put the content element on the hardware layer by appending `translateZ(1px)` to the transform CSS property. This greatly increases performance especially on mobile and achieve a good scrolling effect.\n   - **Note**: Only browsers that support enabling hardware acceleration has the effect.\n\n## useTransition\n   - **Type**: `boolean`\n   - **Default**: `true`\n   - **Usage**: Whether to use CSS3 transition animation. If setted to false, the engine will use `requestAnimationFrame` to do animation.\n\n## bindToWrapper\n   - **Type**: `boolean`\n   - **Default**: `false`\n   - **Usage**: The `touchmove` event is normally bound to the document and not the scroll wrapper. When you move the cursor out of the wrapper the scrolling keeps going(only works in PC). This is usually what you want, but you can also bind the move event to wrapper itself. Doing so as soon as the cursor leaves the wrapper the scroll stops.\n   - **Note**: For the mobile, even if the touchmove event is bound to the wrapper, the wrapper can still be moved if the finger leaves the wrapper.\n\n## disableMouse\n   - **Type**: `boolean`\n   - **Default**: get the result by current browser environment\n   - **Usage**: When in mobile environment (supporting touch event),  disableMouse will be `true` and mouse event will not be listened. While in PC environment, disableMouse will be `false` and mouse event will be listened.\n\n## disableTouch\n   - **Type**: `boolean`\n   - **Default**: get the result by current browser environment\n   - **Usage**: When in mobile environment (supporting touch event),  `disableTouch` will be `false` and touch event will be listened. While in PC environment, `disableMouse` will be `true` and touch event will not be listened. We suggest not modifying this unless you konw what you are doing.\n\n  ::: warning\n  Considering some specific scenarios of the user, such as **the tablet needs to support the touch event, the tablet with mouse has to support the mouse event**, In other words, if you need to listen to the touch and mouse events at the same time, then the instantiation of BetterScroll needs to be configured as follows:\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    disableMouse: false,\n    disableTouch: false\n  })\n  ```\n\n  Due to the different bottom-level implementation logic of different devices and different browser environments, BetterScroll's internal calculations of whether to listen to `touch` or `mouse` events may make wrong judgment, so you can solve this type of problem according to the above option configuration.\n  :::\n\n## autoBlur\n   - **Type**: `boolean`\n   - **Default**: `true`\n   - **Usage**: It will auto blur the active element(input、textarea) before scroll start.\n\n## stopPropagation\n   - **Type**: `boolean`\n   - **Default**: `false`\n   - **Usage**: Whether stop event propagation. It is often used in nested scroll scenes.\n\n## bindToTarget\n   - **Type**: `boolean`\n   - **Default**: `false`\n   - **Usage**: Bind touch or mouse events to the `content` element instead of the container `wrapper`, which is mostly used in [movable](../plugins/movable.html).\n\n## autoEndDistance\n   - **Type**: `number`\n   - **Default**: `5`\n   - **Usage**: When the finger operation is crazy, the `touchend` event may not be triggered when sliding out of the viewport, so the function of autoEndDistance is to automatically call the touchend event when the finger is about to leave the current viewport. When the default distance is 5px from the boundary, the scrolling ends.\n\n## outOfBoundaryDampingFactor\n   - **Type**: `number`\n   - **Default**: `1 / 3`\n   - **Usage**: When out of boundary, the damping behavior is performed. The smaller the damping factor, the greater the resistance. Value range: [0, 1].\n\n## specifiedIndexAsContent <Badge text='2.0.4' />\n   - **Type**: `number`\n   - **Default**: `0`\n   - **Usage**: Specify the child element corresponding to the index of the `wrapper` as the `content`. By default, BetterScroll uses the first child element of the `wrapper` as the content.\n\n   ```html\n   <div class=\"wrapper\">\n      <div class=\"content1\">\n         <div class=\"conten1-item\">1.1</div>\n         <div class=\"conten1-item\">1.2</div>\n      </div>\n      <div class=\"content2\">\n         <div class=\"conten2-item\">2.1</div>\n         <div class=\"conten2-item\">2.2</div>\n      </div>\n   </div>\n   ```\n\n   ```js\n   // For the above DOM structure, when BetterScroll version <= 2.0.3, only div.content1 is used as content\n   // When the version is >= 2.0.4, content can be specified through 'specifiedIndexAsContent'\n\n   let bs = new BScroll('.wrapper', {\n      specifiedIndexAsContent: 1 // use div.content2 as BetterScroll's content\n   })\n   ```\n\n## quadrant <Badge text='2.3.0' />\n   - **Type**: `1 | 2 | 3 | 4`\n   - **Default**: `1`\n   - **Usage**: When the ancestor elements of BetterScroll's wrapper DOM are forced to rotate by CSS, the original displacements in the x and y directions need to perform a certain transformation to ensure a reasonable interaction.\n\n   ```html\n   <style>\n   /* wrapper's parent DOM rotated*/\n   .container {\n      transform: rotate(90deg);\n   }\n   </style>\n   <div class=\"container\">\n      <div class=\"wrapper\">\n         <div class=\"content\">\n            <div class=\"content-item\">1.1</div>\n            <div class=\"content-item\">1.2</div>\n         </div>\n      </div>\n   </div>\n   ```\n\n   ```js\n   let bs = new BScroll('.wrapper', {\n      quadrant: 2\n   })\n   ```\n\n   1. When the rotation angle of the parent element or ancestor element of the `wrapper` is (315, 45], the quadrant can keep the default value;\n   2. When the rotation angle of the parent element or ancestor element of the `wrapper` is (45, 135],Especially **90 degrees**,  the quadrant **must** be `2`;\n   3. When the rotation angle of the parent element or ancestor element of the `wrapper` is (135, 225],Especially **180 degrees**,  the quadrant **must** be `3`;\n   4. When the rotation angle of the parent element or ancestor element of the `wrapper` is (225, 315],Especially **270 degrees**,  the quadrant **must** be `4`;\n   5. When the rotation angle is special, such as 30 degrees or 200 degrees, you may not be satisfied with the built-in transformation logic. You can customize your own transformation logic through the `coordinateTransformation` hook.\n\n   ```js\n   let bs = new BScroll('.wrapper', {\n      quadrant: 1 // default value\n   })\n   bs.scroller.actions.hooks.on(\n      bs.scroller.actions.hooks.eventTypes.coordinateTransformation,\n      (transformateDeltaData) => {\n         // get user finger moved distance\n         const originDeltaX = transformateDeltaData.deltaX\n         const originDeltaY = transformateDeltaData.deltaY\n\n         // apply transformation\n         transformateDeltaData.deltaX = originDeltaY\n         transformateDeltaData.deltaY = originDeltaX\n\n         // transformateDeltaData.deltaX will be used as content DOM style's translateX\n         // transformateDeltaData.deltaY will be used as content DOM style's translateY\n      }\n   )\n   ```\n\n   For example: Use CSS to flip the horizontal scrolling BetterScroll.\n\n   <demo qrcode-url=\"core/horizontal-rotated\">\n      <template slot=\"code-template\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?template\n      </template>\n      <template slot=\"code-script\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?script\n      </template>\n      <template slot=\"code-style\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?style\n      </template>\n      <core-horizontal-rotated slot=\"demo\"></core-horizontal-rotated>\n   </demo>"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/base-scroll.md",
    "content": "# base-scroll\n\nIn the design of BetterScroll 2.0, we abstracted the core scrolling part, which is the smallest unit of use of BetterScroll. The compression volume is nearly one-third smaller than `1.0`. You may only need to complete a pure scrolling, then just import this library as follows:\n\n```bash\n  npm install @better-scroll/core --save\n```\n\n```js\n  import BScroll from '@better-scroll/core'\n  const bs = new BScroll('.div')\n```\n\n## Get started\n\nBetterScroll has a variety of scroll modes.\n\n- **vertical scroll**\n\n  <demo qrcode-url=\"core/default\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/default.vue?style\n    </template>\n    <core-default slot=\"demo\"></core-default>\n  </demo>\n\n  :::warning\n  BetterScroll dispatches the scroll event in real time, which requires setting `probeType` to 3.\n  :::\n\n- **Horizontal scroll**\n\n  <demo qrcode-url=\"core/horizontal\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/horizontal.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/horizontal.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/horizontal.vue?style\n    </template>\n    <core-horizontal slot=\"demo\"></core-horizontal>\n  </demo>\n\n  :::warning\n  BetterScroll achieves horizontal scrolling, which is more demanding for CSS. First you need to make sure that the wrapper doesn't wrap, and the display of content is inline-block.\n\n  ```stylus\n  .scroll-wrapper\n    // ...\n    white-space nowrap\n  .scroll-content\n    // ...\n    display inline-block\n  ```\n  :::\n\n- **freeScroll（Horizontal and vertical scroll simultaneously）**\n\n  <demo qrcode-url=\"core/freescroll\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/freescroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/freescroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/freescroll.vue?style\n    </template>\n    <core-freescroll slot=\"demo\"></core-freescroll>\n  </demo>\n\n## Dynamic Content <Badge text='2.0.4' />\n\nFor the `2.0.4` version, it has the ability to detect `content` becoming other elements, you can check the following example.\n\n<demo qrcode-url=\"core/dynamic-content\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?style\n  </template>\n  <core-dynamic-content slot=\"demo\"></core-dynamic-content>\n</demo>\n\n## specifiedIndexAsContent <Badge text='2.0.4' />\n\nFor the `2.0.4` version, you can specify a child of **wrapper** as **content**. In previous versions, BetterScroll would only process the first child element of the wrapper. [For details.](./base-scroll-options.html#specifiedindexascontent-2-0-4)\n\n<demo qrcode-url=\"core/specified-content\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/specified-content.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/specified-content.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/specified-content.vue?style\n  </template>\n  <core-specified-content slot=\"demo\"></core-specified-content>\n</demo>\n\n## quadrant <Badge text='2.3.0' />\n\nFor the `2.3.0` version, If the parent element or ancestor element of BetterScroll wrapper DOM rotates, you can use the `quadrant` option to modify the user's interactive behavior.\n\n- **Vertical becomes Horizontal**\n\n<demo qrcode-url=\"core/vertical-rotated\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?style\n  </template>\n  <core-vertical-rotated slot=\"demo\"></core-vertical-rotated>\n</demo>\n\n- **Horizontal scroll flipped**\n\n<demo qrcode-url=\"core/horizontal-rotated\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?style\n  </template>\n  <core-horizontal-rotated slot=\"demo\"></core-horizontal-rotated>\n</demo>\n\n## Warm Tips\n\n  :::tip\n  **If there is any situation where scrolling is not possible, you should first check if the height/width of the content element is greater than the height/width of the wrapper**. This is a prerequisite for content to scroll.\n\n  If the content has an image, it may happen that the image has not been downloaded when the DOM element is rendered, so the height of the content element is less than expected, and the scrolling is not normal. At this point you should call the `bs.refresh` method after the image has been loaded, such as the `onload` event callback, which will recalculate the latest scrolling size.\n  :::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/how-to-install.md",
    "content": "# Install\n\n## NPM\n\nBetterScroll is hosted on NPM and executed with the following command:\n\n```bash\nnpm install @better-scroll/core --save\n\n// or\n\nyarn add @better-scroll/core\n```\n\nThe next step is to use it in the code. [webpack](https://webpack.js.org/) and other build tools support the introduction of code from `node_modules` :\n\n``` js\nimport BScroll from '@better-scroll/core'\n```\n\nIf it is the syntax of commonjs, as follows:\n\n``` js\nvar BScroll = require('@better-scroll/scroll')\n```\n\n## Script\n\nBetterScroll also supports direct loading with script, which loads a BScroll object on the window after loading.\n\n```html\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.min.js\"></script>\n```\n\n```js\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = new BScroll(wrapper, {})\n```\n\n## BetterScroll with all plugins\n\n```bash\nnpm install better-scroll --save\n\n// or\n\nyarn add better-scroll\n```\n\n```js\nimport BetterScroll from 'better-scroll'\nlet bs = new BetterScroll('.wrapper', {})\n```\n\nUse script.\n\n```html\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js\"></script>\n```\n\n```js\nlet bs = BetterScroll.createBScroll('.wrapper', {})\n```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/guide/use.md",
    "content": "# How to use\n\n## Basic Usage\n\nIf you only need a list with basic scrolling capabilities, just use `core`.\n\n```js\nimport BScroll from '@better-scroll/core'\nlet bs = new BScroll('.wrapper', {\n  // ...... see options\n})\n```\n\n## Plugins\n\nIf you need some extra features like `pull-up`, you need to import additional plugins, please refer to [plugins](/docs/en-US/plugins).\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pullup from '@better-scroll/pull-up'\n\n// register plugin\nBScroll.use(Pullup)\n\nlet bs = new BScroll('.wrapper', {\n  probeType: 3,\n  pullUpLoad: true\n})\n```\n\n## Full plugins\n\nIf you find it painsome to register plugins one by one, we offer a BetterScroll package with full plugin capabilities. It is used in exactly the same way as the `1.0` version, but the volume will be relatively large, it is recommended to load by plugin.\n\n```js\nimport BScroll from 'better-scroll'\n\nlet bs = new BScroll('.wrapper', {\n  // ...\n  pullUpLoad: true,\n  wheel: true,\n  scrollbar: true,\n  // and so on\n})\n```\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/README.md",
    "content": "# plugins\n\n## Why need plugins\n\nIn order to decouple the functions of the various features of BetterScroll 1.x, to prevent unlimited increase in the size of the bundle. In the `2.x` architecture design, a \"plugin\" architecture design is adopted. Each feature of 1.x will be implemented in the form of Plugin in `2.x`.\n\nExisting plugins:\n- [pulldown](./pulldown.html)\n- [pullup](./pullup.html)\n- [scrollbar](./scroll-bar.html)\n- [slide](./slide.html)\n- [wheel](./wheel.html)\n- [zoom](./zoom.html)\n- [mouse-wheel](./mouse-wheel.html)\n- [observe-dom](./observe-dom.html)\n- [observe-image](./observe-image.html)\n- [nested-scroll](./nested-scroll.html)\n- [infinity](./infinity.html)\n- [movable](./movable.html)\n- [indicators](./indicators.html)\n\nYou can write a plugin by yourself to add new feature to `bs`. If you want do this, please refer to [How to write a plugin](./how-to-write.html).\n\n## Use a plugin\n\nUse plugins by calling the `BScroll.use()` static method. This has to be done before you call `new BScroll()`:\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Plugin from 'somewhere'\n\n  new BScroll('.wrapper', {\n    // pluginKey corresponds to the value of the static attribute pluginName on the Plugin class,\n    // otherwise the plugin cannot be instantiated\n    pluginKey: {}\n  })\n```\n\n## Use a method or property of plugins\n\nThe plugin may expose some methods or properties. These methods or properties are proxied to `bs` via `Object.defineProperty` method. For example, the `zoomTo` method is provided in the zoom plugin, which you can use by `bs.zoomTo`.\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Zoom)\n\n  const bs = new BScroll('#scroll-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    disableMouse: true,\n    useTransition: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n\n  bs.zoomTo(1.5, 0, 0) // zoomTo from Zoom Plugin is proxied to bs instance\n```\n\n## Use a event of plugins\n\nThe hooks exposed in the plugin will be delegated to `bs`. For example, you can listen to the `zoomStart` event, which is exposed in zoom plugin, in the following way:\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Zoom)\n  const bs = new BScroll('#scroll-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n\n  bs.on('zoomStart', () => {\n\n  })\n```\n\n## BetterScroll with all plugins\n\nConsidering the trouble of registering plugins one by one, if your project uses the full plugins of BetterScroll, we offer a once-in-a-lifetime solution.\n\n\n```js\n  import BScroll from 'better-scroll'\n\n  const bs = new BScroll('#scroll-wrapper', {\n    pullUpLoad: true,\n    pullDownRefresh: true,\n    scrollbar: true,\n    // and so on\n  })\n```\n\n::: warning\nimport all of BetterScroll may have a big impact on the size of your bundle, and as the function of BetterScroll expands, the size will increase unlimitedly, **try to import what you need**.\n:::\n\n::: warning\nNormally, you should pay attention to the properties and methods exposed by the BetterScroll instance, because the properties and methods on the plugin instance have been proxied to the bs. If you really need to care about the plugin instance, you can also use `bs.plugins ` to get all plugin information.\n\n```js\n  import BScroll from '@better-scroll/scroll'\n  import zoom from '@better-scroll/zoom'\n\n  BScroll.use(zoom)\n\n  const bs = new BScroll('.wrapper', {\n    zoom: true\n  })\n\n  console.log(bs.plugins.zoom)\n```\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/how-to-write.md",
    "content": "# How to write plugins\n\n### Conceive The Function Of The Plugin\n\n```js\n  import BScroll from '@better-scroll/core'\n  import MyPlugin from '@better-scroll/my-plugin'\n\n  BScroll.use(MyPlugin)\n\n  const bs = new BScroll('.wrapper', {\n    myPlugin: {\n      scrollText: 'I am scrolling',\n      scrollEndText: 'Scroll has ended'\n    },\n    // or\n    myPlugin: true\n  })\n\n  // Use the event that is proxied to bs by plugin\n  bs.on('printScrollEndText', (scrollEndText) => {\n    console.log(scrollEndText) // print \"Scroll has ended, position is (xx, yy)\"\n  })\n\n  // Use the method that is proxied to bs by plugin\n  bs.printScrollText() // print \"I am scrolling\"\n```\n\n### Write Plugin\n\n1. **TypeScript declare merging and expose plugin methods**\n\n```typescript\nimport BScroll from '@better-scroll/core'\n\nexport type MyPluginOptions = Partial<MyPluginConfig> | true\n\ntype MyPluginConfig = {\n  scrollText: string,\n  scrollEndText: string\n}\n\ninterface PluginAPI {\n  printScrollText(): void\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    myPlugin?: myPluginOptions\n  }\n\n  interface CustomAPI {\n    myPlugin: PluginAPI\n  }\n}\n```\n\nThe advantage of this is that when the `myPlugin` plugin is imported and BetterScroll is instantiated, there can be corresponding Options prompts and bs can have corresponding method prompts. Take the pulldown plugin as an example:\n\n<img data-zoomable :src=\"$withBase('/assets/images/tip1.png')\" alt=\"\">\n\n\n<img data-zoomable :src=\"$withBase('/assets/images/tip2.png')\" alt=\"\">\n\n2. **Write the plugin logic**\n\n    - **BetterScroll plugins need to be a class, and have the following characteristics:**\n\n      - The static pluginName property.\n      - Implement the PluginAPI interface (only if it is necessary to proxy the plugin method to bs).\n      - The first argument of the constructor is the BetterScroll instance `bs`. You can inject your own logic through the **event** or **hook** of bs.\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          static pluginName = 'myPlugin'\n          public options: MyPluginConfig\n          constructor(public scroll: BScroll){\n            this.handleOptions()\n\n            this.handleBScroll()\n\n            this.registerHooks()\n          }\n        }\n      ```\n\n    - **handleOptions**\n\n      Merge user options，narrow down it‘s type。\n\n      ```typescript\n        import { extend } from '@better-scroll/shared-utils'\n        export default class MyPlugin {\n          private handleOptions() {\n            const userOptions = (this.scroll.options.myPlugin === true\n              ? {}\n              : this.scroll.options.myPlugin) as Partial<MyPluginConfig>\n            const defaultOptions: MyPluginConfig = {\n              scrollText: 'I am scrolling',\n              scrollEndText: 'Scroll has ended'\n            }\n            this.options = extend(defaultOptions, userOptions)\n          }\n        }\n      ```\n\n    - **handleBScroll**\n\n      Proxy events and methods to the BetterScroll instance.\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          private handleBScroll() {\n            const propertiesConfig = [\n              {\n                key: 'printScrollText',\n                sourceKey: 'plugins.myPluginOptions.printScrollText'\n              }\n            ]\n            // myPlugin.printScrollText is proxied to bs.printScrollText\n            this.scroll.proxy(propertiesConfig)\n            // Proxy printScrollEndText event to bs\n            // Users can subscribe to events via bs.on('printScrollEndText', handler)\n            this.scroll.registerType(['printScrollEndText'])\n          }\n\n          printScrollText() {\n            console.log(this.options.scrollText)\n          }\n        }\n      ```\n\n    - **registerHooks**\n\n      Tap into the bs hook, implement the logic of the plugin, and dispatch custom events of the plugin.\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          private registerHooks() {\n            const scroll = this.scroll\n            scroll.on(scroll.eventTypes.scrollEnd, ({ x, y }) => {\n              scroll.trigger(\n                scroll.eventTypes.printScrollEndText,\n                `${this.options.scrollEndText}, position is (${x}, ${y})`\n              )\n            })\n          }\n        }\n      ```\n\nCongratulations, a simple BetterScroll plugin has been completed. If you need more complex plugin to meet your need, you can read [Events and Hooks](../guide/base-scroll-api.html#events-vs-hooks), it can help you to complete a fantastic plugin."
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/indicators.md",
    "content": "# indicators <Badge text=\"2.2.0\" />\n\n## Introduciton\n\nThe indicators provides the ability to link with another BetterScroll. With this, you can achieve effects such as **Parallax Scrolling**, **Magnifier**.\n\n::: tip\nThis is a very powerful and creative plugin.\n:::\n\n## Install\n\n```bash\nnpm install @better-scroll/indicators --save\n\n// or\n\nyarn add @better-scroll/indicators\n```\n\n## Usage\n\nFirst, install the plugin via the static method `BScroll.use()`\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\n\nBScroll.use(Indicators)\n```\n\npass correct [indicators options](./indicators.html#indicators-options).\n\n```js\nnew BScroll('.wrapper', {\n  indicators: {\n    // For details, please refer to the demo below\n    relationElement: someHTMLElement\n  }\n})\n```\n## Demos\n\n  - **Magnifier**\n\n    <demo qrcode-url=\"indicators/minimap\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/indicators/minimap.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/indicators/minimap.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/indicators/minimap.vue?style\n      </template>\n      <indicators-minimap slot=\"demo\"></indicators-minimap>\n    </demo>\n\n  - **Parallax Scrolling**\n\n    <demo qrcode-url=\"indicators/parallax-scroll\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?style\n      </template>\n      <indicators-parallax-scroll slot=\"demo\"></indicators-parallax-scroll>\n    </demo>\n\n## indicators options\n\n### relationElement\n\n  - **Type**: `HTMLElement`\n\n  The container element associated with another BetterScroll, just like the above demo, `div.scroll-indicator` is releationElement. **releationElement must be passed in by the user and has child elements**.\n\n### relationElementHandleElementIndex\n\n  - **Type**: `number`\n  - **Default**: `0`\n\n  Specify the child element of releationElement as the manipulated element. For details, please refer to the above demo.\n\n### ratio\n\n  - **Type**: `number | Ratio | undefined`\n  - **Default**: `undefined`\n\n  ```ts\n  type Ratio = {\n    // Specify the ratio of the scroll distance of x\n    x: number\n    // Specify the ratio of the scroll distance of y\n    y: number\n  }\n  ```\n  Specify the ratio of releationElement to the scrolling distance of BetterScroll. Usually,**the plugin will automatically** calculate the scroll ratio of the two, but you can also manually specify the ratio to achieve the effect of `Parallax Scrolling`. For details, please refer to the demo above.\n\n### interactive\n\n  - **Type**: `boolean | undefined`\n  - **Default**: `undefined`\n\n  Decide whether the relationElementHandleElementIndex of relationElement can interact. When it is set to `false`, it will not respond to touch / mouse events."
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/infinity.md",
    "content": "# infinity\n\nThe infinity plugin provides BetterScroll with unlimited scrolling capabilities. If you have a large amount of list data to render, you can use the infinity plugin, in which BetterScroll will only render a certain number of DOM elements, so that the page will continue to scroll smoothly when a large amount of data.\n\n> Note: Unless you have a lot of data rendering needs, use coreScroll.\n\n## Install\n\n```shell\nnpm install @better-scroll/infinity --save\n\n// or\n\nyarn add @better-scroll/infinity\n```\n\n## Usage\n\nFirst, install the plugin via the static method `BScroll.use()`\n\n```js\nimport BScroll from '@better-scroll/core'\nimport InfinityScroll from '@better-scroll/infinity'\n\nBScroll.use(InfinityScroll)\n```\n\nThen, To instantiate BetterScroll, you need to pass the related configuration item `infinity`:\n\n```typescript\nnew BScroll('.bs-wrapper', {\n  scrollY: true,\n  infinity: {\n    fetch(count) {\n      // Fetch data that is larger than count, the function is asynchronous, and it needs to return a Promise.。\n      // After you have successfully fetch the data, you need resolve an array of data (or resolve Promise).\n      // Each element of the array is list data, which will be rendered when the render method executes。\n      // If there is no data, you can resolve (false) to tell the infinite scroll list that there is no more data。\n    }\n    render(item, div?: HTMLElement) {\n      // Rendering each element node, item is data from fetch function\n      // div is an element which is recycled from document or undefined\n      // The function needs to return to a html element.\n    },\n    createTombstone() {\n      // Must return a tombstone DOM node.\n    }\n  }\n})\n```\n\n::: danger Note\n`fetch`, `render`, `createTombstone` must be implemented in accordance with the comments as above, otherwise an internal error will be reported.\n\nThe plugin relies on Promise internally. If the browser does not support it, the Promise Polyfill is required.\n:::\n\n## Demo\n\n<demo qrcode-url=\"infinity/\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/infinity/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/infinity/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/infinity/default.vue?style\n  </template>\n  <infinity-default slot=\"demo\"></infinity-default>\n</demo>"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/mouse-wheel.md",
    "content": "# mouse-wheel\n\nmouseWheel extends the capabilities of the BetterScroll mouse wheel.\n\n## Install\n\n```bash\nnpm install @better-scroll/mouse-wheel --save\n\n// or\n\nyarn add @better-scroll/mouse-wheel\n```\n\n::: tip\nCurrently supports mouse wheel: core, slide, wheel, pullup, pulldown plugins.\n:::\n\n## Basic Usage\n\nIn order to enable the mouseWheel plugin, you need to first import it, register the plugin through the static method `BScroll.use()`, and finally pass in the correct [mouseWheel option](./mouse-wheel.html#mousewheel-options)\n\n```js\n  import BScroll from '@better-scroll/core'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  BScroll.use(MouseWheel)\n\n  new BScroll('.bs-wrapper', {\n    //...\n    mouseWheel: {\n      speed: 20,\n      invert: false,\n      easeTime: 300\n    }\n  })\n```\n\n - **VerticalScroll Demo**\n\n  <demo :hide-qrcode=\"true\" :render-code=\"true\" qrcode-url=\"mouse-wheel/vertical-scroll\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?style\n    </template>\n    <mouse-wheel-vertical-scroll slot=\"demo\"></mouse-wheel-vertical-scroll>\n  </demo>\n\n- **HorizontalScroll Demo**\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/horizontal-scroll\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?style\n    </template>\n    <mouse-wheel-horizontal-scroll slot=\"demo\"></mouse-wheel-horizontal-scroll>\n  </demo>\n\n\n## Advanced Usage\n\nThe mouseWheel plugin can also be used with other plugins to increase the operation of the mouse wheel.\n\n- **mouseWheel & slide**\n\n  Operate [slide](./slide.html) with the mouse wheel.\n\n  - **HorizontalSlide Demo**\n\n    <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/horizontal-slide\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?style\n      </template>\n      <mouse-wheel-horizontal-slide slot=\"demo\"></mouse-wheel-horizontal-slide>\n    </demo>\n\n  - **VerticalSlide Demo**\n\n    <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/vertical-slide\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?style\n      </template>\n      <mouse-wheel-vertical-slide slot=\"demo\"></mouse-wheel-vertical-slide>\n    </demo>\n\n- **mouseWheel & pullup**\n\n  use mousewheel do [pullup](./pullup.html) operation.\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/pullup\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?style\n    </template>\n    <mouse-wheel-pullup slot=\"demo\"></mouse-wheel-pullup>\n  </demo>\n\n- **mouseWheel & pulldown**\n\n  use mousewheel do [pulldown](./pulldown.html)  operation.\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/pulldown\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?style\n    </template>\n    <mouse-wheel-pulldown slot=\"demo\"></mouse-wheel-pulldown>\n  </demo>\n\n- **mouseWheel & wheel**\n\n  use mousewheel do [wheel](./wheel.html) operation.\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/picker\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?style\n    </template>\n    <mouse-wheel-picker slot=\"demo\"></mouse-wheel-picker>\n  </demo>\n\n## mouseWheel options\n\n### speed\n\n  - **Type**: `number`\n  - **Default**: `20`\n\n  The speed at which the mouse wheel scrolls.\n\n### invert\n\n  - **Type**: `boolean`\n  - **Default**: `false`\n\n  When the value is true, it means that the scrolling direction of the wheel is opposite to that of BetterScroll.\n\n### easeTime\n\n  - **Type**: `number`\n  - **Default**: `300`(ms)\n\n  The duration of the scroll animation.\n\n### discreteTime\n\n  - **Type**: `number`\n  - **Default**: `400`(ms)\n\n  Because the mouse wheel is a discrete movement, there is no event type of **start**, **move**, **end**, so as long as no scroll is detected within `discreteTime`, then one scroll wheel action ends.\n\n  ::: warning\n  When integrated with [pulldown](./pulldown.html) plugin, `easeTime` and `discreteTime` will be **internally** modified to **reasonable fixed value** to trigger the `pullingDown` hook\n  :::\n\n### throttleTime\n\n  - **Type**: `number`\n  - **Default**: `0`(ms)\n\n  Since the scroll wheel is a high-frequency action, the trigger frequency can be limited by `throttleTime`. MouseWheel will cache the scrolling distance, and calculate the cached distance and scroll every throttleTime.\n\n  > Modifying throttleTime may cause discontinuous scrolling animation, please adjust it according to the actual scene.\n\n### dampingFactor\n\n  - **Type**: `number`\n  - **Default**: `0.1`\n\n  Damping factor, the value range is [0, 1]. When BetterScroll rolls out of the boundary, resistance needs to be applied to prevent the rolling range from being too large. The smaller the value, the greater the resistance.\n\n:::tip\nWhen `mouseWheel` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  mouseWheel: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  mouseWheel: {\n    speed: 20,\n    invert: false,\n    easeTime: 300,\n    discreteTime: 400,\n    throttleTime: 0,\n    dampingFactor: 0.1\n  }\n})\n```\n:::\n\n## Events\n\n### alterOptions\n  - **Arguments**: `MouseWheelConfig`\n    ```typescript\n      export interface MouseWheelConfig {\n        speed: number\n        invert: boolean\n        easeTime: number\n        discreteTime: number\n        throttleTime: number,\n        dampingFactor: number\n      }\n    ```\n  - **Triggered Timing**: The mousewheel begins to scroll, allowing to modify options to control certain behaviors during scrolling.\n\n### mousewheelStart\n  - **Arguments**: none\n  - **Triggered Timing**: The mousewheel starts.\n\n### mousewheelMove\n  - **Arguments**: `{ x, y }`\n  - `{ number } x`: The current x of BetterScroll\n  - `{ number } y`: The current y of BetterScroll\n  - **Type**: `{ x: number, y: number }`\n  - **Triggered Timing**: Mousewheel is scrolling\n\n### mousewheelEnd\n  - **Arguments**:`delta`\n  - **Type**: `WheelDelta`\n  ```typescript\n    interface WheelDelta {\n      x: number\n      y: number\n      directionX: Direction\n      directionY: Direction\n    }\n  ```\n  - **Triggered Timing**: If the mousewheel hook has not been triggered after `discreteTime`, a mousewheel action will be settled.\n\n  ::: danger Note\n  Due to the particularity of the mousewheel hook, the dispatch of mousewheelEnd does not mean the end of the scroll animation.\n  :::\n\n::: tip\nIn most scenarios, if you want to know the current scroll position of BetterScroll accurately, please listen to the scroll and scrollEnd hooks instead of the `mouseXXX` hooks.\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/movable.md",
    "content": "# movable\n\n## Introduction\n\nAdd move functionality for BetterScroll.\n\n## Install\n\n```bash\nnpm install @better-scroll/movable --save\n\n// or\n\nyarn add @better-scroll/movable\n```\n\n## Basic Usage\n\nimport `movable`, then call `BScroll.use()`.\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n\n  BScroll.use(Movable)\n```\n\npass in the correct configuration in options, for example:\n\n```js\n  new BScroll('.bs-wrapper', {\n    bindToTarget: true,\n    scrollX: true,\n    scrollY: true,\n    freeScroll: true,\n    bounce: true\n    movable: true // for movable plugin\n  })\n```\n\nThe following is related to `movable` plugin and [BetterScroll configuration](../guide/base-scroll-options.html):\n\n- **movable(for plugin)**\n\n  Enable zoom functionality, set it `true`.\n\n- **bindToTarget**\n\n  Must be set to `true` to actively bind the touch event to **the element to be moved**, because BetterScroll binds the touch event to **the wrapper element** by default.\n\n- **freeScroll**\n\n  Record the offset of x and y direction when finger moved, set it `true`. In addtional, **scrollX** and **scrollY** are also need to be true.\n\n- **scrollX**\n\n  Enable the scrolling ability in the x direction and set it to `true`.\n\n- **scrollY**\n\n  Enable the scrolling ability in the y direction and set it to `true`.\n\n- **bounce**\n\n  Specifies to turn on boundary rebound.\n\n  - **Examples**\n\n  ```js\n    {\n      bounce: true // Enable all directions,\n      bounce: {\n        left: true, // Enable the left\n        right: true, // Enable the right\n        top: false,\n        bottom: false\n      }\n    }\n  ```\n## Demo\n\n  - **Only one content**\n\n    Usually, there is only one content.\n\n    <demo qrcode-url=\"movable/default\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/default.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/default.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/default.vue?style\n      </template>\n      <movable-default slot=\"demo\"></movable-default>\n    </demo>\n\n  - **Multi content**\n\n    However, in some scenarios, there may be multiple content.\n\n    <demo qrcode-url=\"movable/multi-content\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/multi-content.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/multi-content.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/multi-content.vue?style\n      </template>\n      <movable-multi-content slot=\"demo\"></movable-multi-content>\n    </demo>\n\n## Advanced Usage\n\nWith [ zoom ](./zoom.html#introduction) plugin, increase the zoom capability.\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n  import Zoom from '@better-scroll/zoom'\n  new BScroll('.bs-wrapper', {\n    bindToTarget: true,\n    scrollX: true,\n    scrollY: true,\n    freeScroll: true,\n    bounce: true\n    movable: true // for movable plugin\n    zoom: { // for zoom plugin\n      start: 1,\n      min: 1,\n      max: 3\n    }\n  })\n```\n\n## Demo\n\n  :::warning\n  pc is not allowed, scan the qrcode.\n  :::\n\n  - **One Content**\n\n    <demo qrcode-url=\"movable/scale\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/scale.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/scale.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/scale.vue?style\n      </template>\n      <movable-scale slot=\"demo\"></movable-scale>\n    </demo>\n\n  - **Multi Content**\n\n    <demo qrcode-url=\"movable/multi-content-scale\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?style\n      </template>\n      <movable-multi-content-scale slot=\"demo\"></movable-multi-content-scale>\n    </demo>\n\n## Instance Methods\n\n### putAt(x, y, [time], [easing]) <Badge text='2.0.4' />\n  - **Arguments**\n    - `{PositionX} x`: x coordinate\n      - `PositionX: 'number | 'left' | 'right' | 'center'`\n    - `{PositionY} y`: y coordinate\n      - `PositionY: 'number | 'top' | 'bottom' | 'center'`\n    - `{number} [time]<Optional>`: Scroll animation duration\n    - `{EaseItem} [easing]<Optional>`: Ease effect configuration, refer to [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts), the default is `bounce` effect\n\n    Put the content element in a certain position. x and y can be not only numbers, but also corresponding strings.\n\n  - **Examples**\n\n  ```js\n  const bs = new BScroll('.bs-wrapper', {\n    bindToTarget: true,\n    scrollX: true,\n    scrollY: true,\n    freeScroll: true,\n    movable: true\n  })\n\n  // Placed in the center of the wrapper\n  bs.putAt('center', 'center', 0)\n\n  // Placed in the right-bottom corner of the wrapper, the animation duration is 1s\n  bs.putAt('right', 'bottom', 1000)\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/nested-scroll.md",
    "content": "# nested-scroll\n\n## Introduction\n\nCoordinates nested BetterScroll scrolling behavior.\n\n::: warning\n**v2.1.0** supports BetterScroll with **Multi Nesting**, with more powerful functions and better performance. Only **Double Nesting** is supported in old version, please upgrade to **v2.1.0** as soon as possible.\n:::\n\n::: tip\n**v2.1.0** Perfectly solves the problem that the click event of multi-level nested BetterScroll is dispatched multiple times.\n:::\n\n## Install\n\n```bash\nnpm install @better-scroll/nested-scroll --save\n\n// or\n\nyarn add @better-scroll/nested-scroll\n```\n\n## Usage\n\nimport the `nested-scroll` plugin and use it with the static method `BScroll.use()`\n\n```js\n  import BScroll from '@better-scroll/core'\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  BScroll.use(NestedScroll)\n```\n\nAfter the above steps are completed, `nestedScroll` is configured in BScroll's `options`.\n\n```js\n  // < v2.1.0\n  // parent bs\n  new BScroll('.outerWrapper', {\n    nestedScroll: true\n  })\n  // child bs\n  new BScroll('.innerWrapper', {\n    nestedScroll: true\n  })\n\n  // >= v2.1.0\n  // parent bs\n  new BScroll('.outerWrapper', {\n    nestedScroll: {\n      groupId: 'dummy-divide' // string or number\n    }\n  })\n  // child bs\n  new BScroll('.innerWrapper', {\n    nestedScroll: {\n      groupId: 'dummy-divide'\n    }\n  })\n```\n\nBetterScroll instances (bs) with the same `groupId` **share the same NestedScroll instance**(`ns`), `ns` will coordinate the scrolling behavior of each bs, once a bs is destroyed, `ns` will lose control of it, for example:\n\n```js\n// parent bs\nconst bs1 = new BScroll('.outerWrapper', {\n  nestedScroll: {\n    groupId: 'shared' // string or number\n  }\n})\n// child bs\nconst bs2 = new BScroll('.innerWrapper', {\n  nestedScroll: {\n    groupId: 'shared'\n  }\n})\n\nconsole.log(bs1.plugins.nestedScroll === bs2.plugins.nestedScroll) // true\n\n// nestedScroll no longer constrains bs2\n// nestedScroll no longer coordinates the scrolling behavior of bs1 and bs2\nbs2.destroy()\n```\n\n## Demo\n\n- **Nested vertical scroll <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/vertical\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?style\n    </template>\n    <nested-scroll-vertical slot=\"demo\"></nested-scroll-vertical>\n  </demo>\n\n- **Nested triple vertical scroll <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/triple-vertical\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?style\n    </template>\n    <nested-scroll-triple-vertical slot=\"demo\"></nested-scroll-triple-vertical>\n  </demo>\n\n- **Nested horizontal scroll <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/horizontal\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?style\n    </template>\n    <nested-scroll-horizontal slot=\"demo\"></nested-scroll-horizontal>\n  </demo>\n\n## Instance Methods <Badge text='2.1.0' />\n\n:::tip\nAll methods are proxied to BetterScroll instance, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(NestedScroll)\n\nconst bs1 = new BScroll('.parent-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\nconst bs2 = new BScroll('.child-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\n// purge nestedScroll\n// bs1 and bs2 share the same nestedScroll instance because they have the same groupId\nbs1.purgeNestedScroll() // Same as bs2.purgeNestedScroll()\n```\n:::\n\n### `purgeNestedScroll()`\n\n  - **Details**: Purge the nestedScroll that controls itself\n\n  ::: warning\n  Different `groupId` will generate different nestedScroll, and the same `groupId` will share the same nestedScroll, so you should call `purgeNestedScroll` at the right time (such as when the component is destroyed) to clean up the memory. Or you can call the destroy method of BetterScroll to remove itself from nestedScroll, for example:\n\n  ```js\n  const bs1 = new BScroll('.parent-wrapper', {\n    nestedScroll: {\n      groupId: 'dummy'\n    }\n  })\n\n  const bs2 = new BScroll('.child-wrapper', {\n    nestedScroll: {\n      groupId: 'dummy'\n    }\n  })\n\n  bs1.destroy() // nestedScroll no longer constrains bs1\n  bs2.destroy() // nestedScroll no longer constrains bs2\n  ```\n  :::\n\n## Static Methods <Badge text='2.1.0' />\n\n### `getAllNestedScrolls()`\n\n  - **Details**: Get all current nestedScroll instances\n\n  - **Returns**: An array of nestedScroll instances\n\n  ```typescript\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  const nestedScrolls: NestedScroll[] = NestedScroll.getAllNestedScrolls()\n  ```\n\n### `purgeAllNestedScrolls()`\n\n  - **Details**: Purge all current nestedScroll instances\n\n  ```typescript\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  // No longer constrain any BetterScroll instances\n  NestedScroll.purgeAllNestedScrolls()\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/observe-dom.md",
    "content": "# observe-dom\nEnable detection of content and content child DOM changes. When the plugin is used and these DOM elements change, `bs.refresh()` will be triggered. The observe-dom plugin has the following features:\n\n- Debounce feature for CSS attributions which change frequently\n- If the scroll elements change occurs during the scroll animation, refresh will not be triggered.\n\n## Install\n\n```bash\nnpm install @better-scroll/observe-dom --save\n\n// or\n\nyarn add @better-scroll/observe-dom\n```\n\n## Usage\n\n  ```js\n    import BScroll from '@better-scroll/core'\n    import ObserveDOM from '@better-scroll/observe-dom'\n    BScroll.use(ObserveDOM)\n\n    new BScroll('.bs-wrapper', {\n      //...\n      observeDOM: true // init observe-dom plugin\n    })\n  ```\n\n## Demo\n\n  <demo qrcode-url=\"observe-dom/\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/observe-dom/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/observe-dom/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/observe-dom/default.vue?style\n    </template>\n    <observe-dom-default slot=\"demo\"></observe-dom-default>\n  </demo>\n\n:::warning\nFor version <= `2.0.6`, because the internal implementation of the plugin uses [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver), it cannot detect whether the load of the `img` Element is complete, so for images with uncertain heights inside the content, you need to wait for the image to load before calling `bs.refresh()` to recalculate the scrollable size. If the browser does not support MutationObserver, the fallback inside the plugin is to recalculate the scrollable size every second.\n:::\n\n:::tip\nIn the v2.1.0 version, the [observe-image](./observe-image) plugin is added to detect the loading of the img tag, so the two can be combined to complement the **autorefresh** ability to update BetterScroll every time.\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/observe-image.md",
    "content": "# observe-image <Badge text='2.1.0' />\n\n## Introduction\n\nTurn on the detection of the loading of image elements in the wrapper child element. Regardless of whether the image is loaded successfully or not, BetterScroll's `refresh` method is automatically called to recalculate the scrollable width or height, which was supported in v2.1.0.\n\n:::tip\nFor scenes where CSS has been used to determine the width and height of the image, this plugin should not be used, because each call to refresh will affect performance. You only need it if the width or height of the image is uncertain.\n:::\n\n## Install\n\n```bash\nnpm install @better-scroll/observe-image --save\n\n// or\n\nyarn add @better-scroll/observe-image\n```\n\n## Usage\n\n```js\n  import BScroll from '@better-scroll/core'\n  import ObserveImage from '@better-scroll/observe-image'\n  BScroll.use(ObserveImage)\n\n  new BScroll('.bs-wrapper', {\n    //...\n    observeImage: true\n  })\n```\n\n## Demo\n\n  <demo qrcode-url=\"observe-image/\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/observe-image/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/observe-image/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/observe-image/default.vue?style\n    </template>\n    <observe-image-default slot=\"demo\"></observe-image-default>\n  </demo>\n\n## observeImage Options\n\n:::tip\nWhen `observeImage` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  observeImage: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  observeImage: {\n    debounceTime: 100 // ms\n  }\n})\n```\n:::\n\n### debounceTime\n\n  - **Type**: `number`\n  - **Default**: `100`\n\n    After detecting the success or failure of the image loading, the refresh method will be called after **debounceTime** milliseconds to recalculate the scrollable height or width. If multiple images load successfully or fail within debounceTime milliseconds, the **refresh** method will only be called once.\n\n    :::tip\n    When **debounceTime** is 0, the **refresh** method will be called immediately instead of using **setTimeout**.\n    :::"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/pulldown.md",
    "content": "# pulldown\n\n## Introduction\n\nThe pulldown plugin provides BetterScroll with the ability to monitor pulldown operation.\n\n## Install\n\n```bash\nnpm install @better-scroll/pull-down --save\n\n// or\n\nyarn add @better-scroll/pull-down\n```\n\n## Usage\n\nFirst, install the plugin via the static method `BScroll.use()`\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n```\n\npass in the correct configuration in [options](./pulldown.html#pulldownrefresh-options), for example:\n\n```js\nnew BScroll('.bs-wrapper', {\n  pullDownRefresh: true\n})\n```\n\n## Demo\n\n- **Basic Usage**\n\n<demo qrcode-url=\"pulldown/default\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pulldown/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pulldown/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pulldown/default.vue?style\n  </template>\n  <pulldown-default slot=\"demo\"></pulldown-default>\n</demo>\n\n- **Sina-Weibo <Badge text='2.4.0' />**\n\n<demo qrcode-url=\"pulldown/sina\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?style\n  </template>\n  <pulldown-sina-weibo slot=\"demo\"></pulldown-sina-weibo>\n</demo>\n\nIn order to match the effects of App, in version v2.4.0, pulldown has made some changes and is compatible with previous versions. During a pulldown procedure, there are three internal circulation states, and the states are irreversible. They are as follows:\n\n1. **default**\n\n  The initial state.\n\n2. **moving**\n\n  Moving state, this state represents that the user's finger is manipulating BetterScroll, and the finger is keeping in touch. In this state, BetterScroll will dispatch two events.\n\n  - **enterThreshold**\n\n    Dispatched when BetterScroll scrolls **into** the pulldown threshold area. Inside this event, you can do the logic of texts initialization, such as prompting the user to \"pull down to refresh\"\n  \n  - **leaveThreshold**\n\n    Dispatched when BetterScroll scrolls **out of** the pulldown threshold area. You can prompt the user to \"Release finger\"\n\n3. **fetching**\n\n  Once the finger went away, the pullingDown event is triggered to execute the logic of fetching data\n\nThe state change can only be `default -> moving -> fetching` or `default -> moving`. The latter means that at the moment the user's finger is released, the conditions for triggering the pullingDown event are not met.\n\n## pullDownRefresh Options\n\n### threshold\n\n  - **Type**: `number`\n  - **Default**: `90`\n\n  Configure the top pull-down distance to determine dispatching `pullingDown` hooks.\n\n### stop\n\n  - **Type**: `number`\n  - **Default**: `40`\n\n  Rebound distance. After BetterScroll dispatches the `pullingDown` hook, it will immediately execute the rebound animation.\n\n:::tip\nWhen `pullDownRefresh` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: {\n    threshold: 90,\n    stop: 40\n  }\n})\n```\n:::\n\n## Instance Methods\n\n:::tip\nAll methods are proxied to BetterScroll instance, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nconst bs = new BScroll('.bs-wrapper', {\n  pullDownRefresh: true\n})\n\nbs.finishPullDown()\nbs.openPullDown({})\nbs.autoPullDownRefresh()\n```\n:::\n\n### `finishPullDown()`\n\n  - **Details**: End the pull-down refresh behavior.\n\n  ::: warning\n  Every time the `pullingDown` hook is triggered, you should **actively call** `finishPullDown()` to tell BetterScroll to be ready for the next pullingDown hook.\n  :::\n\n### `openPullDown(config: PullDownRefreshOptions = {})`\n\n  - **Details**: Turn on the pull-down refresh dynamically.\n  - **Arguments**:\n    - `{ PullDownRefreshOptions } config`: Modify the option of the pulldown plugin\n    - `PullDownRefreshOptions`:\n\n    ```typescript\n    export type PullDownRefreshOptions = Partial<PullDownRefreshConfig> | true\n\n    export interface PullDownRefreshConfig {\n      threshold: number\n      stop: number\n    }\n    ```\n\n  ::: warning\n  The **openPullDown** method should be used with **closePullDown**, because in the process of generating the pulldown plugin, the pull-down refresh action has been automatically monitored.\n  :::\n\n### `closePullDown()`\n\n  - **Details**: Turn off the pull-down refresh dynamically.\n\n## Events\n\n### `pullingDown`\n\n  - **Arguments**: None\n  - **Trigger**: A `pullingDown` event is fired when the top pull-down distance is greater than the `threshold` value after touchend.\n\n::: danger Note\nAfter the pull-down refresh action is detected, the consumption opportunity of the `pullingDown` hook is only once, so you need to call `finishPullDown()` to tell BetterScroll to provide the next consumption opportunity of the `pullingDown` event.\n:::\n\n### `enterThreshold` <Badge text='2.4.0' />\n\n  - **Arguments**: None\n  - **Trigger**: when pulldown is in the **moving** state and **enters** threshold area.\n\n### `leaveThreshold` <Badge text='2.4.0' />\n\n  - **Arguments**: None\n  - **Trigger**: when pulldown is in the **moving** state and **leaves** threshold area."
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/pullup.md",
    "content": "# pullup\n\n## Introduction\n\nThe pullup plugin provides BetterScroll with the ability to monitor pulldown operation.\n\n## Install\n\n```bash\nnpm install @better-scroll/pull-up --save\n\n// or\n\nyarn add @better-scroll/pull-up\n```\n\n## Usage\n\nFirst, install the plugin via the static method `BScroll.use()`\n\n```js\n  import BScroll from '@better-scroll/core'\n  import PullUp from '@better-scroll/pull-up'\n\n  BScroll.use(PullUp)\n```\n\npass in the correct configuration in [options](./pullup.html#pullupload-options), for example:\n\n```js\n  new BScroll('.bs-wrapper', {\n    pullUpLoad: true\n  })\n```\n\n## Demo\n\n<demo qrcode-url=\"pullup/\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pullup/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pullup/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pullup/default.vue?style\n  </template>\n  <pullup-default slot=\"demo\"></pullup-default>\n</demo>\n\n## pullUpLoad Options\n\n### threshold\n\n  - **Type**: `number`\n  - **Default**: `0`\n\n  The threshold for triggering a `pullingUp` hook.\n\n:::tip\nWhen `pullUpLoad` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: {\n    threshold: 0\n  }\n})\n```\n:::\n\n## Instance Methods\n\n:::tip\nAll methods are proxied to BetterScroll instance, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullUp from '@better-scroll/pull-up'\n\nBScroll.use(PullUp)\n\nconst bs = new BScroll('.bs-wrapper', {\n  pullUpLoad: true\n})\n\nbs.finishPullUp()\nbs.openPullUp({})\nbs.closePullUp()\n```\n:::\n\n### `finishPullUp()`\n\n  - **Details**: Finish the pullUpLoad behavior.\n\n  ::: warning\n  Every time you trigger the `pullingUp` hook, you should **actively call** `finishPullUp()` to tell BetterScroll to be ready for the next pullingUp hook.\n  :::\n\n### `openPullUp(config: PullUpLoadOptions = {})`\n\n  - **Details**: Turn on the pullUpLoad dynamically.\n  - **Arguments**:\n    - `{ PullDownRefreshOptions } config`: Modify the option of the pullup plugin\n    - `PullDownRefreshOptions`:\n\n    ```typescript\n    export type PullUpLoadOptions = Partial<PullUpLoadConfig> | true\n\n    export interface PullUpLoadConfig {\n      threshold: number\n    }\n    ```\n\n  ::: warning\n  The **openPullUp** method should be used with **closePullUp**, because in the process of generating the pullup plugin, the pullUpLoad action has been automatically monitored.\n  :::\n\n### `closePullUp()`\n\n  - **Details**: Turn off pullUpLoad dynamically.\n\n### `autoPullUpLoad()`\n\n  - **Details**：Auto pullUp.\n\n## Events\n\n### `pullingUp`\n\n  - **Arguments**: None\n  - **Trigger**: When the distance to the bottom is less than the value of `threshold`, a `pullingUp` event is triggered.\n\n  > When threshold is a positive number, it means `pullingUp` is triggered when the threshold pixel is away from the scroll boundary. On the contrary, it means that the event will be triggered when it crosses the scroll boundary.\n\n::: danger Note\nAfter the pullUpLoad action is detected, the consumption opportunity of the `pullingUp` event is only once, so you need to call `finishPullUp()` to tell BetterScroll to provide the next consumption opportunity of the `pullingUp` event.\n:::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/scroll-bar.md",
    "content": "# scrollbar\n\n## Introduciton\n\nThe scrollbar plugin provides a nice scrollbar for BetterScroll.\n\n:::tip\nFor v2.2.0, users can provide custom scroll bars.\n:::\n\n## Install\n\n```bash\nnpm install @better-scroll/scroll-bar --save\n\n// or\n\nyarn add @better-scroll/scroll-bar\n```\n\n## Usage\n\nFirst, install the plugin via the static method `BScroll.use()`\n\n```js\nimport BScroll from '@better-scroll/core'\nimport ScrollBar from '@better-scroll/scroll-bar'\n\nBScroll.use(ScrollBar)\n```\n\npass correct [scrollbar options](./scroll-bar.html#scrollbar-options)\n\n```js\nnew BScroll('.bs-wrapper', {\n  scrollY: true,\n  scrollbar: true\n})\n```\n\n## Demo\n\n  - **Vertical Scrollbar**\n\n    <demo qrcode-url=\"scrollbar/vertical\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?style\n      </template>\n      <scrollbar-vertical slot=\"demo\"></scrollbar-vertical>\n    </demo>\n\n  - **Horizontal Scrollbar**\n\n    <demo qrcode-url=\"scrollbar/horizontal\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?style\n      </template>\n      <scrollbar-horizontal slot=\"demo\"></scrollbar-horizontal>\n    </demo>\n\n  - **Custom Scrollbar**\n\n    <demo qrcode-url=\"scrollbar/custom\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?style\n      </template>\n      <scrollbar-custom slot=\"demo\"></scrollbar-custom>\n    </demo>\n\n  - **With Mousewheel**\n\n    <demo qrcode-url=\"scrollbar/mousewheel\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?style\n      </template>\n      <scrollbar-mousewheel slot=\"demo\"></scrollbar-mousewheel>\n    </demo>\n\n\n## scrollbar options\n\n### fade\n\n  - **Type**: `boolean`\n  - **Default**: `true`\n\n  When the scroll stops, the scrollbar fades out.\n\n### interactive\n\n  - **Type**: `boolean`\n  - **Default**: `false`\n\n  Whether scrollbar can interacted with.\n### customElements <Badge text=\"2.2.0\" />\n\n  - **Type**: `HTMLElement[]`\n  - **Default**: `[]`\n\n  The user provides a custom scroll bar.\n\n  ```js\n  // horizontal\n  const horizontalEl = document.getElementById('User-defined scrollbar')\n  new BScroll('.bs-wrapper', {\n    scrollY: true,\n    scrollbar: {\n      customElements: [horizontalEl]\n    }\n  })\n  // vertical\n  const verticalEl = document.getElementById('User-defined scrollbar')\n  new BScroll('.bs-wrapper', {\n    scrollY: false,\n    scrollX: true,\n    scrollbar: {\n      customElements: [verticalEl]\n    }\n  })\n  // freeScroll\n  const horizontalEl = document.getElementById('User-defined scrollbar')\n  const verticalEl = document.getElementById('User-defined scrollbar')\n  new BScroll('.bs-wrapper', {\n    freeScroll: true,\n    scrollbar: {\n      // When there are two scrollbars\n      // the first element of the array is the horizontal\n      customElements: [horizontalEl, verticalEl]\n    }\n  })\n  ```\n\n### minSize <Badge text=\"2.2.0\" />\n\n  - **Type**: `number`\n  - **Default**: `8`\n\n  The minimum size of the scrollbar. When the user provides a custom scrollbar, this configuration is invalid.\n\n### scrollbarTrackClickable <Badge text=\"2.2.0\" />\n\n  - **Type**: `boolean`\n  - **Default**: `false`\n\n  Whether the scrollbar track allows clicking.\n\n  **Note**：When enabling this configuration, please ensure that the `click` of BetterScroll Options is true, otherwise the click event cannot be triggered. [The reason is here](../FAQ/diagnosis.html#question-4-why-are-the-listeners-for-all-click-events-inside-betterscroll-content-not-triggered).\n\n  ```js\n  new BScroll('.bs-wrapper', {\n    scrollY: true,\n    click: true // essential\n    scrollbar: {\n      scrollbarTrackClickable: true\n    }\n  })\n  ```\n\n### scrollbarTrackOffsetType <Badge text=\"2.2.0\" />\n\n  - **Type**: `string`\n  - **Default**: `'step'`\n\n  After the scroll bar track is clicked, the calculation method of the scroll distance is the same as the browser's performance by default. It can be configured as `'clickedPoint'`, which means the scroll bar is scrolled to the clicked position.\n\n### scrollbarTrackOffsetTime <Badge text=\"2.2.0\" />\n\n  - **Type**: `number`\n  - **Default**: `300`\n\n  the scroll time after the scrollbar track is clicked.\n\n### fadeInTime <Badge text=\"2.4.0\" />\n\n  - **Type**: `number`\n  - **Default**: `250`\n\n  The duration of the animation when the scrollbar fades in.\n\n### fadeOutTime <Badge text=\"2.4.0\" />\n\n  - **Type**: `number`\n  - **Default**: `500`\n\n  The duration of the animation when the scrollbar fades out.\n\n:::tip\nWhen `scrollbar` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  scrollbar: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  scrollbar: {\n    fade: true,\n    interactive: false,\n    // supported in v2.2.0\n    customElements: [],\n    minSize: 8,\n    scrollbarTrackClickable: false,\n    scrollbarTrackOffsetType: 'step',\n    scrollbarTrackOffsetTime: 300,\n    // supported in v2.4.0\n    fadeInTime: 250,\n    fadeOutTime: 500\n  }\n})\n```\n:::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/slide.md",
    "content": "# slide\n\n## Introduction\n\nslide expands the ability of carousel for BetterScroll.\n\n## Install\n\n```bash\nnpm install @better-scroll/slide --save\n\n// or\n\nyarn add @better-scroll/slide\n```\n\n## Usage\n\nimport `slide`, then call `BScroll.use()`.\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n```\n\npass in the correct configuration in options, for example:\n\n```js\n  new BScroll('.bs-wrapper', {\n    scrollX: true,\n    scrollY: false,\n    slide: {\n      threshold: 100\n    },\n    momentum: false,\n    bounce: false,\n    stopPropagation: true\n  })\n```\n\nThe following is related to `slide` plugin and [BetterScroll configuration](../guide/base-scroll-options.html):\n\n- **slide(for plugin)**\n\n  Enable zoom functionality. That is to say, the zoom plugin won't work without the zoom options, see [slide options](./slide.html#slide-options).\n\n- **scrollX**\n\n  When the value is true, set the direction of slide to **horizontal**.\n\n- **scrollY**\n\n  When the value is true, set the direction of slide to **vertical**. **Note: scrollX and scrollY cannot be set to true at the same time**\n\n- **momentum**\n\n  When using slide, this value needs to be set to false to avoid the problem of flickering during fast scrolling caused by inertial animation and the problem of scrolling multiple pages at a time during fast sliding.\n\n- **bounce**\n\n  The bounce value needs to be set to false, otherwise it will flicker when the loop is true.\n\n- **probeType**\n\n  If you want to register the `slideWillChange` event to get the change of the PageIndex of the slide in real time when the user drags the slide, you need to set the probeType value to 2 or 3.\n\n## Terms about slide\n\nIn general, the layout of BetterScroll's slide is as follows:\n\n```html\n<div class=\"slide-wrapper\">\n  <div class=\"slide-content\">\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n  <div/>\n<div/>\n```\n\n- **slide-wrapper**\n\n  slide container.\n\n- **slide-content**\n\n  slide scroll element.\n\n- **slide-page**\n\n  slide is composed of multiple Pages.\n\n  ::: tip\n  In the loop scenario, two more pages will be inserted before and after the slide-content to achieve the visual effect of seamless scrolling.\n  :::\n\n  :::danger\n  The slide-content must have at least one slide-page, if there is only one page, the loop configuration is invalid\n  :::\n\n## Demo\n\n- **Horizontal Slide**\n\n  <demo qrcode-url=\"slide/banner\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/banner.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/banner.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/banner.vue?style\n    </template>\n    <slide-banner slot=\"demo\"></slide-banner>\n  </demo>\n\n- **Fullscreen Slide**\n\n  <demo qrcode-url=\"slide/fullpage\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/fullpage.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/fullpage.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/fullpage.vue?style\n    </template>\n    <slide-fullpage slot=\"demo\"></slide-fullpage>\n  </demo>\n\n- **Vertical Slide**\n\n  <demo qrcode-url=\"slide/vertical\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/vertical.vue?style\n    </template>\n    <slide-vertical slot=\"demo\"></slide-vertical>\n  </demo>\n\n- **Dynamic Slide <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"slide/dynamic\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/dynamic.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/dynamic.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/dynamic.vue?style\n    </template>\n    <slide-dynamic slot=\"demo\"></slide-dynamic>\n  </demo>\n\n- **Initial PageIndex Slide <Badge text='2.3.0' />**\n\n  <demo qrcode-url=\"slide/specified\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/specified-index.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/specified-index.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/specified-index.vue?style\n    </template>\n    <slide-specified-index slot=\"demo\"></slide-specified-index>\n  </demo>\n\n  ::: tip\n  Note: When setting `useTransition = true`, there may be flickering on some iPhone systems. You need to add the following two additional styles to each `slide-page` like the code in the above demo:\n\n  ```css\n  transform: translate3d(0,0,0)\n  backface-visibility: hidden\n  ```\n  :::\n\n## slide options\n\n:::tip\nWhen `slide` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  slide: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  slide: {\n    loop: true,\n    threshold: 0.1,\n    speed: 400,\n    easing: ease.bounce,\n    listenFlick: true,\n    autoplay: true,\n    interval: 3000\n  }\n})\n```\n:::\n\n### loop\n\n  - **Type**: `boolean`\n  - **Default**: `true`\n\n  Is it possible to loop. But when there is only one element, this setting does not take effect.\n\n### autoplay\n\n  - **Type**: `boolean`\n  - **Default**: `true`\n\n  Whether to enable auto play.\n\n### interval\n\n  - **Type**: `number`\n  - **Default**: `3000`\n\n  The interval before the next play.\n\n### speed\n\n  - **Type**: `number`\n  - **Default**: `400`\n\n  the default duration of Page animation.\n\n### easing\n\n  - **Type**: `EaseItem`\n    - `{ string } style`: for `transition-timing-function`\n    - `{ Function } fn`: When setting `useTransition:false`, the animation curve is determined by `easing.fn`.\n  - **Default**:\n  ```js\n  {\n    style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',\n    fn: function(t: number) {\n      return 1 - --t * t * t * t\n    }\n  }\n  ```\n\n  Scrolling easing effect.\n\n### listenFlick\n\n  - **Type**: `boolean`\n  - **Default**: `true`\n\n  When quickly flicking across the slide area, it will trigger the switch to the previous/next page. Set listenFlick to false to turn off the effect.\n\n### threshold\n\n  - **Type**: `number`\n  - **Default**: `0.1`\n\n  :::tip\n  When the scrolling distance is less than the threshold, the switch to the next or previous one will not be triggered.\n\n  It can be set to a decimal, such as 0.1, or an integer, such as 100. When the value is a decimal, the threshold is treated as a percentage, and the final threshold is `slideWrapperWidth * threshold` or `slideWrapperHeight * threshold`. When the value is an integer, the threshold is threshold.\n  :::\n\n  The threshold of the next or previous Page.\n\n### startPageXIndex <Badge text='2.3.0' />\n\n  - **Type**: `number`\n  - **Default**: `0`\n\n  Initial pageXIndex when slide is created.\n\n### startPageYIndex <Badge text='2.3.0' />\n\n  - **Type**: `number`\n  - **Default**: `0`\n\n  Initial pageYIndex when slide is created.\n\n## Instance Methods\n\n:::tip\nAll methods are proxied to BetterScroll instance, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst bs = new BScroll('.bs-wrapper', {\n  slide: true\n})\n\nbs.next()\nbs.prev()\nbs.getCurrentPage()\n```\n:::\n\n### next([time], [easing])\n\n  - **Arguments**:\n    - `{ number } time<Optional>`: Animation duration, default is `options.speed`\n    - `{ EaseItem } easing<Optional>`: Ease effect configuration, refer to [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts), the default is `bounce` effect\n    ```typescript\n    interface EaseItem {\n      style: string\n      fn(t: number): number\n    }\n    ```\n\n  Scroll to the next page.\n\n### prev([time], [easing])\n\n  - **Arguments**:\n    - `{ number } time<Optional>`: Animation duration, default is `options.speed`\n    - `{ EaseItem } easing<Optional>`: Ease effect configuration, refer to [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts), the default is `bounce` effect\n\n  Scroll to the previous page.\n\n### goToPage(pageX, pageY, [time], [easing])\n\n  - **Arguments**:\n    - `{ number } pageX`: Scroll horizontally to the Page of the corresponding index, the subscript starts from 0\n    - `{ number } pageY`: Scroll vertically to the Page of the corresponding index, the subscript starts from 0\n    - `{ number } time<Optional>`: Animation duration, default is `options.speed`\n    - `{ EaseItem } easing<Optional>`: Ease effect configuration, refer to [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts), the default is `bounce` effect\n\n  Scroll to the specified page.\n\n### getCurrentPage()\n\n  - **Returns**: `page`\n  ```typescript\n  type Page = {\n    x: number,\n    y: number,\n    pageX: number, // pageIndex in horizontal direction\n    pageY: number  // pageIndex in vertical direction\n  }\n  const page:Page = BScroll.getCurrentPage()\n  ```\n\n  Get currentPage.\n\n### startPlay()\n\n  If the loop configuration is turned on, manually turn on autoplay.\n\n### pausePlay()\n\n  If the loop configuration is turned on, manually turn off autoplay.\n\n## Events\n\n### slideWillChange\n\n  - **Arguments**: `page` object\n    - `{ number } x`: The x value of the page to be displayed\n    - `{ number } y`: The y value of the page to be displayed\n    - `{ number } pageX`: The index value of the horizontal page to be displayed, the subscript starts from 0\n    - `{ number } pageY`: The index value of the vertical page to be displayed, the subscript starts from 0\n\n  - **Trigger timing**: When the currentPage value of slide is about to change\n\n  - **Usage**:\n\n  In the banner, it is often accompanied by a dot legend to indicate which page the current banner is on, such as the \"Horizontal Slide\" example above. When the user drags the banner to the next one, we hope the dot legend below will change synchronously. As shown below\n\n  <img data-zoomable :src=\"$withBase('/assets/images/slide-pageindex.png')\" style=\"maxHeight: 200px\" alt=\"banner示例图\">\n\n  This effect can be achieved by register the `slideWillChange` event. code show as below:\n\n  ```js\n    let currentPageIndex\n    const slide = new BScroll(this.$refs.slide, {\n      scrollX: true,\n      scrollY: false,\n      slide: {\n        threshold: 100\n      },\n      momentum: false,\n      bounce: false,\n      probeType: 2\n    })\n    slide.on('slideWillChange', (page) => {\n      currentPageIndex = page.pageX\n    })\n  ```\n\n### slidePageChanged <Badge text='2.1.0' />\n\n  - **Arguments**: `page` object\n    - `{ number } x`: The x value of the current page\n    - `{ number } y`: The y value of the current page\n    - `{ number } pageX`: The index value of the horizontal page, the subscript starts from 0\n    - `{ number } pageY`: The index value of the vertical page, the subscript starts from 0\n\n  - **Trigger timing**: When slide page has changed\n\n  ```js\n    const slide = new BScroll(this.$refs.slide, {\n      scrollX: true,\n      scrollY: false,\n      slide: true,\n      momentum: false,\n      bounce: false\n    })\n    slide.on('slidePageChanged', (page) => {\n      currentPageIndex = page.pageX\n    })\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/wheel.md",
    "content": "# wheel\n\n## Introduction\n\nThe wheel plugin is the cornerstone for implementing similar iOS Picker components.\n\n## Install\n\n```bash\nnpm install @better-scroll/wheel --save\n\n// or\n\nyarn add @better-scroll/wheel\n```\n\n## Usage\n\nimport `wheel`, then call `BScroll.use()`.\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n```\n\npass in the correct configuration in options, for example:\n\n```js\n  let bs = new BScroll('.bs-wrapper', {\n    wheel: true // wheel options\n  })\n```\n\n:::tip\nWheel options is `true` or object, otherwise the plugin is invalid, please refer to [wheel options](./wheel.html#wheel-options).\n:::\n\n::: danger\nBetterScroll combined with the Wheel plugin is just the JS logic part of the Picker effect, and the DOM template is user-implemented. Fortunately, for most Picker scenarios, we have corresponding examples.\n:::\n\n- **Basic usage**\n\n  <demo qrcode-url=\"picker/one-column\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/one-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/one-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/one-column.vue?style\n    </template>\n    <picker-one-column slot=\"demo\"></picker-one-column>\n  </demo>\n\n  Single-column Picker is a more common effect. You can use `selectedIndex` to configure the item that initializes the corresponding index. `wheelDisabledItemClass` configures the item you want to disable to simulate the Web Select tag's disable option.\n\n- **Double-column Picker**\n\n  <demo qrcode-url=\"picker/double-column\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/double-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/double-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/double-column.vue?style\n    </template>\n    <picker-double-column slot=\"demo\"></picker-double-column>\n  </demo>\n\n  The JS logic part is not much different from the single-column selector. You will find that there is no correlation between the two column selectors because they are two different BetterScroll instances. If you want to achieve the effect of the provincial and city linkage, then add a part of the code to make the two BetterScroll instances can be associated. Please see the next example:\n\n- **Linkage Picker**\n\n  <demo qrcode-url=\"picker/linkage-column\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?style\n    </template>\n    <picker-linkage-column slot=\"demo\"></picker-linkage-column>\n  </demo>\n\n  The effect of the city linkage Picker must be linked through the JS part of the logic to the different instances of BetterScroll.\n\n## wheel options\n\n:::tip\nWhen `wheel` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  wheel: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  wheel: {\n    wheelWrapperClass: 'wheel-scroll',\n    wheelItemClass: 'wheel-item',\n    rotate: 25,\n    adjustTime: 400,\n    selectedIndex: 0,\n    wheelDisabledItemClass: 'wheel-disabled-item'\n  }\n})\n```\n:::\n\n### selectedIndex\n\n  - **Type**: `number`\n  - **Default**：`0`\n\n  Instantiate the Wheel, the `selectedIndex` item is selected by default, and the index starts from 0.\n\n### rotate\n\n  - **Type**: `number`\n  - **Default**: `25`\n\n  When rolling the wheel, the degree of bending of the wheel item.\n\n### adjustTime\n\n  - **Type**: `number`\n  - **Default**: `400`(ms)\n\n  When an item is clicked, the duration of scroll.\n\n### wheelWrapperClass\n\n  - **Type**: `string`\n  - **Default**: `wheel-scroll`\n\n  The className of the scroll element, where \"scroll element\" refers to the `content` element of BetterScroll.\n\n### wheelItemClass\n\n  - **Type**: `string`\n  - **Default**: `wheel-item`\n\n  The style of the child elements of the scroll element.\n\n### wheelDisabledItemClass\n\n  - **Type**: `string`\n  - **Default**: `wheel-disabled-item`\n\n  The child element that you want to disable in the scroll element is similar to the effect of the disabled option in the select element. The wheel plugin judges whether the item is designated as disabled according to the `wheelDisabledItemClass` configuration.\n\n## Instance Methods\n\n:::tip\nAll methods are proxied to BetterScroll instance, for example:\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n\nconst bs = new BScroll('.bs-wrapper', {\n  wheel: true\n})\n\nbs.getSelectedIndex()\nbs.wheelTo(1, 300)\n```\n:::\n\n### getSelectedIndex()\n\n  - **Returns**: The index of the currently selected item, the subscript starts from 0\n\n  Get the index of the currently selected item.\n\n### wheelTo(index = 0, time = 0, [ease])\n\n  - **Arguments**:\n    - `{ number } index`\n    - `{ number } time`: Animation duration\n    - `{ number } ease<Optional>`: Ease effect configuration, refer to [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts), the default is `bounce` effect\n    ```typescript\n    interface EaseItem {\n      style: string\n      fn(t: number): number\n    }\n    ```\n\n  Scroll to the list item corresponding to the index.\n\n### stop() <Badge text='2.1.0' />\n\n  Force the scrolling BetterScroll to stop and snap to the position of the wheel-item closest to the current one.\n\n### restorePosition() <Badge text='2.1.0' />\n\n  Force the scrolling BetterScroll to stop and return to the position before the scrolling started.\n\n::: tip\nThe above two methods are only valid for **the scrolling BetterScroll**, and `restorePosition` is exactly the same as the original iOS Picker component. Users can choose the corresponding method according to their needs.\n:::\n\n## Events\n\n### wheelIndexChanged <Badge text='2.1.0' />\n\n  - **Arguments**: The index of the current selected wheel-item.\n  - **Trigger timing**: When the selected wheel-item changes.\n\n  ```js\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n\n  BScroll.use(Wheel)\n\n  const bs = new BScroll('.bs-wrapper', {\n    wheel: true\n  })\n\n  bs.on('wheelIndexChanged', (index) => {\n    console.log(index)\n  })\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/en-US/plugins/zoom.md",
    "content": "# zoom\n\n## Introduction\n\nAdd zoom functionality for BetterScroll.\n\n## Install\n\n```bash\nnpm install @better-scroll/zoom --save\n\n// or\n\nyarn add @better-scroll/zoom\n```\n\n## Usage\n\nimport `zoom`, then call `BScroll.use()`.\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\n\nBScroll.use(Zoom)\n```\n\npass in the correct configuration in options, for example:\n\n```js\nnew BScroll('.bs-wrapper', {\n  freeScroll: true,\n  scrollX: true,\n  scrollY: true,\n  zoom: {\n    start: 1,\n    min: 0.5,\n    max: 2\n  }\n})\n```\n\nThe following is related to `zoom` plugin and [BetterScroll configuration](../guide/base-scroll-options.html):\n\n- **zoom(for plugin)**\n\n  Enable zoom functionality. That is to say, the zoom plugin won't work without the zoom option, see [zoom option](./zoom.html#option).\n\n- **freeScroll**\n\n  If you want to scroll in x and y axies after zooming in, the **freeScroll** value should be set to `true`. In addtional, **scrollX** and **scrollY** are also need to be true.\n\n- **scrollX**\n\n  `true` is be required if you want to scroll in x axies after zooming in.\n\n- **scrollY**\n\n  `true` is be required if you want to scroll in y axies after zooming in.\n\n## Demo\n\n  :::warning\n  pc is not allowed, scan the qrcode.\n  :::\n\n  <demo qrcode-url=\"zoom/default\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/zoom/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/zoom/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/zoom/default.vue?style\n    </template>\n    <zoom-default slot=\"demo\"></zoom-default>\n  </demo>\n\n## Option\n\n### start\n  - **Type**: `number`\n  - **Default**: `1`\n\n    Initial scale.\n\n### min\n  - **Type**: `number`\n  - **Default**: `1`\n\n    min scale.\n\n### max\n  - **Type**: `number`\n  - **Default**: `4`\n\n    max scale.\n\n### initialOrigin\n  - **Type**: `[OriginX, OriginY]`\n    - **OriginX**: `number | 'left' | 'right' | 'center'`\n    - **OriginY**: `number | 'top' | 'bottom' | 'center'`\n  - **Default**: `[0, 0]`\n\n    The origin of first initializing zoom instance, It is valid when `start` is not `1`.The origin is all based on the coordinate system of `scaled element`.\n\n  - **Examples**\n\n  ```js\n    new BScroll('.bs-wrapper', {\n      // ... other configuration\n      zoom: {\n        initialOrigin: [50, 50], // Based on 'scaled element', offsetLeft is 50px, offsetRight is 50px\n        initialOrigin: [0, 0], // Based on 'scaled element', the left vertex\n        initialOrigin: ['left', 'top'], // same as above\n        initialOrigin: ['center', 'center'], // Based on 'scaled element', center position\n        initialOrigin: ['right', 'top'], // Based on 'scaled element', the right vertex\n      }\n    })\n  ```\n\n  When you initialize zoom, you usually focus on zooming with the endpoint or center. You can refer to the above example.\n\n### minimalZoomDistance\n  - **Type**: `number`\n  - **Default**: `5`\n\n    When you zoom with two fingers, only when the zoom distance exceeds `minimalZoomDistance`, the zoom will take effect.\n\n### bounceTime\n  - **Type**: `number`\n  - **Default**: `800`(ms)\n\n    When the two fingers continue to zoom and the scale exceeds the threshold of `max`, when the two fingers leave, the internal \"bounce\" to the form of `max`, and `bounceTime` is the animation of this \"bounce\" behavior duration.\n\n:::tip\nWhen `zoom` is configured as `true`, the plugin uses the default plugin option.\n\n```js\nconst bs = new BScroll('.wrapper', {\n  zoom: true\n})\n\n// equals\n\nconst bs = new BScroll('.wrapper', {\n  zoom: {\n    start: 1,\n    min: 1,\n    max: 4,\n    initialOrigin: [0, 0],\n    minimalZoomDistance: 5,\n    bounceTime: 800, // ms\n  }\n})\n```\n:::\n\n## Instance Methods\n\n### zoomTo(scale, x, y, [bounceTime])\n  - **Arguments**\n    - `{number} scale`: scale ratio\n    - `{OriginX} x`: The x of origin that is based on the left vertex of the **scaled element**\n      - `OriginX: 'number | 'left' | 'right' | 'center'`\n    - `{OriginY} y`:  The y of origin that is based on the left vertex of the **scaled element**\n      - `OriginY: 'number | 'top' | 'bottom' | 'center'`\n    - `{number} [bounceTime]<Optional>: Animation duration of a zoom action`\n\n    Scale the element with `[x, y]` as the coordinate origin. x and y can be not only `number`, but also corresponding `string`, because general scenes are scaled based on endpoints or centers.\n\n  - **Examples**\n\n  ```js\n  const bs = new BScroll('.bs-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n  // scaled to 1.8 based on the bottom left point of the scaled element\n  bs.zoomTo(1.8, 'left', 'bottom')\n  // animation duration is 1000ms\n  bs.zoomTo(1.8, 'left', 'bottom', 1000)\n  // scaled to 1.8 based on offsetLeft 100px & offsetTop 100px of the scaled element\n  bs.zoomTo(1.8, 100, 100)\n  // scaled to 2 based on the center of scaled element\n  bs.zoomTo(2, 'center', 'center')\n  ```\n\n## Events\n\n### beforeZoomStart\n- **Arguments**: none\n- **Trigger timing**: When two fingers touch the scaled element, it does not include directly calling the `zoomTo` method\n\n### zoomStart\n\n  - **Arguments**: none\n  - **Trigger timing**: The two finger zoom distance exceeds the minimum threshold `minimalZoomDistance`, and the zoom will start soon. it does not include directly calling the `zoomTo` method\n\n### zooming\n\n  - **Arguments**: `{ scale }`\n  - **Type**: `{ scale: number }`\n  - **Trigger timing**: the process of two-finger zooming action in progress or directly calling `zoomTo` to zoom\n\n  - **Examples**:\n  ```js\n    const bs = new BScroll('.bs-wrapper', {\n      freeScroll: true,\n      scrollX: true,\n      scrollY: true,\n      zoom: {\n        start: 1,\n        min: 0.5,\n        max: 2\n      }\n    })\n\n    bs.on('zooming', ({ scale }) => {\n      // use scale\n      console.log(scale) // current scale\n    })\n  ```\n\n### zoomEnd\n\n  - **Arguments**: `{ scale }`\n  - **Type**: `{ scale: number }`\n  - **Trigger timing**: After two finger zooming behavior ends (if there is a rebound, the trigger timing is after the rebound animation ends) or after calling `zoomTo` to complete the zoom\n\n  ::: warning\n  In the zoom scenario, you should listen to events such as `zoomStart`, `zooming`, `zoomEnd`, and not the lower-level `scroll` and `scrollEnd` events, otherwise it may not match your expectations.\n  :::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/FAQ/README.md",
    "content": "# FAQ\n\n### 为什么 BetterScroll 初始化不能滚动？\n\nBetterScroll 滚动原理是 content 元素的高度／宽度超过 wrapper 元素的高度／宽度。而且，如果你的 content 元素含有不固定尺寸的图片，你必须在图片加载完之后，调用 `refresh()` 方法来确保高度计算正确。还存在一种情况是页面存在表单元素，弹出键盘之后，将页面的视口高度压缩，导致 bs 不能正常工作，依然是调用 `refresh()` 方法。\n\n### 为什么 BetterScroll 区域的点击事件无法被触发？\n\nBetterScroll 默认会阻止浏览器的原生 click 事件。如果你想要 click 事件生效，BetterScroll 会派发一个 click 事件，并且 event 参数的 `_constructed` 为 true。配置项如下：\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  click: true\n})\n```\n\n### 为什么我的 BetterScroll 监听 `scroll` 钩子，监听器不执行？\n\nBetterScroll 通过 probeType 配置项来决定是否派发 `scroll` 钩子，因为这是有一些性能损耗的。probeType 为 2 的时候会实时的派发事件，probeType 为 3 的时候会在 momentum 动量动画的时候派发事件。建议设置为 3。\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  probeType: 3\n})\n```\n\n### slide 用了横向滚动，发现在 slide 区域纵向滚动无效？\n\n如果想要保留浏览器的原生纵向滚动，需要如下配置项：\n\n```js\nimport BScroll from '@better-scroll/core'\n\nlet bs = new BScroll('./div', {\n  eventPassthrough: 'vertical'\n})\n```\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/FAQ/diagnosis.md",
    "content": "# BetterScroll 的“疑难杂症”\n\n### 【问题一】为什么我的 BetterScroll 滑动不了？\n\n问题基本上出在于**高度的计算错误**。首先，你必须对 `BetterScroll` 的滚动原理有一个清晰的认识，对于竖向滚动，简单的来说就是 `wrapper` 容器的高度大于 `content` 内容的高度，修改 `translateY` 来达到滚动的目的，横向滚动的原理类似。那么计算**可滚动的高度**就是 BetterScroll 必备的逻辑。一般这个逻辑出错的场景在于：\n\n  1. **存在不确定尺寸的图片**\n\n      - **原因**\n\n        bs 执行计算**可滚动高度**的时候，图片还未渲染完成，也无法监听到图片的加载。有时候甚至配置 `observeDOM` 为 `true` 也没效果。\n\n      - **解决**\n\n        在图片的 onload 的回调函数里面调用 `bs.refresh()` 来确保得到正确的图片高度之后再计算**可滚动的高度**。\n\n        :::tip\n        `observeDOM` 为 `true` 的时候，BetterScroll 首先通过 [MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver) 来监视对 DOM Tree 的改变，但是**无法监听图片是否加载完成**，所以需要手动调用 `refresh()` 来计算高度。\n\n        如果当前浏览器不支持 MutationObserver，会降级用 `setTimeout` 每隔 1s 来重复计算可滚动的高度，这样又能保证在图片加载完成之后，可滚动的高度计算正确。\n        :::\n\n  2. **Vue 的 keep-alive 组件**\n\n      - **场景**\n\n        假设存在 A、B 两个被 `keep-alive` 包裹的组件，A 组件使用了 BetterScroll，在 A 组件做了某种操作，弹出输入键盘，之后进入到 B 组件，再返回 A 组件的时候，bs 无法滚动。\n\n      - **原因**\n\n        由于 Vue 的 keep-alive 的缓存加上输入键盘弹起时候，会压缩可视区域的高度，导致之前计算过的可滚动的高度有误。\n\n      - **解决**\n\n        可以在 Vue 的 `activated` 的钩子里面调用 `bs.refresh()` 重新计算高度或者重新实例化 bs。\n\n### 【问题二】为什么我用 BetterScroll 做了横向滚动之后，纵向滚动失效？\n\nBetterScroll 提供了 `slide` 的 feature。如果实现了一个横向滚动的 `slide`，在 `slide` 区域做竖向滚动的操作，无法冒泡到浏览器，这样就无法操纵原生浏览器的滚动条了。\n\n- **原因**\n\n  BetterScroll 内部的滚动计算存在于用户的交互，比如移动端就是 `touchstart/touchmove/touchend` 事件，这些事件的侦听器一般都有 `e.preventDefault()` 这一行代码，会阻止浏览器的默认行为，这样浏览器的滚动条无法被滚动。\n\n- **解决**\n\n  配置 `eventPassthrough` 属性。\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    eventPassthrough: 'vertical' // 保持纵向的原生浏览器滚动\n  })\n  ```\n\n### 【问题三】为什么我用 BetterScroll 之后，无法在浏览器弹出长按图片保存等弹窗。\n\n- **原因**\n\n  在**问题二**已经提到了，`touchstart` 事件里面的`e.preventDefault()` 造成的。\n\n- **解决**\n\n  方案一：配置 `preventDefaultException` 属性。\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    preventDefaultException: {\n      className: /(^|\\s)test(\\s|$)/\n    }\n  })\n  ```\n\n  通过 `preventDefaultException` 可以控制 `touchstart` 和 `touchmove` 事件的 `e.preventDefault()`。上述的正则是用来校验当前触摸的目标元素 class 名称是否含有 `test`，如果通过了，则不会调用 `e.preventDefault()`。\n\n  方案二：配置 `preventDefault` 属性。\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    preventDefault: false\n  })\n  ```\n\n  preventDefault 设置为 false，会有一些副作用，一般推荐使用**方案一**。\n\n  :::warning\n  副作用在于：touch 事件可能会冒泡到 document，导致文档也被拖拽。这个时候你需要监听 `wrapper` 元素的父元素或者祖先元素，给他们绑定 touchmove 事件，并且调用 `e.preventDefault()`。最常见的应该是[禁止微信下拉浏览器查看域名](https://www.cnblogs.com/jasonwang2y60/p/6848464.html)\n  :::\n\n### 【问题四】为什么 BetterScroll content 内部的所有的 click 事件的侦听器都不触发？\n\n- **原因**\n\n  依然是 `touch` 事件的 `e.preventDefault()` 的原因。在移动端，如果你在 `touchstart/touchmove/touchend` 的逻辑里面调用 `e.preventDefault()`，会阻止它以及它子元素的 click 事件的执行。因此，BetterScroll 内部会管理 `click` 事件的派发，你只需要 `click` 配置项即可。\n\n- **解决**\n\n  配置 `click` 属性。\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    click: true\n  })\n  ```\n\n### 【问题五】为什么在嵌套 BetterScroll 的时候，click 事件派发两次？\n\n- **原因**\n\n  正如**问题四**所说，BetterScroll 内部会派发 `click` 事件，并且嵌套场景肯定是存在两个或两个以上的 bs。\n\n- **解决**\n\n  你可以通过实例化内层 BetterScroll 的 `stopPropagation` 配置项来管理事件的冒泡，或者通过配置内层 BetterScroll 的 `click` 配置项来防止 click 的多次触发。\n\n  ```js\n  let innerBS = new BScroll('.wrapper', {\n    stopPropagation: true\n  })\n\n  // 或者\n  let innerBS = new BScroll('.wrapper', {\n    click: false\n  })\n  ```\n\n### 【问题六】为什么我监听了 bs 的 scroll 事件，为啥回调不执行？\n\n- **原因**\n\n  BetterScroll 并不是在任何时刻都会派发 `scroll` 事件，因为获取 bs 的滚动位置是有一定的性能损耗。至于是否派发，是取决于 `probeType` 配置项。\n\n- **解决**\n\n  ```js\n  let bs = new BScroll('.div', {\n    probeType: 3 // 实时派发\n  })\n  ```\n\n### 【问题七】在两个纵向嵌套的 bs 场景，为什么移动内层的 bs，会导致外层也被滚动。\n\n- **原因**\n\n  BetterScroll 的内部逻辑都在 touch 事件的侦听器函数体内，既然内部的 bs 的 touch 事件被触发，自然会冒泡到外层的 bs。\n\n- **解决**\n\n  既然知道原因，那么也有相对应的解决办法。比如在你滚动内层的 bs 时候，监听 scroll 事件，调用外层的 `bs.disable()` 来禁用外层的 bs。当内层的 bs 滚动到底部的时候，说明这个时候需要滚动外层的 bs，这个时候调用外层的 `bs.enable()` 来激活外层，并且调用内层的 `bs.disable()` 禁止内层滚动。其实仔细想一想，这个交互就跟原生 Web 的嵌套滚动行为表现一致，只不过浏览器帮你处理了各种滚动嵌套的逻辑，而在 BetterScroll 需要你自己通过派发的事件以及暴露的 API 来实现。\n\n  > cube-ui 的 [scroll](https://didi.github.io/cube-ui/example/#/scroll/v-scrolls) 组件对此场景给出了相应的解决思路。[代码在这](https://github.com/didi/cube-ui/blob/dev/src/components/scroll/scroll.vue)\n\n### 【问题八】在纵向 bs 嵌套横向 bs 的场景，为什么在横向 bs 的区域竖向移动不会使得外层纵向 bs 的垂直滚动？\n\n- **原因**\n\n  原因与**问题二**类似，还是因为 `e.preventDefault()` 影响了默认的滚动行为，导致外层的 bs 不会触发 touch 事件。\n\n- **解决**\n\n  解决办法就是配置内层的 bs 的 `eventPassthrough` 属性，让其保持默认的原生竖向滚动，\n\n  ```js\n  let innerBS = new BScroll('.wrapper', {\n    eventPassthrough: 'vertical' // 保持纵向的原生浏览器滚动\n  })\n  ```\n \n### 【问题九】国产浏览器中，横向 slide 切换卡顿或无效问题。\n\n- **原因**\n\n  国产浏览器大多数都会监听默认的横滑事件，以实现浏览器的快速“回退”和”前进“。\n  \n- **解决**\n\n  主要是使用一个 CSS 特性`touch-action`，如果是横向的滑动，将 `touch-action: pan-y` CSS 设置在 `wrapper` 上面，这样就可以避免横向的切换触发浏览器的默认行为。\n\n\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/README.md",
    "content": "---\nhome: true\nheroText: BetterScroll 2.0\nactionText: 快速上手 →\nactionLink: /zh-CN/guide/\nfeatures:\n- title: 优雅的滚动\n  details: 为移动端（已支持 PC）各种滚动场景提供丝滑的滚动效果。\n- title: 零依赖\n  details: 基于原生 JS 实现的，不依赖任何框架。完美运用于 Vue、React 等 MVVM 框架。\n- title: 扩展灵活\n  details: 提供插件机制，便于对基础滚动进行功能扩展，目前支持上拉加载、下拉刷新、Picker、鼠标滚轮、放大缩小、移动缩放、轮播图、滚动视觉差，放大镜等等能力\nfooter: MIT Licensed | Copyright © 2018-present ustbhuangyi and theniceangel\n---\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/README.md",
    "content": "# 介绍\n\n## BetterScroll 是什么\n\nBetterScroll 是一款重点解决移动端（已支持 PC）各种滚动场景需求的插件。它的核心是借鉴的 [iscroll](https://github.com/cubiq/iscroll) 的实现，它的 API 设计基本兼容 iscroll，在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。\n\nBetterScroll 是使用纯 JavaScript 实现的，这意味着它是无依赖的。\n\n## 示例\n\n[地址](https://better-scroll.github.io/examples/)\n\n<img data-zoomable :src=\"$withBase('/assets/images/qrcode.png')\" alt=\"示例\">\n\n## 起步\n\nBetterScroll 最常见的应用场景是列表滚动，我们来看一下它的 html 结构。\n\n```html\n<div class=\"wrapper\">\n  <ul class=\"content\">\n    <li>...</li>\n    <li>...</li>\n    ...\n  </ul>\n  <!-- 这里可以放一些其它的 DOM，但不会影响滚动 -->\n</div>\n```\n上面的代码中 BetterScroll 是作用在外层 **wrapper** 容器上的，滚动的部分是 **content** 元素。这里要注意的是，BetterScroll 默认处理容器（wrapper）的第一个子元素（content）的滚动，其它的元素都会被忽略。\n\n最简单的初始化代码如下：\n\n``` js\nimport BScroll from '@better-scroll/core'\nlet wrapper = document.querySelector('.wrapper')\nlet scroll = new BScroll(wrapper)\n```\nBetterScroll 提供了一个类，实例化的第一个参数是一个原生的 DOM 对象。当然，如果传递的是一个字符串，BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。\n\n:::warning 注意\nBetterScroll 2.X 里面，我们将 1.X 耦合的 feature 拆分至插件，以达到按需加载、减少包体积的目的。因此，`@better-scroll/core` 只提供了最核心的滚动能力。如果想要实现**上拉加载**、**下拉刷新**的功能，你需要使用对应的[插件](/zh-CN/plugins)。\n:::\n\n:::tip 提示\n版本 2.0.4 的 BetterScroll 可以通过 [specifiedIndexAsContent](./base-scroll-options.html#specifiedindexascontent-2-0-4) 来指定 wrapper 的某个子元素作为 content。\n:::\n\n## 滚动原理\n\n很多人已经用过 BetterScroll，我收到反馈最多的问题是：\n\n> BetterScroll 初始化了， 但是没法滚动。\n\n不能滚动是现象，我们得搞清楚这其中的根本原因。在这之前，我们先来看一下浏览器的滚动原理：\n浏览器的滚动条大家都会遇到，当页面内容的高度超过视口高度的时候，会出现纵向滚动条；当页面内容的宽度超过视口宽度的时候，会出现横向滚动条。也就是当我们的视口展示不下内容的时候，会通过滚动条的方式让用户滚动屏幕看到剩余的内容。\n\nBetterScroll 也是一样的原理，我们可以用一张图更直观的感受一下：\n\n<img data-zoomable :src=\"$withBase('/assets/images/schematic.png')\" alt=\"原理图\">\n\n绿色部分为 wrapper，也就是父容器，它会有**固定的高度**。黄色部分为 content，它是父容器的**第一个子元素**，它的高度会随着内容的大小而撑高。那么，当 content 的高度不超过父容器的高度，是不能滚动的，而它一旦超过了父容器的高度，我们就可以滚动内容区了，这就是 BetterScroll 的滚动原理。\n\n## BetterScroll 在 MVVM 框架的应用\n\n我之前写过一篇[当 BetterScroll 遇见 Vue](https://zhuanlan.zhihu.com/p/27407024)，也希望大家投稿，分享一下 BetterScroll 在其它框架下的使用心得。\n\n一款超赞的基于 Vue 实现的组件库 [cube-ui](https://github.com/didi/cube-ui/)。\n\n## BetterScroll 在实战项目中的运用\n\n如果你想学习 BetterScroll 在实战项目中的运用，也可以去学习我的 2 门实战课程。\n\n[Vue.js 高仿外卖饿了么实战课程](https://coding.imooc.com/class/74.html)\n\n[项目演示地址](http://ustbhuangyi.com/sell/)\n\n![二维码](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fsell%252F%2523%252Fgoods&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=686203a49c4613080b5b3004323ff977)\n\n[Vue.js 音乐 App 高级实战课程](http://coding.imooc.com/class/107.html)\n\n[项目演示地址](http://ustbhuangyi.com/music/)\n\n![二维码](https://qr.api.cli.im/qr?data=http%253A%252F%252Fustbhuangyi.com%252Fmusic%252F&level=H&transparent=false&bgcolor=%23ffffff&forecolor=%23000000&blockpixel=12&marginblock=1&logourl=&size=280&kid=cliim&key=731bbcc2b490454d2cc604f98539952c)\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/base-scroll-api.md",
    "content": "# API\n\n如果想要彻底了解 BetterScroll，就需要了解其实例的常用属性、灵活的方法以及提供的事件与钩子。\n\n## 属性\n\n有时候我们想基于 BetterScroll 做一些扩展，需要对 BetterScroll 的一些属性有所了解，下面介绍几个常用属性。\n\n### x\n  - **类型**：number\n  - **作用**：bs 横轴坐标。\n\n### y\n  - **类型**：number\n  - **作用**：bs 纵轴坐标。\n\n### maxScrollX\n  - **类型**：number\n  - **作用**：bs 最大横向滚动位置。\n  - **备注**：bs 横向滚动的位置区间是 [minScrollX, maxScrollX]，并且 maxScrollX 是负值。\n\n### minScrollX\n  - **类型**：number\n  - **作用**：bs 最小横向滚动位置。\n  - **备注**：bs 横向滚动的位置区间是 [minScrollX, maxScrollX]，并且 minScrollX 是正值。\n\n### maxScrollY\n  - **类型**：number\n  - **作用**：bs 最大纵向滚动位置。\n  - **备注**：bs 纵向滚动的位置区间是 [minScrollY, maxScrollY]，并且 maxScrollY 是负值。\n\n### minScrollY\n  - **类型**：number\n  - **作用**：bs 最小纵向滚动位置。\n  - **备注**：bs 纵向滚动的位置区间是 [minScrollY, maxScrollY]，并且 minScrollY 是正值。\n\n### movingDirectionX\n  - **类型**：number\n  - **作用**：判断 bs 滑动过程中的方向（左右）。\n  - **备注**：-1 表示手指从左向右滑，1 表示从右向左滑，0 表示没有滑动。\n\n### movingDirectionY\n  - **类型**：number\n  - **作用**：判断 bs 滑动过程中的方向（上下）。\n  - **备注**：-1 表示手指从上往下滑，1 表示从下往上滑，0 表示没有滑动。\n\n### directionX\n  - **类型**：number\n  - **作用**：判断 bs 滑动结束后相对于开始滑动位置的方向（左右）。\n  - **备注**：-1 表示手指从左向右滑，1 表示从右向左滑，0 表示没有滑动。\n\n### directionY\n  - **类型**：number\n  - **作用**：判断 bs 滑动结束后相对于开始滑动位置的方向（上下）。\n  - **备注**：-1 表示手指从上往下滑，1 表示从下往上滑，0 表示没有滑动。\n\n### enabled\n  - **类型**：boolean,\n  - **作用**：判断当前 bs 是否处于启用状态，不再响应手指的操作。\n\n### pending\n  - **类型**：boolean,\n  - **作用**：判断当前 bs 是否处于滚动动画过程中。\n\n## 方法\n\nBetterScroll 提供了很多灵活的 API，当我们基于 BetterScroll 去实现一些 feature 的时候，会用到这些 API，了解它们会有助于开发更加复杂的需求。\n\n### refresh()\n  - **参数**：无\n  - **返回值**：无\n  - **作用**：重新计算 BetterScroll，当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。\n\n### scrollTo(x, y, time, easing, extraTransform)\n   - **参数**：\n     - {number} x 横轴坐标（单位 px）\n     - {number} y 纵轴坐标（单位 px）\n     - {number} time 滚动动画执行的时长（单位 ms）\n     - {Object} easing 缓动函数，一般不建议修改，如果想修改，参考源码中的 `packages/shared-utils/src/ease.ts` 里的写法\n     - {Object} extraTransform，只有在你想要修改 CSS transform 的一些其他属性的时候，你才需要传入此参数，结构如下：\n     ```js\n     let extraTransform = {\n       // 起点的属性\n       start: {\n         scale: 0\n       },\n       // 终点的属性\n       end: {\n         scale: 1.1\n       }\n     }\n     bs.scrollTo(0, -60, 300, undefined, extraTransform)\n     ```\n   - **返回值**：无\n   - **作用**：滚动到指定的 x,y 位置。\n\n### scrollBy(x, y, time, easing)\n   - **参数**：\n     - {number} x 横轴变化量（单位 px）\n     - {number} y 纵轴变化量（单位 px）\n     - {number} time 滚动动画执行的时长（单位 ms）\n     - {Object} easing 缓动函数，一般不建议修改，如果想修改，参考源码中的 `packages/shared-utils/src/ease.ts` 里的写法\n   - **返回值**：无\n   - **作用**：相对于当前位置偏移滚动 x,y 的距离。\n\n### scrollToElement(el, time, offsetX, offsetY, easing)\n   - **参数**：\n     - {DOM | string} el 滚动到的目标元素, 如果是字符串，则内部会尝试调用 querySelector 转换成 DOM 对象。\n     - {number} time 滚动动画执行的时长（单位 ms）\n     - {number | boolean} offsetX 相对于目标元素的横轴偏移量，如果设置为 true，则滚到目标元素的中心位置\n     - {number | boolean} offsetY 相对于目标元素的纵轴偏移量，如果设置为 true，则滚到目标元素的中心位置\n     - {Object} easing 缓动函数，一般不建议修改，如果想修改，参考源码中的 `packages/shared-utils/src/ease.ts` 里的写法\n   - **返回值**：无\n   - **作用**：滚动到指定的目标元素。\n\n### stop()\n   - **参数**：无\n   - **返回值**：无\n   - **作用**：立即停止当前运行的滚动动画。\n\n### enable()\n   - **参数**：无\n   - **返回值**：无\n   - **作用**：启用 BetterScroll, 默认 开启。\n\n### disable()\n   - **参数**：无\n   - **返回值**：无\n   - **作用**：禁用 BetterScroll，DOM 事件（如 touchstart、touchmove、touchend）的回调函数不再响应。\n\n### destroy()\n   - **参数**：无\n   - **返回值**：无\n   - **作用**：销毁 BetterScroll，解绑事件。\n\n### on(type, fn, context)\n   - **参数**：\n     - {string} type 事件名\n     - {Function} fn 回调函数\n     - {Object} context 函数执行的上下文环境，默认是 this\n   - **返回值**：无\n   - **作用**：监听当前实例上的钩子函数。如：scroll、scrollEnd 等。\n   - **示例**：\n   ```javascript\n   import BScroll from '@better-scroll/core'\n   let scroll = new BScroll('.wrapper', {\n     probeType: 3\n   })\n   function onScroll(pos) {\n       console.log(`Now position is x: ${pos.x}, y: ${pos.y}`)\n   }\n   scroll.on('scroll', onScroll)\n   ```\n\n### once(type, fn, context)\n   - **参数**：\n     - {string} type 事件名\n     - {Function} fn 回调函数\n     - {Object} context 函数执行的上下文环境，默认是 this\n   - **返回值**：无\n   - **作用**：监听一个自定义事件，但是只触发一次，在第一次触发之后移除监听器。\n\n### off(type, fn)\n   - **参数**：\n     - {string} type 事件名\n     - {Function} fn 回调函数\n   - **返回值**：无\n   - **作用**：移除自定义事件监听器。只会移除这个回调的监听器。\n   - **示例**：\n   ```javascript\n   import BScroll from '@better-scroll/core'\n   let scroll = new BScroll('.wrapper', {\n     probeType: 3\n   })\n   function handler() {\n       console.log('bs is scrolling now')\n   }\n   scroll.on('scroll', handler)\n\n   scroll.off('scroll', handler)\n   ```\n\n## 事件 VS 钩子\n\n基于 2.x 的架构设计，以及对 1.x 事件的兼容，我们延伸出两个概念 ——『**事件**』以及『**钩子**』。从本源上来说它们都是属于 `EventEmitter` 实例，只是叫法不一样。下面我们从节选的源码来讲解一下：\n\n```typescript\n  export default BScrollCore extends EventEmitter {\n    hooks: EventEmitter\n  }\n```\n\n  - **BScrollCore**\n\n    本身继承了 EventEmitter。它派发出来的，我们都称之为『**事件**』。\n\n    ```js\n      import BScroll from '@better-scroll/core'\n      let bs = new BScroll('.wrapper', {})\n\n      // 监听 bs 的 scroll 事件\n      bs.on('scroll', () => {})\n      // 监听 bs 的 refresh 事件\n      bs.on('refresh', () => {})\n    ```\n\n  - **BScrollCore.hooks**\n\n    hooks 也是 EventEmitter 的实例。它派发出来的，我们都称之为『**钩子**』。\n\n    ```js\n      import BScroll from '@better-scroll/core'\n      let bs = new BScroll('.wrapper', {})\n\n      // 监听 bs 的 refresh 钩子\n      bs.hooks.on('refresh', () => {})\n      // 监听 bs 的 enable 钩子\n      bs.hooks.on('enable', () => {})\n    ```\n\n相信现在大家对两者有了更好的区分吧，『**事件**』是为了 1.x 的兼容考虑，用户一般关注的是事件的派发，但是如果要编写一款插件，你应该更加关注『**钩子**』。\n\n## 事件\n\n在 2.0 当中，BetterScroll 事件与 1.x 的事件是拉齐的，只有 BetterScroll 会派发『**事件**』，如果你在编写插件的时候需要暴露事件，你也应该通过 BetterScroll 来派发，[详细的教程看这](../plugins/how-to-write.html)，目前的事件分为下面几种：\n\n  - **refresh**\n    - **触发时机**：BetterScroll 重新计算\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n\n      const bs = new BetterScroll('.wrapper', {})\n\n      bs.on('refresh', () => {})\n    ```\n\n  - **enable**\n    - **触发时机**：BetterScroll 启用，开始响应用户交互\n\n    ```js\n      bs.on('enable', () => {})\n    ```\n\n  - **disable**\n    - **触发时机**：BetterScroll 禁用，不再响应用户交互\n\n    ```js\n      bs.on('disable', () => {})\n    ```\n\n  - **beforeScrollStart**\n    - **触发时机**：用户手指放在滚动区域的时候\n\n    ```js\n      bs.on('beforeScrollStart', () => {})\n    ```\n\n  - **scrollStart**\n    - **触发时机**：content 元素满足滚动条件，即将开始滚动\n\n    ```js\n      bs.on('scrollStart', () => {})\n    ```\n\n  - **scroll**\n    - **触发时机**：正在滚动\n\n    ```js\n      bs.on('scroll', (position) => {\n        console.log(position.x, position.y)\n      })\n    ```\n\n  - **scrollEnd**\n    - **触发时机**：滚动结束，或者让一个正在滚动的 content 强制停止\n\n    ```js\n      bs.on('scrollEnd', () => {})\n    ```\n\n  - **scrollCancel**\n    - **触发时机**：滚动取消\n\n    ```js\n      bs.on('scrollCancel', () => {})\n    ```\n\n  - **touchEnd**\n    - **触发时机**：用户手指离开滚动区域\n\n    ```js\n      bs.on('touchEnd', () => {})\n    ```\n\n  - **flick**\n    - **触发时机**：用户触发轻拂操作\n\n    ```js\n      bs.on('flick', () => {})\n    ```\n\n  - **destroy**\n    - **触发时机**：BetterScroll 销毁\n\n    ```js\n      bs.on('destroy', () => {})\n    ```\n  - **contentChanged** <Badge text='2.0.4' />\n    - **触发时机**：在调用 `bs.refresh()`，探测到 content DOM 变成了其他元素的时候\n\n    ```typescript\n      // bs 版本 >= 2.0.4\n      bs.on('contentChanged', (newContent: HTMLElement) => {})\n    ```\n\n以下的事件必须注册括号中的**插件**才会派发：\n\n  - **alterOptions(__mouse-wheel__)**\n    - **触发时机**：滚轮滚动开始\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import MouseWheel from '@better-scroll/mouse-wheel'\n\n      BetterScroll.use(MouseWheel)\n      const bs = new BetterScroll('.wrapper', {\n        mouseWheel: true\n      })\n\n      bs.on('alterOptions', (mouseWheelOptions) => {\n        /**\n         * mouseWheelOptions.speed：鼠标滚轮滚动的速度\n         * mouseWheelOptions.invert：滚轮滚动和 BetterScroll 滚动的方向是否一致\n         * mouseWheelOptions.easeTime：滚动动画的缓动时长。\n         * mouseWheelOptions.discreteTime：触发 wheelEnd 的间隔时长\n         * mouseWheelOptions.throttleTime：滚轮滚动是高频率的动作，因此可以通过 throttleTime 来限制触发频率\n         * mouseWheelOptions.dampingFactor：阻尼因子，当超出边界会施加阻力\n         **/\n      })\n    ```\n\n  - **mousewheelStart(__mouse-wheel__)**\n    - **触发时机**：滚轮滚动开始\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import MouseWheel from '@better-scroll/mouse-wheel'\n\n      BetterScroll.use(MouseWheel)\n      const bs = new BetterScroll('.wrapper', {\n        mouseWheel: true\n      })\n\n      bs.on('mousewheelStart', () => {})\n    ```\n\n  - **mousewheelMove(__mouse-wheel__)**\n    - **触发时机**：滚轮滚动中\n\n    ```js\n      bs.on('mousewheelMove', () => {})\n    ```\n\n  - **mousewheelEnd(__mouse-wheel__)**\n    - **触发时机**：滚轮滚动结束\n\n    ```js\n      bs.on('mousewheelEnd', () => {})\n    ```\n\n  - **pullingDown(__pull-down__)**\n    - **触发时机**：当顶部下拉距离超过阈值\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Pulldown from '@better-scroll/pull-down'\n\n      BetterScroll.use(Pulldown)\n      const bs = new BetterScroll('.wrapper', {\n        pullDownRefresh: true\n      })\n\n      bs.on('pullingDown', () => {\n        await fetchData()\n        bs.finishPullDown()\n      })\n    ```\n\n  - **pullingUp(__pull-up__)**\n    - **触发时机**：当底部下拉距离超过阈值\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Pullup from '@better-scroll/pull-up'\n\n      BetterScroll.use(Pullup)\n      const bs = new BetterScroll('.wrapper', {\n        pullUpLoad: true\n      })\n\n      bs.on('pullingUp', () => {\n        await fetchData()\n        bs.finishPullUp()\n      })\n    ```\n\n  - **slideWillChange(__slide__)**\n    - **触发时机**：轮播图即将要切换 Page\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Slide from '@better-scroll/slide'\n\n      BetterScroll.use(Slide)\n\n      const bs = new BetterScroll('.wrapper', {\n        slide: true,\n        momentum: false,\n        bounce: false,\n        probeType: 2\n      })\n\n      bs.on('slideWillChange', (page) => {\n        // 即将要切换的页面\n        console.log(page.pageX, page.pageY)\n      })\n    ```\n\n  - **beforeZoomStart(__zoom__)**\n    - **触发时机**：双指接触缩放元素时\n\n    ```js\n      import BetterScroll from '@better-scroll/core'\n      import Zoom from '@better-scroll/zoom'\n\n      BetterScroll.use(Zoom)\n\n      const bs = new BetterScroll('.wrapper', {\n        zoom: true\n      })\n\n      bs.on('beforeZoomStart', () => {})\n    ```\n\n  - **zoomStart(__zoom__)**\n    - **触发时机**：双指缩放距离超过最小阈值\n\n    ```js\n      bs.on('zoomStart', () => {})\n    ```\n\n  - **zooming(__zoom__)**\n    - **触发时机**：双指缩放行为正在进行时\n\n    ```js\n      bs.on('zooming', ({ scale }) => {\n        // scale 当前 scale\n      })\n    ```\n\n  - **zoomEnd(__zoom__)**\n    - **触发时机**：双指缩放行为结束后\n\n    ```js\n      bs.on('zoomEnd', ({ scale }) => {})\n    ```\n\n## 钩子\n\n钩子是 2.0 版本延伸出来的概念，它的本质与事件相同，都是 EventEmitter 实例，也就是典型的订阅发布模式。BScrollCore 作为一个最小的滚动单元，内部也是存在非常多的功能类，每个功能类都有一个叫 hooks 的属性，它架起了不同类之间沟通的桥梁。如果你要编写一个复杂的插件，钩子是必须需要掌握的内容。\n\n  - **BScrollCore.hooks**\n\n    - **beforeInitialScrollTo**\n      - **触发时机**：初始化加载完插件，需要滚动到指定位置\n      - **参数**：position 对象\n        - `{ x: number, y: number }`\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('beforeInitialScrollTo', (postion) => {\n          postion.x = 0\n          position.y = -200 // 初始化滚动至 -200 的位置\n        })\n      ```\n\n    - **refresh**\n      - **触发时机**：重新计算 BetterScroll\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('refresh', () => { console.log('refreshed') })\n      ```\n\n    - **enable**\n      - **触发时机**：启用 BetterScroll，响应用户行为\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('enable', () => { console.log('enabled') })\n      ```\n\n    - **disable**\n      - **触发时机**：禁用 BetterScroll，不再响应用户行为\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('disable', () => { console.log('disabled') })\n      ```\n\n    - **destroy**\n      - **触发时机**：销毁 BetterScroll\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        bs.hooks.on('destroy', () => { console.log('destroyed') })\n      ```\n\n    - **contentChanged** <Badge text='2.0.4' />\n      - **触发时机**：在调用 `bs.refresh()`，探测到 content DOM 变成了其他元素的时候\n      - **示例**\n      ```typescript\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        // bs 版本 >= 2.0.4\n        bs.hooks.on('contentChanged', (newContent: HTMLElement) => { console.log(newContent) })\n      ```\n\n  - **ActionsHandler.hooks**\n\n    - **beforeStart**\n      - **触发时机**：刚响应 touchstart 事件，还未记录手指在屏幕点击的位置\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('beforeStart', (event) => { console.log(event.target) })\n      ```\n\n    - **start**\n      - **触发时机**：记录完手指在屏幕点击的位置，即将触发 touchmove\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('start', (event) => { console.log(event.target) })\n      ```\n\n    - **move**\n      - **触发时机**：响应 touchmove 事件，记录完手指在屏幕点击的位置\n      - **参数**：拥有如下属性的对象\n        - `{ number } deltaX`：x 方向的手指偏移量\n        - `{ number } deltaY`：y 方向的手指偏移量\n        - `{ event } e`：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('move', ({ deltaX, deltaY, e }) => {})\n      ```\n\n    - **end**\n      - **触发时机**：响应 touchend 事件\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actionsHandler.hooks\n        hooks.on('end', (event) => {})\n      ```\n\n    - **click**\n      - **触发时机**：触发 click 事件\n      - **参数**：event 事件对象\n\n  - **ScrollerActions.hooks**\n\n    - **start**\n      - **触发时机**：记录完所有的滚动初始信息\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('start', (event) => { console.log(event.target) })\n      ```\n\n    - **beforeMove**\n      - **触发时机**：在检验是否是合法的滚动之前\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeMove', (event) => { console.log(event.target) })\n      ```\n\n    - **scrollStart**\n      - **触发时机**：校验是合法的滚动，并且即将开始滚动\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('scrollStart', () => {})\n      ```\n\n    - **scroll**\n      - **触发时机**：正在滚动\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('scroll', () => {})\n      ```\n\n    - **beforeEnd**\n      - **触发时机**：刚执行 touchend 事件回调，但是还未更新最终位置\n      - **参数**：event 事件对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeEnd', (event) => { console.log(event) })\n      ```\n\n    - **end**\n      - **触发时机**：刚执行 touchend 事件回调并且更新滚动方向\n      - **参数**：两个参数，第一个是 event 事件对象，第二个是当前位置\n        - `{ event } e`：事件对象\n        - `{ x: number, y: number } postion`：当前位置\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('end', (e, postion) => { console.log(e) })\n      ```\n\n    - **scrollEnd**\n      - **触发时机**：滚动即将结束，但还需要校验一次滚动行为是否触发了 flick、momentum 行为。\n      - **参数**：两个参数，第一个是当前位置，第二个是动画时长\n        - `{ x: number, y: number } postion`：当前位置\n        - `{ number } duration`：动画时长\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('beforeEnd', (pos, duration) => { console.log(pos) })\n      ```\n    \n    - **coordinateTransformation** <Badge text='2.3.0' />\n      - **触发时机**：计算完用户手指的偏移量之后，发生滚动之前。\n      - **参数**：\n        - `{ deltaX: number, deltaY: number } transformateDeltaData`：偏移量对象\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.actions.hooks\n        hooks.on('coordinateTransformation', (：transformateDeltaData) => { \n          // 获取用户手指移动的距离\n         const originDeltaX = transformateDeltaData.deltaX\n         const originDeltaY = transformateDeltaData.deltaY\n\n         // 变换位移\n         transformateDeltaData.deltaX = originDeltaY\n         transformateDeltaData.deltaY = originDeltaX\n\n         // transformateDeltaData.deltaX 最终作用在 BetterScroll content DOM 的 translateX\n         // transformateDeltaData.deltaY 最终作用在 BetterScroll content DOM 的 translateY\n        })\n      ```\n      该钩子通常是为了修正当 BetterScroll 的 wrapper DOM 的祖先元素发生旋转的时候，用户自定义位移变换的逻辑，大部分情况下只需要配置 [quadrant](./base-scroll-options.html#quadrant) 即可。\n\n  - **Behavior.hooks**\n\n    - **beforeComputeBoundary**\n      - **触发时机**：即将计算滚动边界\n      - **参数**：boundary 对象\n        - `{ minScrollPos: number, maxScrollPos: number } boundary`\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('beforeComputeBoundary', () => {})\n      ```\n\n    - **computeBoundary**\n      - **触发时机**：计算滚动边界\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('computeBoundary', (boundary) => {\n          console.log(boundary.minScrollPos) // 上边界最大值，正的越多，下拉的幅度越大\n          console.log(boundary.maxScrollPos) // 下边界最小值，负的越多，滚的越远\n        })\n      ```\n\n    - **momentum**\n      - **触发时机**：满足触发 momentum 动量动画条件，并且在计算之前\n      - **参数**：两个参数，第一个是 momentumData 对象，第二个是滚动偏移量\n        - `{ destination: number, duration: number, rate: number} momentumData`：destination 是目标位置，duration 是缓动时长，rate 是斜率\n        - `{ number } distance`：触发 momentum 的滚动偏移量\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('momentum', (momentumData, distance) => {})\n      ```\n\n    - **end**\n      - **触发时机**：不满足触发 momentum 动量动画条件\n      - **参数**：momentumInfo 对象\n        - `{ destination: number, duration: number} momentumInfo`：destination 是目标位置，duration 是缓动时长\n      - **示例**\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.scrollBehaviorX.hooks\n        hooks.on('end', (momentumInfo) => {\n          console.log(momentumInfo.destination)\n          console.log(momentumInfo.duration)\n        })\n      ```\n\n  - **Animation.hooks(useTransition: false)**\n\n    - **forceStop**\n      - **触发时机**：强制让一个滚动的 bs 停止\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **move**\n      - **触发时机**：滚动进行中\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **end**\n      - **触发时机**：滚动结束\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n  - **Translater.hooks**\n\n    - **beforeTranslate**\n      - **触发时机**：在修改 content 元素的 transform style 之前，zoom 插件监听了钩子\n      - **参数**：第一个是 transformStyle 数组，第二个是 point 对象\n        - `{ ['translateX(0px)'|'translateY(0px)'] } transformStyle`：当前 transform 对应的属性值\n        - `{ x: number, y: number } point`：x 对应 translateX 的值，y 对应 translateY 的值\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.translater.hooks\n          hooks.on('beforeTranslate', (transformStyle, point) => {\n            transformStyle.push('scale(1.2)')\n            console.log(transformStyle) // ['translateX(0px)', 'translateY(0px)', 'scale(1.2)']\n            console.log(point) // { x: 0, y: 0 }\n          })\n        ```\n\n    - **translate**\n      - **触发时机**：修改 content 元素的 transform style 之后，wheel 插件监听了钩子\n      - **参数**：point 对象\n        - `{ x: number, y: number } point`：x 对应 translateX 的值，y 对应 translateY 的值\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.translater.hooks\n          hooks.on('translate', (point) => {\n            console.log(point) // { x: 0, y: 0 }\n          })\n        ```\n\n  - **Transition.hooks(useTransition: true)**\n\n    - **forceStop**\n      - **触发时机**：强制让一个正在做动画的 bs 停止\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **move**\n      - **触发时机**：滚动进行中\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **end**\n      - **触发时机**：滚动结束\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **time**\n      - **触发时机**：CSS3 transition 开始之前，wheel 插件监听了该钩子\n      - **参数**：CSS3 transition duration\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.animater.hooks\n          hooks.on('time', (duration) => {\n            console.log(duration) // 800\n          })\n        ```\n\n    - **timeFunction**\n      - **触发时机**：CSS3 transition 开始之前，wheel 插件监听了该钩子\n      - **参数**：CSS3 transition-timing-function\n        ```js\n          import BScroll from '@better-scroll/core'\n          const bs = new BScroll('.wrapper', {})\n          const hooks = bs.scroller.animater.hooks\n          hooks.on('timeFunction', (easing) => {\n            console.log(easing) // cubic-bezier(0.1, 0.7, 1.0, 0.1)\n          })\n        ```\n\n  - **Scroller.hooks**\n\n    - **beforeStart**\n      同 `ScrollerActions.hooks.start`\n\n    - **beforeMove**\n      同 `ScrollerActions.hooks.beforeMove`\n\n    - **beforeScrollStart**\n      同 `ScrollerActions.hooks.start`\n\n    - **scrollStart**\n      同 `ScrollerActions.hooks.scrollStart`\n\n    - **scroll**\n      - **触发时机**：滚动进行中\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **beforeEnd**\n      同 `ScrollerActions.hooks.beforeEnd`\n\n    - **touchEnd**\n      - **触发时机**：用户手指离开滚动区域\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('touchEnd', () => {\n          console.log('your finger has leave')\n        })\n      ```\n\n    - **end**\n      - **触发时机**：touchEnd 之后，校验 click 之前触发，pull-down 插件基于这个钩子实现\n      - **参数**：position 对象\n       - `{ x: number, y: number } position`：当前位置\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('end', (position) => {\n          console.log(position.x)\n          console.log(position.y)\n        })\n      ```\n\n    - **scrollEnd**\n      - **触发时机**：滚动结束\n      - **参数**：position 对象\n        - `{ x: number, y: number } position`：当前坐标值\n\n    - **resize**\n      - **触发时机**：window 尺寸发生改变\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('resize', () => {\n          console.log(\"window's size has changed\")\n        })\n      ```\n\n    - **flick**\n      - **触发时机**：探测到手指轻拂动作\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('flick', () => {})\n      ```\n\n    - **scrollCancel**\n      - **触发时机**：滚动取消或者未发生\n\n    - **momentum**\n      - **触发时机**：即将进行 momentum 动量位移，slide 插件监听了该钩子\n      - **参数**：scrollMetaData 对象\n        - `{ time: number, easing: EaseItem, newX: number, newY: number }`：time 是动画时长，easing是缓动函数，newX 和 newY 是终点\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('momentum', (scrollMetaData) => {\n          scrollMetaData.newX = 0\n          scrollMetaData.newY = -200\n        })\n      ```\n\n    - **scrollTo**\n      - **触发时机**：调用 bs.scrollTo 方法的时候触发\n      - **参数**：endPoint 对象\n        - `{ x: number, y: number } endPoint`：终点坐标值\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('scrollTo', (endPoint) => {\n          console.log(endPoint.x)\n          console.log(endPoint.y)\n        })\n        bs.scrollTo(0, -200)\n      ```\n\n    - **scrollToElement**\n      - **触发时机**：调用 bs.scrollToElement 方法的时候触发，wheel 插件监听了该钩子\n      - **参数**：第一个是目标 DOM 对象，第二个是终点的坐标\n        - `{ HTMLElment } el`\n        - `{ top: number, left: number } postion`\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('scrollToElement', (el, pos) => {\n          console.log(el)\n          console.log(pos.left)\n          console.log(pos.top)\n        })\n        bs.scrollToElement('.some-item', 300, true, true)\n      ```\n\n    - **beforeRefresh**\n      - **触发时机**：在 behavior 计算边界之前，slide 插件监听了该钩子\n      ```js\n        import BScroll from '@better-scroll/core'\n        const bs = new BScroll('.wrapper', {})\n        const hooks = bs.scroller.hooks\n        hooks.on('beforeRefresh', () => {})\n      ```\n\n::: tip 提示\n细心的你会发现，有部分 Scroller.hooks 与 ScrollActions.hooks 的功能一模一样，其实我们内部采用了一种 [钩子冒泡](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/core/src/utils/bubbling.ts) 的策略，将内层功能类的钩子，通过冒泡的形式一直代理到 BetterScroll Instance 来兼容 1.x 的使用方式。\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/base-scroll-options.md",
    "content": "# 配置项\n\nBetterScroll 支持很多参数配置，可以在初始化的时候传入第二个参数，比如：\n\n``` js\nimport BScroll from '@better-scroll/core'\nlet scroll = new BScroll('.wrapper',{\n    scrollY: true,\n    click: true\n})\n```\n\n这样就实现了一个具有纵向可点击的滚动效果的列表。BetterScroll 支持的参数非常多，接下来我们来列举 BetterScroll 支持的参数。\n\n## startX\n  - **类型**：`number`\n  - **默认值**：`0`\n  - **作用**：横轴方向初始化位置。\n\n## startY\n  - **类型**：`number`\n  - **默认值**：`0`\n  - **作用**：纵轴方向初始化位置。\n\n## scrollX\n  - **类型**：`boolean`\n  - **默认值**： `false`\n  - **作用**：当设置为 true 的时候，可以开启横向滚动。\n  - **备注**：当设置 [eventPassthrough](./base-scroll-options.html#eventpassthrough) 为 'horizontal' 的时候，该配置无效。\n\n## scrollY\n  - **类型**：`boolean`\n  - **默认值**：`true`\n  - **作用**：当设置为 true 的时候，可以开启纵向滚动。\n  - **备注**：当设置 [eventPassthrough](./base-scroll-options.html#eventpassthrough) 为 'vertical' 的时候，该配置无效。\n\n## freeScroll\n  - **类型**：`boolean`\n  - **默认值**：`false`\n  - **作用**：在默认情况下，由于人的手指无法做到绝对垂直或者水平的运动，因此在一次手指操作的过程中，都会存在横向以及纵向的偏移量，内部默认会摒弃偏移量较小的一个方向，保留另一个方向的滚动。但是在某些场景我们需要同时计算横向以及纵向的手指偏移距离，而不是只计算偏移量较大的一个方向，这个时候我们只要设置 `freeScroll` 为 true 即可。\n  - **备注**：当设置 [eventPassthrough](./base-scroll-options.html#eventpassthrough) 不为空的时候，该配置无效。\n  - **示例**：\n  ```js\n  // 手指起点的坐标 e1: { pageX: 120, pageY: 120 }\n  // 手指终点的坐标 e2: { pageX: 121, pageY: 140 }\n  // offsetX:  e2.pageX - e1.pageX = 1\n  // offsetY:  e2.pageY - e1.pageY = 20\n  // 如果 freeScroll 为 false， 由于 offsetY > offsetX + directionLockThreshold\n  // offsetX 被重置为 0， 只保留 offsetY 的偏移量，因此只做一次纵向滚动\n  ```\n\n## directionLockThreshold\n  - **类型**：`number`\n  - **默认值**：`5`\n  - **作用**：当 `freeScroll` 为 false 的情况，我们需要锁定只滚动一个方向的时候，我们在**初始滚动**的时候根据横轴和纵轴滚动的绝对值做差，当差值大于 `directionLockThreshold` 的时候来决定滚动锁定的方向。\n  - **备注**：当设置 [eventPassthrough](./base-scroll-options.html#eventpassthrough) 的时候，`directionLockThreshold` 设置无效，始终为 0。\n\n## eventPassthrough\n  - **类型**： `string`\n  - **默认值**：`''`\n  - **可选值**：`'vertical' | 'horizontal'`\n  - **作用**：有时候我们使用 BetterScroll 在某个方向模拟滚动的时候，希望在另一个方向保留原生的滚动（比如轮播图，我们希望横向模拟横向滚动，而纵向的滚动还是保留原生滚动，我们可以设置 `eventPassthrough` 为 vertical；相应的，如果我们希望保留横向的原生滚动，可以设置`eventPassthrough`为 horizontal）。\n  - **备注**：`eventPassthrough` 的设置会导致其它一些选项配置无效，需要小心使用它。\n\n## click\n  - **类型**：`boolean`\n  - **默认值**：`false`\n  - **作用**：BetterScroll 默认会阻止浏览器的原生 click 事件。当设置为 true，BetterScroll 会派发一个 click 事件，我们会给派发的 event 参数加一个私有属性 `_constructed`，值为 true。\n\n\n## dblclick\n  - **类型**：`boolean | Object`\n  - **默认值**：`false`\n  - **作用**：派发双击点击事件。当配置成 true 的时候，默认 2 次点击的延时为 300 ms，如果配置成对象可以修改 `delay`。\n  ```js\n    dblclick: {\n      delay: 300\n    }\n  ```\n\n## tap\n  - **类型**：`string`\n  - **默认值**：`''`\n  - **作用**：因为 BetterScroll 会阻止原生的 click 事件，我们可以设置 tap 为 'tap'，它会在区域被点击的时候派发一个 tap 事件，你可以像监听原生事件那样去监听它。\n\n## bounce\n   - **类型**：`boolean | Object`\n   - **默认值**：`true`\n   - **作用**：当滚动超过边缘的时候会有一小段回弹动画。设置为 true 则开启动画。\n   ```js\n     bounce: {\n       top: true,\n       bottom: true,\n       left: true,\n       right: true\n     }\n   ```\n   `bounce` 可以支持关闭某些边的回弹效果，可以设置对应边的 `key` 为 `false` 即可。\n\n  :::tip\n  如果想要便捷的设置所有边为 true 或者 false，只需要设置 `bounce` 为 true 或 false 即可。\n  :::\n\n## bounceTime\n   - **类型**：`number`\n   - **默认值**：`800`（单位ms）\n   - **作用**：设置回弹动画的动画时长。\n\n## momentum\n   - **类型**：`boolean`\n   - **默认值**：`true`\n   - **作用**：当快速在屏幕上滑动一段距离的时候，会根据滑动的距离和时间计算出动量，并生成滚动动画。设置为 true 则开启动画。\n\n## momentumLimitTime\n   - **类型**：`number`\n   - **默认值**：`300`（单位ms）\n   - **作用**：只有在屏幕上快速滑动的时间小于 `momentumLimitTime`，才能开启 momentum 动画。\n\n## momentumLimitDistance\n   - **类型**：`number`\n   - **默认值**：`15`（单位px）\n   - **作用**：只有在屏幕上快速滑动的距离大于 `momentumLimitDistance`，才能开启 momentum 动画。\n\n## swipeTime\n   - **类型**：`number`\n   - **默认值**：`2500`（单位ms）\n   - **作用**：设置 momentum 动画的动画时长。\n\n## swipeBounceTime\n   - **类型**：`number`\n   - **默认值**：`500`（单位ms）\n   - **作用**：设置当运行 momentum 动画时，超过边缘后的回弹整个动画时间。\n\n## deceleration\n   - **类型**：`number`\n   - **默认值**：`0.0015`\n   - **作用**：表示 momentum 动画的减速度。\n\n## flickLimitTime\n   - **类型**：`number`\n   - **默认值**：`200`（单位ms）\n   - **作用**：有的时候我们要捕获用户的轻拂动作（短时间滑动一个较短的距离）。只有用户在屏幕上滑动的时间小于 `flickLimitTime` ，才算一次轻拂。\n\n## flickLimitDistance\n   - **类型**：`number`\n   - **默认值**：`100`（单位px）\n   - **作用**：只有用户在屏幕上滑动的距离小于 `flickLimitDistance` ，才算一次轻拂。\n\n## resizePolling\n   - **类型**：`number`\n   - **默认值**：`60`（单位ms)\n   - **作用**：当窗口的尺寸改变的时候，需要对 BetterScroll 做重新计算，为了优化性能，我们对重新计算做了延时。60ms 是一个比较合理的值。\n\n## probeType\n   - **类型**：`number`\n   - **默认值**：`0`\n   - **可选值**：`1|2|3`\n   - **作用**：决定是否派发 scroll 事件，对页面的性能有影响，尤其是在 `useTransition` 为 true 的模式下。\n\n   ```js\n   // 派发 scroll 的场景分为两种：\n   // 1. 手指作用在滚动区域（content DOM）上;\n   // 2. 调用 scrollTo 方法或者触发 momentum 滚动动画（其实底层还是调用 scrollTo 方法）\n\n   // 对于 v2.1.0 版本，对 probeType 做了一次统一\n\n   // 1. probeType 为 0，在任何时候都不派发 scroll 事件，\n   // 2. probeType 为 1，仅仅当手指按在滚动区域上，每隔 momentumLimitTime 毫秒派发一次 scroll 事件，\n   // 3. probeType 为 2，仅仅当手指按在滚动区域上，一直派发 scroll 事件，\n   // 4. probeType 为 3，任何时候都派发 scroll 事件，包括调用 scrollTo 或者触发 momentum 滚动动画\n   ```\n\n## preventDefault\n   - **类型**：`boolean`\n   - **默认值**：`true`\n   - **作用**：当事件派发后是否阻止浏览器默认行为。这个值应该设为 true，除非你真的知道你在做什么，通常你可能用到的是 `preventDefaultException`。\n\n## preventDefaultException\n   - **类型**：`Object`\n   - **默认值**：`{ tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/}`\n   - **作用**：BetterScroll 会阻止原生的滚动，这样也同时阻止了一些原生组件的默认行为。这个时候我们不能对这些元素做 preventDefault，所以我们可以配置 preventDefaultException。默认值 `{tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/}`表示标签名为 input、textarea、button、select、audio 这些元素的默认行为都不会被阻止。\n   - **备注**：这是一个非常有用的配置，它的 key 是 DOM 元素的属性值，value 可以是一个正则表达式。比如我们想配一个 class 名称为 test 的元素，那么配置规则为 `{className:/(^|\\s)test(\\s|$)/}`。\n\n## tagException\n   - **类型**：`Object`\n   - **默认值**：`{ tagName: /^TEXTAREA$/ }`\n   - **作用**：如果 BetterScroll 嵌套了 textarea 等表单元素，往往用户的预期应该是滑动 textarea 不应该引起 bs 滚动，也就是如果操纵的 DOM（eg：textarea 标签） 命中了配置的规则，bs 不会滚动。\n   - **备注**：这是一个非常有用的配置，它的 key 是 DOM 元素的属性值，value 可以是一个正则表达式。比如我们想配一个 classname 含有 test 类名的元素，那么配置规则为 `{className:/(^|\\s)test(\\s|$)/}`。\n\n## HWCompositing\n   - **类型**：`boolean`\n   - **默认值**：`true`\n   - **作用**：是否开启硬件加速，开启它会在 content 元素上添加 `translateZ(1px)` 来开启硬件加速从而提升动画性能，有很好的滚动效果。\n   - **备注**：只有支持硬件加速的浏览器开启才有效果。\n\n## useTransition\n   - **类型**：`boolean`\n   - **默认值**：`true`\n   - **作用**：是否使用 CSS3 transition 动画。如果设置为 false，则使用 requestAnimationFrame 做动画。\n\n## bindToWrapper\n   - **类型**：`boolean`\n   - **默认值**：`false`\n   - **作用**：touchmove 事件通常会绑定到 document 上而不是滚动的容器（wrapper）上，当移动的过程中光标（通常对于 PC 场景）离开滚动的容器滚动仍然会继续，这通常是期望的。当然你也可以把 move 事件绑定到滚动的容器上，`bindToWrapper` 设置为 true 即可，这样一旦移动的过程中光标离开滚动的容器，滚动会立刻停止。\n   - **注意**：对于移动端来说，就算 touchmove 事件绑定在 wrapper 上，手指离开 wrapper，依然能移动 wrapper。\n\n## disableMouse\n   - **类型**：`boolean`\n   - **默认值**：根据当前浏览器环境计算而来\n   - **作用**：当在移动端环境（支持 touch 事件），disableMouse 会计算为 true，这样就不会监听鼠标相关的事件，而在 PC 环境，disableMouse 会计算为 false，就会监听鼠标相关事件。\n\n## disableTouch\n   - **类型**：`boolean`\n   - **默认值**：根据当前浏览器环境计算而来\n   - **作用**：当在移动端环境（支持 touch 事件），disableTouch 会计算为 false，监听 touch 相关的事件，而在 PC 环境，disableTouch 会计算为 true，不会监听 touch 相关事件。\n\n  ::: warning\n  考虑到用户的一些特定场景，比如在**平板电脑需要支持 touch 事件，平板电脑接入鼠标又得支持 mouse 事件**，那么实例化 BetterScroll 需要如下配置：\n\n  ```js\n  let bs = new BScroll('.wrapper', {\n    disableMouse: false,\n    disableTouch: false\n  })\n  ```\n\n  由于不同设备、不同浏览器环境的底层实现逻辑不同，BetterScroll 内部计算是否监听 touch 还是 mouse 事件可能会有判断失误，因此你可以根据以上的选项配置来解决这类问题。\n  :::\n\n## autoBlur\n   - **类型**：`boolean`\n   - **默认值**：`true`\n   - **作用**：在滚动之前会让当前激活的元素（input、textarea）自动失去焦点。\n\n## stopPropagation\n   - **类型**：`boolean`\n   - **默认值**：`false`\n   - **作用**：是否阻止事件冒泡。多用在嵌套 scroll 的场景。\n\n## bindToTarget\n   - **类型**：`boolean`\n   - **默认值**：`false`\n   - **作用**：将 touch 或者 mouse 事件绑定在 `content` 元素而不是容器 `wrapper`上，多用于 [movable 场景](../plugins/movable.html)。\n\n## autoEndDistance\n   - **类型**：`number`\n   - **默认值**：`5`\n   - **作用**：当手指操作幅度过大，滑出视口导致可能没有触发 touchend 事件，因此 autoEndDistance 的作用就是当手指即将脱离当前视口的时候，自动调用 touchend 事件。默认距离边界 5px 的时候，结束滚动。\n\n## outOfBoundaryDampingFactor\n   - **类型**：`number`\n   - **默认值**：`1 / 3`\n   - **作用**：当超过边界的时候，进行阻尼行为，阻尼因子越小，阻力越大，取值范围：[0, 1]。\n\n## specifiedIndexAsContent <Badge text='2.0.4' />\n   - **类型**：`number`\n   - **默认值**：`0`\n   - **作用**：指定 `wrapper` 对应索引的子元素作为 `content`，默认情况下 BetterScroll 采用的是 `wrapper` 的第一个子元素作为 content。\n\n   ```html\n   <div class=\"wrapper\">\n      <div class=\"content1\">\n         <div class=\"conten1-item\">1.1</div>\n         <div class=\"conten1-item\">1.2</div>\n      </div>\n      <div class=\"content2\">\n         <div class=\"conten2-item\">2.1</div>\n         <div class=\"conten2-item\">2.2</div>\n      </div>\n   </div>\n   ```\n\n   ```js\n   // 针对以上 DOM 结构，在 BetterScroll 版本 <= 2.0.3，内部只会使用 wrapper.children[0]，也就是 div.content1 作为 content\n   // 当 版本 >= 2.0.4 的时候，可以通过 specifiedIndexAsContent 配置项来指定 content\n\n   let bs = new BScroll('.wrapper', {\n      specifiedIndexAsContent: 1 // 使用 div.content2 作为 BetterScroll 的 content\n   })\n   ```\n\n## quadrant <Badge text='2.3.0' />\n   - **类型**：`1 | 2 | 3 | 4`\n   - **默认值**：`1`\n   - **作用**：当 BetterScroll 的 wrapper DOM 的祖先元素被 CSS 强制旋转之后，原先的 x 以及 y 方向的位移需要发生一定的变换才能保证交互合理\n\n   ```html\n   <style>\n   /* wrapper 的父元素强制旋转 */\n   .container {\n      transform: rotate(90deg);\n   }\n   </style>\n   <div class=\"container\">\n      <div class=\"wrapper\">\n         <div class=\"content\">\n            <div class=\"content-item\">1.1</div>\n            <div class=\"content-item\">1.2</div>\n         </div>\n      </div>\n   </div>\n   ```\n\n   ```js\n   let bs = new BScroll('.wrapper', {\n      quadrant: 2\n   })\n   ```\n\n   1. 当 wrapper 的父元素或者祖先元素旋转的角度为 (315, 45]，quadrant 保持默认值即可；\n   2. 当 wrapper 的父元素或者祖先元素旋转的角度为 (45, 135]，quadrant **建议**为 `2`，尤其是 90 度，quadrant **必须**为 `2`；\n   3. 当 wrapper 的父元素或者祖先元素旋转的角度为 (135, 225]，quadrant **建议**为 `3`，尤其是 180 度，quadrant **必须**为 `3`；\n   4. 当 wrapper 的父元素或者祖先元素旋转的角度为 (225, 315]，quadrant **建议**为 `4`，尤其是 270 度，quadrant **必须**为 `4`；\n   5. 当旋转角度比较特殊的时候，比如 30 度，200 度，你可能不满意内置的变换逻辑，你可以通过 `coordinateTransformation` hook 来自定义你自己的变换逻辑。\n\n   ```js\n   let bs = new BScroll('.wrapper', {\n      quadrant: 1 // 保持默认即可\n   })\n   bs.scroller.actions.hooks.on(\n      bs.scroller.actions.hooks.eventTypes.coordinateTransformation,\n      (transformateDeltaData) => {\n         // 获取用户手指移动的距离\n         const originDeltaX = transformateDeltaData.deltaX\n         const originDeltaY = transformateDeltaData.deltaY\n\n         // 变换位移\n         transformateDeltaData.deltaX = originDeltaY\n         transformateDeltaData.deltaY = originDeltaX\n\n         // transformateDeltaData.deltaX 最终作用在 BetterScroll content DOM 的 translateX\n         // transformateDeltaData.deltaY 最终作用在 BetterScroll content DOM 的 translateY\n      }\n   )\n   ```\n\n   例如：使用 CSS 将横向滚动的 BetterScroll 翻转。\n\n   <demo qrcode-url=\"core/horizontal-rotated\">\n      <template slot=\"code-template\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?template\n      </template>\n      <template slot=\"code-script\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?script\n      </template>\n      <template slot=\"code-style\">\n         <<< @/examples/vue/components/core/horizontal-rotated.vue?style\n      </template>\n      <core-horizontal-rotated slot=\"demo\"></core-horizontal-rotated>\n   </demo>"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/base-scroll.md",
    "content": "# 核心滚动\n\n在 BetterScroll 2.0 的设计当中，我们抽象了核心滚动部分，它作为 BetterScroll 的最小使用单元，压缩体积比 `1.0` 小了将近三分之一，往往你可能只需要完成一个纯粹的滚动需求，那么你只需要引入这一个库，方式如下：\n\n```bash\nnpm install @better-scroll/core --save\n```\n\n```js\nimport BScroll from '@better-scroll/core'\nconst bs = new BScroll('.div')\n```\n\n## 上手\n\nBetterScroll 有多种滚动模式。\n\n- **垂直滚动**\n\n  <demo qrcode-url=\"core/default\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/default.vue?style\n    </template>\n    <core-default slot=\"demo\"></core-default>\n  </demo>\n\n  :::warning\n  BetterScroll 实时派发 scroll 事件，是需要设置 `probeType` 为 3。\n  :::\n\n- **水平滚动**\n\n  <demo qrcode-url=\"core/horizontal\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/horizontal.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/horizontal.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/horizontal.vue?style\n    </template>\n    <core-horizontal slot=\"demo\"></core-horizontal>\n  </demo>\n\n  :::warning\n  BetterScroll 实现横向滚动，对 CSS 是比较苛刻的。首先你要保证 wrapper 不换行，并且 content 的 display 是 inline-block。\n\n  ```stylus\n  .scroll-wrapper\n    // ...\n    white-space nowrap\n  .scroll-content\n    // ...\n    display inline-block\n  ```\n  :::\n\n- **freeScroll（水平与垂直同时滚动）**\n\n  <demo qrcode-url=\"core/freescroll\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/core/freescroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/core/freescroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/core/freescroll.vue?style\n    </template>\n    <core-freescroll slot=\"demo\"></core-freescroll>\n  </demo>\n\n## 动态 content <Badge text='2.0.4' />\n\n对于 `2.0.4` 版本，已经具备了探测 content 元素变成其他元素的能力，可以查看下面的例子。\n\n<demo qrcode-url=\"core/dynamic-content\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/dynamic-content.vue?style\n  </template>\n  <core-dynamic-content slot=\"demo\"></core-dynamic-content>\n</demo>\n\n## specifiedIndexAsContent <Badge text='2.0.4' />\n\n对于 `2.0.4` 版本，可以指定 **wrapper** 的某一个 children 作为 **content**，在之前的版本，BetterScroll只会处理 wrapper 的第一个子元素。[详细的文档在这](./base-scroll-options.html#specifiedindexascontent-2-0-4)。\n\n<demo qrcode-url=\"core/specified-content\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/specified-content.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/specified-content.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/specified-content.vue?style\n  </template>\n  <core-specified-content slot=\"demo\"></core-specified-content>\n</demo>\n\n## quadrant <Badge text='2.3.0' />\n\n对于 `2.3.0` 版本，如果 BetterScroll 的 wrapper DOM 的父元素或者祖先元素发生旋转，可以通过 `quadrant` 选项来修正用户的交互行为。\n\n- **竖向滚动强制变换成横向滚动**\n\n<demo qrcode-url=\"core/vertical-rotated\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/vertical-rotated.vue?style\n  </template>\n  <core-vertical-rotated slot=\"demo\"></core-vertical-rotated>\n</demo>\n\n- **横向滚动强制翻转**\n\n<demo qrcode-url=\"core/horizontal-rotated\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/core/horizontal-rotated.vue?style\n  </template>\n  <core-horizontal-rotated slot=\"demo\"></core-horizontal-rotated>\n</demo>\n\n## 温馨提示\n\n  :::tip\n  **任何时候如果出现无法滚动的情况，都应该首先查看 content 元素高度/宽度是否大于 wrapper 的高度/宽度**。这是内容能够滚动的前提条件。\n\n  如果内容存在图片的情况，可能会出现 DOM 元素渲染时图片还未下载，因此内容元素的高度小于预期，出现滚动不正常的情况。此时你应该在图片加载完成后，比如 onload 事件回调中，调用 `bs.refresh` 方法，它会重新计算最新的滚动距离。\n  :::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/how-to-install.md",
    "content": "# 安装\n\n## NPM\n\nBetterScroll 托管在 NPM 上，执行如下命令安装：\n\n```bash\nnpm install @better-scroll/core --save\n\n// or\n\nyarn add @better-scroll/core\n```\n\n接下来就可以在代码中引入了，[webpack](https://webpack.js.org/) 等构建工具都支持从 node_modules 里引入代码：\n\n``` js\nimport BScroll from '@better-scroll/core'\n```\n\n如果是 commonjs 的语法，如下：\n\n``` js\nvar BScroll = require('@better-scroll/scroll')\n```\n\n## script 加载\n\nBetterScroll 也支持直接用 script 加载的方式，加载后会在 window 上挂载一个 BScroll 的对象。\n\n```html\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/@better-scroll/core@latest/dist/core.min.js\"></script>\n```\n\n```js\nlet wrapper = document.getElementById(\"wrapper\")\nlet bs = new BScroll(wrapper, {})\n```\n\n## 具备所有插件能力的 BetterScroll\n\n```bash\nnpm install better-scroll --save\n\n// or\n\nyarn add better-scroll\n```\n\n```js\nimport BetterScroll from 'better-scroll'\nlet bs = new BetterScroll('.wrapper', {})\n```\n\n也可以通过 CDN 加载。\n\n```html\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.js\"></script>\n\n<!-- minify -->\n<script src=\"https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js\"></script>\n```\n\n```js\nlet bs = BetterScroll.createBScroll('.wrapper', {})\n```"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/guide/use.md",
    "content": "# 使用\n\n## 基础滚动\n\n如果你只需要一个拥有基础滚动能力的列表，只需要引入 core。\n\n```js\nimport BScroll from '@better-scroll/core'\nlet bs = new BScroll('.wrapper', {\n  // ...... 详见配置项\n})\n```\n\n## 增强型滚动\n\n如果你需要一些额外的 feature。比如 `pull-up`，你需要引入额外的插件，详情查看[插件](/docs/zh-CN/plugins)。\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pullup from '@better-scroll/pull-up'\n\n// 注册插件\nBScroll.use(Pullup)\n\nlet bs = new BScroll('.wrapper', {\n  probeType: 3,\n  pullUpLoad: true\n})\n```\n\n## 全能力的滚动\n\n如果你觉得一个个引入插件很费事，我们提供了一个拥有全部插件能力的 BetterScroll 包。它的使用方式与 `1.0` 版本一模一样，但是体积会相对大很多，推荐**按需引入**。\n\n```js\nimport BScroll from 'better-scroll'\n\nlet bs = new BScroll('.wrapper', {\n  // ...\n  pullUpLoad: true,\n  wheel: true,\n  scrollbar: true,\n  // and so on\n})\n```\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/README.md",
    "content": "# 插件\n\n## 为什么要有插件\n\n为了解耦 BetterScroll 1.x 的各个 feature 的功能，防止 bundle 体积无限制的增加。在 2.x 的架构设计当中，采用了『插件化』 的架构设计。对于 1.x 的各个 feature，在 2.x 都将以 Plugin 的形式实现。\n\n已有的插件：\n- [pulldown](./pulldown.html)\n- [pullup](./pullup.html)\n- [scrollbar](./scroll-bar.html)\n- [slide](./slide.html)\n- [wheel](./wheel.html)\n- [zoom](./zoom.html)\n- [mouse-wheel](./mouse-wheel.html)\n- [observe-dom](./observe-dom.html)\n- [observe-image](./observe-image.html)\n- [nested-scroll](./nested-scroll.html)\n- [infinity](./infinity.html)\n- [movable](./movable.html)\n- [indicators](./indicators.html)\n\n非常欢迎大家参与插件的贡献，在此之前，你需要花点时间了解这个[如何编写插件](./how-to-write.html)。\n\n## 使用插件\n\n通过全局方法 `BScroll.use()` 使用插件。它需要在你调用 `new BScroll()` 之前完成：\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Plugin from 'somewhere'\n\n  BScroll.use(Plugin)\n  new BScroll('.wrapper', {\n    pluginKey: {} // pluginKey 对应 Plugin 类上静态属性 pluginName 的值，否则插件无法实例化\n  })\n```\n\n## 使用插件的方法和属性\n\n插件中可能会暴露一些方法和属性，这些方法和属性在你执行完 `new BScroll()` 之后，会通过 `Object.defineProperty` 的方式代理至 `bs`。例如，zoom 插件中提供了 `zoomTo` 方法，你可以通过下面的方式来使用：\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Zoom)\n\n  const bs = new BScroll('#scroll-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    disableMouse: true,\n    useTransition: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n\n  bs.zoomTo(1.5, 0, 0) // 不用关心 zoom 插件实例，直接通过 bs 获取暴露的属性或者方法。\n```\n\n## 使用插件的事件\n\n和方法、属性类似，插件中暴露的事件最终也会被代理至 `bs`。例如，zoom 插件中提供了 `zoomStart` 事件，你可以通过下面的方式来注册事件侦听器：\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Zoom from '@better-scroll/zoom'\n\n  BScroll.use(Zoom)\n\n  const bs = new BScroll('#scroll-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n\n  bs.on('zoomStart', zoomStartHandler)\n  // so, you can do anything in zoomStartHandler\n```\n\n## 具备所有插件能力的 BetterScroll\n\n考虑到一个个注册插件比较麻烦，如果你的项目用到 BetterScroll 的全部插件能力，我们提供了一劳永逸的方案。\n\n\n```js\n  import BScroll from 'better-scroll'\n\n  const bs = new BScroll('#scroll-wrapper', {\n    pullUpLoad: true,\n    pullDownRefresh: true,\n    scrollbar: true,\n    // 等等\n  })\n```\n\n::: warning 注意\n引用全部的 BetterScroll 可能对你的 bundle 体积有很大的冲击，而且随着 BetterScroll 的功能扩展，体积会无限制的增加，**请按需引入**。\n:::\n\n::: warning 注意\n通常情况下，你应该关注 BetterScroll 实例暴露出来的属性和方法，因为对于插件实例上的属性和方法，都已经代理到 bs 上面，如果你真的需要关心插件实例，你也可以通过 `bs.plugins` 来获取所有插件的信息。\n\n```js\n  import BScroll from '@better-scroll/scroll'\n  import zoom from '@better-scroll/zoom'\n\n  BScroll.use(zoom)\n\n  const bs = new BScroll('.wrapper', {\n    zoom: true\n  })\n\n  console.log(bs.plugins.zoom) // 获取对应插件实例\n```\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/compose-plugins.md",
    "content": "# 插件的高阶使用\n在许多场景下，单插件的better-scroll实例（以下简称`BS`）很难满足我们当下的业务需求，往往我们需要通过多个插件的组合使用来实现我们想要的效果。\n\n## pullup-pulldown\n\n\n## pullup-pulldown-slide\n\n## pullup-pulldown-nestedscroll\n\n## nestedscroll-slide\n\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/how-to-write.md",
    "content": "# 如何写一个插件\n\n### 构思插件的功能\n\n```js\n  import BScroll from '@better-scroll/core'\n  import MyPlugin from '@better-scroll/my-plugin'\n\n  BScroll.use(MyPlugin)\n\n  const bs = new BScroll('.wrapper', {\n    myPlugin: {\n      scrollText: 'I am scrolling',\n      scrollEndText: 'Scroll has ended'\n    },\n    // 或者\n    myPlugin: true\n  })\n\n  // 使用插件暴露到 bs 的事件\n  bs.on('printScrollEndText', (scrollEndText) => {\n    console.log(scrollEndText) // 打印 \"Scroll has ended, position is (xx, yy)\"\n  })\n\n  // 使用插件代理到 bs 实例上的方法\n  bs.printScrollText() // 打印 \"I am scrolling\"\n```\n\n### 编写插件\n\n1. **TypeScript 声明合并以及暴露插件方法**\n\n```typescript\nimport BScroll from '@better-scroll/core'\n\nexport type MyPluginOptions = Partial<MyPluginConfig> | true\n\ntype MyPluginConfig = {\n  scrollText: string,\n  scrollEndText: string\n}\n\ninterface PluginAPI {\n  printScrollText(): void\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    myPlugin?: MyPluginOptions\n  }\n\n  interface CustomAPI {\n    myPlugin: PluginAPI\n  }\n}\n```\n\n这样做的好处，就是为了在引入 `myPlugin` 插件并且实例化 BetterScroll 的时候，能够有对应的 Options 提示以及 bs 能够有对应的方法提示，以 pulldown 插件为例：\n\n<img data-zoomable :src=\"$withBase('/assets/images/tip1.png')\" alt=\"\">\n\n<img data-zoomable :src=\"$withBase('/assets/images/tip2.png')\" alt=\"\">\n\n2. **编写插件主体**\n\n    - **BetterScroll 的插件需要是一个类，并且具有以下特性：**\n\n      - 静态的 pluginName 属性。\n      - 实现 PluginAPI 接口（当且仅当需要把插件方法代理至 bs）。\n      - constructor 的第一个参数就是 BetterScroll 实例 `bs`，你可以通过 bs 的**事件**或者**钩子**来注入自己的逻辑。\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          static pluginName = 'myPlugin'\n          public options: MyPluginConfig\n          constructor(public scroll: BScroll){\n            this.handleOptions()\n\n            this.handleBScroll()\n\n            this.registerHooks()\n          }\n        }\n      ```\n\n    - **handleOptions**\n\n      合并用户传入的 options，收缩它的类型。\n\n      ```typescript\n        import { extend } from '@better-scroll/shared-utils'\n        export default class MyPlugin {\n          private handleOptions() {\n            const userOptions = (this.scroll.options.myPlugin === true\n              ? {}\n              : this.scroll.options.myPlugin) as Partial<MyPluginConfig>\n            const defaultOptions: MyPluginConfig = {\n              scrollText: 'I am scrolling',\n              scrollEndText: 'Scroll has ended'\n            }\n            this.options = extend(defaultOptions, userOptions)\n          }\n        }\n      ```\n\n    - **handleBScroll**\n\n      代理事件以及方法至 BetterScroll 实例。\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          private handleBScroll() {\n            const propertiesConfig = [\n              {\n                key: 'printScrollText',\n                sourceKey: 'plugins.myPlugin.printScrollText'\n              }\n            ]\n            // 将 myPlugin.printScrollText 代理至 bs.printScrollText\n            this.scroll.proxy(propertiesConfig)\n            // 注册 printScrollEndText 事件至 bs，以至于用户可以通过 bs.on('printScrollEndText', handler) 来订阅事件\n            this.scroll.registerType(['printScrollEndText'])\n          }\n\n          printScrollText() {\n            console.log(this.options.scrollText)\n          }\n        }\n      ```\n\n    - **registerHooks**\n\n      钩入 bs 的钩子，实现插件的逻辑，并且派发插件自定义的事件。\n\n      ```typescript\n        export default class MyPlugin implements PluginAPI {\n          private registerHooks() {\n            const scroll = this.scroll\n            scroll.on(scroll.eventTypes.scrollEnd, ({ x, y }) => {\n              scroll.trigger(\n                scroll.eventTypes.printScrollEndText,\n                `${this.options.scrollEndText}, position is (${x}, ${y})`\n              )\n            })\n          }\n        }\n      ```\n\n恭喜你，一个简单的 BetterScroll 插件就已经完成啦，如果结合你的场景，需要更复杂的插件，可以仔细阅读 [事件与钩子大全](../guide/base-scroll-api.html#事件-vs-钩子)，它能很好的帮助你来完成一款独特的插件。\n\n查看[完整的 repo](https://github.com/better-scroll/plugin-tutorial)，以及[线上例子](https://better-scroll.github.io/plugin-tutorial/)"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/indicators.md",
    "content": "# indicators <Badge text=\"2.2.0\" />\n\n## 介绍\n\nindicators 赋予了联动另外一个 BetterScroll 的能力，借助于此，可以实现**视觉滚动差**、**放大镜**等效果。\n\n::: tip 提示\n这是一个非常强大并且具有创造力的插件，限制你的只有你的想象力！\n:::\n\n## 安装\n\n```bash\nnpm install @better-scroll/indicators --save\n\n// or\n\nyarn add @better-scroll/indicators\n```\n\n## 使用\n\n首先引入 indicators 插件，并通过静态方法 `BScroll.use()` 注册插件\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Indicators from '@better-scroll/indicators'\n\nBScroll.use(Indicators)\n```\n\n接着在 `options` 传入正确的配置。\n\n```js\nnew BScroll('.wrapper', {\n  indicators: {\n    // 详情可以参考下面的 demo\n    relationElement: \"联动的元素 DOM\"\n  }\n})\n```\n## 示例\n\n  - **放大镜效果**\n\n    <demo qrcode-url=\"indicators/minimap\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/indicators/minimap.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/indicators/minimap.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/indicators/minimap.vue?style\n      </template>\n      <indicators-minimap slot=\"demo\"></indicators-minimap>\n    </demo>\n\n  - **视觉滚动差**\n\n    <demo qrcode-url=\"indicators/parallax-scroll\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/indicators/parallax-scroll.vue?style\n      </template>\n      <indicators-parallax-scroll slot=\"demo\"></indicators-parallax-scroll>\n    </demo>\n\n## indicators 选项对象\n\n### relationElement\n\n  - **类型**：`HTMLElement`\n\n  与另外一个 BetterScroll 关联的容器元素，正如上面的 demo， `div.scroll-indicator` 就是 releationElement。**releationElement 必须由用户传入，并且拥有子元素**。\n\n### relationElementHandleElementIndex\n\n  - **类型**：`number`\n  - **默认值**：`0`\n\n  指定 releationElement 的第几个子元素作为操控的元素，详细可以参考以上的 demo。\n\n### ratio\n\n  - **类型**：`number | Ratio | undefined`\n  - **默认值**：`undefined`\n\n  ```ts\n  type Ratio = {\n    x: number // 指定 x 方向滚动距离的比例\n    y: number // 指定 y 方向滚动距离的比例饿\n  }\n  ```\n  指定 releationElement 与 BetterScroll 滚动距离的比例。一般情况下，**插件内部会自动计算**两者的滚动比例，但是你也可以手动指定比例，来实现 `视觉滚动差` 的效果。详细可以参考以上的 Demo。\n\n### interactive\n\n  - **类型**：`boolean | undefined`\n  - **默认值**：`undefined`\n\n  决定 relationElement 的第 relationElementHandleElementIndex 个子元素是否可以交互，当它置成 false 的时候，则不响应 touch / mouse 事件。"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/infinity.md",
    "content": "# infinity\n\n## 介绍\n\ninfinity 插件为 BetterScroll 提供了无限滚动的能力。如果有大量的列表数据需要渲染，可以使用 infinity 插件，此时 BetterScroll 只会渲染一定数量的 DOM 元素，从而使页面在大量数据时依然保持流畅滚动。\n\n> 注意：除非你有大量的数据渲染需求，否则使用 core 即可。\n\n## 安装\n\n```shell\nnpm install @better-scroll/infinity --save\n\n// or\n\nyarn add @better-scroll/infinity\n```\n\n## 使用\n\n首先引入 infinity 插件，并通过静态方法 `BScroll.use()` 初始化插件\n\n```js\nimport BScroll from '@better-scroll/core'\nimport InfinityScroll from '@better-scroll/infinity'\n\nBScroll.use(InfinityScroll)\n```\n\n然后，实例化 BetterScroll 时需要传入相关配置项 infinity:\n\n```typescript\nnew BScroll('.bs-wrapper', {\n  scrollY: true,\n  infinity: {\n    fetch(count) {\n      // 获取大于 count 数量的数据，该函数是异步的，它需要返回一个 Promise。\n      // case 1. resolve 数据数组Array<data>，来告诉 infinity 渲染数据，render 的第一个参数就是数据项\n      // case 2. resolve(false), 来停止无限滚动\n    },\n    render(item, div?: HTMLElement) {\n      // item 是 fetch 函数提供的每一个数据项，\n      // div 是页面回收的 DOM，可能不存在\n      // 如果 div 不存在，你需要创建一个新的 HTMLElement 元素\n      // 必须返回一个 HTMLElement\n    },\n    createTombstone() {\n      // 必须返回一个墓碑 DOM 节点。\n    }\n  }\n})\n```\n\n::: danger 危险\n`fetch`、`render`、`createTombstone` 必须按照注释来实现，否则内部会报错。\n\n插件内部依赖 Promise，如果浏览器不支持，需要 Promise 的 Polyfill。\n:::\n\n## 示例\n\n<demo qrcode-url=\"infinity/\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/infinity/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/infinity/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/infinity/default.vue?style\n  </template>\n  <infinity-default slot=\"demo\"></infinity-default>\n</demo>\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/mouse-wheel.md",
    "content": "# mouse-wheel\n\nmouseWheel 扩展 BetterScroll 鼠标滚轮的能力。\n\n# 安装\n\n```bash\nnpm install @better-scroll/mouse-wheel --save\n\n// or\n\nyarn add @better-scroll/mouse-wheel\n```\n\n:::tip\n目前支持鼠标滚轮有：core、slide、wheel、pullup、pulldown\n:::\n\n## 基础使用\n\n为了开启鼠标滚动功能，你需要首先引入 mouseWheel 插件，通过静态方法 `BScroll.use()` 注册插件，最后传入正确的 [mouseWheel 选项对象](./mouse-wheel.html#mousewheel-选项对象)\n\n```js\n  import BScroll from '@better-scroll/core'\n  import MouseWheel from '@better-scroll/mouse-wheel'\n  BScroll.use(MouseWheel)\n\n  new BScroll('.bs-wrapper', {\n    //...\n    mouseWheel: {\n      speed: 20,\n      invert: false,\n      easeTime: 300\n    }\n  })\n```\n\n- **纵向普通滚动示例**\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/vertical-scroll\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/vertical-scroll.vue?style\n    </template>\n    <mouse-wheel-vertical-scroll slot=\"demo\"></mouse-wheel-vertical-scroll>\n  </demo>\n\n- **横向普通滚动示例**\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/horizontal-scroll\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/horizontal-scroll.vue?style\n    </template>\n    <mouse-wheel-horizontal-scroll slot=\"demo\"></mouse-wheel-horizontal-scroll>\n  </demo>\n\n## 进阶使用\n\nmouseWheel 插件还可以搭配其他的插件，为其增加鼠标滚轮的操作。\n\n- **mouseWheel & slide**\n\n  通过鼠标滚轮操作 [slide](./slide.html)。\n\n  - **横向 slide 示例**\n\n    <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/horizontal-slide\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/mouse-wheel/horizontal-slide.vue?style\n      </template>\n      <mouse-wheel-horizontal-slide slot=\"demo\"></mouse-wheel-horizontal-slide>\n    </demo>\n\n  - **纵向 slide 示例**\n\n    <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/vertical-slide\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/mouse-wheel/vertical-slide.vue?style\n      </template>\n      <mouse-wheel-vertical-slide slot=\"demo\"></mouse-wheel-vertical-slide>\n    </demo>\n\n- **mouseWheel & pullup**\n\n  通过鼠标触发上拉加载 [pullup](./pullup.html)。\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/pullup\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/pullup.vue?style\n    </template>\n    <mouse-wheel-pullup slot=\"demo\"></mouse-wheel-pullup>\n  </demo>\n\n- **mouseWheel & pulldown**\n\n  通过鼠标触发下拉加载 [pulldown](./pulldown.html)。\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/pulldown\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/pulldown.vue?style\n    </template>\n    <mouse-wheel-pulldown slot=\"demo\"></mouse-wheel-pulldown>\n  </demo>\n\n- **mouseWheel & wheel**\n\n  通过鼠标触发 [wheel](./wheel.html)。\n\n  <demo :hide-qrcode=\"true\" qrcode-url=\"mouse-wheel/picker\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/mouse-wheel/picker.vue?style\n    </template>\n    <mouse-wheel-picker slot=\"demo\"></mouse-wheel-picker>\n  </demo>\n\n## mouseWheel 选项对象\n\n### speed\n\n  - **类型**：`number`\n  - **默认值**：`20`\n\n  鼠标滚轮滚动的速度。\n\n### invert\n\n  - **类型**：`boolean`\n  - **默认值**：`false`\n\n  当该值为 true 时，表示滚轮滚动和 BetterScroll 滚动的方向相反。\n\n### easeTime\n\n  - **类型**：`number`\n  - **默认值**： `300`(ms)\n\n  滚动动画的缓动时长。\n\n### discreteTime\n\n  - **类型**：`number`\n  - **默认值**： `400`(ms)\n\n  由于滚轮滚动是一种离散的运动，并没有 start、move、end 的事件类型，因此只要在 discreteTime 时间内没有探测到滚动，那么一次的滚轮动作就结束了。\n\n  ::: warning 注意\n  当搭配 [pulldown](./pulldown.html) 插件的时候，`easeTime` 和 `discreteTime` 会被内部修改成合理的固定值，以便触发 `pullingDown` 钩子\n  :::\n\n### throttleTime\n\n  - **类型**：`number`\n  - **默认值**： `0`(ms)\n\n  由于滚轮滚动是高频率的动作，因此可以通过 throttleTime 来限制触发频率，mouseWheel 内部会缓存滚动的距离，并且每隔 throttleTime 会计算缓存的距离并且滚动。\n\n  > 修改 throttleTime 可能会造成滚动动画不连贯，请根据实际场景进行调整。\n\n### dampingFactor\n\n  - **类型**：`number`\n  - **默认值**： `0.1`\n\n  阻尼因子，值的范围是[0, 1]，当 BetterScroll 滚出边界的时候，需要施加阻力，防止滚动幅度过大，值越小，阻力越大。\n\n:::tip 提示\n当 mouseWheel 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  mouseWheel: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  mouseWheel: {\n    speed: 20,\n    invert: false,\n    easeTime: 300,\n    discreteTime: 400,\n    throttleTime: 0,\n    dampingFactor: 0.1\n  }\n})\n```\n:::\n\n## 事件\n\n### alterOptions\n\n  - **参数**：`MouseWheelConfig`\n    ```typescript\n    export interface MouseWheelConfig {\n      speed: number\n      invert: boolean\n      easeTime: number\n      discreteTime: number\n      throttleTime: number,\n      dampingFactor: number\n    }\n    ```\n  - **触发时机**：滚轮滚动开始\n\n  允许修改 options 来控制滚动中的某些行为。\n\n### mousewheelStart\n\n  - **参数**：无\n  - **触发时机**：滚轮滚动开始。\n\n### mousewheelMove\n\n  - **参数**： `{ x, y }`\n  - `{ number } x`：当前 BetterScroll 的横向滚动位置\n  - `{ number } y`：当前 BetterScroll 的纵向滚动位置\n  - **类型**： `{ x: number, y: number }`\n  - **触发时机**：滚轮滚动中\n\n### mousewheelEnd\n\n  - **参数**：`delta`\n  - **类型**： `WheelDelta`\n  ```typescript\n    interface WheelDelta {\n      x: number\n      y: number\n      directionX: Direction\n      directionY: Direction\n    }\n  ```\n  - **触发时机**：discreteTime 之后如果还没有触发 mousewheel 事件，那么便结算一次滚轮滚动行为。\n\n  ::: danger 警告\n  由于 mousewheel 事件的特殊性，mousewheelEnd 派发并不代表滚动动画结束。\n  :::\n\n\n::: tip 提示\n在绝大多数的场景下，如果你想要精确的知道当前 BetterScroll 的滚动位置，请监听 scroll、scrollEnd 钩子，而不是 `mouseXXX` 钩子。\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/movable.md",
    "content": "# movable\n\n## 介绍\n\nmovable 插件为 BetterScroll 拓展可移动拖拽的能力。\n\n## 安装\n\n```bash\nnpm install @better-scroll/movable --save\n\n// or\n\nyarn add @better-scroll/movable\n```\n\n## 基本使用\n\n首先引入 movable 插件，并通过全局方法 `BScroll.use()` 注册插件\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n\n  BScroll.use(Movable)\n```\n\n上面步骤完成后，需要在 `options` 中传入正确的配置：\n\n```js\nnew BScroll('.bs-wrapper', {\n  bindToTarget: true,\n  scrollX: true,\n  scrollY: true,\n  freeScroll: true,\n  bounce: true\n  movable: true // for movable plugin\n})\n```\n\n以下是移动插件专属以及[ BetterScroll 的配置](../guide/base-scroll-options.html)：\n\n- **movable<插件专属>**\n\n  开启 movable 功能，必须设置为 `true`，若没有该项，则插件不会生效。\n\n- **bindToTarget**\n\n  必须设置为 `true`，主动将 touch 事件绑定在**待移动**的元素上，因为BetterScroll 默认将 touch 事件在绑定容器元素（wrapper）上。\n\n- **freeScroll**\n\n  记录 x 和 y 轴的手指偏移量，设置为 `true`。同时需要设置 scrollX 和 scrollY 均为 true。\n\n- **scrollX**\n\n  开启 x 轴方向滚动能力，设置为 `true`。\n\n- **scrollY**\n\n  开启 y 轴方向滚动能力，设置为 `true`。\n\n- **bounce**\n\n  指定开启边界回弹。\n\n  - **示例**\n\n    ```js\n      {\n        bounce: true // 开启四个方向,\n        bounce: {\n          left: true, // 开启左边界回弹\n          right: true, // 开启右边界回弹\n          top: false,\n          bottom: false\n        }\n      }\n    ```\n## 示例\n\n  - **只有一个 content**\n\n    通常场景下，只存在一个 content。\n\n    <demo qrcode-url=\"movable/default\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/default.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/default.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/default.vue?style\n      </template>\n      <movable-default slot=\"demo\"></movable-default>\n    </demo>\n\n  - **多个 content**\n\n    但是在某些场景下，可能存在多个 content。\n\n    <demo qrcode-url=\"movable/multi-content\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/multi-content.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/multi-content.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/multi-content.vue?style\n      </template>\n      <movable-multi-content slot=\"demo\"></movable-multi-content>\n    </demo>\n\n## 进阶使用\n\n搭配[ zoom ](./zoom.html#介绍)插件，增加缩放能力。\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Movable from '@better-scroll/movable'\n  import Zoom from '@better-scroll/zoom'\n  new BScroll('.bs-wrapper', {\n    bindToTarget: true,\n    scrollX: true,\n    scrollY: true,\n    freeScroll: true,\n    bounce: true\n    movable: true // for movable plugin\n    zoom: { // for zoom plugin\n      start: 1,\n      min: 1,\n      max: 3\n    }\n  })\n```\n\n## 示例\n\n  :::warning\n  zoom 暂不支持在 pc 端的交互操作，下方 demo 请扫码体验。\n  :::\n\n  - **一个 content**\n\n    <demo qrcode-url=\"movable/scale\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/scale.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/scale.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/scale.vue?style\n      </template>\n      <movable-scale slot=\"demo\"></movable-scale>\n    </demo>\n\n  - **多个 content**\n\n    <demo qrcode-url=\"movable/multi-content-scale\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/movable/multi-content-scale.vue?style\n      </template>\n      <movable-multi-content-scale slot=\"demo\"></movable-multi-content-scale>\n    </demo>\n\n## 实例方法\n\n### putAt(x, y, [time], [easing]) <Badge text='2.0.4' />\n  - **参数**\n    - `{PositionX} x`： x 坐标\n      - `PositionX：'number | 'left' | 'right' | 'center'`\n    - `{PositionY} y`： y 坐标\n      - `PositionY：'number | 'top' | 'bottom' | 'center'`\n    - `{number} [time]<可选>`：滚动的动画时长\n    - `{EaseItem} [easing]<可选>`：缓动效果配置，参考 [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts)，默认是 `bounce` 效果\n\n    将 content 元素放置在某一个位置。x 与 y 不仅可以是数字，也可以是对应的字符串。\n\n  - **示例**\n\n  ```js\n  const bs = new BScroll('.bs-wrapper', {\n    bindToTarget: true,\n    scrollX: true,\n    scrollY: true,\n    freeScroll: true,\n    movable: true\n  })\n\n  bs.putAt('center', 'center', 0) // 放置在 wrapper 的正中心\n  bs.putAt('right', 'bottom', 1000) // 放置在 wrapper 的右下角，动画时长 1s\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/nested-scroll.md",
    "content": "# nested-scroll\n\n## 介绍\n\n协调嵌套的 BetterScroll 滚动行为。\n\n::: warning 警告\nv2.1.0 支持**多层嵌套**的 BetterScroll，并且功能更强大，性能更好。在这之前，只支持**双层嵌套**，请尽快升级至 2.1.0 版本。\n:::\n\n::: tip 提示\n**v2.1.0** 完美解决多层嵌套 BetterScroll 的 click 事件多次派发的问题。\n:::\n\n## 安装\n\n```bash\nnpm install @better-scroll/nested-scroll --save\n\n// or\n\nyarn add @better-scroll/nested-scroll\n```\n\n## 使用\n\n你需要首先引入 `nested-scroll` 插件，并通过全局方法 `BScroll.use()` 使用\n\n```js\n  import BScroll from '@better-scroll/core'\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  BScroll.use(NestedScroll)\n```\n\n上面步骤完成后，BScroll 的 `options` 中配置 `nestedScroll`。\n\n```js\n  // < v2.1.0\n  // parent bs\n  new BScroll('.outerWrapper', {\n    nestedScroll: true\n  })\n  // child bs\n  new BScroll('.innerWrapper', {\n    nestedScroll: true\n  })\n\n  // v2.1.0\n  // parent bs\n  new BScroll('.outerWrapper', {\n    nestedScroll: {\n      groupId: 'dummy-divide' // string or number\n    }\n  })\n  // child bs\n  new BScroll('.innerWrapper', {\n    nestedScroll: {\n      groupId: 'dummy-divide'\n    }\n  })\n```\n\n具有相同 `groupId` 的 BetterScroll 实例（bs）**共享同一个** NestedScroll 实例（ns），ns 会协调每个 bs 滚动行为，一旦某个 bs 销毁的时候，ns 都会失去对它的掌控，例如：\n\n```js\n// parent bs\nconst bs1 = new BScroll('.outerWrapper', {\n  nestedScroll: {\n    groupId: 'shared' // string or number\n  }\n})\n// child bs\nconst bs2 = new BScroll('.innerWrapper', {\n  nestedScroll: {\n    groupId: 'shared'\n  }\n})\n\nconsole.log(bs1.plugins.nestedScroll === bs2.plugins.nestedScroll) // true\n\nbs2.destroy() // nestedScroll 不再约束 bs2，不再协调 bs1 与 bs2 的滚动行为\n```\n\n## 示例\n\n- **竖向双层嵌套 <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/vertical\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/vertical.vue?style\n    </template>\n    <nested-scroll-vertical slot=\"demo\"></nested-scroll-vertical>\n  </demo>\n\n- **竖向三层嵌套 <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/triple-vertical\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/triple-vertical.vue?style\n    </template>\n    <nested-scroll-triple-vertical slot=\"demo\"></nested-scroll-triple-vertical>\n  </demo>\n\n- **横向双层嵌套 <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"nested-scroll/horizontal\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/nested-scroll/horizontal.vue?style\n    </template>\n    <nested-scroll-horizontal slot=\"demo\"></nested-scroll-horizontal>\n  </demo>\n\n  - **横向竖向双层嵌套 <Badge text='2.1.0' />**\n\n    <demo qrcode-url=\"nested-scroll/horizontal-in-vertical\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/nested-scroll/horizontal-in-vertical.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/nested-scroll/horizontal-in-vertical.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/nested-scroll/horizontal-in-vertical.vue?style\n      </template>\n      <nested-scroll-horizontal-in-vertical slot=\"demo\"></nested-scroll-horizontal-in-vertical>\n    </demo>\n\n## 实例方法 <Badge text='2.1.0' />\n\n:::tip 提示\n以下方法皆已代理至 BetterScroll 实例，例如：\n\n```js\nimport BScroll from '@better-scroll/core'\nimport NestedScroll from '@better-scroll/nested-scroll'\n\nBScroll.use(NestedScroll)\n\nconst bs1 = new BScroll('.parent-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\nconst bs2 = new BScroll('.child-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\n// 销毁 nestedScroll，bs1 与 bs2 共享同一个 nestedScroll 实例，因为他们的 groupId 相同\nbs1.purgeNestedScroll() // 与 bs2.purgeNestedScroll() 的效果一样\n```\n:::\n\n### `purgeNestedScroll()`\n\n  - **介绍**：销毁管控自己的 nestedScroll\n\n::: warning 注意\n不同的 `groupId` 会生成不同的 nestedScroll，相同的 `groupId` 会共享同一份 nestedScroll，因此你应该在合适的时机（比如组件销毁的时候）调用 `purgeNestedScroll` 来清理内存。或者你也可以调用 BetterScroll 的 destroy 方法把自身从 nestedScroll 移除，例如：\n\n```js\nconst bs1 = new BScroll('.parent-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\nconst bs2 = new BScroll('.child-wrapper', {\n  nestedScroll: {\n    groupId: 'dummy'\n  }\n})\n\nbs1.destroy() // nestedScroll 不再管控 bs1\nbs2.destroy() // nestedScroll 不再管控 bs2\n```\n:::\n\n## 静态方法 <Badge text='2.1.0' />\n\n### `getAllNestedScrolls()`\n\n  - **介绍**：获取当前所有的 nestedScroll 实例\n\n  - **返回**：nestedScroll 实例组成的数组\n\n  ```typescript\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  const nestedScrolls: NestedScroll[] = NestedScroll.getAllNestedScrolls()\n  ```\n\n### `purgeAllNestedScrolls()`\n\n  - **介绍**：销毁当前所有的 nestedScroll 实例\n\n  ```typescript\n  import NestedScroll from '@better-scroll/nested-scroll'\n\n  // 不再约束任何 BetterScroll 实例\n  NestedScroll.purgeAllNestedScrolls()\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/observe-dom.md",
    "content": "# observe-dom\n\n开启对 content 以及 content 子元素 DOM 改变的探测。当插件被使用后，当这些 DOM 元素发生变化时，将会触发 scroll 的 refresh 方法。 observe-dom 插件具有以下几个特性：\n\n- 针对改变频繁的 CSS 属性，增加 debounce\n- 如果改变发生在 scroll 动画过程中，则不会触发 refresh\n\n## 安装\n\n```bash\nnpm install @better-scroll/observe-dom --save\n\n// or\n\nyarn add @better-scroll/observe-dom\n```\n\n## 使用\n\n```js\n  import BScroll from '@better-scroll/core'\n  import ObserveDOM from '@better-scroll/observe-dom'\n  BScroll.use(ObserveDOM)\n\n  new BScroll('.bs-wrapper', {\n    //...\n    observeDOM: true // 开启 observe-dom 插件\n  })\n```\n\n## 示例\n\n  <demo qrcode-url=\"observe-dom/\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/observe-dom/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/observe-dom/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/observe-dom/default.vue?style\n    </template>\n    <observe-dom-default slot=\"demo\"></observe-dom-default>\n  </demo>\n\n\n:::warning 注意\n由于插件的内部实现使用的是 [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)，它无法探测到 img 标签的是否加载完成，因此对于 content 内部含有不确定高度的图片，需要等图片加载完成再调用 bs.refresh() 来重新计算可滚动尺寸。如果浏览器不支持 MutationObserver，插件内部的降级方案是每秒重新计算可滚动的尺寸。\n:::\n\n:::tip 提示\nv2.1.0 版本，新增 [observe-image](./observe-image) 插件来探测 img 标签的加载，因此这两者可以搭配起来，补齐**主动刷新**的能力来更新 BetterScroll 每次滚动的宽度或者高度。\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/observe-image.md",
    "content": "# observe-image <Badge text='2.1.0' />\n\n## 介绍\n\n开启对 wrapper 子元素中图片元素的加载的探测。无论图片的加载成功与否，都会自动调用 BetterScroll 的 refresh 方法来重新计算可滚动的宽度或者高度，新增于 v2.1.0 版本。\n\n:::tip 提示\n对于已经用 CSS 确定图片宽高的场景，不应该使用该插件，因为每次调用 refresh 对性能会有影响。只有在**图片的宽度或者高度不确定**的情况下，你才需要它。\n:::\n\n## 安装\n\n```bash\nnpm install @better-scroll/observe-image --save\n\n// or\n\nyarn add @better-scroll/observe-image\n```\n\n## 使用\n\n```js\n  import BScroll from '@better-scroll/core'\n  import ObserveImage from '@better-scroll/observe-image'\n  BScroll.use(ObserveImage)\n\n  new BScroll('.bs-wrapper', {\n    //...\n    observeImage: true // 开启 observe-image 插件\n  })\n```\n\n## 示例\n\n  <demo qrcode-url=\"observe-image/\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/observe-image/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/observe-image/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/observe-image/default.vue?style\n    </template>\n    <observe-image-default slot=\"demo\"></observe-image-default>\n  </demo>\n\n## observeImage 选项对象\n\n:::tip 提示\n当 observeImage 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  observeImage: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  observeImage: {\n    debounceTime: 100 // ms\n  }\n})\n```\n:::\n\n### debounceTime\n\n  - **类型：** `number`\n  - **默认值：** `100`\n\n    探测到图片加载成功或者失败后，过 debounceTime 毫秒后才会调用 refresh 方法，重新计算可滚动的高度或者宽度，如果在 debounceTime 毫秒内有多张图片加载成功或者失败，**只会调用一次 refresh**。\n\n    :::tip 提示\n    当 debounceTime 为 0 的时候，会立马调用 **refresh** 方法，而不是使用 **setTimeout**。\n    :::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/pulldown.md",
    "content": "# pulldown\n\n## 介绍\n\npulldown 插件为 BetterScroll 扩展下拉刷新的能力。\n\n## 安装\n\n```bash\nnpm install @better-scroll/pull-down --save\n\n// or\n\nyarn add @better-scroll/pull-down\n```\n\n## 使用\n\n首先引入 pulldown 插件，并通过静态方法 `BScroll.use()` 初始化插件\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n```\n\n然后，实例化 BetterScroll 时需要传入[ pulldown 配置项](./pulldown.html#pulldownrefresh-选项对象)。\n\n```js\nnew BScroll('.bs-wrapper', {\n  pullDownRefresh: true\n})\n```\n\n## 示例\n\n- **基础使用**\n\n<demo qrcode-url=\"pulldown/default\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pulldown/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pulldown/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pulldown/default.vue?style\n  </template>\n  <pulldown-default slot=\"demo\"></pulldown-default>\n</demo>\n\n- **仿新浪微博 <Badge text='2.4.0' />**\n\n<demo qrcode-url=\"pulldown/sina\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pulldown/sina-weibo.vue?style\n  </template>\n  <pulldown-sina-weibo slot=\"demo\"></pulldown-sina-weibo>\n</demo>\n\n为了拉齐客户端的交互效果，在 v2.4.0 版本，pulldown 内部进行了功能的改造并且兼容以前的版本，在一次 pulldown 的操作过程中，内部存在三个流转的状态，并且状态是不可逆的。分别如下：\n\n1. **default**\n\n  初始状态。\n\n2. **moving**\n\n  移动状态，这个状态代表用户的手指正在操控 BetterScroll，手指未移开，在这种状态下，BetterScroll 会派发两个事件。\n\n  - **enterThreshold**\n\n    当 BetterScroll 滚动到 pulldown 的 threshold 阈值区域**之内**的时候派发，在这个事件内部，你可以做文案初始化的逻辑，比如提示用户“下拉刷新”\n  \n  - **leaveThreshold**\n\n    当 BetterScroll 滚动到 pulldown 的 threshold 阈值区域**之外**的时候派发。你可以提示用户“手指释放刷新”\n\n3. **fetching**\n\n  手指移开的瞬间，触发 pullingDown 事件，执行获取数据的逻辑\n\n状态的变换只可能是 `default -> moving -> fetching` 或者是 `default -> moving`，后者代表用户的手指在释放的瞬间，没有满足触发 pullingDown 事件的条件。\n\n\n\n## pullDownRefresh 选项对象\n\n### threshold\n\n  - **类型：** `number`\n  - **默认值：** `90`\n\n    配置顶部下拉的距离来决定刷新时机。\n\n### stop\n\n  - **类型：** `number`\n  - **默认值：** `40`\n\n    回弹悬停的距离。BetterScroll 在派发 `pullingDown` 钩子之后，会立马执行回弹悬停动画。\n\n:::tip 提示\n当 pullDownRefresh 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  pullDownRefresh: {\n    threshold: 90,\n    stop: 40\n  }\n})\n```\n:::\n\n## 实例方法\n\n:::tip 提示\n以下方法皆已代理至 BetterScroll 实例，例如：\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullDown from '@better-scroll/pull-down'\n\nBScroll.use(PullDown)\n\nconst bs = new BScroll('.bs-wrapper', {\n  pullDownRefresh: true\n})\n\nbs.finishPullDown()\nbs.openPullDown({})\nbs.autoPullDownRefresh()\n```\n:::\n\n### `finishPullDown()`\n\n  - **介绍**：结束下拉刷新行为。\n\n::: warning 注意\n每次触发 `pullingDown` 钩子后，你应该**主动调用** `finishPullDown()` 告诉 BetterScroll 准备好下一次的 pullingDown 钩子。\n:::\n\n### `openPullDown(config: PullDownRefreshOptions = {})`\n\n  - **介绍**：动态开启下拉刷新功能。\n  - **参数**：\n    - `{ PullDownRefreshOptions } config`：修改 pulldown 插件的选项对象\n    - `PullDownRefreshOptions`：类型如下\n    ```typescript\n    export type PullDownRefreshOptions = Partial<PullDownRefreshConfig> | true\n\n    export interface PullDownRefreshConfig {\n      threshold: number\n      stop: number\n    }\n    ```\n  - **返回值**：无\n\n::: warning 注意\nopenPullDown 方法应该配合 closePullDown 一起使用，因为在 pulldown 插件的生成过程当中，已经**自动监测了下拉刷新的动作**。\n:::\n\n### `closePullDown()`\n\n  - **介绍**：动态关闭下拉刷新功能。\n\n### `autoPullDownRefresh()`\n\n  - **介绍**：自动执行下拉刷新。\n\n## 事件\n\n### `pullingDown`\n\n- **参数**：无\n- **触发时机**：当顶部下拉的距离大于 `threshold` 值时，触发一次 `pullingDown` 钩子。\n\n::: danger 危险\n监测到下拉刷新的动作之后，`pullingDown` 钩子的消费机会只有一次，因此你需要调用 `finishPullDown()` 来告诉 BetterScroll 来提供下一次 `pullingDown` 钩子的消费机会。\n:::\n\n### `enterThreshold` <Badge text='2.4.0' />\n\n- **参数**：无\n- **触发时机**：当 pulldown 正处于 moving 状态，并且**进入** threshold 区域的瞬间。\n\n### `leaveThreshold` <Badge text='2.4.0' />\n\n- **参数**：无\n- **触发时机**：当 pulldown 正处于 moving 状态，并且**离开** threshold 区域的瞬间。"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/pullup.md",
    "content": "# pullup\n\n## 介绍\n\npullup 插件为 BetterScroll 扩展上拉加载的能力。\n\n## 安装\n\n```bash\nnpm install @better-scroll/pull-up --save\n\n// or\n\nyarn add @better-scroll/pull-up\n```\n\n## 使用\n\n通过静态方法 `BScroll.use()` 注册插件\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Pullup from '@better-scroll/pull-up'\n\n  BScroll.use(Pullup)\n```\n\n然后，实例化 BetterScroll 时需要传入[ pullup 配置项](./pullup.html#pullupload-选项对象)。\n\n```js\n  new BScroll('.bs-wrapper', {\n    pullUpLoad: true\n  })\n```\n## 示例\n\n<demo qrcode-url=\"pullup/\" :render-code=\"true\">\n  <template slot=\"code-template\">\n    <<< @/examples/vue/components/pullup/default.vue?template\n  </template>\n  <template slot=\"code-script\">\n    <<< @/examples/vue/components/pullup/default.vue?script\n  </template>\n  <template slot=\"code-style\">\n    <<< @/examples/vue/components/pullup/default.vue?style\n  </template>\n  <pullup-default slot=\"demo\"></pullup-default>\n</demo>\n\n## pullUpLoad 选项对象\n\n### threshold\n\n  - **类型：** `number`\n  - **默认值：** `0`\n\n    触发上拉事件的阈值。\n\n\n:::tip 提示\n当 pullUpLoad 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  pullUpLoad: {\n    threshold: 0\n  }\n})\n```\n:::\n\n## 实例方法\n\n:::tip 提示\n以下方法皆已代理至 BetterScroll 实例，例如：\n\n```js\nimport BScroll from '@better-scroll/core'\nimport PullUp from '@better-scroll/pull-up'\n\nBScroll.use(PullUp)\n\nconst bs = new BScroll('.bs-wrapper', {\n  pullUpLoad: true\n})\n\nbs.finishPullUp()\nbs.openPullUp({})\nbs.closePullUp()\n```\n:::\n\n### `finishPullUp()`\n\n  - **介绍**：结束上拉加载行为。\n\n  ::: warning 注意\n  每次触发 `pullingUp` 钩子后，你应该**主动调用** `finishPullUp()` 告诉 BetterScroll 准备好下一次的 pullingUp 钩子。\n  :::\n\n### `openPullUp(config: PullUpLoadOptions = {})`\n\n  - **介绍**：动态开启上拉功能。\n  - **参数**：\n    - `{ PullUpLoadOptions } config`：修改 pullup 插件的选项对象\n    - `PullUpLoadOptions`：类型如下\n    ```typescript\n    export type PullUpLoadOptions = Partial<PullUpLoadConfig> | true\n\n    export interface PullUpLoadConfig {\n      threshold: number\n    }\n    ```\n\n  ::: warning 注意\n  openPullUp 方法应该配合 closePullUp 一起使用，因为在 pullup 插件的生成过程当中，已经**自动监测了上拉加载的动作**。\n  :::\n\n### `closePullUp()`\n\n  - **介绍**：关闭上拉加载功能。\n\n### `autoPullUpLoad()`\n\n  - **介绍**：自动执行上拉加载。\n\n## 事件\n\n### `pullingUp`\n\n  - **参数**：无\n  - **触发时机**：当距离滚动到底部小于 `threshold` 值时，触发一次 `pullingUp` 事件。\n\n  > 当 threshold 为正数，代表距离滚动边界 threshold 像素的时候触发 `pullingUp`，反之，代表越过滚动边界才会触发事件\n\n  ::: danger 警告\n  监测到上拉刷新的动作之后，`pullingUp` 事件的消费机会只有一次，因此你需要调用 `finishPullUp()` 来告诉 BetterScroll 来提供下一次 `pullingUp` 事件的消费机会。\n  :::\n"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/scroll-bar.md",
    "content": "# scrollbar\n\n## 介绍\n\nscrollbar 插件为 BetterScroll 提供了样式美观的滚动条。\n\n:::tip 提示\n从 v2.2.0 开始，用户可以提供自定义的滚动条。\n:::\n\n## 安装\n\n```bash\nnpm install @better-scroll/scroll-bar --save\n\n// or\n\nyarn add @better-scroll/scroll-bar\n```\n\n## 使用\n\n首先引入 scrollbar 插件，并通过静态方法 `BScroll.use()` 注册插件\n\n```js\n  import BScroll from '@better-scroll/core'\n  import ScrollBar from '@better-scroll/scroll-bar'\n\n  BScroll.use(ScrollBar)\n```\n\n接着在 `options` 传入正确的配置。\n\n```js\n  new BScroll('.bs-wrapper', {\n    scrollY: true,\n    scrollbar: true\n  })\n```\n## 示例\n\n  - **竖向滚动条**\n\n    <demo qrcode-url=\"scrollbar/vertical\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/vertical.vue?style\n      </template>\n      <scrollbar-vertical slot=\"demo\"></scrollbar-vertical>\n    </demo>\n\n  - **横向滚动条**\n\n    <demo qrcode-url=\"scrollbar/horizontal\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/horizontal.vue?style\n      </template>\n      <scrollbar-horizontal slot=\"demo\"></scrollbar-horizontal>\n    </demo>\n\n  - **用户定制化滚动条**\n\n    <demo qrcode-url=\"scrollbar/custom\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/custom.vue?style\n      </template>\n      <scrollbar-custom slot=\"demo\"></scrollbar-custom>\n    </demo>\n\n  - **搭配鼠标滚轮**\n\n    <demo qrcode-url=\"scrollbar/mousewheel\" :render-code=\"true\">\n      <template slot=\"code-template\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?template\n      </template>\n      <template slot=\"code-script\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?script\n      </template>\n      <template slot=\"code-style\">\n        <<< @/examples/vue/components/scrollbar/mousewheel.vue?style\n      </template>\n      <scrollbar-mousewheel slot=\"demo\"></scrollbar-mousewheel>\n    </demo>\n\n## scrollbar 选项对象\n\n### fade\n\n  - **类型**：`boolean`\n  - **默认值**：`true`\n\n  当滚动停止的时候，滚动条渐隐。\n\n### interactive\n\n  - **类型**：`boolean`\n  - **默认值**：`false`\n\n  滚动条是否可以交互。\n\n### customElements <Badge text=\"2.2.0\" />\n\n  - **类型**：`HTMLElement[]`\n  - **默认值**：`[]`\n\n  用户提供自定义的滚动条。\n\n  ```js\n  // 横向滚动条\n  const horizontalEl = document.getElementById('用户自定义的滚动条')\n  new BScroll('.bs-wrapper', {\n    scrollY: true,\n    scrollbar: {\n      customElements: [horizontalEl]\n    }\n  })\n  // 竖向滚动条\n  const verticalEl = document.getElementById('用户自定义的滚动条')\n  new BScroll('.bs-wrapper', {\n    scrollY: false,\n    scrollX: true,\n    scrollbar: {\n      customElements: [verticalEl]\n    }\n  })\n  // 双向滚动条\n  const horizontalEl = document.getElementById('用户自定义的滚动条')\n  const verticalEl = document.getElementById('用户自定义的滚动条')\n  new BScroll('.bs-wrapper', {\n    freeScroll: true,\n    scrollbar: {\n      // 当滚动条是 2 个的时候，数组第一个元素是横向滚动条\n      customElements: [horizontalEl, verticalEl]\n    }\n  })\n  ```\n\n### minSize <Badge text=\"2.2.0\" />\n\n  - **类型**：`number`\n  - **默认值**：`8`\n\n  滚动条的最小尺寸，当用户提供了自定义的滚动条，该配置无效。\n\n### scrollbarTrackClickable <Badge text=\"2.2.0\" />\n\n  - **类型**：`boolean`\n  - **默认值**：`false`\n\n  滚动条轨道是否允许点击。\n\n  **注意**：当开启该配置的时候，请保证 BetterScroll Options 的 `click` 为 true，否则无法触发点击事件。[详细原因在这](../FAQ/diagnosis.html#【问题四】为什么-betterscroll-content-内部的所有的-click-事件的侦听器都不触发？)\n\n  ```js\n  new BScroll('.bs-wrapper', {\n    scrollY: true,\n    click: true // 必不可少\n    scrollbar: {\n      scrollbarTrackClickable: true\n    }\n  })\n  ```\n\n### scrollbarTrackOffsetType <Badge text=\"2.2.0\" />\n\n  - **类型**：`string`\n  - **默认值**：`'step'`\n\n  滚动条轨道被点击之后，滚动距离的计算方式，默认与浏览器的表现形式一样，可以配置为 `'clickedPoint'`，代表滚动条滚动至点击的位置。\n\n### fadeInTime <Badge text=\"2.4.0\" />\n\n  - **类型**：`number`\n  - **默认值**：`250`\n\n  滚动条渐显的动画时长。\n\n### fadeOutTime <Badge text=\"2.4.0\" />\n\n  - **类型**：`number`\n  - **默认值**：`500`\n\n  滚动条渐隐的动画时长。\n\n\n:::tip 提示\n当 scrollbar 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  scrollbar: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  scrollbar: {\n    fade: true,\n    interactive: false,\n    // 以下配置项 v2.2.0 才支持\n    customElements: [],\n    minSize: 8,\n    scrollbarTrackClickable: false,\n    scrollbarTrackOffsetType: 'step',\n    scrollbarTrackOffsetTime: 300,\n    // 以下配置项 v2.4.0 才支持\n    fadeInTime: 250,\n    fadeOutTime: 500\n  }\n})\n```\n:::"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/slide.md",
    "content": "# slide\n\n## 介绍\n\nslide 为 BetterScroll 扩展了轮播焦点图的能力。\n\n## 安装\n\n```bash\nnpm install @better-scroll/slide --save\n\n// or\n\nyarn add @better-scroll/slide\n```\n\n## 使用\n\n你需要首先引入 slide 插件，并通过静态方法 `BScroll.use()` 使用\n\n```js\n  import BScroll from '@better-scroll/core'\n  import Slide from '@better-scroll/slide'\n\n  BScroll.use(Slide)\n```\n\n上面步骤完成后，BScroll 的 `options` 中传入 slide 相关的配置。\n\n```js\n  new BScroll('.bs-wrapper', {\n    scrollX: true,\n    scrollY: false,\n    slide: {\n      threshold: 100\n    },\n    momentum: false,\n    bounce: false,\n    stopPropagation: true\n  })\n```\n\n以下是 slide 插件专属以及[ BetterScroll 的配置](../guide/base-scroll-options.html)：\n\n- **slide<插件专属>**\n\n  开启 slide 功能。若没有该项，则插件不会生效。该配置同时也是用来设置 slide 特性的相关配置，具体请参考[ slide 选项对象](./slide.html#slide-选项对象)。\n\n- **scrollX**\n\n  当值为 true 时，设置 slide 的方向为**横向**。\n\n- **scrollY**\n\n  当值为 true 时，设置 slide 的方向为**纵向**。 **注意: scrollX 和 scrollY 不能同时设置为 true**\n\n- **momentum**\n\n  当使用 slide 时，这个值需要设置为 false，用来避免惯性动画带来的快速滚动时的闪烁的问题和快速滑动时一次滚动多页的问题。\n\n- **bounce**\n\n  bounce 值需要设置为 false，否则会在循环衔接的时候出现闪烁。\n\n- **probeType**\n\n  如果你想通过监听 `slideWillChange` 事件，在用户拖动 slide 时，实时获取到 slide 的 PageIndex 的改变，需要设置 probeType 值为 2 或者 3。\n\n## 关于 slide 的术语\n\n一般情况下，BetterScroll 的 slide 的布局如下：\n\n```html\n<div class=\"slide-wrapper\">\n  <div class=\"slide-content\">\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n    <div class=\"slide-page\"><div>\n  <div/>\n<div/>\n```\n\n- **slide-wrapper**\n\n  slide 容器。\n\n- **slide-content**\n\n  slide 滚动元素。\n\n- **slide-page**\n\n  slide 由多个 Page 组成。\n\n  ::: tip\n  在 loop 的场景下，slide-content 前后会多插入两个 Page，以便实现无缝衔接滚动的视觉效果。\n  :::\n\n  :::danger 危险\n  slide-content 必须至少有一个 slide-page，如果只有一个 page，loop 的配置无效。\n  :::\n\n## 示例\n\n- **横向轮播**\n\n  <demo qrcode-url=\"slide/banner\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/banner.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/banner.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/banner.vue?style\n    </template>\n    <slide-banner slot=\"demo\"></slide-banner>\n  </demo>\n\n- **全屏轮播**\n\n  <demo qrcode-url=\"slide/fullpage\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/fullpage.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/fullpage.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/fullpage.vue?style\n    </template>\n    <slide-fullpage slot=\"demo\"></slide-fullpage>\n  </demo>\n\n- **纵向轮播**\n\n  <demo qrcode-url=\"slide/vertical\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/vertical.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/vertical.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/vertical.vue?style\n    </template>\n    <slide-vertical slot=\"demo\"></slide-vertical>\n  </demo>\n\n- **动态卡片轮播 <Badge text='2.1.0' />**\n\n  <demo qrcode-url=\"slide/dynamic\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/dynamic.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/dynamic.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/dynamic.vue?style\n    </template>\n    <slide-dynamic slot=\"demo\"></slide-dynamic>\n  </demo>\n\n- **初始化索引轮播 <Badge text='2.3.0' />**\n\n  <demo qrcode-url=\"slide/specified\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/slide/specified-index.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/slide/specified-index.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/slide/specified-index.vue?style\n    </template>\n    <slide-specified-index slot=\"demo\"></slide-specified-index>\n  </demo>\n\n  ::: tip\n  注意：当设置 `useTransition = true`时，可能在 iphone 某些系统上出现闪烁。你需要像上面 demo 中的代码一样，每个 `slide-page` 额外增加下面两个样式：\n\n  ```css\n    transform: translate3d(0,0,0)\n    backface-visibility: hidden\n  ```\n  :::\n\n## slide 选项对象\n\n:::tip 提示\n当 slide 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  slide: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  slide: {\n    loop: true,\n    threshold: 0.1,\n    speed: 400,\n    easing: ease.bounce,\n    listenFlick: true,\n    autoplay: true,\n    interval: 3000\n  }\n})\n```\n:::\n\n### loop\n\n  - **类型**：`boolean`\n  - **默认值**：`true`\n\n  是否可以循环。但是当只有一个元素的时候，该设置不生效。\n\n### autoplay\n\n  - **类型**：`boolean`\n  - **默认值**：`true`\n\n  是否开启自动播放。\n\n### interval\n\n  - **类型**：`number`\n  - **默认值**：`3000`\n\n  距离下一次播放的间隔。\n\n### speed\n\n  - **类型**：`number`\n  - **默认值**：`400`\n\n  切换 Page 动画的默认时长。\n\n### easing\n\n  - **类型**：`EaseItem`\n    - `{ string } style`：用来设置过度动画的 `transition-timing-function` 值。\n    - `{ Function } fn`：当设置 `useTransition:false` 时，由 `easing.fn` 来确定动画曲线。\n  - **默认值**：\n  ```js\n  {\n    style: 'cubic-bezier(0.165, 0.84, 0.44, 1)',\n    fn: function(t: number) {\n      return 1 - --t * t * t * t\n    }\n  }\n  ```\n\n  滚动的缓动效果配置。\n\n### listenFlick\n\n  - **类型**：`boolean`\n  - **默认值**：`true`\n\n  当快速轻抚过 slide 区域时，会触发切换上一页/下一页。设置 listenFlick 为 false，可关闭该效果。\n\n### threshold\n\n  - **类型**：`number`\n  - **默认值**：`0.1`\n\n  切换下一个或上一个 Page 的阈值。\n\n  :::tip\n  当滚动距离小于该阈值时，不会触发切换到下一个或上一个。\n\n  可以设置为小数，如 0.1，或者整数，如 100。当该值为小数时，threshold 被当成一个百分比，最终的阈值为 `slideWrapperWidth * threshold` 或者 `slideWrapperHeight * threshold`。当该值为整数时，则阈值就是 threshold。\n  :::\n\n### startPageXIndex <Badge text='2.3.0' />\n\n  - **类型**：`number`\n  - **默认值**：`0`\n\n  实例化 slide 的时候，滚动到横向对应索引的 page。\n\n### startPageYIndex <Badge text='2.3.0' />\n\n  - **类型**：`number`\n  - **默认值**：`0`\n\n  实例化 slide 的时候，滚动到竖向对应索引的 page。\n\n## 实例方法\n\n:::tip 提示\n以下方法皆已代理至 BetterScroll 实例，例如：\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Slide from '@better-scroll/slide'\n\nBScroll.use(Slide)\n\nconst bs = new BScroll('.bs-wrapper', {\n  slide: true\n})\n\nbs.next()\nbs.prev()\nbs.getCurrentPage()\n```\n:::\n\n### next([time], [easing])\n\n  - **参数**：\n    - `{ number } time<可选>`：动画时长，默认是 `options.speed`\n    - `{ EaseItem } easing<可选>`：缓动效果配置，参考 [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts)，默认是 `bounce` 效果\n    ```typescript\n    interface EaseItem {\n      style: string\n      fn(t: number): number\n    }\n    ```\n\n  滚动到下一张。\n\n### prev([time], [easing])\n\n  - **参数**：\n    - `{ number } time<可选>`：动画时长，默认是 `options.speed`\n    - `{ EaseItem } easing<可选>`：缓动效果配置，参考 [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts)，默认是 `bounce` 效果\n\n  滚动到上一张。\n\n### goToPage(pageX, pageY, [time], [easing])\n\n  - **参数**：\n    - `{ number } pageX`：横向滚动到对应索引的 Page，下标从 0 开始\n    - `{ number } pageY`：纵向滚动到对应索引的 Page，下标从 0 开始\n    - `{ number } time<可选>`：动画时长，默认是 `options.speed`\n    - `{ EaseItem } easing<可选>`：缓动效果配置，参考 [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts)，默认是 `bounce` 效果\n\n  滚动到指定的 Page 位置。\n\n### getCurrentPage()\n\n  - **返回值**： `page`\n  ```typescript\n  type Page = {\n    x: number,\n    y: number,\n    pageX: number, // 横向对应 Page 的索引，下标从 0 开始\n    pageY: number  // 纵向对应 Page 的索引，下标从 0 开始\n  }\n  const page:Page = BScroll.getCurrentPage()\n  ```\n\n  获取当前页面的信息。\n\n### startPlay()\n\n  如果开启了 loop 的配置，手动开启循环播放。\n\n### pausePlay()\n\n  如果开启了 loop 的配置，手动关闭循环播放。\n\n## 事件\n\n### slideWillChange\n\n  - **参数**：page 对象\n    - `{ number } x`：即将展示页面的 x 坐标值\n    - `{ number } y`：即将展示页面的 y 坐标值\n    - `{ number } pageX`：即将展示的横向页面的索引值，下标从 0 开始\n    - `{ number } pageY`：即将展示的纵向页面的索引值，下标从 0 开始\n\n  - **触发时机**：slide 的 currentPage 值将要改变时\n\n  - **用法**：\n\n  在 banner 展示中，常常伴随着一个 dot 图例，来指示当前 banner 是第几页，例如前面“横向轮播图”的示例。当用户拖动 banner 出现下一张时，我们希望下面的 dot 图例会同步变换。如下图\n\n  <img data-zoomable :src=\"$withBase('/assets/images/slide-pageindex.png')\" style=\"maxHeight: 200px\" alt=\"banner示例图\">\n\n  通过监听 `slideWillChange` 事件，可以实现该效果。代码如下：\n\n  ```js\n    let currentPageIndex // 控制当前页面\n    const slide = new BScroll(this.$refs.slide, {\n      scrollX: true,\n      scrollY: false,\n      slide: {\n        threshold: 100\n      },\n      useTransition: true,\n      momentum: false,\n      bounce: false,\n      stopPropagation: true,\n      probeType: 2\n    })\n    slide.on('slideWillChange', (page) => {\n      currentPageIndex = page.pageX\n    })\n  ```\n\n### slidePageChanged <Badge text='2.1.0' />\n\n  - **参数**：page 对象\n    - `{ number } x`：当前页面的 x 坐标值\n    - `{ number } y`：当前页面的 y 坐标值\n    - `{ number } pageX`：当前横向页面的索引值，下标从 0 开始\n    - `{ number } pageY`：当前纵向页面的索引值，下标从 0 开始\n\n  - **触发时机**：当 slide 切换 page 之后触发\n\n  ```js\n    const slide = new BScroll(this.$refs.slide, {\n      scrollX: true,\n      scrollY: false,\n      slide: true,\n      momentum: false,\n      bounce: false\n    })\n    slide.on('slidePageChanged', (page) => {\n      currentPageIndex = page.pageX\n    })\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/wheel.md",
    "content": "# wheel\n\n## 介绍\n\nwheel 插件，是实现类似 IOS Picker 组件的基石。\n\n## 安装\n\n```bash\nnpm install @better-scroll/wheel --save\n\n// or\n\nyarn add @better-scroll/wheel\n```\n\n## 使用\n\n首先引入 wheel 插件，并通过静态方法 `BScroll.use()` 注册插件。\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n```\n\n接着在 `options` 传入正确的配置\n\n```js\n  let bs = new BScroll('.bs-wrapper', {\n    wheel: true // wheel options 为 true\n  })\n```\n\n:::tip\nwheel options 是 true 或者对象，否则插件功能失效，具体请参考[ wheel options](./wheel.html#wheel-选项对象)。\n:::\n\n::: danger 危险\nBetterScroll 结合 wheel 插件只是实现 Picker 效果的 JS 逻辑部分，还有 DOM 模版是需要用户去实现，所幸，对于大多数的 Picker 场景，我们给出了相对应的示例。\n:::\n\n- **基本使用**\n\n  <demo qrcode-url=\"picker/one-column\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/one-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/one-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/one-column.vue?style\n    </template>\n    <picker-one-column slot=\"demo\"></picker-one-column>\n  </demo>\n\n  单列 Picker 是一个比较常见的效果。你可以通过 `selectedIndex` 来配置初始化时选中对应索引的 item，`wheelDisabledItemClass` 配置想要禁用的 item 项来模拟 Web Select 标签 disable 的效果。\n\n- **多项选择器**\n\n  <demo qrcode-url=\"picker/double-column\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/double-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/double-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/double-column.vue?style\n    </template>\n    <picker-double-column slot=\"demo\"></picker-double-column>\n  </demo>\n\n  示例是一个两列的选择器，JS 逻辑部分与单列选择器没有多大的区别，你会发现这个两列选择器之间是没有任何关联，因为它们是两个不同的 BetterScroll 实例。如果你想要实现省市联动的效果，那么得加上一部分代码，让这两个 BetterScroll 实例能够关联起来。请看下一个例子：\n\n- **城市联动选择器**\n\n  <demo qrcode-url=\"picker/linkage-column\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/picker/linkage-column.vue?style\n    </template>\n    <picker-linkage-column slot=\"demo\"></picker-linkage-column>\n  </demo>\n\n  城市联动 Picker 的效果，必须通过 JS 部分逻辑将不同 BetterScroll 的实例联系起来，不管是省市，还是省市区的联动，亦是如此。\n\n## wheel 选项对象\n\n:::tip 提示\n当 wheel 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  wheel: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  wheel: {\n    wheelWrapperClass: 'wheel-scroll',\n    wheelItemClass: 'wheel-item',\n    rotate: 25,\n    adjustTime: 400,\n    selectedIndex: 0,\n    wheelDisabledItemClass: 'wheel-disabled-item'\n  }\n})\n```\n:::\n\n### selectedIndex\n\n  - **类型**：`number`\n  - **默认值**：`0`\n\n  实例化 Wheel，默认选中第 selectedIndex 项，索引从 0 开始。\n\n### rotate\n\n  - **类型**：`number`\n  - **默认值**：`25`\n\n  当滚动 wheel 时，wheel item 的弯曲程度。\n\n### adjustTime\n\n  - **类型**：`number`\n  - **默认值**：`400`(ms)\n\n  当点击某一项的时候，滚动过去的动画时长。\n\n### wheelWrapperClass\n\n  - **类型**：`string`\n  - **默认值**：`wheel-scroll`\n\n  滚动元素的 className，这里的「滚动元素」 指的就是 BetterScroll 的 content 元素。\n\n### wheelItemClass\n\n  - **类型**：`string`\n  - **默认值**：`wheel-item`\n\n  滚动元素的子元素的样式。\n\n### wheelDisabledItemClass\n\n  - **类型**：`string`\n  - **默认值**：`wheel-disabled-item`\n\n  滚动元素中想要禁用的子元素，类似于 `select` 元素中禁用的 `option` 效果。wheel 插件的内部根据 `wheelDisabledItemClass` 配置来判断是否将该项指定为 disabled 状态。\n\n## 实例方法\n\n:::tip 提示\n以下方法皆已代理至 BetterScroll 实例，例如：\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\n\nBScroll.use(Wheel)\n\nconst bs = new BScroll('.bs-wrapper', {\n  wheel: true\n})\n\nbs.getSelectedIndex()\nbs.wheelTo(1, 300)\n```\n:::\n\n### getSelectedIndex()\n\n  - **返回值**：当前选中项的 index，下标从 0 开始\n\n  获取当前选中项的索引。\n\n### wheelTo(index = 0, time = 0, [ease])\n\n  - **参数**：\n    - `{ number } index`：选项索引\n    - `{ number } time`：动画时长\n    - `{ number } ease<可选>`：动画时长。缓动效果配置，参考 [ease.ts](https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/ease.ts)，默认是 `bounce` 效果\n\n  滚动至对应索引的列表项。\n\n### stop() <Badge text='2.1.0' />\n\n  强制让滚动的 BetterScroll 停止下来，并且吸附至当前距离最近的 wheel-item 的位置。\n\n### restorePosition() <Badge text='2.1.0' />\n\n  强制让滚动的 BetterScroll 停止下来，并且恢复至滚动开始前的位置。\n\n::: tip 提示\n以上两个方法只对处于**滚动中的 BetterScroll** 有效，并且 `restorePosition` 是与原生的 iOS picker 组件的效果一模一样，用户可以根据自己的需求选择对应的方法。\n:::\n\n## 事件\n\n### wheelIndexChanged <Badge text='2.1.0' />\n\n  - **参数**：当前选中的 wheel-item 的索引。\n  - **触发时机**：当列表项发生改变的时候。\n\n  ```js\n  import BScroll from '@better-scroll/core'\n  import Wheel from '@better-scroll/wheel'\n\n  BScroll.use(Wheel)\n\n  const bs = new BScroll('.bs-wrapper', {\n    wheel: true\n  })\n\n  bs.on('wheelIndexChanged', (index) => {\n    console.log(index)\n  })\n  ```"
  },
  {
    "path": "packages/vuepress-docs/docs/zh-CN/plugins/zoom.md",
    "content": "# zoom\n\n## 介绍\n\nzoom 插件为 BetterScroll 提供缩放功能。\n\n## 安装\n\n```bash\nnpm install @better-scroll/zoom --save\n\n// or\n\nyarn add @better-scroll/zoom\n```\n\n## 使用\n\n为了开启缩放功能，你需要首先引入 zoom 插件，并通过静态方法 `BScroll.use()` 注册插件\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\n\nBScroll.use(Zoom)\n```\n\n上面步骤完成后，在 BetterScroll 的基础能力上扩展了缩放的功能，但是想要缩放真正生效，还需要在 `options` 中传入正确的配置：\n\n```js\nnew BScroll('.bs-wrapper', {\n  freeScroll: true,\n  scrollX: true,\n  scrollY: true,\n  zoom: {\n    start: 1,\n    min: 0.5,\n    max: 2\n  }\n})\n```\n\n以下是 zoom 插件专属以及[ BetterScroll 的配置](../guide/base-scroll-options.html)：\n\n- **zoom<插件专属>**\n\n  开启 zoom 功能。若没有该项，则插件不会生效。该配置同时也是用来设置 zoom 特性的相关配置，具体请参考[ zoom 选项对象](./zoom.html#zoom-选项对象)。\n\n- **freeScroll**\n\n  如果希望当放大之后，当前区域在 x 和 y 轴方向都可以滚动时，必须设置为 `true`。同时需要设置 scrollX 和 scrollY 均为 true。\n\n- **scrollX**\n\n  如果希望当放大之后，当前区域在 x 轴方向可以滚动时，必须设置为 `true`。\n\n- **scrollY**\n\n  如果希望当放大之后，当前区域在 y 轴方向可以滚动时，必须设置为 `true`。\n\n## 示例\n\n  :::warning\n  zoom 暂不支持在 pc 端的交互操作，下方 demo 请扫码体验。\n  :::\n\n  <demo qrcode-url=\"zoom/default\" :render-code=\"true\">\n    <template slot=\"code-template\">\n      <<< @/examples/vue/components/zoom/default.vue?template\n    </template>\n    <template slot=\"code-script\">\n      <<< @/examples/vue/components/zoom/default.vue?script\n    </template>\n    <template slot=\"code-style\">\n      <<< @/examples/vue/components/zoom/default.vue?style\n    </template>\n    <zoom-default slot=\"demo\"></zoom-default>\n  </demo>\n\n## zoom 选项对象\n\n### start\n  - **类型**：`number`\n  - **默认值**：`1`\n\n    初始缩放比例。\n\n### min\n  - **类型**：`number`\n  - **默认值**：`1`\n\n    最小缩放比例。\n\n### max\n  - **类型**：`number`\n  - **默认值**：`4`\n\n    最大缩放比例。\n\n### initialOrigin\n  - **类型**：`[OriginX, OriginY]`\n    - **OriginX**：`number | 'left' | 'right' | 'center'`\n    - **OriginY**：`number | 'top' | 'bottom' | 'center'`\n  - **默认值**：`[0, 0]`\n\n    初始化 zoom 插件的缩放原点，当 `start` 不为 `1` 的时候有效，缩放原点都是以`缩放元素`为坐标系。\n\n  - **示例**\n\n  ```js\n    new BScroll('.bs-wrapper', {\n      // ... 其他配置项\n      zoom: {\n        initialOrigin: [50, 50], // 基于缩放元素的左顶点上下偏移量都是 50 px\n        initialOrigin: [0, 0], // 基于缩放元素的左顶点\n        initialOrigin: ['left', 'top'], // 与上面效果相同\n        initialOrigin: ['center', 'center'], // 基于缩放元素的中心\n        initialOrigin: ['right', 'top'], // 基于缩放元素的右顶点\n      }\n    })\n  ```\n\n  往往你初始化 zoom 的时候只专注于以端点或者中心进行缩放，可以参考以上示例。\n\n### minimalZoomDistance\n  - **类型**：`number`\n  - **默认值**：`5`\n\n    当你双指进行缩放操作的时候，只有当缩放的距离超过 `minimalZoomDistance`，zoom 才生效。\n\n### bounceTime\n  - **类型**：`number`\n  - **默认值**：`800`(毫秒)\n\n    双指不断进行缩放操作并且 scale 超过 `max` 阈值的时候，当双指离开的时候，内部会「回弹」至 `max` 的形态，而 `bounceTime` 就是这次「回弹」行为的动画时长。\n\n:::tip 提示\n当 zoom 配置为 true 的时候，插件内部使用的是默认的插件选项对象。\n\n```js\nconst bs = new BScroll('.wrapper', {\n  zoom: true\n})\n\n// 相当于\n\nconst bs = new BScroll('.wrapper', {\n  zoom: {\n    start: 1,\n    min: 1,\n    max: 4,\n    initialOrigin: [0, 0],\n    minimalZoomDistance: 5,\n    bounceTime: 800, // ms\n  }\n})\n```\n:::\n\n## 实例方法\n\n### zoomTo(scale, x, y, [bounceTime])\n  - **参数**\n    - `{number} scale`： 缩放比例\n    - `{OriginX} x`： 缩放原点的 x 坐标，相当于**缩放元素**的左顶点\n      - `OriginX：'number | 'left' | 'right' | 'center'`\n    - `{OriginY} y`： 缩放原点的 y 坐标，相当于**缩放元素**的左顶点\n      - `OriginY：'number | 'top' | 'bottom' | 'center'`\n    - `{number} [bounceTime]<可选>：一次缩放行为的动画时长`\n\n    以 `[x, y]` 坐标作为原点对元素进行缩放。x 与 y 不仅可以是数字，也可以是对应的字符串，因为一般的场景都是基于端点或者中心进行缩放。\n\n  - **示例**\n\n  ```js\n  const bs = new BScroll('.bs-wrapper', {\n    freeScroll: true,\n    scrollX: true,\n    scrollY: true,\n    zoom: {\n      start: 1,\n      min: 0.5,\n      max: 2\n    }\n  })\n\n  bs.zoomTo(1.8, 'left', 'bottom') // 基于缩放元素的左底点缩放至 1.8 倍\n  bs.zoomTo(1.8, 'left', 'bottom', 1000) // 基于缩放元素的左底点缩放，动画时长为 1s\n  bs.zoomTo(1.8, 100, 100) // 基于缩放元素左顶点的上下偏移量 100 为原点进行缩放\n  bs.zoomTo(2, 'center', 'center') // 基于缩放元素的中心进行缩放\n  ```\n\n## 事件\n\n### beforeZoomStart\n\n  - **参数**：无\n  - **触发时机**：双指接触缩放元素时，不包括直接调用 zoomTo 方法\n\n### zoomStart\n\n  - **参数**：无\n  - **触发时机**：双指缩放距离超过最小阈值 `minimalZoomDistance`，缩放即将开始。不包括直接调用 zoomTo 方法\n\n### zooming\n\n  - **参数**：`{ scale }`\n  - **类型**：`{ scale: number }`\n  - **触发时机**：双指缩放行为正在进行时或者直接调用 zoomTo 进行缩放的过程\n\n  - **示例**：\n  ```js\n    const bs = new BScroll('.bs-wrapper', {\n      freeScroll: true,\n      scrollX: true,\n      scrollY: true,\n      zoom: {\n        start: 1,\n        min: 0.5,\n        max: 2\n      }\n    })\n\n    bs.on('zooming', ({ scale }) => {\n      // use scale\n      console.log(scale) // 当前 scale 的值\n    })\n  ```\n\n### zoomEnd\n\n  - **参数**：`{ scale }`\n  - **类型**：`{ scale: number }`\n  - **触发时机**：双指缩放行为结束后（如果有回弹，触发时机在回弹动画结束之后）或者调用 zoomTo 完成缩放之后\n\n  :::warning\n  在 zoom 的场景下，你应该监听 zoomStart、zooming、zoomEnd 等等事件，而不是更底层的 scroll、scrollEnd 事件，要不然可能与你的预期不符。\n  :::"
  },
  {
    "path": "packages/vuepress-docs/docs-release.sh",
    "content": "#!/usr/bin/env sh\n\nset -e\n\nyarn run docs:build\n\ncd docs/.vuepress/dist\n\ngit init\ngit add -A\ngit commit -m 'update docs'\n\ngit push -f git@github.com:better-scroll/docs.git master:gh-pages\n\ncd -\n"
  },
  {
    "path": "packages/vuepress-docs/package.json",
    "content": "{\n    \"name\": \"vuepress-docs\",\n    \"version\": \"2.5.1\",\n    \"description\": \"Docs of BetterScroll\",\n    \"author\": {\n        \"name\": \"huangyi\",\n        \"email\": \"ustbhuangyi@gmail.com\"\n    },\n    \"private\": true,\n    \"scripts\": {\n        \"docs:dev\": \"vuepress dev docs --open\",\n        \"docs:build\": \"vuepress build docs\",\n        \"docs:release\": \"sh docs-release.sh\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n    },\n    \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n    \"keywords\": [\n        \"scroll\",\n        \"iscroll\",\n        \"javascript\",\n        \"typescript\",\n        \"ios\",\n        \"vuepress-docs\"\n    ],\n    \"license\": \"MIT\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n        \"directory\": \"packages/vuepress-docs\"\n    },\n    \"devDependencies\": {\n        \"ts-loader\": \"^5.4.5\",\n        \"vuepress\": \"^1.8.0\"\n    },\n    \"dependencies\": {\n        \"qrcode-js-package\": \"^1.0.4\",\n        \"v-tooltip\": \"^2.0.2\"\n    }\n}\n"
  },
  {
    "path": "packages/wheel/README.md",
    "content": "# @better-scroll/wheel\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/wheel/README_zh-CN.md)\n\nImplement a plugin similar to the effects of the IOS Picker component.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\nBScroll.use(Wheel)\n\nconst bs = new BScroll('.wheel-wrapper', {\n  wheel: {\n    selectedIndex: 0,\n    wheelWrapperClass: 'wheel-scroll',\n    wheelItemClass: 'wheel-item',\n    wheelDisabledItemClass: 'wheel-disabled-item'\n  },\n  probeType: 3\n})\n```\n"
  },
  {
    "path": "packages/wheel/README_zh-CN.md",
    "content": "# @better-scroll/wheel\n\n实现类似于 IOS Picker 组件效果的插件。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Wheel from '@better-scroll/wheel'\nBScroll.use(Wheel)\n\nconst bs = new BScroll('.wheel-wrapper', {\n  wheel: {\n    selectedIndex: 0,\n    wheelWrapperClass: 'wheel-scroll',\n    wheelItemClass: 'wheel-item',\n    wheelDisabledItemClass: 'wheel-disabled-item'\n  },\n  probeType: 3\n})\n```\n"
  },
  {
    "path": "packages/wheel/package.json",
    "content": "{\n  \"name\": \"@better-scroll/wheel\",\n  \"version\": \"2.5.1\",\n  \"description\": \"a BetterScroll plugin to imitate IOS Picker\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/wheel.min.js\",\n  \"module\": \"dist/wheel.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/wheel/src/__tests__/index.spec.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\nimport Wheel from '../index'\n\njest.mock('@better-scroll/core')\n\nconst createWheel = (wheelOptions: Object) => {\n  const wrapper = document.createElement('div')\n  const content = document.createElement('div')\n  wrapper.appendChild(content)\n  const scroll = new BScroll(wrapper, { wheel: wheelOptions })\n  const wheel = new Wheel(scroll)\n  return { scroll, wheel }\n}\n\nconst addPropertiesToWheel = <T extends Object>(wheel: Wheel, obj: T) => {\n  for (const key in obj) {\n    ;(wheel as any)[key] = obj[key]\n  }\n  return wheel\n}\ndescribe('wheel plugin tests', () => {\n  let scroll: BScroll\n  let wheel: Wheel\n\n  beforeEach(() => {\n    const created = createWheel({})\n    // create DOM\n    wheel = created.wheel\n    scroll = created.scroll\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to BScroll instance', () => {\n    expect(scroll.proxy).toBeCalled()\n    expect(scroll.proxy).toHaveBeenLastCalledWith([\n      {\n        key: 'wheelTo',\n        sourceKey: 'plugins.wheel.wheelTo'\n      },\n      {\n        key: 'getSelectedIndex',\n        sourceKey: 'plugins.wheel.getSelectedIndex'\n      },\n      {\n        key: 'restorePosition',\n        sourceKey: 'plugins.wheel.restorePosition'\n      }\n    ])\n  })\n\n  it('should handle options', () => {\n    expect(wheel.options.rotate).toBe(25)\n    expect(wheel.options.adjustTime).toBe(400)\n    expect(wheel.options.selectedIndex).toBe(0)\n    expect(wheel.options.wheelWrapperClass).toBe('wheel-scroll')\n    expect(wheel.options.wheelItemClass).toBe('wheel-item')\n    expect(wheel.options.wheelDisabledItemClass).toBe('wheel-disabled-item')\n  })\n\n  it('should refresh BehaviorX and BehaviorY boundary', () => {\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n\n    expect(scrollBehaviorX.refresh).toBeCalled()\n    expect(scrollBehaviorY.refresh).toBeCalled()\n  })\n\n  it('should handle selectedIndex', () => {\n    // default\n    expect(wheel.selectedIndex).toBe(0)\n    // specified\n    const { wheel: wheel2 } = createWheel({\n      selectedIndex: 2\n    })\n    expect(wheel2.selectedIndex).toBe(2)\n  })\n\n  it('should trigger scroll.scrollTo when invoking wheelTo method', () => {\n    addPropertiesToWheel(wheel, {\n      itemHeight: 40\n    })\n    wheel.wheelTo(0)\n\n    expect(scroll.scrollTo).toBeCalled()\n    expect(scroll.scrollTo).toHaveBeenLastCalledWith(0, -0, 0, undefined)\n  })\n\n  it('should return seletedIndex when invoking getSelectedIndex', () => {\n    const { wheel: wheel2 } = createWheel({\n      selectedIndex: 2\n    })\n    expect(wheel2.getSelectedIndex()).toBe(2)\n  })\n\n  it('should support scrollTo somewhere by selectedIndex when initialized', () => {\n    addPropertiesToWheel(wheel, {\n      selectedIndex: 1,\n      itemHeight: 50\n    })\n    const postion = {\n      x: 100,\n      y: 100\n    }\n    // manually trigger\n    scroll.hooks.trigger(scroll.hooks.eventTypes.beforeInitialScrollTo, postion)\n    expect(postion).toMatchObject({\n      x: 0,\n      y: -50\n    })\n  })\n\n  it('should invoke wheelTo when scroll.scroller trigger checkClick hook', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: div,\n      wheelTo: jest.fn()\n    })\n\n    scroll.scroller.hooks.trigger('checkClick')\n    expect(wheel.wheelTo).toBeCalled()\n    expect(wheel.wheelTo).toHaveBeenCalledWith(0, 400, expect.anything())\n\n    // if target element is not found\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: null,\n      wheelTo: jest.fn()\n    })\n    let ret = scroll.scroller.hooks.trigger('checkClick')\n    expect(ret).toBe(true)\n  })\n\n  it('should invoke findNearestValidWheel when scroll.scroller trigger scrollTo hook', () => {\n    let endPoint = { x: 0, y: -20 }\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: div,\n      itemHeight: 40,\n      wheelTo: jest.fn()\n    })\n\n    scroll.scroller.hooks.trigger('scrollTo', endPoint)\n    expect(endPoint.y).toBe(-0)\n  })\n\n  it('should change position when scroll.scroller trigger scrollToElement hook', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: div,\n      itemHeight: 40\n    })\n    let pos = {\n      top: -20,\n      left: 0\n    }\n    div.className = 'wheel-item'\n    scroll.scroller.hooks.trigger('scrollToElement', div, pos)\n\n    expect(pos).toEqual({\n      top: -0,\n      left: 0\n    })\n\n    // mismatch target element\n    let div1 = document.createElement('div')\n    let pos1 = {\n      top: -40,\n      left: 0\n    }\n    addPropertiesToWheel(wheel, {\n      items: [div1],\n      target: div1,\n      itemHeight: 40\n    })\n    let ret = scroll.scroller.hooks.trigger('scrollToElement', div1, pos1)\n    expect(ret).toBe(true)\n    expect(pos1).toMatchObject({\n      top: -40,\n      left: 0\n    })\n  })\n\n  it('should change target when scroll.scroller.actionsHandler trigger beforeStart hook', () => {\n    let e = {} as any\n    let div = document.createElement('div')\n    e.target = div\n    scroll.scroller.actionsHandler.hooks.trigger('beforeStart', e)\n\n    expect(wheel.target).toEqual(div)\n  })\n\n  it('should modify boundary when scrollBehaviorY or scrollBehaviorX computedBoundary', () => {\n    let div = document.createElement('div')\n    let cachedXBoundary = {} as Boundary\n    let cachedYBoundary = {} as Boundary\n    addPropertiesToWheel(wheel, {\n      items: [div, div],\n      itemHight: 50\n    })\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n    // append two element\n    scroll.scroller.content.appendChild(document.createElement('div'))\n    scroll.scroller.content.appendChild(document.createElement('div'))\n    scrollBehaviorY.contentSize = 100\n\n    // manually trigger\n    scrollBehaviorX.hooks.trigger(\n      scrollBehaviorX.hooks.eventTypes.computeBoundary,\n      cachedXBoundary\n    )\n    scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      cachedYBoundary\n    )\n\n    expect(cachedXBoundary).toMatchObject({\n      minScrollPos: 0,\n      maxScrollPos: 0\n    })\n\n    expect(cachedYBoundary).toMatchObject({\n      minScrollPos: 0,\n      maxScrollPos: -50\n    })\n  })\n\n  it('should change momentumInfo when scroll.scroller.scrollBehaviorY trigger momentum or end hook', () => {\n    let momentumInfo = {\n      destination: 0,\n      rate: 15\n    }\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: div,\n      itemHeight: 40\n    })\n    scroll.scroller.scrollBehaviorY.hooks.trigger('momentum', momentumInfo)\n\n    expect(momentumInfo).toEqual({\n      destination: -0,\n      rate: 4\n    })\n\n    scroll.scroller.scrollBehaviorY.currentPos = -20\n    scroll.scroller.scrollBehaviorY.hooks.trigger('end', momentumInfo)\n    expect(momentumInfo).toEqual({\n      destination: -0,\n      rate: 4,\n      duration: 400\n    })\n\n    scroll.scroller.scrollBehaviorY.hooks.trigger('momentum', momentumInfo, 800)\n    expect(momentumInfo).toEqual({\n      destination: -0,\n      rate: 4,\n      duration: 400\n    })\n  })\n\n  it('scroll.hooks.refresh ', () => {\n    let newContent = document.createElement('p')\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      target: div,\n      itemHeight: 40\n    })\n    wheel.options.selectedIndex = 1\n    scroll.hooks.trigger(scroll.hooks.eventTypes.refresh, newContent)\n\n    expect(scroll.scrollTo).toBeCalledWith(0, -40, 0, undefined)\n  })\n\n  it('scroll.scroller.animater.hooks.time ', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div]\n    })\n    const animater = scroll.scroller.animater\n    animater.hooks.trigger(animater.hooks.eventTypes.time, 100)\n    expect(div.style.transitionDuration).toBe('100ms')\n  })\n\n  it('scroll.scroller.animater.hooks.timeFunction ', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div]\n    })\n    const animater = scroll.scroller.animater\n    animater.hooks.trigger(\n      animater.hooks.eventTypes.timeFunction,\n      'cubic-bezier(0.23, 1, 0.32, 1)'\n    )\n    expect(div.style.transitionTimingFunction).toBe(\n      'cubic-bezier(0.23, 1, 0.32, 1)'\n    )\n  })\n\n  it('scroll.scroller.animater.hooks.callStop', () => {\n    let div1 = document.createElement('div')\n    let div2 = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div1, div2],\n      itemHeight: 40,\n      wheelItemsAllDisabled: false\n    })\n    scroll.y = -41\n    scroll.maxScrollY = -80\n    scroll.scroller.animater.hooks.trigger('callStop')\n    expect(scroll.scrollTo).toBeCalledWith(0, -40, 0, undefined)\n  })\n\n  it('scroll.scroller.animater.translater.hooks.translate', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div],\n      itemHeight: 40,\n      wheelItemsAllDisabled: false\n    })\n    const translater = scroll.scroller.animater.translater\n    translater.hooks.trigger(translater.hooks.eventTypes.translate, {\n      x: 0,\n      y: -20\n    })\n    expect(wheel.selectedIndex).toEqual(0)\n  })\n\n  it('scroll.scroller.hooks.minDistanceScroll ', () => {\n    let div = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      items: [div]\n    })\n    const scroller = scroll.scroller\n    scroller.animater.forceStopped = true\n    scroller.hooks.trigger(scroller.hooks.eventTypes.minDistanceScroll)\n    expect(scroller.animater.forceStopped).toBe(false)\n  })\n\n  it('scrollEnd event', () => {\n    let div1 = document.createElement('div')\n    let div2 = document.createElement('div')\n    addPropertiesToWheel(wheel, {\n      itemHeight: 40,\n      items: [div1, div2]\n    })\n    scroll.maxScrollY = -80\n    scroll.scroller.animater.forceStopped = true\n    // stopped from an animation,\n    // prevent user's scrollEnd callback triggered twice\n    const ret = scroll.trigger(scroll.eventTypes.scrollEnd, { y: 0 })\n    expect(ret).toBe(true)\n\n    wheel.isAdjustingPosition = true\n    // update selectedIndex\n    scroll.trigger(scroll.eventTypes.scrollEnd, { y: -41 })\n\n    expect(wheel.getSelectedIndex()).toBe(1)\n    expect(wheel.isAdjustingPosition).toBe(false)\n  })\n\n  it('wheel.restorePosition()', () => {\n    addPropertiesToWheel(wheel, {\n      itemHeight: 40\n    })\n    // simulate bs is scrolling\n    scroll.pending = true\n    wheel.restorePosition()\n    expect(scroll.scroller.animater.clearTimer).toBeCalled()\n    expect(scroll.scrollTo).toBeCalledWith(0, -0, 0, undefined)\n  })\n\n  it('should support disable wheel items', () => {\n    let div1 = document.createElement('div')\n    let div2 = document.createElement('div')\n    const scroller = scroll.scroller\n    const position = { y: -41 }\n    addPropertiesToWheel(wheel, {\n      items: [div1, div2],\n      itemHeight: 40,\n      wheelItemsAllDisabled: false\n    })\n    scroll.y = -41\n    scroll.maxScrollY = -80\n    div2.className = 'wheel-disabled-item'\n    scroller.hooks.trigger(scroller.hooks.eventTypes.scrollTo, position)\n    expect(position.y).toBe(-0)\n\n    div1.className = 'wheel-disabled-item'\n    wheel.wheelItemsAllDisabled = true\n    scroller.hooks.trigger(scroller.hooks.eventTypes.scrollTo, position)\n    expect(position.y).toBe(-0)\n\n    let div3 = document.createElement('div')\n    let position3 = {\n      y: -39\n    }\n    addPropertiesToWheel(wheel, {\n      items: [div1, div2, div3],\n      itemHeight: 40,\n      wheelItemsAllDisabled: false\n    })\n    scroller.hooks.trigger(scroller.hooks.eventTypes.scrollTo, position3)\n    expect(position3.y).toBe(-80)\n  })\n})\n"
  },
  {
    "path": "packages/wheel/src/index.ts",
    "content": "import BScroll, { Boundary } from '@better-scroll/core'\nimport {\n  style,\n  hasClass,\n  ease,\n  EaseItem,\n  extend,\n  Position,\n  HTMLCollectionToArray\n} from '@better-scroll/shared-utils'\nimport propertiesConfig from './propertiesConfig'\n\nexport type WheelOptions = Partial<WheelConfig> | true\n\nconst WHEEL_INDEX_CHANGED_EVENT_NAME = 'wheelIndexChanged'\n\nexport interface WheelConfig {\n  selectedIndex: number\n  rotate: number\n  adjustTime: number\n  wheelWrapperClass: string\n  wheelItemClass: string\n  wheelDisabledItemClass: string\n}\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    wheel?: WheelOptions\n  }\n  interface CustomAPI {\n    wheel: PluginAPI\n  }\n}\n\ninterface PluginAPI {\n  wheelTo(index?: number, time?: number, ease?: EaseItem): void\n  getSelectedIndex(): number\n  restorePosition(): void\n}\n\nconst CONSTANTS = {\n  rate: 4\n}\nexport default class Wheel implements PluginAPI {\n  static pluginName = 'wheel'\n  options: WheelConfig\n  wheelItemsAllDisabled: boolean\n  items: HTMLCollection\n  itemHeight: number\n  selectedIndex: number\n  isAdjustingPosition: boolean\n  target: EventTarget | null\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  init() {\n    this.handleBScroll()\n    this.handleOptions()\n    this.handleHooks()\n    // init boundary for Wheel\n    this.refreshBoundary()\n    this.setSelectedIndex(this.options.selectedIndex)\n  }\n\n  private handleBScroll() {\n    this.scroll.proxy(propertiesConfig)\n    this.scroll.registerType([WHEEL_INDEX_CHANGED_EVENT_NAME])\n  }\n\n  private handleOptions() {\n    const userOptions = (this.scroll.options.wheel === true\n      ? {}\n      : this.scroll.options.wheel) as Partial<WheelConfig>\n\n    const defaultOptions: WheelConfig = {\n      wheelWrapperClass: 'wheel-scroll',\n      wheelItemClass: 'wheel-item',\n      rotate: 25,\n      adjustTime: 400,\n      selectedIndex: 0,\n      wheelDisabledItemClass: 'wheel-disabled-item'\n    }\n    this.options = extend(defaultOptions, userOptions)\n  }\n\n  private handleHooks() {\n    const scroll = this.scroll\n    const scroller = this.scroll.scroller\n    const {\n      actionsHandler,\n      scrollBehaviorX,\n      scrollBehaviorY,\n      animater\n    } = scroller\n    let prevContent = scroller.content\n    // BScroll\n    scroll.on(scroll.eventTypes.scrollEnd, (position: Position) => {\n      const index = this.findNearestValidWheel(position.y).index\n      if (scroller.animater.forceStopped && !this.isAdjustingPosition) {\n        this.target = this.items[index]\n        // since stopped from an animation.\n        // prevent user's scrollEnd callback triggered twice\n        return true\n      } else {\n        this.setSelectedIndex(index)\n        if (this.isAdjustingPosition) {\n          this.isAdjustingPosition = false\n        }\n      }\n    })\n    // BScroll.hooks\n    this.scroll.hooks.on(\n      this.scroll.hooks.eventTypes.refresh,\n      (content: HTMLElement) => {\n        if (content !== prevContent) {\n          prevContent = content\n          this.setSelectedIndex(this.options.selectedIndex, true)\n        }\n        // rotate all wheel-items\n        // because position may not change\n        this.rotateX(this.scroll.y)\n        // check we are stop at a disable item or not\n        this.wheelTo(this.selectedIndex, 0)\n      }\n    )\n\n    this.scroll.hooks.on(\n      this.scroll.hooks.eventTypes.beforeInitialScrollTo,\n      (position: Position) => {\n        // selectedIndex has higher priority than bs.options.startY\n        position.x = 0\n        position.y = -(this.selectedIndex * this.itemHeight)\n      }\n    )\n    // Scroller\n    scroller.hooks.on(scroller.hooks.eventTypes.checkClick, () => {\n      const index = HTMLCollectionToArray(this.items).indexOf(this.target)\n      if (index === -1) return true\n\n      this.wheelTo(index, this.options.adjustTime, ease.swipe)\n      return true\n    })\n    scroller.hooks.on(\n      scroller.hooks.eventTypes.scrollTo,\n      (endPoint: Position) => {\n        endPoint.y = this.findNearestValidWheel(endPoint.y).y\n      }\n    )\n    // when content is scrolling\n    // click wheel-item DOM repeatedly and crazily will cause scrollEnd not triggered\n    // so reset forceStopped\n    scroller.hooks.on(scroller.hooks.eventTypes.minDistanceScroll, () => {\n      const animater = scroller.animater\n      if (animater.forceStopped === true) {\n        animater.forceStopped = false\n      }\n    })\n    scroller.hooks.on(\n      scroller.hooks.eventTypes.scrollToElement,\n      (el: HTMLElement, pos: { top: number; left: number }) => {\n        if (!hasClass(el, this.options.wheelItemClass)) {\n          return true\n        } else {\n          pos.top = this.findNearestValidWheel(pos.top).y\n        }\n      }\n    )\n\n    // ActionsHandler\n    actionsHandler.hooks.on(\n      actionsHandler.hooks.eventTypes.beforeStart,\n      (e: TouchEvent) => {\n        this.target = e.target\n      }\n    )\n\n    // ScrollBehaviorX\n    // Wheel has no x direction now\n    scrollBehaviorX.hooks.on(\n      scrollBehaviorX.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        boundary.maxScrollPos = 0\n        boundary.minScrollPos = 0\n      }\n    )\n\n    // ScrollBehaviorY\n    scrollBehaviorY.hooks.on(\n      scrollBehaviorY.hooks.eventTypes.computeBoundary,\n      (boundary: Boundary) => {\n        this.items = this.scroll.scroller.content.children\n        this.checkWheelAllDisabled()\n\n        this.itemHeight =\n          this.items.length > 0\n            ? scrollBehaviorY.contentSize / this.items.length\n            : 0\n\n        boundary.maxScrollPos = -this.itemHeight * (this.items.length - 1)\n        boundary.minScrollPos = 0\n      }\n    )\n    scrollBehaviorY.hooks.on(\n      scrollBehaviorY.hooks.eventTypes.momentum,\n      (momentumInfo: {\n        destination: number\n        duration: number\n        rate: number\n      }) => {\n        momentumInfo.rate = CONSTANTS.rate\n        momentumInfo.destination = this.findNearestValidWheel(\n          momentumInfo.destination\n        ).y\n      }\n    )\n    scrollBehaviorY.hooks.on(\n      scrollBehaviorY.hooks.eventTypes.end,\n      (momentumInfo: { destination: number; duration: number }) => {\n        let validWheel = this.findNearestValidWheel(scrollBehaviorY.currentPos)\n        momentumInfo.destination = validWheel.y\n        momentumInfo.duration = this.options.adjustTime\n      }\n    )\n\n    // Animater\n    animater.hooks.on(animater.hooks.eventTypes.time, (time: number) => {\n      this.transitionDuration(time)\n    })\n    animater.hooks.on(\n      animater.hooks.eventTypes.timeFunction,\n      (easing: string) => {\n        this.timeFunction(easing)\n      }\n    )\n    // bs.stop() to make wheel stop at a correct position when pending\n    animater.hooks.on(animater.hooks.eventTypes.callStop, () => {\n      const { index } = this.findNearestValidWheel(this.scroll.y)\n      this.isAdjustingPosition = true\n      this.wheelTo(index, 0)\n    })\n\n    // Translater\n    animater.translater.hooks.on(\n      animater.translater.hooks.eventTypes.translate,\n      (endPoint: Position) => {\n        this.rotateX(endPoint.y)\n      }\n    )\n  }\n\n  private refreshBoundary() {\n    const { scrollBehaviorX, scrollBehaviorY, content } = this.scroll.scroller\n    scrollBehaviorX.refresh(content)\n    scrollBehaviorY.refresh(content)\n  }\n\n  setSelectedIndex(index: number, contentChanged: boolean = false) {\n    const prevSelectedIndex = this.selectedIndex\n    this.selectedIndex = index\n\n    // if content DOM changed, should not trigger event\n    if (prevSelectedIndex !== index && !contentChanged) {\n      this.scroll.trigger(WHEEL_INDEX_CHANGED_EVENT_NAME, index)\n    }\n  }\n\n  getSelectedIndex() {\n    return this.selectedIndex\n  }\n\n  wheelTo(index = 0, time = 0, ease?: EaseItem) {\n    const y = -index * this.itemHeight\n    this.scroll.scrollTo(0, y, time, ease)\n  }\n\n  restorePosition() {\n    // bs is scrolling\n    const isPending = this.scroll.pending\n    if (isPending) {\n      const selectedIndex = this.getSelectedIndex()\n      this.scroll.scroller.animater.clearTimer()\n      this.wheelTo(selectedIndex, 0)\n    }\n  }\n\n  private transitionDuration(time: number) {\n    for (let i = 0; i < this.items.length; i++) {\n      ;(this.items[i] as HTMLElement).style[style.transitionDuration as any] =\n        time + 'ms'\n    }\n  }\n\n  private timeFunction(easing: string) {\n    for (let i = 0; i < this.items.length; i++) {\n      ;(this.items[i] as HTMLElement).style[\n        style.transitionTimingFunction as any\n      ] = easing\n    }\n  }\n\n  private rotateX(y: number) {\n    const { rotate = 25 } = this.options\n    for (let i = 0; i < this.items.length; i++) {\n      const deg = rotate * (y / this.itemHeight + i)\n      // Too small value is invalid in some phones, issue 1026\n      const SafeDeg = deg.toFixed(3)\n      ;(this.items[i] as HTMLElement).style[\n        style.transform as any\n      ] = `rotateX(${SafeDeg}deg)`\n    }\n  }\n\n  private findNearestValidWheel(y: number) {\n    y = y > 0 ? 0 : y < this.scroll.maxScrollY ? this.scroll.maxScrollY : y\n    let currentIndex = Math.abs(Math.round(-y / this.itemHeight))\n    const cacheIndex = currentIndex\n    const items = this.items\n    const wheelDisabledItemClassName = this.options\n      .wheelDisabledItemClass as string\n    // implement web native select element\n    // first, check whether there is a enable item whose index is smaller than currentIndex\n    // then, check whether there is a enable item whose index is bigger than currentIndex\n    // otherwise, there are all disabled items, just keep currentIndex unchange\n    while (currentIndex >= 0) {\n      if (\n        !hasClass(\n          items[currentIndex] as HTMLElement,\n          wheelDisabledItemClassName\n        )\n      ) {\n        break\n      }\n      currentIndex--\n    }\n\n    if (currentIndex < 0) {\n      currentIndex = cacheIndex\n      while (currentIndex <= items.length - 1) {\n        if (\n          !hasClass(\n            items[currentIndex] as HTMLElement,\n            wheelDisabledItemClassName\n          )\n        ) {\n          break\n        }\n        currentIndex++\n      }\n    }\n\n    // keep it unchange when all the items are disabled\n    if (currentIndex === items.length) {\n      currentIndex = cacheIndex\n    }\n    // when all the items are disabled, selectedIndex should always be -1\n    return {\n      index: this.wheelItemsAllDisabled ? -1 : currentIndex,\n      y: -currentIndex * this.itemHeight\n    }\n  }\n\n  private checkWheelAllDisabled() {\n    const wheelDisabledItemClassName = this.options.wheelDisabledItemClass\n    const items = this.items\n    this.wheelItemsAllDisabled = true\n    for (let i = 0; i < items.length; i++) {\n      if (!hasClass(items[i] as HTMLElement, wheelDisabledItemClassName)) {\n        this.wheelItemsAllDisabled = false\n        break\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/wheel/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.wheel'\nconst propertiesMap = [\n  {\n    key: 'wheelTo',\n    name: 'wheelTo',\n  },\n  {\n    key: 'getSelectedIndex',\n    name: 'getSelectedIndex',\n  },\n  {\n    key: 'restorePosition',\n    name: 'restorePosition',\n  },\n]\nexport default propertiesMap.map((item) => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`,\n  }\n})\n"
  },
  {
    "path": "packages/zoom/README.md",
    "content": "# @better-scroll/pull-up\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/zoom/README_zh-CN.md)\n\nPlugin for zooming in or out.\n\n## Usage\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\nBScroll.use(Zoom)\n\nconst bs = new BScroll('.zoom-wrapper', {\n  freeScroll: true,\n  scrollX: true,\n  scrollY: true,\n  disableMouse: true,\n  useTransition: true,\n  zoom: {\n    start: 1,\n    min: 0.5,\n    max: 2\n  }\n})\n```\n"
  },
  {
    "path": "packages/zoom/README_zh-CN.md",
    "content": "# @better-scroll/zoom\n\n为 BetterScroll 提供放大或者缩小的效果的插件。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\nBScroll.use(Zoom)\n\nconst bs = new BScroll('.zoom-wrapper', {\n  freeScroll: true,\n  scrollX: true,\n  scrollY: true,\n  disableMouse: true,\n  useTransition: true,\n  zoom: {\n    start: 1,\n    min: 0.5,\n    max: 2\n  }\n})\n```\n"
  },
  {
    "path": "packages/zoom/package.json",
    "content": "{\n  \"name\": \"@better-scroll/zoom\",\n  \"version\": \"2.5.1\",\n  \"description\": \"a BetterScroll plugin to enlarge or narrow\",\n  \"author\": {\n    \"name\": \"jizhi\",\n    \"email\": \"theniceangel@163.com\"\n  },\n  \"main\": \"dist/zoom.min.js\",\n  \"module\": \"dist/zoom.esm.js\",\n  \"typings\": \"dist/types/index.d.ts\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/ustbhuangyi/better-scroll/issues\"\n  },\n  \"homepage\": \"https://github.com/ustbhuangyi/better-scroll\",\n  \"keywords\": [\n    \"scroll\",\n    \"iscroll\",\n    \"javascript\",\n    \"typescript\",\n    \"ios\",\n    \"image-preview\",\n    \"zoom\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:ustbhuangyi/better-scroll.git\",\n    \"directory\": \"packages/zoom\"\n  },\n  \"dependencies\": {\n    \"@better-scroll/core\": \"^2.5.1\"\n  },\n  \"gitHead\": \"f441227b6137d44ba0b44b97ed4cd49de9386130\"\n}\n"
  },
  {
    "path": "packages/zoom/src/__tests__/__utils__/util.ts",
    "content": "import {\n  createEvent,\n  CustomTouchEvent\n} from '@better-scroll/core/src/__tests__/__utils__/event'\nimport { createDiv } from '@better-scroll/core/src/__tests__/__utils__/layout'\n\nexport function createZoomElements() {\n  const wrapper = createDiv(300, 300)\n  const scaledElement = createDiv(300, 300, 0, 0)\n  wrapper.appendChild(scaledElement)\n  return { wrapper, scaledElement }\n}\n\nexport function createTouchEvent(\n  firstFingerPoint: { pageX: number; pageY: number },\n  secondFingerPoint?: { pageX: number; pageY: number }\n): CustomTouchEvent {\n  const e = createEvent('Event', 'touch') as CustomTouchEvent\n  e.touches = [firstFingerPoint]\n  if (secondFingerPoint) {\n    e.touches.push(secondFingerPoint)\n  }\n  return e\n}\n"
  },
  {
    "path": "packages/zoom/src/__tests__/index.spec.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport Zoom from '../index'\nimport { ease } from '@better-scroll/shared-utils'\nimport { createTouchEvent, createZoomElements } from './__utils__/util'\njest.mock('@better-scroll/core')\njest.mock('@better-scroll/core/src/animater/index')\n\ndescribe('zoom plugin', () => {\n  let scroll: BScroll\n\n  beforeEach(() => {\n    // create DOM\n    const { wrapper } = createZoomElements()\n    scroll = new BScroll(wrapper)\n  })\n\n  afterEach(() => {\n    jest.clearAllMocks()\n  })\n\n  it('should proxy properties to BScroll instance', () => {\n    new Zoom(scroll)\n\n    expect(scroll.proxy).toBeCalled()\n    expect(scroll.proxy).toHaveBeenLastCalledWith([\n      {\n        key: 'zoomTo',\n        sourceKey: 'plugins.zoom.zoomTo',\n      },\n    ])\n  })\n\n  it('should register hooks to BScroll instance', () => {\n    new Zoom(scroll)\n\n    expect(scroll.registerType).toBeCalled()\n    expect(scroll.registerType).toHaveBeenLastCalledWith([\n      'beforeZoomStart',\n      'zoomStart',\n      'zooming',\n      'zoomEnd',\n    ])\n    expect(scroll.eventTypes.beforeZoomStart).toEqual('beforeZoomStart')\n    expect(scroll.eventTypes.zoomStart).toEqual('zoomStart')\n  })\n\n  it('should handle default options and user options', () => {\n    // case 1\n    scroll.options.zoom = true\n    let zoom = new Zoom(scroll)\n    expect(zoom.zoomOpt).toMatchObject({\n      start: 1,\n      min: 1,\n      max: 4,\n      initialOrigin: [0, 0],\n      minimalZoomDistance: 5,\n      bounceTime: 800,\n    })\n\n    // case 2\n    scroll.options.zoom = {\n      initialOrigin: ['center', 'center'],\n      bounceTime: 300,\n    }\n    zoom = new Zoom(scroll)\n    expect(zoom.zoomOpt).toMatchObject({\n      start: 1,\n      min: 1,\n      max: 4,\n      initialOrigin: ['center', 'center'],\n      minimalZoomDistance: 5,\n      bounceTime: 300,\n    })\n  })\n\n  it('should try initialZoomTo when new zoom()', () => {\n    // start is 1, no zoomTo\n    new Zoom(scroll)\n    expect(scroll.scroller.scrollTo).toBeCalledTimes(0)\n\n    // start !== 1\n    scroll.options.zoom = {\n      start: 1.5,\n      initialOrigin: [0, 0],\n    }\n    new Zoom(scroll)\n    expect(scroll.scroller.scrollTo).toBeCalledTimes(1)\n    expect(scroll.scroller.scrollTo).toHaveBeenLastCalledWith(\n      0,\n      0,\n      0,\n      ease.bounce,\n      {\n        start: {\n          scale: 1,\n        },\n        end: {\n          scale: 1.5,\n        },\n      }\n    )\n\n    // start should <= max\n    scroll.options.zoom = {\n      start: 3.5,\n      max: 3,\n      initialOrigin: [0, 0],\n    }\n    new Zoom(scroll)\n    expect(scroll.scroller.scrollTo).toBeCalledTimes(2)\n    expect(scroll.scroller.scrollTo).toHaveBeenLastCalledWith(\n      0,\n      0,\n      0,\n      ease.bounce,\n      {\n        start: {\n          scale: 1,\n        },\n        end: {\n          scale: 3, // equals max\n        },\n      }\n    )\n  })\n\n  it(\"should set scaled element's transform origin\", () => {\n    new Zoom(scroll)\n    expect(scroll.scroller.content.style['transform-origin' as any]).toBe('0 0')\n  })\n\n  it('should not response with one finger', () => {\n    const zoom = new Zoom(scroll)\n    const hooks = scroll.scroller.actions.hooks\n\n    const zoomStartSpy = jest.spyOn(zoom, 'zoomStart')\n    const zoomSpy = jest.spyOn(zoom, 'zoom')\n    const zoomEndSpy = jest.spyOn(zoom, 'zoomEnd')\n\n    const e = createTouchEvent({ pageX: 0, pageY: 0 })\n    hooks.trigger(hooks.eventTypes.start, e)\n    expect(zoomStartSpy).not.toHaveBeenCalled()\n\n    hooks.trigger(hooks.eventTypes.beforeMove, e)\n    expect(zoomSpy).not.toHaveBeenCalled()\n\n    hooks.trigger(hooks.eventTypes.beforeEnd, e)\n    expect(zoomEndSpy).not.toHaveBeenCalled()\n  })\n\n  it('should compute boundary of Behavior when zoom ends', () => {\n    const zoom = new Zoom(scroll) as any\n\n    // simulate two fingers\n    zoom.numberOfFingers = 2\n    // allow trigger beforeEnd hooks\n    zoom.zoomed = true\n\n    const e = createTouchEvent({ pageX: 0, pageY: 0 }, { pageX: 20, pageY: 20 })\n    const actions = scroll.scroller.actions\n    const behaviorX = scroll.scroller.scrollBehaviorX\n    const behaviorY = scroll.scroller.scrollBehaviorY\n    behaviorX.checkInBoundary = jest.fn().mockImplementation(() => {\n      return { inBoundary: true }\n    })\n    behaviorY.checkInBoundary = jest.fn().mockImplementation(() => {\n      return { inBoundary: true }\n    })\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeEnd, e)\n\n    expect(behaviorX.computeBoundary).toHaveBeenCalled()\n    expect(behaviorY.computeBoundary).toHaveBeenCalled()\n\n    // we should zoomed before call zoomEnd\n    zoom.zoomed = false\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeEnd, e)\n    expect(behaviorX.computeBoundary).toBeCalledTimes(1)\n  })\n\n  it('should fail when zooming distance < minimalZoomDistance', () => {\n    scroll.options.zoom = {\n      minimalZoomDistance: 10,\n    }\n    new Zoom(scroll)\n    const actions = scroll.scroller.actions\n    const mockZoomingFn = jest.fn()\n    scroll.on(scroll.eventTypes.zooming, mockZoomingFn)\n    // zoomStart\n    const e = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 130, pageY: 130 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.start, e)\n    // zoom\n    const e2 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 135, pageY: 135 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeMove, e2)\n    expect(mockZoomingFn).toHaveBeenCalledTimes(0)\n  })\n\n  it('should have correct behavior when zooming out', () => {\n    scroll.options.zoom = {\n      max: 2,\n    }\n    const zoom = new Zoom(scroll)\n    const actions = scroll.scroller.actions\n    const translater = scroll.scroller.translater\n    const mockZoomingFn = jest.fn()\n    scroll.on(scroll.eventTypes.zooming, mockZoomingFn)\n\n    // zoomStart\n    const e = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 130, pageY: 130 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.start, e)\n    // zoom\n    const e2 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 150, pageY: 150 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeMove, e2)\n\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalled()\n    expect(mockZoomingFn).toHaveBeenCalledTimes(1)\n    // beforeMove hooks use translater.translate, not scroller.scrollTo\n    expect(scroll.scroller.translater.translate).toBeCalledWith({\n      x: -16,\n      y: -16,\n      scale: 1.2,\n    })\n    expect(zoom.scale).toBe(1.2)\n    // triggered beforeTranslate hooks\n    const transformString: string[] = []\n    const transformPoint = {\n      scale: 1.2,\n    }\n    translater.hooks.trigger(\n      translater.hooks.eventTypes.beforeTranslate,\n      transformString,\n      transformPoint\n    )\n    expect(transformString[0]).toBe('scale(1.2)')\n\n    // keep zoom\n    const e3 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 170, pageY: 170 }\n    )\n    actions.hooks.trigger('beforeMove', e3)\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalledTimes(2)\n\n    expect(scroll.scroller.translater.translate).toHaveBeenLastCalledWith({\n      x: -32,\n      y: -32,\n      scale: 1.4,\n    })\n    expect(zoom.scale).toBe(1.4)\n\n    // keep zoom, allow zooming exceeds max\n    const e4 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 240, pageY: 240 }\n    )\n    actions.hooks.trigger('beforeMove', e4)\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalledTimes(3)\n\n    expect(scroll.scroller.translater.translate).toHaveBeenLastCalledWith({\n      x: -85,\n      y: -85,\n      scale: 2 * 2 * Math.pow(0.5, 2 / 2.1),\n    })\n    expect(zoom.scale).toBeCloseTo(2.067)\n\n    // zoom end, perform a rebound animation,back to max scale\n    actions.hooks.trigger('beforeEnd')\n    expect(zoom.scale).toBe(2)\n    expect(scroll.scroller.scrollTo).toHaveBeenLastCalledWith(\n      0,\n      0,\n      800,\n      ease.bounce,\n      {\n        start: {\n          scale: 2.0671155660140554,\n        },\n        end: {\n          scale: 2,\n        },\n      }\n    )\n  })\n\n  it('should have correct behavior when zooming in', () => {\n    scroll.options.zoom = {\n      min: 0.5,\n    }\n    const zoom = new Zoom(scroll)\n    const actions = scroll.scroller.actions\n    const translater = scroll.scroller.translater\n    const mockZoomingFn = jest.fn()\n    scroll.on(scroll.eventTypes.zooming, mockZoomingFn)\n\n    // zoomStart\n    const e = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 130, pageY: 130 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.start, e)\n    // zoom\n    const e2 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 110, pageY: 110 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeMove, e2)\n\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalled()\n    expect(mockZoomingFn).toHaveBeenCalledTimes(1)\n    // beforeMove hooks use translater.translate, not scroller.scrollTo\n    expect(scroll.scroller.translater.translate).toBeCalledWith({\n      x: 16,\n      y: 16,\n      scale: 0.8,\n    })\n    expect(zoom.scale).toBe(0.8)\n    // triggered beforeTranslate hooks\n    const transformString: string[] = []\n    const transformPoint = {\n      scale: 0.8,\n    }\n    translater.hooks.trigger(\n      translater.hooks.eventTypes.beforeTranslate,\n      transformString,\n      transformPoint\n    )\n    expect(transformString[0]).toBe('scale(0.8)')\n\n    // keep zoom\n    const e3 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 90, pageY: 90 }\n    )\n    actions.hooks.trigger('beforeMove', e3)\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalledTimes(2)\n\n    expect(scroll.scroller.translater.translate).toHaveBeenLastCalledWith({\n      x: 32,\n      y: 32,\n      scale: 0.6,\n    })\n    expect(zoom.scale).toBe(0.6)\n\n    // keep zoom, allow zooming exceeds max\n    const e4 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 40, pageY: 40 }\n    )\n    actions.hooks.trigger('beforeMove', e4)\n    // triggered zooming hooks\n    expect(mockZoomingFn).toHaveBeenCalledTimes(3)\n\n    expect(scroll.scroller.translater.translate).toHaveBeenLastCalledWith({\n      x: 57,\n      y: 57,\n      scale: 0.5 * 0.5 * Math.pow(2, 0.1 / 0.5),\n    })\n    expect(zoom.scale).toBeCloseTo(0.287)\n\n    // zoom end, perform a rebound animation,back to max scale\n    actions.hooks.trigger('beforeEnd')\n    expect(zoom.scale).toBe(0.5)\n    expect(scroll.scroller.scrollTo).toHaveBeenLastCalledWith(\n      0,\n      0,\n      800,\n      ease.bounce,\n      {\n        start: {\n          scale: 0.2871745887492588,\n        },\n        end: {\n          scale: 0.5,\n        },\n      }\n    )\n  })\n\n  it('should have correct behavior for zoomTo', (done) => {\n    scroll.options.zoom = {\n      min: 0.5,\n      max: 3,\n      start: 1,\n    }\n    const zoom = new Zoom(scroll)\n    const { scrollBehaviorX, scrollBehaviorY } = scroll.scroller\n    scrollBehaviorX.contentSize = 100\n    scrollBehaviorY.contentSize = 100\n    scrollBehaviorX.wrapperSize = 100\n    scrollBehaviorY.wrapperSize = 100\n\n    // [0, 0] as origin, scale to 2\n    zoom.zoomTo(2, 0, 0)\n    expect(scroll.scroller.scrollTo).toHaveBeenCalledWith(\n      0,\n      0,\n      800,\n      ease.bounce,\n      {\n        start: {\n          scale: 1,\n        },\n        end: {\n          scale: 2,\n        },\n      }\n    )\n\n    // ['center', 'center'] as origin, time is 300, scale to 1.5\n    zoom.zoomTo(1.5, 'center', 'center', 300)\n    expect(scroll.scroller.scrollTo).toHaveBeenCalledWith(\n      0,\n      0,\n      300,\n      ease.bounce,\n      {\n        start: {\n          scale: 2,\n        },\n        end: {\n          scale: 1.5,\n        },\n      }\n    )\n\n    // ['left', 'top'] as origin, time is 300, scale to 3\n    zoom.zoomTo(3, 'left', 'top', 300)\n    expect(scroll.scroller.scrollTo).toHaveBeenCalledWith(\n      0,\n      0,\n      300,\n      ease.bounce,\n      {\n        start: {\n          scale: 1.5,\n        },\n        end: {\n          scale: 3,\n        },\n      }\n    )\n\n    // ['right', 'bottom'] as origin, time is 300, scale to 3\n    zoom.zoomTo(2, 'right', 'bottom', 300)\n    expect(scroll.scroller.scrollTo).toHaveBeenCalledWith(\n      0,\n      0,\n      300,\n      ease.bounce,\n      {\n        start: {\n          scale: 3,\n        },\n        end: {\n          scale: 2,\n        },\n      }\n    )\n    // The purpose for improving test coverage\n    setTimeout(() => {\n      done()\n    }, 320)\n  })\n\n  it('should support full hooks', () => {\n    scroll.options.zoom = {\n      min: 1,\n      start: 1,\n      max: 4,\n    }\n    new Zoom(scroll)\n    const actions = scroll.scroller.actions\n    const behaviorX = scroll.scroller.scrollBehaviorX\n    const behaviorY = scroll.scroller.scrollBehaviorY\n    behaviorX.checkInBoundary = jest.fn().mockImplementation(() => {\n      return { inBoundary: true }\n    })\n    behaviorY.checkInBoundary = jest.fn().mockImplementation(() => {\n      return { inBoundary: true }\n    })\n\n    const mockBeforeZoomStartFn = jest.fn()\n    const mockZoomStartFn = jest.fn()\n    const mockZoomingFn = jest.fn()\n    const mockZoomEndFn = jest.fn()\n\n    // tap hooks\n    scroll.on(scroll.eventTypes.beforeZoomStart, mockBeforeZoomStartFn)\n    scroll.on(scroll.eventTypes.zoomStart, mockZoomStartFn)\n    scroll.on(scroll.eventTypes.zooming, mockZoomingFn)\n    scroll.on(scroll.eventTypes.zoomEnd, mockZoomEndFn)\n\n    // zoomStart\n    const e1 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 130, pageY: 130 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.start, e1)\n    // zooming\n    const e2 = createTouchEvent(\n      { pageX: 30, pageY: 30 },\n      { pageX: 150, pageY: 150 }\n    )\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeMove, e2)\n    // zoomEnd\n    actions.hooks.trigger(actions.hooks.eventTypes.beforeEnd)\n\n    expect(mockBeforeZoomStartFn).toBeCalledTimes(1)\n    expect(mockZoomStartFn).toBeCalledTimes(1)\n    expect(mockZoomingFn).toBeCalledTimes(1)\n    expect(mockZoomEndFn).toBeCalledTimes(1)\n  })\n\n  it('should destroy all events', () => {\n    new Zoom(scroll)\n    const {\n      actions,\n      scrollBehaviorX,\n      scrollBehaviorY,\n      translater,\n    } = scroll.scroller\n    scroll.hooks.trigger(scroll.hooks.eventTypes.destroy)\n    expect(scrollBehaviorX.hooks.events['beforeComputeBoundary'].length).toBe(0)\n    expect(scrollBehaviorY.hooks.events['beforeComputeBoundary'].length).toBe(0)\n    expect(actions.hooks.events['start'].length).toBe(0)\n    expect(actions.hooks.events['beforeMove'].length).toBe(0)\n    expect(actions.hooks.events['beforeEnd'].length).toBe(0)\n    expect(translater.hooks.events['beforeTranslate'].length).toBe(0)\n  })\n\n  it('should work well when content DOM has changed', () => {\n    const zoom = new Zoom(scroll)\n    const newContent = document.createElement('p')\n    scroll.hooks.trigger(scroll.hooks.eventTypes.contentChanged, newContent)\n\n    expect(zoom.scale).toBe(1)\n    expect(newContent.style['transform-origin' as any]).toBe('0 0')\n  })\n\n  it('should prevent initial scroll when startScale not equals 1', () => {\n    const { wrapper } = createZoomElements()\n    scroll = new BScroll(wrapper, {\n      zoom: {\n        start: 2,\n      },\n    })\n    new Zoom(scroll)\n    const ret = scroll.hooks.trigger(\n      scroll.hooks.eventTypes.beforeInitialScrollTo\n    )\n\n    expect(ret).toBeTruthy()\n  })\n\n  it('should calculate right size when scrollBehavior triggered beforeComputeBoundary hook', () => {\n    const zoom = new Zoom(scroll)\n    zoom.scale = 1.2\n    const scrollBehaviorX = scroll.scroller.scrollBehaviorX\n    const scrollBehaviorY = scroll.scroller.scrollBehaviorY\n    scrollBehaviorX.hooks.trigger(\n      scrollBehaviorX.hooks.eventTypes.beforeComputeBoundary\n    )\n    scrollBehaviorY.hooks.trigger(\n      scrollBehaviorY.hooks.eventTypes.beforeComputeBoundary\n    )\n\n    expect(scrollBehaviorX.contentSize).toBe(360)\n    expect(scrollBehaviorY.contentSize).toBe(360)\n  })\n\n  it('should dispatch scrollEnd event when two fingers make bs scroll', () => {\n    new Zoom(scroll)\n    let endScale\n\n    scroll.scroller.actions.hooks.trigger(\n      scroll.scroller.actions.hooks.eventTypes.start,\n      {\n        touches: [\n          {\n            pageX: 1,\n            pageY: 1,\n          },\n          {\n            pageX: 2,\n            pageY: 2,\n          },\n        ],\n      }\n    )\n\n    scroll.on(scroll.eventTypes.zoomEnd, ({ scale }: { scale: number }) => {\n      endScale = scale\n    })\n\n    scroll.scroller.hooks.trigger(scroll.scroller.hooks.eventTypes.scrollEnd)\n\n    expect(endScale).toBe(1)\n  })\n})\n"
  },
  {
    "path": "packages/zoom/src/index.ts",
    "content": "import BScroll, { Behavior, TranslaterPoint } from '@better-scroll/core'\nimport propertiesConfig from './propertiesConfig'\nimport {\n  getDistance,\n  ease,\n  between,\n  offsetToBody,\n  getRect,\n  style,\n  EventEmitter,\n  extend,\n  getNow,\n  requestAnimationFrame,\n  cancelAnimationFrame,\n} from '@better-scroll/shared-utils'\n\nexport type ZoomOptions = Partial<ZoomConfig> | true\n\nexport interface ZoomConfig {\n  start: number\n  min: number\n  max: number\n  initialOrigin: [OriginX, OriginY]\n  minimalZoomDistance: number\n  bounceTime: number\n}\n\ntype OriginX = number | 'left' | 'right' | 'center'\ntype OriginY = number | 'top' | 'bottom' | 'center'\n\ndeclare module '@better-scroll/core' {\n  interface CustomOptions {\n    zoom?: ZoomOptions\n  }\n  interface CustomAPI {\n    zoom: PluginAPI\n  }\n}\ninterface PluginAPI {\n  zoomTo(scale: number, x: OriginX, y: OriginY, bounceTime?: number): void\n}\n\ninterface Point {\n  x: number\n  y: number\n  baseScale: number\n}\n\ninterface ResolveFormula {\n  left(): number\n  top(): number\n  right(): number\n  bottom(): number\n  center(index: number): number\n}\n\nconst TWO_FINGERS = 2\nconst RAW_SCALE = 1\nexport default class Zoom implements PluginAPI {\n  static pluginName = 'zoom'\n  origin: Point\n  scale: number = RAW_SCALE\n  zoomOpt: ZoomConfig\n  numberOfFingers: number\n  private zoomed: boolean\n  private startDistance: number\n  private startScale: number\n  private wrapper: HTMLElement\n  private prevScale: number = 1\n  private hooksFn: Array<[EventEmitter, string, Function]>\n  constructor(public scroll: BScroll) {\n    this.init()\n  }\n\n  init() {\n    this.handleBScroll()\n\n    this.handleOptions()\n\n    this.handleHooks()\n\n    this.tryInitialZoomTo(this.zoomOpt)\n  }\n\n  zoomTo(scale: number, x: OriginX, y: OriginY, bounceTime?: number) {\n    const { originX, originY } = this.resolveOrigin(x, y)\n    const origin: Point = {\n      x: originX,\n      y: originY,\n      baseScale: this.scale,\n    }\n    this._doZoomTo(scale, origin, bounceTime, true)\n  }\n\n  private handleBScroll() {\n    this.scroll.proxy(propertiesConfig)\n    this.scroll.registerType([\n      'beforeZoomStart',\n      'zoomStart',\n      'zooming',\n      'zoomEnd',\n    ])\n  }\n\n  private handleOptions() {\n    const userOptions = (this.scroll.options.zoom === true\n      ? {}\n      : this.scroll.options.zoom) as Partial<ZoomConfig>\n    const defaultOptions: ZoomConfig = {\n      start: 1,\n      min: 1,\n      max: 4,\n      initialOrigin: [0, 0],\n      minimalZoomDistance: 5,\n      bounceTime: 800, // ms\n    }\n    this.zoomOpt = extend(defaultOptions, userOptions)\n  }\n\n  private handleHooks() {\n    const scroll = this.scroll\n    const scroller = this.scroll.scroller\n    this.wrapper = this.scroll.scroller.wrapper\n\n    this.setTransformOrigin(this.scroll.scroller.content)\n\n    const scrollBehaviorX = scroller.scrollBehaviorX\n    const scrollBehaviorY = scroller.scrollBehaviorY\n\n    this.hooksFn = []\n\n    // BScroll\n    this.registerHooks(\n      scroll.hooks,\n      scroll.hooks.eventTypes.contentChanged,\n      (content: HTMLElement) => {\n        this.setTransformOrigin(content)\n        this.scale = RAW_SCALE\n        this.tryInitialZoomTo(this.zoomOpt)\n      }\n    )\n    this.registerHooks(\n      scroll.hooks,\n      scroll.hooks.eventTypes.beforeInitialScrollTo,\n      () => {\n        // if perform a zoom action, we should prevent initial scroll when initialised\n        if (this.zoomOpt.start !== RAW_SCALE) {\n          return true\n        }\n      }\n    )\n\n    // enlarge boundary\n    this.registerHooks(\n      scrollBehaviorX.hooks,\n      scrollBehaviorX.hooks.eventTypes.beforeComputeBoundary,\n      () => {\n        // content may change, don't cache it's size\n        const contentSize = getRect(this.scroll.scroller.content)\n        scrollBehaviorX.contentSize = Math.floor(contentSize.width * this.scale)\n      }\n    )\n    this.registerHooks(\n      scrollBehaviorY.hooks,\n      scrollBehaviorY.hooks.eventTypes.beforeComputeBoundary,\n      () => {\n        // content may change, don't cache it's size\n        const contentSize = getRect(this.scroll.scroller.content)\n        scrollBehaviorY.contentSize = Math.floor(\n          contentSize.height * this.scale\n        )\n      }\n    )\n\n    // touch event\n    this.registerHooks(\n      scroller.actions.hooks,\n      scroller.actions.hooks.eventTypes.start,\n      (e: TouchEvent) => {\n        const numberOfFingers = (e.touches && e.touches.length) || 0\n        this.fingersOperation(numberOfFingers)\n        if (numberOfFingers === TWO_FINGERS) {\n          this.zoomStart(e)\n        }\n      }\n    )\n    this.registerHooks(\n      scroller.actions.hooks,\n      scroller.actions.hooks.eventTypes.beforeMove,\n      (e: TouchEvent) => {\n        const numberOfFingers = (e.touches && e.touches.length) || 0\n        this.fingersOperation(numberOfFingers)\n        if (numberOfFingers === TWO_FINGERS) {\n          this.zoom(e)\n          return true\n        }\n      }\n    )\n    this.registerHooks(\n      scroller.actions.hooks,\n      scroller.actions.hooks.eventTypes.beforeEnd,\n      (e: TouchEvent) => {\n        const numberOfFingers = this.fingersOperation()\n        if (numberOfFingers === TWO_FINGERS) {\n          this.zoomEnd()\n          return true\n        }\n      }\n    )\n\n    this.registerHooks(\n      scroller.translater.hooks,\n      scroller.translater.hooks.eventTypes.beforeTranslate,\n      (transformStyle: string[], point: TranslaterPoint) => {\n        const scale = point.scale ? point.scale : this.prevScale\n        this.prevScale = scale\n        transformStyle.push(`scale(${scale})`)\n      }\n    )\n\n    this.registerHooks(\n      scroller.hooks,\n      scroller.hooks.eventTypes.scrollEnd,\n      () => {\n        if (this.fingersOperation() === TWO_FINGERS) {\n          this.scroll.trigger(this.scroll.eventTypes.zoomEnd, {\n            scale: this.scale,\n          })\n        }\n      }\n    )\n\n    this.registerHooks(this.scroll.hooks, 'destroy', this.destroy)\n  }\n\n  private setTransformOrigin(content: HTMLElement) {\n    content.style[style.transformOrigin as any] = '0 0'\n  }\n\n  private tryInitialZoomTo(options: ZoomConfig) {\n    const { start, initialOrigin } = options\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    if (start !== RAW_SCALE) {\n      // Movable plugin may wanna modify minScrollPos or maxScrollPos\n      // so we force Movable to caculate them\n      this.resetBoundaries([scrollBehaviorX, scrollBehaviorY])\n\n      this.zoomTo(start, initialOrigin[0], initialOrigin[1], 0)\n    }\n  }\n\n  // getter or setter operation\n  private fingersOperation(amounts?: number): number | void {\n    if (typeof amounts === 'number') {\n      this.numberOfFingers = amounts\n    } else {\n      return this.numberOfFingers\n    }\n  }\n\n  private _doZoomTo(\n    scale: number,\n    origin: Point,\n    time: number = this.zoomOpt.bounceTime,\n    useCurrentPos = false\n  ) {\n    const { min, max } = this.zoomOpt\n    const fromScale = this.scale\n    const toScale = between(scale, min, max)\n\n    // dispatch zooming hooks\n    ;(() => {\n      if (time === 0) {\n        this.scroll.trigger(this.scroll.eventTypes.zooming, {\n          scale: toScale,\n        })\n        return\n      }\n      if (time > 0) {\n        let timer: number\n        const startTime = getNow()\n        const endTime = startTime + time\n        const scheduler = () => {\n          const now = getNow()\n          if (now >= endTime) {\n            this.scroll.trigger(this.scroll.eventTypes.zooming, {\n              scale: toScale,\n            })\n            cancelAnimationFrame(timer)\n            return\n          }\n          const ratio = ease.bounce.fn((now - startTime) / time)\n          const currentScale = ratio * (toScale - fromScale) + fromScale\n          this.scroll.trigger(this.scroll.eventTypes.zooming, {\n            scale: currentScale,\n          })\n          timer = requestAnimationFrame(scheduler)\n        }\n        // start scheduler job\n        scheduler()\n      }\n    })()\n\n    // suppose you are zooming by two fingers\n    this.fingersOperation(2)\n    this._zoomTo(toScale, fromScale, origin, time, useCurrentPos)\n  }\n\n  private _zoomTo(\n    toScale: number,\n    fromScale: number,\n    origin: Point,\n    time: number,\n    useCurrentPos = false\n  ) {\n    const ratio = toScale / origin.baseScale\n    this.setScale(toScale)\n\n    const scroller = this.scroll.scroller\n    const { scrollBehaviorX, scrollBehaviorY } = scroller\n\n    this.resetBoundaries([scrollBehaviorX, scrollBehaviorY])\n\n    // position is restrained in boundary\n    const newX = this.getNewPos(\n      origin.x,\n      ratio,\n      scrollBehaviorX,\n      true,\n      useCurrentPos\n    )\n    const newY = this.getNewPos(\n      origin.y,\n      ratio,\n      scrollBehaviorY,\n      true,\n      useCurrentPos\n    )\n\n    if (\n      scrollBehaviorX.currentPos !== Math.round(newX) ||\n      scrollBehaviorY.currentPos !== Math.round(newY) ||\n      toScale !== fromScale\n    ) {\n      scroller.scrollTo(newX, newY, time, ease.bounce, {\n        start: {\n          scale: fromScale,\n        },\n        end: {\n          scale: toScale,\n        },\n      })\n    }\n  }\n\n  private resolveOrigin(x: OriginX, y: OriginY) {\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    const resolveFormula: ResolveFormula = {\n      left() {\n        return 0\n      },\n      top() {\n        return 0\n      },\n      right() {\n        return scrollBehaviorX.contentSize\n      },\n      bottom() {\n        return scrollBehaviorY.contentSize\n      },\n      center(index: number) {\n        const baseSize =\n          index === 0\n            ? scrollBehaviorX.contentSize\n            : scrollBehaviorY.contentSize\n        return baseSize / 2\n      },\n    }\n    return {\n      originX: typeof x === 'number' ? x : resolveFormula[x](0),\n      originY: typeof y === 'number' ? y : resolveFormula[y](1),\n    }\n  }\n\n  zoomStart(e: TouchEvent) {\n    const firstFinger = e.touches[0]\n    const secondFinger = e.touches[1]\n    this.startDistance = this.getFingerDistance(e)\n    this.startScale = this.scale\n\n    let { left, top } = offsetToBody(this.wrapper)\n\n    this.origin = {\n      x:\n        Math.abs(firstFinger.pageX + secondFinger.pageX) / 2 +\n        left -\n        this.scroll.x,\n      y:\n        Math.abs(firstFinger.pageY + secondFinger.pageY) / 2 +\n        top -\n        this.scroll.y,\n      baseScale: this.startScale,\n    }\n    this.scroll.trigger(this.scroll.eventTypes.beforeZoomStart)\n  }\n\n  zoom(e: TouchEvent) {\n    const currentDistance = this.getFingerDistance(e)\n    // at least minimalZoomDistance pixels for the zoom to initiate\n    if (\n      !this.zoomed &&\n      Math.abs(currentDistance - this.startDistance) <\n        this.zoomOpt.minimalZoomDistance\n    ) {\n      return\n    }\n    // when out of boundary , perform a damping algorithm\n    const endScale = this.dampingScale(\n      (currentDistance / this.startDistance) * this.startScale\n    )\n    const ratio = endScale / this.startScale\n    this.setScale(endScale)\n\n    if (!this.zoomed) {\n      this.zoomed = true\n      this.scroll.trigger(this.scroll.eventTypes.zoomStart)\n    }\n\n    const scroller = this.scroll.scroller\n    const { scrollBehaviorX, scrollBehaviorY } = scroller\n    const x = this.getNewPos(\n      this.origin.x,\n      ratio,\n      scrollBehaviorX,\n      false,\n      false\n    )\n    const y = this.getNewPos(\n      this.origin.y,\n      ratio,\n      scrollBehaviorY,\n      false,\n      false\n    )\n\n    this.scroll.trigger(this.scroll.eventTypes.zooming, {\n      scale: this.scale,\n    })\n    scroller.translater.translate({ x, y, scale: endScale })\n  }\n\n  zoomEnd() {\n    if (!this.zoomed) return\n    // if out of boundary, do rebound!\n    if (this.shouldRebound()) {\n      this._doZoomTo(this.scale, this.origin, this.zoomOpt.bounceTime)\n      return\n    }\n    this.scroll.trigger(this.scroll.eventTypes.zoomEnd, { scale: this.scale })\n  }\n\n  private getFingerDistance(e: TouchEvent): number {\n    const firstFinger = e.touches[0]\n    const secondFinger = e.touches[1]\n    const deltaX = Math.abs(firstFinger.pageX - secondFinger.pageX)\n    const deltaY = Math.abs(firstFinger.pageY - secondFinger.pageY)\n\n    return getDistance(deltaX, deltaY)\n  }\n\n  private shouldRebound(): boolean {\n    const { min, max } = this.zoomOpt\n    const currentScale = this.scale\n    // scale exceeded!\n    if (currentScale !== between(currentScale, min, max)) {\n      return true\n    }\n\n    const { scrollBehaviorX, scrollBehaviorY } = this.scroll.scroller\n    // enlarge boundaries manually when zoom is end\n    this.resetBoundaries([scrollBehaviorX, scrollBehaviorY])\n    const { inBoundary: xInBoundary } = scrollBehaviorX.checkInBoundary()\n    const { inBoundary: yInBoundary } = scrollBehaviorX.checkInBoundary()\n    return !(xInBoundary && yInBoundary)\n  }\n\n  private dampingScale(scale: number) {\n    const { min, max } = this.zoomOpt\n\n    if (scale < min) {\n      scale = 0.5 * min * Math.pow(2.0, scale / min)\n    } else if (scale > max) {\n      scale = 2.0 * max * Math.pow(0.5, max / scale)\n    }\n    return scale\n  }\n\n  private setScale(scale: number) {\n    this.scale = scale\n  }\n\n  private resetBoundaries(scrollBehaviorPairs: [Behavior, Behavior]) {\n    scrollBehaviorPairs.forEach((behavior) => behavior.computeBoundary())\n  }\n\n  private getNewPos(\n    origin: number,\n    lastScale: number,\n    scrollBehavior: Behavior,\n    shouldInBoundary?: boolean,\n    useCurrentPos = false\n  ) {\n    let newPos =\n      origin -\n      origin * lastScale +\n      (useCurrentPos ? scrollBehavior.currentPos : scrollBehavior.startPos)\n    if (shouldInBoundary) {\n      newPos = between(\n        newPos,\n        scrollBehavior.maxScrollPos,\n        scrollBehavior.minScrollPos\n      )\n    }\n    // maxScrollPos or minScrollPos maybe a negative or positive digital\n    return newPos > 0 ? Math.floor(newPos) : Math.ceil(newPos)\n  }\n\n  private registerHooks(hooks: EventEmitter, name: string, handler: Function) {\n    hooks.on(name, handler, this)\n    this.hooksFn.push([hooks, name, handler])\n  }\n\n  destroy() {\n    this.hooksFn.forEach((item) => {\n      const hooks = item[0]\n      const hooksName = item[1]\n      const handlerFn = item[2]\n      hooks.off(hooksName, handlerFn)\n    })\n    this.hooksFn.length = 0\n  }\n}\n"
  },
  {
    "path": "packages/zoom/src/propertiesConfig.ts",
    "content": "const sourcePrefix = 'plugins.zoom'\nconst propertiesMap = [\n  {\n    key: 'zoomTo',\n    name: 'zoomTo'\n  }\n]\nexport default propertiesMap.map(item => {\n  return {\n    key: item.key,\n    sourceKey: `${sourcePrefix}.${item.name}`\n  }\n})\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: [\n    require('autoprefixer')({\n      browsers: require('./package.json').browserslist\n    })\n  ]\n}\n"
  },
  {
    "path": "scripts/build.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst inquirer = require('inquirer')\nconst rollup = require('rollup')\nconst chalk = require('chalk')\nconst zlib = require('zlib')\nconst rimraf = require('rimraf')\nconst typescript = require('rollup-plugin-typescript2')\nconst uglify = require('rollup-plugin-uglify').uglify\nconst execa = require('execa')\nconst ora = require('ora')\nconst spinner = ora({\n  prefixText: `${chalk.green('\\n[building tasks]')}`\n})\n\nfunction getPackagesName () {\n  let ret\n  let all = fs.readdirSync(resolve('packages'))\n  // drop hidden file whose name is startWidth '.'\n  // drop packages which would not be published(eg: examples and docs)\n  ret = all\n        .filter(name => {\n          const isHiddenFile = /^\\./g.test(name)\n          return !isHiddenFile\n        }).filter(name => {\n          const isPrivatePackages = require(resolve(`packages/${name}/package.json`)).private\n          return !isPrivatePackages\n        })\n\n  return ret\n}\n\nfunction cleanPackagesOldDist(packagesName) {\n  packagesName.forEach(name => {\n    const distPath = resolve(`packages/${name}/dist`)\n    const typePath = resolve(`packages/${name}/dist/types`)\n\n    if (fs.existsSync(distPath)) {\n      rimraf.sync(distPath)\n    }\n\n    fs.mkdirSync(distPath)\n    fs.mkdirSync(typePath)\n  })\n}\n\nfunction resolve(p) {\n  return path.resolve(__dirname, '../', p)\n}\n\nfunction PascalCase(str){\n  const re=/-(\\w)/g;\n  const newStr = str.replace(re, function (match, group1){\n      return group1.toUpperCase();\n  })\n  return newStr.charAt(0).toUpperCase() + newStr.slice(1);\n}\n\nconst generateBanner = (packageName) => {\n  let ret =\n  '/*!\\n' +\n  ' * better-scroll / ' + packageName + '\\n' +\n  ' * (c) 2016-' + new Date().getFullYear() + ' ustbhuangyi\\n' +\n  ' * Released under the MIT License.\\n' +\n  ' */'\n  return ret\n}\n\n\n\nconst buildType = [\n  {\n    format: 'umd',\n    ext: '.js'\n  },\n  {\n    format: 'umd',\n    ext: '.min.js'\n  },\n  {\n    format: 'es',\n    ext: '.esm.js'\n  }\n]\n\nfunction generateBuildConfigs(packagesName) {\n  const result = []\n  packagesName.forEach(name => {\n    buildType.forEach((type) => {\n      let config = {\n        input: resolve(`packages/${name}/src/index.ts`),\n        output: {\n          file: resolve(`packages/${name}/dist/${name}${type.ext}`),\n          name: PascalCase(name),\n          format: type.format,\n          banner: generateBanner(name)\n        },\n        plugins: generateBuildPluginsConfigs(type.ext.indexOf('min')>-1, name)\n      }\n      // rename\n      if (name === 'core' && config.output.format !== 'es') {\n        config.output.name = 'BScroll'\n        /** Disable warning for default imports */\n        config.output.exports = 'named'\n        // it seems the umd bundle can not satisfies our demand\n        config.output.footer = 'if(typeof window !== \"undefined\" && window.BScroll) { \\n' +\n                              '  window.BScroll = window.BScroll.default;\\n}'\n      }\n      // rollup will valiate config properties of config own and output a warning.\n      // put packageName in prototype to ignore warning.\n      Object.defineProperties(config, {\n        'packageName': {\n          value: name\n        },\n        'ext': {\n          value: type.ext\n        }\n      })\n      result.push(config)\n    })\n  })\n  return result\n}\nfunction generateBuildPluginsConfigs(isMin) {\n  const tsConfig = {\n    verbosity: -1,\n    tsconfig: path.resolve(__dirname, '../tsconfig.json'),\n  }\n  const plugins = []\n    if (isMin) {\n      plugins.push(uglify())\n    }\n  plugins.push(typescript(tsConfig))\n  return plugins\n}\n\nfunction build(builds) {\n  let built = 0\n  const total = builds.length\n  const next = () => {\n    buildEntry(builds[built], built + 1, () => {\n      builds[built-1] = null\n      built++\n      if (built < total) {\n        next()\n      }\n    })\n  }\n  next()\n}\n\nfunction buildEntry(config, curIndex, next) {\n  const isProd = /min\\.js$/.test(config.output.file)\n\n  spinner.start(`${config.packageName}${config.ext} is buiding now. \\n`)\n\n  rollup.rollup(config).then((bundle) => {\n    bundle.write(config.output).then(({ output }) => {\n      const code = output[0].code\n\n      spinner.succeed(`${config.packageName}${config.ext} building has ended.`)\n\n      function report(extra) {\n        console.log(chalk.magenta(path.relative(process.cwd(), config.output.file)) + ' ' + getSize(code) + (extra || ''))\n        next()\n      }\n      if (isProd) {\n        zlib.gzip(code, (err, zipped) => {\n          if (err) return reject(err)\n          let words =  `(gzipped: ${chalk.magenta(getSize(zipped))})`\n          report(words)\n        })\n      } else {\n        report()\n      }\n\n      // since we need bundle code for three types\n      // just generate .d.ts only once\n      if (curIndex % 3 === 0) {\n        copyDTSFiles(config.packageName)\n      }\n    })\n  }).catch((e) => {\n    spinner.fail('buiding is failed')\n    console.log(e)\n  })\n}\n\nfunction copyDTSFiles (packageName) {\n  console.log(chalk.cyan('> start copying .d.ts file to dist dir of packages own.'))\n  const sourceDir = resolve(`packages/${packageName}/dist/packages/${packageName}/src/*`)\n  const targetDir = resolve(`packages/${packageName}/dist/types/`)\n  execa.commandSync(`mv ${sourceDir} ${targetDir}`, { shell: true })\n  console.log(chalk.cyan('> copy job is done.'))\n  rimraf.sync(resolve(`packages/${packageName}/dist/packages`))\n  rimraf.sync(resolve(`packages/${packageName}/dist/node_modules`))\n}\n\nfunction getSize(code) {\n  return (code.length / 1024).toFixed(2) + 'kb'\n}\n\nconst getAnswersFromInquirer = async (packagesName) => {\n  const question = {\n    type: 'checkbox',\n    name: 'packages',\n    scroll: false,\n    message: 'Select build repo(Support Multiple selection)',\n    choices: packagesName.map(name => ({\n      value: name,\n      name\n    }))\n  }\n  let { packages } = await inquirer.prompt(question)\n  // make no choice\n  if (!packages.length) {\n    console.log(chalk.yellow(`\n      It seems that you did't make a choice.\n\n      Please try it again.\n    `))\n    return\n  }\n\n  // chose 'all' option\n  if (packages.some(package => package === 'all')) {\n    packages = getPackagesName()\n  }\n  const { yes } = await inquirer.prompt([{\n    name: 'yes',\n    message: `Confirm build ${packages.join(' and ')} packages?`,\n    type: 'list',\n    choices: ['Y', 'N']\n  }])\n\n  if (yes === 'N') {\n    console.log(chalk.yellow('[release] cancelled.'))\n    return\n  }\n\n  return packages\n}\nconst buildBootstrap = async () => {\n  const packagesName = getPackagesName()\n  // provide 'all' option\n  packagesName.unshift('all')\n\n  const answers = await getAnswersFromInquirer(packagesName)\n\n  if (!answers) return\n\n  cleanPackagesOldDist(answers)\n\n  const buildConfigs = generateBuildConfigs(answers)\n\n  build(buildConfigs)\n\n}\nbuildBootstrap().catch(err => {\n  console.error(err)\n  process.exit(1)\n})\n"
  },
  {
    "path": "scripts/checkYarn.js",
    "content": "if (!/yarn\\.js$/.test(process.env.npm_execpath || '')) {\n  console.warn(\n    '\\u001b[33mThis repository requires Yarn 1.x for scripts to work properly.\\u001b[39m\\n'\n  )\n  process.exit(1)\n}\n"
  },
  {
    "path": "scripts/release.js",
    "content": "const execa = require('execa')\nconst semver = require('semver')\nconst inquirer = require('inquirer')\nconst chalk = require('chalk')\n\nconst curVersion = require('../lerna.json').version\n\nconst release = async () => {\n  console.log(chalk.yellow(`Current version: ${curVersion}`))\n\n  const bumps = ['patch', 'minor', 'major', 'prerelease-alpha', 'prerelease-beta', 'premajor']\n  const versions = {}\n  bumps.forEach(b => {\n    const args = b.split('-')\n    versions[b] = semver.inc(curVersion, ...args)\n  })\n  const bumpChoices = bumps.map(b => ({ name: `${b} (${versions[b]})`, value: b }))\n\n  function getVersion (answers) {\n    return answers.customVersion || versions[answers.bump]\n  }\n\n  function getNpmTags (version) {\n    if (isPreRelease(version)) {\n      return ['next']\n    }\n    return ['latest', 'next']\n  }\n\n  function isPreRelease (version) {\n    return !!semver.prerelease(version)\n  }\n\n  const { bump, customVersion, npmTag } = await inquirer.prompt([\n    {\n      name: 'bump',\n      message: 'Select release type:',\n      type: 'list',\n      choices: [\n        ...bumpChoices,\n        { name: 'custom', value: 'custom' }\n      ]\n    },\n    {\n      name: 'customVersion',\n      message: 'Input version:',\n      type: 'input',\n      when: answers => answers.bump === 'custom'\n    },\n    {\n      name: 'npmTag',\n      message: 'Input npm tag:',\n      type: 'list',\n      choices: answers => getNpmTags(getVersion(answers))\n    }\n  ])\n\n  const version = customVersion || versions[bump]\n\n  const { yes } = await inquirer.prompt([{\n    name: 'yes',\n    message: `Confirm releasing ${version} (${npmTag})?`,\n    type: 'list',\n    choices: ['Y', 'N']\n  }])\n\n  if (yes === 'N') {\n    console.log(chalk.red('[release] cancelled.'))\n    return\n  }\n\n  const releaseArguments = [\n    'publish',\n    version,\n    '--force-publish',\n    '*',\n    '--npm-tag',\n    npmTag\n  ]\n\n  console.log(chalk.grey(`lerna ${releaseArguments.join(' ')}`))\n\n  await execa(require.resolve('lerna/cli'), releaseArguments, { stdio: 'inherit' })\n\n  // it seems that sometimes 'gitHead' property in packages/**/package.json will change\n  // but sometimes it won't, at this condition. work tree is clean, 'git commit ' will cause en error\n  // so put it in try/catch, because we want to sync dev from master\n  try {\n    await execa('git', ['add', '-A'], { stdio: 'inherit' })\n    await execa('git', ['commit', '-m', `chore: ${version} published`], { stdio: 'inherit' })\n    await execa('git', ['push', 'origin', `master`], { stdio: 'inherit' })\n  } catch (error) {}\n\n  // sync dev from master\n  await execa('git', ['checkout', 'dev'], { stdio: 'inherit' })\n  await execa('git', ['rebase', 'master'], { stdio: 'inherit' })\n  await execa('git', ['push', 'origin', 'dev'], { stdio: 'inherit' })\n  await execa('git', ['checkout', 'master'], { stdio: 'inherit' })\n}\n\nrelease().catch(err => {\n  console.error(err)\n  process.exit(1)\n})\n"
  },
  {
    "path": "test-dts/core.test-d.ts",
    "content": "import {\n  BScroll,\n  expectFuncArguments,\n  expectFuncReturnValue,\n  Options\n} from './index'\nimport { EaseItem } from '@better-scroll/shared-utils/src'\n\ndescribe('core api parameter type should be correct', () => {\n  type ExtraTransform = { start: object; end: object }\n  expectFuncArguments<[], BScroll['refresh']>()\n  expectFuncArguments<\n    [number, number, number?, EaseItem?, ExtraTransform?],\n    BScroll['scrollTo']\n  >()\n  expectFuncArguments<\n    [number, number, number?, EaseItem?],\n    BScroll['scrollBy']\n  >()\n  expectFuncArguments<\n    [\n      string | HTMLElement,\n      number,\n      number | boolean,\n      number | boolean,\n      EaseItem?\n    ],\n    BScroll['scrollToElement']\n  >()\n  expectFuncArguments<[], BScroll['stop']>()\n  expectFuncArguments<[], BScroll['enable']>()\n  expectFuncArguments<[], BScroll['disable']>()\n  expectFuncArguments<[], BScroll['destroy']>()\n  // Events API\n  expectFuncArguments<[string, Function, Object?], BScroll['on']>()\n  expectFuncArguments<[string, Function, Object?], BScroll['once']>()\n  expectFuncArguments<[string?, Function?], BScroll['off']>()\n  expectFuncReturnValue<BScroll<Options>, BScroll['on']>()\n  expectFuncReturnValue<BScroll<Options>, BScroll['once']>()\n  expectFuncReturnValue<BScroll<Options> | undefined, BScroll['off']>()\n})\n"
  },
  {
    "path": "test-dts/index.d.ts",
    "content": "import BScroll from '@better-scroll/core'\nimport Zoom from '@better-scroll/zoom'\nimport Wheel from '@better-scroll/wheel'\nimport Slide from '@better-scroll/slide'\nimport ScrollBar from '@better-scroll/scroll-bar'\nimport PullUp from '@better-scroll/pull-up'\nimport PullDown from '@better-scroll/pull-down'\nimport ObserveDom from '@better-scroll/observe-dom'\nimport NestedScroll from '@better-scroll/nested-scroll'\nimport MouseWheel from '@better-scroll/mouse-wheel'\nimport Infinity from '@better-scroll/infinity'\nimport Movable from '@better-scroll/movable'\nimport { IfEquals } from './util'\n\nexport * from '@better-scroll/core'\nexport * from '@better-scroll/zoom'\nexport * from '@better-scroll/wheel'\nexport * from '@better-scroll/slide'\nexport * from '@better-scroll/scroll-bar'\nexport * from '@better-scroll/pull-up'\nexport * from '@better-scroll/pull-down'\nexport * from '@better-scroll/observe-dom'\nexport * from '@better-scroll/nested-scroll'\nexport * from '@better-scroll/mouse-wheel'\nexport * from '@better-scroll/infinity'\nexport * from '@better-scroll/movable'\n\nexport type ArgumentsCheck<\n  T extends any[],\n  U extends (...args: any[]) => any\n> = (\n  ...args: any[]\n) => U extends (...args: infer P) => any\n  ? IfEquals<T, P, ReturnType<U>, T>\n  : never\nexport type ReturnValueCheck<T, U extends (...args: any[]) => any> = (\n  ...args: any[]\n) => U extends (...args: any[]) => infer P ? IfEquals<T, P, P, T> : never\nexport declare function expectType<T, T1 extends IfEquals<T, T1, T, T>>(): void\nexport declare function expectError<T>(value: T): void\nexport declare function expectAssignable<T1 extends T, T>(): void\nexport declare function expectFuncArguments<\n  T extends any[],\n  T1 extends ArgumentsCheck<T, T1>\n>(): void\nexport declare function expectFuncReturnValue<\n  T,\n  T1 extends ReturnValueCheck<T, T1>\n>(): void\n\nexport {\n  BScroll,\n  Zoom,\n  Wheel,\n  Slide,\n  ScrollBar,\n  PullUp,\n  PullDown,\n  ObserveDom,\n  NestedScroll,\n  MouseWheel,\n  Infinity,\n  Movable\n}\n"
  },
  {
    "path": "test-dts/plugin.test-d.ts",
    "content": "import {\n  expectType,\n  expectError,\n  expectFuncArguments,\n  expectFuncReturnValue,\n  BScroll,\n  createBScroll,\n  Zoom,\n  Wheel,\n  Slide,\n  ScrollBar,\n  PullUp,\n  PullDown,\n  ObserveDom,\n  NestedScroll,\n  MouseWheel,\n  Movable,\n  ZoomConfig,\n  WheelConfig,\n  SlideConfig,\n  MouseWheelOptions,\n  ScrollbarOptions,\n  ScrollbarConfig,\n  PullUpLoadOptions,\n  PullUpLoadConfig,\n  PullDownRefreshOptions,\n  PullDownRefreshConfig,\n  NestedScrollOptions,\n} from './index'\nimport {\n  DeepNonNullable,\n  FilterType,\n  FilterUndef,\n  FilterBoolean,\n  ExcludeTrue,\n} from './util'\nimport { EaseItem } from '@better-scroll/shared-utils/src'\ndescribe('BScroll.use should be used normally', () => {\n  // @ts-expect-error\n  expectError(BScroll.use())\n  // @ts-expect-error\n  expectError(BScroll.use({}))\n  // @ts-expect-error\n  expectError(BScroll.use({ pluginName: 'pluginName' }))\n  // @ts-expect-error\n  expectError(BScroll.use(function () {}))\n  // @ts-expect-error\n  expectError(BScroll.use(class Plugin {}))\n  expectError(\n    BScroll.use(\n      class Plugin {\n        static pluginName = 'pluginName'\n      }\n    )\n  )\n})\n\ndescribe('zoom plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(Zoom)\n  const bscroll = createBScroll('', {\n    zoom: {\n      max: 1,\n      min: 1,\n      start: 1,\n      initialOrigin: ['left', 'top'],\n      minimalZoomDistance: 5,\n      bounceTime: 800,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<Partial<ZoomConfig> | true, BSOptions['zoom']>()\n  expectType<number, FilterUndef<ExcludeTrue<BSOptions['zoom']>['max']>>()\n  expectType<number, FilterUndef<ExcludeTrue<BSOptions['zoom']>['min']>>()\n  expectType<number, FilterUndef<ExcludeTrue<BSOptions['zoom']>['start']>>()\n  expectType<\n    [OriginX, OriginY],\n    FilterUndef<ExcludeTrue<BSOptions['zoom']>['initialOrigin']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['zoom']>['minimalZoomDistance']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['zoom']>['bounceTime']>\n  >()\n  // API\n  type ZoomToAPI = typeof bscroll.zoomTo\n  type OriginX = number | 'left' | 'right' | 'center'\n  type OriginY = number | 'top' | 'bottom' | 'center'\n  expectFuncArguments<[number, OriginX, OriginY, number?], ZoomToAPI>()\n})\n\ndescribe('whell plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(Wheel)\n  const bscroll = new BScroll('', {\n    wheel: {\n      selectedIndex: 1,\n      rotate: 1,\n      adjustTime: 1,\n      wheelWrapperClass: 'wheelWrapperClass',\n      wheelItemClass: 'wheelItemClass',\n      wheelDisabledItemClass: 'wheelDisabledItemClass',\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<Partial<WheelConfig> | true, BSOptions['wheel']>()\n  expectType<number, FilterUndef<ExcludeTrue<BSOptions['wheel']>['rotate']>>()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['wheel']>['adjustTime']>\n  >()\n  expectType<\n    string,\n    FilterUndef<ExcludeTrue<BSOptions['wheel']>['wheelWrapperClass']>\n  >()\n  expectType<\n    string,\n    FilterUndef<ExcludeTrue<BSOptions['wheel']>['wheelItemClass']>\n  >()\n  expectType<\n    string,\n    FilterUndef<ExcludeTrue<BSOptions['wheel']>['wheelDisabledItemClass']>\n  >()\n  // API\n  type WhellToAPI = typeof bscroll.wheelTo\n  type GetSelectedIndexAPI = typeof bscroll.getSelectedIndex\n  expectFuncArguments<[number?, number?, EaseItem?], WhellToAPI>()\n  expectFuncReturnValue<number, GetSelectedIndexAPI>()\n})\n\ndescribe('slider plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(Slide)\n  const bscroll = createBScroll('', {\n    slide: {\n      loop: true,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  type EaseType = {\n    style: string\n    fn: (t: number) => number\n  }\n  expectType<Partial<SlideConfig>, FilterBoolean<BSOptions['slide']>>()\n  expectType<boolean, FilterType<BSOptions['slide'], Partial<SlideConfig>>>()\n  expectType<boolean, FilterUndef<FilterBoolean<BSOptions['slide']>['loop']>>()\n  expectType<\n    number,\n    FilterUndef<FilterBoolean<BSOptions['slide']>['threshold']>\n  >()\n  expectType<number, FilterUndef<FilterBoolean<BSOptions['slide']>['speed']>>()\n  expectType<\n    EaseType,\n    FilterUndef<FilterBoolean<BSOptions['slide']>['easing']>\n  >()\n  expectType<\n    boolean,\n    FilterUndef<FilterBoolean<BSOptions['slide']>['listenFlick']>\n  >()\n  // API\n  type BS = typeof bscroll\n  type Page = {\n    pageX: number\n    pageY: number\n  }\n  expectFuncArguments<[number?, EaseItem?], BS['next']>()\n  expectFuncArguments<[number?, EaseItem?], BS['prev']>()\n  expectFuncArguments<[number, number, number?, EaseItem?], BS['goToPage']>()\n  expectFuncReturnValue<Page, BS['getCurrentPage']>()\n})\n\ndescribe('scrollBar plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(ScrollBar)\n  const bscroll = new BScroll('', {\n    scrollbar: {\n      fade: true,\n      interactive: true,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<ScrollbarOptions, BSOptions['scrollbar']>()\n  expectType<\n    boolean,\n    FilterType<BSOptions['scrollbar'], Partial<ScrollbarConfig>>\n  >()\n  expectType<Partial<ScrollbarConfig>, FilterBoolean<BSOptions['scrollbar']>>()\n  expectType<\n    boolean,\n    FilterUndef<FilterBoolean<BSOptions['scrollbar']>['fade']>\n  >()\n  expectType<\n    boolean,\n    FilterUndef<FilterBoolean<BSOptions['scrollbar']>['interactive']>\n  >()\n  // API\n})\n\ndescribe('pullUp plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(PullUp)\n  const bscroll = new BScroll('', {\n    pullUpLoad: true,\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<PullUpLoadOptions, BSOptions['pullUpLoad']>()\n  expectType<\n    boolean,\n    FilterType<BSOptions['pullUpLoad'], Partial<PullUpLoadConfig>>\n  >()\n  expectType<\n    number,\n    FilterUndef<FilterBoolean<BSOptions['pullUpLoad']>['threshold']>\n  >()\n  // API\n  type BS = typeof bscroll\n  expectFuncArguments<[], BS['finishPullUp']>()\n  expectFuncArguments<[(true | Partial<PullUpLoadConfig>)?], BS['openPullUp']>()\n  expectFuncArguments<[], BS['closePullUp']>()\n})\n\ndescribe('pullDown plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(PullDown)\n  const bscroll = new BScroll('', {\n    pullDownRefresh: {\n      threshold: 1,\n      stop: 1,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<PullDownRefreshOptions | true, BSOptions['pullDownRefresh']>()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['pullDownRefresh']>['threshold']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['pullDownRefresh']>['stop']>\n  >()\n  // API\n  type BS = typeof bscroll\n  expectFuncArguments<[], BS['finishPullDown']>()\n  expectFuncArguments<\n    [(true | Partial<PullDownRefreshConfig>)?],\n    BS['openPullDown']\n  >()\n  expectFuncArguments<[], BS['closePullDown']>()\n  expectFuncArguments<[], BS['autoPullDownRefresh']>()\n})\n\ndescribe('observeDom plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(ObserveDom)\n  const bscroll = new BScroll('', {\n    observeDOM: true,\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<true, BSOptions['observeDOM']>()\n})\n\ndescribe('nestedScroll plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(NestedScroll)\n  const bscroll = new BScroll('', {\n    nestedScroll: {\n      groupId: 1,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<\n    string | number,\n    ExcludeTrue<BSOptions['nestedScroll']>['groupId']\n  >()\n})\n\ndescribe('mouseWheel plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(MouseWheel)\n  const bscroll = new BScroll('', {\n    mouseWheel: {\n      speed: 1,\n      invert: true,\n      easeTime: 1,\n      discreteTime: 1,\n      throttleTime: 1,\n      dampingFactor: 0.1,\n    },\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<Partial<MouseWheelOptions> | true, BSOptions['mouseWheel']>()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['speed']>\n  >()\n  expectType<\n    boolean,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['invert']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['easeTime']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['discreteTime']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['throttleTime']>\n  >()\n  expectType<\n    number,\n    FilterUndef<ExcludeTrue<BSOptions['mouseWheel']>['dampingFactor']>\n  >()\n})\n\ndescribe('movable plugin options and api type shoule be inferred correctly', () => {\n  BScroll.use(Movable)\n  const bscroll = new BScroll('', {\n    movable: true,\n  })\n  // Options\n  type BSOptions = DeepNonNullable<typeof bscroll.options>\n  expectType<true, FilterUndef<BSOptions['movable']>>()\n})\n"
  },
  {
    "path": "test-dts/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"declaration\": true\n  },\n  \"include\": [\"../test-dts\"],\n  \"exclude\": [\n    \"../tests\",\n    \"../packages/*/src/__tests__\",\n    \"../packages/*/src/__mocks__\",\n    \"../packages/*/src\"\n  ]\n}\n"
  },
  {
    "path": "test-dts/util.d.ts",
    "content": "type NonUndefined<T> = T extends undefined ? never : T\nexport type DeepNonNullable<T> = {\n  [P in keyof T]-?: T[P] extends object\n    ? DeepNonNullable<NonUndefined<T[P]>>\n    : NonUndefined<T[P]>\n}\n\nexport type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X\n  ? 1\n  : 2) extends <T>() => T extends Y ? 1 : 2\n  ? A\n  : B\n\nexport type FilterType<T, F> = T extends F ? never : T\n\nexport type FilterUndef<T> = FilterType<T, undefined>\nexport type FilterBoolean<T> = FilterType<T, boolean>\nexport type FilterNull<T> = FilterType<T, null>\nexport type FilterString<T> = FilterType<T, string>\nexport type FilterNumber<T> = FilterType<T, number>\nexport type FilterSymbol<T> = FilterType<T, symbol>\nexport type FilterArray<T> = FilterType<T, Array<any>>\nexport type FilterFunc<T> = FilterType<T, Function>\nexport type FilterObject<T> = FilterType<T, object>\n\nexport type ExcludeTrue<T> = FilterType<T, true>\n"
  },
  {
    "path": "tests/dts/index.d.ts",
    "content": ""
  },
  {
    "path": "tests/e2e/compose-plugins/compose-plugins.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendsTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(1000000)\n\ndescribe('compose plugins', () => {\n  let page = (global as any).page as Page\n  extendsTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/compose/')\n  })\n  it('should display 4 items at least', async () => {\n    const itemsCounts = await page.$$eval(\n      '.compose .example-item',\n      (elements) => elements.length\n    )\n    const itemsContent = await page.$$eval(\n      '.compose .example-item',\n      (elements) => {\n        return elements.map((el) => el.textContent)\n      }\n    )\n    expect(itemsContent).toEqual([\n      'pullup-pulldown',\n      'pullup-pulldown-slide',\n      'pullup-pulldown-outnested',\n      'slide-nested',\n    ])\n    await expect(itemsCounts).toBeGreaterThanOrEqual(4)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/compose-plugins/pullup-pulldown-nested.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendsTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(1000000)\n\ndescribe('Compose/pullup-pulldown-nested', () => {\n  let page = (global as any).page as Page\n  extendsTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/compose/pullup-pulldown-outnested')\n  })\n\n  it('should trigger outer scroll pullingdown when BS reached the top', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 150,\n      xDistance: 0,\n      yDistance: 300,\n      gestureSourceType: 'touch',\n    })\n\n    const { isShowPullDownTxt, isShowLoading } = await page.$$eval(\n      '.pulldown-wrapper',\n      (elements) => {\n        const isShowPullDownTxt =\n          window.getComputedStyle(elements[0].children[0]).display === 'block'\n        const isShowLoading =\n          window.getComputedStyle(elements[0].children[1].children[0])\n            .display === 'block'\n        return {\n          isShowPullDownTxt,\n          isShowLoading,\n        }\n      }\n    )\n    expect(isShowPullDownTxt).toEqual(false)\n    expect(isShowLoading).toEqual(true)\n  })\n\n  it('should trigger outer scroll pullingup when BS reached the bottom', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 300,\n      xDistance: 0,\n      yDistance: -500,\n      speed: 1500,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(4000)\n    const itemsCounts = await page.$$eval(\n      '.outer-list-item2',\n      (element) => element.length\n    )\n    await expect(itemsCounts).toBeGreaterThanOrEqual(16)\n  })\n\n  it('the inner scroll should scroll normally', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 200,\n      xDistance: 0,\n      yDistance: -500,\n      speed: 1500,\n      gestureSourceType: 'touch',\n    })\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const y = getTranslate(transformText, 'y')\n\n    expect(y).toBe(-814)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/compose-plugins/pullup-pulldown-slide.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendsTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(1000000)\n\ndescribe('Compose/pullup-pulldown-slide', () => {\n  let page = (global as any).page as Page\n  extendsTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/compose/pullup-pulldown-slide')\n  })\n\n  it('should trigger pullingdown when BS reached the top', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 150,\n      xDistance: 0,\n      yDistance: 300,\n      gestureSourceType: 'touch',\n    })\n    const { isShowPullDownTxt, isShowLoading } = await page.$$eval(\n      '.pulldown-wrapper',\n      (elements) => {\n        const isShowPullDownTxt =\n          window.getComputedStyle(elements[0].children[0]).display === 'block'\n        const isShowLoading =\n          window.getComputedStyle(elements[0].children[1].children[0])\n            .display === 'block'\n        return {\n          isShowPullDownTxt,\n          isShowLoading,\n        }\n      }\n    )\n    expect(isShowPullDownTxt).toEqual(false)\n    expect(isShowLoading).toEqual(true)\n\n    await page.waitFor(1000)\n  })\n  it('should switch next page when BS scroll half page', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 200,\n      xDistance: 0,\n      yDistance: -50,\n      gestureSourceType: 'touch',\n    })\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval(\n      '.pullup-pulldown-slide-scroller',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n    const y = getTranslate(transformText, 'y')\n\n    expect(y).toBe(-627)\n  })\n  it('should trigger pullingup when BS reached the bottom', async () => {\n    await page.waitFor(1000)\n\n    for (let i = 0; i < 9; i++) {\n      await page.dispatchScroll({\n        x: 100,\n        y: 200,\n        xDistance: 0,\n        yDistance: -150,\n        gestureSourceType: 'touch',\n      })\n      await page.waitFor(1500)\n    }\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 200,\n      xDistance: 0,\n      yDistance: -150,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2000)\n    const itemsCounts = await page.$$eval(\n      '.pullup-pulldown-slide-item',\n      (element) => element.length\n    )\n    await expect(itemsCounts).toBeGreaterThanOrEqual(10)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/compose-plugins/pullup-pulldown.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendsTouch from '../../util/extendTouch'\n\njest.setTimeout(1000000)\n\ndescribe('Compose/pullup-pulldown', () => {\n  let page = (global as any).page as Page\n  extendsTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/compose/pullup-pulldown')\n  })\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.pullup-down-list-item',\n      element => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(30)\n  })\n\n  it('should trigger pullingdown when BS reached the top', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 150,\n      xDistance: 0,\n      yDistance: 300,\n      gestureSourceType: 'touch'\n    })\n    const { isShowPullDownTxt, isShowLoading } = await page.$$eval(\n      '.pulldown-wrapper',\n      elements => {\n        const isShowPullDownTxt =\n          window.getComputedStyle(elements[0].children[0]).display === 'block'\n        const isShowLoading =\n          window.getComputedStyle(elements[0].children[1].children[0])\n            .display === 'block'\n        return {\n          isShowPullDownTxt,\n          isShowLoading\n        }\n      }\n    )\n    expect(isShowPullDownTxt).toEqual(false)\n    expect(isShowLoading).toEqual(true)\n    await page.waitFor(1000)\n  })\n\n  it('should trigger pullingup when BS reached the bottom', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 630,\n      xDistance: 0,\n      yDistance: -1000,\n      speed: 1500,\n      gestureSourceType: 'touch'\n    })\n    await page.waitFor(4000)\n    const itemsCounts = await page.$$eval(\n      '.pullup-down-list-item',\n      element => element.length\n    )\n    await expect(itemsCounts).toBeGreaterThanOrEqual(60)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/compose-plugins/slide-nested.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendsTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(1000000)\n\ndescribe('Compose/slide-nested', () => {\n  let page = (global as any).page as Page\n  extendsTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/compose/slide-nested')\n  })\n\n  it('the outer scroll should scroll normally', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 100,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const y = getTranslate(transformText, 'y')\n    await expect(y).toBeLessThan(-30)\n  })\n\n  it('the inner scroll should scroll normally', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 300,\n      xDistance: -100,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-666)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/core/corescroll.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\n// set default timeout\njest.setTimeout(1000000)\n\ndescribe('CoreScroll', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/core/')\n  })\n\n  it('should display 4 items at least', async () => {\n    const itemsCounts = await page.$$eval(\n      '.core .example-item',\n      (element) => element.length\n    )\n    const itemsContent = await page.$$eval('.core .example-item', (element) =>\n      element.map((el) => el.textContent)\n    )\n\n    expect(itemsContent).toEqual([\n      'vertical',\n      'horizontal',\n      'dynamic-content',\n      'specified-content',\n      'freescroll',\n      'vertical rotated(v2.3.0)',\n      'horizontal rotated(v2.3.0)',\n    ])\n    expect(itemsCounts).toBeGreaterThanOrEqual(5)\n  })\n\n  it(\"should display correct items's texts\", async () => {\n    const itemsContent = await page.$$eval('.core .example-item', (element) =>\n      element.map((el) => el.textContent)\n    )\n\n    expect(itemsContent).toEqual([\n      'vertical',\n      'horizontal',\n      'dynamic-content',\n      'specified-content',\n      'freescroll',\n      'vertical rotated(v2.3.0)',\n      'horizontal rotated(v2.3.0)',\n    ])\n  })\n\n  describe('CoreScroll/vertical', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/default')\n    })\n\n    it('should render corrent DOM', async () => {\n      const wrapper = await page.$('.scroll-wrapper')\n      const content = await page.$('.scroll-content')\n\n      expect(wrapper).toBeTruthy()\n      expect(content).toBeTruthy()\n    })\n\n    it('should trigger eventListener when click wrapper DOM', async () => {\n      let mockHandler = jest.fn()\n      page.once('dialog', async (dialog) => {\n        mockHandler()\n        await dialog.dismiss()\n      })\n\n      // wait for router transition ends\n      await page.waitFor(1000)\n      await page.touchscreen.tap(100, 100)\n\n      expect(mockHandler).toHaveBeenCalled()\n    })\n\n    it('should scroll when dispatch touch', async () => {\n      await page.waitFor(1000)\n\n      await page.dispatchScroll({\n        x: 100,\n        y: 150,\n        xDistance: 0,\n        yDistance: -70,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(1000)\n\n      const transformText = await page.$eval('.scroll-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n      const y = getTranslate(transformText, 'y')\n\n      expect(y).toBeLessThan(0)\n    })\n\n    it('should dispatch scroll event', async () => {\n      let mockHandler = jest.fn()\n      page.once('console', async (message) => {\n        mockHandler()\n      })\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 100,\n        y: 150,\n        xDistance: 0,\n        yDistance: -70,\n        gestureSourceType: 'touch',\n      })\n      await page.waitFor(1000)\n      expect(mockHandler).toBeCalled()\n    })\n  })\n\n  describe('CoreScroll/horizontal', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/horizontal')\n    })\n\n    it('should render corrent DOM', async () => {\n      const wrapper = await page.$('.scroll-wrapper')\n      const container = await page.$('.horizontal-container')\n\n      expect(wrapper).toBeTruthy()\n      expect(container).toBeTruthy()\n    })\n\n    it('should scroll to right when finger moves from right to left', async () => {\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 100,\n        y: 120,\n        xDistance: -70,\n        yDistance: 0,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(1000)\n\n      const transformText = await page.$eval('.scroll-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n      const x = getTranslate(transformText, 'x')\n\n      expect(x).toBeLessThan(0)\n    })\n  })\n\n  describe('CoreScroll/freescroll', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/freescroll')\n    })\n\n    it('should scroll correctly when oblique scrolling occurred', async () => {\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 100,\n        y: 100,\n        xDistance: -70,\n        yDistance: -70,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(1000)\n\n      const transformText = await page.$eval('.scroll-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n      const y = getTranslate(transformText, 'y')\n      const x = getTranslate(transformText, 'x')\n\n      expect(x).toBeLessThan(0)\n      expect(y).toBeLessThan(0)\n    })\n  })\n\n  describe('CoreScroll/dynamicContent', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/dynamic-content')\n    })\n\n    it('should support switching content dynamically', async () => {\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 100,\n        y: 100,\n        xDistance: 0,\n        yDistance: -70,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(50)\n\n      await page.click('.btn')\n\n      await page.waitFor(100)\n\n      const itemsCounts = await page.$$eval(\n        '.scroll-content .scroll-item',\n        (element) => element.length\n      )\n\n      expect(itemsCounts).toBe(60)\n    })\n  })\n\n  describe('CoreScroll/verticalRotated', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/vertical-rotated')\n    })\n\n    it('should support vertical rotated scroll', async () => {\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 210,\n        y: 180,\n        xDistance: 70,\n        yDistance: 0,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(100)\n\n      const transformText = await page.$eval('.scroll-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const y = getTranslate(transformText, 'y')\n      expect(y).toBeLessThan(-20)\n    })\n  })\n\n  describe('CoreScroll/horizontalRotated', () => {\n    beforeAll(async () => {\n      await page.goto('http://0.0.0.0:8932/#/core/horizontal-rotated')\n    })\n\n    it('should support horizontal rotated scroll', async () => {\n      await page.waitFor(1000)\n      await page.dispatchScroll({\n        x: 180,\n        y: 100,\n        xDistance: 70,\n        yDistance: 0,\n        gestureSourceType: 'touch',\n      })\n\n      await page.waitFor(100)\n\n      const transformText = await page.$eval('.scroll-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const x = getTranslate(transformText, 'x')\n      expect(x).toBeLessThan(-20)\n    })\n  })\n})\n"
  },
  {
    "path": "tests/e2e/form/textarea.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000000)\n\ndescribe('BetterScroll in Form-Textarea', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/form/textarea')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should scroll when not manipulating texatea tag', async () => {\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 150,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch'\n    })\n\n    const content = await page.$('.textarea-scroller')\n    await page.waitFor(1000)\n    const boundingBox = await content!.boundingBox()\n    await expect(boundingBox!.y).toBeLessThan(0)\n  })\n\n  it('should not scroll when manipulating texatea tag', async () => {\n    await page.reload()\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 570,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch'\n    })\n\n    const content = await page.$('.textarea-scroller')\n    await page.waitFor(1000)\n    const boundingBox = await content!.boundingBox()\n    await expect(boundingBox!.y).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/homepage.e2e.ts",
    "content": "import { Page } from 'puppeteer'\n\njest.setTimeout(10000000)\n\ndescribe('Homepage', () => {\n  let page = (global as any).page as Page\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/')\n  })\n\n  it('should display \"BetterScroll\" text on homepage', async () => {\n    await expect(page).toMatch('BetterScroll')\n  })\n\n  it('should display seven items at least', async () => {\n    const itemsCounts = await page.$$eval(\n      '.example-item',\n      element => element.length\n    )\n    await expect(itemsCounts).toBeGreaterThanOrEqual(7)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/indicators/minimap.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Indicators-minimap', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/indicators/minimap')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should render correctly', async () => {\n    await page.waitFor(300)\n\n    const transformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const transformBScrollY = getTranslate(transformText, 'y')\n\n    const indicatorTransformText = await page.$eval(\n      '.scroll-indicator-handle',\n      node => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const indicatorTransformY = getTranslate(indicatorTransformText, 'y')\n\n    expect(transformBScrollY).toBe(-50)\n    expect(indicatorTransformY).toBe(8)\n  })\n\n  it('should trigger BS to move when manipulating indicator', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 70,\n      y: 240,\n      xDistance: 70,\n      yDistance: 70,\n      gestureSourceType: 'touch'\n    })\n\n    const transformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const transformBScrollY = getTranslate(transformText, 'y')\n\n    const indicatorTransformText = await page.$eval(\n      '.scroll-indicator-handle',\n      node => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const indicatorTransformY = getTranslate(indicatorTransformText, 'y')\n\n    expect(transformBScrollY).toBeLessThan(-50)\n    expect(indicatorTransformY).toBeGreaterThan(8)\n  })\n\n  it('should make scrollbar scroll in when manipulating BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: -50,\n      yDistance: -50,\n      gestureSourceType: 'touch'\n    })\n\n    const transformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const transformBScrollY = getTranslate(transformText, 'y')\n\n    const indicatorTransformText = await page.$eval(\n      '.scroll-indicator-handle',\n      node => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const indicatorTransformY = getTranslate(indicatorTransformText, 'y')\n\n    expect(transformBScrollY).toBeLessThan(-50)\n    expect(indicatorTransformY).toBeGreaterThan(8)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/indicators/parallax-scrolling.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Indicators-parallax-scroll', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/indicators/parallax-scroll')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should trigger BS to move when manipulating indicator', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 70,\n      y: 240,\n      xDistance: -70,\n      yDistance: -70,\n      gestureSourceType: 'touch'\n    })\n\n    await page.waitFor(800)\n\n    const transformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const transformBScrollY = getTranslate(transformText, 'y')\n\n    const indicator1TransformText = await page.$eval('.star1-bg', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const indicator2TransformText = await page.$eval('.star2-bg', node => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const indicator1TransformY = getTranslate(indicator1TransformText, 'y')\n    const indicator2TransformY = getTranslate(indicator2TransformText, 'y')\n\n    expect(transformBScrollY).toBeLessThan(0)\n    expect(indicator1TransformY).toBeLessThan(0)\n    expect(indicator2TransformY).toBeLessThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/infinity/infinity.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000)\n\ndescribe('Infinity', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/infinity/')\n  })\n\n  beforeEach(async () => {\n    await page.reload()\n  })\n\n  it('should not render all elements when fetch data too mouch', async () => {\n    await page.waitForSelector(\n      '.infinity-timeline .infinity-item:not(.tombstone)'\n    )\n    // when\n    await page.dispatchScroll({\n      x: 100,\n      y: 555,\n      xDistance: 0,\n      yDistance: -555,\n      gestureSourceType: 'touch'\n    })\n    await page.waitFor(1001) // wait fetch data\n    // then\n    const itemNum = await page.$$eval(\n      '.infinity-timeline .infinity-item:not(.tombstone)',\n      items => items.length\n    )\n\n    expect(itemNum).toBeLessThan(60)\n  })\n\n  it('should render tombstones when data not loaded', async () => {\n    await page.waitForSelector('.infinity-timeline .infinity-item')\n    // when\n    await page.dispatchScroll({\n      x: 100,\n      y: 555,\n      xDistance: 0,\n      yDistance: -555,\n      gestureSourceType: 'touch'\n    })\n    // then\n    const itemNum = await page.$$eval(\n      '.infinity-timeline .tombstone',\n      items => items.length\n    )\n\n    expect(itemNum).toBeGreaterThan(30)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/mousewheel/mousewheel.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendMouseWheel from '../../util/extendMouseWheel'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(1000000)\n\ndescribe('MouseWheel plugin', () => {\n  let page = (global as any).page as Page\n  extendMouseWheel(page)\n\n  beforeAll(async () => {\n    // emulate pc scene\n    await page.emulate({\n      viewport: {\n        isMobile: false,\n        width: 375,\n        height: 667,\n      },\n      // tslint:disable-next-line: max-line-length\n      userAgent:\n        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36',\n    })\n  })\n\n  describe('MouseWheel & Core', () => {\n    it('should scroll correctly in vertical direction when integrating with CoreScroll', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/vertical-scroll')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 100,\n        y: 100,\n        deltaX: 0,\n        deltaY: 50,\n      })\n\n      await page.waitFor(1000)\n\n      const transformText = await page.$eval('.mouse-wheel-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const y = getTranslate(transformText, 'y')\n      await expect(y).toBeLessThan(0)\n    })\n\n    it('should scroll correctly in horizontal direction when integrating with CoreScroll', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/horizontal-scroll')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 130,\n        y: 130,\n        deltaX: 0,\n        deltaY: 100,\n      })\n\n      await page.waitFor(1000)\n      const transformText = await page.$eval('.mouse-wheel-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const x = getTranslate(transformText, 'x')\n      await expect(x).toBeLessThan(0)\n    })\n  })\n\n  describe('MouseWheel & Slide', () => {\n    it('should scroll correctly in vertical direction when integrating with Slide', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/vertical-slide')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 100,\n        y: 100,\n        deltaX: 0,\n        deltaY: 200,\n      })\n\n      await page.waitFor(3000)\n\n      const transformText = await page.$eval('.slide-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const y = getTranslate(transformText, 'y')\n      await expect(y).toBeLessThan(-667)\n    })\n\n    it('should scroll correctly in horizontal direction when integrating with Slide', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/horizontal-slide')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 130,\n        y: 130,\n        deltaX: 0,\n        deltaY: 200,\n      })\n\n      await page.waitFor(1000)\n      const transformText = await page.$eval('.slide-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const x = getTranslate(transformText, 'x')\n\n      await expect(x).toBeLessThan(-600)\n    })\n  })\n\n  describe('MouseWheel & PullUp', () => {\n    it('should scroll correctly when integrating with PullUp', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/pullup')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 100,\n        y: 100,\n        deltaX: 0,\n        deltaY: 10000,\n      })\n\n      await page.waitFor(2000)\n\n      const transformText = await page.$eval('.pullup-content', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const y = getTranslate(transformText, 'y')\n      expect(y).toBeLessThan(0)\n\n      // wait for loading data\n      await page.waitFor(2000)\n\n      const itemsCounts = await page.$$eval(\n        '.pullup-list-item',\n        (element) => element.length\n      )\n\n      await expect(itemsCounts).toBeGreaterThanOrEqual(30)\n    })\n  })\n\n  describe('MouseWheel & PullDown', () => {\n    it('should scroll correctly when integrating with PullDown', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/pulldown')\n\n      await page.waitFor(300)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 100,\n        y: 100,\n        deltaX: 0,\n        deltaY: -1000,\n      })\n\n      // wait for loading data\n      await page.waitFor(2000)\n\n      const itemsCounts = await page.$$eval(\n        '.pulldown-list-item',\n        (element) => element.length\n      )\n\n      await expect(itemsCounts).toBeGreaterThanOrEqual(40)\n    })\n  })\n\n  describe('MouseWheel & Wheel', () => {\n    it('should scroll correctly when integrating with Wheel', async () => {\n      await page.goto('http://0.0.0.0:8932/#/mouse-wheel/picker')\n\n      await page.waitFor(300)\n\n      await page.click('.open')\n\n      await page.waitFor(500)\n\n      await page.dispatchMouseWheel({\n        type: 'mouseWheel',\n        x: 200,\n        y: 630,\n        deltaX: 0,\n        deltaY: 100,\n      })\n\n      await page.waitFor(1000)\n\n      const transformText = await page.$eval('.wheel-scroll', (node) => {\n        return window.getComputedStyle(node).transform\n      })\n\n      const y = getTranslate(transformText, 'y')\n      expect(y).toBeLessThan(-30)\n    })\n  })\n})\n"
  },
  {
    "path": "tests/e2e/movable/default.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Movable Plugin', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/movable/default')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should work well when specify \"startX & startY\"', async () => {\n    await page.waitFor(300)\n    const scaledElTransformText = await page.$eval(\n      '.scroll-content',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n    const x = getTranslate(scaledElTransformText, 'x')\n    const y = getTranslate(scaledElTransformText, 'y')\n\n    expect(x).toBe(20)\n    expect(y).toBe(20)\n  })\n\n  it('should work well when dispatchScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: -70,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2000)\n\n    const scaledElTransformText = await page.$eval(\n      '.scroll-content',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n    const x = getTranslate(scaledElTransformText, 'x')\n    const y = getTranslate(scaledElTransformText, 'y')\n\n    expect(x).toBe(0)\n    expect(y).toBe(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/movable/multi-content-scale.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\nimport getScale from '../../util/getScale'\n\njest.setTimeout(10000000)\n\ndescribe('Movable & Zoom with multi content', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/movable/multi-content-scale')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should work well', async () => {\n    await page.waitFor(300)\n    const transformText1 = await page.$eval('.content1', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x1 = getTranslate(transformText1, 'x')\n    const y1 = getTranslate(transformText1, 'y')\n    const scale1 = getScale(transformText1)\n\n    expect(x1).toBe(47.5)\n    expect(y1).toBe(89)\n    expect(scale1).toBe(1.2)\n\n    const transformText2 = await page.$eval('.content2', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n    const y2 = getTranslate(transformText2, 'y')\n\n    expect(x2).toBe(0)\n    expect(y2).toBe(150)\n  })\n\n  it('should work well when call putAt()', async () => {\n    await page.waitFor(300)\n\n    await page.click('.btn')\n\n    await page.waitFor(1000)\n    const transformText2 = await page.$eval('.content2', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n    const y2 = getTranslate(transformText2, 'y')\n\n    expect(x2).toBe(135)\n    expect(y2).toBe(215)\n  })\n\n  it('should work well when perform a zoom-out gesture', async () => {\n    await page.waitFor(300)\n\n    // zoom out\n    await page.dispatchPinch({\n      x: 265,\n      y: 165,\n      scaleFactor: 1.5,\n      gestureSourceType: 'touch'\n    })\n    const transformText1 = await page.$eval('.content1', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale1 = getScale(transformText1)\n\n    expect(scale1).toBeGreaterThan(1.2)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/movable/multi-content.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Movable with multi content', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/movable/multi-content')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should work well', async () => {\n    await page.waitFor(300)\n    const transformText1 = await page.$eval('.content1', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x1 = getTranslate(transformText1, 'x')\n    const y1 = getTranslate(transformText1, 'y')\n\n    expect(x1).toBe(10)\n    expect(y1).toBe(10)\n\n    const transformText2 = await page.$eval('.content2', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n    const y2 = getTranslate(transformText2, 'y')\n\n    expect(x2).toBe(0)\n    expect(y2).toBe(170)\n  })\n\n  it('should work well when call putAt()', async () => {\n    await page.waitFor(300)\n\n    await page.click('.btn')\n\n    await page.waitFor(1000)\n    const transformText2 = await page.$eval('.content2', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n    const y2 = getTranslate(transformText2, 'y')\n\n    expect(x2).toBe(67.5)\n    expect(y2).toBe(107.5)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/movable/scaled.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\nimport getScale from '../../util/getScale'\n\njest.setTimeout(10000000)\n\ndescribe('Movable & Zoom Plugin', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/movable/scale')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should support zoom in', async () => {\n    await page.waitFor(1000)\n\n    await page.waitFor(1000)\n    // zoom in\n    await page.dispatchPinch({\n      x: 180,\n      y: 180,\n      scaleFactor: 0.9,\n      gestureSourceType: 'touch'\n    })\n\n    const scaledElTransformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(scaledElTransformText, 'x')\n    const y = getTranslate(scaledElTransformText, 'x')\n    const scale = getScale(scaledElTransformText)\n\n    expect(x).toBeGreaterThan(0)\n    expect(y).toBeGreaterThan(20)\n    expect(scale).toBeLessThan(1)\n  })\n\n  it('should support zoom out', async () => {\n    await page.waitFor(1000)\n    // zoom out\n    await page.dispatchPinch({\n      x: 180,\n      y: 180,\n      scaleFactor: 1.5,\n      gestureSourceType: 'touch'\n    })\n    await page.waitFor(1000)\n\n    const scaledElTransformText = await page.$eval('.scroll-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(scaledElTransformText, 'x')\n    const y = getTranslate(scaledElTransformText, 'x')\n    const scale = getScale(scaledElTransformText)\n\n    expect(x).toBeLessThan(0)\n    expect(y).toBeLessThan(0)\n    expect(scale).toBeGreaterThan(1)\n  })\n\n  // it('should work well when dispatchScroll', async () => {\n  //   await page.waitFor(300)\n\n  //   await page.dispatchScroll({\n  //     x: 100,\n  //     y: 100,\n  //     xDistance: -70,\n  //     yDistance: -70,\n  //     gestureSourceType: 'touch'\n  //   })\n\n  //   await page.waitFor(2000)\n\n  //   const scaledElTransformText = await page.$eval('.scroll-content', node => {\n  //     return window.getComputedStyle(node).transform\n  //   })\n  //   const x = getTranslate(scaledElTransformText, 'x')\n  //   const y = getTranslate(scaledElTransformText, 'x')\n\n  //   expect(x).toBe(0)\n  //   expect(y).toBe(0)\n  // })\n})\n"
  },
  {
    "path": "tests/e2e/nested-scroll/horizontal-in-vertical.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Nested horizontal-in-vertical scroll', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto(\n      'http://0.0.0.0:8932/#/nested-scroll/horizontal-in-vertical'\n    )\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should make outer BScroll scroll when manipulating outerBScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 60,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.vertical-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const translateX = getTranslate(transformText!, 'y')\n    await expect(translateX).toBeLessThan(-30)\n  })\n\n  it('should only make innerBScroll scroll when manipulating innerBScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 200,\n      xDistance: -300,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const outerTransformText = await page.$eval('.vertical-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateX = getTranslate(outerTransformText!, 'x')\n    await expect(outerTranslateX).toBe(0)\n\n    const innerTransformText = await page.$eval(\n      '.slide-banner-content',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n    const innerTranslateX = getTranslate(innerTransformText!, 'x')\n    await expect(innerTranslateX).toBeLessThan(-100)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/nested-scroll/horizontal.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Nested horizontal scroll', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/nested-scroll/horizontal')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should make outer BScroll scroll when manipulating outerBScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 110,\n      xDistance: -70,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2500)\n\n    const transformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const translateX = getTranslate(transformText!, 'x')\n    await expect(translateX).toBeLessThan(-30)\n  })\n\n  it('should only make innerBScroll scroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 270,\n      y: 110,\n      xDistance: -70,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2500)\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateX = getTranslate(outerTransformText!, 'x')\n    await expect(outerTranslateX).toBe(0)\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTranslateY = getTranslate(innerTransformText!, 'x')\n    await expect(innerTranslateY).toBeLessThan(-30)\n  })\n\n  it('should make outer BScroll scroll when innerScroll reached boundary', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 270,\n      y: 110,\n      xDistance: -600,\n      yDistance: 0,\n      speed: 1800,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2500)\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTranslateX = getTranslate(innerTransformText!, 'x')\n    await expect(innerTranslateX).toBeLessThan(-50)\n\n    await page.dispatchScroll({\n      x: 270,\n      y: 110,\n      xDistance: -50,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateX = getTranslate(outerTransformText!, 'x')\n    await expect(outerTranslateX).toBeLessThan(-20)\n  })\n\n  it('should support click handle when use nestedScroll plugin', async () => {\n    const mockOuterHandler = jest.fn()\n    const mockInnerHandler = jest.fn()\n    page.once('dialog', async (dialog) => {\n      mockOuterHandler()\n      await dialog.dismiss()\n    })\n\n    // outer click\n    await page.touchscreen.tap(150, 110)\n    expect(mockOuterHandler).toBeCalledTimes(1)\n\n    await page.waitFor(500)\n\n    page.once('dialog', async (dialog) => {\n      mockInnerHandler()\n      await dialog.dismiss()\n    })\n\n    // inner click\n    await page.touchscreen.tap(300, 110)\n    expect(mockInnerHandler).toBeCalledTimes(1)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/nested-scroll/triple-vertical.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Nested triple-vertical scroll', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/nested-scroll/triple-vertical')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should make outer BScroll scroll and others keep unmoved when manipulating outerBScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 150,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2500)\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const middleTransformText = await page.$eval('.middle-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n    const middleTranslateY = getTranslate(middleTransformText!, 'y')\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n\n    expect(outerTranslateY).toBeLessThan(-30)\n    expect(middleTranslateY).toBeNaN()\n    expect(innerTranslateY).toBeNaN()\n  })\n\n  it('should only make middle scroll and others keep unmoved', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 250,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n\n    const middleTransformText = await page.$eval('.middle-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const middleTranslateY = getTranslate(middleTransformText!, 'y')\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n\n    expect(innerTranslateY).toBeNaN()\n    expect(middleTranslateY).toBeLessThan(-30)\n    expect(outerTranslateY).toBe(0)\n  })\n\n  it('should only make inner scroll and others keep unmoved', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 450,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n\n    const middleTransformText = await page.$eval('.middle-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const middleTranslateY = getTranslate(middleTransformText!, 'y')\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n\n    expect(innerTranslateY).toBeLessThan(-30)\n    expect(middleTranslateY).toBe(0)\n    expect(outerTranslateY).toBe(0)\n  })\n\n  it('should make parent BScroll scroll when innerScroll reached boundary', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 450,\n      xDistance: 0,\n      yDistance: 50,\n      speed: 3000,\n      gestureSourceType: 'touch',\n    })\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n\n    const middleTransformText = await page.$eval('.middle-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const middleTranslateY = getTranslate(middleTransformText!, 'y')\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n\n    expect(outerTranslateY).toBeGreaterThan(0)\n    expect(middleTranslateY).toBeNaN()\n    expect(innerTranslateY).toBeNaN()\n  })\n\n  it('click', async () => {\n    const mockOuterHandler = jest.fn()\n    const mockMiddleHandler = jest.fn()\n    const mockInnerHandler = jest.fn()\n\n    // outer click\n    page.once('dialog', async (dialog) => {\n      mockOuterHandler()\n      await dialog.dismiss()\n    })\n    await page.touchscreen.tap(150, 100)\n    expect(mockOuterHandler).toBeCalledTimes(1)\n\n    await page.waitFor(500)\n\n    // middle click\n    page.once('dialog', async (dialog) => {\n      mockMiddleHandler()\n      await dialog.dismiss()\n    })\n    await page.touchscreen.tap(150, 250)\n    expect(mockMiddleHandler).toBeCalledTimes(1)\n\n    await page.waitFor(500)\n\n    // inner click\n    page.once('dialog', async (dialog) => {\n      mockInnerHandler()\n      await dialog.dismiss()\n    })\n    await page.touchscreen.tap(150, 400)\n    expect(mockInnerHandler).toBeCalledTimes(1)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/nested-scroll/vertical.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Nested vertical scroll', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/nested-scroll/vertical')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should make outer BScroll scroll when manipulating outerBScroll', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 150,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(2500)\n\n    const transformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const translateY = getTranslate(transformText!, 'y')\n    await expect(translateY).toBeLessThan(-30)\n  })\n\n  it('should only make innerBScroll scroll and outerBScroll stop', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 300,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n    await expect(outerTranslateY).toBe(0)\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n    await expect(innerTranslateY).toBeLessThan(-30)\n  })\n\n  it('should make outer BScroll scroll when innerScroll reached boundary', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 300,\n      xDistance: 0,\n      yDistance: -300,\n      speed: 3000,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const innerTransformText = await page.$eval('.inner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const innerTranslateY = getTranslate(innerTransformText!, 'y')\n    await expect(innerTranslateY).toBeLessThan(-20)\n\n    await page.dispatchScroll({\n      x: 160,\n      y: 300,\n      xDistance: 0,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    const outerTransformText = await page.$eval('.outer-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const outerTranslateY = getTranslate(outerTransformText!, 'y')\n    await expect(outerTranslateY).toBeLessThan(-50)\n  })\n\n  it('should support click handle when use nestedScroll plugin', async () => {\n    const mockOuterHandler = jest.fn()\n    const mockInnerHandler = jest.fn()\n    page.once('dialog', async (dialog) => {\n      mockOuterHandler()\n      await dialog.dismiss()\n    })\n\n    // outer click\n    await page.touchscreen.tap(300, 100)\n    expect(mockOuterHandler).toBeCalledTimes(1)\n\n    await page.waitFor(500)\n\n    page.once('dialog', async (dialog) => {\n      mockInnerHandler()\n      await dialog.dismiss()\n    })\n\n    // inner click\n    await page.touchscreen.tap(350, 500)\n    expect(mockInnerHandler).toBeCalledTimes(1)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/observe-dom/observe-dom.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('ObserveDOM', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeEach(async () => {\n    await page.goto('http://0.0.0.0:8932/#/observe-dom/')\n  })\n\n  it('should observe DOM change and auto refresh bs', async () => {\n    await page.waitFor(300)\n\n    const preItemsCounts = await page.$$eval(\n      '.scroll-item',\n      (element) => element.length\n    )\n\n    expect(preItemsCounts).toBe(10)\n\n    await page.click('.btn')\n\n    await page.waitFor(100)\n\n    const PostItemsCounts = await page.$$eval(\n      '.scroll-item',\n      (element) => element.length\n    )\n\n    expect(PostItemsCounts).toBe(12)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -150,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n    await page.waitFor(2600)\n\n    const transformText = await page.$eval('.scroll-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBeLessThanOrEqual(-361)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/observe-image/observe-image.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('ObserveImage', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeEach(async () => {\n    await page.goto('http://0.0.0.0:8932/#/observe-image/')\n  })\n\n  it('should autorefresh when img loaded', async () => {\n    await page.waitFor(3000)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: 0,\n      yDistance: -50,\n      gestureSourceType: 'touch',\n    })\n\n    const transformText = await page.$eval('.scroll-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const y = getTranslate(transformText, 'y')\n\n    expect(y).toBeLessThan(-30)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/picker/double-column.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Double column picker', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/picker/double-column')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should render picker DOM correctly', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(500)\n\n    const displayText = await page.$eval('.picker-panel', (node) => {\n      return window.getComputedStyle(node).display\n    })\n\n    await expect(displayText).toBe('block')\n  })\n\n  it('should get correct text when click \"confirm\" button', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    const openBtn = await page.$('.open')\n\n    await page.click('.confirm')\n\n    // wait for transition ends\n    await page.waitFor(100)\n\n    const innerText = await page.$eval('.open', (node) => {\n      return node.textContent\n    })\n\n    await expect(innerText).toBe('Venomancer-0__Durable-0')\n  })\n\n  it('should scroll correctly when simulate touch event on each column', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    // first column\n    await page.dispatchScroll({\n      x: 100,\n      y: 630,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    // second column\n    await page.dispatchScroll({\n      x: 270,\n      y: 630,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    // wait for transition ends\n    await page.waitFor(1000)\n\n    const transformTexts = await page.$$eval('.wheel-scroll', (nodes) => {\n      return nodes.map((node) => window.getComputedStyle(node).transform)\n    })\n\n    for (const transformText of transformTexts) {\n      const translateY = getTranslate(transformText, 'y')\n      await expect(translateY).toBeLessThan(-72)\n    }\n  })\n})\n"
  },
  {
    "path": "tests/e2e/picker/linkage-column.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000000)\n\ndescribe('Linkage column picker', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/picker/linkage-column')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should get correct text when click \"confirm\" button', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    const openBtn = await page.$('.open')\n\n    await page.click('.confirm')\n\n    // wait for transition ends\n    await page.waitFor(100)\n\n    const innerText = await page.$eval('.open', (node) => {\n      return node.textContent\n    })\n\n    await expect(innerText).toBe('北京市-0__北京市-0')\n  })\n\n  it('should linkage correctly when click TianJin province', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    const [firstWheelScroll] = await page.$$('.wheel-scroll')\n\n    const [, TianJinProvince] = await firstWheelScroll.$$('.wheel-item')\n\n    await TianJinProvince.tap()\n\n    // when transition ends\n    await page.waitFor(500)\n\n    const cityBtnText = await page.$$eval('.wheel-scroll', (nodes) => {\n      return nodes[1].querySelectorAll('.wheel-item')[0].textContent\n    })\n\n    await expect(cityBtnText).toBe('天津市')\n  })\n\n  it('should linkage correctly when dispatch touch event in first column', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    // first column\n    await page.dispatchScroll({\n      x: 100,\n      y: 630,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    // when transition ends\n    await page.waitFor(1000)\n\n    const cityBtnText = await page.$$eval('.wheel-scroll', (nodes) => {\n      return nodes[1].querySelectorAll('.wheel-item')[0].textContent\n    })\n\n    await expect(cityBtnText).not.toBe('北京市')\n  })\n})\n"
  },
  {
    "path": "tests/e2e/picker/one-column.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('One column picker', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/picker/one-column')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should render picker DOM correctly', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(500)\n\n    const displayText = await page.$eval('.picker-panel', (node) => {\n      return window.getComputedStyle(node).display\n    })\n\n    await expect(displayText).toBe('block')\n  })\n\n  it('should wheelTo third item by default', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(500)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n\n    await expect(translateY).toBe(-72)\n  })\n\n  it('should not select disabled item', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    await page.tap('.wheel-disabled-item')\n\n    await page.waitFor(500)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n    await expect(translateY).toBe(-36)\n  })\n\n  it('should wheel to second item when click second item', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    const items = await page.$$('.wheel-item')\n    const secondItem = items[1]\n\n    await secondItem.tap()\n\n    // wait for transition ends\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n    await expect(translateY).toBe(-36)\n  })\n\n  it('should scroll correctly when simulate touch event', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 630,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    // wait for transition ends\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n    await expect(translateY).toBeLessThan(-72)\n  })\n\n  it('should restore position when bs is scrolling and invoke restorePosition()', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 630,\n      xDistance: 0,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    await page.click('.cancel')\n\n    await page.waitFor(500)\n\n    await page.click('.open')\n\n    await page.waitFor(500)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n\n    expect(translateY).toBe(-72)\n  })\n\n  it('should stop at the nearest wheel item when bs is scrolling and invoke stop()', async () => {\n    await page.waitFor(300)\n\n    await page.click('.open')\n\n    await page.waitFor(1000)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 630,\n      xDistance: 0,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    await page.click('.confirm')\n\n    await page.waitFor(500)\n\n    await page.click('.open')\n\n    await page.waitFor(500)\n\n    const transformText = await page.$eval('.wheel-scroll', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const translateY = getTranslate(transformText, 'y')\n\n    expect(translateY).toBeLessThan(-180)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/pulldown/default.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000000)\n\ndescribe('Pulldown', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeEach(async () => {\n    // disable cache\n    await page.setCacheEnabled(false)\n    await page.goto('http://0.0.0.0:8932/#/pulldown/default')\n  })\n\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.pulldown-list-item',\n      (element) => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(20)\n  })\n\n  it('should trigger pullingdown when BS reached the top', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 100,\n      xDistance: 0,\n      yDistance: 400,\n      speed: 1500,\n      gestureSourceType: 'touch',\n    })\n\n    // wait for requesting data\n    await page.waitFor(3000)\n    const itemsCounts = await page.$$eval(\n      '.pulldown-list-item',\n      (element) => element.length\n    )\n    // has loaded\n    await expect(itemsCounts).toBeGreaterThanOrEqual(40)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/pulldown/sina.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000000)\n\nconst chunk = <T>(array: T[], size: number) => {\n  let index = 0\n  let resIndex = 0\n  const length = array.length\n  const result = new Array(Math.ceil(length / size))\n\n  while (index < length) {\n    result[resIndex++] = array.slice(index, (index += size))\n  }\n  return result\n}\n\ndescribe('Pulldown-sina-weibo', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeEach(async () => {\n    // disable cache\n    await page.setCacheEnabled(false)\n    await page.goto('http://0.0.0.0:8932/#/pulldown/sina')\n  })\n\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.pulldown-list-item',\n      (element) => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(20)\n  })\n\n  it('should go through correct phase', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchTouch({\n      type: 'touchStart',\n      touchPoints: [\n        {\n          x: 200,\n          y: 40,\n        },\n      ],\n    })\n    const touchMovePoints = (() => {\n      const start = 70\n      const step = 5\n      const end = 550\n      const x = 200\n      let ret: Array<{ x: number; y: number }> = []\n      for (let i = start; i <= end; i += step) {\n        ret.push({\n          x,\n          y: i,\n        })\n      }\n      // chrome only allow 16 items in touchPoints array\n      return chunk(ret, 16)\n    })()\n    // touchmove\n    for (const touchPoint of touchMovePoints) {\n      await page.dispatchTouch({\n        type: 'touchMove',\n        touchPoints: touchPoint,\n      })\n    }\n\n    const textContent = await page.$$eval(\n      '.pulldown-wrapper',\n      (element) => element[0].textContent\n    )\n    expect(textContent).toContain('Release')\n\n    await page.dispatchTouch({\n      type: 'touchEnd',\n      touchPoints: [\n        {\n          x: 200,\n          y: 560,\n        },\n      ],\n    })\n\n    await page.waitFor(300)\n    // loading\n    const textContent2 = await page.$$eval(\n      '.pulldown-wrapper',\n      (element) => element[0].textContent\n    )\n    expect(textContent2).toContain('Loading...')\n\n    await page.waitFor(3000)\n\n    // refresh succeed\n    const textContent3 = await page.$$eval(\n      '.pulldown-wrapper',\n      (element) => element[0].textContent\n    )\n    expect(textContent3).toContain('Refresh succeed')\n    const itemsCounts = await page.$$eval(\n      '.pulldown-list-item',\n      (element) => element.length\n    )\n    // has loaded\n    expect(itemsCounts).toBeGreaterThanOrEqual(40)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/pullup/default.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\n\njest.setTimeout(10000000)\n\ndescribe('Pullup', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeEach(async () => {\n    // disable cache\n    await page.setCacheEnabled(false)\n    await page.goto('http://0.0.0.0:8932/#/pullup/')\n  })\n\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.pullup-list-item',\n      element => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(30)\n  })\n\n  it('should trigger pullingup when BS reached the bottom', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 630,\n      xDistance: 0,\n      yDistance: -1000,\n      speed: 1500,\n      gestureSourceType: 'touch'\n    })\n\n    // wait for requesting data\n    await page.waitFor(3000)\n\n    const itemsCounts = await page.$$eval(\n      '.pullup-list-item',\n      element => element.length\n    )\n    // has loaded\n    await expect(itemsCounts).toBeGreaterThan(40)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/scrollbar/custom.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Scrollbar-custom', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/scrollbar/custom')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should trigger BS to move when manipulating scrollbar', async () => {\n    await page.waitFor(300)\n\n    // horizontal\n    await page.dispatchScroll({\n      x: 123,\n      y: 288,\n      xDistance: 70,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const transformXText = await page.$eval(\n      '.custom-scrollbar-content',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const x = getTranslate(transformXText, 'x')\n    expect(x).toBeLessThan(0)\n\n    // vertical\n    await page.dispatchScroll({\n      x: 288,\n      y: 123,\n      xDistance: 0,\n      yDistance: 70,\n      gestureSourceType: 'touch',\n    })\n\n    const transformYText = await page.$eval(\n      '.custom-scrollbar-content',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const y = getTranslate(transformYText, 'y')\n    expect(y).toBeLessThan(0)\n  })\n\n  it('should trigger make scrollbar scroll in when manipulating BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: -100,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const transformXText = await page.$eval(\n      '.custom-horizontal-indicator',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const x = getTranslate(transformXText, 'x')\n    expect(x).toBeGreaterThan(0)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: 100,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    const transformYText = await page.$eval(\n      '.custom-vertical-indicator',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const y = getTranslate(transformYText, 'y')\n    expect(y).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/scrollbar/horizontal.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Scrollbar-horizontal', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/scrollbar/horizontal')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.bscroll-horizontal-scrollbar',\n      (element) => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(1)\n  })\n\n  it('should trigger BS to move when manipulating scrollbar', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 70,\n      y: 195,\n      xDistance: 70,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const transformText = await page.$eval('.scroll-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const x = getTranslate(transformText, 'x')\n    expect(x).toBeLessThan(0)\n  })\n\n  it('should make scrollbar fade in when dispatch touch event in BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: -100,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const opacity = await page.$eval(\n      '.bscroll-horizontal-scrollbar',\n      (node) => {\n        return window.getComputedStyle(node).opacity\n      }\n    )\n    expect(Number(opacity)).toBeGreaterThan(0)\n  })\n\n  it('should make scrollbar scroll in when manipulating BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: -100,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    const transformText = await page.$eval('.bscroll-indicator', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const x = getTranslate(transformText, 'x')\n    expect(x).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/scrollbar/mousewheel.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendMouseWheel from '../../util/extendMouseWheel'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Scrollbar-mousewheel', () => {\n  let page = (global as any).page as Page\n\n  extendMouseWheel(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/scrollbar/mousewheel')\n  })\n\n  it('should trigger BS to move when using mousewheel', async () => {\n    await page.waitFor(2000)\n    await page.dispatchMouseWheel({\n      type: 'mouseWheel',\n      x: 100,\n      y: 100,\n      deltaX: 0,\n      deltaY: 100,\n    })\n    await page.waitFor(1000)\n    const transformText = await page.$eval(\n      '.custom-horizontal-indicator',\n      (node) => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n\n    const x = getTranslate(transformText, 'x')\n    expect(x).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/scrollbar/vertical.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Scrollbar-vertical', () => {\n  let page = (global as any).page as Page\n\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/scrollbar/vertical')\n  })\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should render DOM correctly', async () => {\n    await page.waitFor(300)\n\n    const itemsCounts = await page.$$eval(\n      '.bscroll-vertical-scrollbar',\n      (element) => element.length\n    )\n\n    await expect(itemsCounts).toBeGreaterThanOrEqual(1)\n  })\n\n  it('should trigger BS to move when manipulating scrollbar', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 325,\n      y: 560,\n      xDistance: 0,\n      yDistance: -70,\n      gestureSourceType: 'touch',\n    })\n\n    const transformText = await page.$eval('.scrollbar-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const y = getTranslate(transformText, 'y')\n    expect(y).toBeLessThan(0)\n  })\n\n  it('should make scrollbar fade in when dispatch touch event in BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: 0,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    const opacity = await page.$eval('.bscroll-vertical-scrollbar', (node) => {\n      return window.getComputedStyle(node).opacity\n    })\n    expect(Number(opacity)).toBeGreaterThan(0)\n  })\n\n  it('should make scrollbar scroll in when manipulating BS', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 100,\n      xDistance: 0,\n      yDistance: -100,\n      gestureSourceType: 'touch',\n    })\n\n    const transformText = await page.$eval('.bscroll-indicator', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n\n    const y = getTranslate(transformText, 'y')\n    expect(y).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/slide/banner.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Slider for banner', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/slide/banner')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should loop by default', async () => {\n    await page.waitFor(300)\n\n    // wait for slide autoplay\n    await page.waitFor(4000)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-670)\n  })\n\n  it('should go nextPage when click nextPage button', async () => {\n    await page.waitFor(300)\n\n    // simulate click\n    await page.click('.next')\n\n    // wait for bs to do a transition\n    await page.waitFor(1500)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-670)\n  })\n\n  it('should go prevPage when click prevPage button', async () => {\n    await page.waitFor(300)\n\n    await page.click('.next')\n    // wairt for bs to do a transition\n    await page.waitFor(1500)\n\n    const transformText1 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x1 = getTranslate(transformText1, 'x')\n\n    expect(x1).toBe(-670)\n\n    // simulate click\n    await page.click('.prev')\n    await page.waitFor(1500)\n\n    const transformText2 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n\n    expect(x2).toBe(-335)\n  })\n  it('should change index when drag slide', async () => {\n    await page.waitFor(300)\n    const currentIndex = await page.$eval('.dots-wrapper', (el) => {\n      const children = el.children\n      let index = 0\n      for (let i = 0; i < children.length; i++) {\n        if (children[i].className.indexOf('active') > -1) {\n          index = i\n          break\n        }\n      }\n      return index + 1\n    })\n    const nextDotsIndex = currentIndex === 3 ? 0 : currentIndex + 1\n    await page.dispatchScroll({\n      x: 200,\n      y: 120,\n      xDistance: -150,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n    const secondDots = await page.$eval(\n      `.dots-wrapper .dot:nth-child(${nextDotsIndex})`,\n      (el) => el.className\n    )\n    expect(secondDots).toContain('active')\n  })\n})\n"
  },
  {
    "path": "tests/e2e/slide/dynamic.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Slider for fullpage', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/slide/dynamic')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('increase', async () => {\n    await page.waitFor(300)\n\n    await page.click('.increase')\n\n    await page.waitFor(200)\n\n    const slidePageLen = await page.$$eval(\n      '.slide-page',\n      (element) => element.length\n    )\n    const slidePageTexts = await page.$$eval('.slide-page', (node) =>\n      node.map((n) => n.textContent)\n    )\n\n    expect(slidePageLen).toBe(4)\n    expect(slidePageTexts).toMatchObject([\n      'page 2',\n      'page 1',\n      'page 2',\n      'page 1',\n    ])\n  })\n\n  it('decrease', async () => {\n    await page.waitFor(300)\n\n    await page.click('.increase')\n\n    await page.waitFor(200)\n\n    await page.click('.decrease')\n\n    await page.waitFor(200)\n\n    const slidePageLen = await page.$$eval(\n      '.slide-page',\n      (element) => element.length\n    )\n    const slidePageTexts = await page.$$eval('.slide-page', (node) =>\n      node.map((n) => n.textContent)\n    )\n\n    expect(slidePageLen).toBe(1)\n    expect(slidePageTexts).toMatchObject(['page 1'])\n  })\n\n  it('scroll ', async () => {\n    await page.waitFor(300)\n\n    await page.click('.increase')\n\n    await page.waitFor(200)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 150,\n      xDistance: -150,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(500)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-670)\n\n    await page.click('.increase')\n\n    await page.waitFor(200)\n\n    const transformText2 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x2 = getTranslate(transformText2, 'x')\n\n    expect(x2).toBe(-670)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 150,\n      xDistance: -150,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(500)\n\n    const transformText3 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x3 = getTranslate(transformText3, 'x')\n\n    expect(x3).toBe(-1005)\n\n    await page.click('.decrease')\n\n    await page.waitFor(200)\n\n    const transformText4 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x4 = getTranslate(transformText4, 'x')\n\n    expect(x4).toBe(-670)\n\n    await page.click('.decrease')\n\n    await page.waitFor(200)\n\n    const transformText5 = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x5 = getTranslate(transformText5, 'x')\n\n    expect(x5).toBe(0)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/slide/fullpage.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Slider for fullpage', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/slide/fullpage')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should not allow move to pre page when it is first page and loop is false', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: 110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(200)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(0)\n  })\n\n  it('should not allow move to next page when it is last page and loop is false', async () => {\n    await page.waitFor(300)\n\n    // to second page\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    // to third page\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    // to last page\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    // attempts to go next\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1000)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-1125)\n  })\n\n  it('should work by dispatching touch events', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: -110,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n\n    await page.waitFor(1500)\n\n    const transformText = await page.$eval('.slide-banner-content', (node) => {\n      return window.getComputedStyle(node).transform\n    })\n    const x = getTranslate(transformText, 'x')\n\n    expect(x).toBe(-375)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/slide/specifiedIndex.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Slider for specified index', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/slide/specified')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded',\n    })\n  })\n\n  it('should work well when initialised', async () => {\n    await page.waitFor(300)\n\n    const textContext = await page.$eval('.description', (node) => {\n      return node.textContent\n    })\n    expect(textContext).toBe('currentPageIndex is 2')\n  })\n\n  it('should go nextPage when click nextPage button', async () => {\n    await page.waitFor(300)\n\n    // simulate click\n    await page.click('.next')\n\n    // wait for bs to do a transition\n    await page.waitFor(1500)\n\n    const textContext = await page.$eval('.description', (node) => {\n      return node.textContent\n    })\n\n    expect(textContext).toBe('currentPageIndex is 3')\n  })\n\n  it('should go prevPage when click prevPage button', async () => {\n    await page.waitFor(300)\n\n    await page.click('.prev')\n\n    // wait for bs to do a transition\n    await page.waitFor(1500)\n\n    const textContext = await page.$eval('.description', (node) => {\n      return node.textContent\n    })\n\n    expect(textContext).toBe('currentPageIndex is 1')\n  })\n  it('should change index when drap slide', async () => {\n    await page.waitFor(300)\n\n    await page.dispatchScroll({\n      x: 200,\n      y: 120,\n      xDistance: -150,\n      yDistance: 0,\n      gestureSourceType: 'touch',\n    })\n    // wait for bs to do a transition\n    await page.waitFor(1500)\n\n    const textContext = await page.$eval('.description', (node) => {\n      return node.textContent\n    })\n\n    expect(textContext).toBe('currentPageIndex is 3')\n  })\n})\n"
  },
  {
    "path": "tests/e2e/slide/vertical.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Slider for vertical', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/slide/vertical')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should work by dispatching touch events', async () => {\n    await page.waitFor(1500)\n\n    await page.dispatchScroll({\n      x: 100,\n      y: 200,\n      xDistance: 0,\n      yDistance: -50,\n      gestureSourceType: 'touch'\n    })\n\n    await page.waitFor(1000)\n    const transformText = await page.$eval('.slide-vertical-content', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const y = getTranslate(transformText, 'y')\n\n    expect(y).toBe(-1334)\n  })\n})\n"
  },
  {
    "path": "tests/e2e/zoom/zoom.e2e.ts",
    "content": "import { Page } from 'puppeteer'\nimport extendTouch from '../../util/extendTouch'\nimport getScale from '../../util/getScale'\nimport getTranslate from '../../util/getTranslate'\n\njest.setTimeout(10000000)\n\ndescribe('Zoom', () => {\n  let page = (global as any).page as Page\n  extendTouch(page)\n\n  beforeAll(async () => {\n    await page.goto('http://0.0.0.0:8932/#/zoom')\n  })\n\n  beforeEach(async () => {\n    await page.reload({\n      waitUntil: 'domcontentloaded'\n    })\n  })\n\n  it('should work when initializing to scaled > 1', async () => {\n    await page.waitFor(300)\n    const scaledElTransformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale = getScale(scaledElTransformText)\n\n    expect(scale).toBe(1.5)\n  })\n\n  it('should work when initialOrigin is set to \"center\"', async () => {\n    await page.waitFor(300)\n    const scaledElTransformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale = getScale(scaledElTransformText)\n    const x = getTranslate(scaledElTransformText, 'x')\n    const y = getTranslate(scaledElTransformText, 'x')\n\n    expect(scale).toBe(1.5)\n    expect(x).toBeLessThan(0)\n    expect(y).toBeLessThan(0)\n  })\n\n  it('should work by dispatching zooming out', async () => {\n    await page.waitFor(300)\n\n    // zoom out\n    await page.dispatchPinch({\n      x: 100,\n      y: 100,\n      scaleFactor: 1.1,\n      gestureSourceType: 'touch'\n    })\n\n    const scaledElTransformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale = getScale(scaledElTransformText)\n\n    expect(scale).toBeGreaterThan(1.5)\n  })\n\n  it('should work by dispatching zooming in', async () => {\n    await page.waitFor(300)\n\n    // zoom in\n    await page.dispatchPinch({\n      x: 200,\n      y: 200,\n      scaleFactor: 0.5,\n      gestureSourceType: 'touch'\n    })\n\n    const scaledElTransformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale = getScale(scaledElTransformText)\n\n    expect(scale).toBeLessThan(1.5)\n  })\n\n  it('should do a rebound animation when scale exceed \"max\", and recover to \"max\"', async () => {\n    await page.waitFor(300)\n\n    // zoom in\n    await page.dispatchPinch({\n      x: 200,\n      y: 200,\n      scaleFactor: 4,\n      gestureSourceType: 'touch'\n    })\n\n    // wait for rebound animation ends\n    await page.waitFor(1000)\n\n    const scaledElTransformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scale = getScale(scaledElTransformText)\n\n    expect(scale).toBe(3)\n  })\n\n  it('should support zoomTo api', async () => {\n    await page.waitFor(300)\n\n    // zoomTo scale(0.5)\n    await page.click('.zoom-half')\n    await page.waitFor(1000)\n    const scaledElTransformTextHalf = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    let scaleHalf = getScale(scaledElTransformTextHalf)\n\n    expect(scaleHalf).toBe(0.5)\n\n    // zoomTo scale(1)\n    await page.click('.zoom-original')\n    await page.waitFor(1000)\n    let scaledElTransformTextOriginal = await page.$eval(\n      '.zoom-items',\n      node => {\n        return window.getComputedStyle(node).transform\n      }\n    )\n    const scaleOriginal = getScale(scaledElTransformTextOriginal)\n    const xOriginal = getTranslate(scaledElTransformTextOriginal, 'x')\n    const yOriginal = getTranslate(scaledElTransformTextOriginal, 'y')\n\n    expect(scaleOriginal).toBe(1)\n    expect(xOriginal).toBe(0)\n    expect(yOriginal).toBe(0)\n\n    // zoomTo scale(2)\n    await page.click('.zoom-double')\n    await page.waitFor(1000)\n    let scaledElTransformTextDouble = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const scaleDouble = getScale(scaledElTransformTextDouble)\n    const xDouble = getTranslate(scaledElTransformTextDouble, 'x')\n    const yDouble = getTranslate(scaledElTransformTextDouble, 'y')\n\n    expect(scaleDouble).toBe(2)\n    expect(xDouble).toBeLessThan(0)\n    expect(yDouble).toBeLessThan(0)\n  })\n\n  it('should allow moving with one finger when scaled out', async () => {\n    await page.waitFor(300)\n\n    // zoom out\n    await page.dispatchPinch({\n      x: 100,\n      y: 100,\n      scaleFactor: 1.2,\n      gestureSourceType: 'touch'\n    })\n\n    // touchmove\n    await page.dispatchScroll({\n      x: 100,\n      y: 120,\n      xDistance: 200,\n      yDistance: 0,\n      gestureSourceType: 'touch'\n    })\n\n    await page.waitFor(2500)\n\n    let transformText = await page.$eval('.zoom-items', node => {\n      return window.getComputedStyle(node).transform\n    })\n    const xDouble = getTranslate(transformText, 'x')\n    expect(xDouble).toEqual(0)\n  })\n})\n"
  },
  {
    "path": "tests/util/extendMouseWheel.ts",
    "content": "import { Page } from 'puppeteer'\n\ninterface EventParams {\n  type: string\n  x: number\n  y: number\n  deltaX: number\n  deltaY: number\n}\n\nconst DEFAULT_CHROMIUM_MOUSE_WHEEL_NAME = 'Input.dispatchMouseEvent'\n\ndeclare module 'puppeteer' {\n  interface Mouse {\n    _client: {\n      send: (name: string, eventParams: EventParams) => Promise<void>\n    }\n  }\n  interface Page {\n    dispatchMouseWheel: (eventParams: EventParams) => Promise<void>\n    mouse: Mouse\n  }\n}\n\n// puppeteer 1.17.0 has no api to implement MouseWheel\n// since puppeteer is connected to chromium with chromeDevTools\n// https://chromedevtools.github.io/devtools-protocol/tot/Input/#method-dispatchMouseEvent\n// so we can do it by ourselves\nexport default (page: Page) => {\n  page.dispatchMouseWheel = async (eventParams: EventParams) => {\n    await page.mouse._client.send(\n      DEFAULT_CHROMIUM_MOUSE_WHEEL_NAME,\n      eventParams\n    )\n  }\n}\n"
  },
  {
    "path": "tests/util/extendTouch.ts",
    "content": "import { Page, Touchscreen } from 'puppeteer'\n\n// https://chromedevtools.github.io/devtools-protocol/tot/Input#method-synthesizePinchGesture\ninterface PinchParams {\n  x: number\n  y: number\n  scaleFactor: number\n  gestureSourceType: 'touch' | 'default' | 'mouse'\n}\n\n// https://chromedevtools.github.io/devtools-protocol/tot/Input#method-synthesizeScrollGesture\ninterface ScrollParams {\n  x: number // X coordinate of the start of the gesture in CSS pixels.\n  y: number // Y coordinate of the start of the gesture in CSS pixels.\n  xDistance: number // positive to scroll left\n  yDistance: number // positive to scroll up\n  gestureSourceType: 'touch' | 'default' | 'mouse'\n  speed?: number // Swipe speed in pixels per second\n  xOverscroll?: number\n  yOverscroll?: number\n  preventFling?: boolean\n  repeatCount?: number\n  repeatDelayMs?: number\n}\n\ninterface TouchPoint {\n  x: number\n  y: number\n  radiusX?: number\n  radiusY?: number\n  rotationAngle?: number\n  force?: number\n  tangentialPressure?: number\n  tiltX?: number\n  tiltY?: number\n  twist?: number\n  id?: number\n}\n\ninterface TouchesParams {\n  type: 'touchStart' | 'touchEnd' | 'touchMove' | 'touchCancel'\n  touchPoints: TouchPoint[]\n  modifiers?: number // Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n  timestamp?: number\n}\n\nconst PINCH_NAME = 'Input.synthesizePinchGesture'\nconst SCROLL_NAME = 'Input.synthesizeScrollGesture'\nconst TOUCHES_NAME = 'Input.dispatchTouchEvent'\n\ndeclare module 'puppeteer' {\n  interface Touchscreen {\n    _client: {\n      send: <T>(\n        name: T,\n        params: PinchParams | ScrollParams | TouchesParams\n      ) => Promise<void>\n    }\n  }\n  interface Page {\n    dispatchPinch: (pinchParams: PinchParams) => Promise<void>\n    dispatchScroll: (scrollParams: ScrollParams) => Promise<void>\n    dispatchTouch: (touchesParams: TouchesParams) => Promise<void>\n    touchsceen: Touchscreen\n  }\n}\n\n// puppeteer 1.17.0 has no api to implement touchmove\n// since puppeteer is connected to chromium with chromeDevTools\n// https://chromedevtools.github.io/devtools-protocol/tot/Input#method-dispatchTouchEvent\nexport default (page: Page) => {\n  page.dispatchPinch = async (pinchParams) => {\n    await page.touchscreen._client.send(PINCH_NAME, pinchParams)\n  }\n  page.dispatchScroll = async (scrollParams) => {\n    await page.touchscreen._client.send(SCROLL_NAME, scrollParams)\n  }\n  page.dispatchTouch = async (touchesParams) => {\n    await page.touchscreen._client.send(TOUCHES_NAME, touchesParams)\n  }\n}\n"
  },
  {
    "path": "tests/util/getScale.ts",
    "content": "export default function getScale(transformText: string) {\n  const matrix = transformText.split(')')[0].split(', ')\n  const prefix = matrix[0]\n  return +prefix.split('(')[1]\n}\n"
  },
  {
    "path": "tests/util/getTranslate.ts",
    "content": "export default function getTranslate(\n  transformText: string,\n  direction: 'x' | 'y'\n) {\n  const matrix = transformText.split(')')[0].split(', ')\n  let ret =\n    direction === 'x' ? +(matrix[12] || matrix[4]) : +(matrix[13] || matrix[5])\n  return ret\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"rootDir\": \".\",\n    \"outDir\": \"dist\",\n    \"moduleResolution\": \"node\",\n    \"paths\": {\n      \"@better-scroll/*\": [\"packages/*/src\"]\n    },\n    \"target\": \"es5\",\n    \"module\": \"es2015\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2017\", \"dom\"],\n    \"esModuleInterop\": true,\n    \"strictPropertyInitialization\": false,\n    \"strict\": true,\n    \"preserveSymlinks\": true,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": false,\n    \"typeRoots\": [\"node_modules/@types\"]\n  },\n  \"include\": [\"packages/*/src\"],\n  \"exclude\": [\"packages/*/src/**/__tests__\", \"packages/*/src/**/__mocks__\"]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "\n{\n  \"extends\": [\n    \"tslint-config-standard\",\n    \"tslint-config-prettier\"\n  ],\n  \"rules\": {\n    \"max-line-length\": {\n        \"options\": [140]\n    },\n    \"no-empty\": false,\n    \"deprecation\": false,\n    \"strict-type-predicates\": false,\n    \"no-angle-bracket-type-assertion\": false,\n    \"no-unnecessary-type-assertion\": false,\n    \"no-unused-expression\": false,\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-conditional-assignment\": true,\n    \"no-consecutive-blank-lines\": false,\n    \"no-console\": false,\n    \"await-promise\": false\n  }\n}\n"
  }
]