Showing preview only (1,434K chars total). Download the full file or copy to clipboard to get everything.
Repository: ustbhuangyi/better-scroll
Branch: dev
Commit: f87fbd161d4b
Files: 480
Total size: 1.3 MB
Directory structure:
gitextract_mo2zutw5/
├── .editorconfig
├── .eslintrc.js
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── .huskyrc.json
├── .npmignore
├── .travis.yml
├── .yarnrc
├── LICENSE
├── README.md
├── README_zh-CN.md
├── jest-e2e.config.js
├── jest-puppeteer.config.js
├── jest.config.js
├── lerna.json
├── package.json
├── packages/
│ ├── better-scroll/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ └── index.ts
│ ├── core/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── BScroll.ts
│ │ ├── Instance.ts
│ │ ├── Options.ts
│ │ ├── __mocks__/
│ │ │ ├── Options.ts
│ │ │ └── index.ts
│ │ ├── __tests__/
│ │ │ ├── Options.spec.ts
│ │ │ ├── __utils__/
│ │ │ │ ├── event.ts
│ │ │ │ └── layout.ts
│ │ │ └── index.spec.ts
│ │ ├── animater/
│ │ │ ├── Animation.ts
│ │ │ ├── Base.ts
│ │ │ ├── Transition.ts
│ │ │ ├── __mocks__/
│ │ │ │ ├── Animation.ts
│ │ │ │ ├── Transition.ts
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── Animation.spec.ts
│ │ │ │ ├── Transition.spec.ts
│ │ │ │ └── index.spec.ts
│ │ │ └── index.ts
│ │ ├── base/
│ │ │ ├── ActionsHandler.ts
│ │ │ ├── __mocks__/
│ │ │ │ └── ActionsHandler.ts
│ │ │ └── __tests__/
│ │ │ └── ActionsHandler.spec.ts
│ │ ├── index.ts
│ │ ├── scroller/
│ │ │ ├── Actions.ts
│ │ │ ├── Behavior.ts
│ │ │ ├── DirectionLock.ts
│ │ │ ├── Scroller.ts
│ │ │ ├── __mocks__/
│ │ │ │ ├── Actions.ts
│ │ │ │ ├── Behavior.ts
│ │ │ │ ├── DirectionLock.ts
│ │ │ │ └── Scroller.ts
│ │ │ ├── __tests__/
│ │ │ │ ├── Actions.spec.ts
│ │ │ │ ├── Behavior.spec.ts
│ │ │ │ ├── DirectionLock.spec.ts
│ │ │ │ ├── Scroller.spec.ts
│ │ │ │ └── createOptions.spec.ts
│ │ │ └── createOptions.ts
│ │ ├── translater/
│ │ │ ├── __mocks__/
│ │ │ │ └── index.ts
│ │ │ ├── __tests__/
│ │ │ │ └── index.spec.ts
│ │ │ └── index.ts
│ │ └── utils/
│ │ ├── __tests__/
│ │ │ └── bubbling.spec.ts
│ │ ├── bubbling.ts
│ │ ├── compare.ts
│ │ ├── compat.ts
│ │ └── typesHelper.ts
│ ├── examples/
│ │ ├── README.md
│ │ ├── build/
│ │ │ ├── vue-example-build.js
│ │ │ └── vue-webpack.conf.js
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── static/
│ │ │ └── css/
│ │ │ ├── github-light.css
│ │ │ ├── normalize.css
│ │ │ ├── reset.css
│ │ │ └── stylesheet.css
│ │ ├── vue/
│ │ │ ├── App.vue
│ │ │ ├── components/
│ │ │ │ ├── compose/
│ │ │ │ │ ├── pullup-pulldown-outnested.vue
│ │ │ │ │ ├── pullup-pulldown-slide.vue
│ │ │ │ │ ├── pullup-pulldown.vue
│ │ │ │ │ └── slide-nested.vue
│ │ │ │ ├── core/
│ │ │ │ │ ├── default.vue
│ │ │ │ │ ├── dynamic-content.vue
│ │ │ │ │ ├── freescroll.vue
│ │ │ │ │ ├── horizontal-rotated.vue
│ │ │ │ │ ├── horizontal.vue
│ │ │ │ │ ├── specified-content.vue
│ │ │ │ │ └── vertical-rotated.vue
│ │ │ │ ├── form/
│ │ │ │ │ └── textarea.vue
│ │ │ │ ├── indicators/
│ │ │ │ │ ├── minimap.vue
│ │ │ │ │ └── parallax-scroll.vue
│ │ │ │ ├── infinity/
│ │ │ │ │ ├── data/
│ │ │ │ │ │ └── message.json
│ │ │ │ │ └── default.vue
│ │ │ │ ├── mouse-wheel/
│ │ │ │ │ ├── horizontal-scroll.vue
│ │ │ │ │ ├── horizontal-slide.vue
│ │ │ │ │ ├── picker.vue
│ │ │ │ │ ├── pulldown.vue
│ │ │ │ │ ├── pullup.vue
│ │ │ │ │ ├── vertical-scroll.vue
│ │ │ │ │ └── vertical-slide.vue
│ │ │ │ ├── movable/
│ │ │ │ │ ├── default.vue
│ │ │ │ │ ├── multi-content-scale.vue
│ │ │ │ │ ├── multi-content.vue
│ │ │ │ │ └── scale.vue
│ │ │ │ ├── nested-scroll/
│ │ │ │ │ ├── horizontal-in-vertical.vue
│ │ │ │ │ ├── horizontal.vue
│ │ │ │ │ ├── triple-vertical.vue
│ │ │ │ │ └── vertical.vue
│ │ │ │ ├── observe-dom/
│ │ │ │ │ └── default.vue
│ │ │ │ ├── observe-image/
│ │ │ │ │ └── default.vue
│ │ │ │ ├── picker/
│ │ │ │ │ ├── double-column.vue
│ │ │ │ │ ├── linkage-column.vue
│ │ │ │ │ └── one-column.vue
│ │ │ │ ├── pulldown/
│ │ │ │ │ ├── default.vue
│ │ │ │ │ └── sina-weibo.vue
│ │ │ │ ├── pullup/
│ │ │ │ │ └── default.vue
│ │ │ │ ├── scrollbar/
│ │ │ │ │ ├── custom.vue
│ │ │ │ │ ├── horizontal.vue
│ │ │ │ │ ├── mousewheel.vue
│ │ │ │ │ └── vertical.vue
│ │ │ │ ├── slide/
│ │ │ │ │ ├── banner.vue
│ │ │ │ │ ├── dynamic.vue
│ │ │ │ │ ├── fullpage.vue
│ │ │ │ │ ├── specified-index.vue
│ │ │ │ │ └── vertical.vue
│ │ │ │ └── zoom/
│ │ │ │ └── default.vue
│ │ │ ├── index.html
│ │ │ ├── main.js
│ │ │ ├── pages/
│ │ │ │ ├── compose-entry.vue
│ │ │ │ ├── core-entry.vue
│ │ │ │ ├── form-entry.vue
│ │ │ │ ├── indicators-entry.vue
│ │ │ │ ├── infinity-entry.vue
│ │ │ │ ├── mouse-wheel-entry.vue
│ │ │ │ ├── movable-entry.vue
│ │ │ │ ├── nested-scroll-entry.vue
│ │ │ │ ├── observe-dom-entry.vue
│ │ │ │ ├── observe-image-entry.vue
│ │ │ │ ├── picker-entry.vue
│ │ │ │ ├── pulldown-entry.vue
│ │ │ │ ├── pullup-entry.vue
│ │ │ │ ├── scrollbar-entry.vue
│ │ │ │ ├── slide-entry.vue
│ │ │ │ └── zoom-entry.vue
│ │ │ └── router/
│ │ │ └── index.js
│ │ └── vue-release.sh
│ ├── indicators/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __mocks__/
│ │ │ └── indicator.ts
│ │ ├── __tests__/
│ │ │ ├── index.spec.ts
│ │ │ └── indicator.spec.ts
│ │ ├── index.ts
│ │ ├── indicator.ts
│ │ └── types.ts
│ ├── infinity/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── DataManager.ts
│ │ ├── DomManager.ts
│ │ ├── IndexCalculator.ts
│ │ ├── Tombstone.ts
│ │ ├── __tests__/
│ │ │ ├── DataManager.spec.ts
│ │ │ ├── IndexCalculator.spec.ts
│ │ │ └── __utils__/
│ │ │ ├── FakeList.ts
│ │ │ └── constans.ts
│ │ └── index.ts
│ ├── mouse-wheel/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ └── index.ts
│ ├── movable/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ ├── nested-scroll/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── BScrollFamily.ts
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ ├── observe-dom/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ └── index.ts
│ ├── observe-image/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ └── index.ts
│ ├── pull-down/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ ├── pull-up/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ ├── react-examples/
│ │ ├── README.md
│ │ ├── config-overrides.js
│ │ ├── package.json
│ │ ├── public/
│ │ │ ├── index.html
│ │ │ └── static/
│ │ │ └── css/
│ │ │ ├── github-light.css
│ │ │ ├── normalize.css
│ │ │ ├── reset.css
│ │ │ └── stylesheet.css
│ │ └── src/
│ │ ├── App.js
│ │ ├── index.js
│ │ ├── index.styl
│ │ ├── pages/
│ │ │ ├── compose/
│ │ │ │ ├── components/
│ │ │ │ │ ├── pullup-pulldown-outnested.js
│ │ │ │ │ ├── pullup-pulldown-slide.js
│ │ │ │ │ ├── pullup-pulldown.js
│ │ │ │ │ └── slide-nested.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── core/
│ │ │ │ ├── components/
│ │ │ │ │ ├── default.js
│ │ │ │ │ ├── dynamic-content.js
│ │ │ │ │ ├── freescroll.js
│ │ │ │ │ ├── horizontal-rotated.js
│ │ │ │ │ ├── horizontal.js
│ │ │ │ │ ├── specified-content.js
│ │ │ │ │ └── vertical-rotated.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── form/
│ │ │ │ ├── components/
│ │ │ │ │ └── textarea.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── indicators/
│ │ │ │ ├── components/
│ │ │ │ │ ├── minimap.js
│ │ │ │ │ └── parallax-scroll.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── infinity/
│ │ │ │ ├── data/
│ │ │ │ │ └── message.json
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── mouse-wheel/
│ │ │ │ ├── components/
│ │ │ │ │ ├── horizontal-scroll.js
│ │ │ │ │ ├── horizontal-slide.js
│ │ │ │ │ ├── picker.js
│ │ │ │ │ ├── pulldown.js
│ │ │ │ │ ├── pullup.js
│ │ │ │ │ ├── vertical-scroll.js
│ │ │ │ │ └── vertical-slide.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── movable/
│ │ │ │ ├── components/
│ │ │ │ │ ├── default.js
│ │ │ │ │ ├── multi-content-scale.js
│ │ │ │ │ ├── multi-content.js
│ │ │ │ │ └── scale.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── nested-scroll/
│ │ │ │ ├── components/
│ │ │ │ │ ├── horizontal-in-vertical.js
│ │ │ │ │ ├── horizontal.js
│ │ │ │ │ ├── triple-vertical.js
│ │ │ │ │ └── vertical.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── observe-dom/
│ │ │ │ ├── components/
│ │ │ │ │ └── default.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── observe-image/
│ │ │ │ ├── components/
│ │ │ │ │ └── default.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── picker/
│ │ │ │ ├── components/
│ │ │ │ │ ├── double-column.js
│ │ │ │ │ ├── linkage-column.js
│ │ │ │ │ └── one-column.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── pulldown/
│ │ │ │ ├── components/
│ │ │ │ │ ├── default.js
│ │ │ │ │ └── sina-weibo.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── pullup/
│ │ │ │ ├── components/
│ │ │ │ │ └── default.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── scrollbar/
│ │ │ │ ├── components/
│ │ │ │ │ ├── custom.js
│ │ │ │ │ ├── horizontal.js
│ │ │ │ │ ├── mousewheel.js
│ │ │ │ │ └── vertical.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ ├── slide/
│ │ │ │ ├── components/
│ │ │ │ │ ├── banner.js
│ │ │ │ │ ├── dynamic.js
│ │ │ │ │ ├── fullpage.js
│ │ │ │ │ ├── specified-index.js
│ │ │ │ │ └── vertical.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.styl
│ │ │ └── zoom/
│ │ │ ├── components/
│ │ │ │ └── default.js
│ │ │ ├── index.js
│ │ │ └── index.styl
│ │ ├── reportWebVitals.js
│ │ ├── router.js
│ │ └── setupTests.js
│ ├── scroll-bar/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __mocks__/
│ │ │ ├── event-handler.ts
│ │ │ └── indicator.ts
│ │ ├── __tests__/
│ │ │ ├── __snapshots__/
│ │ │ │ └── index.spec.ts.snap
│ │ │ ├── event-handler.spec.ts
│ │ │ ├── index.spec.ts
│ │ │ └── indicator.spec.ts
│ │ ├── event-handler.ts
│ │ ├── index.ts
│ │ └── indicator.ts
│ ├── shared-utils/
│ │ ├── README.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── Touch.ts
│ │ ├── __mocks__/
│ │ │ ├── dom.ts
│ │ │ └── ease.ts
│ │ ├── __tests__/
│ │ │ ├── debug.spec.ts
│ │ │ ├── dom.spec.ts
│ │ │ ├── ease.spec.ts
│ │ │ ├── events.spec.ts
│ │ │ ├── lang.spec.ts
│ │ │ ├── propertiesProxy.spec.ts
│ │ │ └── raf.spec.ts
│ │ ├── debug.ts
│ │ ├── dom.ts
│ │ ├── ease.ts
│ │ ├── enums.ts
│ │ ├── env.ts
│ │ ├── events.ts
│ │ ├── index.ts
│ │ ├── lang.ts
│ │ ├── propertiesProxy.ts
│ │ ├── raf.ts
│ │ └── types.ts
│ ├── slide/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── PagesMatrix.ts
│ │ ├── SlidePages.ts
│ │ ├── __mocks__/
│ │ │ ├── PagesMatrix.ts
│ │ │ └── SlidePages.ts
│ │ ├── __tests__/
│ │ │ ├── PagesMatrix.spec.ts
│ │ │ ├── SlidePages.spec.ts
│ │ │ └── index.spec.ts
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ ├── vuepress-docs/
│ │ ├── docs/
│ │ │ ├── .vuepress/
│ │ │ │ ├── components/
│ │ │ │ │ ├── demo.vue
│ │ │ │ │ └── qrcode.vue
│ │ │ │ ├── config.js
│ │ │ │ ├── enhanceApp.js
│ │ │ │ ├── nav/
│ │ │ │ │ ├── en-US.js
│ │ │ │ │ └── zh-CN.js
│ │ │ │ ├── plugins/
│ │ │ │ │ └── extract-code.js
│ │ │ │ ├── public/
│ │ │ │ │ └── assets/
│ │ │ │ │ └── stylus/
│ │ │ │ │ └── index.styl
│ │ │ │ └── sidebar/
│ │ │ │ ├── FAQ.js
│ │ │ │ ├── guide.js
│ │ │ │ └── plugins.js
│ │ │ ├── en-US/
│ │ │ │ ├── FAQ/
│ │ │ │ │ ├── README.md
│ │ │ │ │ └── diagnosis.md
│ │ │ │ ├── README.md
│ │ │ │ ├── guide/
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── base-scroll-api.md
│ │ │ │ │ ├── base-scroll-options.md
│ │ │ │ │ ├── base-scroll.md
│ │ │ │ │ ├── how-to-install.md
│ │ │ │ │ └── use.md
│ │ │ │ └── plugins/
│ │ │ │ ├── README.md
│ │ │ │ ├── how-to-write.md
│ │ │ │ ├── indicators.md
│ │ │ │ ├── infinity.md
│ │ │ │ ├── mouse-wheel.md
│ │ │ │ ├── movable.md
│ │ │ │ ├── nested-scroll.md
│ │ │ │ ├── observe-dom.md
│ │ │ │ ├── observe-image.md
│ │ │ │ ├── pulldown.md
│ │ │ │ ├── pullup.md
│ │ │ │ ├── scroll-bar.md
│ │ │ │ ├── slide.md
│ │ │ │ ├── wheel.md
│ │ │ │ └── zoom.md
│ │ │ └── zh-CN/
│ │ │ ├── FAQ/
│ │ │ │ ├── README.md
│ │ │ │ └── diagnosis.md
│ │ │ ├── README.md
│ │ │ ├── guide/
│ │ │ │ ├── README.md
│ │ │ │ ├── base-scroll-api.md
│ │ │ │ ├── base-scroll-options.md
│ │ │ │ ├── base-scroll.md
│ │ │ │ ├── how-to-install.md
│ │ │ │ └── use.md
│ │ │ └── plugins/
│ │ │ ├── README.md
│ │ │ ├── compose-plugins.md
│ │ │ ├── how-to-write.md
│ │ │ ├── indicators.md
│ │ │ ├── infinity.md
│ │ │ ├── mouse-wheel.md
│ │ │ ├── movable.md
│ │ │ ├── nested-scroll.md
│ │ │ ├── observe-dom.md
│ │ │ ├── observe-image.md
│ │ │ ├── pulldown.md
│ │ │ ├── pullup.md
│ │ │ ├── scroll-bar.md
│ │ │ ├── slide.md
│ │ │ ├── wheel.md
│ │ │ └── zoom.md
│ │ ├── docs-release.sh
│ │ └── package.json
│ ├── wheel/
│ │ ├── README.md
│ │ ├── README_zh-CN.md
│ │ ├── package.json
│ │ └── src/
│ │ ├── __tests__/
│ │ │ └── index.spec.ts
│ │ ├── index.ts
│ │ └── propertiesConfig.ts
│ └── zoom/
│ ├── README.md
│ ├── README_zh-CN.md
│ ├── package.json
│ └── src/
│ ├── __tests__/
│ │ ├── __utils__/
│ │ │ └── util.ts
│ │ └── index.spec.ts
│ ├── index.ts
│ └── propertiesConfig.ts
├── postcss.config.js
├── scripts/
│ ├── build.js
│ ├── checkYarn.js
│ └── release.js
├── test-dts/
│ ├── core.test-d.ts
│ ├── index.d.ts
│ ├── plugin.test-d.ts
│ ├── tsconfig.json
│ └── util.d.ts
├── tests/
│ ├── dts/
│ │ └── index.d.ts
│ ├── e2e/
│ │ ├── compose-plugins/
│ │ │ ├── compose-plugins.e2e.ts
│ │ │ ├── pullup-pulldown-nested.e2e.ts
│ │ │ ├── pullup-pulldown-slide.e2e.ts
│ │ │ ├── pullup-pulldown.e2e.ts
│ │ │ └── slide-nested.e2e.ts
│ │ ├── core/
│ │ │ └── corescroll.e2e.ts
│ │ ├── form/
│ │ │ └── textarea.e2e.ts
│ │ ├── homepage.e2e.ts
│ │ ├── indicators/
│ │ │ ├── minimap.e2e.ts
│ │ │ └── parallax-scrolling.e2e.ts
│ │ ├── infinity/
│ │ │ └── infinity.e2e.ts
│ │ ├── mousewheel/
│ │ │ └── mousewheel.e2e.ts
│ │ ├── movable/
│ │ │ ├── default.e2e.ts
│ │ │ ├── multi-content-scale.e2e.ts
│ │ │ ├── multi-content.e2e.ts
│ │ │ └── scaled.e2e.ts
│ │ ├── nested-scroll/
│ │ │ ├── horizontal-in-vertical.e2e.ts
│ │ │ ├── horizontal.e2e.ts
│ │ │ ├── triple-vertical.e2e.ts
│ │ │ └── vertical.e2e.ts
│ │ ├── observe-dom/
│ │ │ └── observe-dom.e2e.ts
│ │ ├── observe-image/
│ │ │ └── observe-image.e2e.ts
│ │ ├── picker/
│ │ │ ├── double-column.e2e.ts
│ │ │ ├── linkage-column.e2e.ts
│ │ │ └── one-column.e2e.ts
│ │ ├── pulldown/
│ │ │ ├── default.e2e.ts
│ │ │ └── sina.e2e.ts
│ │ ├── pullup/
│ │ │ └── default.e2e.ts
│ │ ├── scrollbar/
│ │ │ ├── custom.e2e.ts
│ │ │ ├── horizontal.e2e.ts
│ │ │ ├── mousewheel.e2e.ts
│ │ │ └── vertical.e2e.ts
│ │ ├── slide/
│ │ │ ├── banner.e2e.ts
│ │ │ ├── dynamic.e2e.ts
│ │ │ ├── fullpage.e2e.ts
│ │ │ ├── specifiedIndex.e2e.ts
│ │ │ └── vertical.e2e.ts
│ │ └── zoom/
│ │ └── zoom.e2e.ts
│ └── util/
│ ├── extendMouseWheel.ts
│ ├── extendTouch.ts
│ ├── getScale.ts
│ └── getTranslate.ts
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
env: {
browser: true,
},
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'indent': 0,
'no-tabs': 0,
'space-before-function-paren': 0,
'eol-last': 0,
'no-unused-expressions': 0
}
};
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
IMPORTANT: Please use the following link to create a new issue:
https://cube-ui.github.io/cube-issue-helper/?repo=ustbhuangyi/better-scroll
If your issue was not created using the app above, it will be closed immediately.
中文用户请注意:
请使用上面的链接来创建新的 issue。如果不是用上述工具创建的 issue 会被自动关闭。
-->
================================================
FILE: .gitignore
================================================
.bin/
node_modules/
.idea/
.DS_Store
.npm-debug.log
coverage/
.rpt2_cache
.vscode/
dist/
yarn-debug.log*
yarn-error.log*
yarn.lock
================================================
FILE: .gitpod.Dockerfile
================================================
FROM gitpod/workspace-full
RUN sudo apt-get update && \
sudo apt-get install -y \
ca-certificates \
fonts-liberation \
libappindicator3-1 \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm1 \
libgcc1 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
wget \
xdg-utils && \
sudo rm -rf /var/lib/apt/lists/*
RUN sudo apt-get update && \
sudo apt-get install -yq chromium-browser && \
sudo rm -rf /var/lib/apt/lists/*
ENV GITPOD=true
================================================
FILE: .gitpod.yml
================================================
image:
file: .gitpod.Dockerfile
tasks:
- command: gp await-port 8080 && sleep 3 && gp preview $(gp url 8080)/docs
- name: Dev
init: yarn install && gp sync-done install
command: yarn vue:dev
- name: Docs
init: gp sync-await install && yarn docs:build
command: yarn docs:dev
openMode: split-right
ports:
- port: 8080
onOpen: ignore
- port: 8932
onOpen: open-preview
================================================
FILE: .huskyrc.json
================================================
{
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
================================================
FILE: .npmignore
================================================
build/
config/
docs/
doc/
example/
static/
.idea/
.github/
postcss.config.js
.bin/
.DS_Store
.npm-debug.log
.babelrc
.npmignore
.editorconfig
.eslintrc.js
.tsconfig.json
package-lock.json
================================================
FILE: .travis.yml
================================================
language: node_js
sudo: false
cache:
directories:
- node_modules
node_js:
- "lts/*"
branches:
only:
- master
- dev
script:
- npm test
- ./node_modules/.bin/codecov
================================================
FILE: .yarnrc
================================================
registry "https://registry.npmjs.org"
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 HuangYi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# better-scroll
<img src="https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg">
[](https://www.npmjs.com/package/better-scroll) [](https://www.npmjs.com/package/better-scroll) [](https://travis-ci.org/ustbhuangyi/better-scroll) [](http://packagequality.com/#?package=better-scroll) [](http://codecov.io/github/ustbhuangyi/better-scroll) [](https://gitpod.io/from-referrer/)
[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/README_zh-CN.md)
[1.x Docs](https://better-scroll.github.io/docs-v1/)
[2.x Docs](https://better-scroll.github.io/docs/en-US/)
[2.x Demo](https://better-scroll.github.io/examples/)
> **Note**: `1.x` is not maintained. please migrate your version as soon as possible
# Install
```bash
npm install better-scroll -S # install 2.x,with full-featured plugin.
npm install @better-scroll/core # only CoreScroll
```
```js
import BetterScroll from 'better-scroll'
let bs = new BetterScroll('.wrapper', {
movable: true,
zoom: true
})
import BScroll from '@better-scroll/core'
let bs = new BScroll('.wrapper', {})
```
# CDN
BetterScroll with full-featured plugin.
```html
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.js"></script>
<!-- minify -->
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js"></script>
```
```js
let wrapper = document.getElementById("wrapper")
let bs = BetterScroll.createBScroll(wrapper, {})
```
Only CoreScroll
```html
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>
<!-- minify -->
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.min.js"></script>
```
```js
let wrapper = document.getElementById("wrapper")
let bs = new BScroll(wrapper, {})
```
## What is BetterScroll ?
BetterScroll 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.
BetterScroll is implemented with plain JavaScript, which means it's dependency free.
## Getting started
The most common application scenario of BetterScroll is list scrolling. Let's see its HTML:
```html
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
<!-- you can put some other DOMs here, it won't affect the scrolling
</div>
```
In 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.
The simplest initialization code is as follow:
```javascript
import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)
```
BetterScroll 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.
## The principle of scrolling
Many developers have used BetterScroll, but the most common problem they have met is:
> I have initiated BetterScroll, but the content can't scroll.
The 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.
The principle of BetterScroll is samed as the browser. We can feel about this more obviously using a picture:

The 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.
## Plugins
Enhance the ability of BetterScroll core scroll through plugins, such as
```js
import BScroll from '@better-scroll/core'
import PullUp from '@better-scroll/pull-up'
let bs = new BScroll('.wrapper', {
pullUpLoad: true
})
```
Please see for details, [Plugins](https://better-scroll.github.io/docs/en-US/plugins/).
## Using BetterScroll with MVVM frameworks
I 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.
A fantastic mobile ui lib implement by Vue: [cube-ui](https://github.com/didi/cube-ui/)
## Contributing
### Online one-click setup
You 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:
- clone the `better-scroll` repo.
- install all of the dependencies.
- run `yarn vue:dev`,
- run `yarn docs:build` and `yarn docs:dev`.
[](https://gitpod.io/from-referrer/)
## Using BetterScroll in the real project
If you want to learn how to use BetterScroll in the real project,you can learn my two practical courses(in Chinese)。
[High imitating starvation takeout practical course base on Vue.js](https://coding.imooc.com/class/74.html)
[Project demo address](http://ustbhuangyi.com/sell/)

[Music App advanced practical course base on Vue.js](http://coding.imooc.com/class/107.html)
[Project demo address](http://ustbhuangyi.com/music/)

================================================
FILE: README_zh-CN.md
================================================
# better-scroll
<img src="https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg">
[](https://www.npmjs.com/package/better-scroll) [](https://www.npmjs.com/package/better-scroll) [](https://travis-ci.org/ustbhuangyi/better-scroll) [](http://packagequality.com/#?package=better-scroll) [](http://codecov.io/github/ustbhuangyi/better-scroll)
[1.x Docs](https://better-scroll.github.io/docs-v1/)
[2.x Docs](https://better-scroll.github.io/docs/zh-CN/)
> **注意**:1.x 的代码已经不维护,请尽早升级版本。
# puppeteer 安装
BetterScroll 依赖 puppeteer,在国内下载 puppeteer 容易失败,可以设置 puppeteer 的淘宝镜像。
```sh
PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors yarn
```
# 安装
```bash
npm install better-scroll -S # 安装带有所有插件的 BetterScroll
npm install @better-scroll/core # 核心滚动,大部分情况可能只需要一个简单的滚动
```
```js
import BetterScroll from 'better-scroll'
let bs = new BetterScroll('.wrapper', {
movable: true,
zoom: true
})
import BScroll from '@better-scroll/core'
let bs = new BScroll('.wrapper', {})
```
# CDN
带有所有插件的 BetterScroll
```js
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.js"></script>
// minify
<script src="https://unpkg.com/better-scroll@latest/dist/better-scroll.min.js"></script>
let wrapper = document.getElementById("wrapper")
let bs = BetterScroll.createBScroll(wrapper, {})
```
不带有任何插件的 CoreScroll
```js
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>
// minify
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.min.js"></script>
let wrapper = document.getElementById("wrapper")
let bs = new BScroll(wrapper, {})
```
# BetterScroll 是什么
BetterScroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 [iscroll](https://github.com/cubiq/iscroll) 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。
BetterScroll 是使用纯 JavaScript 实现的,这意味着它是无依赖的。
## 起步
BetterScroll 最常见的应用场景是列表滚动,我们来看一下它的 html 结构。
```html
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
<!-- 这里可以放一些其它的 DOM,但不会影响滚动 -->
</div>
```
上面的代码中 BetterScroll 是作用在外层 wrapper 容器上的,滚动的部分是 content 元素。这里要注意的是,BetterScroll 默认处理容器(wrapper)的第一个子元素(content)的滚动,其它的元素都会被忽略。不过对于 BetterScroll v2.0.4 版本,可以通过 specifiedIndexAsContent 配置项来指定 content,详细的请参考文档。
最简单的初始化代码如下:
``` js
import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)
```
BetterScroll 提供了一个类,实例化的第一个参数是一个原生的 DOM 对象。当然,如果传递的是一个字符串,BetterScroll 内部会尝试调用 querySelector 去获取这个 DOM 对象。
## 滚动原理
很多人已经用过 BetterScroll,我收到反馈最多的问题是:
> BetterScroll 初始化了, 但是没法滚动。
不能滚动是现象,我们得搞清楚这其中的根本原因。在这之前,我们先来看一下浏览器的滚动原理:
浏览器的滚动条大家都会遇到,当页面内容的高度超过视口高度的时候,会出现纵向滚动条;当页面内容的宽度超过视口宽度的时候,会出现横向滚动条。也就是当我们的视口展示不下内容的时候,会通过滚动条的方式让用户滚动屏幕看到剩余的内容。
BetterScroll 也是一样的原理,我们可以用一张图更直观的感受一下:

绿色部分为 wrapper,也就是父容器,它会有**固定的高度**。黄色部分为 content,它是父容器的**第一个子元素**,它的高度会随着内容的大小而撑高。那么,当 content 的高度不超过父容器的高度,是不能滚动的,而它一旦超过了父容器的高度,我们就可以滚动内容区了,这就是 BetterScroll 的滚动原理。
## 插件
通过插件,增强 BetterScroll core scroll 的能力,比如
```js
import BScroll from '@better-scroll/core'
import PullUp from '@better-scroll/pull-up'
let bs = new BScroll('.wrapper', {
pullUpLoad: true
})
```
详细请看[插件文档](https://better-scroll.github.io/docs/zh-CN/plugins/)
## BetterScroll 在 MVVM 框架的应用
我之前写过一篇[当 BetterScroll 遇见 Vue](https://zhuanlan.zhihu.com/p/27407024),也希望大家投稿,分享一下 BetterScroll 在其它框架下的使用心得。
一款超赞的基于 Vue 实现的组件库 [cube-ui](https://github.com/didi/cube-ui/)。
## BetterScroll 在实战项目中的运用
如果你想学习 BetterScroll 在实战项目中的运用,也可以去学习我的 2 门实战课程。
[Vue.js 高仿外卖饿了么实战课程](https://coding.imooc.com/class/74.html)
[项目演示地址](http://ustbhuangyi.com/sell/)

[Vue.js 音乐 App 高级实战课程](http://coding.imooc.com/class/107.html)
[项目演示地址](http://ustbhuangyi.com/music/)

================================================
FILE: jest-e2e.config.js
================================================
module.exports = {
"verbose": true,
"roots": ["<rootDir>"],
"cache": false,
"globals": {
"ts-jest": {
"diagnostics": false
}
},
"preset": "jest-puppeteer",
"testMatch": ["**/tests/e2e/**/*.e2e.ts"],
"transform": {
".ts": "ts-jest"
},
"moduleFileExtensions": [
"ts",
"js"
]
}
================================================
FILE: jest-puppeteer.config.js
================================================
module.exports = {
launch: {
headless: process.env.GITPOD !== undefined,
defaultViewport: {
width: 375,
height: 667,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true
},
args: ['--disable-infobars', '--no-sandbox', '--disable-setuid-sandbox'],
}
}
================================================
FILE: jest.config.js
================================================
module.exports = {
"verbose": true,
globals: {},
"roots": ["<rootDir>"],
"cache": false,
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|js)$",
"testPathIgnorePatterns": [
'/__tests__/__utils__'
],
"transform": {
".ts": "ts-jest"
},
"testEnvironment": "jsdom",
"moduleFileExtensions": [
"ts",
"js"
],
"moduleNameMapper": {
'^@better-scroll/(.*)/(.*)$': '<rootDir>/packages/$1/$2',
'^@better-scroll/(.*)$': '<rootDir>/packages/$1/src/index',
'^@/(.*)$': '<rootDir>/$1'
},
"coverageDirectory": "<rootDir>/tests/coverage",
"coveragePathIgnorePatterns": [
"/test/",
"/__tests__/"
],
"coverageReporters": ['json', 'text', 'lcov', 'clover']
}
================================================
FILE: lerna.json
================================================
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
],
"version": "2.5.1"
}
================================================
FILE: package.json
================================================
{
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"bootstrap": "lerna bootstrap",
"packages:build": "node scripts/build.js",
"packages:release": "node scripts/release.js",
"docs:dev": "lerna run --stream --scope vuepress-docs docs:dev",
"docs:build": "lerna run --stream --scope vuepress-docs docs:build",
"docs:release": "lerna run --stream --scope vuepress-docs docs:release",
"vue:dev": "lerna run --stream --scope examples vue:dev",
"vue:build": "lerna run --stream --scope examples vue:build",
"vue:release": "lerna run --stream --scope examples vue:release",
"react:dev": "lerna run --stream --scope react-examples dev",
"react:build": "lerna run --stream --scope react-examples build",
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
"test": "jest --coverage && yarn test:tsd",
"test:e2e": "jest --config=jest-e2e.config.js --runInBand",
"test:tsd": "tsc -p ./test-dts/tsconfig.json",
"vue:test:e2e": "lerna run --stream --scope examples vue:test:e2e",
"cm": "git-cz",
"preinstall": "node ./scripts/checkYarn.js",
"postinstall": "yarn bootstrap"
},
"lint-staged": {
"*.ts": [
"prettier --write"
]
},
"prettier": {
"semi": false,
"singleQuote": true
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"homepage": "https://github.com/ustbhuangyi/better-scroll",
"keywords": [
"scroll",
"iscroll",
"javascript",
"typescript",
"ios"
],
"devDependencies": {
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2",
"@types/jest": "^26.0.10",
"@types/node": "^14.6.0",
"@types/puppeteer": "^3.0.1",
"@vuepress/plugin-back-to-top": "^1.8.0",
"@vuepress/plugin-medium-zoom": "^1.5.4",
"codecov": "^3.5.0",
"commitizen": "^4.1.5",
"coveralls": "^3.0.2",
"cross-env": "^7.0.2",
"cz-conventional-changelog": "^3.2.0",
"execa": "^4.0.3",
"husky": "^4.2.5",
"inquirer": "^7.3.3",
"jest": "^26.0.1",
"jest-config": "^26.4.2",
"jest-puppeteer": "4.4.0",
"lerna": "^3.14.1",
"lint-staged": "^10.2.11",
"ora": "^5.0.0",
"prettier": "^2.0.5",
"puppeteer": "^5.2.1",
"rimraf": "^3.0.2",
"rollup": "^2.23.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.6.2",
"rollup-plugin-typescript2": "^0.27.1",
"rollup-plugin-uglify": "^6.0.1",
"semver": "^7.3.2",
"ts-jest": "^26.2.0",
"ts-loader": "^8.0.2",
"ts-node": "^9.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.15.0",
"tslint-config-standard": "^9.0.0",
"typescript": "4.0.2",
"vconsole": "^3.3.4",
"zlib": "^1.0.5"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8",
"Android >= 4.0",
"iOS >= 8"
],
"name": "better-scroll"
}
================================================
FILE: packages/better-scroll/README.md
================================================
# better-scroll
[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/better-scroll/README_zh-CN.md)
BetterScroll with full plugin capabilities, don't care about the details of various plugin registrations.
## Usage
```js
import BScroll from 'better-scroll'
const bs = new BScroll('.wrapper', {
pullUpLoad: true,
scrollbar: true,
pullDownRefresh: true
// and so on
})
```
================================================
FILE: packages/better-scroll/README_zh-CN.md
================================================
# better-scroll
具备完整插件能力的 BetterScroll,不用关心各种插件注册的细节。
## 使用
```js
import BScroll from 'better-scroll'
const bs = new BScroll('.wrapper', {
pullUpLoad: true,
scrollbar: true,
pullDownRefresh: true
// and so on
})
```
================================================
FILE: packages/better-scroll/package.json
================================================
{
"name": "better-scroll",
"version": "2.5.1",
"description": "Full-featured BetterScroll",
"author": {
"name": "jizhi",
"email": "theniceangel@163.com"
},
"publishConfig": {
"access": "public"
},
"main": "dist/better-scroll.js",
"module": "dist/better-scroll.esm.js",
"typings": "dist/types/index.d.ts",
"scripts": {},
"bugs": {
"url": "https://github.com/ustbhuangyi/better-scroll/issues"
},
"homepage": "https://github.com/ustbhuangyi/better-scroll",
"keywords": [
"scroll",
"iscroll",
"javascript",
"typescript",
"ios"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:ustbhuangyi/better-scroll.git",
"directory": "packages/better-scroll"
},
"dependencies": {
"@better-scroll/core": "^2.5.1",
"@better-scroll/indicators": "^2.5.1",
"@better-scroll/infinity": "^2.5.1",
"@better-scroll/mouse-wheel": "^2.5.1",
"@better-scroll/movable": "^2.5.1",
"@better-scroll/nested-scroll": "^2.5.1",
"@better-scroll/observe-dom": "^2.5.1",
"@better-scroll/observe-image": "^2.5.1",
"@better-scroll/pull-down": "^2.5.1",
"@better-scroll/pull-up": "^2.5.1",
"@better-scroll/scroll-bar": "^2.5.1",
"@better-scroll/slide": "^2.5.1",
"@better-scroll/wheel": "^2.5.1",
"@better-scroll/zoom": "^2.5.1"
},
"gitHead": "f441227b6137d44ba0b44b97ed4cd49de9386130"
}
================================================
FILE: packages/better-scroll/src/index.ts
================================================
import BScroll from '@better-scroll/core'
import MouseWheel from '@better-scroll/mouse-wheel'
import ObserveDom from '@better-scroll/observe-dom'
import PullDownRefresh from '@better-scroll/pull-down'
import PullUpLoad from '@better-scroll/pull-up'
import ScrollBar from '@better-scroll/scroll-bar'
import Slide from '@better-scroll/slide'
import Wheel from '@better-scroll/wheel'
import Zoom from '@better-scroll/zoom'
import NestedScroll from '@better-scroll/nested-scroll'
import InfinityScroll from '@better-scroll/infinity'
import Movable from '@better-scroll/movable'
import ObserveImage from '@better-scroll/observe-image'
import Indicators from '@better-scroll/indicators'
export {
createBScroll,
BScrollInstance,
Options,
CustomOptions,
TranslaterPoint,
MountedBScrollHTMLElement,
Behavior,
Boundary,
CustomAPI
} from '@better-scroll/core'
export {
MouseWheel,
ObserveDom,
PullDownRefresh,
PullUpLoad,
ScrollBar,
Slide,
Wheel,
Zoom,
NestedScroll,
InfinityScroll,
Movable,
ObserveImage,
Indicators
}
BScroll.use(MouseWheel)
.use(ObserveDom)
.use(PullDownRefresh)
.use(PullUpLoad)
.use(ScrollBar)
.use(Slide)
.use(Wheel)
.use(Zoom)
.use(NestedScroll)
.use(InfinityScroll)
.use(Movable)
.use(ObserveImage)
.use(Indicators)
export default BScroll
================================================
FILE: packages/core/README.md
================================================
# @better-scroll/core
[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/core/README_zh-CN.md)
core scroll from BetterScroll.
## Usage
```js
import BScroll from '@better-scroll/core'
const bs = new BScroll('.wrapper', {/* ... */})
```
================================================
FILE: packages/core/README_zh-CN.md
================================================
# @better-scroll/core
核心滚动,实现基础的列表滚动效果。
## 使用
```js
import BScroll from '@better-scroll/core'
const bs = new BScroll('.wrapper', {/* ... */})
```
================================================
FILE: packages/core/package.json
================================================
{
"name": "@better-scroll/core",
"version": "2.5.1",
"description": "Minimalistic core scrolling for BetterScroll, it is pure and tiny",
"author": {
"name": "jizhi",
"email": "theniceangel@163.com"
},
"publishConfig": {
"access": "public"
},
"main": "dist/core.js",
"module": "dist/core.esm.js",
"typings": "dist/types/index.d.ts",
"scripts": {},
"bugs": {
"url": "https://github.com/ustbhuangyi/better-scroll/issues"
},
"homepage": "https://github.com/ustbhuangyi/better-scroll",
"keywords": [
"scroll",
"iscroll",
"javascript",
"typescript",
"ios"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:ustbhuangyi/better-scroll.git",
"directory": "packages/core"
},
"dependencies": {
"@better-scroll/shared-utils": "^2.5.1"
},
"gitHead": "f441227b6137d44ba0b44b97ed4cd49de9386130"
}
================================================
FILE: packages/core/src/BScroll.ts
================================================
import { BScrollInstance, propertiesConfig } from './Instance'
import { Options, DefOptions, OptionsConstructor } from './Options'
import Scroller from './scroller/Scroller'
import {
getElement,
warn,
isUndef,
propertiesProxy,
ApplyOrder,
EventEmitter
} from '@better-scroll/shared-utils'
import { bubbling } from './utils/bubbling'
import { UnionToIntersection } from './utils/typesHelper'
interface PluginCtor {
pluginName: string
applyOrder?: ApplyOrder
new (scroll: BScroll): any
}
interface PluginItem {
name: string
applyOrder?: ApplyOrder.Pre | ApplyOrder.Post
ctor: PluginCtor
}
interface PluginsMap {
[key: string]: boolean
}
interface PropertyConfig {
key: string
sourceKey: string
}
type ElementParam = HTMLElement | string
export interface MountedBScrollHTMLElement extends HTMLElement {
isBScrollContainer?: boolean
}
export class BScrollConstructor<O = {}> extends EventEmitter {
static plugins: PluginItem[] = []
static pluginsMap: PluginsMap = {}
scroller: Scroller
options: OptionsConstructor
hooks: EventEmitter
plugins: { [name: string]: any }
wrapper: HTMLElement
content: HTMLElement;
[key: string]: any
static use(ctor: PluginCtor) {
const name = ctor.pluginName
const installed = BScrollConstructor.plugins.some(
plugin => ctor === plugin.ctor
)
if (installed) return BScrollConstructor
if (isUndef(name)) {
warn(
`Plugin Class must specify plugin's name in static property by 'pluginName' field.`
)
return BScrollConstructor
}
BScrollConstructor.pluginsMap[name] = true
BScrollConstructor.plugins.push({
name,
applyOrder: ctor.applyOrder,
ctor
})
return BScrollConstructor
}
constructor(el: ElementParam, options?: Options & O) {
super([
'refresh',
'contentChanged',
'enable',
'disable',
'beforeScrollStart',
'scrollStart',
'scroll',
'scrollEnd',
'scrollCancel',
'touchEnd',
'flick',
'destroy'
])
const wrapper = getElement(el)
if (!wrapper) {
warn('Can not resolve the wrapper DOM.')
return
}
this.plugins = {}
this.options = new OptionsConstructor().merge(options).process()
if (!this.setContent(wrapper).valid) {
return
}
this.hooks = new EventEmitter([
'refresh',
'enable',
'disable',
'destroy',
'beforeInitialScrollTo',
'contentChanged'
])
this.init(wrapper)
}
setContent(wrapper: MountedBScrollHTMLElement) {
let contentChanged = false
let valid = true
const content = wrapper.children[
this.options.specifiedIndexAsContent
] as HTMLElement
if (!content) {
warn(
'The wrapper need at least one child element to be content element to scroll.'
)
valid = false
} else {
contentChanged = this.content !== content
if (contentChanged) {
this.content = content
}
}
return {
valid,
contentChanged
}
}
private init(wrapper: MountedBScrollHTMLElement) {
this.wrapper = wrapper
// mark wrapper to recognize bs instance by DOM attribute
wrapper.isBScrollContainer = true
this.scroller = new Scroller(wrapper, this.content, this.options)
this.scroller.hooks.on(this.scroller.hooks.eventTypes.resize, () => {
this.refresh()
})
this.eventBubbling()
this.handleAutoBlur()
this.enable()
this.proxy(propertiesConfig)
this.applyPlugins()
// maybe boundary has changed, should refresh
this.refreshWithoutReset(this.content)
const { startX, startY } = this.options
const position = {
x: startX,
y: startY
}
// maybe plugins want to control scroll position
if (
this.hooks.trigger(this.hooks.eventTypes.beforeInitialScrollTo, position)
) {
return
}
this.scroller.scrollTo(position.x, position.y)
}
private applyPlugins() {
const options = this.options
BScrollConstructor.plugins
.sort((a, b) => {
const applyOrderMap = {
[ApplyOrder.Pre]: -1,
[ApplyOrder.Post]: 1
}
const aOrder = a.applyOrder ? applyOrderMap[a.applyOrder] : 0
const bOrder = b.applyOrder ? applyOrderMap[b.applyOrder] : 0
return aOrder - bOrder
})
.forEach((item: PluginItem) => {
const ctor = item.ctor
if (options[item.name] && typeof ctor === 'function') {
this.plugins[item.name] = new ctor(this)
}
})
}
private handleAutoBlur() {
/* istanbul ignore if */
if (this.options.autoBlur) {
this.on(this.eventTypes.beforeScrollStart, () => {
let activeElement = document.activeElement as HTMLElement
if (
activeElement &&
(activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA')
) {
activeElement.blur()
}
})
}
}
private eventBubbling() {
bubbling(this.scroller.hooks, this, [
this.eventTypes.beforeScrollStart,
this.eventTypes.scrollStart,
this.eventTypes.scroll,
this.eventTypes.scrollEnd,
this.eventTypes.scrollCancel,
this.eventTypes.touchEnd,
this.eventTypes.flick
])
}
private refreshWithoutReset(content: HTMLElement) {
this.scroller.refresh(content)
this.hooks.trigger(this.hooks.eventTypes.refresh, content)
this.trigger(this.eventTypes.refresh, content)
}
proxy(propertiesConfig: PropertyConfig[]) {
propertiesConfig.forEach(({ key, sourceKey }) => {
propertiesProxy(this, sourceKey, key)
})
}
refresh() {
const { contentChanged, valid } = this.setContent(this.wrapper)
if (valid) {
const content = this.content
this.refreshWithoutReset(content)
if (contentChanged) {
this.hooks.trigger(this.hooks.eventTypes.contentChanged, content)
this.trigger(this.eventTypes.contentChanged, content)
}
this.scroller.resetPosition()
}
}
enable() {
this.scroller.enable()
this.hooks.trigger(this.hooks.eventTypes.enable)
this.trigger(this.eventTypes.enable)
}
disable() {
this.scroller.disable()
this.hooks.trigger(this.hooks.eventTypes.disable)
this.trigger(this.eventTypes.disable)
}
destroy() {
this.hooks.trigger(this.hooks.eventTypes.destroy)
this.trigger(this.eventTypes.destroy)
this.scroller.destroy()
}
eventRegister(names: string[]) {
this.registerType(names)
}
}
export interface BScrollConstructor extends BScrollInstance {}
export interface CustomAPI {
[key: string]: {}
}
type ExtractAPI<O> = {
[K in keyof O]: K extends string
? DefOptions[K] extends undefined
? CustomAPI[K]
: never
: never
}[keyof O]
export function createBScroll<O = {}>(
el: ElementParam,
options?: Options & O
): BScrollConstructor & UnionToIntersection<ExtractAPI<O>> {
const bs = new BScrollConstructor(el, options)
return (bs as unknown) as BScrollConstructor &
UnionToIntersection<ExtractAPI<O>>
}
createBScroll.use = BScrollConstructor.use
createBScroll.plugins = BScrollConstructor.plugins
createBScroll.pluginsMap = BScrollConstructor.pluginsMap
type createBScroll = typeof createBScroll
export interface BScrollFactory extends createBScroll {
new <O = {}>(el: ElementParam, options?: Options & O): BScrollConstructor &
UnionToIntersection<ExtractAPI<O>>
}
export type BScroll<O = Options> = BScrollConstructor<O> &
UnionToIntersection<ExtractAPI<O>>
export const BScroll = (createBScroll as unknown) as BScrollFactory
================================================
FILE: packages/core/src/Instance.ts
================================================
import { Behavior } from './scroller/Behavior'
import Actions from './scroller/Actions'
import { ExposedAPI as ExposedAPIByScroller } from './scroller/Scroller'
import { Animater } from './animater'
import { ExposedAPI as ExposedAPIByAnimater } from './animater/Base'
export interface BScrollInstance
extends ExposedAPIByScroller,
ExposedAPIByAnimater {
[key: string]: any
x: Behavior['currentPos']
y: Behavior['currentPos']
hasHorizontalScroll: Behavior['hasScroll']
hasVerticalScroll: Behavior['hasScroll']
scrollerWidth: Behavior['contentSize']
scrollerHeight: Behavior['contentSize']
maxScrollX: Behavior['maxScrollPos']
maxScrollY: Behavior['maxScrollPos']
minScrollX: Behavior['minScrollPos']
minScrollY: Behavior['minScrollPos']
movingDirectionX: Behavior['movingDirection']
movingDirectionY: Behavior['movingDirection']
directionX: Behavior['direction']
directionY: Behavior['direction']
enabled: Actions['enabled']
pending: Animater['pending']
}
export const propertiesConfig = [
{
sourceKey: 'scroller.scrollBehaviorX.currentPos',
key: 'x'
},
{
sourceKey: 'scroller.scrollBehaviorY.currentPos',
key: 'y'
},
{
sourceKey: 'scroller.scrollBehaviorX.hasScroll',
key: 'hasHorizontalScroll'
},
{
sourceKey: 'scroller.scrollBehaviorY.hasScroll',
key: 'hasVerticalScroll'
},
{
sourceKey: 'scroller.scrollBehaviorX.contentSize',
key: 'scrollerWidth'
},
{
sourceKey: 'scroller.scrollBehaviorY.contentSize',
key: 'scrollerHeight'
},
{
sourceKey: 'scroller.scrollBehaviorX.maxScrollPos',
key: 'maxScrollX'
},
{
sourceKey: 'scroller.scrollBehaviorY.maxScrollPos',
key: 'maxScrollY'
},
{
sourceKey: 'scroller.scrollBehaviorX.minScrollPos',
key: 'minScrollX'
},
{
sourceKey: 'scroller.scrollBehaviorY.minScrollPos',
key: 'minScrollY'
},
{
sourceKey: 'scroller.scrollBehaviorX.movingDirection',
key: 'movingDirectionX'
},
{
sourceKey: 'scroller.scrollBehaviorY.movingDirection',
key: 'movingDirectionY'
},
{
sourceKey: 'scroller.scrollBehaviorX.direction',
key: 'directionX'
},
{
sourceKey: 'scroller.scrollBehaviorY.direction',
key: 'directionY'
},
{
sourceKey: 'scroller.actions.enabled',
key: 'enabled'
},
{
sourceKey: 'scroller.animater.pending',
key: 'pending'
},
{
sourceKey: 'scroller.animater.stop',
key: 'stop'
},
{
sourceKey: 'scroller.scrollTo',
key: 'scrollTo'
},
{
sourceKey: 'scroller.scrollBy',
key: 'scrollBy'
},
{
sourceKey: 'scroller.scrollToElement',
key: 'scrollToElement'
},
{
sourceKey: 'scroller.resetPosition',
key: 'resetPosition'
}
]
================================================
FILE: packages/core/src/Options.ts
================================================
import {
hasTransition,
hasPerspective,
hasTouch,
Probe,
EventPassthrough,
extend,
Quadrant,
} from '@better-scroll/shared-utils'
// type
export type Tap = 'tap' | ''
export type BounceOptions = Partial<BounceConfig> | boolean
export type DblclickOptions = Partial<DblclickConfig> | boolean
// interface
export interface BounceConfig {
top: boolean
bottom: boolean
left: boolean
right: boolean
}
export interface DblclickConfig {
delay: number
}
export interface CustomOptions {}
export interface DefOptions {
[key: string]: any
startX?: number
startY?: number
scrollX?: boolean
scrollY?: boolean
freeScroll?: boolean
directionLockThreshold?: number
eventPassthrough?: string
click?: boolean
tap?: Tap
bounce?: BounceOptions
bounceTime?: number
momentum?: boolean
momentumLimitTime?: number
momentumLimitDistance?: number
swipeTime?: number
swipeBounceTime?: number
deceleration?: number
flickLimitTime?: number
flickLimitDistance?: number
resizePolling?: number
probeType?: number
stopPropagation?: boolean
preventDefault?: boolean
preventDefaultException?: {
tagName?: RegExp
className?: RegExp
}
tagException?: {
tagName?: RegExp
className?: RegExp
}
HWCompositing?: boolean
useTransition?: boolean
bindToWrapper?: boolean
bindToTarget?: boolean
disableMouse?: boolean
disableTouch?: boolean
autoBlur?: boolean
translateZ?: string
dblclick?: DblclickOptions
autoEndDistance?: number
outOfBoundaryDampingFactor?: number
specifiedIndexAsContent?: number
quadrant?: Quadrant
}
export interface Options extends DefOptions, CustomOptions {}
export class CustomOptions {}
export class OptionsConstructor extends CustomOptions implements DefOptions {
[key: string]: any
startX: number
startY: number
scrollX: boolean
scrollY: boolean
freeScroll: boolean
directionLockThreshold: number
eventPassthrough: string
click: boolean
tap: Tap
bounce: BounceConfig
bounceTime: number
momentum: boolean
momentumLimitTime: number
momentumLimitDistance: number
swipeTime: number
swipeBounceTime: number
deceleration: number
flickLimitTime: number
flickLimitDistance: number
resizePolling: number
probeType: number
stopPropagation: boolean
preventDefault: boolean
preventDefaultException: {
tagName?: RegExp
className?: RegExp
}
tagException: {
tagName?: RegExp
className?: RegExp
}
HWCompositing: boolean
useTransition: boolean
bindToWrapper: boolean
bindToTarget: boolean
disableMouse: boolean
disableTouch: boolean
autoBlur: boolean
translateZ: string
dblclick: DblclickOptions
autoEndDistance: number
outOfBoundaryDampingFactor: number
specifiedIndexAsContent: number
quadrant: Quadrant
constructor() {
super()
this.startX = 0
this.startY = 0
this.scrollX = false
this.scrollY = true
this.freeScroll = false
this.directionLockThreshold = 0
this.eventPassthrough = EventPassthrough.None
this.click = false
this.dblclick = false
this.tap = ''
this.bounce = {
top: true,
bottom: true,
left: true,
right: true,
}
this.bounceTime = 800
this.momentum = true
this.momentumLimitTime = 300
this.momentumLimitDistance = 15
this.swipeTime = 2500
this.swipeBounceTime = 500
this.deceleration = 0.0015
this.flickLimitTime = 200
this.flickLimitDistance = 100
this.resizePolling = 60
this.probeType = Probe.Default
this.stopPropagation = false
this.preventDefault = true
this.preventDefaultException = {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
}
this.tagException = {
tagName: /^TEXTAREA$/,
}
this.HWCompositing = true
this.useTransition = true
this.bindToWrapper = false
this.bindToTarget = false
this.disableMouse = hasTouch
this.disableTouch = !hasTouch
this.autoBlur = true
this.autoEndDistance = 5
this.outOfBoundaryDampingFactor = 1 / 3
this.specifiedIndexAsContent = 0
this.quadrant = Quadrant.First
}
merge(options?: Options) {
if (!options) return this
for (let key in options) {
if (key === 'bounce') {
this.bounce = this.resolveBounce(options[key]!)
continue
}
this[key] = options[key]
}
return this
}
process() {
this.translateZ =
this.HWCompositing && hasPerspective ? ' translateZ(1px)' : ''
this.useTransition = this.useTransition && hasTransition
this.preventDefault = !this.eventPassthrough && this.preventDefault
// If you want eventPassthrough I have to lock one of the axes
this.scrollX =
this.eventPassthrough === EventPassthrough.Horizontal
? false
: this.scrollX
this.scrollY =
this.eventPassthrough === EventPassthrough.Vertical ? false : this.scrollY
// With eventPassthrough we also need lockDirection mechanism
this.freeScroll = this.freeScroll && !this.eventPassthrough
// force true when freeScroll is true
this.scrollX = this.freeScroll ? true : this.scrollX
this.scrollY = this.freeScroll ? true : this.scrollY
this.directionLockThreshold = this.eventPassthrough
? 0
: this.directionLockThreshold
return this
}
resolveBounce(bounceOptions: BounceOptions): BounceConfig {
const DEFAULT_BOUNCE = {
top: true,
right: true,
bottom: true,
left: true,
}
const NEGATED_BOUNCE = {
top: false,
right: false,
bottom: false,
left: false,
}
let ret: BounceConfig
if (typeof bounceOptions === 'object') {
ret = extend(DEFAULT_BOUNCE, bounceOptions)
} else {
ret = bounceOptions ? DEFAULT_BOUNCE : NEGATED_BOUNCE
}
return ret
}
}
================================================
FILE: packages/core/src/__mocks__/Options.ts
================================================
const mockOptions = jest.fn().mockImplementation(() => {
return {
startX: 0,
startY: 0,
scrollX: false,
scrollY: true,
freeScroll: false,
directionLockThreshold: 0,
eventPassthrough: '',
click: false,
tap: '',
translateZ: ' translateZ(0)',
bounce: {
top: true,
bottom: true,
left: true,
right: true,
},
bounceTime: 800,
momentum: true,
momentumLimitTime: 300,
momentumLimitDistance: 15,
swipeTime: 2500,
swipeBounceTime: 500,
deceleration: 0.0015,
flickLimitTime: 200,
flickLimitDistance: 100,
resizePolling: 60,
probeType: 0,
stopPropagation: false,
preventDefault: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
HWCompositing: true,
useTransition: true,
bindToWrapper: false,
disableMouse: true,
observeDOM: true,
autoBlur: true,
mouseWheel: false,
infinity: false,
specifiedIndexAsContent: 0,
quadrant: 0,
outOfBoundaryDampingFactor: 1 / 3,
merge: jest.fn(),
process: jest.fn(),
}
})
export { mockOptions as OptionsConstructor }
================================================
FILE: packages/core/src/__mocks__/index.ts
================================================
import Scroller from '../scroller/Scroller'
import { OptionsConstructor } from '../Options'
import { EventEmitter } from '@better-scroll/shared-utils'
jest.mock('../scroller/Scroller')
jest.mock('../Options')
const BScroll = jest.fn().mockImplementation((wrapper, options) => {
options = Object.assign(new OptionsConstructor(), options)
const eventEmitter = new EventEmitter([
// bscroll
'refresh',
'enable',
'disable',
'destroy',
// scroller
'beforeScrollStart',
'scrollStart',
'scroll',
'scrollEnd',
'touchEnd',
'flick',
'alterOptions',
'mousewheelStart',
'mousewheelMove',
'mousewheelEnd',
])
const res = {
wrapper: wrapper,
options: options,
hooks: new EventEmitter([
'refresh',
'enable',
'disable',
'destroy',
'beforeInitialScrollTo',
]),
scroller: new Scroller(wrapper, wrapper.children[0], options),
// own methods
proxy: jest.fn(),
refresh: jest.fn(),
// proxy methods
scrollTo: jest.fn(),
resetPosition: jest.fn(),
registerType: jest.fn().mockImplementation((names: string[]) => {
names.forEach((name) => {
const eventTypes = eventEmitter.eventTypes
eventTypes[name] = name
})
}),
disable: jest.fn(),
enable: jest.fn(),
stop: jest.fn(),
plugins: {},
x: 0,
y: 0,
maxScrollY: 0,
maxScrollX: 0,
minScrollX: 0,
minScrollY: 0,
hasVerticalScroll: true,
hasHorizontalScroll: false,
enabled: true,
pending: false,
}
Object.setPrototypeOf(res, eventEmitter)
return res
})
export default BScroll
================================================
FILE: packages/core/src/__tests__/Options.spec.ts
================================================
import { OptionsConstructor } from '../Options'
describe('BetterScroll Options', () => {
let options: OptionsConstructor
beforeEach(() => {
options = new OptionsConstructor()
})
afterEach(() => {
jest.clearAllMocks()
})
it('should have default value', () => {
expect(options).toEqual({
HWCompositing: true,
autoBlur: true,
bindToWrapper: false,
bounce: {
bottom: true,
left: true,
right: true,
top: true,
},
bounceTime: 800,
click: false,
dblclick: false,
deceleration: 0.0015,
directionLockThreshold: 0,
disableMouse: false,
disableTouch: true,
eventPassthrough: '',
flickLimitDistance: 100,
flickLimitTime: 200,
freeScroll: false,
momentum: true,
momentumLimitDistance: 15,
momentumLimitTime: 300,
preventDefault: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
tagException: {
tagName: /^TEXTAREA$/,
},
probeType: 0,
resizePolling: 60,
scrollX: false,
scrollY: true,
startX: 0,
startY: 0,
stopPropagation: false,
swipeBounceTime: 500,
swipeTime: 2500,
tap: '',
useTransition: true,
autoEndDistance: 5,
bindToTarget: false,
outOfBoundaryDampingFactor: 1 / 3,
specifiedIndexAsContent: 0,
quadrant: 1,
})
})
it('should shallow copy options when call merge(options)', () => {
options.merge({
scrollY: false,
scrollX: true,
bounce: false,
})
expect(options).toEqual({
HWCompositing: true,
autoBlur: true,
bindToWrapper: false,
bounce: {
top: false,
right: false,
bottom: false,
left: false,
},
bounceTime: 800,
click: false,
dblclick: false,
deceleration: 0.0015,
directionLockThreshold: 0,
disableMouse: false,
disableTouch: true,
eventPassthrough: '',
flickLimitDistance: 100,
flickLimitTime: 200,
freeScroll: false,
momentum: true,
momentumLimitDistance: 15,
momentumLimitTime: 300,
preventDefault: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
tagException: {
tagName: /^TEXTAREA$/,
},
probeType: 0,
resizePolling: 60,
scrollX: true,
scrollY: false,
startX: 0,
startY: 0,
stopPropagation: false,
swipeBounceTime: 500,
swipeTime: 2500,
tap: '',
useTransition: true,
autoEndDistance: 5,
bindToTarget: false,
outOfBoundaryDampingFactor: 1 / 3,
specifiedIndexAsContent: 0,
quadrant: 1,
})
// an invalid parameter
const ret = options.merge()
expect(ret).toBe(options)
})
it('should generate some extra properties of options', () => {
options.process()
expect(options).toEqual({
HWCompositing: true,
autoBlur: true,
bindToWrapper: false,
bounce: {
bottom: true,
left: true,
right: true,
top: true,
},
bounceTime: 800,
click: false,
dblclick: false,
deceleration: 0.0015,
directionLockThreshold: 0,
disableMouse: false,
disableTouch: true,
eventPassthrough: '',
flickLimitDistance: 100,
flickLimitTime: 200,
freeScroll: false,
momentum: true,
momentumLimitDistance: 15,
momentumLimitTime: 300,
preventDefault: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
tagException: {
tagName: /^TEXTAREA$/,
},
probeType: 0,
resizePolling: 60,
scrollX: false,
scrollY: true,
startX: 0,
startY: 0,
stopPropagation: false,
swipeBounceTime: 500,
swipeTime: 2500,
tap: '',
useTransition: true,
translateZ: '',
autoEndDistance: 5,
bindToTarget: false,
outOfBoundaryDampingFactor: 1 / 3,
specifiedIndexAsContent: 0,
quadrant: 1,
})
})
it('should resolve bounce when calling process', () => {
options.merge({
bounce: false,
})
expect(options.bounce).toEqual({
bottom: false,
left: false,
right: false,
top: false,
})
options.merge({
bounce: true,
})
expect(options.bounce).toEqual({
bottom: true,
left: true,
right: true,
top: true,
})
options.merge({
bounce: {
top: false,
bottom: false,
},
})
expect(options.bounce).toEqual({
bottom: false,
left: true,
right: true,
top: false,
})
})
})
================================================
FILE: packages/core/src/__tests__/__utils__/event.ts
================================================
export function createEvent(type: string, name: string): Event {
const e = document.createEvent(type || 'Event')
e.initEvent(name, true, true)
return e
}
interface CustomClickEvent extends MouseEvent {
pageX: number
pageY: number
}
export function dispatchClick(target: EventTarget, name = 'click') {
const event = <CustomClickEvent>createEvent('', name)
event.pageX = 0
event.pageY = 0
target.dispatchEvent(event)
}
interface CustomTouch {
pageX: number
pageY: number
}
type CustomTouches = CustomTouch[] | CustomTouch
export interface CustomTouchEvent extends Event {
touches: CustomTouches
targetTouches: CustomTouches
changedTouches: CustomTouches
}
interface CustomMouseEvent extends Event {
button: 0 | 1
pageX: number
pageY: number
}
export function dispatchTouch(
target: EventTarget,
name = 'touchstart',
touches: CustomTouches
): void {
const event = <CustomTouchEvent>createEvent('', name)
event.touches = event.targetTouches = event.changedTouches = touches
target.dispatchEvent(event)
}
export function dispatchMouse(
target: EventTarget,
name = 'mousedown',
useLeftButton = true
): void {
const event = <CustomMouseEvent>createEvent('', name)
event.button = useLeftButton ? 0 : 1
event.pageX = 0
event.pageY = 0
target.dispatchEvent(event)
}
export function dispatchTouchStart(
target: EventTarget,
touches: CustomTouches
): void {
dispatchTouch(target, 'touchstart', touches)
}
export function dispatchTouchMove(
target: EventTarget,
touches: CustomTouches
): void {
dispatchTouch(target, 'touchmove', touches)
}
export function dispatchTouchEnd(
target: EventTarget,
touches: CustomTouches
): void {
dispatchTouch(target, 'touchend', touches)
}
export function dispatchTouchCancel(
target: EventTarget,
touches: CustomTouches
): void {
dispatchTouch(target, 'touchcancel', touches)
}
export function dispatchSwipe(
target: EventTarget,
touches: CustomTouches,
duration: number,
cb: () => any
): void {
// TODO 优化写法
if (!Array.isArray(touches)) {
touches = [touches]
}
if (touches instanceof Array) {
dispatchTouchStart(target, touches[0])
const moveAndEnd = () => {
if (touches instanceof Array) {
dispatchTouchMove(target, touches[1] || touches[0])
dispatchTouchEnd(target, touches[2] || touches[1] || touches[0])
}
cb && cb()
}
if (duration) {
setTimeout(moveAndEnd, duration)
} else {
moveAndEnd()
}
}
}
================================================
FILE: packages/core/src/__tests__/__utils__/layout.ts
================================================
export interface CustomHTMLDivElement extends HTMLDivElement {
clientWidth: number
clientHeight: number
offsetWidth: number
offsetHeight: number
offsetTop: number
offsetLeft: number
_jsdomMockClientWidth?: number
_jsdomMockClientHeight?: number
_jsdomMockOffsetWidth?: number
_jsdomMockOffsetHeight?: number
_jsdomMockOffsetTop?: number
_jsdomMockOffsetLeft?: number
[key: string]: any
}
function firstUpper(key: string) {
return key.charAt(0).toUpperCase() + key.slice(1)
}
function genMockPrototype(mockName: string) {
return {
get: jest.fn().mockImplementation(dom => {
return Number(dom.getAttribute(mockName))
})
}
}
function mockHTMLPrototype(propName: string, mockGetter: jest.Mock) {
Object.defineProperty(HTMLElement.prototype, propName, {
get: function() {
return mockGetter(this)
},
configurable: true
})
}
export function mockDomOffset(
dom: CustomHTMLDivElement,
offsetObj: {
width?: number
height?: number
top?: number
left?: number
[key: string]: any
}
) {
Object.keys(offsetObj).forEach(key => {
const mockName = `_jsdomMockOffset${firstUpper(key)}`
dom.setAttribute(mockName, offsetObj[key])
})
}
export function mockDomClient(
dom: CustomHTMLDivElement,
clientObj: {
width?: number
height?: number
[key: string]: any
}
) {
Object.keys(clientObj).forEach(key => {
const mockName = `_jsdomMockClient${firstUpper(key)}`
dom.setAttribute(mockName, clientObj[key])
})
}
export function createDiv(
width: number = 0,
height: number = 0,
top: number = 0,
left: number = 0
) {
const dom = document.createElement('div') as CustomHTMLDivElement
mockDomOffset(dom, {
width,
height,
top,
left
})
mockDomClient(dom, {
width,
height
})
return dom
}
export const mockClientWidth = genMockPrototype('_jsdomMockClientWidth')
mockHTMLPrototype('clientWidth', mockClientWidth.get)
export const mockClientHeight = genMockPrototype('_jsdomMockClientHeight')
mockHTMLPrototype('clientHeight', mockClientHeight.get)
export const mockOffsetWidth = genMockPrototype('_jsdomMockOffsetWidth')
mockHTMLPrototype('offsetWidth', mockOffsetWidth.get)
export const mockOffsetHeight = genMockPrototype('_jsdomMockOffsetHeight')
mockHTMLPrototype('offsetHeight', mockOffsetHeight.get)
export const mockOffsetTop = genMockPrototype('_jsdomMockOffsetTop')
mockHTMLPrototype('offsetTop', mockOffsetTop.get)
export const mockOffsetLeft = genMockPrototype('_jsdomMockOffsetLeft')
mockHTMLPrototype('offsetLeft', mockOffsetLeft.get)
================================================
FILE: packages/core/src/__tests__/index.spec.ts
================================================
import BScroll from '../index'
describe('BetterScroll Core', () => {
let bscroll: BScroll
let wrapper = document.createElement('div')
let content = document.createElement('p')
wrapper.appendChild(content)
beforeEach(() => {
bscroll = new BScroll(wrapper, {})
})
afterEach(() => {
BScroll.plugins = []
BScroll.pluginsMap = {}
})
it('use()', () => {
const plugin = class MyPlugin {
static pluginName = 'myPlugin'
}
BScroll.use(plugin)
// has installed
BScroll.use(plugin)
expect(BScroll.plugins.length).toBe(1)
// Plugin should specify pluginName
const spyFn = jest.spyOn(console, 'error')
const unnamedPlugin = class UnnamedPlugin {}
BScroll.use(unnamedPlugin as any)
expect(spyFn).toBeCalled()
spyFn.mockRestore()
})
it('should init plugins when set top-level of BScroll options', () => {
let mockFn = jest.fn()
const plugin = class MyPlugin {
static pluginName = 'myPlugin2'
constructor(bscroll: BScroll) {
mockFn(bscroll)
}
}
BScroll.use(plugin)
let wrapper = document.createElement('div')
wrapper.appendChild(document.createElement('p'))
let bs = new BScroll(wrapper, {
myPlugin2: true
})
expect(mockFn).toBeCalledWith(bs)
})
it('should throw error when wrapper is not a ElementNode or wrapper has no children ', () => {
let spy = jest.spyOn(console, 'error')
let bs = new BScroll('.div', {})
let bs2 = new BScroll(document.createElement('div'), {})
expect(spy).toHaveBeenCalled()
expect(spy).toBeCalledTimes(2)
})
it('disable()', () => {
const mockFn = jest.fn()
bscroll.on(bscroll.eventTypes.disable, mockFn)
bscroll.hooks.on(bscroll.hooks.eventTypes.disable, mockFn)
bscroll.disable()
expect(mockFn).toBeCalledTimes(2)
})
it('destroy()', () => {
const mockFn = jest.fn()
bscroll.on(bscroll.eventTypes.destroy, mockFn)
bscroll.hooks.on(bscroll.hooks.eventTypes.destroy, mockFn)
bscroll.destroy()
expect(mockFn).toBeCalledTimes(2)
})
it('eventRegister()', () => {
bscroll.eventRegister(['dummy'])
expect(bscroll.eventTypes.dummy).toBeTruthy()
})
it('should refresh when window resized', () => {
const mockFn = jest.fn()
bscroll.on(bscroll.eventTypes.refresh, mockFn)
bscroll.scroller.hooks.trigger(bscroll.scroller.hooks.eventTypes.resize)
expect(mockFn).toBeCalledTimes(1)
})
it('plugin wanna control scroll position ', () => {
const mockFn = jest.fn().mockImplementation(() => true)
class DummyPlugin {
static pluginName = 'dummy'
constructor(scroll: BScroll) {
scroll.hooks.on(scroll.hooks.eventTypes.beforeInitialScrollTo, mockFn)
}
}
BScroll.use(DummyPlugin)
bscroll = new BScroll(wrapper, { dummy: true })
expect(mockFn).toBeCalled()
})
it('should trigger contentChanged hook when content DOM has changed', () => {
const mockFn = jest.fn()
bscroll.on(bscroll.eventTypes.contentChanged, mockFn)
// content DOM has
wrapper.removeChild(content)
wrapper.appendChild(document.createElement('div'))
bscroll.refresh()
expect(mockFn).toBeCalled()
})
})
================================================
FILE: packages/core/src/animater/Animation.ts
================================================
import Base from './Base'
import { TranslaterPoint } from '../translater'
import {
getNow,
requestAnimationFrame,
cancelAnimationFrame,
EaseFn,
Probe,
} from '@better-scroll/shared-utils'
export default class Animation extends Base {
move(
startPoint: TranslaterPoint,
endPoint: TranslaterPoint,
time: number,
easingFn: EaseFn | string
) {
// time is 0
if (!time) {
this.translate(endPoint)
if (this.options.probeType === Probe.Realtime) {
this.hooks.trigger(this.hooks.eventTypes.move, endPoint)
}
this.hooks.trigger(this.hooks.eventTypes.end, endPoint)
return
}
this.animate(startPoint, endPoint, time, easingFn as EaseFn)
}
private animate(
startPoint: TranslaterPoint,
endPoint: TranslaterPoint,
duration: number,
easingFn: EaseFn
) {
let startTime = getNow()
const destTime = startTime + duration
const isRealtimeProbeType = this.options.probeType === Probe.Realtime
const step = () => {
let now = getNow()
// js animation end
if (now >= destTime) {
this.translate(endPoint)
if (isRealtimeProbeType) {
this.hooks.trigger(this.hooks.eventTypes.move, endPoint)
}
this.hooks.trigger(this.hooks.eventTypes.end, endPoint)
return
}
now = (now - startTime) / duration
let easing = easingFn(now)
const newPoint = {} as TranslaterPoint
Object.keys(endPoint).forEach((key) => {
const startValue = startPoint[key]
const endValue = endPoint[key]
newPoint[key] = (endValue - startValue) * easing + startValue
})
this.translate(newPoint)
if (isRealtimeProbeType) {
this.hooks.trigger(this.hooks.eventTypes.move, newPoint)
}
if (this.pending) {
this.timer = requestAnimationFrame(step)
}
// call bs.stop() should not dispatch end hook again.
// forceStop hook will do this.
/* istanbul ignore if */
if (!this.pending) {
if (this.callStopWhenPending) {
this.callStopWhenPending = false
} else {
// raf ends should dispatch end hook.
this.hooks.trigger(this.hooks.eventTypes.end, endPoint)
}
}
}
this.setPending(true)
// when manually call bs.stop(), then bs.scrollTo()
// we should reset callStopWhenPending to dispatch end hook
if (this.callStopWhenPending) {
this.setCallStop(false)
}
cancelAnimationFrame(this.timer)
step()
}
doStop(): boolean {
const pending = this.pending
this.setForceStopped(false)
this.setCallStop(false)
// still in requestFrameAnimation
if (pending) {
this.setPending(false)
cancelAnimationFrame(this.timer)
const pos = this.translater.getComputedPosition()
this.setForceStopped(true)
this.setCallStop(true)
this.hooks.trigger(this.hooks.eventTypes.forceStop, pos)
}
return pending
}
stop() {
const stopFromAnimation = this.doStop()
if (stopFromAnimation) {
this.hooks.trigger(this.hooks.eventTypes.callStop)
}
}
}
================================================
FILE: packages/core/src/animater/Base.ts
================================================
import {
EaseFn,
safeCSSStyleDeclaration,
cancelAnimationFrame,
EventEmitter,
Probe,
} from '@better-scroll/shared-utils'
import Translater, { TranslaterPoint } from '../translater'
export interface ExposedAPI {
stop(): void
}
export default abstract class Base implements ExposedAPI {
content: HTMLElement
style: safeCSSStyleDeclaration
hooks: EventEmitter
timer: number = 0
pending: boolean
callStopWhenPending: boolean
forceStopped: boolean
_reflow: number;
[key: string]: any
constructor(
content: HTMLElement,
public translater: Translater,
public options: {
probeType: number
}
) {
this.hooks = new EventEmitter([
'move',
'end',
'beforeForceStop',
'forceStop',
'callStop',
'time',
'timeFunction',
])
this.setContent(content)
}
translate(endPoint: TranslaterPoint) {
this.translater.translate(endPoint)
}
setPending(pending: boolean) {
this.pending = pending
}
setForceStopped(forceStopped: boolean) {
this.forceStopped = forceStopped
}
setCallStop(called: boolean) {
this.callStopWhenPending = called
}
setContent(content: HTMLElement) {
if (this.content !== content) {
this.content = content
this.style = content.style as safeCSSStyleDeclaration
this.stop()
}
}
clearTimer() {
if (this.timer) {
cancelAnimationFrame(this.timer)
this.timer = 0
}
}
abstract move(
startPoint: TranslaterPoint,
endPoint: TranslaterPoint,
time: number,
easing: string | EaseFn
): void
abstract doStop(): void
abstract stop(): void
destroy() {
this.hooks.destroy()
cancelAnimationFrame(this.timer)
}
}
================================================
FILE: packages/core/src/animater/Transition.ts
================================================
import {
style,
requestAnimationFrame,
cancelAnimationFrame,
EaseFn,
Probe,
} from '@better-scroll/shared-utils'
import Base from './Base'
import { TranslaterPoint } from '../translater'
import { isValidPostion } from '../utils/compat'
export default class Transition extends Base {
startProbe(startPoint: TranslaterPoint, endPoint: TranslaterPoint) {
let prePos = startPoint
const probe = () => {
let pos = this.translater.getComputedPosition()
if (isValidPostion(startPoint, endPoint, pos, prePos)) {
this.hooks.trigger(this.hooks.eventTypes.move, pos)
}
// call bs.stop() should not dispatch end hook again.
// forceStop hook will do this.
/* istanbul ignore if */
if (!this.pending) {
if (this.callStopWhenPending) {
this.callStopWhenPending = false
} else {
// transition ends should dispatch end hook.
this.hooks.trigger(this.hooks.eventTypes.end, pos)
}
}
prePos = pos
if (this.pending) {
this.timer = requestAnimationFrame(probe)
}
}
// when manually call bs.stop(), then bs.scrollTo()
// we should reset callStopWhenPending to dispatch end hook
if (this.callStopWhenPending) {
this.setCallStop(false)
}
cancelAnimationFrame(this.timer)
probe()
}
transitionTime(time = 0) {
this.style[style.transitionDuration] = time + 'ms'
this.hooks.trigger(this.hooks.eventTypes.time, time)
}
transitionTimingFunction(easing: string) {
this.style[style.transitionTimingFunction] = easing
this.hooks.trigger(this.hooks.eventTypes.timeFunction, easing)
}
transitionProperty() {
this.style[style.transitionProperty] = style.transform
}
move(
startPoint: TranslaterPoint,
endPoint: TranslaterPoint,
time: number,
easingFn: string | EaseFn
) {
this.setPending(time > 0)
this.transitionTimingFunction(easingFn as string)
this.transitionProperty()
this.transitionTime(time)
this.translate(endPoint)
const isRealtimeProbeType = this.options.probeType === Probe.Realtime
if (time && isRealtimeProbeType) {
this.startProbe(startPoint, endPoint)
}
// if we change content's transformY in a tick
// such as: 0 -> 50px -> 0
// transitionend will not be triggered
// so we forceupdate by reflow
if (!time) {
this._reflow = this.content.offsetHeight
if (isRealtimeProbeType) {
this.hooks.trigger(this.hooks.eventTypes.move, endPoint)
}
this.hooks.trigger(this.hooks.eventTypes.end, endPoint)
}
}
doStop(): boolean {
const pending = this.pending
this.setForceStopped(false)
this.setCallStop(false)
// still in transition
if (pending) {
this.setPending(false)
cancelAnimationFrame(this.timer)
const { x, y } = this.translater.getComputedPosition()
this.transitionTime()
this.translate({ x, y })
this.setForceStopped(true)
this.setCallStop(true)
this.hooks.trigger(this.hooks.eventTypes.forceStop, { x, y })
}
return pending
}
stop() {
const stopFromTransition = this.doStop()
if (stopFromTransition) {
this.hooks.trigger(this.hooks.eventTypes.callStop)
}
}
}
================================================
FILE: packages/core/src/animater/__mocks__/Animation.ts
================================================
import { EventEmitter } from '@better-scroll/shared-utils'
const Animation = jest
.fn()
.mockImplementation((content, translater, bscrollOptions) => {
return {
content,
translater,
options: bscrollOptions,
style: content.style,
pending: false,
forceStopped: false,
timer: 0,
hooks: new EventEmitter([
'move',
'end',
'forceStop',
'beforeForceStop',
'callStop',
'time',
'timeFunction',
]),
translate: jest.fn(),
stop: jest.fn(),
doStop: jest.fn(),
move: jest.fn(),
destroy: jest.fn(),
setPending: jest.fn(),
setForceStopped: jest.fn(),
setCallStop: jest.fn(),
setContent: jest.fn(),
clearTimer: jest.fn(),
}
})
export default Animation
================================================
FILE: packages/core/src/animater/__mocks__/Transition.ts
================================================
import { EventEmitter } from '@better-scroll/shared-utils'
const Transition = jest
.fn()
.mockImplementation((content, translater, bscrollOptions) => {
return {
content,
translater,
options: bscrollOptions,
style: content.style,
pending: false,
forceStopped: false,
timer: 0,
hooks: new EventEmitter([
'move',
'end',
'forceStop',
'beforeForceStop',
'callStop',
'time',
'timeFunction',
]),
translate: jest.fn(),
stop: jest.fn(),
doStop: jest.fn(),
move: jest.fn(),
startProbe: jest.fn(),
transitionTime: jest.fn(),
transitionTimingFunction: jest.fn(),
destroy: jest.fn(),
setPending: jest.fn(),
setForceStopped: jest.fn(),
setCallStop: jest.fn(),
setContent: jest.fn(),
clearTimer: jest.fn(),
}
})
export default Transition
================================================
FILE: packages/core/src/animater/__mocks__/index.ts
================================================
import Transition from '../Transition'
import Animation from '../Animation'
jest.mock('../Transition')
jest.mock('../Animation')
const createAnimater = jest
.fn()
.mockImplementation((element, translater, bscrollOptions) => {
if (bscrollOptions.useTransition) {
return new Transition(element, translater, bscrollOptions as {
probeType: number
})
} else {
return new Animation(element, translater, bscrollOptions as {
probeType: number
})
}
})
export default createAnimater
================================================
FILE: packages/core/src/animater/__tests__/Animation.spec.ts
================================================
import Translater from '../../translater'
jest.mock('../../translater')
let mockRequestAnimationFrame = jest.fn()
let mockCancelAnimationFrame = jest.fn()
jest.mock('@better-scroll/shared-utils/src/raf', () => {
return {
requestAnimationFrame: (cb: any) => mockRequestAnimationFrame(cb),
cancelAnimationFrame: () => mockCancelAnimationFrame(),
}
})
let mockGetNow = jest.fn()
jest.mock('@better-scroll/shared-utils/src/lang', () => {
return {
getNow: () => mockGetNow(),
}
})
import Animation from '../Animation'
function createAnimation(probeType: number) {
const dom = document.createElement('div')
const translater = new Translater(dom)
const animation = new Animation(dom, translater, { probeType })
return {
dom,
translater,
animation,
}
}
describe('Animation Class test suit', () => {
beforeAll(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.clearAllTimers()
jest.clearAllMocks()
})
it('should off hooks and cancelAnimationFrame when destroy', () => {
const { animation } = createAnimation(0)
const hooksDestroySpy = jest.spyOn(animation.hooks, 'destroy')
animation.destroy()
expect(mockCancelAnimationFrame).toBeCalledTimes(1)
expect(hooksDestroySpy).toBeCalledTimes(1)
})
it('should move to endPoint and trigger hooks in one step when time=0', () => {
const { animation, translater } = createAnimation(0)
const onMove = jest.fn()
const onEnd = jest.fn()
animation.hooks.on('move', onMove)
animation.hooks.on('end', onEnd)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 10,
y: 10,
}
animation.options.probeType = 3
animation.move(startPoint, endPoint, 0, 'easing')
expect(translater.translate).toBeCalledTimes(1)
expect(translater.translate).toBeCalledWith(endPoint)
expect(onMove).toBeCalled()
expect(onEnd).toBeCalled()
})
it('should move to endPoint for serveral steps with time', () => {
const { animation, translater, dom } = createAnimation(3)
const onMove = jest.fn()
const onEnd = jest.fn()
const easeFn = jest.fn()
animation.hooks.on('move', onMove)
animation.hooks.on('end', onEnd)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 0,
y: 100,
}
mockRequestAnimationFrame.mockImplementation((cb) => {
setTimeout(() => {
cb()
}, 200)
})
mockGetNow
.mockImplementationOnce(() => {
return 1000
})
.mockImplementationOnce(() => {
return 1100
})
.mockImplementationOnce(() => {
return 1600
})
easeFn.mockImplementationOnce(() => {
return 0.2
})
animation.move(startPoint, endPoint, 500, easeFn)
expect(easeFn).toBeCalledWith(0.2)
expect(translater.translate).toBeCalledWith({
x: 0,
y: 20,
})
expect(onMove).toBeCalledTimes(1)
jest.advanceTimersByTime(200)
expect(translater.translate).toBeCalledWith({
x: 0,
y: 100,
})
expect(onMove).toBeCalledTimes(2)
expect(onEnd).toBeCalled()
animation.destroy()
})
it('should force stop', () => {
const { animation, translater } = createAnimation(3)
animation.setCallStop(true)
const onMove = jest.fn()
const onForceStop = jest.fn()
const easeFn = jest.fn()
animation.hooks.on('move', onMove)
animation.hooks.on('forceStop', onForceStop)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 0,
y: 100,
}
mockRequestAnimationFrame.mockImplementation((cb) => {
setTimeout(() => {
cb()
}, 200)
})
mockGetNow
.mockImplementationOnce(() => {
return 1000
})
.mockImplementationOnce(() => {
return 1100
})
.mockImplementationOnce(() => {
return 1600
})
easeFn.mockImplementationOnce(() => {
return 0.2
})
animation.move(startPoint, endPoint, 500, easeFn)
expect(easeFn).toBeCalledWith(0.2)
expect(translater.translate).toBeCalledWith({
x: 0,
y: 20,
})
expect(animation.pending).toBe(true)
expect(animation.callStopWhenPending).toBe(false)
;(<jest.Mock>translater.getComputedPosition).mockImplementation(() => {
return 20
})
animation.stop()
expect(animation.pending).toBe(false)
expect(mockCancelAnimationFrame).toBeCalled()
expect(animation.callStopWhenPending).toBe(true)
expect(onForceStop).toBeCalledWith(20)
animation.destroy()
})
})
================================================
FILE: packages/core/src/animater/__tests__/Transition.spec.ts
================================================
import Translater from '../../translater/index'
jest.mock('../../translater/index')
let mockRequestAnimationFrame = jest.fn()
let mockCancelAnimationFrame = jest.fn()
jest.mock('@better-scroll/shared-utils/src/raf', () => {
return {
requestAnimationFrame: (cb: any) => mockRequestAnimationFrame(cb),
cancelAnimationFrame: () => mockCancelAnimationFrame(),
}
})
import Transition from '@better-scroll/core/src/animater/Transition'
function createTransition(probeType: number) {
const dom = document.createElement('div')
const translater = new Translater(dom)
const transition = new Transition(dom, translater, { probeType })
return {
dom,
translater,
transition,
}
}
describe('Transition Class test suit', () => {
beforeAll(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.clearAllTimers()
jest.clearAllMocks()
})
it('should off hooks and cancelAnimationFrame when destroy', () => {
const { transition } = createTransition(0)
const hooksDestroySpy = jest.spyOn(transition.hooks, 'destroy')
transition.destroy()
expect(mockCancelAnimationFrame).toBeCalledTimes(1)
expect(hooksDestroySpy).toBeCalledTimes(1)
})
it('should set timeFunction and trigger event', () => {
const { transition, dom } = createTransition(0)
const onTimeFunction = jest.fn()
const onTime = jest.fn()
transition.hooks.on('time', onTime)
transition.hooks.on('timeFunction', onTimeFunction)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 10,
y: 10,
}
transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')
expect(onTime).toHaveBeenCalledTimes(1)
expect(onTimeFunction).toHaveBeenCalledTimes(1)
expect(dom.style.transitionTimingFunction).toBe(
'cubic-bezier(0.23, 1, 0.32, 1)'
)
expect(dom.style.transitionDuration).toBe('200ms')
transition.destroy()
})
it('should call translater with right arguments', () => {
const { transition, translater } = createTransition(0)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 10,
y: 10,
}
transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')
expect(translater.translate).toBeCalledWith(endPoint)
transition.destroy()
})
it('should trigger end hook with time=0', () => {
const { transition } = createTransition(0)
const onEnd = jest.fn()
transition.hooks.on('end', onEnd)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 10,
y: 10,
}
transition.options.probeType = 3
transition.move(startPoint, endPoint, 0, 'cubic-bezier(0.23, 1, 0.32, 1)')
expect(onEnd).toHaveBeenCalled()
transition.destroy()
})
it('should stop', () => {
const { transition, translater, dom } = createTransition(0)
const onForceStop = jest.fn()
transition.hooks.on('forceStop', onForceStop)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 0,
y: 10,
}
transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')
;(<jest.Mock>translater.getComputedPosition).mockImplementation(() => {
return { x: 10, y: 10 }
})
transition.stop()
expect(dom.style.transitionDuration).toBe('0ms')
expect(translater.translate).toBeCalledWith({ x: 10, y: 10 })
expect(onForceStop).toBeCalledWith({ x: 10, y: 10 })
expect(mockCancelAnimationFrame).toBeCalled()
expect(transition.callStopWhenPending).toBe(true)
transition.destroy()
})
it('should startProbe with probeType=3', () => {
const { transition, translater } = createTransition(3)
translater.getComputedPosition = jest.fn().mockImplementation(() => {
return { x: 0, y: 0 }
})
mockRequestAnimationFrame.mockImplementation((cb) => {
setTimeout(() => {
cb()
}, 200)
})
const onMove = jest.fn()
const onEnd = jest.fn()
transition.hooks.on('time', onMove)
transition.hooks.on('end', onEnd)
const startPoint = {
x: 0,
y: 0,
}
const endPoint = {
x: 10,
y: 10,
}
transition.move(startPoint, endPoint, 200, 'cubic-bezier(0.23, 1, 0.32, 1)')
expect(transition.callStopWhenPending).toBe(false)
jest.advanceTimersByTime(200)
expect(onMove).toBeCalled()
transition.pending = false
jest.advanceTimersByTime(200)
expect(onEnd).toBeCalled()
transition.destroy()
})
it('clearTimer ', () => {
const { transition } = createTransition(0)
transition.timer = 1
transition.clearTimer()
expect(transition.timer).toBe(0)
})
it('should reset callStopWhenPending', () => {
const { transition } = createTransition(0)
transition.setPending(true)
transition.stop()
transition.startProbe({ x: 0, y: 0 }, { x: 0, y: -10 })
expect(transition.callStopWhenPending).toBe(false)
})
})
================================================
FILE: packages/core/src/animater/__tests__/index.spec.ts
================================================
import Translater from '../../translater/index'
import Transition from '../Transition'
import Animation from '../Animation'
import { OptionsConstructor } from '../../Options'
jest.mock('../Animation')
jest.mock('../Transition')
jest.mock('../Base')
jest.mock('../../translater/index')
jest.mock('../../Options')
import createAnimater from '../index'
describe('animater create test suit', () => {
const dom = document.createElement('div')
const translater = new Translater(dom)
it('should create Transition class when useTransition=true', () => {
const options = new OptionsConstructor()
options.probeType = 0
options.useTransition = true
const animater = createAnimater(dom, translater, options)
expect(Transition).toBeCalledWith(dom, translater, { probeType: 0 })
})
it('should create Animation class when useTransition=false', () => {
const options = new OptionsConstructor()
options.probeType = 0
options.useTransition = false
const animater = createAnimater(dom, translater, options)
expect(Animation).toBeCalledWith(dom, translater, { probeType: 0 })
})
})
================================================
FILE: packages/core/src/animater/index.ts
================================================
import Translater from '../translater'
import { Options as BScrollOptions } from '../Options'
import Animater from './Base'
import Transition from './Transition'
import Animation from './Animation'
export { Animater, Transition, Animation }
export default function createAnimater(
element: HTMLElement,
translater: Translater,
options: BScrollOptions
) {
const useTransition = options.useTransition
let animaterOptions = {}
Object.defineProperty(animaterOptions, 'probeType', {
enumerable: true,
configurable: false,
get() {
return options.probeType
},
})
if (useTransition) {
return new Transition(
element,
translater,
animaterOptions as {
probeType: number
}
)
} else {
return new Animation(
element,
translater,
animaterOptions as {
probeType: number
}
)
}
}
================================================
FILE: packages/core/src/base/ActionsHandler.ts
================================================
import {
TouchEvent,
// dom
preventDefaultExceptionFn,
tagExceptionFn,
eventTypeMap,
EventType,
MouseButton,
EventRegister,
EventEmitter,
} from '@better-scroll/shared-utils'
type Exception = {
tagName?: RegExp
className?: RegExp
}
export interface Options {
[key: string]: boolean | number | Exception
click: boolean
bindToWrapper: boolean
disableMouse: boolean
disableTouch: boolean
preventDefault: boolean
stopPropagation: boolean
preventDefaultException: Exception
tagException: Exception
autoEndDistance: number
}
export default class ActionsHandler {
hooks: EventEmitter
initiated: number
pointX: number
pointY: number
wrapperEventRegister: EventRegister
targetEventRegister: EventRegister
constructor(public wrapper: HTMLElement, public options: Options) {
this.hooks = new EventEmitter([
'beforeStart',
'start',
'move',
'end',
'click',
])
this.handleDOMEvents()
}
private handleDOMEvents() {
const { bindToWrapper, disableMouse, disableTouch, click } = this.options
const wrapper = this.wrapper
const target = bindToWrapper ? wrapper : window
const wrapperEvents = []
const targetEvents = []
const shouldRegisterTouch = !disableTouch
const shouldRegisterMouse = !disableMouse
if (click) {
wrapperEvents.push({
name: 'click',
handler: this.click.bind(this),
capture: true,
})
}
if (shouldRegisterTouch) {
wrapperEvents.push({
name: 'touchstart',
handler: this.start.bind(this),
})
targetEvents.push(
{
name: 'touchmove',
handler: this.move.bind(this),
},
{
name: 'touchend',
handler: this.end.bind(this),
},
{
name: 'touchcancel',
handler: this.end.bind(this),
}
)
}
if (shouldRegisterMouse) {
wrapperEvents.push({
name: 'mousedown',
handler: this.start.bind(this),
})
targetEvents.push(
{
name: 'mousemove',
handler: this.move.bind(this),
},
{
name: 'mouseup',
handler: this.end.bind(this),
}
)
}
this.wrapperEventRegister = new EventRegister(wrapper, wrapperEvents)
this.targetEventRegister = new EventRegister(target, targetEvents)
}
private beforeHandler(e: TouchEvent, type: 'start' | 'move' | 'end') {
const {
preventDefault,
stopPropagation,
preventDefaultException,
} = this.options
const preventDefaultConditions = {
start: () => {
return (
preventDefault &&
!preventDefaultExceptionFn(e.target, preventDefaultException)
)
},
end: () => {
return (
preventDefault &&
!preventDefaultExceptionFn(e.target, preventDefaultException)
)
},
move: () => {
return preventDefault
},
}
if (preventDefaultConditions[type]()) {
e.preventDefault()
}
if (stopPropagation) {
e.stopPropagation()
}
}
setInitiated(type: number = 0) {
this.initiated = type
}
private start(e: TouchEvent) {
const _eventType = eventTypeMap[e.type]
if (this.initiated && this.initiated !== _eventType) {
return
}
this.setInitiated(_eventType)
// if textarea or other html tags in options.tagException is manipulated
// do not make bs scroll
if (tagExceptionFn(e.target, this.options.tagException)) {
this.setInitiated()
return
}
// only allow mouse left button
if (_eventType === EventType.Mouse && e.button !== MouseButton.Left) return
if (this.hooks.trigger(this.hooks.eventTypes.beforeStart, e)) {
return
}
this.beforeHandler(e, 'start')
let point = (e.touches ? e.touches[0] : e) as Touch
this.pointX = point.pageX
this.pointY = point.pageY
this.hooks.trigger(this.hooks.eventTypes.start, e)
}
private move(e: TouchEvent) {
if (eventTypeMap[e.type] !== this.initiated) {
return
}
this.beforeHandler(e, 'move')
let point = (e.touches ? e.touches[0] : e) as Touch
let deltaX = point.pageX - this.pointX
let deltaY = point.pageY - this.pointY
this.pointX = point.pageX
this.pointY = point.pageY
if (
this.hooks.trigger(this.hooks.eventTypes.move, {
deltaX,
deltaY,
e,
})
) {
return
}
// auto end when out of viewport
let scrollLeft =
document.documentElement.scrollLeft ||
window.pageXOffset ||
document.body.scrollLeft
let scrollTop =
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop
let pX = this.pointX - scrollLeft
let pY = this.pointY - scrollTop
const autoEndDistance = this.options.autoEndDistance
if (
pX > document.documentElement.clientWidth - autoEndDistance ||
pY > document.documentElement.clientHeight - autoEndDistance ||
pX < autoEndDistance ||
pY < autoEndDistance
) {
this.end(e)
}
}
private end(e: TouchEvent) {
if (eventTypeMap[e.type] !== this.initiated) {
return
}
this.setInitiated()
this.beforeHandler(e, 'end')
this.hooks.trigger(this.hooks.eventTypes.end, e)
}
private click(e: TouchEvent) {
this.hooks.trigger(this.hooks.eventTypes.click, e)
}
setContent(content: HTMLElement) {
if (content !== this.wrapper) {
this.wrapper = content
this.rebindDOMEvents()
}
}
rebindDOMEvents() {
this.wrapperEventRegister.destroy()
this.targetEventRegister.destroy()
this.handleDOMEvents()
}
destroy() {
this.wrapperEventRegister.destroy()
this.targetEventRegister.destroy()
this.hooks.destroy()
}
}
================================================
FILE: packages/core/src/base/__mocks__/ActionsHandler.ts
================================================
import { EventRegister, EventEmitter } from '@better-scroll/shared-utils'
const ActionsHandler = jest
.fn()
.mockImplementation((wrapper, bscrollOptions) => {
return {
wrapper,
options: bscrollOptions,
initiated: 1,
pointX: 0,
pointY: 0,
startClickRegister: new EventRegister(wrapper, []),
moveEndRegister: new EventRegister(wrapper, []),
hooks: new EventEmitter(['beforeStart', 'start', 'move', 'end', 'click']),
destroy: jest.fn(),
setInitiated: jest.fn(),
setContent: jest.fn(),
rebindDOMEvents: jest.fn(),
}
})
export default ActionsHandler
================================================
FILE: packages/core/src/base/__tests__/ActionsHandler.spec.ts
================================================
import ActionsHandler, {
Options,
} from '@better-scroll/core/src/base/ActionsHandler'
import {
dispatchTouch,
dispatchMouse,
dispatchTouchStart,
dispatchTouchEnd,
dispatchTouchCancel,
} from '@better-scroll/core/src/__tests__/__utils__/event'
describe('ActionsHandler', () => {
let actionsHandler: ActionsHandler
let wrapper: HTMLElement
let options: Options
beforeEach(() => {
wrapper = document.createElement('wrapper')
options = {
click: false,
bindToWrapper: false,
disableMouse: false,
disableTouch: false,
preventDefault: true,
stopPropagation: true,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
tagException: { tagName: /^TEXTAREA$/ },
autoEndDistance: 5,
}
})
afterEach(() => {
jest.clearAllMocks()
})
it('should bind mouse event when options.disableMouse is false', () => {
options.disableTouch = true
actionsHandler = new ActionsHandler(wrapper, options)
const wrapperEventsName = actionsHandler.wrapperEventRegister.events.map(
(event) => event.name
)
const targetEventsName = actionsHandler.targetEventRegister.events.map(
(event) => event.name
)
expect(wrapperEventsName).toMatchObject(['mousedown'])
expect(targetEventsName).toMatchObject(['mousemove', 'mouseup'])
})
it('should invoke start method when dispatch mousedown', () => {
actionsHandler = new ActionsHandler(wrapper, options)
const beforeStartMockHandler = jest.fn().mockImplementation(() => {
return 'dummy test'
})
const startMockHandler = jest.fn().mockImplementation(() => {
return 'dummy test'
})
actionsHandler.hooks.on('beforeStart', beforeStartMockHandler)
actionsHandler.hooks.on('start', startMockHandler)
dispatchMouse(wrapper, 'mousedown')
expect(beforeStartMockHandler).toBeCalledTimes(1)
expect(startMockHandler).toBeCalledTimes(1)
// return early
actionsHandler.setInitiated(1)
dispatchMouse(wrapper, 'mousedown')
expect(beforeStartMockHandler).toBeCalledTimes(1)
expect(startMockHandler).toBeCalledTimes(1)
// only allow mouse left button
actionsHandler.setInitiated(0)
dispatchMouse(wrapper, 'mousedown', false)
expect(beforeStartMockHandler).toBeCalledTimes(1)
expect(startMockHandler).toBeCalledTimes(1)
// cancelable beforeStart hook
actionsHandler.hooks.on('beforeStart', () => true)
dispatchMouse(wrapper, 'mousedown')
expect(beforeStartMockHandler).toBeCalledTimes(2)
expect(startMockHandler).toBeCalledTimes(1)
})
it('should invoke move method when dispatch touchmove', () => {
actionsHandler = new ActionsHandler(wrapper, options)
const moveMockHandler1 = jest.fn().mockImplementationOnce(() => {
return true
})
const moveMockHandler2 = jest.fn().mockImplementation(() => {
return 'dummy test'
})
actionsHandler.hooks.on('move', moveMockHandler1)
actionsHandler.hooks.on('move', moveMockHandler2)
dispatchMouse(wrapper, 'mousedown')
dispatchMouse(window, 'mousemove')
expect(moveMockHandler1).toBeCalledTimes(1)
// cancelable move hook
expect(moveMockHandler2).not.toBeCalled()
// simulate finger moved out of viewport
actionsHandler.pointX = 5
const endMockHandler = jest.fn()
actionsHandler.hooks.on(actionsHandler.hooks.eventTypes.end, endMockHandler)
dispatchMouse(window, 'mousemove')
expect(endMockHandler).toBeCalled()
})
it('should invoke end method when dispatch touchend', () => {
actionsHandler = new ActionsHandler(wrapper, options)
const endMockHandler = jest.fn().mockImplementation(() => {
return 'dummy test'
})
actionsHandler.hooks.on('end', endMockHandler)
dispatchTouchStart(wrapper, [{ pageX: 0, pageY: 0 }])
dispatchTouchEnd(window, [{ pageX: 0, pageY: 0 }])
expect(endMockHandler).toBeCalled()
})
it('should invoke end method when dispatch touchcancel', () => {
actionsHandler = new ActionsHandler(wrapper, options)
const endMockHandler = jest.fn().mockImplementation(() => {
return 'dummy test'
})
actionsHandler.hooks.on('end', endMockHandler)
dispatchTouchStart(wrapper, [{ pageX: 0, pageY: 0 }])
dispatchTouchCancel(window, [{ pageX: 0, pageY: 0 }])
expect(endMockHandler).toBeCalled()
})
it('should call click method when dispatch click', () => {
options.click = true
actionsHandler = new ActionsHandler(wrapper, options)
const clickMockHandler = jest.fn().mockImplementation(() => {
return 'dummy test'
})
actionsHandler.hooks.on('click', clickMockHandler)
dispatchTouch(wrapper, 'click', [
{
pageX: 10,
pageY: 10,
},
])
expect(clickMockHandler).toBeCalled()
})
it('should make bs not take effect when manipulate textarea DOM tag', () => {
const textarea = document.createElement('textarea')
const content = document.createElement('div')
content.appendChild(textarea)
wrapper.appendChild(content)
actionsHandler = new ActionsHandler(wrapper, options)
dispatchMouse(textarea, 'mousedown')
expect(actionsHandler.initiated).toBeFalsy()
})
it('destroy()', () => {
actionsHandler = new ActionsHandler(wrapper, options)
actionsHandler.destroy()
expect(actionsHandler.wrapperEventRegister.events.length).toBe(0)
expect(actionsHandler.targetEventRegister.events.length).toBe(0)
expect(actionsHandler.hooks.eventTypes).toMatchObject({})
expect(actionsHandler.hooks.events).toMatchObject({})
})
it('setContent()', () => {
const p = document.createElement('p')
actionsHandler.setContent(p)
expect(actionsHandler.wrapper).toBe(p)
})
})
================================================
FILE: packages/core/src/index.ts
================================================
import { BScroll } from './BScroll'
export { BScrollInstance } from './Instance'
export { Options, CustomOptions } from './Options'
export { TranslaterPoint } from './translater'
export { MountedBScrollHTMLElement } from './BScroll'
export { Behavior, Boundary } from './scroller/Behavior'
export { createBScroll, CustomAPI } from './BScroll'
export default BScroll
================================================
FILE: packages/core/src/scroller/Actions.ts
================================================
import ActionsHandler from '../base/ActionsHandler'
import { Behavior } from './Behavior'
import DirectionLockAction from './DirectionLock'
import { Animater } from '../animater'
import { OptionsConstructor as BScrollOptions } from '../Options'
import { TranslaterPoint } from '../translater'
import {
preventDefaultExceptionFn,
TouchEvent,
getNow,
Probe,
EventEmitter,
between,
Quadrant,
maybePrevent,
} from '@better-scroll/shared-utils'
const applyQuadrantTransformation = (
deltaX: number,
deltaY: number,
quadrant: Quadrant
) => {
if (quadrant === Quadrant.Second) {
return [deltaY, -deltaX]
} else if (quadrant === Quadrant.Third) {
return [-deltaX, -deltaY]
} else if (quadrant === Quadrant.Forth) {
return [-deltaY, deltaX]
} else {
return [deltaX, deltaY]
}
}
export default class ScrollerActions {
hooks: EventEmitter
scrollBehaviorX: Behavior
scrollBehaviorY: Behavior
actionsHandler: ActionsHandler
animater: Animater
options: BScrollOptions
directionLockAction: DirectionLockAction
fingerMoved: boolean
contentMoved: boolean
enabled: boolean
startTime: number
endTime: number
ensuringInteger: boolean
constructor(
scrollBehaviorX: Behavior,
scrollBehaviorY: Behavior,
actionsHandler: ActionsHandler,
animater: Animater,
options: BScrollOptions
) {
this.hooks = new EventEmitter([
'start',
'beforeMove',
'scrollStart',
'scroll',
'beforeEnd',
'end',
'scrollEnd',
'contentNotMoved',
'detectMovingDirection',
'coordinateTransformation',
])
this.scrollBehaviorX = scrollBehaviorX
this.scrollBehaviorY = scrollBehaviorY
this.actionsHandler = actionsHandler
this.animater = animater
this.options = options
this.directionLockAction = new DirectionLockAction(
options.directionLockThreshold,
options.freeScroll,
options.eventPassthrough
)
this.enabled = true
this.bindActionsHandler()
}
private bindActionsHandler() {
// [mouse|touch]start event
this.actionsHandler.hooks.on(
this.actionsHandler.hooks.eventTypes.start,
(e: TouchEvent) => {
if (!this.enabled) return true
return this.handleStart(e)
}
)
// [mouse|touch]move event
this.actionsHandler.hooks.on(
this.actionsHandler.hooks.eventTypes.move,
({
deltaX,
deltaY,
e,
}: {
deltaX: number
deltaY: number
e: TouchEvent
}) => {
if (!this.enabled) return true
const [transformateDeltaX, transformateDeltaY] =
applyQuadrantTransformation(deltaX, deltaY, this.options.quadrant)
const transformateDeltaData = {
deltaX: transformateDeltaX,
deltaY: transformateDeltaY,
}
this.hooks.trigger(
this.hooks.eventTypes.coordinateTransformation,
transformateDeltaData
)
return this.handleMove(
transformateDeltaData.deltaX,
transformateDeltaData.deltaY,
e
)
}
)
// [mouse|touch]end event
this.actionsHandler.hooks.on(
this.actionsHandler.hooks.eventTypes.end,
(e: TouchEvent) => {
if (!this.enabled) return true
return this.handleEnd(e)
}
)
// click
this.actionsHandler.hooks.on(
this.actionsHandler.hooks.eventTypes.click,
(e: TouchEvent) => {
// handle native click event
if (this.enabled && !e._constructed) {
this.handleClick(e)
}
}
)
}
private handleStart(e: TouchEvent) {
const timestamp = getNow()
this.fingerMoved = false
this.contentMoved = false
this.startTime = timestamp
this.directionLockAction.reset()
this.scrollBehaviorX.start()
this.scrollBehaviorY.start()
// force stopping last transition or animation
this.animater.doStop()
this.scrollBehaviorX.resetStartPos()
this.scrollBehaviorY.resetStartPos()
this.hooks.trigger(this.hooks.eventTypes.start, e)
}
private handleMove(deltaX: number, deltaY: number, e: TouchEvent) {
if (this.hooks.trigger(this.hooks.eventTypes.beforeMove, e)) {
return
}
const absDistX = this.scrollBehaviorX.getAbsDist(deltaX)
const absDistY = this.scrollBehaviorY.getAbsDist(deltaY)
const timestamp = getNow()
// We need to move at least momentumLimitDistance pixels
// for the scrolling to initiate
if (this.checkMomentum(absDistX, absDistY, timestamp)) {
return true
}
if (this.directionLockAction.checkMovingDirection(absDistX, absDistY, e)) {
this.actionsHandler.setInitiated()
return true
}
const delta = this.directionLockAction.adjustDelta(deltaX, deltaY)
const prevX = this.scrollBehaviorX.getCurrentPos()
const newX = this.scrollBehaviorX.move(delta.deltaX)
const prevY = this.scrollBehaviorY.getCurrentPos()
const newY = this.scrollBehaviorY.move(delta.deltaY)
if (this.hooks.trigger(this.hooks.eventTypes.detectMovingDirection)) {
return
}
if (!this.fingerMoved) {
this.fingerMoved = true
}
const positionChanged = newX !== prevX || newY !== prevY
if (!this.contentMoved && !positionChanged) {
this.hooks.trigger(this.hooks.eventTypes.contentNotMoved)
}
if (!this.contentMoved && positionChanged) {
this.contentMoved = true
this.hooks.trigger(this.hooks.eventTypes.scrollStart)
}
if (this.contentMoved && positionChanged) {
this.animater.translate({
x: newX,
y: newY,
})
this.dispatchScroll(timestamp)
}
}
private dispatchScroll(timestamp: number) {
// dispatch scroll in interval time
if (timestamp - this.startTime > this.options.momentumLimitTime) {
// refresh time and starting position to initiate a momentum
this.startTime = timestamp
this.scrollBehaviorX.updateStartPos()
this.scrollBehaviorY.updateStartPos()
if (this.options.probeType === Probe.Throttle) {
this.hooks.trigger(this.hooks.eventTypes.scroll, this.getCurrentPos())
}
}
// dispatch scroll all the time
if (this.options.probeType > Probe.Throttle) {
this.hooks.trigger(this.hooks.eventTypes.scroll, this.getCurrentPos())
}
}
private checkMomentum(absDistX: number, absDistY: number, timestamp: number) {
return (
timestamp - this.endTime > this.options.momentumLimitTime &&
absDistY < this.options.momentumLimitDistance &&
absDistX < this.options.momentumLimitDistance
)
}
private handleEnd(e: TouchEvent) {
if (this.hooks.trigger(this.hooks.eventTypes.beforeEnd, e)) {
return
}
let currentPos = this.getCurrentPos()
this.scrollBehaviorX.updateDirection()
this.scrollBehaviorY.updateDirection()
if (this.hooks.trigger(this.hooks.eventTypes.end, e, currentPos)) {
return true
}
currentPos = this.ensureIntegerPos(currentPos)
this.animater.translate(currentPos)
this.endTime = getNow()
const duration = this.endTime - this.startTime
this.hooks.trigger(this.hooks.eventTypes.scrollEnd, currentPos, duration)
}
private ensureIntegerPos(currentPos: TranslaterPoint) {
this.ensuringInteger = true
let { x, y } = currentPos
const { minScrollPos: minScrollPosX, maxScrollPos: maxScrollPosX } =
this.scrollBehaviorX
const { minScrollPos: minScrollPosY, maxScrollPos: maxScrollPosY } =
this.scrollBehaviorY
x = x > 0 ? Math.ceil(x) : Math.floor(x)
y = y > 0 ? Math.ceil(y) : Math.floor(y)
x = between(x, maxScrollPosX, minScrollPosX)
y = between(y, maxScrollPosY, minScrollPosY)
return { x, y }
}
private handleClick(e: TouchEvent) {
if (
!preventDefaultExceptionFn(e.target, this.options.preventDefaultException)
) {
maybePrevent(e)
e.stopPropagation()
}
}
getCurrentPos(): TranslaterPoint {
return {
x: this.scrollBehaviorX.getCurrentPos(),
y: this.scrollBehaviorY.getCurrentPos(),
}
}
refresh() {
this.endTime = 0
}
destroy() {
this.hooks.destroy()
}
}
================================================
FILE: packages/core/src/scroller/Behavior.ts
================================================
import { getRect, Direction, EventEmitter } from '@better-scroll/shared-utils'
export type Bounces = [boolean, boolean]
export type Rect = { size: string; position: string }
export interface Options {
scrollable: boolean
momentum: boolean
momentumLimitTime: number
momentumLimitDistance: number
deceleration: number
swipeBounceTime: number
swipeTime: number
bounces: Bounces
rect: Rect
outOfBoundaryDampingFactor: number
specifiedIndexAsContent: number
[key: string]: number | boolean | Bounces | Rect
}
export type Boundary = { minScrollPos: number; maxScrollPos: number }
export class Behavior {
content: HTMLElement
currentPos: number
startPos: number
absStartPos: number
dist: number
minScrollPos: number
maxScrollPos: number
hasScroll: boolean
direction: number
movingDirection: number
relativeOffset: number
wrapperSize: number
contentSize: number
hooks: EventEmitter
constructor(
public wrapper: HTMLElement,
content: HTMLElement,
public options: Options
) {
this.hooks = new EventEmitter([
'beforeComputeBoundary',
'computeBoundary',
'momentum',
'end',
'ignoreHasScroll'
])
this.refresh(content)
}
start() {
this.dist = 0
this.setMovingDirection(Direction.Default)
this.setDirection(Direction.Default)
}
move(delta: number) {
delta = this.hasScroll ? delta : 0
this.setMovingDirection(delta)
return this.performDampingAlgorithm(
delta,
this.options.outOfBoundaryDampingFactor
)
}
setMovingDirection(delta: number) {
this.movingDirection =
delta > 0
? Direction.Negative
: delta < 0
? Direction.Positive
: Direction.Default
}
setDirection(delta: number) {
this.direction =
delta > 0
? Direction.Negative
: delta < 0
? Direction.Positive
: Direction.Default
}
performDampingAlgorithm(delta: number, dampingFactor: number): number {
let newPos = this.currentPos + delta
// Slow down or stop if outside of the boundaries
if (newPos > this.minScrollPos || newPos < this.maxScrollPos) {
if (
(newPos > this.minScrollPos && this.options.bounces[0]) ||
(newPos < this.maxScrollPos && this.options.bounces[1])
) {
newPos = this.currentPos + delta * dampingFactor
} else {
newPos =
newPos > this.minScrollPos ? this.minScrollPos : this.maxScrollPos
}
}
return newPos
}
end(duration: number) {
let momentumInfo: {
destination?: number
duration?: number
} = {
duration: 0
}
const absDist = Math.abs(this.currentPos - this.startPos)
// start momentum animation if needed
if (
this.options.momentum &&
duration < this.options.momentumLimitTime &&
absDist > this.options.momentumLimitDistance
) {
const wrapperSize =
(this.direction === Direction.Negative && this.options.bounces[0]) ||
(this.direction === Direction.Positive && this.options.bounces[1])
? this.wrapperSize
: 0
momentumInfo = this.hasScroll
? this.momentum(
this.currentPos,
this.startPos,
duration,
this.maxScrollPos,
this.minScrollPos,
wrapperSize,
this.options
)
: { destination: this.currentPos, duration: 0 }
} else {
this.hooks.trigger(this.hooks.eventTypes.end, momentumInfo)
}
return momentumInfo
}
private momentum(
current: number,
start: number,
time: number,
lowerMargin: number,
upperMargin: number,
wrapperSize: number,
options = this.options
) {
const distance = current - start
const speed = Math.abs(distance) / time
const { deceleration, swipeBounceTime, swipeTime } = options
const duration = Math.min(swipeTime, (speed * 2) / deceleration)
const momentumData = {
destination:
current + ((speed * speed) / deceleration) * (distance < 0 ? -1 : 1),
duration,
rate: 15
}
this.hooks.trigger(this.hooks.eventTypes.momentum, momentumData, distance)
if (momentumData.destination < lowerMargin) {
momentumData.destination = wrapperSize
? Math.max(
lowerMargin - wrapperSize / 4,
lowerMargin - (wrapperSize / momentumData.rate) * speed
)
: lowerMargin
momentumData.duration = swipeBounceTime
} else if (momentumData.destination > upperMargin) {
momentumData.destination = wrapperSize
? Math.min(
upperMargin + wrapperSize / 4,
upperMargin + (wrapperSize / momentumData.rate) * speed
)
: upperMargin
momentumData.duration = swipeBounceTime
}
momentumData.destination = Math.round(momentumData.destination)
return momentumData
}
updateDirection() {
const absDist = this.currentPos - this.absStartPos
this.setDirection(absDist)
}
refresh(content: HTMLElement) {
const { size, position } = this.options.rect
const isWrapperStatic =
window.getComputedStyle(this.wrapper, null).position === 'static'
// Force reflow
const wrapperRect = getRect(this.wrapper)
// use client is more fair than offset
this.wrapperSize = this.wrapper[
size === 'width' ? 'clientWidth' : 'clientHeight'
]
this.setContent(content)
const contentRect = getRect(this.content)
this.contentSize = contentRect[size]
this.relativeOffset = contentRect[position]
/* istanbul ignore if */
if (isWrapperStatic) {
this.relativeOffset -= wrapperRect[position]
}
this.computeBoundary()
this.setDirection(Direction.Default)
}
private setContent(content: HTMLElement) {
if (content !== this.content) {
this.content = content
this.resetState()
}
}
private resetState() {
this.currentPos = 0
this.startPos = 0
this.dist = 0
this.setDirection(Direction.Default)
this.setMovingDirection(Direction.Default)
this.resetStartPos()
}
computeBoundary() {
this.hooks.trigger(this.hooks.eventTypes.beforeComputeBoundary)
const boundary: Boundary = {
minScrollPos: 0,
maxScrollPos: this.wrapperSize - this.contentSize
}
if (boundary.maxScrollPos < 0) {
boundary.maxScrollPos -= this.relativeOffset
if (this.options.specifiedIndexAsContent === 0) {
boundary.minScrollPos = -this.relativeOffset
}
}
this.hooks.trigger(this.hooks.eventTypes.computeBoundary, boundary)
this.minScrollPos = boundary.minScrollPos
this.maxScrollPos = boundary.maxScrollPos
this.hasScroll =
this.options.scrollable && this.maxScrollPos < this.minScrollPos
if (!this.hasScroll && this.minScrollPos < this.maxScrollPos) {
this.maxScrollPos = this.minScrollPos
this.contentSize = this.wrapperSize
}
}
updatePosition(pos: number) {
this.currentPos = pos
}
getCurrentPos() {
return this.currentPos
}
checkInBoundary() {
const position = this.adjustPosition(this.currentPos)
const inBoundary = position === this.getCurrentPos()
return {
position,
inBoundary
}
}
// adjust position when out of boundary
adjustPosition(pos: number) {
if (
!this.hasScroll &&
!this.hooks.trigger(this.hooks.eventTypes.ignoreHasScroll)
) {
pos = this.minScrollPos
} else if (pos > this.minScrollPos) {
pos = this.minScrollPos
} else if (pos < this.maxScrollPos) {
pos = this.maxScrollPos
}
return pos
}
updateStartPos() {
this.startPos = this.currentPos
}
updateAbsStartPos() {
this.absStartPos = this.currentPos
}
resetStartPos() {
this.updateStartPos()
this.updateAbsStartPos()
}
getAbsDist(delta: number) {
this.dist += delta
return Math.abs(this.dist)
}
destroy() {
this.hooks.destroy()
}
}
================================================
FILE: packages/core/src/scroller/DirectionLock.ts
================================================
import {
TouchEvent,
DirectionLock,
EventPassthrough,
maybePrevent,
} from '@better-scroll/shared-utils'
const enum Passthrough {
Yes = 'yes',
No = 'no',
}
interface DirectionMap {
[key: string]: {
[key: string]: EventPassthrough
}
}
const PassthroughHandlers = {
[Passthrough.Yes]: (e: TouchEvent) => {
return true
},
[Passthrough.No]: (e: TouchEvent) => {
maybePrevent(e)
return false
},
}
const DirectionMap: DirectionMap = {
[DirectionLock.Horizontal]: {
[Passthrough.Yes]: EventPassthrough.Horizontal,
[Passthrough.No]: EventPassthrough.Vertical,
},
[DirectionLock.Vertical]: {
[Passthrough.Yes]: EventPassthrough.Vertical,
[Passthrough.No]: EventPassthrough.Horizontal,
},
}
export default class DirectionLockAction {
directionLocked: DirectionLock
constructor(
public directionLockThreshold: number,
public freeScroll: boolean,
public eventPassthrough: string
) {
this.reset()
}
reset() {
this.directionLocked = DirectionLock.Default
}
checkMovingDirection(absDistX: number, absDistY: number, e: TouchEvent) {
this.computeDirectionLock(absDistX, absDistY)
return this.handleEventPassthrough(e)
}
adjustDelta(deltaX: number, deltaY: number) {
if (this.directionLocked === DirectionLock.Horizontal) {
deltaY = 0
} else if (this.directionLocked === DirectionLock.Vertical) {
deltaX = 0
}
return {
deltaX,
deltaY,
}
}
private computeDirectionLock(absDistX: number, absDistY: number) {
// If you are scrolling in one direction, lock it
if (this.directionLocked === DirectionLock.Default && !this.freeScroll) {
if (absDistX > absDistY + this.directionLockThreshold) {
this.directionLocked = DirectionLock.Horizontal // lock horizontally
} else if (absDistY >= absDistX + this.directionLockThreshold) {
this.directionLocked = DirectionLock.Vertical // lock vertically
} else {
this.directionLocked = DirectionLock.None // no lock
}
}
}
private handleEventPassthrough(e: TouchEvent) {
const handleMap = DirectionMap[this.directionLocked]
if (handleMap) {
if (this.eventPassthrough === handleMap[Passthrough.Yes]) {
return PassthroughHandlers[Passthrough.Yes](e)
} else if (this.eventPassthrough === handleMap[Passthrough.No]) {
return PassthroughHandlers[Passthrough.No](e)
}
}
return false
}
}
================================================
FILE: packages/core/src/scroller/Scroller.ts
================================================
import ActionsHandler from '../base/ActionsHandler'
import Translater, { TranslaterPoint } from '../translater'
import createAnimater, { Animater, Transition } from '../animater'
import { OptionsConstructor as BScrollOptions } from '../Options'
import { Behavior } from './Behavior'
import ScrollerActions from './Actions'
import {
createActionsHandlerOptions,
createBehaviorOptions,
} from './createOptions'
import {
getElement,
ease,
offset,
style,
preventDefaultExceptionFn,
TouchEvent,
isAndroid,
isIOSBadVersion,
click,
dblclick,
tap,
isUndef,
getNow,
cancelAnimationFrame,
EaseItem,
Probe,
EventEmitter,
EventRegister,
} from '@better-scroll/shared-utils'
import { bubbling } from '../utils/bubbling'
import { isSamePoint } from '../utils/compare'
import { MountedBScrollHTMLElement } from '../BScroll'
const MIN_SCROLL_DISTANCE = 1
export interface ExposedAPI {
scrollTo(
x: number,
y: number,
time?: number,
easing?: EaseItem,
extraTransform?: { start: object; end: object }
): void
scrollBy(
deltaX: number,
deltaY: number,
time?: number,
easing?: EaseItem
): void
scrollToElement(
el: HTMLElement | string,
time: number,
offsetX: number | boolean,
offsetY: number | boolean,
easing?: EaseItem
): void
resetPosition(time?: number, easing?: EaseItem): boolean
}
export default class Scroller implements ExposedAPI {
actionsHandler: ActionsHandler
translater: Translater
animater: Animater
scrollBehaviorX: Behavior
scrollBehaviorY: Behavior
actions: ScrollerActions
hooks: EventEmitter
resizeRegister: EventRegister
transitionEndRegister: EventRegister
options: BScrollOptions
wrapperOffset: {
left: number
top: number
}
_reflow: number
resizeTimeout: number = 0
lastClickTime: number | null;
[key: string]: any
constructor(
public wrapper: HTMLElement,
public content: HTMLElement,
options: BScrollOptions
) {
this.hooks = new EventEmitter([
'beforeStart',
'beforeMove',
'beforeScrollStart',
'scrollStart',
'scroll',
'beforeEnd',
'scrollEnd',
'resize',
'touchEnd',
'end',
'flick',
'scrollCancel',
'momentum',
'scrollTo',
'minDistanceScroll',
'scrollToElement',
'beforeRefresh',
])
this.options = options
const { left, right, top, bottom } = this.options.bounce
// direction X
this.scrollBehaviorX = new Behavior(
wrapper,
content,
createBehaviorOptions(options, 'scrollX', [left, right], {
size: 'width',
position: 'left',
})
)
// direction Y
this.scrollBehaviorY = new Behavior(
wrapper,
content,
createBehaviorOptions(options, 'scrollY', [top, bottom], {
size: 'height',
position: 'top',
})
)
this.translater = new Translater(this.content)
this.animater = createAnimater(this.content, this.translater, this.options)
this.actionsHandler = new ActionsHandler(
this.options.bindToTarget ? this.content : wrapper,
createActionsHandlerOptions(this.options)
)
this.actions = new ScrollerActions(
this.scrollBehaviorX,
this.scrollBehaviorY,
this.actionsHandler,
this.animater,
this.options
)
const resizeHandler = this.resize.bind(this)
this.resizeRegister = new EventRegister(window, [
{
name: 'orientationchange',
handler: resizeHandler,
},
{
name: 'resize',
handler: resizeHandler,
},
])
this.registerTransitionEnd()
this.init()
}
private init() {
this.bindTranslater()
this.bindAnimater()
this.bindActions()
// enable pointer events when scrolling ends
this.hooks.on(this.hooks.eventTypes.scrollEnd, () => {
this.togglePointerEvents(true)
})
}
private registerTransitionEnd() {
this.transitionEndRegister = new EventRegister(this.content, [
{
name: style.transitionEnd,
handler: this.transitionEnd.bind(this),
},
])
}
private bindTranslater() {
const hooks = this.translater.hooks
hooks.on(hooks.eventTypes.beforeTranslate, (transformStyle: string[]) => {
if (this.options.translateZ) {
transformStyle.push(this.options.translateZ)
}
})
// disable pointer events when scrolling
hooks.on(hooks.eventTypes.translate, (pos: TranslaterPoint) => {
const prevPos = this.getCurrentPos()
this.updatePositions(pos)
// scrollEnd will dispatch when scroll is force stopping in touchstart handler
// so in touchend handler, don't toggle pointer-events
if (this.actions.ensuringInteger === true) {
this.actions.ensuringInteger = false
return
}
// a valid translate
if (pos.x !== prevPos.x || pos.y !== prevPos.y) {
this.togglePointerEvents(false)
}
})
}
private bindAnimater() {
// reset position
this.animater.hooks.on(
this.animater.hooks.eventTypes.end,
(pos: TranslaterPoint) => {
if (!this.resetPosition(this.options.bounceTime)) {
this.animater.setPending(false)
this.hooks.trigger(this.hooks.eventTypes.scrollEnd, pos)
}
}
)
bubbling(this.animater.hooks, this.hooks, [
{
source: this.animater.hooks.eventTypes.move,
target: this.hooks.eventTypes.scroll,
},
{
source: this.animater.hooks.eventTypes.forceStop,
target: this.hooks.eventTypes.scrollEnd,
},
])
}
private bindActions() {
const actions = this.actions
bubbling(actions.hooks, this.hooks, [
{
source: actions.hooks.eventTypes.start,
target: this.hooks.eventTypes.beforeStart,
},
{
source: actions.hooks.eventTypes.start,
target: this.hooks.eventTypes.beforeScrollStart, // just for event api
},
{
source: actions.hooks.eventTypes.beforeMove,
target: this.hooks.eventTypes.beforeMove,
},
{
source: actions.hooks.eventTypes.scrollStart,
target: this.hooks.eventTypes.scrollStart,
},
{
source: actions.hooks.eventTypes.scroll,
target: this.hooks.eventTypes.scroll,
},
{
source: actions.hooks.eventTypes.beforeEnd,
target: this.hooks.eventTypes.beforeEnd,
},
])
actions.hooks.on(
actions.hooks.eventTypes.end,
(e: TouchEvent, pos: TranslaterPoint) => {
this.hooks.trigger(this.hooks.eventTypes.touchEnd, pos)
if (this.hooks.trigger(this.hooks.eventTypes.end, pos)) {
return true
}
// check if it is a click operation
if (!actions.fingerMoved) {
this.hooks.trigger(this.hooks.eventTypes.scrollCancel)
if (this.checkClick(e)) {
return true
}
}
// reset if we are outside of the boundaries
if (this.resetPosition(this.options.bounceTime, ease.bounce)) {
this.animater.setForceStopped(false)
return true
}
}
)
actions.hooks.on(
actions.hooks.eventTypes.scrollEnd,
(pos: TranslaterPoint, duration: number) => {
const deltaX = Math.abs(pos.x - this.scrollBehaviorX.startPos)
const deltaY = Math.abs(pos.y - this.scrollBehaviorY.startPos)
if (this.checkFlick(duration, deltaX, deltaY)) {
this.animater.setForceStopped(false)
this.hooks.trigger(this.hooks.eventTypes.flick)
return
}
if (this.momentum(pos, duration)) {
this.animater.setForceStopped(false)
return
}
if (actions.contentMoved) {
this.hooks.trigger(this.hooks.eventTypes.scrollEnd, pos)
}
if (this.animater.forceStopped) {
this.animater.setForceStopped(false)
}
}
)
}
private checkFlick(duration: number, deltaX: number, deltaY: number) {
const flickMinMovingDistance = 1 // distinguish flick from click
if (
this.hooks.events.flick.length > 1 &&
duration < this.options.flickLimitTime &&
deltaX < this.options.flickLimitDistance &&
deltaY < this.options.flickLimitDistance &&
(deltaY > flickMinMovingDistance || deltaX > flickMinMovingDistance)
) {
return true
}
}
private momentum(pos: TranslaterPoint, duration: number) {
const meta = {
time: 0,
easing: ease.swiper,
newX: pos.x,
newY: pos.y,
}
// start momentum animation if needed
const momentumX = this.scrollBehaviorX.end(duration)
const momentumY = this.scrollBehaviorY.end(duration)
meta.newX = isUndef(momentumX.destination)
? meta.newX
: (momentumX.destination as number)
meta.newY = isUndef(momentumY.destination)
? meta.newY
: (momentumY.destination as number)
meta.time = Math.max(
momentumX.duration as number,
momentumY.duration as number
)
this.hooks.trigger(this.hooks.eventTypes.momentum, meta, this)
// when x or y changed, do momentum animation now!
if (meta.newX !== pos.x || meta.newY !== pos.y) {
// change easing function when scroller goes out of the boundaries
if (
meta.newX > this.scrollBehaviorX.minScrollPos ||
meta.newX < this.scrollBehaviorX.maxScrollPos ||
meta.newY > this.scrollBehaviorY.minScrollPos ||
meta.newY < this.scrollBehaviorY.maxScrollPos
) {
meta.easing = ease.swipeBounce
}
this.scrollTo(meta.newX, meta.newY, meta.time, meta.easing)
return true
}
}
private checkClick(e: TouchEvent) {
const cancelable = {
preventClick: this.animater.forceStopped,
}
// we scrolled less than momentumLimitDistance pixels
if (this.hooks.trigger(this.hooks.eventTypes.checkClick)) {
this.animater.setForceStopped(false)
return true
}
if (!cancelable.preventClick) {
const _dblclick = this.options.dblclick
let dblclickTrigged = false
if (_dblclick && this.lastClickTime) {
const { delay = 300 } = _dblclick as any
if (getNow() - this.lastClickTime < delay) {
dblclickTrigged = true
dblclick(e)
}
}
if (this.options.tap) {
tap(e, this.options.tap)
}
if (
this.options.click &&
!preventDefaultExceptionFn(
e.target,
this.options.preventDefaultException
)
) {
click(e)
}
this.lastClickTime = dblclickTrigged ? null : getNow()
return true
}
return false
}
private resize() {
if (!this.actions.enabled) {
return
}
// fix a scroll problem under Android condition
/* istanbul ignore if */
if (isAndroid) {
this.wrapper.scrollTop = 0
}
clearTimeout(this.resizeTimeout)
this.resizeTimeout = window.setTimeout(() => {
this.hooks.trigger(this.hooks.eventTypes.resize)
}, this.options.resizePolling)
}
/* istanbul ignore next */
private transitionEnd(e: TouchEvent) {
if (e.target !== this.content || !this.animater.pending) {
return
}
const animater = this.animater as Transition
animater.transitionTime()
if (!this.resetPosition(this.options.bounceTime, ease.bounce)) {
this.animater.setPending(false)
if (this.options.probeType !== Probe.Realtime) {
this.hooks.trigger(
this.hooks.eventTypes.scrollEnd,
this.getCurrentPos()
)
}
}
}
togglePointerEvents(enabled = true) {
let el = this.content.children.length
? this.content.children
: [this.content]
let pointerEvents = enabled ? 'auto' : 'none'
for (let i = 0; i < el.length; i++) {
let node = el[i] as MountedBScrollHTMLElement
// ignore BetterScroll instance's wrapper DOM
/* istanbul ignore if */
if (node.isBScrollContainer) {
continue
}
node.style.pointerEvents = pointerEvents
}
}
refresh(content: HTMLElement) {
const contentChanged = this.setContent(content)
this.hooks.trigger(this.hooks.eventTypes.beforeRefresh)
this.scrollBehaviorX.refresh(content)
this.scrollBehaviorY.refresh(content)
if (contentChanged) {
this.translater.setContent(content)
this.animater.setContent(content)
this.transitionEndRegister.destroy()
this.registerTransitionEnd()
if (this.options.bindToTarget) {
this.actionsHandler.setContent(content)
}
}
this.actions.refresh()
this.wrapperOffset = offset(this.wrapper)
}
private setContent(content: HTMLElement): boolean {
const contentChanged = content !== this.content
if (contentChanged) {
this.content = content
}
return contentChanged
}
scrollBy(deltaX: number, deltaY: number, time = 0, easing?: EaseItem) {
const { x, y } = this.getCurrentPos()
easing = !easing ? ease.bounce : easing
deltaX += x
deltaY += y
this.scrollTo(deltaX, deltaY, time, easing)
}
scrollTo(
x: number,
y: number,
time = 0,
easing = ease.bounce,
extraTransform = {
start: {},
end: {},
}
) {
const easingFn = this.options.useTransition ? easing.style : easing.fn
const currentPos = this.getCurrentPos()
const startPoint = {
x: currentPos.x,
y: currentPos.y,
...extraTransform.start,
}
const endPoint = {
x,
y,
...extraTransform.end,
}
this.hooks.trigger(this.hooks.eventTypes.scrollTo, endPoint)
// it is an useless move
if (isSamePoint(startPoint, endPoint)) return
const deltaX = Math.abs(endPoint.x - startPoint.x)
const deltaY = Math.abs(endPoint.y - startPoint.y)
// considering of browser compatibility for decimal transform value
// force translating immediately
if (deltaX < MIN_SCROLL_DISTANCE && deltaY < MIN_SCROLL_DISTANCE) {
time = 0
this.hooks.trigger(this.hooks.eventTypes.minDistanceScroll)
}
this.animater.move(startPoint, endPoint, time, easingFn)
}
scrollToElement(
el: HTMLElement | string,
time: number,
offsetX: number | boolean,
offsetY: number | boolean,
easing?: EaseItem
) {
const targetEle = getElement(el)
const pos = offset(targetEle)
const getOffset = (
offset: number | boolean,
size: number,
wrapperSize: number
) => {
if (typeof offset === 'number') {
return offset
}
// if offsetX/Y are true we center the element to the screen
return offset ? Math.round(size / 2 - wrapperSize / 2) : 0
}
offsetX = getOffset(
offsetX,
targetEle.offsetWidth,
this.wrapper.offsetWidth
)
offsetY = getOffset(
offsetY,
targetEle.offsetHeight,
this.wrapper.offsetHeight
)
const getPos = (
pos: number,
wrapperPos: number,
offset: number,
scrollBehavior: Behavior
) => {
pos -= wrapperPos
pos = scrollBehavior.adjustPosition(pos - offset)
return pos
}
pos.left = getPos(
pos.left,
this.wrapperOffset.left,
offsetX,
this.scrollBehaviorX
)
pos.top = getPos(
pos.top,
this.wrapperOffset.top,
offsetY,
this.scrollBehaviorY
)
if (
this.hooks.trigger(this.hooks.eventTypes.scrollToElement, targetEle, pos)
) {
return
}
this.scrollTo(pos.left, pos.top, time, easing)
}
resetPosition(time = 0, easing = ease.bounce) {
const {
position: x,
inBoundary: xInBoundary,
} = this.scrollBehaviorX.checkInBoundary()
const {
position: y,
inBoundary: yInBoundary,
} = this.scrollBehaviorY.checkInBoundary()
if (xInBoundary && yInBoundary) {
return false
}
/* istanbul ignore if */
if (isIOSBadVersion) {
// fix ios 13.4 bouncing
// see it in issues 982
this.reflow()
}
// out of boundary
this.scrollTo(x, y, time, easing)
return true
}
/* istanbul ignore next */
reflow() {
this._reflow = this.content.offsetHeight
}
updatePositions(pos: TranslaterPoint) {
this.scrollBehaviorX.updatePosition(pos.x)
this.scrollBehaviorY.updatePosition(pos.y)
}
getCurrentPos() {
return this.actions.getCurrentPos()
}
enable() {
this.actions.enabled = true
}
disable() {
cancelAnimationFrame(this.animater.timer)
this.actions.enabled = false
}
destroy(this: Scroller) {
const keys = [
'resizeRegister',
'transitionEndRegister',
'actionsHandler',
'actions',
'hooks',
'animater',
'translater',
'scrollBehaviorX',
'scrollBehaviorY',
]
keys.forEach((key) => this[key].destroy())
}
}
================================================
FILE: packages/core/src/scroller/__mocks__/Actions.ts
================================================
import DirectionLock from '../DirectionLock'
jest.mock('../DirectionLock')
import { EventEmitter } from '@better-scroll/shared-utils'
const ScrollerActions = jest
.fn()
.mockImplementation(
(
scrollBehaviorX,
scrollBehaviorY,
actionsHandler,
animater,
bscrollOptions
) => {
const directionLockAction = new DirectionLock(0, false, '')
return {
options: bscrollOptions,
scrollBehaviorX,
scrollBehaviorY,
actionsHandler,
animater,
directionLockAction,
moved: false,
enabled: true,
startTime: 0,
endTime: 0,
ensuringInteger: false,
getCurrentPos: jest.fn().mockImplementation(() => {
return {
x: 0,
y: 0,
}
}),
refresh: jest.fn(),
destroy: jest.fn(),
hooks: new EventEmitter([
'start',
'beforeMove',
'scrollStart',
'scroll',
'beforeEnd',
'end',
'scrollEnd',
'contentNotMoved',
'detectMovingDirection',
]),
}
}
)
export default ScrollerActions
================================================
FILE: packages/core/src/scroller/__mocks__/Behavior.ts
================================================
import { EventEmitter } from '@better-scroll/shared-utils'
const Behavior = jest.fn().mockImplementation((content, bscrollOptions) => {
return {
content,
options: bscrollOptions,
startPos: 0,
currentPos: 0,
absStartPos: 0,
dist: 0,
minScrollPos: 0,
maxScrollPos: 0,
hasScroll: true,
direction: 0,
movingDirection: 0,
relativeOffset: 0,
wrapperSize: 0,
contentSize: 0,
hooks: new EventEmitter([
'momentum',
'end',
'beforeComputeBoundary',
'computeBoundary',
'ignoreHasScroll',
]),
start: jest.fn(),
move: jest.fn(),
end: jest.fn(),
updateDirection: jest.fn(),
refresh: jest.fn(),
updatePosition: jest.fn(),
getCurrentPos: jest.fn().mockImplementation(() => {
return 0
}),
checkInBoundary: jest.fn().mockImplementation(() => {
return {
position: 0,
inBoundary: false,
}
}),
adjustPosition: jest.fn(),
updateStartPos: jest.fn(),
updateAbsStartPos: jest.fn(),
resetStartPos: jest.fn(),
getAbsDist: jest.fn().mockImplementation((delta: number) => {
return Math.abs(delta)
}),
destroy: jest.fn(),
computeBoundary: jest.fn(),
setMovingDirection: jest.fn(),
setDirection: jest.fn(),
performDampingAlgorithm: jest.fn(),
}
})
export { Behavior }
================================================
FILE: packages/core/src/scroller/__mocks__/DirectionLock.ts
================================================
const DirectionLock = jest
.fn()
.mockImplementation((content, bscrollOptions) => {
return {
directionLocked: '',
directionLockThreshold: '5',
eventPassthrough: '',
freeScroll: false,
reset: jest.fn(),
checkMovingDirection: jest.fn().mockImplementation((ret = true) => {
return ret
}),
adjustDelta: jest
.fn()
.mockImplementation((deltaX: number = 0, deltaY: number = 0) => {
return {
deltaX,
deltaY,
}
}),
}
})
export default DirectionLock
================================================
FILE: packages/core/src/scroller/__mocks__/Scroller.ts
================================================
import createAnimater from '../../animater'
import Translater from '../../translater'
import { Behavior } from '../Behavior'
import ActionsHandler from '../../base/ActionsHandler'
import Actions from '../Actions'
jest.mock('../../animater')
jest.mock('../../translater')
jest.mock('../Behavior')
jest.mock('../../base/ActionsHandler')
jest.mock('../Actions')
import { EventEmitter, EventRegister } from '@better-scroll/shared-utils'
const Scroller = jest.fn().mockImplementation((wrapper, bscrollOptions) => {
const content = wrapper.children[0]
const translater = new Translater(content)
const animater = createAnimater(content, translater, bscrollOptions)
const actionsHandler = new ActionsHandler(wrapper, bscrollOptions)
const scrollBehaviorX = new Behavior(
wrapper,
content,
Object.assign(bscrollOptions, { scrollable: bscrollOptions.scrollX })
)
const scrollBehaviorY = new Behavior(
wrapper,
content,
Object.assign(bscrollOptions, { scrollable: bscrollOptions.scrollY })
)
const actions = new Actions(
scrollBehaviorX,
scrollBehaviorY,
actionsHandler,
animater,
bscrollOptions
)
return {
wrapper,
content,
options: bscrollOptions,
translater,
animater,
actionsHandler,
actions,
hooks: new EventEmitter([
'beforeStart',
'beforeMove',
'beforeScrollStart',
'scrollStart',
'scroll',
'beforeEnd',
'scrollEnd',
'resize',
'touchEnd',
'end',
'flick',
'scrollCancel',
'momentum',
'scrollTo',
'minDistanceScroll',
'scrollToElement',
'transitionEnd',
'checkClick',
'beforeRefresh',
]),
scrollBehaviorX,
scrollBehaviorY,
resizeRegister: new EventRegister(wrapper, []),
transitionEndRegister: new EventRegister(wrapper, []),
scrollTo: jest.fn(),
resetPosition: jest.fn(),
togglePointerEvents: jest.fn(),
reflow: jest.fn(),
}
})
export default Scroller
================================================
FILE: packages/core/src/scroller/__tests__/Actions.spec.ts
================================================
import { Behavior } from '../Behavior'
import createAnimater from '../../animater'
import Translater from '../../translater'
import { OptionsConstructor } from '../../Options'
import ActionsHandler from '../../base/ActionsHandler'
import DirectionLockAction from '../DirectionLock'
jest.mock('../Behavior')
jest.mock('../../animater')
jest.mock('../../translater')
jest.mock('../../Options')
jest.mock('../../base/ActionsHandler')
jest.mock('../DirectionLock')
import Actions from '../Actions'
describe('Actions Class tests', () => {
let actions: Actions
beforeEach(() => {
// redefine window.performance
// because we will use window.performance.timing.navigationStart
// in our file('src/util/lang.ts')
Object.defineProperty(window, 'performance', {
get() {
return undefined
},
})
let content = document.createElement('div')
let wrapper = document.createElement('div')
let bscrollOptions = new OptionsConstructor() as any
let scrollBehaviorX = new Behavior(wrapper, content, bscrollOptions)
let scrollBehaviorY = new Behavior(wrapper, content, bscrollOptions)
let actionsHandler = new ActionsHandler(wrapper, bscrollOptions)
let translater = new Translater(content)
let animater = createAnimater(content, translater, bscrollOptions)
actions = new Actions(
scrollBehaviorX,
scrollBehaviorY,
actionsHandler,
animater,
bscrollOptions
)
})
it('should init hooks when call constructor function', () => {
expect(actions.hooks.eventTypes).toHaveProperty('start')
expect(actions.hooks.eventTypes).toHaveProperty('beforeMove')
expect(actions.hooks.eventTypes).toHaveProperty('scroll')
expect(actions.hooks.eventTypes).toHaveProperty('beforeEnd')
expect(actions.hooks.eventTypes).toHaveProperty('end')
expect(actions.hooks.eventTypes).toHaveProperty('scrollEnd')
})
it('should invoke handleStart when actionsHandler trigger start hook', () => {
actions.actionsHandler.hooks.trigger('start')
expect(actions.fingerMoved).toBe(false)
expect(actions.scrollBehaviorX.start).toBeCalled()
expect(actions.scrollBehaviorY.start).toBeCalled()
expect(actions.scrollBehaviorX.resetStartPos).toBeCalled()
expect(actions.scrollBehaviorY.resetStartPos).toBeCalled()
expect(actions.animater.doStop).toBeCalled()
})
it('should invoke handleMove when actionsHandler trigger move hook', () => {
let e = new Event('touchmove')
let beforeMoveMockHandler = jest.fn()
let scrollStartHandler = jest.fn()
let scrollHandler = jest.fn()
// cancelable beforeMove hook
actions.hooks.on(
actions.hooks.eventTypes.beforeMove,
jest.fn().mockImplementationOnce(() => true)
)
actions.hooks.on(actions.hooks.eventTypes.beforeMove, beforeMoveMockHandler)
actions.hooks.on(actions.hooks.eventTypes.scrollStart, scrollStartHandler)
actions.hooks.on(actions.hooks.eventTypes.scroll, scrollHandler)
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(beforeMoveMockHandler).toHaveBeenCalledTimes(0)
// moved less than 15 px
actions.endTime = Date.now() - 400
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: 10,
e,
})
expect(beforeMoveMockHandler).toHaveBeenCalledTimes(1)
expect(actions.directionLockAction.checkMovingDirection).not.toBeCalled()
// lock direction
actions.endTime = Date.now()
actions.actionsHandler.hooks.trigger('move', {
deltaX: 10,
deltaY: 0,
e,
})
expect(beforeMoveMockHandler).toHaveBeenCalledTimes(2)
expect(actions.actionsHandler.setInitiated).toBeCalled()
actions.startTime = Date.now() - 400
actions.options.probeType = 1
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(beforeMoveMockHandler).toHaveBeenCalledTimes(3)
expect(scrollStartHandler).toBeCalled()
expect(scrollHandler).toBeCalledTimes(1)
expect(actions.scrollBehaviorY.getAbsDist).toHaveBeenCalledWith(-20)
expect(actions.scrollBehaviorY.move).toHaveBeenCalledWith(-20)
actions.startTime = Date.now()
actions.options.probeType = 3
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(scrollHandler).toBeCalledTimes(2)
const cbMock = jest.fn().mockImplementationOnce(() => true)
actions.fingerMoved = true
actions.hooks.on(actions.hooks.eventTypes.detectMovingDirection, cbMock)
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(cbMock).toBeCalled()
expect(actions.fingerMoved).toBe(true)
// content not moved
const mockFn = jest.fn()
actions.contentMoved = false
actions.hooks.on(actions.hooks.eventTypes.contentNotMoved, mockFn)
actions.startTime = Date.now() - 400
actions.scrollBehaviorX.move = jest.fn().mockImplementation(() => 0)
actions.scrollBehaviorY.move = jest.fn().mockImplementation(() => 0)
actions.endTime = Date.now() + 400
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: 0,
e,
})
expect(mockFn).toBeCalled()
})
it('should invoke handleEnd when actionsHandler trigger end hook', () => {
let beforeEndMockHandler = jest.fn()
let endMockHandler = jest.fn()
let scrollEndHandler = jest.fn()
let e = new Event('touchend')
// cancelable beforeEnd hook
actions.hooks.on(
actions.hooks.eventTypes.beforeEnd,
jest.fn().mockImplementationOnce(() => true)
)
// cancelable end hook
actions.hooks.on(
actions.hooks.eventTypes.end,
jest.fn().mockImplementationOnce(() => true)
)
actions.hooks.on(actions.hooks.eventTypes.beforeEnd, beforeEndMockHandler)
actions.hooks.on(actions.hooks.eventTypes.end, endMockHandler)
actions.hooks.on(actions.hooks.eventTypes.scrollEnd, scrollEndHandler)
actions.actionsHandler.hooks.trigger(
actions.actionsHandler.hooks.eventTypes.end,
e
)
expect(beforeEndMockHandler).not.toBeCalled()
expect(actions.scrollBehaviorX.updateDirection).not.toBeCalled()
actions.actionsHandler.hooks.trigger(
actions.actionsHandler.hooks.eventTypes.end,
e
)
expect(beforeEndMockHandler).toHaveBeenCalledTimes(1)
expect(actions.scrollBehaviorX.updateDirection).toHaveBeenCalledTimes(1)
expect(actions.scrollBehaviorY.updateDirection).toHaveBeenCalledTimes(1)
expect(endMockHandler).not.toBeCalled()
actions.actionsHandler.hooks.trigger(
actions.actionsHandler.hooks.eventTypes.end,
e
)
expect(scrollEndHandler).toBeCalled()
})
it('should get correct position when invoking getCurrentPos method', () => {
actions.getCurrentPos()
expect(actions.scrollBehaviorX.getCurrentPos).toBeCalled()
expect(actions.scrollBehaviorY.getCurrentPos).toBeCalled()
})
it('should reset endTime when refreshed', () => {
actions.refresh()
expect(actions.endTime).toBe(0)
})
it('destroy()', () => {
actions.destroy()
expect(actions.hooks.events).toEqual({})
expect(actions.hooks.eventTypes).toEqual({})
})
it('should can be disabled', () => {
actions.enabled = false
const actionsHandler = actions.actionsHandler
actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.start)
expect(actions.directionLockAction.reset).not.toBeCalled()
actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.move, {
deltaX: 0,
deltaY: 0,
})
expect(actions.scrollBehaviorX.getAbsDist).not.toBeCalled()
actionsHandler.hooks.trigger(actionsHandler.hooks.eventTypes.end)
expect(actions.scrollBehaviorX.updateDirection).not.toBeCalled()
})
it('should prevent native click event', () => {
actions.actionsHandler.hooks.trigger(
actions.actionsHandler.hooks.eventTypes.click,
{
_constructed: false,
target: document.createElement('div'),
preventDefault() {},
stopPropagation() {},
}
)
})
it('apply quadrant transformation when force rotating by CSS', () => {
let e = new Event('touchmove')
// second quadrant
actions.options.quadrant = 2
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(-20)
expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(-0)
// third quadrant
actions.options.quadrant = 3
actions.actionsHandler.hooks.trigger('move', {
deltaX: -20,
deltaY: 0,
e,
})
expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(20)
expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(-0)
// forth quadrant
actions.options.quadrant = 4
actions.actionsHandler.hooks.trigger('move', {
deltaX: 20,
deltaY: 0,
e,
})
expect(actions.scrollBehaviorX.getAbsDist).toBeCalledWith(-0)
expect(actions.scrollBehaviorY.getAbsDist).toBeCalledWith(20)
})
it('coordinateTransformation hook', () => {
let e = new Event('touchmove')
const mockFn = jest.fn()
actions.hooks.on(actions.hooks.eventTypes.coordinateTransformation, mockFn)
actions.actionsHandler.hooks.trigger('move', {
deltaX: 0,
deltaY: -20,
e,
})
expect(mockFn).toBeCalled()
})
})
================================================
FILE: packages/core/src/scroller/__tests__/Behavior.spec.ts
================================================
import { Behavior } from '../Behavior'
import { createDiv } from '../../__tests__/__utils__/layout'
describe('Behavior Class tests', () => {
let behavior: Behavior
let content: HTMLElement
let wrapper: HTMLElement
let options = {
movable: false,
scrollable: true,
momentum: true,
momentumLimitTime: 300,
momentumLimitDistance: 15,
deceleration: 0.001,
swipeBounceTime: 2500,
outOfBoundaryDampingFactor: 1 / 3,
specifiedIndexAsContent: 0,
swipeTime: 2000,
bounces: [true, true] as [boolean, boolean],
rect: {
size: 'height',
position: 'top',
},
}
beforeEach(() => {
wrapper = createDiv(100, 200, 0, 0)
content = createDiv(100, 400, 0, 0)
document.body.appendChild(content)
wrapper.appendChild(content)
behavior = new Behavior(wrapper, content, options)
})
it('should init hooks when call constructor function', () => {
expect(behavior.hooks.eventTypes).toHaveProperty('momentum')
expect(behavior.hooks.eventTypes).toHaveProperty('end')
expect(behavior.currentPos).toBe(0)
expect(behavior.startPos).toBe(0)
expect(behavior.content).toEqual(content)
})
it('should refresh some properties when invoking refresh method', () => {
behavior.refresh(behavior.content)
expect(behavior.wrapperSize).toBe(200)
expect(behavior.contentSize).toBe(400)
expect(behavior.relativeOffset).toBe(0)
expect(behavior.minScrollPos).toBe(-0)
expect(behavior.maxScrollPos).toBe(-200)
expect(behavior.hasScroll).toBe(true)
expect(behavior.direction).toBe(0)
})
it('should refresh some properties when invoking start method', () => {
behavior.start()
expect(behavior.direction).toBe(0)
expect(behavior.movingDirection).toBe(0)
expect(behavior.dist).toBe(0)
})
it('should refresh some properties when invoking move method', () => {
behavior.refresh(behavior.content)
expect(behavior.move(-10)).toBe(-10)
expect(behavior.movingDirection).toBe(1)
})
it('should not trigger momentum scroll when duration is exceed momentumLimitTime', () => {
let endMockHandler = jest.fn()
behavior.hooks.on('end', endMockHandler)
behavior.refresh(behavior.content)
behavior.end(400)
expect(endMockHandler).toBeCalled()
expect(endMockHandler).toHaveBeenCalledWith({
duration: 0,
})
})
it('should trigger momentum scroll', () => {
behavior.refresh(behavior.content)
behavior.currentPos = -100
expect(behavior.end(100)).toEqual({
destination: -200,
duration: 2500,
rate: 15,
})
behavior.hooks.on(
behavior.hooks.eventTypes.momentum,
(momentumData: any) => {
momentumData.destination = 200
}
)
expect(behavior.end(100)).toEqual({
destination: -0,
duration: 2500,
rate: 15,
})
})
it('should keep direction unchanged when invoking updateDirection method', () => {
behavior.updateDirection()
expect(behavior.direction).toBe(0)
})
it('should update position when invoking updatePosition method', () => {
behavior.updatePosition(100)
expect(behavior.currentPos).toBe(100)
})
it('should auto bouncing within boundary when out of boundary', () => {
behavior.refresh(behavior.content)
behavior.updatePosition(-400)
expect(behavior.checkInBoundary()).toEqual({
position: -200,
inBoundary: false,
})
})
it('performDampingAlgorithm()', () => {
const ret = behavior.performDampingAlgorithm(20, 0.1)
expect(ret).toBe(2)
behavior.options.bounces = [false, false]
// simulate out of the boundaries and no bounce
const ret2 = behavior.performDampingAlgorithm(20, 0.1)
expect(ret2).toBe(-0)
})
it('getAbsDist()', () => {
const ret = behavior.getAbsDist(-20)
expect(ret).toBe(20)
})
it('adjustPosition()', () => {
const ret = behavior.adjustPosition(20.1)
expect(ret).toBe(-0)
})
it('computeBoundary()', () => {
behavior.hooks.on(
behavior.hooks.eventTypes.computeBoundary,
(boundary: { minScrollPos: number; maxScrollPos: number }) => {
boundary.minScrollPos = 20
boundary.maxScrollPos = 30
}
)
behavior.computeBoundary()
expect(behavior.maxScrollPos).toEqual(behavior.minScrollPos)
})
})
================================================
FILE: packages/core/src/scroller/__tests__/DirectionLock.spec.ts
================================================
import DirectionLock from '../DirectionLock'
import {
Direction,
DirectionLock as DirectionLockEnum,
} from '@better-scroll/shared-utils'
describe('DirectionLock Class tests', () => {
let directionLock: DirectionLock
let e = new Event('touchstart') as any
beforeEach(() => {
directionLock = new DirectionLock(5, false, '')
})
it('should call reset when call constructor function', () => {
expect(directionLock.directionLocked).toBe('')
})
it('should lock vertically when scrolled in direction Y', () => {
directionLock.checkMovingDirection(0, 20, e)
expect(directionLock.directionLocked).toBe('vertical')
})
it('should lock horizontally when scrolled in direction X', () => {
directionLock.checkMovingDirection(20, 0, e)
expect(directionLock.directionLocked).toBe('horizontal')
})
it('should no lock when freeScroll is true', () => {
directionLock.freeScroll = true
directionLock.checkMovingDirection(20, 20, e)
expect(directionLock.directionLocked).toBe('')
})
it('adjustDelta() ', () => {
directionLock.directionLocked = DirectionLockEnum.Horizontal
const ret1 = directionLock.adjustDelta(20, 20)
expect(ret1).toMatchObject({
deltaX: 20,
deltaY: 0,
})
directionLock.directionLocked = DirectionLockEnum.Vertical
const ret2 = directionLock.adjustDelta(20, 20)
expect(ret2).toMatchObject({
deltaX: 0,
deltaY: 20,
})
})
it('reset()', () => {
directionLock.directionLocked = DirectionLockEnum.Vertical
directionLock.reset()
expect(directionLock.directionLocked).toBe(DirectionLockEnum.Default)
})
it('checkMovingDirection()', () => {
directionLock.directionLocked = DirectionLockEnum.Horizontal
directionLock.eventPassthrough = DirectionLockEnum.Horizontal
const ret1 = directionLock.checkMovingDirection(20, 20, {
preventDefault() {
return true
},
} as any)
expect(ret1).toBe(true)
directionLock.directionLocked = DirectionLockEnum.Horizontal
directionLock.eventPassthrough = DirectionLockEnum.Vertical
const ret2 = directionLock.checkMovingDirection(20, 20, {
preventDefault() {
return true
},
} as any)
expect(ret2).toBe(false)
// no locked
directionLock.directionLocked = DirectionLockEnum.Default
const ret3 = directionLock.checkMovingDirection(20, 20, {
preventDefault() {
return true
},
} as any)
expect(ret3).toBe(false)
})
})
================================================
FILE: packages/core/src/scroller/__tests__/Scroller.spec.ts
================================================
import { Behavior } from '../Behavior'
import createAnimater from '../../animater'
import Translater from '../../translater'
import { OptionsConstructor } from '../../Options'
import ActionsHandler from '../../base/ActionsHandler'
import Actions from '../Actions'
jest.mock('../Behavior')
jest.mock('../../animater')
jest.mock('../../translater')
jest.mock('../../Options')
jest.mock('../../base/ActionsHandler')
jest.mock('../Actions')
import Scroller from '../Scroller'
import { createDiv } from '../../__tests__/__utils__/layout'
describe('Scroller Class tests', () => {
let scroller: Scroller
let wrapper: HTMLElement
let content: HTMLElement
beforeEach(() => {
// redefine window.performance
// because we will use window.performance.timing.navigationStart
// in our file('src/util/lang.ts')
Object.defineProperty(window, 'performance', {
get() {
return undefined
},
})
wrapper = createDiv(100, 200, 0, 0)
content = createDiv(100, 400, 0, 0)
document.body.appendChild(content)
wrapper.appendChild(content)
let bscrollOptions = new OptionsConstructor() as any
scroller = new Scroller(wrapper, content, bscrollOptions)
})
it('should init hooks when call constructor function', () => {
;[
'beforeStart',
'beforeMove',
'beforeScrollStart',
'scrollStart',
'scroll',
'beforeEnd',
'scrollEnd',
'resize',
'beforeRefresh',
'touchEnd',
'flick',
'scrollCancel',
'momentum',
'scrollTo',
'scrollToElement',
'minDistanceScroll',
].forEach((key) => {
expect(scroller.hooks.eventTypes).toHaveProperty(key)
})
})
describe('bindTranslater', () => {
it('should bind beforeTranslate hook', () => {
let transform: string[] = []
scroller.translater.hooks.trigger('beforeTranslate', transform)
expect(transform).toContain(' translateZ(0)')
})
it('should bind translate hook', () => {
scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {
return {
x: 0,
y: 0,
}
})
scroller.translater.hooks.trigger('translate', { x: 0, y: -20 })
expect(scroller.scrollBehaviorX.updatePosition).toBeCalled()
expect(scroller.scrollBehaviorY.updatePosition).toBeCalled()
expect(scroller.scrollBehaviorX.updatePosition).toHaveBeenCalledWith(0)
expect(scroller.scrollBehaviorY.updatePosition).toHaveBeenCalledWith(-20)
})
})
describe('bindAnimater', () => {
it('should bind end hook ', () => {
let pos = {
x: 0,
y: 20,
}
let scrollEndMockHandler = jest.fn()
scroller.hooks.on('scrollEnd', scrollEndMockHandler)
scroller.scrollBehaviorX.checkInBoundary = jest
.fn()
.mockImplementation(() => {
return {
position: 0,
inBoundary: true,
}
})
scroller.scrollBehaviorY.checkInBoundary = jest
.fn()
.mockImplementation(() => {
return {
position: 0,
inBoundary: true,
}
})
scroller.animater.hooks.trigger('end', pos)
expect(scroller.animater.setPending).toHaveBeenCalledWith(false)
expect(scrollEndMockHandler).toHaveBeenCalledWith({
x: 0,
y: 20,
})
})
it('should bubble hooks', () => {
let scrollEndMockHandler = jest.fn()
let scrollMockHandler = jest.fn()
scroller.hooks.on('scrollEnd', scrollEndMockHandler)
scroller.hooks.on('scroll', scrollMockHandler)
scroller.animater.hooks.trigger('forceStop')
scroller.animater.hooks.trigger('move')
expect(scrollEndMockHandler).toBeCalled()
expect(scrollMockHandler).toBeCalled()
})
})
describe('bindActions', () => {
it('bind end hook', () => {
let touchEndMockHandler = jest.fn()
let e = new Event('touch') as any
Object.defineProperty(e, 'target', {
get() {
return scroller.wrapper
},
})
// cancelable scroller end hook
scroller.hooks.on(scroller.hooks.eventTypes.touchEnd, touchEndMockHandler)
scroller.hooks.trigger(scroller.hooks.eventTypes.touchEnd, { x: 0, y: 0 })
scroller.hooks.on(
scroller.hooks.eventTypes.end,
jest.fn().mockImplementationOnce(() => true)
)
const ret = scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.end,
e,
{ x: 0, y: 0 }
)
expect(ret).toBe(true)
expect(touchEndMockHandler).toBeCalled()
expect(touchEndMockHandler).toHaveBeenCalledWith({
x: 0,
y: 0,
})
/* click operation */
// case 1
scroller.hooks.on(
scroller.hooks.eventTypes.checkClick,
jest.fn().mockImplementationOnce(() => true)
)
scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {
x: 0,
y: 0,
})
expect(scroller.animater.setForceStopped).toBeCalledWith(false)
// case 2 dblclick
const mockFn2 = jest.fn()
scroller.options.dblclick = true
scroller.lastClickTime = Date.now()
scroller.wrapper.addEventListener('dblclick', mockFn2)
scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {
x: 0,
y: 0,
})
expect(mockFn2).toBeCalled()
// case 3 tap
const mockFn3 = jest.fn()
scroller.options.tap = 'tap'
scroller.wrapper.addEventListener('tap', mockFn3)
scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {
x: 0,
y: 0,
})
expect(mockFn3).toBeCalled()
// case 4 click
const mockFn4 = jest.fn()
scroller.options.click = true
scroller.wrapper.addEventListener('click', mockFn4)
scroller.actions.hooks.trigger(scroller.actions.hooks.eventTypes.end, e, {
x: 0,
y: 0,
})
expect(mockFn4).toBeCalled()
// case 5 force stopped
scroller.animater.forceStopped = true
const ret2 = scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.end,
e,
{ x: 0, y: 0 }
)
expect(ret2).toBe(true)
})
it('bind scrollEnd hook', () => {
let momentumMockHandler = jest.fn()
let noop = (() => {}) as any
scroller.hooks.on('momentum', momentumMockHandler)
scroller.scrollBehaviorX.end = jest.fn().mockImplementation(() => {
return {
duration: 400,
destination: 0,
}
})
scroller.scrollBehaviorY.end = jest.fn().mockImplementation(() => {
return {
duration: 400,
destination: -20,
}
})
// flick
const mockFn = jest.fn()
scroller.hooks.events['flick'] = [noop, noop]
scroller.hooks.on(scroller.hooks.eventTypes.flick, mockFn)
scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.scrollEnd,
{ x: 0, y: -20 },
50
)
expect(mockFn).toBeCalled()
// momentum
scroller.hooks.events['flick'] = []
scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.scrollEnd,
{ x: 0, y: -40 },
50
)
expect(scroller.animater.setForceStopped).toBeCalledWith(false)
// force stop from transition
scroller.actions.contentMoved = false
scroller.animater.forceStopped = true
scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.scrollEnd,
{ x: 0, y: -20 },
50
)
expect(scroller.animater.setForceStopped).toBeCalledWith(false)
const mockFn2 = jest.fn()
scroller.actions.contentMoved = true
scroller.hooks.on(scroller.hooks.eventTypes.scrollEnd, mockFn2)
scroller.actions.hooks.trigger(
scroller.actions.hooks.eventTypes.scrollEnd,
{ x: 0, y: -20 },
50
)
expect(mockFn2).toBeCalledWith({
x: 0,
y: -20,
})
})
})
it('should invoke resize method when window is resized', () => {
jest.useFakeTimers()
const mockFn = jest.fn()
scroller.hooks.on(scroller.hooks.eventTypes.resize, mockFn)
const resizeEvent = document.createEvent('Event')
resizeEvent.initEvent('resize', true, true)
window.dispatchEvent(resizeEvent)
jest.advanceTimersByTime(60)
jest.clearAllTimers()
expect(mockFn).toBeCalledTimes(1)
// disable scroller
scroller.actions.enabled = false
resizeEvent.initEvent('resize', true, true)
window.dispatchEvent(resizeEvent)
expect(mockFn).toBeCalledTimes(1)
})
it('should trigger scrollTo hook when invoking scrollTo method', () => {
let scrollToMockHandler = jest.fn()
scroller.hooks.on('scrollTo', scrollToMockHandler)
scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {
return {
x: 0,
y: 0,
}
})
scroller.scrollTo(0, -20, 800)
expect(scrollToMockHandler).toBeCalledWith({
x: 0,
y: -20,
})
expect(scroller.animater.move).toBeCalledWith(
{
x: 0,
y: 0,
},
{
x: 0,
y: -20,
},
800,
'cubic-bezier(0.165, 0.84, 0.44, 1)'
)
})
it('scrollToElement()', () => {
let scrollToElementMockHandler = jest.fn()
scroller.hooks.on(
scroller.hooks.eventTypes.scrollToElement,
scrollToElementMockHandler
)
scroller.refresh(scroller.content)
scroller.scrollBehaviorX.adjustPosition = jest.fn(() => {
return 0
})
scroller.scrollBehaviorY.adjustPosition = jest.fn(() => {
return 0
})
scroller.actions.getCurrentPos = jest.fn().mockImplementation(() => {
return {
x: 0,
y: 0,
}
})
scroller.scrollToElement(content, 0, false, false)
expect(scrollToElementMockHandler).toBeCalled()
// to a specified position
scroller.scrollToElement(content, 0, 0, 0)
expect(scrollToElementMockHandler).toHaveBeenLastCalledWith(content, {
left: 0,
top: 0,
})
const mockFn2 = jest.fn()
scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, () => true)
scroller.hooks.on(scroller.hooks.eventTypes.scrollToElement, mockFn2)
scroller.scrollToElement(content, 0, 0, 0)
expect(mockFn2).not.toBeCalled()
})
it('scrollBy ', () => {
const mockFn = jest.fn()
scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, mockFn)
scroller.scrollBy(20, 20)
expect(mockFn).toBeCalledWith({
x: 20,
y: 20,
})
})
it('enable() & disable()', () => {
scroller.disable()
expect(scroller.actions.enabled).toBe(false)
scroller.enable()
expect(scroller.actions.enabled).toBe(true)
})
it('should update postions when invoking updatePositions method', () => {
scroller.updatePositions({
x: 20,
y: -20,
})
expect(scroller.scrollBehaviorX.updatePosition).toHaveBeenCalledWith(20)
expect(scroller.scrollBehaviorY.updatePosition).toHaveBeenCalledWith(-20)
})
it('refresh()', () => {
scroller.options.bindToTarget = true
scroller.refresh(document.createElement('p'))
expect(scroller.scrollBehaviorX.refresh).toBeCalled()
expect(scroller.scrollBehaviorY.refresh).toBeCalled()
expect(scroller.actions.refresh).toBeCalled()
expect(scroller.actionsHandler.setContent).toBeCalled()
})
it('destroy()', () => {
scroller.destroy()
const keys = [
'actionsHandler',
'actions',
'animater',
'translater',
'scrollBehaviorX',
'scrollBehaviorY',
]
keys.forEach((key) => {
expect(scroller[key].destroy).toBeCalled()
})
})
it('resetPosition() ', () => {
const mockFn = jest.fn()
scroller.hooks.on(scroller.hooks.eventTypes.scrollTo, mockFn)
scroller.resetPosition()
expect(mockFn).toBeCalledWith({
x: 0,
y: 0,
})
})
it('scrollTo()', () => {
// minDistanceScroll
const mockFn = jest.fn()
scroller.hooks.on(scroller.hooks.eventTypes.minDistanceScroll, mockFn)
scroller.scrollTo(0, 0.5, 300)
})
it('should not toggle pointer-events when casting last position into integer in touchend handlers', () => {
scroller.actions.ensuringInteger = true
scroller.translater.hooks.trigger('translate', { x: 0, y: -20 })
expect(scroller.actions.ensuringInteger).toBe(false)
})
})
================================================
FILE: packages/core/src/scroller/__tests__/createOptions.spec.ts
================================================
import {
createActionsHandlerOptions,
createBehaviorOptions,
} from '../createOptions'
import { OptionsConstructor } from '../../Options'
jest.mock('../../Options')
describe('createOptions helper function tests', () => {
let bsOptions: any
beforeEach(() => {
bsOptions = new OptionsConstructor()
})
it('should return correct object when invoking createActionsHandlerOptions function', () => {
let ret = createActionsHandlerOptions(bsOptions)
expect(ret).toEqual({
click: false,
bindToWrapper: false,
disableMouse: true,
preventDefault: true,
stopPropagation: false,
preventDefaultException: {
tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|AUDIO)$/,
},
})
})
it('should return correct object when invoking createBehaviorOptions function', () => {
let ret = createBehaviorOptions(bsOptions, 'scrollY', [true, true], {
size: 'width',
position: 'top',
})
expect(ret).toEqual({
momentum: true,
momentumLimitTime: 300,
momentumLimitDistance: 15,
deceleration: 0.0015,
swipeBounceTime: 500,
swipeTime: 2500,
scrollable: true,
outOfBoundaryDampingFactor: 1 / 3,
specifiedIndexAsContent: 0,
bounces: [true, true],
rect: {
size: 'width',
position: 'top',
},
})
})
})
================================================
FILE: packages/core/src/scroller/createOptions.ts
================================================
import { Options as BScrollOptions } from '../Options'
import { Options as ActionsHandlerOptions } from '../base/ActionsHandler'
import { Options as BehaviorOptions, Bounces, Rect } from './Behavior'
export function createActionsHandlerOptions(bsOptions: BScrollOptions) {
const options = [
'click',
'bindToWrapper',
'disableMouse',
'disableTouch',
'preventDefault',
'stopPropagation',
'tagException',
'preventDefaultException',
'autoEndDistance',
].reduce<ActionsHandlerOptions>((prev, cur) => {
prev[cur] = bsOptions[cur]
return prev
}, {} as ActionsHandlerOptions)
return options
}
export function createBehaviorOptions(
bsOptions: BScrollOptions,
extraProp: 'scrollX' | 'scrollY',
bounces: Bounces,
rect: Rect
) {
const options = [
'momentum',
'momentumLimitTime',
'momentumLimitDistance',
'deceleration',
'swipeBounceTime',
'swipeTime',
'outOfBoundaryDampingFactor',
'specifiedIndexAsContent',
].reduce<BehaviorOptions>((prev, cur) => {
prev[cur] = bsOptions[cur]
return prev
}, {} as BehaviorOptions)
// add extra property
options.scrollable = !!bsOptions[extraProp]
options.bounces = bounces
options.rect = rect
return options
}
================================================
FILE: packages/core/src/translater/__mocks__/index.ts
================================================
import { EventEmitter } from '@better-scroll/shared-utils'
const Translater = jest.fn().mockImplementation((content) => {
return {
style: content.style,
hooks: new EventEmitter(['beforeTranslate', 'translate']),
getComputedPosition: jest
.fn()
.mockImplementation((position = { x: 0, y: 0 }) => {
return position
}),
translate: jest.fn(),
destroy: jest.fn(),
setContent: jest.fn(),
}
})
export default Translater
================================================
FILE: packages/core/src/translater/__tests__/index.spec.ts
================================================
import Translater from '../index'
describe('Translater Class test suit', () => {
let translater: Translater
let contentEl = document.createElement('div')
beforeEach(() => {
translater = new Translater(contentEl)
})
afterEach(() => {
jest.clearAllMocks()
})
it('work well when call translate()', () => {
const mockFn1 = jest.fn()
const mockFn2 = jest.fn()
translater.hooks.on(translater.hooks.eventTypes.beforeTranslate, mockFn1)
translater.hooks.on(translater.hooks.eventTypes.translate, mockFn2)
translater.translate({ x: 0, y: 0, dummy: 0 })
expect(mockFn1).toBeCalled()
expect(mockFn1).toBeCalledWith(['translateX(0px)', 'translateY(0px)'], {
x: 0,
y: 0,
dummy: 0,
})
expect(mockFn2).toBeCalled()
expect(mockFn2).toBeCalledWith({ x: 0, y: 0, dummy: 0 })
expect(translater.content.style.transform).toBe(
'translateX(0px) translateY(0px)'
)
})
it('get correct position when call getComputedPosition()', () => {
// jsDOM library's getComputedStyle is different from browser
window.getComputedStyle = jest.fn().mockImplementation(() => {
return {
transform: 'matrix(1, 0, 0, 1, 0, 0)',
}
})
const { x, y } = translater.getComputedPosition()
expect(x).toBe(0)
expect(y).toBe(0)
})
it('should clear hooks when destroyed', () => {
translater.hooks.on(translater.hooks.eventTypes.beforeTranslate, () => {})
expect(translater.hooks.eventTypes.beforeTranslate).toBe('beforeTranslate')
translater.destroy()
expect(translater.hooks.eventTypes.beforeTranslate).toBeFalsy()
})
})
================================================
FILE: packages/core/src/translater/index.ts
================================================
import {
style,
safeCSSStyleDeclaration,
EventEmitter,
} from '@better-scroll/shared-utils'
export interface TranslaterPoint {
x: number
y: number
[key: string]: number
}
interface TranslaterMetaData {
x: [string, string]
y: [string, string]
[key: string]: any
}
const translaterMetaData: TranslaterMetaData = {
x: ['translateX', 'px'],
y: ['translateY', 'px'],
}
export default class Translater {
content: HTMLElement
style: CSSStyleDeclaration
hooks: EventEmitter
constructor(content: HTMLElement) {
this.setContent(content)
this.hooks = new EventEmitter(['beforeTranslate', 'translate'])
}
getComputedPosition() {
let cssStyle = window.getComputedStyle(
this.content,
null
) as safeCSSStyleDeclaration
let matrix = cssStyle[style.transform].split(')')[0].split(', ')
const x = +(matrix[12] || matrix[4]) || 0
const y = +(matrix[13] || matrix[5]) || 0
return {
x,
y,
}
}
translate(point: TranslaterPoint) {
let transformStyle = [] as string[]
Object.keys(point).forEach((key) => {
if (!translaterMetaData[key]) {
return
}
const transformFnName = translaterMetaData[key][0]
if (transformFnName) {
const transformFnArgUnit = translaterMetaData[key][1]
const transformFnArg = point[key]
transformStyle.push(
`${transformFnName}(${transformFnArg}${transformFnArgUnit})`
)
}
})
this.hooks.trigger(
this.hooks.eventTypes.beforeTranslate,
transformStyle,
point
)
this.style[style.transform as any] = transformStyle.join(' ')
this.hooks.trigger(this.hooks.eventTypes.translate, point)
}
setContent(content: HTMLElement) {
if (this.content !== content) {
this.content = content
this.style = content.style
}
}
destroy() {
this.hooks.destroy()
}
}
================================================
FILE: packages/core/src/utils/__tests__/bubbling.spec.ts
================================================
import { bubbling } from '../bubbling'
import { EventEmitter } from '@better-scroll/shared-utils'
describe('bubbling', () => {
it('bubbling', () => {
const parentHooks = new EventEmitter(['test'])
const childHooks = new EventEmitter(['test'])
bubbling(childHooks, parentHooks, ['test'])
const handler = jest.fn(() => {})
parentHooks.on('test', handler)
childHooks.trigger('test', 'dummy test')
expect(handler).toBeCalledWith('dummy test')
})
})
================================================
FILE: packages/core/src/utils/bubbling.ts
================================================
import { EventEmitter } from '@better-scroll/shared-utils'
interface BubblingEventMap {
source: string
target: string
}
type BubblingEventConfig = BubblingEventMap | string
export function bubbling(
source: EventEmitter,
target: EventEmitter,
events: BubblingEventConfig[]
) {
events.forEach(event => {
let sourceEvent: string
let targetEvent: string
if (typeof event === 'string') {
sourceEvent = targetEvent = event
} else {
sourceEvent = event.source
targetEvent = event.target
}
source.on(sourceEvent, function(...args: any[]) {
return target.trigger(targetEvent, ...args)
})
})
}
================================================
FILE: packages/core/src/utils/compare.ts
================================================
import { TranslaterPoint } from '../translater'
export function isSamePoint(
startPoint: TranslaterPoint,
endPoint: TranslaterPoint
): boolean {
// keys of startPoint and endPoint should be equal
const keys = Object.keys(startPoint)
for (let key of keys) {
if (startPoint[key] !== endPoint[key]) return false
}
return true
}
================================================
FILE: packages/core/src/utils/compat.ts
================================================
import { Direction } from '@better-scroll/shared-utils'
import { TranslaterPoint } from '../translater'
type Position = {
x: number
y: number
}
// iOS 13.6 - 14.x, window.getComputedStyle sometimes will get wrong transform value
// when bs use transition mode
// eg: translateY -100px -> -200px, when the last frame which is about to scroll to -200px
// window.getComputedStyle(this.content) will calculate transformY to be -100px(startPoint)
// it is weird
// so we should validate position caculated by 'window.getComputedStyle'
export const isValidPostion = (
startPoint: TranslaterPoint,
endPoint: TranslaterPoint,
currentPos: Position,
prePos: Position
) => {
const computeDirection = (endValue: number, startValue: number) => {
const delta = endValue - startValue
const direction =
delta > 0
? Direction.Negative
: delta < 0
? Direction.Positive
: Direction.Default
return direction
}
const directionX = computeDirection(endPoint.x, startPoint.x)
const directionY = computeDirection(endPoint.y, startPoint.y)
const deltaX = currentPos.x - prePos.x
const deltaY = currentPos.y - prePos.y
return directionX * deltaX <= 0 && directionY * deltaY <= 0
}
================================================
FILE: packages/core/src/utils/typesHelper.ts
================================================
export type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
================================================
FILE: packages/examples/README.md
================================================
# examples
BetterScroll example in different Vue scenarios.
[vue demo](https://better-scroll.github.io/examples/#/)
================================================
FILE: packages/examples/build/vue-example-build.js
================================================
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var webpackConfig = require('./vue-webpack.conf.js')
var spinner = ora('building for production...')
spinner.start()
const assetsRoot = path.join(__dirname, '../dist/vue')
console.log(chalk.red(assetsRoot))
rm(assetsRoot, err => {
if (err) throw err
webpack(webpackConfig, function(err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
================================================
FILE: packages/examples/build/vue-webpack.conf.js
================================================
const Config = require('webpack-chain')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const webpackBar = require('webpackbar')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const path = require('path')
const fs = require('fs')
const { e2e } = require('yargs').argv
const execa = require('execa')
const isProd = process.env.NODE_ENV === 'production'
function resolve(dir) {
return path.join(__dirname, '../', dir)
}
const webpackConfig = new Config()
console.log('-----', isProd)
webpackConfig
.mode(isProd ? 'production' : 'development')
.devtool(isProd ? 'false' : 'eval-source-map')
.entry('app')
.add('./vue/main.js')
.end()
.output
.path(isProd ? path.resolve(__dirname, '../dist/vue') : undefined)
.publicPath(isProd ? '/examples/' : '/')
.filename(isProd ? 'static/js/[name].[chunkhash].js' : '[name].js')
.chunkFilename(isProd ? 'static/js/[id].[chunkhash].js' : '[name].js')
.end()
.resolve
.alias
.set('vue-example', resolve('vue'))
.set('common', resolve('common'))
.end()
.extensions
.add('.js')
.add('.vue')
.add('.json')
.add('.ts')
.end()
.end()
.module
.rule('compileVue')
.test(/\.vue$/)
.use('vue')
.loader('vue-loader')
.end()
.end()
.rule('transformJs')
.test(/\.js$/)
.use('babel')
.loader('babel-loader')
.options({
presets: ["@babel/preset-env"]
})
.end()
.include
.add(resolve('common'))
.add(resolve('vue'))
.end()
.end()
.rule('url')
.test(/\.(png|jpe?g|gif|svg|webp)(\?.*)?$/)
.use('url')
.loader('url-loader')
.options({
esModule: false,
limit: 10000,
name: 'static/img/[name].[hash:7].[ext]'
})
.end()
.end()
.rule('ts')
.test(/\.ts$/)
.use('ts')
.loader('ts-loader')
.options({
transpileOnly: true
})
.end()
.end()
.rule('css')
.test(/\.css$/)
.use('style-loader')
.loader(
process.env.NODE_ENV !== 'production' ?
'vue-style-loader' :
MiniCssExtractPlugin.loader
)
.end()
.use('css-loader')
.loader('css-loader')
.end()
.end()
.rule('stylus')
.test(/.styl(us)?$/)
.use('style-loader')
.loader(
process.env.NODE_ENV !== 'production' ?
'vue-style-loader' :
MiniCssExtractPlugin.loader
)
.end()
.use('css-loader')
.loader('css-loader')
.end()
.use('postcss-loader')
.loader('postcss-loader')
.end()
.use('stylus-loader')
.loader('stylus-loader')
.end()
.end()
.end()
.plugin('VueLoaderPlugin')
.use(VueLoaderPlugin)
.end()
.plugin('WebpackBar')
.use(webpackBar)
.end()
.plugin('MiniCssExtractPlugin')
.use(MiniCssExtractPlugin, [{
filename: 'static/css/[name].[contenthash].css'
}])
.end()
.when(!isProd, () => {
webpackConfig
.devServer
.open(true)
.hot(true)
.compress(true)
.end()
.plugin('HotModuleReplacementPlugin')
.use(webpack.HotModuleReplacementPlugin)
.end()
.plugin('NoEmitOnErrorsPlugin')
.use(webpack.NoEmitOnErrorsPlugin)
.end()
.plugin('HtmlWebpackPlugin')
.use(HtmlWebpackPlugin, [{
filename: 'index.html',
template: './vue/index.html',
inject: true
}])
.end()
.plugin('FriendlyErrorsPlugin')
.use(FriendlyErrorsPlugin)
.end()
}, () => {
webpackConfig
.optimization
.minimizer('TerserPlugin')
.use(TerserPlugin)
.end()
.end()
.plugin('OptimizeCSSPlugin')
.use(OptimizeCSSPlugin, [{
cssProcessorOptions: {
safe: true
}
}])
.end()
.plugin('HtmlWebpackPlugin')
.use(HtmlWebpackPlugin, [{
filename: 'index.html',
template: './vue/index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}])
.end()
.plugin('CopyWebpackPlugin')
.use(CopyWebpackPlugin, [[{
from: path.resolve(__dirname, '../static'),
to: 'static',
ignore: ['.*']
}]])
.end()
})
function getPackagesName() {
let ret
let all = fs.readdirSync(resolve('../../packages'))
// drop hidden file whose name is startWidth '.'
// drop packages which would not be published(eg: examples and vuepress-docs)
ret = all
.filter(name => {
const isHiddenFile = /^\./g.test(name)
return !isHiddenFile
})
.filter(name => {
const isPrivatePackages = require(resolve(`../../packages/${name}/package.json`)).private
return !isPrivatePackages
})
.map((name) => {
return require(resolve(`../../packages/${name}/package.json`)).name
})
return ret
}
// add alias
getPackagesName().forEach((name) => {
webpackConfig.resolve.alias.set(`${name}$`, `${name}/src/index.ts`)
})
let config = { ...webpackConfig.toConfig(), devServer: { host: '0.0.0.0', disableHostCheck: true }}
// run test e2e
if (e2e) {
config.devServer.setup = (app, server) => {
server.middleware.waitUntilValid(async () => {
// back to src directory
const cwd = path.join(__dirname, '../../')
await execa('yarn', ['test:e2e'], { stdio: 'inherit', cwd })
})
}
}
module.exports = config
================================================
FILE: packages/examples/package.json
================================================
{
"name": "examples",
"version": "2.5.1",
"description": "Examples of BetterScroll",
"author": {
"name": "jizhi",
"email": "theniceangel@163.com"
},
"private": true,
"scripts": {
"vue:dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build/vue-webpack.conf.js --host 0.0.0.0 --port 8932 --colors",
"vue:test:e2e": "yarn vue:dev --e2e",
"vue:build": "cross-env NODE_ENV=production node build/vue-example-build.js",
"vue:release": "sh vue-release.sh"
},
"bugs": {
"url": "https://github.com/ustbhuangyi/better-scroll/issues"
},
"homepage": "https://github.com/ustbhuangyi/better-scroll",
"keywords": [
"scroll",
"iscroll",
"javascript",
"typescript",
"ios",
"better-scroll examples"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git@github.com:ustbhuangyi/better-scroll.git",
"directory": "packages/examples"
},
"devDependencies": {
"@babel/polyfill": "^7.4.4",
"babel-loader": "8.1.0",
"copy-webpack-plugin": "^5.0.2",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"ora": "^3.4.0",
"terser-webpack-plugin": "^4.1.0",
"uglifyjs-webpack-plugin": "^2.1.2",
"vue-loader": "15.9.6",
"webpack-cli": "3.3.0",
"webpackbar": "3.2.0"
}
}
================================================
FILE: packages/examples/postcss.config.js
================================================
module.exports = {
plugins: [
require('autoprefixer')({
browsers: require('./package.json').browserslist
})
]
}
================================================
FILE: packages/examples/static/css/github-light.css
================================================
/*
Copyright 2014 GitHub Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.pl-c /* comment */ {
color: #969896;
}
.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
.pl-s .pl-v /* string variable */ {
color: #0086b3;
}
.pl-e /* entity */,
.pl-en /* entity.name */ {
color: #795da3;
}
.pl-s .pl-s1 /* string source */,
.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
color: #333;
}
.pl-ent /* entity.name.tag */ {
color: #63a35c;
}
.pl-k /* keyword, storage, storage.type */ {
color: #a71d5d;
}
.pl-pds /* punctuation.definition.string, string.regexp.character-class */,
.pl-s /* string */,
.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
.pl-sr /* string.regexp */,
.pl-sr .pl-cce /* string.regexp constant.character.escape */,
.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
color: #183691;
}
.pl-v /* variable */ {
color: #ed6a43;
}
.pl-id /* invalid.deprecated */ {
color: #b52a1d;
}
.pl-ii /* invalid.illegal */ {
background-color: #b52a1d;
color: #f8f8f8;
}
.pl-sr .pl-cce /* string.regexp constant.character.escape */ {
color: #63a35c;
font-weight: bold;
}
.pl-ml /* markup.list */ {
color: #693a17;
}
.pl-mh /* markup.heading */,
.pl-mh .pl-en /* markup.heading entity.name */,
.pl-ms /* meta.separator */ {
color: #1d3e81;
font-weight: bold;
}
.pl-mq /* markup.quote */ {
color: #008080;
}
.pl-mi /* markup.italic */ {
color: #333;
font-style: italic;
}
.pl-mb /* markup.bold */ {
color: #333;
font-weight: bold;
}
.pl-md /* markup.deleted, meta.diff.header.from-file */ {
background-color: #ffecec;
color: #bd2c00;
}
.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
background-color: #eaffea;
color: #55a532;
}
.pl-mdr /* meta.diff.range */ {
color: #795da3;
font-weight: bold;
}
.pl-mo /* meta.output */ {
color: #1d3e81;
}
================================================
FILE: packages/examples/static/css/normalize.css
================================================
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary.md` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended
gitextract_mo2zutw5/ ├── .editorconfig ├── .eslintrc.js ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .huskyrc.json ├── .npmignore ├── .travis.yml ├── .yarnrc ├── LICENSE ├── README.md ├── README_zh-CN.md ├── jest-e2e.config.js ├── jest-puppeteer.config.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages/ │ ├── better-scroll/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ └── index.ts │ ├── core/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── BScroll.ts │ │ ├── Instance.ts │ │ ├── Options.ts │ │ ├── __mocks__/ │ │ │ ├── Options.ts │ │ │ └── index.ts │ │ ├── __tests__/ │ │ │ ├── Options.spec.ts │ │ │ ├── __utils__/ │ │ │ │ ├── event.ts │ │ │ │ └── layout.ts │ │ │ └── index.spec.ts │ │ ├── animater/ │ │ │ ├── Animation.ts │ │ │ ├── Base.ts │ │ │ ├── Transition.ts │ │ │ ├── __mocks__/ │ │ │ │ ├── Animation.ts │ │ │ │ ├── Transition.ts │ │ │ │ └── index.ts │ │ │ ├── __tests__/ │ │ │ │ ├── Animation.spec.ts │ │ │ │ ├── Transition.spec.ts │ │ │ │ └── index.spec.ts │ │ │ └── index.ts │ │ ├── base/ │ │ │ ├── ActionsHandler.ts │ │ │ ├── __mocks__/ │ │ │ │ └── ActionsHandler.ts │ │ │ └── __tests__/ │ │ │ └── ActionsHandler.spec.ts │ │ ├── index.ts │ │ ├── scroller/ │ │ │ ├── Actions.ts │ │ │ ├── Behavior.ts │ │ │ ├── DirectionLock.ts │ │ │ ├── Scroller.ts │ │ │ ├── __mocks__/ │ │ │ │ ├── Actions.ts │ │ │ │ ├── Behavior.ts │ │ │ │ ├── DirectionLock.ts │ │ │ │ └── Scroller.ts │ │ │ ├── __tests__/ │ │ │ │ ├── Actions.spec.ts │ │ │ │ ├── Behavior.spec.ts │ │ │ │ ├── DirectionLock.spec.ts │ │ │ │ ├── Scroller.spec.ts │ │ │ │ └── createOptions.spec.ts │ │ │ └── createOptions.ts │ │ ├── translater/ │ │ │ ├── __mocks__/ │ │ │ │ └── index.ts │ │ │ ├── __tests__/ │ │ │ │ └── index.spec.ts │ │ │ └── index.ts │ │ └── utils/ │ │ ├── __tests__/ │ │ │ └── bubbling.spec.ts │ │ ├── bubbling.ts │ │ ├── compare.ts │ │ ├── compat.ts │ │ └── typesHelper.ts │ ├── examples/ │ │ ├── README.md │ │ ├── build/ │ │ │ ├── vue-example-build.js │ │ │ └── vue-webpack.conf.js │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── static/ │ │ │ └── css/ │ │ │ ├── github-light.css │ │ │ ├── normalize.css │ │ │ ├── reset.css │ │ │ └── stylesheet.css │ │ ├── vue/ │ │ │ ├── App.vue │ │ │ ├── components/ │ │ │ │ ├── compose/ │ │ │ │ │ ├── pullup-pulldown-outnested.vue │ │ │ │ │ ├── pullup-pulldown-slide.vue │ │ │ │ │ ├── pullup-pulldown.vue │ │ │ │ │ └── slide-nested.vue │ │ │ │ ├── core/ │ │ │ │ │ ├── default.vue │ │ │ │ │ ├── dynamic-content.vue │ │ │ │ │ ├── freescroll.vue │ │ │ │ │ ├── horizontal-rotated.vue │ │ │ │ │ ├── horizontal.vue │ │ │ │ │ ├── specified-content.vue │ │ │ │ │ └── vertical-rotated.vue │ │ │ │ ├── form/ │ │ │ │ │ └── textarea.vue │ │ │ │ ├── indicators/ │ │ │ │ │ ├── minimap.vue │ │ │ │ │ └── parallax-scroll.vue │ │ │ │ ├── infinity/ │ │ │ │ │ ├── data/ │ │ │ │ │ │ └── message.json │ │ │ │ │ └── default.vue │ │ │ │ ├── mouse-wheel/ │ │ │ │ │ ├── horizontal-scroll.vue │ │ │ │ │ ├── horizontal-slide.vue │ │ │ │ │ ├── picker.vue │ │ │ │ │ ├── pulldown.vue │ │ │ │ │ ├── pullup.vue │ │ │ │ │ ├── vertical-scroll.vue │ │ │ │ │ └── vertical-slide.vue │ │ │ │ ├── movable/ │ │ │ │ │ ├── default.vue │ │ │ │ │ ├── multi-content-scale.vue │ │ │ │ │ ├── multi-content.vue │ │ │ │ │ └── scale.vue │ │ │ │ ├── nested-scroll/ │ │ │ │ │ ├── horizontal-in-vertical.vue │ │ │ │ │ ├── horizontal.vue │ │ │ │ │ ├── triple-vertical.vue │ │ │ │ │ └── vertical.vue │ │ │ │ ├── observe-dom/ │ │ │ │ │ └── default.vue │ │ │ │ ├── observe-image/ │ │ │ │ │ └── default.vue │ │ │ │ ├── picker/ │ │ │ │ │ ├── double-column.vue │ │ │ │ │ ├── linkage-column.vue │ │ │ │ │ └── one-column.vue │ │ │ │ ├── pulldown/ │ │ │ │ │ ├── default.vue │ │ │ │ │ └── sina-weibo.vue │ │ │ │ ├── pullup/ │ │ │ │ │ └── default.vue │ │ │ │ ├── scrollbar/ │ │ │ │ │ ├── custom.vue │ │ │ │ │ ├── horizontal.vue │ │ │ │ │ ├── mousewheel.vue │ │ │ │ │ └── vertical.vue │ │ │ │ ├── slide/ │ │ │ │ │ ├── banner.vue │ │ │ │ │ ├── dynamic.vue │ │ │ │ │ ├── fullpage.vue │ │ │ │ │ ├── specified-index.vue │ │ │ │ │ └── vertical.vue │ │ │ │ └── zoom/ │ │ │ │ └── default.vue │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── pages/ │ │ │ │ ├── compose-entry.vue │ │ │ │ ├── core-entry.vue │ │ │ │ ├── form-entry.vue │ │ │ │ ├── indicators-entry.vue │ │ │ │ ├── infinity-entry.vue │ │ │ │ ├── mouse-wheel-entry.vue │ │ │ │ ├── movable-entry.vue │ │ │ │ ├── nested-scroll-entry.vue │ │ │ │ ├── observe-dom-entry.vue │ │ │ │ ├── observe-image-entry.vue │ │ │ │ ├── picker-entry.vue │ │ │ │ ├── pulldown-entry.vue │ │ │ │ ├── pullup-entry.vue │ │ │ │ ├── scrollbar-entry.vue │ │ │ │ ├── slide-entry.vue │ │ │ │ └── zoom-entry.vue │ │ │ └── router/ │ │ │ └── index.js │ │ └── vue-release.sh │ ├── indicators/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __mocks__/ │ │ │ └── indicator.ts │ │ ├── __tests__/ │ │ │ ├── index.spec.ts │ │ │ └── indicator.spec.ts │ │ ├── index.ts │ │ ├── indicator.ts │ │ └── types.ts │ ├── infinity/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── DataManager.ts │ │ ├── DomManager.ts │ │ ├── IndexCalculator.ts │ │ ├── Tombstone.ts │ │ ├── __tests__/ │ │ │ ├── DataManager.spec.ts │ │ │ ├── IndexCalculator.spec.ts │ │ │ └── __utils__/ │ │ │ ├── FakeList.ts │ │ │ └── constans.ts │ │ └── index.ts │ ├── mouse-wheel/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ └── index.ts │ ├── movable/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ ├── nested-scroll/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── BScrollFamily.ts │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ ├── observe-dom/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ └── index.ts │ ├── observe-image/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ └── index.ts │ ├── pull-down/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ ├── pull-up/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ ├── react-examples/ │ │ ├── README.md │ │ ├── config-overrides.js │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── static/ │ │ │ └── css/ │ │ │ ├── github-light.css │ │ │ ├── normalize.css │ │ │ ├── reset.css │ │ │ └── stylesheet.css │ │ └── src/ │ │ ├── App.js │ │ ├── index.js │ │ ├── index.styl │ │ ├── pages/ │ │ │ ├── compose/ │ │ │ │ ├── components/ │ │ │ │ │ ├── pullup-pulldown-outnested.js │ │ │ │ │ ├── pullup-pulldown-slide.js │ │ │ │ │ ├── pullup-pulldown.js │ │ │ │ │ └── slide-nested.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── core/ │ │ │ │ ├── components/ │ │ │ │ │ ├── default.js │ │ │ │ │ ├── dynamic-content.js │ │ │ │ │ ├── freescroll.js │ │ │ │ │ ├── horizontal-rotated.js │ │ │ │ │ ├── horizontal.js │ │ │ │ │ ├── specified-content.js │ │ │ │ │ └── vertical-rotated.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── form/ │ │ │ │ ├── components/ │ │ │ │ │ └── textarea.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── indicators/ │ │ │ │ ├── components/ │ │ │ │ │ ├── minimap.js │ │ │ │ │ └── parallax-scroll.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── infinity/ │ │ │ │ ├── data/ │ │ │ │ │ └── message.json │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── mouse-wheel/ │ │ │ │ ├── components/ │ │ │ │ │ ├── horizontal-scroll.js │ │ │ │ │ ├── horizontal-slide.js │ │ │ │ │ ├── picker.js │ │ │ │ │ ├── pulldown.js │ │ │ │ │ ├── pullup.js │ │ │ │ │ ├── vertical-scroll.js │ │ │ │ │ └── vertical-slide.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── movable/ │ │ │ │ ├── components/ │ │ │ │ │ ├── default.js │ │ │ │ │ ├── multi-content-scale.js │ │ │ │ │ ├── multi-content.js │ │ │ │ │ └── scale.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── nested-scroll/ │ │ │ │ ├── components/ │ │ │ │ │ ├── horizontal-in-vertical.js │ │ │ │ │ ├── horizontal.js │ │ │ │ │ ├── triple-vertical.js │ │ │ │ │ └── vertical.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── observe-dom/ │ │ │ │ ├── components/ │ │ │ │ │ └── default.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── observe-image/ │ │ │ │ ├── components/ │ │ │ │ │ └── default.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── picker/ │ │ │ │ ├── components/ │ │ │ │ │ ├── double-column.js │ │ │ │ │ ├── linkage-column.js │ │ │ │ │ └── one-column.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── pulldown/ │ │ │ │ ├── components/ │ │ │ │ │ ├── default.js │ │ │ │ │ └── sina-weibo.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── pullup/ │ │ │ │ ├── components/ │ │ │ │ │ └── default.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── scrollbar/ │ │ │ │ ├── components/ │ │ │ │ │ ├── custom.js │ │ │ │ │ ├── horizontal.js │ │ │ │ │ ├── mousewheel.js │ │ │ │ │ └── vertical.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ ├── slide/ │ │ │ │ ├── components/ │ │ │ │ │ ├── banner.js │ │ │ │ │ ├── dynamic.js │ │ │ │ │ ├── fullpage.js │ │ │ │ │ ├── specified-index.js │ │ │ │ │ └── vertical.js │ │ │ │ ├── index.js │ │ │ │ └── index.styl │ │ │ └── zoom/ │ │ │ ├── components/ │ │ │ │ └── default.js │ │ │ ├── index.js │ │ │ └── index.styl │ │ ├── reportWebVitals.js │ │ ├── router.js │ │ └── setupTests.js │ ├── scroll-bar/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __mocks__/ │ │ │ ├── event-handler.ts │ │ │ └── indicator.ts │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ └── index.spec.ts.snap │ │ │ ├── event-handler.spec.ts │ │ │ ├── index.spec.ts │ │ │ └── indicator.spec.ts │ │ ├── event-handler.ts │ │ ├── index.ts │ │ └── indicator.ts │ ├── shared-utils/ │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ ├── Touch.ts │ │ ├── __mocks__/ │ │ │ ├── dom.ts │ │ │ └── ease.ts │ │ ├── __tests__/ │ │ │ ├── debug.spec.ts │ │ │ ├── dom.spec.ts │ │ │ ├── ease.spec.ts │ │ │ ├── events.spec.ts │ │ │ ├── lang.spec.ts │ │ │ ├── propertiesProxy.spec.ts │ │ │ └── raf.spec.ts │ │ ├── debug.ts │ │ ├── dom.ts │ │ ├── ease.ts │ │ ├── enums.ts │ │ ├── env.ts │ │ ├── events.ts │ │ ├── index.ts │ │ ├── lang.ts │ │ ├── propertiesProxy.ts │ │ ├── raf.ts │ │ └── types.ts │ ├── slide/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── PagesMatrix.ts │ │ ├── SlidePages.ts │ │ ├── __mocks__/ │ │ │ ├── PagesMatrix.ts │ │ │ └── SlidePages.ts │ │ ├── __tests__/ │ │ │ ├── PagesMatrix.spec.ts │ │ │ ├── SlidePages.spec.ts │ │ │ └── index.spec.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ ├── vuepress-docs/ │ │ ├── docs/ │ │ │ ├── .vuepress/ │ │ │ │ ├── components/ │ │ │ │ │ ├── demo.vue │ │ │ │ │ └── qrcode.vue │ │ │ │ ├── config.js │ │ │ │ ├── enhanceApp.js │ │ │ │ ├── nav/ │ │ │ │ │ ├── en-US.js │ │ │ │ │ └── zh-CN.js │ │ │ │ ├── plugins/ │ │ │ │ │ └── extract-code.js │ │ │ │ ├── public/ │ │ │ │ │ └── assets/ │ │ │ │ │ └── stylus/ │ │ │ │ │ └── index.styl │ │ │ │ └── sidebar/ │ │ │ │ ├── FAQ.js │ │ │ │ ├── guide.js │ │ │ │ └── plugins.js │ │ │ ├── en-US/ │ │ │ │ ├── FAQ/ │ │ │ │ │ ├── README.md │ │ │ │ │ └── diagnosis.md │ │ │ │ ├── README.md │ │ │ │ ├── guide/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── base-scroll-api.md │ │ │ │ │ ├── base-scroll-options.md │ │ │ │ │ ├── base-scroll.md │ │ │ │ │ ├── how-to-install.md │ │ │ │ │ └── use.md │ │ │ │ └── plugins/ │ │ │ │ ├── README.md │ │ │ │ ├── how-to-write.md │ │ │ │ ├── indicators.md │ │ │ │ ├── infinity.md │ │ │ │ ├── mouse-wheel.md │ │ │ │ ├── movable.md │ │ │ │ ├── nested-scroll.md │ │ │ │ ├── observe-dom.md │ │ │ │ ├── observe-image.md │ │ │ │ ├── pulldown.md │ │ │ │ ├── pullup.md │ │ │ │ ├── scroll-bar.md │ │ │ │ ├── slide.md │ │ │ │ ├── wheel.md │ │ │ │ └── zoom.md │ │ │ └── zh-CN/ │ │ │ ├── FAQ/ │ │ │ │ ├── README.md │ │ │ │ └── diagnosis.md │ │ │ ├── README.md │ │ │ ├── guide/ │ │ │ │ ├── README.md │ │ │ │ ├── base-scroll-api.md │ │ │ │ ├── base-scroll-options.md │ │ │ │ ├── base-scroll.md │ │ │ │ ├── how-to-install.md │ │ │ │ └── use.md │ │ │ └── plugins/ │ │ │ ├── README.md │ │ │ ├── compose-plugins.md │ │ │ ├── how-to-write.md │ │ │ ├── indicators.md │ │ │ ├── infinity.md │ │ │ ├── mouse-wheel.md │ │ │ ├── movable.md │ │ │ ├── nested-scroll.md │ │ │ ├── observe-dom.md │ │ │ ├── observe-image.md │ │ │ ├── pulldown.md │ │ │ ├── pullup.md │ │ │ ├── scroll-bar.md │ │ │ ├── slide.md │ │ │ ├── wheel.md │ │ │ └── zoom.md │ │ ├── docs-release.sh │ │ └── package.json │ ├── wheel/ │ │ ├── README.md │ │ ├── README_zh-CN.md │ │ ├── package.json │ │ └── src/ │ │ ├── __tests__/ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── propertiesConfig.ts │ └── zoom/ │ ├── README.md │ ├── README_zh-CN.md │ ├── package.json │ └── src/ │ ├── __tests__/ │ │ ├── __utils__/ │ │ │ └── util.ts │ │ └── index.spec.ts │ ├── index.ts │ └── propertiesConfig.ts ├── postcss.config.js ├── scripts/ │ ├── build.js │ ├── checkYarn.js │ └── release.js ├── test-dts/ │ ├── core.test-d.ts │ ├── index.d.ts │ ├── plugin.test-d.ts │ ├── tsconfig.json │ └── util.d.ts ├── tests/ │ ├── dts/ │ │ └── index.d.ts │ ├── e2e/ │ │ ├── compose-plugins/ │ │ │ ├── compose-plugins.e2e.ts │ │ │ ├── pullup-pulldown-nested.e2e.ts │ │ │ ├── pullup-pulldown-slide.e2e.ts │ │ │ ├── pullup-pulldown.e2e.ts │ │ │ └── slide-nested.e2e.ts │ │ ├── core/ │ │ │ └── corescroll.e2e.ts │ │ ├── form/ │ │ │ └── textarea.e2e.ts │ │ ├── homepage.e2e.ts │ │ ├── indicators/ │ │ │ ├── minimap.e2e.ts │ │ │ └── parallax-scrolling.e2e.ts │ │ ├── infinity/ │ │ │ └── infinity.e2e.ts │ │ ├── mousewheel/ │ │ │ └── mousewheel.e2e.ts │ │ ├── movable/ │ │ │ ├── default.e2e.ts │ │ │ ├── multi-content-scale.e2e.ts │ │ │ ├── multi-content.e2e.ts │ │ │ └── scaled.e2e.ts │ │ ├── nested-scroll/ │ │ │ ├── horizontal-in-vertical.e2e.ts │ │ │ ├── horizontal.e2e.ts │ │ │ ├── triple-vertical.e2e.ts │ │ │ └── vertical.e2e.ts │ │ ├── observe-dom/ │ │ │ └── observe-dom.e2e.ts │ │ ├── observe-image/ │ │ │ └── observe-image.e2e.ts │ │ ├── picker/ │ │ │ ├── double-column.e2e.ts │ │ │ ├── linkage-column.e2e.ts │ │ │ └── one-column.e2e.ts │ │ ├── pulldown/ │ │ │ ├── default.e2e.ts │ │ │ └── sina.e2e.ts │ │ ├── pullup/ │ │ │ └── default.e2e.ts │ │ ├── scrollbar/ │ │ │ ├── custom.e2e.ts │ │ │ ├── horizontal.e2e.ts │ │ │ ├── mousewheel.e2e.ts │ │ │ └── vertical.e2e.ts │ │ ├── slide/ │ │ │ ├── banner.e2e.ts │ │ │ ├── dynamic.e2e.ts │ │ │ ├── fullpage.e2e.ts │ │ │ ├── specifiedIndex.e2e.ts │ │ │ └── vertical.e2e.ts │ │ └── zoom/ │ │ └── zoom.e2e.ts │ └── util/ │ ├── extendMouseWheel.ts │ ├── extendTouch.ts │ ├── getScale.ts │ └── getTranslate.ts ├── tsconfig.json └── tslint.json
SYMBOL INDEX (816 symbols across 94 files)
FILE: packages/core/src/BScroll.ts
type PluginCtor (line 15) | interface PluginCtor {
type PluginItem (line 21) | interface PluginItem {
type PluginsMap (line 26) | interface PluginsMap {
type PropertyConfig (line 29) | interface PropertyConfig {
type ElementParam (line 34) | type ElementParam = HTMLElement | string
type MountedBScrollHTMLElement (line 36) | interface MountedBScrollHTMLElement extends HTMLElement {
class BScrollConstructor (line 40) | class BScrollConstructor<O = {}> extends EventEmitter {
method use (line 51) | static use(ctor: PluginCtor) {
method constructor (line 72) | constructor(el: ElementParam, options?: Options & O) {
method setContent (line 113) | setContent(wrapper: MountedBScrollHTMLElement) {
method init (line 136) | private init(wrapper: MountedBScrollHTMLElement) {
method applyPlugins (line 169) | private applyPlugins() {
method handleAutoBlur (line 189) | private handleAutoBlur() {
method eventBubbling (line 205) | private eventBubbling() {
method refreshWithoutReset (line 217) | private refreshWithoutReset(content: HTMLElement) {
method proxy (line 223) | proxy(propertiesConfig: PropertyConfig[]) {
method refresh (line 228) | refresh() {
method enable (line 241) | enable() {
method disable (line 247) | disable() {
method destroy (line 253) | destroy() {
method eventRegister (line 258) | eventRegister(names: string[]) {
type BScrollConstructor (line 263) | interface BScrollConstructor extends BScrollInstance {}
method use (line 51) | static use(ctor: PluginCtor) {
method constructor (line 72) | constructor(el: ElementParam, options?: Options & O) {
method setContent (line 113) | setContent(wrapper: MountedBScrollHTMLElement) {
method init (line 136) | private init(wrapper: MountedBScrollHTMLElement) {
method applyPlugins (line 169) | private applyPlugins() {
method handleAutoBlur (line 189) | private handleAutoBlur() {
method eventBubbling (line 205) | private eventBubbling() {
method refreshWithoutReset (line 217) | private refreshWithoutReset(content: HTMLElement) {
method proxy (line 223) | proxy(propertiesConfig: PropertyConfig[]) {
method refresh (line 228) | refresh() {
method enable (line 241) | enable() {
method disable (line 247) | disable() {
method destroy (line 253) | destroy() {
method eventRegister (line 258) | eventRegister(names: string[]) {
type CustomAPI (line 265) | interface CustomAPI {
type ExtractAPI (line 269) | type ExtractAPI<O> = {
function createBScroll (line 277) | function createBScroll<O = {}>(
type createBScroll (line 290) | type createBScroll = typeof createBScroll
type BScrollFactory (line 291) | interface BScrollFactory extends createBScroll {
type BScroll (line 296) | type BScroll<O = Options> = BScrollConstructor<O> &
FILE: packages/core/src/Instance.ts
type BScrollInstance (line 7) | interface BScrollInstance
FILE: packages/core/src/Options.ts
type Tap (line 12) | type Tap = 'tap' | ''
type BounceOptions (line 13) | type BounceOptions = Partial<BounceConfig> | boolean
type DblclickOptions (line 14) | type DblclickOptions = Partial<DblclickConfig> | boolean
type BounceConfig (line 17) | interface BounceConfig {
type DblclickConfig (line 24) | interface DblclickConfig {
type CustomOptions (line 28) | interface CustomOptions {}
type DefOptions (line 30) | interface DefOptions {
type Options (line 78) | interface Options extends DefOptions, CustomOptions {}
class CustomOptions (line 79) | class CustomOptions {}
class OptionsConstructor (line 80) | class OptionsConstructor extends CustomOptions implements DefOptions {
method constructor (line 127) | constructor() {
method merge (line 186) | merge(options?: Options) {
method process (line 197) | process() {
method resolveBounce (line 227) | resolveBounce(bounceOptions: BounceOptions): BounceConfig {
FILE: packages/core/src/__tests__/__utils__/event.ts
function createEvent (line 1) | function createEvent(type: string, name: string): Event {
type CustomClickEvent (line 7) | interface CustomClickEvent extends MouseEvent {
function dispatchClick (line 12) | function dispatchClick(target: EventTarget, name = 'click') {
type CustomTouch (line 19) | interface CustomTouch {
type CustomTouches (line 23) | type CustomTouches = CustomTouch[] | CustomTouch
type CustomTouchEvent (line 25) | interface CustomTouchEvent extends Event {
type CustomMouseEvent (line 31) | interface CustomMouseEvent extends Event {
function dispatchTouch (line 37) | function dispatchTouch(
function dispatchMouse (line 47) | function dispatchMouse(
function dispatchTouchStart (line 59) | function dispatchTouchStart(
function dispatchTouchMove (line 66) | function dispatchTouchMove(
function dispatchTouchEnd (line 73) | function dispatchTouchEnd(
function dispatchTouchCancel (line 80) | function dispatchTouchCancel(
function dispatchSwipe (line 87) | function dispatchSwipe(
FILE: packages/core/src/__tests__/__utils__/layout.ts
type CustomHTMLDivElement (line 1) | interface CustomHTMLDivElement extends HTMLDivElement {
function firstUpper (line 17) | function firstUpper(key: string) {
function genMockPrototype (line 21) | function genMockPrototype(mockName: string) {
function mockHTMLPrototype (line 29) | function mockHTMLPrototype(propName: string, mockGetter: jest.Mock) {
function mockDomOffset (line 38) | function mockDomOffset(
function mockDomClient (line 54) | function mockDomClient(
function createDiv (line 68) | function createDiv(
FILE: packages/core/src/__tests__/index.spec.ts
method constructor (line 38) | constructor(bscroll: BScroll) {
class DummyPlugin (line 93) | class DummyPlugin {
method constructor (line 95) | constructor(scroll: BScroll) {
FILE: packages/core/src/animater/Animation.ts
class Animation (line 11) | class Animation extends Base {
method move (line 12) | move(
method animate (line 30) | private animate(
method doStop (line 92) | doStop(): boolean {
method stop (line 109) | stop() {
FILE: packages/core/src/animater/Base.ts
type ExposedAPI (line 10) | interface ExposedAPI {
method constructor (line 25) | constructor(
method translate (line 44) | translate(endPoint: TranslaterPoint) {
method setPending (line 48) | setPending(pending: boolean) {
method setForceStopped (line 52) | setForceStopped(forceStopped: boolean) {
method setCallStop (line 56) | setCallStop(called: boolean) {
method setContent (line 60) | setContent(content: HTMLElement) {
method clearTimer (line 67) | clearTimer() {
method destroy (line 84) | destroy() {
FILE: packages/core/src/animater/Transition.ts
class Transition (line 12) | class Transition extends Base {
method startProbe (line 13) | startProbe(startPoint: TranslaterPoint, endPoint: TranslaterPoint) {
method transitionTime (line 48) | transitionTime(time = 0) {
method transitionTimingFunction (line 53) | transitionTimingFunction(easing: string) {
method transitionProperty (line 58) | transitionProperty() {
method move (line 62) | move(
method doStop (line 93) | doStop(): boolean {
method stop (line 112) | stop() {
FILE: packages/core/src/animater/__tests__/Animation.spec.ts
function createAnimation (line 22) | function createAnimation(probeType: number) {
FILE: packages/core/src/animater/__tests__/Transition.spec.ts
function createTransition (line 15) | function createTransition(probeType: number) {
FILE: packages/core/src/animater/index.ts
function createAnimater (line 10) | function createAnimater(
FILE: packages/core/src/base/ActionsHandler.ts
type Exception (line 13) | type Exception = {
type Options (line 18) | interface Options {
class ActionsHandler (line 31) | class ActionsHandler {
method constructor (line 38) | constructor(public wrapper: HTMLElement, public options: Options) {
method handleDOMEvents (line 49) | private handleDOMEvents() {
method beforeHandler (line 109) | private beforeHandler(e: TouchEvent, type: 'start' | 'move' | 'end') {
method setInitiated (line 142) | setInitiated(type: number = 0) {
method start (line 146) | private start(e: TouchEvent) {
method move (line 177) | private move(e: TouchEvent) {
method end (line 223) | private end(e: TouchEvent) {
method click (line 234) | private click(e: TouchEvent) {
method setContent (line 238) | setContent(content: HTMLElement) {
method rebindDOMEvents (line 245) | rebindDOMEvents() {
method destroy (line 251) | destroy() {
FILE: packages/core/src/scroller/Actions.ts
class ScrollerActions (line 33) | class ScrollerActions {
method constructor (line 47) | constructor(
method bindActionsHandler (line 84) | private bindActionsHandler() {
method handleStart (line 147) | private handleStart(e: TouchEvent) {
method handleMove (line 168) | private handleMove(deltaX: number, deltaY: number, e: TouchEvent) {
method dispatchScroll (line 223) | private dispatchScroll(timestamp: number) {
method checkMomentum (line 241) | private checkMomentum(absDistX: number, absDistY: number, timestamp: n...
method handleEnd (line 249) | private handleEnd(e: TouchEvent) {
method ensureIntegerPos (line 273) | private ensureIntegerPos(currentPos: TranslaterPoint) {
method handleClick (line 289) | private handleClick(e: TouchEvent) {
method getCurrentPos (line 298) | getCurrentPos(): TranslaterPoint {
method refresh (line 305) | refresh() {
method destroy (line 309) | destroy() {
FILE: packages/core/src/scroller/Behavior.ts
type Bounces (line 3) | type Bounces = [boolean, boolean]
type Rect (line 5) | type Rect = { size: string; position: string }
type Options (line 7) | interface Options {
type Boundary (line 22) | type Boundary = { minScrollPos: number; maxScrollPos: number }
class Behavior (line 24) | class Behavior {
method constructor (line 39) | constructor(
method start (line 54) | start() {
method move (line 60) | move(delta: number) {
method setMovingDirection (line 69) | setMovingDirection(delta: number) {
method setDirection (line 78) | setDirection(delta: number) {
method performDampingAlgorithm (line 87) | performDampingAlgorithm(delta: number, dampingFactor: number): number {
method end (line 104) | end(duration: number) {
method momentum (line 142) | private momentum(
method updateDirection (line 187) | updateDirection() {
method refresh (line 192) | refresh(content: HTMLElement) {
method setContent (line 216) | private setContent(content: HTMLElement) {
method resetState (line 223) | private resetState() {
method computeBoundary (line 232) | computeBoundary() {
method updatePosition (line 259) | updatePosition(pos: number) {
method getCurrentPos (line 263) | getCurrentPos() {
method checkInBoundary (line 267) | checkInBoundary() {
method adjustPosition (line 277) | adjustPosition(pos: number) {
method updateStartPos (line 292) | updateStartPos() {
method updateAbsStartPos (line 296) | updateAbsStartPos() {
method resetStartPos (line 300) | resetStartPos() {
method getAbsDist (line 305) | getAbsDist(delta: number) {
method destroy (line 310) | destroy() {
FILE: packages/core/src/scroller/DirectionLock.ts
type Passthrough (line 8) | const enum Passthrough {
type DirectionMap (line 13) | interface DirectionMap {
class DirectionLockAction (line 39) | class DirectionLockAction {
method constructor (line 41) | constructor(
method reset (line 49) | reset() {
method checkMovingDirection (line 53) | checkMovingDirection(absDistX: number, absDistY: number, e: TouchEvent) {
method adjustDelta (line 59) | adjustDelta(deltaX: number, deltaY: number) {
method computeDirectionLock (line 71) | private computeDirectionLock(absDistX: number, absDistY: number) {
method handleEventPassthrough (line 84) | private handleEventPassthrough(e: TouchEvent) {
FILE: packages/core/src/scroller/Scroller.ts
constant MIN_SCROLL_DISTANCE (line 35) | const MIN_SCROLL_DISTANCE = 1
type ExposedAPI (line 36) | interface ExposedAPI {
class Scroller (line 60) | class Scroller implements ExposedAPI {
method constructor (line 79) | constructor(
method init (line 159) | private init() {
method registerTransitionEnd (line 169) | private registerTransitionEnd() {
method bindTranslater (line 178) | private bindTranslater() {
method bindAnimater (line 202) | private bindAnimater() {
method bindActions (line 226) | private bindActions() {
method checkFlick (line 307) | private checkFlick(duration: number, deltaX: number, deltaY: number) {
method momentum (line 320) | private momentum(pos: TranslaterPoint, duration: number) {
method checkClick (line 359) | private checkClick(e: TouchEvent) {
method resize (line 396) | private resize() {
method transitionEnd (line 413) | private transitionEnd(e: TouchEvent) {
method togglePointerEvents (line 431) | togglePointerEvents(enabled = true) {
method refresh (line 447) | refresh(content: HTMLElement) {
method setContent (line 469) | private setContent(content: HTMLElement): boolean {
method scrollBy (line 477) | scrollBy(deltaX: number, deltaY: number, time = 0, easing?: EaseItem) {
method scrollTo (line 486) | scrollTo(
method scrollToElement (line 527) | scrollToElement(
method resetPosition (line 592) | resetPosition(time = 0, easing = ease.bounce) {
method reflow (line 619) | reflow() {
method updatePositions (line 623) | updatePositions(pos: TranslaterPoint) {
method getCurrentPos (line 628) | getCurrentPos() {
method enable (line 632) | enable() {
method disable (line 636) | disable() {
method destroy (line 641) | destroy(this: Scroller) {
FILE: packages/core/src/scroller/__tests__/Actions.spec.ts
method get (line 24) | get() {
method preventDefault (line 245) | preventDefault() {}
method stopPropagation (line 246) | stopPropagation() {}
FILE: packages/core/src/scroller/__tests__/DirectionLock.spec.ts
method preventDefault (line 63) | preventDefault() {
method preventDefault (line 72) | preventDefault() {
method preventDefault (line 81) | preventDefault() {
FILE: packages/core/src/scroller/__tests__/Scroller.spec.ts
method get (line 28) | get() {
method get (line 140) | get() {
FILE: packages/core/src/scroller/createOptions.ts
function createActionsHandlerOptions (line 5) | function createActionsHandlerOptions(bsOptions: BScrollOptions) {
function createBehaviorOptions (line 23) | function createBehaviorOptions(
FILE: packages/core/src/translater/index.ts
type TranslaterPoint (line 6) | interface TranslaterPoint {
type TranslaterMetaData (line 12) | interface TranslaterMetaData {
class Translater (line 22) | class Translater {
method constructor (line 26) | constructor(content: HTMLElement) {
method getComputedPosition (line 31) | getComputedPosition() {
method translate (line 46) | translate(point: TranslaterPoint) {
method setContent (line 70) | setContent(content: HTMLElement) {
method destroy (line 77) | destroy() {
FILE: packages/core/src/utils/bubbling.ts
type BubblingEventMap (line 3) | interface BubblingEventMap {
type BubblingEventConfig (line 7) | type BubblingEventConfig = BubblingEventMap | string
function bubbling (line 8) | function bubbling(
FILE: packages/core/src/utils/compare.ts
function isSamePoint (line 3) | function isSamePoint(
FILE: packages/core/src/utils/compat.ts
type Position (line 4) | type Position = {
FILE: packages/core/src/utils/typesHelper.ts
type UnionToIntersection (line 1) | type UnionToIntersection<U> = (
FILE: packages/examples/build/vue-webpack.conf.js
function resolve (line 17) | function resolve(dir) {
function getPackagesName (line 192) | function getPackagesName() {
FILE: packages/indicators/src/index.ts
type CustomOptions (line 7) | interface CustomOptions {
class Indicators (line 12) | class Indicators {
method constructor (line 16) | constructor(public scroll: BScroll) {
method handleOptions (line 21) | private handleOptions() {
method createIndicators (line 36) | private createIndicators(options: IndicatorOptions) {
method handleHooks (line 40) | private handleHooks() {
FILE: packages/indicators/src/indicator.ts
class Indicator (line 37) | class Indicator {
method constructor (line 61) | constructor(public scroll: BScroll, public options: IndicatorOptions) {
method handleDOM (line 67) | private handleDOM() {
method handleHooks (line 76) | private handleHooks() {
method transitionTime (line 109) | private transitionTime(time: number = 0) {
method transitionTimingFunction (line 113) | private transitionTimingFunction(easing: string) {
method handleInteractive (line 117) | private handleInteractive() {
method registerHooks (line 123) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method registerEvents (line 128) | private registerEvents() {
method refresh (line 179) | refresh() {
method start (line 218) | private start(e: TouchEvent) {
method BScrollIsDisabled (line 236) | private BScrollIsDisabled() {
method move (line 240) | private move(e: TouchEvent) {
method end (line 267) | private end(e: TouchEvent) {
method getBScrollPosByRatio (line 287) | private getBScrollPosByRatio(
method indicatorNotMoved (line 327) | private indicatorNotMoved(deltaX: number, deltaY: number): boolean {
method syncBScroll (line 338) | private syncBScroll(newPos: Postion) {
method updatePosition (line 358) | updatePosition(BScrollPos: Postion) {
method applyTransformProperty (line 364) | private applyTransformProperty(pos: Postion) {
method getIndicatorPosByRatio (line 375) | private getIndicatorPosByRatio(BScrollPos: Postion) {
method destroy (line 402) | destroy() {
FILE: packages/indicators/src/types.ts
type Ratio (line 1) | type Ratio = number | RatioOfDirection
type RatioOfDirection (line 2) | type RatioOfDirection = {
type IndicatorOptions (line 6) | interface IndicatorOptions {
type Direction (line 13) | const enum Direction {
type Postion (line 18) | type Postion = {
type ValueSign (line 23) | const enum ValueSign {
FILE: packages/infinity/src/DataManager.ts
class ListItem (line 1) | class ListItem {
method constructor (line 9) | constructor() {
type pListItem (line 19) | type pListItem = Partial<ListItem>
class DataManager (line 21) | class DataManager {
method constructor (line 26) | constructor(
method update (line 34) | async update(end: number): Promise<void> {
method add (line 49) | add(data: Array<any>): Array<pListItem> {
method addEmptyData (line 64) | addEmptyData(len: number): Array<pListItem> {
method fetch (line 71) | async fetch(len: number): Promise<Array<any> | boolean> {
method checkToFetch (line 81) | async checkToFetch(end: number): Promise<void> {
method getList (line 106) | getList() {
method resetState (line 110) | resetState() {
FILE: packages/infinity/src/DomManager.ts
constant ANIMATION_DURATION_MS (line 5) | const ANIMATION_DURATION_MS = 200
class DomManager (line 7) | class DomManager {
method constructor (line 12) | constructor(
method update (line 20) | update(
method collectUnusedDom (line 54) | private collectUnusedDom(
method createDom (line 81) | private createDom(list: Array<pListItem>, start: number, end: number):...
method cacheHeight (line 103) | private cacheHeight(
method positionDom (line 115) | private positionDom(
method getStartPos (line 161) | private getStartPos(
method removeTombstone (line 201) | removeTombstone(): void {
method setContent (line 208) | setContent(content: HTMLElement) {
method destroy (line 214) | destroy(): void {
method resetState (line 220) | resetState() {
FILE: packages/infinity/src/IndexCalculator.ts
constant PRE_NUM (line 1) | const PRE_NUM = 10
constant POST_NUM (line 2) | const POST_NUM = 30
type DIRECTION (line 4) | const enum DIRECTION {
class IndexCalculator (line 9) | class IndexCalculator {
method constructor (line 13) | constructor(public wrapperHeight: number, private tombstoneHeight: num...
method calculate (line 15) | calculate(pos: number, list: Array<any>): { start: number; end: number...
method getDirection (line 44) | private getDirection(offset: number): DIRECTION {
method calculateIndex (line 57) | private calculateIndex(
method resetState (line 84) | resetState() {
FILE: packages/infinity/src/Tombstone.ts
class Tombstone (line 3) | class Tombstone {
method constructor (line 10) | constructor(private create: () => HTMLElement) {
method isTombstone (line 14) | static isTombstone(el: HTMLElement): boolean {
method getSize (line 21) | private getSize(): void {
method getOne (line 37) | getOne(): HTMLElement {
method recycle (line 51) | recycle(tombstones: Array<HTMLElement>): Array<HTMLElement> {
method recycleOne (line 59) | recycleOne(tombstone: HTMLElement) {
FILE: packages/infinity/src/__tests__/__utils__/FakeList.ts
class FakeList (line 4) | class FakeList {
method constructor (line 6) | constructor(size: number) {
method fill (line 12) | fill(val: any, start: number = 0, end?: number): this {
method fillPos (line 24) | fillPos(end?: number): this {
method fillDom (line 40) | fillDom(start: number, end?: number, height = TOMBSTONE_HEIGHT): this {
method syncDomTo (line 54) | syncDomTo(content: HTMLElement): this {
method getList (line 63) | getList(): any[] {
FILE: packages/infinity/src/__tests__/__utils__/constans.ts
constant TOMBSTONE_HEIGHT (line 1) | const TOMBSTONE_HEIGHT = 37 // 元素默认的高度
constant WRAPPER_HEIGHT (line 3) | const WRAPPER_HEIGHT = 370 // 滚动窗口的高度
constant VISIBLE_CNT (line 5) | const VISIBLE_CNT = 10 // 滚动窗口内能显示的元素
FILE: packages/infinity/src/index.ts
type InfinityOptions (line 8) | interface InfinityOptions {
type CustomOptions (line 15) | interface CustomOptions {
constant EXTRA_SCROLL_Y (line 20) | const EXTRA_SCROLL_Y = -2000
class InfinityScroll (line 22) | class InfinityScroll {
method constructor (line 32) | constructor(public scroll: BScroll) {
method init (line 36) | init() {
method modifyBoundary (line 85) | private modifyBoundary(boundary: Boundary) {
method handleOptions (line 90) | private handleOptions() {
method update (line 111) | update(pos: { y: number }): void {
method onFetchFinish (line 125) | private onFetchFinish(list: Array<any>, hasMore: boolean) {
method updateDom (line 136) | private updateDom(
method destroy (line 162) | destroy() {
FILE: packages/mouse-wheel/src/__tests__/index.spec.ts
type CustomMouseWheel (line 7) | interface CustomMouseWheel extends Event {
function dispatchMouseWheel (line 22) | function dispatchMouseWheel(
FILE: packages/mouse-wheel/src/index.ts
type MouseWheelOptions (line 13) | type MouseWheelOptions = Partial<MouseWheelConfig> | true
type MouseWheelConfig (line 15) | interface MouseWheelConfig {
type CustomOptions (line 25) | interface CustomOptions {
type CompatibleWheelEvent (line 30) | interface CompatibleWheelEvent extends WheelEvent {
type WheelDelta (line 38) | interface WheelDelta {
class MouseWheel (line 45) | class MouseWheel {
method constructor (line 55) | constructor(public scroll: BScroll) {
method init (line 59) | private init() {
method handleBScroll (line 67) | private handleBScroll() {
method handleOptions (line 76) | private handleOptions() {
method handleHooks (line 93) | private handleHooks() {
method registerEvent (line 98) | private registerEvent() {
method registerHooks (line 115) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method wheelHandler (line 120) | private wheelHandler(e: CompatibleWheelEvent) {
method wheelStartHandler (line 140) | private wheelStartHandler(e: CompatibleWheelEvent) {
method cleanCache (line 153) | private cleanCache() {
method wheelMoveHandler (line 157) | private wheelMoveHandler(delta: {
method wheelEndDetector (line 215) | private wheelEndDetector(delta: WheelDelta) {
method getWheelDelta (line 226) | private getWheelDelta(e: CompatibleWheelEvent): WheelDelta {
method beforeHandler (line 285) | private beforeHandler(e: CompatibleWheelEvent) {
method getEaseTime (line 299) | private getEaseTime() {
method destroy (line 314) | destroy() {
FILE: packages/movable/src/index.ts
type PositionX (line 10) | type PositionX = number | 'left' | 'right' | 'center'
type PositionY (line 11) | type PositionY = number | 'top' | 'bottom' | 'center'
type CustomOptions (line 14) | interface CustomOptions {
type CustomAPI (line 17) | interface CustomAPI {
type PluginAPI (line 22) | interface PluginAPI {
class Movable (line 26) | class Movable implements PluginAPI {
method constructor (line 30) | constructor(public scroll: BScroll) {
method handleBScroll (line 35) | private handleBScroll() {
method handleHooks (line 39) | private handleHooks() {
method putAt (line 86) | putAt(
method resolvePostion (line 96) | private resolvePostion(x: PositionX, y: PositionY): { x: number; y: nu...
method destroy (line 125) | destroy() {
method registerHooks (line 134) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
FILE: packages/nested-scroll/src/BScrollFamily.ts
type BScrollFamilyTuple (line 5) | type BScrollFamilyTuple = [BScrollFamily, number]
class BScrollFamily (line 7) | class BScrollFamily {
method create (line 8) | static create(scroll: BScroll) {
method constructor (line 16) | constructor(scroll: BScroll) {
method hasAncestors (line 20) | hasAncestors(bscrollFamily: BScrollFamily) {
method hasDescendants (line 27) | hasDescendants(bscrollFamily: BScrollFamily) {
method addAncestor (line 34) | addAncestor(bscrollFamily: BScrollFamily, distance: number) {
method addDescendant (line 43) | addDescendant(bscrollFamily: BScrollFamily, distance: number) {
method removeAncestor (line 52) | removeAncestor(bscrollFamily: BScrollFamily) {
method removeDescendant (line 64) | removeDescendant(bscrollFamily: BScrollFamily) {
method registerHooks (line 76) | registerHooks(hook: EventEmitter, eventType: string, handler: Function) {
method setAnalyzed (line 81) | setAnalyzed(flag = false) {
method purge (line 85) | purge() {
FILE: packages/nested-scroll/src/index.ts
constant DEFAUL_GROUP_ID (line 12) | const DEFAUL_GROUP_ID = 'INTERNAL_NESTED_SCROLL'
type NestedScrollGroupId (line 14) | type NestedScrollGroupId = string | number
type NestedScrollConfig (line 16) | interface NestedScrollConfig {
type NestedScrollOptions (line 20) | type NestedScrollOptions = NestedScrollConfig | true
type CustomOptions (line 23) | interface CustomOptions {
type CustomAPI (line 26) | interface CustomAPI {
type PluginAPI (line 31) | interface PluginAPI {
type NestedScrollInstancesMap (line 35) | interface NestedScrollInstancesMap {
class NestedScroll (line 164) | class NestedScroll implements PluginAPI {
method constructor (line 170) | constructor(scroll: BScroll) {
method getAllNestedScrolls (line 184) | static getAllNestedScrolls(): NestedScroll[] {
method purgeAllNestedScrolls (line 189) | static purgeAllNestedScrolls() {
method handleOptions (line 194) | private handleOptions(scroll: BScroll): number | string {
method init (line 211) | private init(scroll: BScroll) {
method handleHooks (line 220) | private handleHooks(scroll: BScroll) {
method deleteScroll (line 226) | deleteScroll(scroll: BScroll) {
method addBScroll (line 249) | addBScroll(scroll: BScroll) {
method buildBScrollGraph (line 253) | private buildBScrollGraph() {
method analyzeBScrollGraph (line 288) | private analyzeBScrollGraph() {
method ensureEventInvokeSequence (line 368) | private ensureEventInvokeSequence() {
method registerHooks (line 379) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method purgeNestedScroll (line 384) | purgeNestedScroll() {
FILE: packages/observe-dom/src/index.ts
type CustomOptions (line 5) | interface CustomOptions {
class ObserveDOM (line 9) | class ObserveDOM {
method constructor (line 14) | constructor(public scroll: BScroll) {
method init (line 17) | init() {
method handleMutationObserver (line 21) | private handleMutationObserver() {
method handleHooks (line 32) | private handleHooks() {
method mutationObserverHandler (line 67) | private mutationObserverHandler(mutations: MutationRecord[], timer: nu...
method startObserve (line 99) | private startObserve(observer: MutationObserver) {
method shouldNotRefresh (line 108) | private shouldNotRefresh() {
method checkDOMUpdate (line 119) | private checkDOMUpdate() {
method registerHooks (line 151) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method stopObserve (line 156) | private stopObserve() {
method destroy (line 163) | destroy() {
FILE: packages/observe-image/src/index.ts
type ObserveImageOptions (line 4) | type ObserveImageOptions = Partial<ObserveImageConfig> | true
type ObserveImageConfig (line 6) | interface ObserveImageConfig {
type CustomOptions (line 11) | interface CustomOptions {
class ObserveImage (line 20) | class ObserveImage {
method constructor (line 26) | constructor(public scroll: BScroll) {
method init (line 29) | init() {
method handleOptions (line 34) | private handleOptions(userOptions: ObserveImageOptions = {}) {
method bindEventsToWrapper (line 44) | private bindEventsToWrapper() {
method load (line 62) | private load(e: Event) {
FILE: packages/pull-down/src/index.ts
type PullDownRefreshOptions (line 6) | type PullDownRefreshOptions = Partial<PullDownRefreshConfig> | true
type PullDownPhase (line 12) | const enum PullDownPhase {
type ThresholdBoundary (line 18) | const enum ThresholdBoundary {
type PullDownRefreshConfig (line 24) | interface PullDownRefreshConfig {
type CustomOptions (line 30) | interface CustomOptions {
type CustomAPI (line 33) | interface CustomAPI {
type PluginAPI (line 38) | interface PluginAPI {
constant PULLING_DOWN_EVENT (line 45) | const PULLING_DOWN_EVENT = 'pullingDown'
constant ENTER_THRESHOLD_EVENT (line 46) | const ENTER_THRESHOLD_EVENT = 'enterThreshold'
constant LEAVE_THRESHOLD_EVENT (line 47) | const LEAVE_THRESHOLD_EVENT = 'leaveThreshold'
class PullDown (line 49) | class PullDown implements PluginAPI {
method constructor (line 59) | constructor(public scroll: BScroll) {
method setPulling (line 63) | private setPulling(status: PullDownPhase) {
method setThresholdBoundary (line 67) | private setThresholdBoundary(boundary: ThresholdBoundary) {
method init (line 71) | private init() {
method handleBScroll (line 81) | private handleBScroll() {
method handleOptions (line 91) | private handleOptions(userOptions: PullDownRefreshOptions = {}) {
method handleHooks (line 104) | private handleHooks() {
method registerHooks (line 157) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method hasMouseWheelPlugin (line 162) | private hasMouseWheelPlugin() {
method watch (line 166) | private watch() {
method resetStateBeforeScrollStart (line 196) | private resetStateBeforeScrollStart() {
method checkLocationOfThresholdBoundary (line 204) | private checkLocationOfThresholdBoundary() {
method locateInsideThresholdBoundary (line 227) | private locateInsideThresholdBoundary() {
method unwatch (line 231) | private unwatch() {
method checkPullDown (line 246) | private checkPullDown() {
method isFetchingStatus (line 271) | private isFetchingStatus() {
method modifyBehaviorYBoundary (line 275) | private modifyBehaviorYBoundary(stopDistance: number) {
method finishPullDown (line 284) | finishPullDown() {
method openPullDown (line 296) | openPullDown(config: PullDownRefreshOptions = {}) {
method closePullDown (line 303) | closePullDown() {
method autoPullDownRefresh (line 307) | autoPullDownRefresh() {
FILE: packages/pull-up/src/index.ts
type PullUpLoadOptions (line 10) | type PullUpLoadOptions = Partial<PullUpLoadConfig> | true
type PullUpLoadConfig (line 11) | interface PullUpLoadConfig {
type CustomOptions (line 16) | interface CustomOptions {
type CustomAPI (line 19) | interface CustomAPI {
type PluginAPI (line 23) | interface PluginAPI {
constant PULL_UP_HOOKS_NAME (line 30) | const PULL_UP_HOOKS_NAME = 'pullingUp'
class PullUp (line 32) | class PullUp implements PluginAPI {
method constructor (line 38) | constructor(public scroll: BScroll) {
method init (line 42) | private init() {
method handleBScroll (line 52) | private handleBScroll() {
method handleOptions (line 58) | private handleOptions(userOptions: PullUpLoadOptions = {}) {
method handleHooks (line 70) | private handleHooks() {
method registerHooks (line 95) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method watch (line 100) | private watch() {
method unwatch (line 112) | private unwatch() {
method checkPullUp (line 117) | private checkPullUp(pos: { x: number; y: number }) {
method finishPullUp (line 134) | finishPullUp() {
method openPullUp (line 147) | openPullUp(config: PullUpLoadOptions = {}) {
method closePullUp (line 152) | closePullUp() {
method autoPullUpLoad (line 156) | autoPullUpLoad() {
FILE: packages/react-examples/src/pages/compose/components/pullup-pulldown-outnested.js
constant TIME_BOUNCE (line 21) | const TIME_BOUNCE = 800
constant REQUEST_TIME (line 22) | const REQUEST_TIME = 3000
constant THRESHOLD (line 23) | const THRESHOLD = 70
constant STOP (line 24) | const STOP = 56
constant TOP_OUT_ITEMS (line 26) | const TOP_OUT_ITEMS = [
constant BOTTOM_OUT_ITEMS (line 37) | const BOTTOM_OUT_ITEMS = [
constant INNER_ITEMS (line 48) | const INNER_ITEMS = [
FILE: packages/react-examples/src/pages/compose/components/pullup-pulldown-slide.js
constant TIME_BOUNCE (line 21) | const TIME_BOUNCE = 700
constant REQUEST_TIME (line 22) | const REQUEST_TIME = 1000
constant THRESHOLD (line 23) | const THRESHOLD = 50
constant STOP (line 24) | const STOP = 56
function generateData (line 26) | function generateData() {
FILE: packages/react-examples/src/pages/compose/components/pullup-pulldown.js
constant TIME_BOUNCE (line 19) | const TIME_BOUNCE = 800
constant REQUEST_TIME (line 20) | const REQUEST_TIME = 3000
constant THRESHOLD (line 21) | const THRESHOLD = 70
constant STOP (line 22) | const STOP = 56
function generateData (line 24) | function generateData() {
FILE: packages/react-examples/src/pages/compose/components/slide-nested.js
constant DATA (line 9) | const DATA = [
FILE: packages/react-examples/src/pages/infinity/index.js
constant NUM_AVATARS (line 11) | const NUM_AVATARS = 4
constant NUM_IMAGES (line 12) | const NUM_IMAGES = 77
constant INIT_TIME (line 13) | const INIT_TIME = new Date().getTime()
function getItem (line 15) | function getItem(id) {
FILE: packages/react-examples/src/pages/mouse-wheel/components/picker.js
constant DATA (line 11) | const DATA = [
FILE: packages/react-examples/src/pages/mouse-wheel/components/pulldown.js
constant TIME_BOUNCE (line 28) | const TIME_BOUNCE = 800
constant STEP (line 29) | let STEP = 0
function generateData (line 31) | function generateData() {
FILE: packages/react-examples/src/pages/picker/components/double-column.js
constant DATA1 (line 8) | const DATA1 = [
constant DATA2 (line 51) | const DATA2 = [
FILE: packages/react-examples/src/pages/picker/components/linkage-column.js
constant DATA (line 8) | const DATA = [
FILE: packages/react-examples/src/pages/picker/components/one-column.js
constant DATA (line 9) | const DATA = [
FILE: packages/react-examples/src/pages/pulldown/components/default.js
constant TIME_BOUNCE (line 26) | const TIME_BOUNCE = 800
constant STEP (line 27) | let STEP = 0
function generateData (line 29) | function generateData() {
FILE: packages/react-examples/src/pages/pulldown/components/sina-weibo.js
function generateData (line 7) | function generateData() {
constant PHASE (line 19) | const PHASE = {
constant TIME_BOUNCE (line 27) | const TIME_BOUNCE = 800
constant REQUEST_TIME (line 28) | const REQUEST_TIME = 2000
constant THRESHOLD (line 29) | const THRESHOLD = 70
constant STOP (line 30) | const STOP = 56
constant STEP (line 31) | let STEP = 0
constant ARROW_BOTTOM (line 32) | const ARROW_BOTTOM =
constant ARROW_UP (line 34) | const ARROW_UP =
FILE: packages/react-examples/src/router.js
constant ROUTES (line 4) | const ROUTES = [
FILE: packages/scroll-bar/src/event-handler.ts
type EventHandlerOptions (line 10) | interface EventHandlerOptions {
class EventHandler (line 15) | class EventHandler {
method constructor (line 24) | constructor(
method registerEvents (line 31) | private registerEvents() {
method BScrollIsDisabled (line 85) | private BScrollIsDisabled() {
method start (line 89) | private start(e: TouchEvent) {
method move (line 103) | private move(e: TouchEvent) {
method end (line 118) | private end(e: TouchEvent) {
method destroy (line 130) | destroy() {
FILE: packages/scroll-bar/src/index.ts
type ScrollbarOptions (line 9) | type ScrollbarOptions = Partial<ScrollbarConfig> | true
type ScrollbarConfig (line 11) | interface ScrollbarConfig {
type CustomOptions (line 23) | interface CustomOptions {
class ScrollBar (line 28) | class ScrollBar {
method constructor (line 33) | constructor(public scroll: BScroll) {
method handleHooks (line 39) | private handleHooks() {
method handleOptions (line 48) | private handleOptions() {
method createIndicators (line 67) | private createIndicators() {
method createScrollbarElement (line 106) | private createScrollbarElement(
FILE: packages/scroll-bar/src/indicator.ts
type IndicatorDirection (line 12) | const enum IndicatorDirection {
type ScrollTo (line 17) | const enum ScrollTo {
type OffsetType (line 22) | const enum OffsetType {
type IndicatorOptions (line 27) | interface IndicatorOptions {
type KeysMap (line 41) | interface KeysMap {
type ScrollInfo (line 53) | interface ScrollInfo {
class Indicator (line 60) | class Indicator {
method constructor (line 74) | constructor(public scroll: BScroll, public options: IndicatorOptions) {
method handleFade (line 85) | private handleFade() {
method handleHooks (line 91) | private handleHooks() {
method registerHooks (line 184) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method bindClick (line 189) | private bindClick() {
method handleClick (line 199) | private handleClick(e: MouseEvent) {
method calculateclickOffsetPos (line 207) | private calculateclickOffsetPos(e: MouseEvent) {
method getKeysMap (line 224) | getKeysMap(): KeysMap {
method fade (line 251) | fade(visible?: boolean) {
method refresh (line 259) | refresh() {
method transitionTime (line 285) | transitionTime(time: number = 0) {
method transitionTimingFunction (line 289) | transitionTimingFunction(easing: string) {
method canScroll (line 293) | private canScroll(hasScroll: boolean): boolean {
method refreshScrollInfo (line 298) | private refreshScrollInfo(
method updatePosition (line 327) | updatePosition(point: TranslaterPoint) {
method caculatePosAndSize (line 333) | private caculatePosAndSize(
method refreshStyle (line 360) | private refreshStyle(size: number, pos: number) {
method startHandler (line 374) | startHandler() {
method moveHandler (line 383) | moveHandler(delta: number) {
method endHandler (line 396) | endHandler() {
method indicatorNotMoved (line 409) | private indicatorNotMoved(delta: number): boolean {
method syncBScroll (line 418) | private syncBScroll(newPos: number) {
method newPos (line 453) | private newPos(
method destroy (line 466) | destroy() {
FILE: packages/shared-utils/src/Touch.ts
type TouchList (line 1) | interface TouchList {
type Touch (line 7) | interface Touch {
type TouchEvent (line 18) | interface TouchEvent extends UIEvent {
FILE: packages/shared-utils/src/__tests__/dom.spec.ts
method get (line 91) | get() {
FILE: packages/shared-utils/src/__tests__/propertiesProxy.spec.ts
method c (line 36) | c() {
FILE: packages/shared-utils/src/debug.ts
function warn (line 1) | function warn(msg: string) {
function assert (line 5) | function assert(condition: string | boolean, msg: string) {
FILE: packages/shared-utils/src/dom.ts
type safeCSSStyleDeclaration (line 4) | type safeCSSStyleDeclaration = {
type DOMRect (line 7) | interface DOMRect {
function prefixStyle (line 55) | function prefixStyle(style: string): string {
function getElement (line 70) | function getElement(el: HTMLElement | string) {
function addEvent (line 76) | function addEvent(
function removeEvent (line 91) | function removeEvent(
function maybePrevent (line 102) | function maybePrevent(e: Event) {
function offset (line 108) | function offset(el: HTMLElement | null) {
function offsetToBody (line 124) | function offsetToBody(el: HTMLElement) {
function getRect (line 177) | function getRect(el: HTMLElement): DOMRect {
function preventDefaultExceptionFn (line 197) | function preventDefaultExceptionFn(
function tap (line 215) | function tap(e: any, eventName: string) {
function click (line 223) | function click(e: any, event = 'click') {
function dblclick (line 285) | function dblclick(e: Event) {
function prepend (line 289) | function prepend(el: HTMLElement, target: HTMLElement) {
function before (line 298) | function before(el: HTMLElement, target: HTMLElement) {
function removeChild (line 303) | function removeChild(el: HTMLElement, child: HTMLElement) {
function hasClass (line 307) | function hasClass(el: HTMLElement, className: string) {
function addClass (line 312) | function addClass(el: HTMLElement, className: string) {
function removeClass (line 322) | function removeClass(el: HTMLElement, className: string) {
function HTMLCollectionToArray (line 331) | function HTMLCollectionToArray(el: HTMLCollection) {
function getClientSize (line 335) | function getClientSize(el: HTMLElement) {
FILE: packages/shared-utils/src/ease.ts
type EaseItem (line 1) | interface EaseItem {
type EaseMap (line 5) | interface EaseMap {
type EaseFn (line 9) | interface EaseFn {
FILE: packages/shared-utils/src/enums.ts
type DirectionLock (line 1) | const enum DirectionLock {
type Direction (line 8) | const enum Direction {
type ApplyOrder (line 16) | const enum ApplyOrder {
type EventPassthrough (line 21) | const enum EventPassthrough {
type EventType (line 27) | const enum EventType {
type MouseButton (line 32) | const enum MouseButton {
type Probe (line 38) | const enum Probe {
type Quadrant (line 45) | const enum Quadrant {
FILE: packages/shared-utils/src/env.ts
method get (line 30) | get() {
FILE: packages/shared-utils/src/events.ts
type Events (line 4) | interface Events {
type EventTypes (line 8) | interface EventTypes {
type WithFnFunction (line 12) | interface WithFnFunction extends Function {
class EventEmitter (line 16) | class EventEmitter {
method constructor (line 19) | constructor(names: string[]) {
method on (line 25) | on(type: string, fn: Function, context: Object = this) {
method once (line 35) | once(type: string, fn: Function, context: Object = this) {
method off (line 50) | off(type?: string, fn?: Function) {
method trigger (line 82) | trigger(type: string, ...args: any[]) {
method registerType (line 103) | registerType(names: string[]) {
method destroy (line 109) | destroy() {
method hasType (line 114) | private hasType(type: string) {
type EventData (line 127) | interface EventData {
class EventRegister (line 133) | class EventRegister {
method constructor (line 134) | constructor(
method destroy (line 141) | destroy() {
method addDOMEvents (line 146) | private addDOMEvents() {
method removeDOMEvents (line 150) | private removeDOMEvents() {
method handleDOMEvents (line 154) | private handleDOMEvents(eventOperation: Function) {
method handleEvent (line 162) | private handleEvent(e: UIEvent) {
FILE: packages/shared-utils/src/lang.ts
function getNow (line 1) | function getNow() {
function isUndef (line 19) | function isUndef(v: any): boolean {
function getDistance (line 23) | function getDistance(x: number, y: number) {
function between (line 26) | function between(x: number, min: number, max: number) {
function findIndex (line 36) | function findIndex<T>(
FILE: packages/shared-utils/src/propertiesProxy.ts
type TraversedObject (line 4) | interface TraversedObject {
function propertiesProxy (line 41) | function propertiesProxy(
FILE: packages/shared-utils/src/raf.ts
type DelayedHandler (line 3) | interface DelayedHandler {
constant DEFAULT_INTERVAL (line 8) | const DEFAULT_INTERVAL = 1000 / 60
function noop (line 12) | function noop() {}
FILE: packages/shared-utils/src/types.ts
type Position (line 1) | type Position = {
FILE: packages/slide/src/PagesMatrix.ts
type PageStats (line 5) | interface PageStats {
class PagesMatrix (line 14) | class PagesMatrix {
method constructor (line 22) | constructor(private scroll: BScroll) {
method init (line 25) | init() {
method getPageStats (line 37) | getPageStats(pageX: number, pageY: number): PageStats {
method getNearestPageIndex (line 46) | getNearestPageIndex(x: number, y: number): PageIndex {
method buildPagesMatrix (line 69) | private buildPagesMatrix(
FILE: packages/slide/src/SlidePages.ts
type PageIndex (line 7) | interface PageIndex {
type Position (line 11) | interface Position {
type Page (line 16) | type Page = PageIndex & Position
type Direction (line 18) | const enum Direction {
class SlidePages (line 23) | class SlidePages {
method constructor (line 31) | constructor(public scroll: BScroll, private slideOptions: SlideConfig) {
method refresh (line 35) | refresh() {
method getAdjustedCurrentPage (line 41) | getAdjustedCurrentPage(): Page {
method setCurrentPage (line 59) | setCurrentPage(newPage: Page) {
method getInternalPage (line 63) | getInternalPage(pageX: number, pageY: number): Page {
method getInitialPage (line 86) | getInitialPage(
method getExposedPage (line 115) | getExposedPage(page: Page): Page {
method getExposedPageByPageIndex (line 133) | getExposedPageByPageIndex(pageIndexX: number, pageIndexY: number): Page {
method getWillChangedPage (line 153) | getWillChangedPage(page: Page): Page {
method fixedPage (line 173) | private fixedPage(page: number, realPageLen: number): number {
method getPageStats (line 183) | getPageStats(): PageStats {
method getValidPageIndex (line 190) | getValidPageIndex(x: number, y: number): PageIndex {
method nextPageIndex (line 214) | nextPageIndex(): PageIndex {
method prevPageIndex (line 218) | prevPageIndex(): PageIndex {
method getNearestPage (line 222) | getNearestPage(x: number, y: number): Page {
method getPageByDirection (line 237) | getPageByDirection(page: Page, directionX: number, directionY: number)...
method resetLoopPage (line 257) | resetLoopPage(): PageIndex | undefined {
method getPageIndexByDirection (line 288) | private getPageIndexByDirection(direction: Direction): PageIndex {
method checkSlideLoop (line 303) | private checkSlideLoop() {
FILE: packages/slide/src/constants.ts
constant BASE_PAGE (line 1) | const BASE_PAGE = {
constant DEFAULT_PAGE_STATS (line 8) | const DEFAULT_PAGE_STATS = {
FILE: packages/slide/src/index.ts
type SlideConfig (line 17) | interface SlideConfig {
type SlideOptions (line 31) | type SlideOptions = Partial<SlideConfig> | true
type CustomOptions (line 34) | interface CustomOptions {
type CustomAPI (line 37) | interface CustomAPI {
type PluginAPI (line 42) | interface PluginAPI {
type styleConfiguration (line 55) | type styleConfiguration = {
class Slide (line 60) | class Slide implements PluginAPI {
method constructor (line 77) | constructor(public scroll: BScroll) {
method satisfyInitialization (line 84) | private satisfyInitialization(): boolean {
method init (line 95) | init() {
method createPages (line 104) | private createPages() {
method handleBScroll (line 108) | private handleBScroll() {
method handleOptions (line 113) | private handleOptions() {
method handleLoop (line 131) | private handleLoop(prevSlideContent: HTMLElement) {
method resetLoopChangedStatus (line 165) | private resetLoopChangedStatus() {
method handleHooks (line 170) | private handleHooks() {
method startPlay (line 269) | startPlay() {
method pausePlay (line 279) | pausePlay() {
method setSlideInlineStyle (line 285) | private setSlideInlineStyle() {
method next (line 319) | next(time?: number, easing?: EaseItem) {
method prev (line 323) | prev(time?: number, easing?: EaseItem) {
method goToPage (line 328) | goToPage(pageX: number, pageY: number, time?: number, easing?: EaseIte...
method getCurrentPage (line 333) | getCurrentPage(): Page {
method setCurrentPage (line 337) | setCurrentPage(page: Page) {
method nearestPage (line 342) | nearestPage(x: number, y: number): Page {
method satisfyThreshold (line 359) | private satisfyThreshold(x: number, y: number): boolean {
method refreshHandler (line 373) | private refreshHandler(content: HTMLElement) {
method computeThreshold (line 405) | private computeThreshold() {
method cloneFirstAndLastSlidePage (line 420) | private cloneFirstAndLastSlidePage(slideContent: HTMLElement) {
method removeClonedSlidePage (line 431) | private removeClonedSlidePage(slideContent: HTMLElement) {
method modifyCurrentPage (line 442) | private modifyCurrentPage(point: Position) {
method goTo (line 508) | private goTo(pageX: number, pageY: number, time?: number, easing?: Eas...
method flickHandler (line 524) | private flickHandler() {
method getEaseTime (line 545) | private getEaseTime(deltaX: number, deltaY: number): number {
method modifyScrollMetaHandler (line 558) | private modifyScrollMetaHandler(scrollMeta: {
method scrollHandler (line 586) | private scrollHandler({ x, y }: Position) {
method pageWillChangeTo (line 593) | private pageWillChangeTo(newPage: Page) {
method registerHooks (line 604) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method destroy (line 609) | destroy() {
FILE: packages/vuepress-docs/docs/.vuepress/config.js
function resolve (line 5) | function resolve(p) {
function getIp (line 118) | function getIp() {
function getPackagesName (line 134) | function getPackagesName() {
FILE: packages/vuepress-docs/docs/.vuepress/plugins/extract-code.js
function extractCodeFromVueSFC (line 3) | function extractCodeFromVueSFC(md, options = {}) {
method chainMarkdown (line 89) | chainMarkdown(config) {
FILE: packages/wheel/src/index.ts
type WheelOptions (line 13) | type WheelOptions = Partial<WheelConfig> | true
constant WHEEL_INDEX_CHANGED_EVENT_NAME (line 15) | const WHEEL_INDEX_CHANGED_EVENT_NAME = 'wheelIndexChanged'
type WheelConfig (line 17) | interface WheelConfig {
type CustomOptions (line 27) | interface CustomOptions {
type CustomAPI (line 30) | interface CustomAPI {
type PluginAPI (line 35) | interface PluginAPI {
constant CONSTANTS (line 41) | const CONSTANTS = {
class Wheel (line 44) | class Wheel implements PluginAPI {
method constructor (line 53) | constructor(public scroll: BScroll) {
method init (line 57) | init() {
method handleBScroll (line 66) | private handleBScroll() {
method handleOptions (line 71) | private handleOptions() {
method handleHooks (line 87) | private handleHooks() {
method refreshBoundary (line 252) | private refreshBoundary() {
method setSelectedIndex (line 258) | setSelectedIndex(index: number, contentChanged: boolean = false) {
method getSelectedIndex (line 268) | getSelectedIndex() {
method wheelTo (line 272) | wheelTo(index = 0, time = 0, ease?: EaseItem) {
method restorePosition (line 277) | restorePosition() {
method transitionDuration (line 287) | private transitionDuration(time: number) {
method timeFunction (line 294) | private timeFunction(easing: string) {
method rotateX (line 302) | private rotateX(y: number) {
method findNearestValidWheel (line 314) | private findNearestValidWheel(y: number) {
method checkWheelAllDisabled (line 363) | private checkWheelAllDisabled() {
FILE: packages/zoom/src/__tests__/__utils__/util.ts
function createZoomElements (line 7) | function createZoomElements() {
function createTouchEvent (line 14) | function createTouchEvent(
FILE: packages/zoom/src/index.ts
type ZoomOptions (line 17) | type ZoomOptions = Partial<ZoomConfig> | true
type ZoomConfig (line 19) | interface ZoomConfig {
type OriginX (line 28) | type OriginX = number | 'left' | 'right' | 'center'
type OriginY (line 29) | type OriginY = number | 'top' | 'bottom' | 'center'
type CustomOptions (line 32) | interface CustomOptions {
type CustomAPI (line 35) | interface CustomAPI {
type PluginAPI (line 39) | interface PluginAPI {
type Point (line 43) | interface Point {
type ResolveFormula (line 49) | interface ResolveFormula {
constant TWO_FINGERS (line 57) | const TWO_FINGERS = 2
constant RAW_SCALE (line 58) | const RAW_SCALE = 1
class Zoom (line 59) | class Zoom implements PluginAPI {
method constructor (line 71) | constructor(public scroll: BScroll) {
method init (line 75) | init() {
method zoomTo (line 85) | zoomTo(scale: number, x: OriginX, y: OriginY, bounceTime?: number) {
method handleBScroll (line 95) | private handleBScroll() {
method handleOptions (line 105) | private handleOptions() {
method handleHooks (line 120) | private handleHooks() {
method setTransformOrigin (line 236) | private setTransformOrigin(content: HTMLElement) {
method tryInitialZoomTo (line 240) | private tryInitialZoomTo(options: ZoomConfig) {
method fingersOperation (line 253) | private fingersOperation(amounts?: number): number | void {
method _doZoomTo (line 261) | private _doZoomTo(
method _zoomTo (line 309) | private _zoomTo(
method resolveOrigin (line 356) | private resolveOrigin(x: OriginX, y: OriginY) {
method zoomStart (line 385) | zoomStart(e: TouchEvent) {
method zoom (line 407) | zoom(e: TouchEvent) {
method zoomEnd (line 452) | zoomEnd() {
method getFingerDistance (line 462) | private getFingerDistance(e: TouchEvent): number {
method shouldRebound (line 471) | private shouldRebound(): boolean {
method dampingScale (line 487) | private dampingScale(scale: number) {
method setScale (line 498) | private setScale(scale: number) {
method resetBoundaries (line 502) | private resetBoundaries(scrollBehaviorPairs: [Behavior, Behavior]) {
method getNewPos (line 506) | private getNewPos(
method registerHooks (line 528) | private registerHooks(hooks: EventEmitter, name: string, handler: Func...
method destroy (line 533) | destroy() {
FILE: scripts/build.js
function getPackagesName (line 16) | function getPackagesName () {
function cleanPackagesOldDist (line 33) | function cleanPackagesOldDist(packagesName) {
function resolve (line 47) | function resolve(p) {
function PascalCase (line 51) | function PascalCase(str){
function generateBuildConfigs (line 86) | function generateBuildConfigs(packagesName) {
function generateBuildPluginsConfigs (line 124) | function generateBuildPluginsConfigs(isMin) {
function build (line 137) | function build(builds) {
function buildEntry (line 152) | function buildEntry(config, curIndex, next) {
function copyDTSFiles (line 189) | function copyDTSFiles (packageName) {
function getSize (line 199) | function getSize(code) {
FILE: scripts/release.js
function getVersion (line 19) | function getVersion (answers) {
function getNpmTags (line 23) | function getNpmTags (version) {
function isPreRelease (line 30) | function isPreRelease (version) {
FILE: test-dts/core.test-d.ts
type ExtraTransform (line 10) | type ExtraTransform = { start: object; end: object }
FILE: test-dts/index.d.ts
type ArgumentsCheck (line 28) | type ArgumentsCheck<
type ReturnValueCheck (line 36) | type ReturnValueCheck<T, U extends (...args: any[]) => any> = (
FILE: test-dts/plugin.test-d.ts
type BSOptions (line 71) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type ZoomToAPI (line 89) | type ZoomToAPI = typeof bscroll.zoomTo
type OriginX (line 90) | type OriginX = number | 'left' | 'right' | 'center'
type OriginY (line 91) | type OriginY = number | 'top' | 'bottom' | 'center'
type BSOptions (line 108) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type WhellToAPI (line 128) | type WhellToAPI = typeof bscroll.wheelTo
type GetSelectedIndexAPI (line 129) | type GetSelectedIndexAPI = typeof bscroll.getSelectedIndex
type BSOptions (line 142) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type EaseType (line 143) | type EaseType = {
type BS (line 164) | type BS = typeof bscroll
type Page (line 165) | type Page = {
type BSOptions (line 184) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BSOptions (line 208) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BS (line 219) | type BS = typeof bscroll
type BSOptions (line 234) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BS (line 245) | type BS = typeof bscroll
type BSOptions (line 261) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BSOptions (line 273) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BSOptions (line 293) | type BSOptions = DeepNonNullable<typeof bscroll.options>
type BSOptions (line 327) | type BSOptions = DeepNonNullable<typeof bscroll.options>
FILE: test-dts/util.d.ts
type NonUndefined (line 1) | type NonUndefined<T> = T extends undefined ? never : T
type DeepNonNullable (line 2) | type DeepNonNullable<T> = {
type IfEquals (line 8) | type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X
type FilterType (line 14) | type FilterType<T, F> = T extends F ? never : T
type FilterUndef (line 16) | type FilterUndef<T> = FilterType<T, undefined>
type FilterBoolean (line 17) | type FilterBoolean<T> = FilterType<T, boolean>
type FilterNull (line 18) | type FilterNull<T> = FilterType<T, null>
type FilterString (line 19) | type FilterString<T> = FilterType<T, string>
type FilterNumber (line 20) | type FilterNumber<T> = FilterType<T, number>
type FilterSymbol (line 21) | type FilterSymbol<T> = FilterType<T, symbol>
type FilterArray (line 22) | type FilterArray<T> = FilterType<T, Array<any>>
type FilterFunc (line 23) | type FilterFunc<T> = FilterType<T, Function>
type FilterObject (line 24) | type FilterObject<T> = FilterType<T, object>
type ExcludeTrue (line 26) | type ExcludeTrue<T> = FilterType<T, true>
FILE: tests/util/extendMouseWheel.ts
type EventParams (line 3) | interface EventParams {
constant DEFAULT_CHROMIUM_MOUSE_WHEEL_NAME (line 11) | const DEFAULT_CHROMIUM_MOUSE_WHEEL_NAME = 'Input.dispatchMouseEvent'
type Mouse (line 14) | interface Mouse {
type Page (line 19) | interface Page {
FILE: tests/util/extendTouch.ts
type PinchParams (line 4) | interface PinchParams {
type ScrollParams (line 12) | interface ScrollParams {
type TouchPoint (line 26) | interface TouchPoint {
type TouchesParams (line 40) | interface TouchesParams {
constant PINCH_NAME (line 47) | const PINCH_NAME = 'Input.synthesizePinchGesture'
constant SCROLL_NAME (line 48) | const SCROLL_NAME = 'Input.synthesizeScrollGesture'
constant TOUCHES_NAME (line 49) | const TOUCHES_NAME = 'Input.dispatchTouchEvent'
type Touchscreen (line 52) | interface Touchscreen {
type Page (line 60) | interface Page {
FILE: tests/util/getScale.ts
function getScale (line 1) | function getScale(transformText: string) {
FILE: tests/util/getTranslate.ts
function getTranslate (line 1) | function getTranslate(
Condensed preview — 480 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,436K chars).
[
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".eslintrc.js",
"chars": 631,
"preview": "module.exports = {\n\troot: true,\n\tparser: 'babel-eslint',\n\tparserOptions: {\n\t\tsourceType: 'module'\n\t},\n\t// https://github"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 292,
"preview": "<!--\nIMPORTANT: Please use the following link to create a new issue:\n\n https://cube-ui.github.io/cube-issue-helper/?rep"
},
{
"path": ".gitignore",
"chars": 132,
"preview": ".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"
},
{
"path": ".gitpod.Dockerfile",
"chars": 1054,
"preview": "FROM gitpod/workspace-full\n\nRUN sudo apt-get update && \\\n sudo apt-get install -y \\\n ca-certificates \\\n "
},
{
"path": ".gitpod.yml",
"chars": 407,
"preview": "image:\n file: .gitpod.Dockerfile\ntasks:\n - command: gp await-port 8080 && sleep 3 && gp preview $(gp url 8080)/docs\n "
},
{
"path": ".huskyrc.json",
"chars": 105,
"preview": "{\n \"hooks\": {\n \"pre-commit\": \"lint-staged\",\n \"commit-msg\": \"commitlint -E HUSKY_GIT_PARAMS\"\n }\n}\n"
},
{
"path": ".npmignore",
"chars": 190,
"preview": "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.np"
},
{
"path": ".travis.yml",
"chars": 186,
"preview": "language: node_js\nsudo: false\ncache:\n directories:\n - node_modules\nnode_js:\n - \"lts/*\"\nbranches:\n only:\n - mast"
},
{
"path": ".yarnrc",
"chars": 37,
"preview": "registry \"https://registry.npmjs.org\""
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 HuangYi\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 7380,
"preview": "# better-scroll\n\n<img src=\"https://dpubstatic.udache.com/static/dpubimg/t_L6vAgQ-E/logo.svg\">\n\n["
},
{
"path": "packages/better-scroll/README_zh-CN.md",
"chars": 228,
"preview": "# better-scroll\n\n具备完整插件能力的 BetterScroll,不用关心各种插件注册的细节。\n\n## 使用\n\n```js\nimport BScroll from 'better-scroll'\n\nconst bs = new"
},
{
"path": "packages/better-scroll/package.json",
"chars": 1418,
"preview": "{\n \"name\": \"better-scroll\",\n \"version\": \"2.5.1\",\n \"description\": \"Full-featured BetterScroll\",\n \"author\": {\n \"nam"
},
{
"path": "packages/better-scroll/src/index.ts",
"chars": 1324,
"preview": "import BScroll from '@better-scroll/core'\nimport MouseWheel from '@better-scroll/mouse-wheel'\nimport ObserveDom from '@b"
},
{
"path": "packages/core/README.md",
"chars": 262,
"preview": "# @better-scroll/core\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/core/README_zh-CN.md)\n\nc"
},
{
"path": "packages/core/README_zh-CN.md",
"chars": 150,
"preview": "# @better-scroll/core\n\n核心滚动,实现基础的列表滚动效果。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\n\nconst bs = new BScroll"
},
{
"path": "packages/core/package.json",
"chars": 902,
"preview": "{\n \"name\": \"@better-scroll/core\",\n \"version\": \"2.5.1\",\n \"description\": \"Minimalistic core scrolling for BetterScroll,"
},
{
"path": "packages/core/src/BScroll.ts",
"chars": 7669,
"preview": "import { BScrollInstance, propertiesConfig } from './Instance'\nimport { Options, DefOptions, OptionsConstructor } from '"
},
{
"path": "packages/core/src/Instance.ts",
"chars": 2748,
"preview": "import { Behavior } from './scroller/Behavior'\nimport Actions from './scroller/Actions'\nimport { ExposedAPI as ExposedAP"
},
{
"path": "packages/core/src/Options.ts",
"chars": 5836,
"preview": "import {\n hasTransition,\n hasPerspective,\n hasTouch,\n Probe,\n EventPassthrough,\n extend,\n Quadrant,\n} from '@bett"
},
{
"path": "packages/core/src/__mocks__/Options.ts",
"chars": 1176,
"preview": "const mockOptions = jest.fn().mockImplementation(() => {\n return {\n startX: 0,\n startY: 0,\n scrollX: false,\n "
},
{
"path": "packages/core/src/__mocks__/index.ts",
"chars": 1643,
"preview": "import Scroller from '../scroller/Scroller'\nimport { OptionsConstructor } from '../Options'\nimport { EventEmitter } from"
},
{
"path": "packages/core/src/__tests__/Options.spec.ts",
"chars": 4828,
"preview": "import { OptionsConstructor } from '../Options'\n\ndescribe('BetterScroll Options', () => {\n let options: OptionsConstruc"
},
{
"path": "packages/core/src/__tests__/__utils__/event.ts",
"chars": 2513,
"preview": "export function createEvent(type: string, name: string): Event {\n const e = document.createEvent(type || 'Event')\n e.i"
},
{
"path": "packages/core/src/__tests__/__utils__/layout.ts",
"chars": 2603,
"preview": "export interface CustomHTMLDivElement extends HTMLDivElement {\n clientWidth: number\n clientHeight: number\n offsetWidt"
},
{
"path": "packages/core/src/__tests__/index.spec.ts",
"chars": 3220,
"preview": "import BScroll from '../index'\n\ndescribe('BetterScroll Core', () => {\n let bscroll: BScroll\n let wrapper = document.cr"
},
{
"path": "packages/core/src/animater/Animation.ts",
"chars": 3152,
"preview": "import Base from './Base'\nimport { TranslaterPoint } from '../translater'\nimport {\n getNow,\n requestAnimationFrame,\n "
},
{
"path": "packages/core/src/animater/Base.ts",
"chars": 1733,
"preview": "import {\n EaseFn,\n safeCSSStyleDeclaration,\n cancelAnimationFrame,\n EventEmitter,\n Probe,\n} from '@better-scroll/sh"
},
{
"path": "packages/core/src/animater/Transition.ts",
"chars": 3294,
"preview": "import {\n style,\n requestAnimationFrame,\n cancelAnimationFrame,\n EaseFn,\n Probe,\n} from '@better-scroll/shared-util"
},
{
"path": "packages/core/src/animater/__mocks__/Animation.ts",
"chars": 820,
"preview": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Animation = jest\n .fn()\n .mockImplementation((conten"
},
{
"path": "packages/core/src/animater/__mocks__/Transition.ts",
"chars": 927,
"preview": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Transition = jest\n .fn()\n .mockImplementation((conte"
},
{
"path": "packages/core/src/animater/__mocks__/index.ts",
"chars": 534,
"preview": "import Transition from '../Transition'\nimport Animation from '../Animation'\n\njest.mock('../Transition')\njest.mock('../An"
},
{
"path": "packages/core/src/animater/__tests__/Animation.spec.ts",
"chars": 4595,
"preview": "import Translater from '../../translater'\njest.mock('../../translater')\n\nlet mockRequestAnimationFrame = jest.fn()\nlet m"
},
{
"path": "packages/core/src/animater/__tests__/Transition.spec.ts",
"chars": 4973,
"preview": "import Translater from '../../translater/index'\njest.mock('../../translater/index')\n\nlet mockRequestAnimationFrame = jes"
},
{
"path": "packages/core/src/animater/__tests__/index.spec.ts",
"chars": 1116,
"preview": "import Translater from '../../translater/index'\nimport Transition from '../Transition'\nimport Animation from '../Animati"
},
{
"path": "packages/core/src/animater/index.ts",
"chars": 888,
"preview": "import Translater from '../translater'\nimport { Options as BScrollOptions } from '../Options'\n\nimport Animater from './B"
},
{
"path": "packages/core/src/base/ActionsHandler.ts",
"chars": 5897,
"preview": "import {\n TouchEvent,\n // dom\n preventDefaultExceptionFn,\n tagExceptionFn,\n eventTypeMap,\n EventType,\n MouseButto"
},
{
"path": "packages/core/src/base/__mocks__/ActionsHandler.ts",
"chars": 633,
"preview": "import { EventRegister, EventEmitter } from '@better-scroll/shared-utils'\n\nconst ActionsHandler = jest\n .fn()\n .mockIm"
},
{
"path": "packages/core/src/base/__tests__/ActionsHandler.spec.ts",
"chars": 5815,
"preview": "import ActionsHandler, {\n Options,\n} from '@better-scroll/core/src/base/ActionsHandler'\nimport {\n dispatchTouch,\n dis"
},
{
"path": "packages/core/src/index.ts",
"chars": 368,
"preview": "import { BScroll } from './BScroll'\n\nexport { BScrollInstance } from './Instance'\nexport { Options, CustomOptions } from"
},
{
"path": "packages/core/src/scroller/Actions.ts",
"chars": 8227,
"preview": "import ActionsHandler from '../base/ActionsHandler'\nimport { Behavior } from './Behavior'\nimport DirectionLockAction fro"
},
{
"path": "packages/core/src/scroller/Behavior.ts",
"chars": 8021,
"preview": "import { getRect, Direction, EventEmitter } from '@better-scroll/shared-utils'\n\nexport type Bounces = [boolean, boolean]"
},
{
"path": "packages/core/src/scroller/DirectionLock.ts",
"chars": 2478,
"preview": "import {\n TouchEvent,\n DirectionLock,\n EventPassthrough,\n maybePrevent,\n} from '@better-scroll/shared-utils'\n\nconst "
},
{
"path": "packages/core/src/scroller/Scroller.ts",
"chars": 16954,
"preview": "import ActionsHandler from '../base/ActionsHandler'\nimport Translater, { TranslaterPoint } from '../translater'\nimport c"
},
{
"path": "packages/core/src/scroller/__mocks__/Actions.ts",
"chars": 1180,
"preview": "import DirectionLock from '../DirectionLock'\n\njest.mock('../DirectionLock')\n\nimport { EventEmitter } from '@better-scrol"
},
{
"path": "packages/core/src/scroller/__mocks__/Behavior.ts",
"chars": 1360,
"preview": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Behavior = jest.fn().mockImplementation((content, bscr"
},
{
"path": "packages/core/src/scroller/__mocks__/DirectionLock.ts",
"chars": 580,
"preview": "const DirectionLock = jest\n .fn()\n .mockImplementation((content, bscrollOptions) => {\n return {\n directionLock"
},
{
"path": "packages/core/src/scroller/__mocks__/Scroller.ts",
"chars": 2001,
"preview": "import createAnimater from '../../animater'\nimport Translater from '../../translater'\nimport { Behavior } from '../Behav"
},
{
"path": "packages/core/src/scroller/__tests__/Actions.spec.ts",
"chars": 9510,
"preview": "import { Behavior } from '../Behavior'\nimport createAnimater from '../../animater'\nimport Translater from '../../transla"
},
{
"path": "packages/core/src/scroller/__tests__/Behavior.spec.ts",
"chars": 4329,
"preview": "import { Behavior } from '../Behavior'\nimport { createDiv } from '../../__tests__/__utils__/layout'\n\ndescribe('Behavior "
},
{
"path": "packages/core/src/scroller/__tests__/DirectionLock.spec.ts",
"chars": 2510,
"preview": "import DirectionLock from '../DirectionLock'\nimport {\n Direction,\n DirectionLock as DirectionLockEnum,\n} from '@better"
},
{
"path": "packages/core/src/scroller/__tests__/Scroller.spec.ts",
"chars": 12503,
"preview": "import { Behavior } from '../Behavior'\nimport createAnimater from '../../animater'\nimport Translater from '../../transla"
},
{
"path": "packages/core/src/scroller/__tests__/createOptions.spec.ts",
"chars": 1357,
"preview": "import {\n createActionsHandlerOptions,\n createBehaviorOptions,\n} from '../createOptions'\nimport { OptionsConstructor }"
},
{
"path": "packages/core/src/scroller/createOptions.ts",
"chars": 1254,
"preview": "import { Options as BScrollOptions } from '../Options'\nimport { Options as ActionsHandlerOptions } from '../base/Actions"
},
{
"path": "packages/core/src/translater/__mocks__/index.ts",
"chars": 469,
"preview": "import { EventEmitter } from '@better-scroll/shared-utils'\n\nconst Translater = jest.fn().mockImplementation((content) =>"
},
{
"path": "packages/core/src/translater/__tests__/index.spec.ts",
"chars": 1644,
"preview": "import Translater from '../index'\n\ndescribe('Translater Class test suit', () => {\n let translater: Translater\n let con"
},
{
"path": "packages/core/src/translater/index.ts",
"chars": 1904,
"preview": "import {\n style,\n safeCSSStyleDeclaration,\n EventEmitter,\n} from '@better-scroll/shared-utils'\nexport interface Trans"
},
{
"path": "packages/core/src/utils/__tests__/bubbling.spec.ts",
"chars": 478,
"preview": "import { bubbling } from '../bubbling'\nimport { EventEmitter } from '@better-scroll/shared-utils'\n\ndescribe('bubbling', "
},
{
"path": "packages/core/src/utils/bubbling.ts",
"chars": 652,
"preview": "import { EventEmitter } from '@better-scroll/shared-utils'\n\ninterface BubblingEventMap {\n source: string\n target: stri"
},
{
"path": "packages/core/src/utils/compare.ts",
"chars": 344,
"preview": "import { TranslaterPoint } from '../translater'\n\nexport function isSamePoint(\n startPoint: TranslaterPoint,\n endPoint:"
},
{
"path": "packages/core/src/utils/compat.ts",
"chars": 1233,
"preview": "import { Direction } from '@better-scroll/shared-utils'\nimport { TranslaterPoint } from '../translater'\n\ntype Position ="
},
{
"path": "packages/core/src/utils/typesHelper.ts",
"chars": 127,
"preview": "export type UnionToIntersection<U> = (\n U extends any ? (k: U) => void : never\n) extends (k: infer I) => void\n ? I\n :"
},
{
"path": "packages/examples/README.md",
"chars": 118,
"preview": "# 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",
"chars": 960,
"preview": "var ora = require('ora')\nvar rm = require('rimraf')\nvar path = require('path')\nvar chalk = require('chalk')\nvar webpack "
},
{
"path": "packages/examples/build/vue-webpack.conf.js",
"chars": 6388,
"preview": "const Config = require('webpack-chain')\nconst VueLoaderPlugin = require('vue-loader/lib/plugin')\nconst MiniCssExtractPlu"
},
{
"path": "packages/examples/package.json",
"chars": 1523,
"preview": "{\n \"name\": \"examples\",\n \"version\": \"2.5.1\",\n \"description\": \"Examples of BetterScroll\",\n \"author\": {\n "
},
{
"path": "packages/examples/postcss.config.js",
"chars": 130,
"preview": "module.exports = {\n plugins: [\n require('autoprefixer')({\n browsers: require('./package.json').browserslist\n "
},
{
"path": "packages/examples/static/css/github-light.css",
"chars": 2721,
"preview": "/*\n Copyright 2014 GitHub Inc.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use "
},
{
"path": "packages/examples/static/css/normalize.css",
"chars": 7702,
"preview": "/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n/**\n * 1. Set default font family to sans-serif.\n * 2. Pre"
},
{
"path": "packages/examples/static/css/reset.css",
"chars": 1797,
"preview": "/**\n * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)\n * http://cssreset.com\n */\nhtml, body, di"
},
{
"path": "packages/examples/static/css/stylesheet.css",
"chars": 5683,
"preview": "* {\n box-sizing: border-box; }\n\nbody {\n padding: 0;\n margin: 0;\n font-family: \"Open Sans\", \"Helvetica Neue\", Helveti"
},
{
"path": "packages/examples/vue/App.vue",
"chars": 3079,
"preview": "<template>\n <div id=\"app\">\n <section class=\"page-header\">\n <h1 class=\"project-name\">BetterScroll</h1>\n <h2"
},
{
"path": "packages/examples/vue/components/compose/pullup-pulldown-outnested.vue",
"chars": 5998,
"preview": "<template>\n <div class=\"container\">\n <div ref=\"outerScroll\" class=\"outer-wrapper\">\n <div class=\"outer-content\">"
},
{
"path": "packages/examples/vue/components/compose/pullup-pulldown-slide.vue",
"chars": 5699,
"preview": "<template>\n <div class=\"pullup-down-slide-wrapper\">\n <!-- pulldown -->\n <div\n class=\"pulldown-wrapper\"\n "
},
{
"path": "packages/examples/vue/components/compose/pullup-pulldown.vue",
"chars": 3972,
"preview": "<template>\n <div class=\"pullup-down\">\n <div\n class=\"pullup-down-bswrapper\"\n ref=\"bsWrapper\"\n >\n <d"
},
{
"path": "packages/examples/vue/components/compose/slide-nested.vue",
"chars": 3353,
"preview": "<template>\n <div class=\"container\">\n <div ref=\"outerScroll\" class=\"outer-wrapper\">\n <div class=\"outer-content\">"
},
{
"path": "packages/examples/vue/components/core/default.vue",
"chars": 2208,
"preview": "<template>\n <div class=\"core-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scroll-content"
},
{
"path": "packages/examples/vue/components/core/dynamic-content.vue",
"chars": 1976,
"preview": "<template>\n <div class=\"core-dynamic-content-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class"
},
{
"path": "packages/examples/vue/components/core/freescroll.vue",
"chars": 5842,
"preview": "<template>\n <div class=\"free-scroll-container\">\n <div class=\"free-scroll-wrapper\">\n <div class=\"scroll-wrapper\""
},
{
"path": "packages/examples/vue/components/core/horizontal-rotated.vue",
"chars": 1508,
"preview": "<template>\n <div class=\"horizontal-rotated-container\">\n <div class=\"description\">Flipping layout via CSS</div>\n <"
},
{
"path": "packages/examples/vue/components/core/horizontal.vue",
"chars": 1738,
"preview": "<template>\n <div class=\"horizontal-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scroll-c"
},
{
"path": "packages/examples/vue/components/core/specified-content.vue",
"chars": 1569,
"preview": "<template>\n <div class=\"core-specified-content-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div cla"
},
{
"path": "packages/examples/vue/components/core/vertical-rotated.vue",
"chars": 1335,
"preview": "<template>\n <div class=\"vertical-rotated-container\">\n <div class=\"description\">Horizontal layout via CSS</div>\n <"
},
{
"path": "packages/examples/vue/components/form/textarea.vue",
"chars": 1476,
"preview": "<template>\n <div class=\"textarea-container\">\n <div ref=\"scroller\" class=\"textarea-wrapper\">\n <div class=\"textar"
},
{
"path": "packages/examples/vue/components/indicators/minimap.vue",
"chars": 1805,
"preview": "<template>\n <div class=\"minimap-container\">\n <div class=\"scroll-wrapper\" ref=\"wrapper\">\n <!-- maxWidth is used "
},
{
"path": "packages/examples/vue/components/indicators/parallax-scroll.vue",
"chars": 1813,
"preview": "<template>\n <div class=\"parallax-scroll-container\">\n <div class=\"parallax-scroll-box\">\n <div class=\"scroll-wrap"
},
{
"path": "packages/examples/vue/components/infinity/data/message.json",
"chars": 11095,
"preview": "[\n \"when you popState and actually being well, we expect it further\",\n \"But I'm going to take care of ripping out my c"
},
{
"path": "packages/examples/vue/components/infinity/default.vue",
"chars": 6437,
"preview": "<template>\n <div class=\"infinity\">\n <div class=\"template\">\n <li ref=\"message\" class=\"infinity-item\">\n <i"
},
{
"path": "packages/examples/vue/components/mouse-wheel/horizontal-scroll.vue",
"chars": 1252,
"preview": "<template>\n <div class=\"mouse-wheel-horizontal-scroll\">\n <div class=\"mouse-wheel-wrapper\" ref=\"scroll\">\n <div c"
},
{
"path": "packages/examples/vue/components/mouse-wheel/horizontal-slide.vue",
"chars": 2618,
"preview": "<template>\n <div class=\"mouse-wheel-horizontal-slide\">\n <div class=\"slide-container\">\n <div class=\"slide-wrappe"
},
{
"path": "packages/examples/vue/components/mouse-wheel/picker.vue",
"chars": 7685,
"preview": "<template>\n <div class=\"mouse-wheel-picker\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"show\""
},
{
"path": "packages/examples/vue/components/mouse-wheel/pulldown.vue",
"chars": 3709,
"preview": "<template>\n <div class=\"mouse-wheel-pulldown\">\n <div ref=\"scroll\" class=\"pulldown-wrapper\">\n <div class=\"pulldo"
},
{
"path": "packages/examples/vue/components/mouse-wheel/pullup.vue",
"chars": 2309,
"preview": "<template>\n <div class=\"mouse-wheel-pullup\">\n <div ref=\"scroll\" class=\"pullup-wrapper\">\n <div class=\"pullup-con"
},
{
"path": "packages/examples/vue/components/mouse-wheel/vertical-scroll.vue",
"chars": 1033,
"preview": "<template>\n <div class=\"mouse-wheel-vertical-scroll\">\n <div class=\"mouse-wheel-wrapper\" ref=\"scroll\">\n <div cla"
},
{
"path": "packages/examples/vue/components/mouse-wheel/vertical-slide.vue",
"chars": 2548,
"preview": "<template>\n <div class=\"mouse-wheel-slide-vertical\">\n <div class=\"slide-container\">\n <div class=\"slide-wrapper\""
},
{
"path": "packages/examples/vue/components/movable/default.vue",
"chars": 1478,
"preview": "<template>\n <div class=\"movable-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scroll-cont"
},
{
"path": "packages/examples/vue/components/movable/multi-content-scale.vue",
"chars": 2947,
"preview": "<template>\n <div class=\"movable-multi-content-scale-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <di"
},
{
"path": "packages/examples/vue/components/movable/multi-content.vue",
"chars": 2732,
"preview": "<template>\n <div class=\"movable-multi-content-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div clas"
},
{
"path": "packages/examples/vue/components/movable/scale.vue",
"chars": 1583,
"preview": "<template>\n <div class=\"core-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scroll-content"
},
{
"path": "packages/examples/vue/components/nested-scroll/horizontal-in-vertical.vue",
"chars": 3207,
"preview": "<template>\n <div class=\"horizontal-in-vertical-container\">\n <div class=\"vertical-wrapper\" ref=\"outerScroll\">\n <"
},
{
"path": "packages/examples/vue/components/nested-scroll/horizontal.vue",
"chars": 2952,
"preview": "<template>\n <div class=\"container\">\n <div ref=\"outerScroll\" class=\"outer-wrapper\">\n <ul class=\"outer-conten"
},
{
"path": "packages/examples/vue/components/nested-scroll/triple-vertical.vue",
"chars": 5007,
"preview": "<template>\n <div class=\"container\">\n <div ref=\"outerScroll\" class=\"outer-wrapper\">\n <div class=\"outer-content\">"
},
{
"path": "packages/examples/vue/components/nested-scroll/vertical.vue",
"chars": 3388,
"preview": "<template>\n <div class=\"container\">\n <div ref=\"outerScroll\" class=\"outer-wrapper\">\n <div class=\"outer-content\">"
},
{
"path": "packages/examples/vue/components/observe-dom/default.vue",
"chars": 1681,
"preview": "<template>\n <div class=\"observe-dom-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scroll-"
},
{
"path": "packages/examples/vue/components/observe-image/default.vue",
"chars": 1385,
"preview": "<template>\n <div class=\"observe-image-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class=\"scrol"
},
{
"path": "packages/examples/vue/components/picker/double-column.vue",
"chars": 9361,
"preview": "<template>\n <div class=\"container\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"show\">\n "
},
{
"path": "packages/examples/vue/components/picker/linkage-column.vue",
"chars": 11239,
"preview": "<template>\n <div class=\"container\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"show\">\n "
},
{
"path": "packages/examples/vue/components/picker/one-column.vue",
"chars": 8884,
"preview": "<template>\n <div class=\"container\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"show\">\n "
},
{
"path": "packages/examples/vue/components/pulldown/default.vue",
"chars": 3292,
"preview": "<template>\n <div class=\"pulldown\">\n <div\n class=\"pulldown-bswrapper\"\n ref=\"bsWrapper\"\n >\n <div cla"
},
{
"path": "packages/examples/vue/components/pulldown/sina-weibo.vue",
"chars": 3819,
"preview": "<template>\n <div class=\"pulldown-sina\">\n <div\n class=\"pulldown-bswrapper\"\n ref=\"bsWrapper\"\n >\n <di"
},
{
"path": "packages/examples/vue/components/pullup/default.vue",
"chars": 2119,
"preview": "<template>\n <div class=\"pullup\">\n <div ref=\"scroll\" class=\"pullup-wrapper\">\n <div class=\"pullup-content\">\n "
},
{
"path": "packages/examples/vue/components/scrollbar/custom.vue",
"chars": 2173,
"preview": "<template>\n <div class=\"custom-scrollbar-container\">\n <div ref=\"wrapper\" class=\"custom-scrollbar-wrapper\">\n <im"
},
{
"path": "packages/examples/vue/components/scrollbar/horizontal.vue",
"chars": 2203,
"preview": "<template>\n <div class=\"horizontal-scrollbar-container\">\n <div class=\"scroll-wrapper\" ref=\"scroll\">\n <div class"
},
{
"path": "packages/examples/vue/components/scrollbar/mousewheel.vue",
"chars": 1979,
"preview": "<template>\n <div class=\"mousewheel-scrollbar-container\">\n <div ref=\"wrapper\" class=\"custom-scrollbar-wrapper\">\n "
},
{
"path": "packages/examples/vue/components/scrollbar/vertical.vue",
"chars": 954,
"preview": "<template>\n <div class=\"scrollbar\">\n <div ref=\"wrapper\" class=\"scrollbar-wrapper\">\n <ul class=\"scrollbar-conten"
},
{
"path": "packages/examples/vue/components/slide/banner.vue",
"chars": 2910,
"preview": "<template>\n <div class=\"slide-banner\">\n <div class=\"banner-wrapper\">\n <div class=\"slide-banner-wrapper\" ref=\"sl"
},
{
"path": "packages/examples/vue/components/slide/dynamic.vue",
"chars": 3482,
"preview": "<template>\n <div class=\"dynamic-slide-banner\">\n <div class=\"banner-wrapper\">\n <div class=\"slide-banner-wrapper\""
},
{
"path": "packages/examples/vue/components/slide/fullpage.vue",
"chars": 2809,
"preview": "<template>\n <div class=\"slide-fullpage\">\n <div class=\"banner-wrapper\">\n <div class=\"slide-banner-wrapper\" ref=\""
},
{
"path": "packages/examples/vue/components/slide/specified-index.vue",
"chars": 2594,
"preview": "<template>\n <div class=\"slide-specified-index\">\n <div class=\"banner-wrapper\">\n <div class=\"slide-specified-wrap"
},
{
"path": "packages/examples/vue/components/slide/vertical.vue",
"chars": 2359,
"preview": "<template>\n <div class=\"slide-vertical\">\n <div class=\"vertical-wrapper\">\n <div class=\"slide-vertical-wrapper\" r"
},
{
"path": "packages/examples/vue/components/zoom/default.vue",
"chars": 2712,
"preview": "<template>\n <div class=\"zoom-default\">\n <div class=\"zoom-wrapper\" ref=\"zoom\">\n <div class=\"zoom-items\">\n "
},
{
"path": "packages/examples/vue/index.html",
"chars": 854,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>BetterScroll Examples</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\"\n "
},
{
"path": "packages/examples/vue/main.js",
"chars": 336,
"preview": "import '@babel/polyfill'\nimport Vue from 'vue'\nimport router from './router'\nimport App from './App.vue'\n\n// /* eslint-d"
},
{
"path": "packages/examples/vue/pages/compose-entry.vue",
"chars": 1018,
"preview": "<template>\n <div class=\"compose\">\n <ul class=\"example-list\">\n <li\n @click=\"goPage('/compose/pullup-pulld"
},
{
"path": "packages/examples/vue/pages/core-entry.vue",
"chars": 1236,
"preview": "<template>\n <div class=\"core\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/core/defau"
},
{
"path": "packages/examples/vue/pages/form-entry.vue",
"chars": 497,
"preview": "<template>\n <div class=\"core\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/form/texta"
},
{
"path": "packages/examples/vue/pages/indicators-entry.vue",
"chars": 543,
"preview": "<template>\n <div class=\"indicators\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/indi"
},
{
"path": "packages/examples/vue/pages/infinity-entry.vue",
"chars": 209,
"preview": "<template>\n <infinity />\n</template>\n\n<script>\nimport Infinity from 'vue-example/components/infinity/default.vue'\nexpor"
},
{
"path": "packages/examples/vue/pages/mouse-wheel-entry.vue",
"chars": 1268,
"preview": "<template>\n <div class=\"mouse-wheel\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/mou"
},
{
"path": "packages/examples/vue/pages/movable-entry.vue",
"chars": 799,
"preview": "<template>\n <div class=\"movable\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/movable"
},
{
"path": "packages/examples/vue/pages/nested-scroll-entry.vue",
"chars": 952,
"preview": "<template>\n <div class=\"core\">\n <ul class=\"example-list\">\n <li\n class=\"example-item\"\n :key=\"index"
},
{
"path": "packages/examples/vue/pages/observe-dom-entry.vue",
"chars": 186,
"preview": "<template>\n <observe-dom />\n</template>\n\n<script>\nimport ObserveDom from 'vue-example/components/observe-dom/default.vu"
},
{
"path": "packages/examples/vue/pages/observe-image-entry.vue",
"chars": 194,
"preview": "<template>\n <observe-image />\n</template>\n\n<script>\nimport ObserveImage from 'vue-example/components/observe-image/defa"
},
{
"path": "packages/examples/vue/pages/picker-entry.vue",
"chars": 716,
"preview": "<template>\n <div class=\"container\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/picke"
},
{
"path": "packages/examples/vue/pages/pulldown-entry.vue",
"chars": 534,
"preview": "<template>\n <div class=\"container\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/pulld"
},
{
"path": "packages/examples/vue/pages/pullup-entry.vue",
"chars": 201,
"preview": "<template>\n <pullup />\n</template>\n\n<script>\nimport Pullup from 'vue-example/components/pullup/default.vue'\nexport defa"
},
{
"path": "packages/examples/vue/pages/scrollbar-entry.vue",
"chars": 796,
"preview": "<template>\n <div class=\"scroll-bar\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/scro"
},
{
"path": "packages/examples/vue/pages/slide-entry.vue",
"chars": 977,
"preview": "<template>\n <div class=\"slide\">\n <ul class=\"example-list\">\n <li class=\"example-item\" @click=\"goPage('/slide/ban"
},
{
"path": "packages/examples/vue/pages/zoom-entry.vue",
"chars": 198,
"preview": "<template>\n <zoom />\n</template>\n\n<script type=\"text/ecmascript-6\">\n import Zoom from 'vue-example/components/zoom/def"
},
{
"path": "packages/examples/vue/router/index.js",
"chars": 9885,
"preview": "import Vue from 'vue'\nimport Router from 'vue-router'\n\nimport CoreEntry from 'vue-example/pages/core-entry'\nimport Obser"
},
{
"path": "packages/examples/vue-release.sh",
"chars": 189,
"preview": "#!/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 pus"
},
{
"path": "packages/indicators/README.md",
"chars": 613,
"preview": "# @better-scroll/indicators\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/indicators/README_"
},
{
"path": "packages/indicators/README_zh-CN.md",
"chars": 442,
"preview": "# @better-scroll/indicators\n\n指示器,可用来实现放大镜、视觉滚动等效果。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Indica"
},
{
"path": "packages/indicators/package.json",
"chars": 947,
"preview": "{\n \"name\": \"@better-scroll/indicators\",\n \"version\": \"2.5.1\",\n \"description\": \"used as parallax scrolling, magnifier e"
},
{
"path": "packages/indicators/src/__mocks__/indicator.ts",
"chars": 191,
"preview": "const mockIndicator = jest\n .fn()\n .mockImplementation(function IndicatorMockFn(scroll: any, options: any) {\n retur"
},
{
"path": "packages/indicators/src/__tests__/index.spec.ts",
"chars": 2214,
"preview": "import Indicator from '../indicator'\nimport BScroll from '@better-scroll/core'\n\njest.mock('@better-scroll/core')\njest.mo"
},
{
"path": "packages/indicators/src/__tests__/indicator.spec.ts",
"chars": 5034,
"preview": "import Indicator from '../indicator'\nimport BScroll from '@better-scroll/core'\nimport { IndicatorOptions } from '../type"
},
{
"path": "packages/indicators/src/index.ts",
"chars": 1317,
"preview": "import BScroll from '@better-scroll/core'\nimport Indicator from './indicator'\nimport { IndicatorOptions } from './types'"
},
{
"path": "packages/indicators/src/indicator.ts",
"chars": 10819,
"preview": "import BScroll from '@better-scroll/core'\nimport { IndicatorOptions, Ratio, Postion, ValueSign } from './types'\nimport {"
},
{
"path": "packages/indicators/src/types.ts",
"chars": 466,
"preview": "export type Ratio = number | RatioOfDirection\nexport type RatioOfDirection = {\n x: number\n y: number\n}\nexport interfac"
},
{
"path": "packages/infinity/README.md",
"chars": 1120,
"preview": "# @better-scroll/infinity\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/infinity/README_zh-C"
},
{
"path": "packages/infinity/README_zh-CN.md",
"chars": 654,
"preview": "# @better-scroll/infinity\n\n为 BetterScroll 注入上拉加载的能力。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Infi"
},
{
"path": "packages/infinity/package.json",
"chars": 872,
"preview": "{\n \"name\": \"@better-scroll/infinity\",\n \"version\": \"2.5.1\",\n \"description\": \"The ability to inject a infinity load for"
},
{
"path": "packages/infinity/src/DataManager.ts",
"chars": 2568,
"preview": "class ListItem {\n data: any | null\n dom: HTMLElement | null\n tombstone: HTMLElement | null\n width: number\n height: "
},
{
"path": "packages/infinity/src/DomManager.ts",
"chars": 5149,
"preview": "import { pListItem } from './DataManager'\nimport Tombstone from './Tombstone'\nimport { style, cssVendor } from '@better-"
},
{
"path": "packages/infinity/src/IndexCalculator.ts",
"chars": 1809,
"preview": "export const PRE_NUM = 10\nexport const POST_NUM = 30\n\nconst enum DIRECTION {\n UP,\n DOWN,\n}\n\nexport default class Index"
},
{
"path": "packages/infinity/src/Tombstone.ts",
"chars": 1480,
"preview": "import { style } from '@better-scroll/shared-utils'\n\nexport default class Tombstone {\n private cached: Array<HTMLElemen"
},
{
"path": "packages/infinity/src/__tests__/DataManager.spec.ts",
"chars": 1648,
"preview": "import DataManager from '../DataManager'\n\ndescribe('DataManager unit test', () => {\n const NEW_LEN = 10\n let list: Arr"
},
{
"path": "packages/infinity/src/__tests__/IndexCalculator.spec.ts",
"chars": 2015,
"preview": "import IndexCalculator, { PRE_NUM, POST_NUM } from '../IndexCalculator'\n\nimport FakeList from './__utils__/FakeList'\n\nim"
},
{
"path": "packages/infinity/src/__tests__/__utils__/FakeList.ts",
"chars": 1393,
"preview": "import { mockDomOffset } from '@better-scroll/core/src/__tests__/__utils__/layout'\nimport { TOMBSTONE_HEIGHT } from './c"
},
{
"path": "packages/infinity/src/__tests__/__utils__/constans.ts",
"chars": 175,
"preview": "const TOMBSTONE_HEIGHT = 37 // 元素默认的高度\n\nconst WRAPPER_HEIGHT = 370 // 滚动窗口的高度\n\nconst VISIBLE_CNT = 10 // 滚动窗口内能显示的元素\n\nex"
},
{
"path": "packages/infinity/src/index.ts",
"chars": 4634,
"preview": "import BScroll, { Boundary } from '@better-scroll/core'\nimport { Probe, warn } from '@better-scroll/shared-utils'\nimport"
},
{
"path": "packages/mouse-wheel/README.md",
"chars": 450,
"preview": "# @better-scroll/mouse-wheel\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/mouse-wheel/READM"
},
{
"path": "packages/mouse-wheel/README_zh-CN.md",
"chars": 303,
"preview": "# @better-scroll/mouse-wheel\n\n允许鼠标滚轮来操纵滚动行为。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport MouseWheel f"
},
{
"path": "packages/mouse-wheel/package.json",
"chars": 882,
"preview": "{\n \"name\": \"@better-scroll/mouse-wheel\",\n \"version\": \"2.5.1\",\n \"description\": \"support for MouseWheel in PC\",\n \"auth"
},
{
"path": "packages/mouse-wheel/src/__tests__/index.spec.ts",
"chars": 8853,
"preview": "import BScroll from '@better-scroll/core'\njest.mock('@better-scroll/core')\n\nimport MouseWheel from '../index'\nimport { c"
},
{
"path": "packages/mouse-wheel/src/index.ts",
"chars": 8380,
"preview": "import BScroll from '@better-scroll/core'\nimport {\n warn,\n preventDefaultExceptionFn,\n EventRegister,\n EventEmitter,"
},
{
"path": "packages/movable/README.md",
"chars": 327,
"preview": "# @better-scroll/movable\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/movable/README.md)\n\nM"
},
{
"path": "packages/movable/README_zh-CN.md",
"chars": 326,
"preview": "# @better-scroll/movable\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/movable/README_zh-CN."
},
{
"path": "packages/movable/package.json",
"chars": 816,
"preview": "{\n \"name\": \"@better-scroll/movable\",\n \"version\": \"2.5.1\",\n \"description\": \"Movable plugin for BetterScroll\",\n \"autho"
},
{
"path": "packages/movable/src/__tests__/index.spec.ts",
"chars": 3425,
"preview": "import BScroll, { Boundary } from '@better-scroll/core'\njest.mock('@better-scroll/core')\nimport { createDiv } from '@bet"
},
{
"path": "packages/movable/src/index.ts",
"chars": 3508,
"preview": "import BScroll, { Behavior, Boundary } from '@better-scroll/core'\nimport {\n ease,\n EventEmitter,\n ApplyOrder,\n EaseI"
},
{
"path": "packages/movable/src/propertiesConfig.ts",
"chars": 240,
"preview": "const sourcePrefix = 'plugins.movable'\nconst propertiesMap = [\n {\n key: 'putAt',\n name: 'putAt',\n },\n]\nexport de"
},
{
"path": "packages/nested-scroll/README.md",
"chars": 496,
"preview": "# better-scroll\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/nested-scroll/README_zh-CN.md)"
},
{
"path": "packages/nested-scroll/package.json",
"chars": 904,
"preview": "{\n \"name\": \"@better-scroll/nested-scroll\",\n \"version\": \"2.5.1\",\n \"description\": \"make your nested scrolls reconciliat"
},
{
"path": "packages/nested-scroll/src/BScrollFamily.ts",
"chars": 2722,
"preview": "import BScroll from '@better-scroll/core'\nimport { EventEmitter, findIndex } from '@better-scroll/shared-utils'\n\n// seco"
},
{
"path": "packages/nested-scroll/src/__tests__/index.spec.ts",
"chars": 11605,
"preview": "import BScroll from '@better-scroll/core'\njest.mock('@better-scroll/core')\n\nimport NestedScroll, { DEFAUL_GROUP_ID } fro"
},
{
"path": "packages/nested-scroll/src/index.ts",
"chars": 10757,
"preview": "import BScroll, { MountedBScrollHTMLElement } from '@better-scroll/core'\nimport {\n Direction,\n EventEmitter,\n extend,"
},
{
"path": "packages/nested-scroll/src/propertiesConfig.ts",
"chars": 271,
"preview": "const sourcePrefix = 'plugins.nestedScroll'\n\nconst propertiesMap = [\n {\n key: 'purgeNestedScroll',\n name: 'purgeN"
},
{
"path": "packages/observe-dom/README.md",
"chars": 637,
"preview": "# @better-scroll/observe-dom\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/observe-dom/READM"
},
{
"path": "packages/observe-dom/README_zh-CN.md",
"chars": 387,
"preview": "# @better-scroll/observe-dom\n\n动态计算 BetterScroll 的可滚动高度或者宽度,你并不需要自己在高度或者宽度发生变化的时候,手动调用 `refresh()` 方法。插件通过 `MutationObser"
},
{
"path": "packages/observe-dom/package.json",
"chars": 909,
"preview": "{\n \"name\": \"@better-scroll/observe-dom\",\n \"version\": \"2.5.1\",\n \"description\": \"Dynamic recalculating container's size"
},
{
"path": "packages/observe-dom/src/__tests__/index.spec.ts",
"chars": 4934,
"preview": "import BScroll from '@better-scroll/core'\nimport ObserveDOM from '../index'\nimport {\n mockDomOffset,\n CustomHTMLDivEle"
},
{
"path": "packages/observe-dom/src/index.ts",
"chars": 4364,
"preview": "import BScroll from '@better-scroll/core'\nimport { getRect, EventEmitter } from '@better-scroll/shared-utils'\n\ndeclare m"
},
{
"path": "packages/observe-image/README.md",
"chars": 430,
"preview": "# @better-scroll/observe-image\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/observe-image/R"
},
{
"path": "packages/observe-image/README_zh-CN.md",
"chars": 353,
"preview": "# @better-scroll/observe-image\n\n当探测到 BetterScroll content DOM 的子元素是 image 标签,并且加载成功或者失败的时候,自动调用 `refresh()` 方法重新计算可滚动的高度"
},
{
"path": "packages/observe-image/package.json",
"chars": 871,
"preview": "{\n \"name\": \"@better-scroll/observe-image\",\n \"version\": \"2.5.1\",\n \"description\": \"Observe image loading for BetterScro"
},
{
"path": "packages/observe-image/src/__tests__/index.spec.ts",
"chars": 1395,
"preview": "import BScroll from '@better-scroll/core'\nimport ObserveImage from '../index'\nimport { createEvent } from '@better-scrol"
},
{
"path": "packages/observe-image/src/index.ts",
"chars": 1990,
"preview": "import BScroll from '@better-scroll/core'\nimport { EventRegister, extend } from '@better-scroll/shared-utils'\n\nexport ty"
},
{
"path": "packages/pull-down/README.md",
"chars": 387,
"preview": "# @better-scroll/pull-down\n\n[中文文档](https://github.com/ustbhuangyi/better-scroll/blob/master/packages/pull-down/README_zh"
},
{
"path": "packages/pull-down/README_zh-CN.md",
"chars": 249,
"preview": "# @better-scroll/pull-down\n\n为 BetterScroll 注入下拉刷新的能力。\n\n## 使用\n\n```js\nimport BScroll from '@better-scroll/core'\nimport Pul"
},
{
"path": "packages/pull-down/package.json",
"chars": 917,
"preview": "{\n \"name\": \"@better-scroll/pull-down\",\n \"version\": \"2.5.1\",\n \"description\": \"pull down to refresh, behave likes App l"
}
]
// ... and 280 more files (download for full content)
About this extraction
This page contains the full source code of the ustbhuangyi/better-scroll GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 480 files (1.3 MB), approximately 379.5k tokens, and a symbol index with 816 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.