├── .eslintignore
├── .eslintrc.js
├── .flowconfig
├── .github
├── FUNDING.yml
└── workflows
│ ├── codeql.yml
│ ├── gh-pages.yml
│ ├── nodejs-ci.yml
│ └── semantic-release-dry.yml
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── karma.conf.js
├── package-lock.json
├── package.json
├── scripts
├── gulpfile.js
└── proxyDirectories.js
├── src
├── DOMMouseMoveTracker.ts
├── PointerMoveTracker.ts
├── WheelHandler.ts
├── addClass.ts
├── addStyle.ts
├── canUseDOM.ts
├── cancelAnimationFramePolyfill.ts
├── contains.ts
├── getAnimationEnd.ts
├── getContainer.ts
├── getHeight.ts
├── getOffset.ts
├── getOffsetParent.ts
├── getPosition.ts
├── getScrollbarSize.ts
├── getStyle.ts
├── getTransitionEnd.ts
├── getTransitionProperties.ts
├── getWidth.ts
├── getWindow.ts
├── hasClass.ts
├── index.ts
├── isFocusable.ts
├── isOverflowing.ts
├── nodeName.ts
├── off.ts
├── on.ts
├── ownerDocument.ts
├── ownerWindow.ts
├── removeClass.ts
├── removeStyle.ts
├── requestAnimationFramePolyfill.ts
├── scrollLeft.ts
├── scrollTop.ts
├── toggleClass.ts
├── translateDOMPositionXY.ts
└── utils
│ ├── BrowserSupportCore.ts
│ ├── UserAgent.ts
│ ├── camelize.ts
│ ├── camelizeStyleName.ts
│ ├── debounce.ts
│ ├── emptyFunction.ts
│ ├── getComputedStyle.ts
│ ├── getGlobal.ts
│ ├── getVendorPrefixedName.ts
│ ├── hyphenateStyleName.ts
│ ├── isEventSupported.ts
│ ├── normalizeWheel.ts
│ ├── stringFormatter.ts
│ └── throttle.ts
├── test
├── PointerMoveTrackerSpec.js
├── WheelHandlerSpec.js
├── classSpec.js
├── eventSpec.js
├── html
│ ├── PointerMoveTracker.html
│ ├── WheelHandler.html
│ ├── class.html
│ ├── events.html
│ ├── query.html
│ └── style.html
├── index.js
├── querySpec.js
├── styleSpec.js
└── utilsSpec.js
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | bin
2 | build
3 | lib
4 | tools
5 | node_modules
6 | coverage
7 | /.git
8 | karma.conf.js
9 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const OFF = 0;
2 | const WARNING = 1;
3 | const ERROR = 2;
4 |
5 | module.exports = {
6 | env: {
7 | browser: true,
8 | es6: true
9 | },
10 | parser: '@typescript-eslint/parser',
11 | extends: [
12 | 'plugin:@typescript-eslint/recommended',
13 | 'prettier/@typescript-eslint',
14 | 'plugin:prettier/recommended'
15 | ],
16 | parserOptions: {},
17 | plugins: ['@typescript-eslint'],
18 | rules: {
19 | quotes: [ERROR, 'single'],
20 | semi: [ERROR, 'always'],
21 | 'space-infix-ops': ERROR,
22 | 'prefer-spread': ERROR,
23 | 'no-multi-spaces': ERROR,
24 | 'class-methods-use-this': WARNING,
25 | 'arrow-parens': [ERROR, 'as-needed'],
26 | '@typescript-eslint/no-unused-vars': ERROR,
27 | '@typescript-eslint/no-explicit-any': OFF,
28 | '@typescript-eslint/explicit-function-return-type': OFF,
29 | '@typescript-eslint/explicit-member-accessibility': OFF,
30 | '@typescript-eslint/no-namespace': OFF,
31 | '@typescript-eslint/explicit-module-boundary-types': OFF,
32 | '@typescript-eslint/ban-ts-comment': OFF,
33 | '@typescript-eslint/no-empty-function': OFF
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/build/.*
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 |
10 | [options]
11 |
12 | [strict]
13 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: rsuite
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 | schedule:
9 | - cron: "5 11 * * 6"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ javascript ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v2
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v2
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v2
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - docs/typedoc
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-20.04
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Setup Node.js
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: 'lts/*'
21 |
22 | - name: 📦 Install dependencies
23 | run: npm install
24 |
25 | - name: 📖 Generate TypeDoc docs
26 | run: npm run docs:generate
27 |
28 | - name: 🚀 Deploy to GitHub Pages
29 | uses: peaceiris/actions-gh-pages@v3
30 | with:
31 | github_token: ${{ secrets.GITHUB_TOKEN }}
32 | publish_dir: ./docs
33 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs-ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 | pull_request:
11 | branches:
12 | - master
13 |
14 | jobs:
15 | test:
16 | name: 'Test'
17 |
18 | runs-on: ubuntu-latest
19 |
20 | strategy:
21 | matrix:
22 | browser: [ChromeCi, Firefox]
23 |
24 | steps:
25 | - uses: actions/checkout@v2
26 |
27 | - name: Setup kernel, increase watchers
28 | run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
29 |
30 | - name: Use Node.js 12.x
31 | uses: actions/setup-node@v1
32 | with:
33 | node-version: '12.x'
34 | - name: Cache Node.js modules
35 | uses: actions/cache@v1
36 | with:
37 | path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
38 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
39 | restore-keys: |
40 | ${{ runner.OS }}-node-
41 | ${{ runner.OS }}-
42 | - name: Install dependencies
43 | run: npm ci
44 | - name: Run headless tests
45 | run: xvfb-run --auto-servernum npm test
46 | env:
47 | CI: true
48 | BROWSER: ${{ matrix.browser }}
49 |
50 |
51 | release:
52 | name: Release
53 | needs: test
54 | runs-on: ubuntu-latest
55 | steps:
56 | - name: Checkout
57 | uses: actions/checkout@v2
58 | with:
59 | fetch-depth: 0
60 | - name: Setup Node.js
61 | uses: actions/setup-node@v1
62 | with:
63 | node-version: 16
64 | - name: Install dependencies
65 | run: npm ci
66 | - name: 🔨 Build package
67 | run: npm run build
68 | - name: 🚀 Semantic Release
69 | env:
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
72 | run: npx semantic-release
--------------------------------------------------------------------------------
/.github/workflows/semantic-release-dry.yml:
--------------------------------------------------------------------------------
1 | # This workflow runs semantic-release in dry mode to see whether changes in
2 | # semantic-release configurations are working as expected
3 |
4 | name: semantic-release (dryRun)
5 |
6 | on:
7 | push:
8 | branches:
9 | - 'ci/semantic-release'
10 |
11 | jobs:
12 | release-dry:
13 | name: Release (dry)
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v2
18 | with:
19 | fetch-depth: 0
20 | - name: Setup Node.js
21 | uses: actions/setup-node@v1
22 | with:
23 | node-version: 16
24 | - name: Install dependencies
25 | run: npm ci
26 | - name: 🔨 Build package
27 | run: npm run build
28 | - name: 🚀 Semantic Release (dry)
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
32 | run: npx semantic-release --dry-run --branches 'ci/semantic-release'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | .DS_Store
40 | dist/
41 | docs/
42 | lib/
43 | es/
44 | yarn.lock
45 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist/
2 | src/
3 | test/
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | tabWidth: 2,
4 | singleQuote: true,
5 | arrowParens: 'avoid',
6 | trailingComma: 'none'
7 | };
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [3.0.1](https://github.com/rsuite/dom-lib/compare/3.0.0...3.0.1) (2021-12-16)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * **getContainer:** container can be null ([#27](https://github.com/rsuite/dom-lib/issues/27)) ([121ac6d](https://github.com/rsuite/dom-lib/commit/121ac6dec305d0c9193e5e65e31274520595fcde))
7 |
8 |
9 |
10 | # [3.0.0](https://github.com/rsuite/dom-lib/compare/2.1.0...3.0.0) (2021-11-02)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * **animation:** remove webkitRequestAnimationFrame ([#22](https://github.com/rsuite/dom-lib/issues/22)) ([69dbbb1](https://github.com/rsuite/dom-lib/commit/69dbbb17a54cf1dfb3a7dee65e6ce4069c578982))
16 | * **deps:** add @babel/runtime ([#25](https://github.com/rsuite/dom-lib/issues/25)) ([b40f9e5](https://github.com/rsuite/dom-lib/commit/b40f9e5e2d965115c1fa8f30e294cc4665e82826))
17 |
18 |
19 | ### Features
20 |
21 | * **query:** add support for isFocusable ([#23](https://github.com/rsuite/dom-lib/issues/23)) ([eee920a](https://github.com/rsuite/dom-lib/commit/eee920ac0efe5670762734d16c14c2117c48f053))
22 |
23 |
24 |
25 | # 2.1.0
26 |
27 | - feat(getPosition): support keep margin (#20)
28 |
29 | # 2.0.2
30 |
31 | - Fix typescript type definition
32 |
33 | # 2.0.1
34 |
35 | - fix: Update type definition
36 |
37 | # 2.0.0
38 |
39 | - refactor: Migrate from flow to typescript
40 |
41 | # 1.3.0
42 |
43 | - Add animation events helper
44 |
45 | # 1.2.1
46 |
47 | - Add parameter enable3DTransform for translateDOMPositionXY
48 |
49 | # 1.2.0
50 |
51 | - Added support for ESM
52 |
53 | # 1.1.0
54 |
55 | - Support Server-side Rendering
56 | - Upgrade to Bebel 7
57 |
58 | # 0.2.3
59 |
60 | > 2017-06-26
61 |
62 | - Added `WheelHandler`
63 | - Added `translateDOMPositionXY`
64 |
65 | # 0.2.1
66 |
67 | - Added `throttle` and `debounce`
68 | - Change `space` to 2
69 |
70 | # 0.2.0
71 |
72 | - All changes to support es2015
73 | - Added test case
74 |
75 | # 0.1.1
76 |
77 | > 2017-03-31
78 |
79 | - Feature: Added support react 15.\*
80 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) HYPERS, Inc. and its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DOM helper library
2 |
3 | [](https://github.com/rsuite/dom-lib/actions)
4 | [](https://www.npmjs.com/package/dom-lib)
5 |
6 | Click the "Exports" link in the sidebar to see a complete list of everything in the package.
7 |
8 | ## Install
9 |
10 | ```
11 | npm install dom-lib --save
12 | ```
13 |
14 | ## Usage
15 |
16 | ```js
17 | import addClass from 'dom-lib/addClass';
18 |
19 | addClass(element, 'foo');
20 | // output:
21 | ```
22 |
23 | ## API
24 |
25 | Class
26 |
27 | ```typescript
28 | hasClass: (node: Element, className: string) => boolean;
29 | addClass: (node: Element, className: string) => Element;
30 | removeClass: (node: Element, className: string) => Element;
31 | toggleClass: (node: Element, className: string) => Element;
32 | ```
33 |
34 | Style
35 |
36 | ```typescript
37 | getStyle: (node: Element, property: string) => string;
38 | getStyle: (node: Element) => Object;
39 |
40 | removeStyle: (node: Element, property: string) => void;
41 | removeStyle: (node: Element, propertys: Array) => void;
42 |
43 | addStyle: (node: Element, property: string, value: string) => void;
44 | addStyle: (node: Element, style: Object) => void;
45 | ```
46 |
47 | Events
48 |
49 | ```typescript
50 | on: (target: Element, eventName: string, listener: Function, capture: boolean = false) => {
51 | off: Function;
52 | };
53 | off: (target: Element, eventName: string, listener: Function, capture: boolean = false) =>
54 | void;
55 | ```
56 |
57 | Query
58 |
59 | ```typescript
60 | activeElement: () => Element;
61 | getHeight: (node: Element, client: Element) => number;
62 | getWidth: (node: Element, client: Element) => number;
63 | getOffset: (node: Element) => Object;
64 | getOffsetParent: (node: Element) => Object;
65 | getPosition: (node: Element, offsetParent) => Object;
66 | getWindow: (node: Element) => String;
67 | nodeName: (node: Element) => String;
68 | ownerDocument: (node: Element) => Object;
69 | ownerWindow: (node: Element) => Object;
70 | contains: (context: Element, node: Element) => boolean;
71 | scrollLeft: (node: Element) => number;
72 | scrollTop: (node: Element) => number;
73 | isFocusable: (node: Element) => boolean;
74 | ```
75 |
76 | Utils
77 |
78 | ```typescript
79 | scrollLeft: (node: Element)=> number;
80 | scrollLeft: (node: Element, val: number)=> void;
81 |
82 | scrollTop: (node: Element)=> number;
83 | scrollTop: (node: Element, val: number) => void;
84 | ```
85 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api, options) => {
2 | const { NODE_ENV } = options || process.env;
3 | const modules = NODE_ENV === 'esm' ? false : 'commonjs';
4 |
5 | if (api) {
6 | api.cache(() => NODE_ENV);
7 | }
8 |
9 | const plugins = [
10 | ['@babel/plugin-proposal-class-properties', { loose: true }],
11 | '@babel/plugin-proposal-optional-chaining',
12 | '@babel/plugin-proposal-nullish-coalescing-operator',
13 | '@babel/plugin-proposal-export-namespace-from',
14 | '@babel/plugin-proposal-export-default-from',
15 | ['@babel/plugin-transform-runtime', { useESModules: !modules }]
16 | ];
17 |
18 | if (NODE_ENV !== 'test') {
19 | plugins.push('babel-plugin-add-import-extension');
20 | }
21 |
22 | if (modules) {
23 | plugins.push('add-module-exports');
24 | }
25 |
26 | return {
27 | presets: [['@babel/preset-env', { modules, loose: true }], '@babel/preset-typescript'],
28 | plugins
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 执行全部测试用例: npm run tdd
3 | * 执行单个组件的测试用例: M=BreadcrumbItem npm run tdd
4 | */
5 |
6 | const webpackConfig = {
7 | output: {
8 | pathinfo: true
9 | },
10 | mode: 'development',
11 | resolve: {
12 | extensions: ['.ts', '.tsx', '.js']
13 | },
14 | devtool: 'inline-source-map',
15 | entry: __dirname + '/test/index.js',
16 | module: {
17 | rules: [
18 | {
19 | test: /\.tsx?$/,
20 | use: ['babel-loader?babelrc'],
21 | exclude: /node_modules/
22 | }
23 | ]
24 | }
25 | };
26 |
27 | module.exports = config => {
28 | const { env } = process;
29 |
30 | config.set({
31 | basePath: '',
32 | frameworks: ['mocha', 'sinon-chai'],
33 | reporters: ['mocha'],
34 | files: ['test/html/*.html', 'test/index.js'],
35 | port: 9876,
36 | colors: true,
37 | autoWatch: true,
38 | logLevel: config.LOG_INFO,
39 |
40 | browsers: env.BROWSER ? env.BROWSER.split(',') : ['Chrome'],
41 | customLaunchers: {
42 | ChromeCi: {
43 | base: 'Chrome',
44 | flags: ['--no-sandbox']
45 | }
46 | },
47 | preprocessors: {
48 | 'test/html/*.html': 'html2js',
49 | 'test/index.js': ['webpack', 'sourcemap']
50 | },
51 | webpack: webpackConfig,
52 | webpackMiddleware: {
53 | noInfo: true
54 | }
55 | });
56 | };
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dom-lib",
3 | "version": "0.0.0-development",
4 | "description": "DOM helper library",
5 | "main": "lib/cjs/index.js",
6 | "module": "lib/esm/index.js",
7 | "typings": "lib/esm/index.d.ts",
8 | "directories": {
9 | "lib": "cjs/"
10 | },
11 | "keywords": [
12 | "dom-library",
13 | "dom"
14 | ],
15 | "scripts": {
16 | "build": "npm run build:gulp && npm run build:types",
17 | "build:gulp": "gulp build --gulpfile scripts/gulpfile.js",
18 | "build:types": "npx tsc --emitDeclarationOnly --outDir lib/cjs && npx tsc --emitDeclarationOnly --outDir lib/esm",
19 | "tdd": "NODE_ENV=test karma start",
20 | "docs:generate": "typedoc src/index.ts",
21 | "lint": "eslint src/**/*.ts",
22 | "test": "npm run lint && NODE_ENV=test karma start --single-run",
23 | "prepublishOnly": "npm run build",
24 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
25 | },
26 | "author": "Simon Guo ",
27 | "license": "MIT",
28 | "repository": {
29 | "type": "git",
30 | "url": "https:git@github.com:rsuite/dom-lib.git"
31 | },
32 | "files": [
33 | "CHANGELOG.md",
34 | "lib",
35 | "es"
36 | ],
37 | "devDependencies": {
38 | "@babel/cli": "^7.7.0",
39 | "@babel/core": "^7.7.2",
40 | "@babel/plugin-proposal-class-properties": "^7.0.0",
41 | "@babel/plugin-proposal-export-default-from": "^7.0.0",
42 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
43 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
44 | "@babel/plugin-proposal-optional-chaining": "^7.6.0",
45 | "@babel/plugin-syntax-dynamic-import": "^7.0.0",
46 | "@babel/plugin-transform-proto-to-assign": "^7.0.0",
47 | "@babel/plugin-transform-runtime": "^7.1.0",
48 | "@babel/preset-env": "^7.7.1",
49 | "@babel/preset-typescript": "^7.12.7",
50 | "@typescript-eslint/eslint-plugin": "^4.11.1",
51 | "@typescript-eslint/parser": "^4.11.1",
52 | "babel-eslint": "^10.0.3",
53 | "babel-loader": "^8.0.0",
54 | "babel-plugin-add-import-extension": "^1.6.0",
55 | "babel-plugin-add-module-exports": "^1.0.4",
56 | "brfs": "^1.5.0",
57 | "chai": "^3.5.0",
58 | "conventional-changelog-cli": "^2.1.1",
59 | "del": "^6.0.0",
60 | "es5-shim": "^4.1.14",
61 | "eslint": "^6.7.2",
62 | "eslint-config-prettier": "^6.11.0",
63 | "eslint-plugin-babel": "^5.3.0",
64 | "eslint-plugin-import": "^2.19.1",
65 | "eslint-plugin-prettier": "^3.3.1",
66 | "gulp": "^4.0.2",
67 | "gulp-babel": "^8.0.0",
68 | "jquery": "^3.2.1",
69 | "karma": "^6.3.14",
70 | "karma-chrome-launcher": "^2.2.0",
71 | "karma-cli": "^2.0.0",
72 | "karma-coverage": "^1.1.1",
73 | "karma-es5-shim": "^0.0.4",
74 | "karma-firefox-launcher": "^1.0.1",
75 | "karma-html2js-preprocessor": "^1.1.0",
76 | "karma-mocha": "^1.1.1",
77 | "karma-mocha-reporter": "^2.0.4",
78 | "karma-safari-launcher": "^1.0.0",
79 | "karma-sinon-chai": "^1.2.2",
80 | "karma-sourcemap-loader": "^0.3.7",
81 | "karma-webpack": "^4.0.0-beta.0",
82 | "mocha": "^10.1.0",
83 | "prettier": "^2.2.1",
84 | "semantic-release": "^19.0.2",
85 | "simulant": "^0.2.2",
86 | "sinon": "^2.1.0",
87 | "sinon-chai": "^2.9.0",
88 | "style-loader": "^0.13.1",
89 | "typedoc": "^0.22.13",
90 | "typescript": "^4.1.3",
91 | "webpack": "^4.27.1",
92 | "webpack-cli": "^3.1.2"
93 | },
94 | "dependencies": {
95 | "@babel/runtime": "^7.20.0"
96 | },
97 | "release": {
98 | "tagFormat": "${version}",
99 | "plugins": [
100 | "@semantic-release/commit-analyzer",
101 | "@semantic-release/release-notes-generator",
102 | "@semantic-release/github",
103 | [
104 | "@semantic-release/npm",
105 | {
106 | "pkgRoot": "lib"
107 | }
108 | ]
109 | ]
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/scripts/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const fs = require('fs');
3 | const util = require('util');
4 | const del = require('del');
5 | const path = require('path');
6 | const babel = require('gulp-babel');
7 | const gulp = require('gulp');
8 | const babelrc = require('../babel.config');
9 | const { default: proxyDirectories } = require('./proxyDirectories');
10 | const pkg = require('../package.json');
11 |
12 | const writeFile = util.promisify(fs.writeFile);
13 | const srcRoot = path.join(__dirname, '../src');
14 | const libRoot = path.join(__dirname, '../lib');
15 |
16 | const esmRoot = path.join(libRoot, 'esm');
17 | const cjsRoot = path.join(libRoot, 'cjs');
18 | const tsSources = [`${srcRoot}/**/*.ts`];
19 |
20 | function clean(done) {
21 | del.sync([libRoot], { force: true });
22 | done();
23 | }
24 |
25 | function buildCjs() {
26 | return gulp.src(tsSources).pipe(babel(babelrc())).pipe(gulp.dest(cjsRoot));
27 | }
28 |
29 | function buildEsm() {
30 | return gulp
31 | .src(tsSources)
32 | .pipe(
33 | babel(
34 | babelrc(null, {
35 | NODE_ENV: 'esm'
36 | })
37 | )
38 | )
39 | .pipe(gulp.dest(esmRoot));
40 | }
41 |
42 | function buildDirectories(done) {
43 | proxyDirectories().then(() => {
44 | done();
45 | });
46 | }
47 |
48 | function copyDocs() {
49 | return gulp.src(['../README.md', '../CHANGELOG.md', '../LICENSE']).pipe(gulp.dest(libRoot));
50 | }
51 |
52 | function createPkgFile(done) {
53 | delete pkg.devDependencies;
54 | delete pkg.files;
55 |
56 | pkg.main = 'cjs/index.js';
57 | pkg.module = 'esm/index.js';
58 | pkg.typings = 'esm/index.d.ts';
59 | pkg.scripts = {
60 | //prepublishOnly: '../node_modules/mocha/bin/mocha ../test/validateBuilds.js'
61 | };
62 |
63 | writeFile(`${libRoot}/package.json`, JSON.stringify(pkg, null, 2) + '\n')
64 | .then(() => {
65 | done();
66 | })
67 | .catch(err => {
68 | if (err) console.error(err.toString());
69 | });
70 | }
71 |
72 | exports.build = gulp.series(
73 | clean,
74 | gulp.parallel(buildCjs, buildEsm),
75 | gulp.parallel(copyDocs, createPkgFile),
76 | buildDirectories
77 | );
78 |
--------------------------------------------------------------------------------
/scripts/proxyDirectories.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create a package.json for each directory and proxy to CJS and ESM files.
3 | * Can make importing a component easier.
4 | *
5 | * E.g:
6 | * import addStyle from 'dom-lib/addStyle';
7 | */
8 |
9 | /* eslint-disable @typescript-eslint/no-var-requires */
10 |
11 | const path = require('path');
12 | const fs = require('fs');
13 | const util = require('util');
14 |
15 | const mkDir = util.promisify(fs.mkdir);
16 | const writeFile = util.promisify(fs.writeFile);
17 | const srcRoot = path.join(__dirname, '../src');
18 | const libRoot = path.join(__dirname, '../lib');
19 |
20 | function findResources(options) {
21 | const { dir = srcRoot, ignores = [], isFile } = options;
22 | const resources = [];
23 | fs.readdirSync(dir).forEach(item => {
24 | const itemPath = path.resolve(dir, item);
25 | const pathname = itemPath.replace(/[a-z0-9\-]*\//gi, '').replace('.ts', '');
26 |
27 | if (fs.statSync(itemPath).isDirectory()) {
28 | resources.push(pathname);
29 | }
30 | if (isFile && fs.statSync(itemPath).isFile()) {
31 | resources.push(pathname);
32 | }
33 | });
34 |
35 | //console.log(resources);
36 |
37 | return resources.filter(item => !ignores.includes(item));
38 | }
39 |
40 | function proxyResource(options) {
41 | const { pkgName = 'rsuite', name, file, filePath = '../' } = options;
42 | const proxyPkg = {
43 | name: `${pkgName}/${name}`,
44 | private: true,
45 | main: `${filePath}/cjs/${file}.js`,
46 | module: `${filePath}/esm/${file}.js`,
47 | types: `${filePath}/esm/${file}.d.ts`
48 | };
49 |
50 | return JSON.stringify(proxyPkg, null, 2) + '\n';
51 | }
52 |
53 | async function writePkgFile(options) {
54 | const { resources = [], pkgName = 'dom-lib' } = options;
55 | await Promise.all(
56 | resources.map(async item => {
57 | const name = item;
58 | const file = `${item}`;
59 | const filePath = '..';
60 | const proxyDir = path.join(libRoot, name);
61 | await mkDir(libRoot).catch(() => {});
62 | await mkDir(proxyDir).catch(() => {});
63 | await writeFile(
64 | `${proxyDir}/package.json`,
65 | proxyResource({ pkgName, name, file, filePath })
66 | ).catch(err => {
67 | if (err) console.error(err.toString());
68 | });
69 | })
70 | );
71 | }
72 |
73 | /**
74 | * Use package.json file to proxy component directory
75 | *
76 | * outputs:
77 | * lib/addClass/package.json
78 | * lib/addStyle/package.json
79 | * .....
80 | */
81 | async function proxyComponent() {
82 | const resources = findResources({ dir: srcRoot, isFile: true, ignores: ['utils'] });
83 |
84 | await writePkgFile({ resources });
85 | }
86 |
87 | async function proxy() {
88 | await proxyComponent();
89 | }
90 |
91 | module.exports.findResources = findResources;
92 | module.exports.default = proxy;
93 |
--------------------------------------------------------------------------------
/src/DOMMouseMoveTracker.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Source code reference from:
3 | * https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/dom/DOMMouseMoveTracker.js
4 | */
5 |
6 | import on from './on';
7 | import cancelAnimationFramePolyfill from './cancelAnimationFramePolyfill';
8 | import requestAnimationFramePolyfill from './requestAnimationFramePolyfill';
9 |
10 | /**
11 | * Mouse drag tracker, get the coordinate value where the mouse moves in time.
12 | *
13 | * ```typescript
14 | * const tracker = new DOMMouseMoveTracker(
15 | * onMove:(deltaX: number, deltaY: number, moveEvent: Object) => void,
16 | * onMoveEnd:() => void,
17 | * container: HTMLElement
18 | * );
19 | * ```
20 | */
21 | class DOMMouseMoveTracker {
22 | isDraggingStatus = false;
23 | animationFrameID = null;
24 | domNode: Element;
25 | onMove = null;
26 | onMoveEnd = null;
27 | eventMoveToken = null;
28 | eventUpToken = null;
29 | moveEvent = null;
30 | deltaX = 0;
31 | deltaY = 0;
32 | x = 0;
33 | y = 0;
34 |
35 | /**
36 | * onMove is the callback that will be called on every mouse move.
37 | * onMoveEnd is called on mouse up when movement has ended.
38 | */
39 | constructor(onMove: (x: number, y: number, e) => void, onMoveEnd: (e) => void, domNode: Element) {
40 | this.domNode = domNode;
41 | this.onMove = onMove;
42 | this.onMoveEnd = onMoveEnd;
43 | }
44 |
45 | /**
46 | * This is to set up the listeners for listening to mouse move
47 | * and mouse up signaling the movement has ended. Please note that these
48 | * listeners are added at the document.body level. It takes in an event
49 | * in order to grab inital state.
50 | */
51 | captureMouseMoves(event) {
52 | if (!this.eventMoveToken && !this.eventUpToken) {
53 | this.eventMoveToken = on(this.domNode, 'mousemove', this.onMouseMove);
54 | this.eventUpToken = on(this.domNode, 'mouseup', this.onMouseUp);
55 | }
56 |
57 | if (!this.isDraggingStatus) {
58 | this.deltaX = 0;
59 | this.deltaY = 0;
60 | this.isDraggingStatus = true;
61 | this.x = event.clientX;
62 | this.y = event.clientY;
63 | }
64 |
65 | event.preventDefault();
66 | }
67 |
68 | /**
69 | * These releases all of the listeners on document.body.
70 | */
71 | releaseMouseMoves() {
72 | if (this.eventMoveToken) {
73 | this.eventMoveToken.off();
74 | this.eventMoveToken = null;
75 | }
76 |
77 | if (this.eventUpToken) {
78 | this.eventUpToken.off();
79 | this.eventUpToken = null;
80 | }
81 |
82 | if (this.animationFrameID !== null) {
83 | cancelAnimationFramePolyfill(this.animationFrameID);
84 | this.animationFrameID = null;
85 | }
86 |
87 | if (this.isDraggingStatus) {
88 | this.isDraggingStatus = false;
89 | this.x = 0;
90 | this.y = 0;
91 | }
92 | }
93 |
94 | /**
95 | * Returns whether or not if the mouse movement is being tracked.
96 | */
97 | isDragging = () => this.isDraggingStatus;
98 |
99 | /**
100 | * Calls onMove passed into constructor and updates internal state.
101 | */
102 | onMouseMove = event => {
103 | const x = (event as MouseEvent).clientX;
104 | const y = (event as MouseEvent).clientY;
105 |
106 | this.deltaX += x - this.x;
107 | this.deltaY += y - this.y;
108 |
109 | if (this.animationFrameID === null) {
110 | // The mouse may move faster then the animation frame does.
111 | // Use `requestAnimationFramePolyfill` to avoid over-updating.
112 | this.animationFrameID = requestAnimationFramePolyfill(this.didMouseMove);
113 | }
114 |
115 | this.x = x;
116 | this.y = y;
117 |
118 | this.moveEvent = event;
119 | event.preventDefault();
120 | };
121 |
122 | didMouseMove = () => {
123 | this.animationFrameID = null;
124 | this.onMove(this.deltaX, this.deltaY, this.moveEvent);
125 |
126 | this.deltaX = 0;
127 | this.deltaY = 0;
128 | };
129 | /**
130 | * Calls onMoveEnd passed into constructor and updates internal state.
131 | */
132 | onMouseUp = event => {
133 | if (this.animationFrameID) {
134 | this.didMouseMove();
135 | }
136 | this.onMoveEnd && this.onMoveEnd(event);
137 | };
138 | }
139 |
140 | export default DOMMouseMoveTracker;
141 |
--------------------------------------------------------------------------------
/src/PointerMoveTracker.ts:
--------------------------------------------------------------------------------
1 | import on from './on';
2 | import isEventSupported from './utils/isEventSupported';
3 |
4 | interface PointerMoveTrackerOptions {
5 | useTouchEvent?: boolean;
6 | onMove: (x: number, y: number, event: MouseEvent | TouchEvent) => void;
7 | onMoveEnd: (event: MouseEvent | TouchEvent) => void;
8 | }
9 |
10 | /**
11 | * Track mouse/touch events for a given element.
12 | */
13 | export default class PointerMoveTracker {
14 | isDragStatus = false;
15 | useTouchEvent = true;
16 | animationFrameID = null;
17 | domNode: Element;
18 | onMove = null;
19 | onMoveEnd = null;
20 | eventMoveToken = null;
21 | eventUpToken = null;
22 | moveEvent = null;
23 | deltaX = 0;
24 | deltaY = 0;
25 | x = 0;
26 | y = 0;
27 |
28 | /**
29 | * onMove is the callback that will be called on every mouse move.
30 | * onMoveEnd is called on mouse up when movement has ended.
31 | */
32 | constructor(
33 | domNode: Element,
34 | { onMove, onMoveEnd, useTouchEvent = true }: PointerMoveTrackerOptions
35 | ) {
36 | this.domNode = domNode;
37 | this.onMove = onMove;
38 | this.onMoveEnd = onMoveEnd;
39 | this.useTouchEvent = useTouchEvent;
40 | }
41 |
42 | isSupportTouchEvent() {
43 | return this.useTouchEvent && isEventSupported('touchstart');
44 | }
45 |
46 | getClientX(event: TouchEvent | MouseEvent) {
47 | return this.isSupportTouchEvent()
48 | ? (event as TouchEvent).touches?.[0].clientX
49 | : (event as MouseEvent).clientX;
50 | }
51 |
52 | getClientY(event: TouchEvent | MouseEvent) {
53 | return this.isSupportTouchEvent()
54 | ? (event as TouchEvent).touches?.[0].clientY
55 | : (event as MouseEvent).clientY;
56 | }
57 |
58 | /**
59 | * This is to set up the listeners for listening to mouse move
60 | * and mouse up signaling the movement has ended. Please note that these
61 | * listeners are added at the document.body level. It takes in an event
62 | * in order to grab inital state.
63 | */
64 | captureMoves(event) {
65 | if (!this.eventMoveToken && !this.eventUpToken) {
66 | if (this.isSupportTouchEvent()) {
67 | this.eventMoveToken = on(this.domNode, 'touchmove', this.onDragMove, { passive: false });
68 | this.eventUpToken = on(this.domNode, 'touchend', this.onDragUp, { passive: false });
69 | on(this.domNode, 'touchcancel', this.releaseMoves);
70 | } else {
71 | this.eventMoveToken = on(this.domNode, 'mousemove', this.onDragMove);
72 | this.eventUpToken = on(this.domNode, 'mouseup', this.onDragUp);
73 | }
74 | }
75 |
76 | if (!this.isDragStatus) {
77 | this.deltaX = 0;
78 | this.deltaY = 0;
79 | this.isDragStatus = true;
80 | this.x = this.getClientX(event);
81 | this.y = this.getClientY(event);
82 | }
83 |
84 | if (event.cancelable) {
85 | event.preventDefault();
86 | }
87 | }
88 |
89 | /**
90 | * These releases all of the listeners on document.body.
91 | */
92 | releaseMoves() {
93 | if (this.eventMoveToken) {
94 | this.eventMoveToken.off();
95 | this.eventMoveToken = null;
96 | }
97 |
98 | if (this.eventUpToken) {
99 | this.eventUpToken.off();
100 | this.eventUpToken = null;
101 | }
102 |
103 | if (this.animationFrameID !== null) {
104 | cancelAnimationFrame(this.animationFrameID);
105 | this.animationFrameID = null;
106 | }
107 |
108 | if (this.isDragStatus) {
109 | this.isDragStatus = false;
110 | this.x = 0;
111 | this.y = 0;
112 | }
113 | }
114 |
115 | /**
116 | * Returns whether or not if the mouse movement is being tracked.
117 | */
118 | isDragging = () => this.isDragStatus;
119 |
120 | /**
121 | * Calls onMove passed into constructor and updates internal state.
122 | */
123 | onDragMove = (event: MouseEvent | TouchEvent) => {
124 | const x = this.getClientX(event);
125 | const y = this.getClientY(event);
126 |
127 | this.deltaX += x - this.x;
128 | this.deltaY += x - this.y;
129 |
130 | if (this.animationFrameID === null) {
131 | // The mouse may move faster then the animation frame does.
132 | // Use `requestAnimationFrame` to avoid over-updating.
133 | this.animationFrameID = requestAnimationFrame(this.didDragMove);
134 | }
135 |
136 | this.x = x;
137 | this.y = y;
138 |
139 | this.moveEvent = event;
140 |
141 | if (event.cancelable) {
142 | event.preventDefault();
143 | }
144 | };
145 |
146 | didDragMove = () => {
147 | this.animationFrameID = null;
148 | this.onMove(this.deltaX, this.deltaY, this.moveEvent);
149 |
150 | this.deltaX = 0;
151 | this.deltaY = 0;
152 | };
153 | /**
154 | * Calls onMoveEnd passed into constructor and updates internal state.
155 | */
156 | onDragUp = event => {
157 | if (this.animationFrameID) {
158 | this.didDragMove();
159 | }
160 | this.onMoveEnd?.(event);
161 | };
162 | }
163 |
--------------------------------------------------------------------------------
/src/WheelHandler.ts:
--------------------------------------------------------------------------------
1 | import emptyFunction from './utils/emptyFunction';
2 | import normalizeWheel from './utils/normalizeWheel';
3 | import requestAnimationFramePolyfill from './requestAnimationFramePolyfill';
4 |
5 | const swapWheelAxis = normalizedEvent => {
6 | return {
7 | spinX: normalizedEvent.spinY,
8 | spinY: normalizedEvent.spinX,
9 | pixelX: normalizedEvent.pixelY,
10 | pixelY: normalizedEvent.pixelX
11 | };
12 | };
13 |
14 | /**
15 | * Used to handle scrolling trackpad and mouse wheel events.
16 | */
17 | class WheelHandler {
18 | animationFrameID = null;
19 | deltaX = 0;
20 | deltaY = 0;
21 | handleScrollX = null;
22 | handleScrollY = null;
23 | stopPropagation = null;
24 | onWheelCallback = null;
25 |
26 | constructor(onWheel, handleScrollX, handleScrollY, stopPropagation) {
27 | this.didWheel = this.didWheel.bind(this);
28 |
29 | if (typeof handleScrollX !== 'function') {
30 | handleScrollX = handleScrollX
31 | ? emptyFunction.thatReturnsTrue
32 | : emptyFunction.thatReturnsFalse;
33 | }
34 |
35 | if (typeof handleScrollY !== 'function') {
36 | handleScrollY = handleScrollY
37 | ? emptyFunction.thatReturnsTrue
38 | : emptyFunction.thatReturnsFalse;
39 | }
40 |
41 | if (typeof stopPropagation !== 'function') {
42 | stopPropagation = stopPropagation
43 | ? emptyFunction.thatReturnsTrue
44 | : emptyFunction.thatReturnsFalse;
45 | }
46 |
47 | this.handleScrollX = handleScrollX;
48 | this.handleScrollY = handleScrollY;
49 | this.stopPropagation = stopPropagation;
50 | this.onWheelCallback = onWheel;
51 | this.onWheel = this.onWheel.bind(this);
52 | }
53 |
54 | /**
55 | * Binds the wheel handler.
56 | * @param event The wheel event.
57 | */
58 | onWheel(event) {
59 | let normalizedEvent = normalizeWheel(event);
60 |
61 | // on some platforms (e.g. Win10), browsers do not automatically swap deltas for horizontal scroll
62 | if (navigator.platform !== 'MacIntel' && event.shiftKey) {
63 | normalizedEvent = swapWheelAxis(normalizedEvent);
64 | }
65 |
66 | const deltaX = this.deltaX + normalizedEvent.pixelX;
67 | const deltaY = this.deltaY + normalizedEvent.pixelY;
68 | const handleScrollX = this.handleScrollX(deltaX, deltaY);
69 | const handleScrollY = this.handleScrollY(deltaY, deltaX);
70 | if (!handleScrollX && !handleScrollY) {
71 | return;
72 | }
73 |
74 | this.deltaX += handleScrollX ? normalizedEvent.pixelX : 0;
75 | this.deltaY += handleScrollY ? normalizedEvent.pixelY : 0;
76 | event.preventDefault();
77 |
78 | let changed;
79 | if (this.deltaX !== 0 || this.deltaY !== 0) {
80 | if (this.stopPropagation()) {
81 | event.stopPropagation();
82 | }
83 | changed = true;
84 | }
85 |
86 | if (changed === true && this.animationFrameID === null) {
87 | this.animationFrameID = requestAnimationFramePolyfill(this.didWheel);
88 | }
89 | }
90 |
91 | /**
92 | * Fires a callback if the wheel event has changed.
93 | */
94 | didWheel() {
95 | this.animationFrameID = null;
96 | this.onWheelCallback(this.deltaX, this.deltaY);
97 | this.deltaX = 0;
98 | this.deltaY = 0;
99 | }
100 | }
101 |
102 | export default WheelHandler;
103 |
--------------------------------------------------------------------------------
/src/addClass.ts:
--------------------------------------------------------------------------------
1 | import hasClass from './hasClass';
2 |
3 | /**
4 | * Adds specific class to a given element
5 | *
6 | * @param target The element to add class to
7 | * @param className The class to be added
8 | *
9 | * @returns The target element
10 | */
11 | export default function addClass(target: Element, className: string): Element {
12 | if (className) {
13 | if (target.classList) {
14 | target.classList.add(className);
15 | } else if (!hasClass(target, className)) {
16 | target.className = `${target.className} ${className}`;
17 | }
18 | }
19 | return target;
20 | }
21 |
--------------------------------------------------------------------------------
/src/addStyle.ts:
--------------------------------------------------------------------------------
1 | import hyphenateStyleName from './utils/hyphenateStyleName';
2 | import removeStyle from './removeStyle';
3 |
4 | export interface CSSProperty {
5 | [key: string]: string | number;
6 | }
7 |
8 | /**
9 | * Apply a single CSS style rule to a given element
10 | *
11 | * @param node The element to add styles to
12 | * @param property The style property to be added
13 | * @param value The style value to be added
14 | */
15 | function addStyle(node: Element, property: string, value: string | number): void;
16 |
17 | /**
18 | * Apply multiple CSS style rules to a given element
19 | *
20 | * @param node The element to add styles to
21 | * @param properties The key-value object of style properties to be added
22 | */
23 | function addStyle(node: Element, properties: Partial): void;
24 | function addStyle(
25 | node: Element,
26 | property: string | Partial,
27 | value?: string | number
28 | ): void {
29 | let css = '';
30 | let props = property;
31 |
32 | if (typeof property === 'string') {
33 | if (value === undefined) {
34 | throw new Error('value is undefined');
35 | }
36 | (props = {})[property] = value;
37 | }
38 |
39 | if (typeof props === 'object') {
40 | for (const key in props) {
41 | if (Object.prototype.hasOwnProperty.call(props, key)) {
42 | if (!props[key] && props[key] !== 0) {
43 | removeStyle(node, hyphenateStyleName(key));
44 | } else {
45 | css += `${hyphenateStyleName(key)}:${props[key]};`;
46 | }
47 | }
48 | }
49 | }
50 |
51 | (node as HTMLElement).style.cssText += `;${css}`;
52 | }
53 |
54 | export default addStyle;
55 |
--------------------------------------------------------------------------------
/src/canUseDOM.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if the current environment is in the browser and can access and modify the DOM.
3 | */
4 | const canUseDOM = !!(
5 | typeof window !== 'undefined' &&
6 | window.document &&
7 | window.document.createElement
8 | );
9 |
10 | export default canUseDOM;
11 |
--------------------------------------------------------------------------------
/src/cancelAnimationFramePolyfill.ts:
--------------------------------------------------------------------------------
1 | import getGlobal from './utils/getGlobal';
2 |
3 | const g = getGlobal();
4 |
5 | /**
6 | * @deprecated use `cancelAnimationFrame` instead
7 | */
8 | const cancelAnimationFramePolyfill = g.cancelAnimationFrame || g.clearTimeout;
9 |
10 | export default cancelAnimationFramePolyfill;
11 |
--------------------------------------------------------------------------------
/src/contains.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from './canUseDOM';
2 |
3 | const fallback = (context: Element, node: (Node & ParentNode) | null) => {
4 | if (!node) return false;
5 |
6 | do {
7 | if (node === context) {
8 | return true;
9 | }
10 | } while (node.parentNode && (node = node.parentNode));
11 |
12 | return false;
13 | };
14 |
15 | /**
16 | * Checks if an element contains another given element.
17 | *
18 | * @param context The context element
19 | * @param node The element to check
20 | * @returns `true` if the given element is contained, `false` otherwise
21 | */
22 | const contains = (context: Element, node: (Node & ParentNode) | null) => {
23 | if (!node) return false;
24 |
25 | if (context.contains) {
26 | return context.contains(node);
27 | } else if (context.compareDocumentPosition) {
28 | return context === node || !!(context.compareDocumentPosition(node) & 16);
29 | }
30 |
31 | return fallback(context, node);
32 | };
33 |
34 | export default (() => (canUseDOM ? contains : fallback))();
35 |
--------------------------------------------------------------------------------
/src/getAnimationEnd.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from './canUseDOM';
2 |
3 | const vendorMap = {
4 | animation: 'animationend',
5 | OAnimation: 'oAnimationEnd',
6 | MozAnimation: 'animationend',
7 | WebkitAnimation: 'webkitAnimationEnd'
8 | };
9 |
10 | function getAnimationEnd() {
11 | if (!canUseDOM) {
12 | return;
13 | }
14 |
15 | let tempAnimationEnd;
16 | const style = document.createElement('div').style;
17 | for (tempAnimationEnd in vendorMap) {
18 | if (style[tempAnimationEnd] !== undefined) {
19 | return vendorMap[tempAnimationEnd];
20 | }
21 | }
22 | }
23 |
24 | export default getAnimationEnd;
25 |
--------------------------------------------------------------------------------
/src/getContainer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get a DOM container
3 | * @param container
4 | * @param defaultContainer
5 | * @returns
6 | */
7 | export default function getContainer(
8 | container: Element | null | (() => Element | null),
9 | defaultContainer?: Element
10 | ): Element {
11 | container = typeof container === 'function' ? container() : container;
12 | return container || defaultContainer;
13 | }
14 |
--------------------------------------------------------------------------------
/src/getHeight.ts:
--------------------------------------------------------------------------------
1 | import getWindow from './getWindow';
2 | import getOffset from './getOffset';
3 |
4 | /**
5 | * Get the height of a DOM element
6 | * @param node The DOM element
7 | * @param client Whether to get the client height
8 | * @returns The height of the DOM element
9 | */
10 | export default function getHeight(node: Element | Window, client?: Element): number {
11 | const win = getWindow(node);
12 |
13 | if (win) {
14 | return win.innerHeight;
15 | }
16 |
17 | return client ? (node as Element).clientHeight : getOffset(node as Element).height;
18 | }
19 |
--------------------------------------------------------------------------------
/src/getOffset.ts:
--------------------------------------------------------------------------------
1 | import ownerDocument from './ownerDocument';
2 | import getWindow from './getWindow';
3 | import contains from './contains';
4 |
5 | export type Offset = {
6 | top: number;
7 | left: number;
8 | height: number;
9 | width: number;
10 | };
11 |
12 | /**
13 | * Get the offset of a DOM element
14 | * @param node The DOM element
15 | * @returns The offset of the DOM element
16 | */
17 | export default function getOffset(node: Element | null): Offset | DOMRect | null {
18 | const doc = ownerDocument(node);
19 | const win = getWindow(doc);
20 | const docElem = doc && doc.documentElement;
21 |
22 | let box = {
23 | top: 0,
24 | left: 0,
25 | height: 0,
26 | width: 0
27 | };
28 |
29 | if (!doc) {
30 | return null;
31 | }
32 |
33 | // Make sure it's not a disconnected DOM node
34 | if (!contains(docElem, node)) {
35 | return box;
36 | }
37 |
38 | if (node?.getBoundingClientRect !== undefined) {
39 | box = node.getBoundingClientRect();
40 | }
41 |
42 | if ((box.width || box.height) && docElem && win) {
43 | box = {
44 | top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
45 | left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0),
46 | width: (box.width === null ? (node as HTMLElement).offsetWidth : box.width) || 0,
47 | height: (box.height === null ? (node as HTMLElement).offsetHeight : box.height) || 0
48 | };
49 | }
50 |
51 | return box;
52 | }
53 |
--------------------------------------------------------------------------------
/src/getOffsetParent.ts:
--------------------------------------------------------------------------------
1 | import ownerDocument from './ownerDocument';
2 | import nodeName from './nodeName';
3 | import getStyle from './getStyle';
4 |
5 | /**
6 | * Get the offset parent of a DOM element
7 | * @param node The DOM element
8 | * @returns The offset parent of the DOM element
9 | */
10 | export default function getOffsetParent(node: Element): Element {
11 | const doc = ownerDocument(node);
12 | let offsetParent: Element = (node as HTMLElement)?.offsetParent;
13 |
14 | while (
15 | offsetParent &&
16 | nodeName(node) !== 'html' &&
17 | getStyle(offsetParent, 'position') === 'static'
18 | ) {
19 | offsetParent = (offsetParent as HTMLElement).offsetParent;
20 | }
21 |
22 | return offsetParent || doc.documentElement;
23 | }
24 |
--------------------------------------------------------------------------------
/src/getPosition.ts:
--------------------------------------------------------------------------------
1 | import getOffsetParent from './getOffsetParent';
2 | import getOffset, { Offset } from './getOffset';
3 | import getStyle from './getStyle';
4 | import scrollTop from './scrollTop';
5 | import scrollLeft from './scrollLeft';
6 | import nodeName from './nodeName';
7 |
8 | /**
9 | * Get the position of a DOM element
10 | * @param node The DOM element
11 | * @param offsetParent The offset parent of the DOM element
12 | * @param calcMargin Whether to calculate the margin
13 | * @returns The position of the DOM element
14 | */
15 | export default function getPosition(
16 | node: Element,
17 | offsetParent?: Element,
18 | calcMargin = true
19 | ): Offset | DOMRect | null {
20 | const parentOffset = {
21 | top: 0,
22 | left: 0
23 | };
24 |
25 | let offset = null;
26 |
27 | // Fixed elements are offset from window (parentOffset = {top:0, left: 0},
28 | // because it is its only offset parent
29 | if (getStyle(node, 'position') === 'fixed') {
30 | offset = node.getBoundingClientRect();
31 | } else {
32 | offsetParent = offsetParent || getOffsetParent(node);
33 | offset = getOffset(node);
34 |
35 | if (nodeName(offsetParent) !== 'html') {
36 | const nextParentOffset = getOffset(offsetParent);
37 | if (nextParentOffset) {
38 | parentOffset.top = nextParentOffset.top;
39 | parentOffset.left = nextParentOffset.left;
40 | }
41 | }
42 |
43 | parentOffset.top +=
44 | parseInt(getStyle(offsetParent, 'borderTopWidth') as string, 10) - scrollTop(offsetParent) ||
45 | 0;
46 | parentOffset.left +=
47 | parseInt(getStyle(offsetParent, 'borderLeftWidth') as string, 10) -
48 | scrollLeft(offsetParent) || 0;
49 | }
50 |
51 | // Subtract parent offsets and node margins
52 |
53 | if (offset) {
54 | const marginTop = calcMargin ? parseInt(getStyle(node, 'marginTop') as string, 10) || 0 : 0;
55 | const marginLeft = calcMargin ? parseInt(getStyle(node, 'marginLeft') as string, 10) || 0 : 0;
56 | return {
57 | ...offset,
58 | top: offset.top - parentOffset.top - marginTop,
59 | left: offset.left - parentOffset.left - marginLeft
60 | };
61 | }
62 |
63 | return null;
64 | }
65 |
--------------------------------------------------------------------------------
/src/getScrollbarSize.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from './canUseDOM';
2 |
3 | let size;
4 |
5 | /**
6 | * Returns the size of the scrollbar.
7 | * @param recalc Force recalculation.
8 | * @returns The size of the scrollbar.
9 | */
10 | export default function getScrollbarSize(recalc?: boolean): number | void {
11 | if (size === undefined || recalc) {
12 | if (canUseDOM) {
13 | const scrollDiv = document.createElement('div');
14 | const body: any = document.body;
15 |
16 | scrollDiv.style.position = 'absolute';
17 | scrollDiv.style.top = '-9999px';
18 | scrollDiv.style.width = '50px';
19 | scrollDiv.style.height = '50px';
20 | scrollDiv.style.overflow = 'scroll';
21 |
22 | body.appendChild(scrollDiv);
23 | size = scrollDiv.offsetWidth - scrollDiv.clientWidth;
24 | body.removeChild(scrollDiv);
25 | }
26 | }
27 |
28 | return size;
29 | }
30 |
--------------------------------------------------------------------------------
/src/getStyle.ts:
--------------------------------------------------------------------------------
1 | import camelizeStyleName from './utils/camelizeStyleName';
2 | import getComputedStyle from './utils/getComputedStyle';
3 | import hyphenateStyleName from './utils/hyphenateStyleName';
4 |
5 | /**
6 | * Gets the value for a style property
7 | * @param node The DOM element
8 | * @param property The style property
9 | * @returns The value of the style property
10 | */
11 | export default function getStyle(node: Element, property?: string) {
12 | if (property) {
13 | const value = (node as HTMLElement).style[camelizeStyleName(property)];
14 |
15 | if (value) {
16 | return value;
17 | }
18 |
19 | const styles = getComputedStyle(node);
20 |
21 | if (styles) {
22 | return styles.getPropertyValue(hyphenateStyleName(property));
23 | }
24 | }
25 |
26 | return (node as HTMLElement).style || getComputedStyle(node);
27 | }
28 |
--------------------------------------------------------------------------------
/src/getTransitionEnd.ts:
--------------------------------------------------------------------------------
1 | import getTransitionProperties from './getTransitionProperties';
2 |
3 | export default function getTransitionEnd() {
4 | return getTransitionProperties().end;
5 | }
6 |
--------------------------------------------------------------------------------
/src/getTransitionProperties.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from './canUseDOM';
2 |
3 | function getTransitionProperties() {
4 | if (!canUseDOM) {
5 | return {};
6 | }
7 |
8 | const vendorMap = {
9 | O: e => `o${e.toLowerCase()}`,
10 | Moz: e => e.toLowerCase(),
11 | Webkit: e => `webkit${e}`,
12 | ms: e => `MS${e}`
13 | };
14 |
15 | const vendors = Object.keys(vendorMap);
16 |
17 | let style = document.createElement('div').style;
18 |
19 | let tempTransitionEnd;
20 | let tempPrefix = '';
21 |
22 | for (let i = 0; i < vendors.length; i += 1) {
23 | const vendor = vendors[i];
24 |
25 | if (`${vendor}TransitionProperty` in style) {
26 | tempPrefix = `-${vendor.toLowerCase()}`;
27 | tempTransitionEnd = vendorMap[vendor]('TransitionEnd');
28 | break;
29 | }
30 | }
31 |
32 | if (!tempTransitionEnd && 'transitionProperty' in style) {
33 | tempTransitionEnd = 'transitionend';
34 | }
35 |
36 | style = null;
37 |
38 | const addPrefix = (name: string) => `${tempPrefix}-${name}`;
39 |
40 | return {
41 | end: tempTransitionEnd,
42 | backfaceVisibility: addPrefix('backface-visibility'),
43 | transform: addPrefix('transform'),
44 | property: addPrefix('transition-property'),
45 | timing: addPrefix('transition-timing-function'),
46 | delay: addPrefix('transition-delay'),
47 | duration: addPrefix('transition-duration')
48 | };
49 | }
50 |
51 | export default getTransitionProperties;
52 |
--------------------------------------------------------------------------------
/src/getWidth.ts:
--------------------------------------------------------------------------------
1 | import getWindow from './getWindow';
2 | import getOffset from './getOffset';
3 |
4 | /**
5 | * Get the width of a DOM element
6 | * @param node The DOM element
7 | * @param client Whether to get the client width
8 | * @returns The width of the DOM element
9 | */
10 | export default function getWidth(node: Element | Window, client?: Element): number {
11 | const win = getWindow(node);
12 |
13 | if (win) {
14 | return win.innerWidth;
15 | }
16 |
17 | if (client) {
18 | return (node as Element).clientWidth;
19 | }
20 |
21 | const offset = getOffset(node as Element);
22 |
23 | return offset ? offset.width : 0;
24 | }
25 |
--------------------------------------------------------------------------------
/src/getWindow.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the Window object of browser
3 | * @param node The DOM element
4 | * @returns The Window object of browser
5 | */
6 | export default function getWindow(node: any): Window {
7 | if (node === node?.window) {
8 | return node;
9 | }
10 |
11 | return node?.nodeType === 9 ? node?.defaultView || node?.parentWindow : null;
12 | }
13 |
--------------------------------------------------------------------------------
/src/hasClass.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Check whether an element has a specific class
3 | *
4 | * @param target The element to be checked
5 | * @param className The class to be checked
6 | *
7 | * @returns `true` if the element has the class, `false` otherwise
8 | */
9 | export default function hasClass(target: Element, className: string): boolean {
10 | if (target.classList) {
11 | return !!className && target.classList.contains(className);
12 | }
13 | return ` ${target.className} `.indexOf(` ${className} `) !== -1;
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /** events */
2 | export { default as on } from './on';
3 | export { default as off } from './off';
4 | export { default as WheelHandler } from './WheelHandler';
5 | export { default as DOMMouseMoveTracker } from './DOMMouseMoveTracker';
6 | export { default as PointerMoveTracker } from './PointerMoveTracker';
7 |
8 | /** classNames */
9 | export { default as addClass } from './addClass';
10 | export { default as removeClass } from './removeClass';
11 | export { default as hasClass } from './hasClass';
12 | export { default as toggleClass } from './toggleClass';
13 |
14 | /** animation */
15 | export { default as cancelAnimationFramePolyfill } from './cancelAnimationFramePolyfill';
16 | export { default as requestAnimationFramePolyfill } from './requestAnimationFramePolyfill';
17 | export { default as getAnimationEnd } from './getAnimationEnd';
18 |
19 | /** DOM query */
20 | export { default as ownerDocument } from './ownerDocument';
21 | export { default as ownerWindow } from './ownerWindow';
22 | export { default as getWindow } from './getWindow';
23 | export { default as getContainer } from './getContainer';
24 | export { default as canUseDOM } from './canUseDOM';
25 | export { default as contains } from './contains';
26 | export { default as scrollTop } from './scrollTop';
27 | export { default as scrollLeft } from './scrollLeft';
28 | export { default as getOffset } from './getOffset';
29 | export { default as nodeName } from './nodeName';
30 | export { default as getOffsetParent } from './getOffsetParent';
31 | export { default as getPosition } from './getPosition';
32 | export { default as isOverflowing } from './isOverflowing';
33 | export { default as getScrollbarSize } from './getScrollbarSize';
34 | export { default as getHeight } from './getHeight';
35 | export { default as getWidth } from './getWidth';
36 | export { default as isFocusable } from './isFocusable';
37 |
38 | /** styles */
39 | export { default as getStyle } from './getStyle';
40 | export { default as removeStyle } from './removeStyle';
41 | export { default as addStyle } from './addStyle';
42 | export { default as translateDOMPositionXY } from './translateDOMPositionXY';
43 |
--------------------------------------------------------------------------------
/src/isFocusable.ts:
--------------------------------------------------------------------------------
1 | const selector = `input:not([type='hidden']):not([disabled]),
2 | select:not([disabled]), textarea:not([disabled]), a[href],
3 | button:not([disabled]),[tabindex],iframe,object, embed, area[href],
4 | audio[controls],video[controls],[contenteditable]:not([contenteditable='false'])`;
5 |
6 | function isVisible(element: Element) {
7 | const htmlElement = element as HTMLElement;
8 | return (
9 | htmlElement.offsetWidth > 0 ||
10 | htmlElement.offsetHeight > 0 ||
11 | element.getClientRects().length > 0
12 | );
13 | }
14 |
15 | /**
16 | * Checks whether `element` is focusable or not.
17 | *
18 | * ```typescript
19 | * isFocusable(document.querySelector("input")); // true
20 | * isFocusable(document.querySelector("input[tabindex='-1']")); // true
21 | * isFocusable(document.querySelector("input[hidden]")); // false
22 | * isFocusable(document.querySelector("input:disabled")); // false
23 | * ```
24 | */
25 | function isFocusable(element: Element): boolean {
26 | return isVisible(element) && element?.matches(selector);
27 | }
28 |
29 | export default isFocusable;
30 |
--------------------------------------------------------------------------------
/src/isOverflowing.ts:
--------------------------------------------------------------------------------
1 | import getWindow from './getWindow';
2 | import ownerDocument from './ownerDocument';
3 |
4 | function bodyIsOverflowing(node) {
5 | const doc = ownerDocument(node);
6 | const win = getWindow(doc);
7 | const fullWidth = win.innerWidth;
8 |
9 | if (doc.body) {
10 | return doc.body.clientWidth < fullWidth;
11 | }
12 |
13 | return false;
14 | }
15 |
16 | /**
17 | * Check if the document is overflowing and account for the scrollbar width
18 | * @param container The container to check
19 | * @returns The document is overflowing
20 | */
21 | export default function isOverflowing(container: Element) {
22 | const win = getWindow(container);
23 | const isBody = container && container.tagName.toLowerCase() === 'body';
24 |
25 | return win || isBody
26 | ? bodyIsOverflowing(container)
27 | : container.scrollHeight > container.clientHeight;
28 | }
29 |
--------------------------------------------------------------------------------
/src/nodeName.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the name of the DOM element
3 | * @param node The DOM element
4 | * @returns The name of the DOM element
5 | */
6 | export default function nodeName(node: Element): string {
7 | return node?.nodeName && node?.nodeName?.toLowerCase();
8 | }
9 |
--------------------------------------------------------------------------------
/src/off.ts:
--------------------------------------------------------------------------------
1 | export interface CustomEventListener {
2 | (evt: T): void;
3 | }
4 |
5 | /**
6 | * Unbind `target` event `eventName`'s callback `listener`.
7 | * @param target The DOM element
8 | * @param eventName The event name
9 | * @param listener The event listener
10 | * @param options The event options
11 | */
12 | export default function on(
13 | target: Element | Window | Document | EventTarget,
14 | eventName: K,
15 | listener: EventListenerOrEventListenerObject | CustomEventListener,
16 | options: boolean | AddEventListenerOptions = false
17 | ): void {
18 | target.removeEventListener(eventName, listener, options);
19 | }
20 |
--------------------------------------------------------------------------------
/src/on.ts:
--------------------------------------------------------------------------------
1 | export interface CustomEventListener {
2 | (evt: T): void;
3 | }
4 |
5 | /**
6 | * Bind `target` event `eventName`'s callback `listener`.
7 | * @param target The DOM element
8 | * @param eventType The event name
9 | * @param listener The event listener
10 | * @param options The event options
11 | * @returns The event listener
12 | */
13 | export default function on(
14 | target: Element | Window | Document | EventTarget,
15 | eventType: K,
16 | listener: EventListenerOrEventListenerObject | CustomEventListener,
17 | options: boolean | AddEventListenerOptions = false
18 | ): { off: () => void } {
19 | target.addEventListener(eventType, listener, options);
20 |
21 | return {
22 | off() {
23 | target.removeEventListener(eventType, listener, options);
24 | }
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/ownerDocument.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the top-level document object of the node.
3 | * @param node The DOM element
4 | * @returns The top-level document object of the node
5 | */
6 | export default function ownerDocument(node: Element | null): Document {
7 | return (node && node.ownerDocument) || document;
8 | }
9 |
--------------------------------------------------------------------------------
/src/ownerWindow.ts:
--------------------------------------------------------------------------------
1 | import ownerDocument from './ownerDocument';
2 |
3 | /**
4 | * Returns the top-level window object of the node.
5 | * @param componentOrElement The DOM element
6 | * @returns The top-level window object of the node
7 | */
8 | export default function ownerWindow(componentOrElement: Element): Window {
9 | const doc = ownerDocument(componentOrElement);
10 | return doc.defaultView;
11 | }
12 |
--------------------------------------------------------------------------------
/src/removeClass.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Remove a class from a given element
3 | *
4 | * @param target The element to remove the class from
5 | * @param className The class to be removed
6 | *
7 | * @returns The target element
8 | */
9 | export default function removeClass(target: Element, className: string): Element {
10 | if (className) {
11 | if (target.classList) {
12 | target.classList.remove(className);
13 | } else {
14 | target.className = target.className
15 | .replace(new RegExp(`(^|\\s)${className}(?:\\s|$)`, 'g'), '$1')
16 | .replace(/\s+/g, ' ') // multiple spaces to one
17 | .replace(/^\s*|\s*$/g, ''); // trim the ends
18 | }
19 | }
20 | return target;
21 | }
22 |
--------------------------------------------------------------------------------
/src/removeStyle.ts:
--------------------------------------------------------------------------------
1 | function _removeStyle(node: Element, key: string) {
2 | (node as HTMLElement).style?.removeProperty?.(key);
3 | }
4 |
5 | /**
6 | * Remove a style property from a DOM element
7 | * @param node The DOM element
8 | * @param keys key(s) typeof [string , array]
9 | */
10 | export default function removeStyle(node: Element, keys: string | Array) {
11 | if (typeof keys === 'string') {
12 | _removeStyle(node, keys);
13 | } else if (Array.isArray(keys)) {
14 | keys.forEach(key => _removeStyle(node, key));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/requestAnimationFramePolyfill.ts:
--------------------------------------------------------------------------------
1 | import getGlobal from './utils/getGlobal';
2 |
3 | const g = getGlobal();
4 | let lastTime = 0;
5 |
6 | function _setTimeout(callback: (t: number) => void) {
7 | const currTime = Date.now();
8 | const timeDelay = Math.max(0, 16 - (currTime - lastTime));
9 | lastTime = currTime + timeDelay;
10 | return g.setTimeout(() => {
11 | callback(Date.now());
12 | }, timeDelay);
13 | }
14 |
15 | /**
16 | * @deprecated Use `requestAnimationFrame` instead.
17 | */
18 | const requestAnimationFramePolyfill = g.requestAnimationFrame || _setTimeout;
19 |
20 | export default requestAnimationFramePolyfill;
21 |
--------------------------------------------------------------------------------
/src/scrollLeft.ts:
--------------------------------------------------------------------------------
1 | import getWindow from './getWindow';
2 |
3 | /**
4 | * Gets the number of pixels to scroll the element's content from the left edge.
5 | * @param node The DOM element
6 | */
7 | function scrollLeft(node: Element): number;
8 |
9 | /**
10 | * Sets the number of pixels to scroll the element's content from its left edge.
11 | * @param node The DOM element
12 | * @param val The number of pixels to scroll the element's content from its left edge
13 | */
14 | function scrollLeft(node: Element, val: number): void;
15 | function scrollLeft(node: Element, val?: number): number {
16 | const win = getWindow(node);
17 | let left = node.scrollLeft;
18 | let top = 0;
19 |
20 | if (win) {
21 | left = win.pageXOffset;
22 | top = win.pageYOffset;
23 | }
24 |
25 | if (val !== undefined) {
26 | if (win) {
27 | win.scrollTo(val, top);
28 | } else {
29 | node.scrollLeft = val;
30 | }
31 | }
32 |
33 | return left;
34 | }
35 |
36 | export default scrollLeft;
37 |
--------------------------------------------------------------------------------
/src/scrollTop.ts:
--------------------------------------------------------------------------------
1 | import getWindow from './getWindow';
2 |
3 | /**
4 | * Gets the number of pixels that an element's content is scrolled vertically.
5 | * @param node The DOM element
6 | */
7 | function scrollTop(node: Element): number;
8 |
9 | /**
10 | * Sets the number of pixels that an element's content is scrolled vertically.
11 | * @param node The DOM element
12 | * @param val The number of pixels that an element's content is scrolled vertically
13 | */
14 | function scrollTop(node: Element, val: number): void;
15 | function scrollTop(node: Element, val?: number): number {
16 | const win = getWindow(node);
17 | let top = node.scrollTop;
18 | let left = 0;
19 |
20 | if (win) {
21 | top = win.pageYOffset;
22 | left = win.pageXOffset;
23 | }
24 |
25 | if (val !== undefined) {
26 | if (win) {
27 | win.scrollTo(left, val);
28 | } else {
29 | node.scrollTop = val;
30 | }
31 | }
32 |
33 | return top;
34 | }
35 |
36 | export default scrollTop;
37 |
--------------------------------------------------------------------------------
/src/toggleClass.ts:
--------------------------------------------------------------------------------
1 | import hasClass from './hasClass';
2 | import addClass from './addClass';
3 | import removeClass from './removeClass';
4 |
5 | /**
6 | * Toggle a class on an element
7 | * @param target The DOM element
8 | * @param className The class name
9 | * @returns The DOM element
10 | */
11 | export default function toggleClass(target: Element, className: string): Element {
12 | if (hasClass(target, className)) {
13 | return removeClass(target, className);
14 | }
15 | return addClass(target, className);
16 | }
17 |
--------------------------------------------------------------------------------
/src/translateDOMPositionXY.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Source code reference from:
3 | * https://github.com/facebook/fbjs/blob/d308fa83c9/packages/fbjs/src/dom/translateDOMPositionXY.js
4 | */
5 |
6 | import BrowserSupportCore from './utils/BrowserSupportCore';
7 | import getVendorPrefixedName from './utils/getVendorPrefixedName';
8 | import getGlobal from './utils/getGlobal';
9 |
10 | const g = getGlobal();
11 | const TRANSFORM = getVendorPrefixedName('transform');
12 | const BACKFACE_VISIBILITY = getVendorPrefixedName('backfaceVisibility');
13 |
14 | export interface Options {
15 | enableTransform?: boolean;
16 | enable3DTransform?: boolean;
17 | forceUseTransform?: boolean;
18 | }
19 |
20 | const appendLeftAndTop = (style: CSSStyleDeclaration, x = 0, y = 0) => {
21 | style.left = `${x}px`;
22 | style.top = `${y}px`;
23 |
24 | return style;
25 | };
26 |
27 | const appendTranslate = (style: CSSStyleDeclaration, x = 0, y = 0) => {
28 | style[TRANSFORM] = `translate(${x}px,${y}px)`;
29 |
30 | return style;
31 | };
32 |
33 | const appendTranslate3d = (style: CSSStyleDeclaration, x = 0, y = 0) => {
34 | style[TRANSFORM] = `translate3d(${x}px,${y}px,0)`;
35 | style[BACKFACE_VISIBILITY] = 'hidden';
36 |
37 | return style;
38 | };
39 |
40 | export const getTranslateDOMPositionXY = (conf?: Options) => {
41 | const { enableTransform = true, enable3DTransform = true, forceUseTransform } = conf || {};
42 | if (forceUseTransform) {
43 | return conf.enable3DTransform ? appendTranslate3d : appendTranslate;
44 | }
45 |
46 | if (BrowserSupportCore.hasCSSTransforms() && enableTransform) {
47 | const ua = g.window ? g.window.navigator.userAgent : 'UNKNOWN';
48 | const isSafari = /Safari\//.test(ua) && !/Chrome\//.test(ua);
49 |
50 | // It appears that Safari messes up the composition order
51 | // of GPU-accelerated layers
52 | // (see bug https://bugs.webkit.org/show_bug.cgi?id=61824).
53 | // Use 2D translation instead.
54 | if (!isSafari && BrowserSupportCore.hasCSS3DTransforms() && enable3DTransform) {
55 | return appendTranslate3d;
56 | }
57 |
58 | return appendTranslate;
59 | }
60 |
61 | return appendLeftAndTop;
62 | };
63 |
64 | const translateDOMPositionXY = getTranslateDOMPositionXY();
65 |
66 | export default translateDOMPositionXY;
67 |
--------------------------------------------------------------------------------
/src/utils/BrowserSupportCore.ts:
--------------------------------------------------------------------------------
1 | import getVendorPrefixedName from './getVendorPrefixedName';
2 |
3 | export default {
4 | /**
5 | * @return {bool} True if browser supports css animations.
6 | */
7 | hasCSSAnimations: () => !!getVendorPrefixedName('animationName'),
8 |
9 | /**
10 | * @return {bool} True if browser supports css transforms.
11 | */
12 | hasCSSTransforms: () => !!getVendorPrefixedName('transform'),
13 |
14 | /**
15 | * @return {bool} True if browser supports css 3d transforms.
16 | */
17 | hasCSS3DTransforms: () => !!getVendorPrefixedName('perspective'),
18 |
19 | /**
20 | * @return {bool} True if browser supports css transitions.
21 | */
22 | hasCSSTransitions: () => !!getVendorPrefixedName('transition')
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/UserAgent.ts:
--------------------------------------------------------------------------------
1 | let populated = false;
2 |
3 | // Browsers
4 | let ie;
5 | let firefox;
6 | let opera;
7 | let webkit;
8 | let chrome;
9 |
10 | // Actual IE browser for compatibility mode
11 | let ieRealVersion;
12 |
13 | // Platforms
14 | let osx;
15 | let windows;
16 | let linux;
17 | let android;
18 |
19 | // Architectures
20 | let win64;
21 |
22 | // Devices
23 | let iphone;
24 | let ipad;
25 | let native;
26 |
27 | let mobile;
28 |
29 | function populate(): null {
30 | if (populated) {
31 | return;
32 | }
33 |
34 | populated = true;
35 |
36 | // To work around buggy JS libraries that can't handle multi-digit
37 | // version numbers, Opera 10's user agent string claims it's Opera
38 | // 9, then later includes a Version/X.Y field:
39 | //
40 | // Opera/9.80 (foo) Presto/2.2.15 Version/10.10
41 | const uas = navigator.userAgent;
42 | let agent =
43 | /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(
44 | uas
45 | );
46 | const os = /(Mac OS X)|(Windows)|(Linux)/.exec(uas);
47 |
48 | iphone = /\b(iPhone|iP[ao]d)/.exec(uas);
49 | ipad = /\b(iP[ao]d)/.exec(uas);
50 | android = /Android/i.exec(uas);
51 | native = /FBAN\/\w+;/i.exec(uas);
52 | mobile = /Mobile/i.exec(uas);
53 |
54 | // Note that the IE team blog would have you believe you should be checking
55 | // for 'Win64; x64'. But MSDN then reveals that you can actually be coming
56 | // from either x64 or ia64; so ultimately, you should just check for Win64
57 | // as in indicator of whether you're in 64-bit IE. 32-bit IE on 64-bit
58 | // Windows will send 'WOW64' instead.
59 | win64 = !!/Win64/.exec(uas);
60 |
61 | if (agent) {
62 | if (agent[1]) {
63 | ie = parseFloat(agent[1]);
64 | } else {
65 | ie = agent[5] ? parseFloat(agent[5]) : NaN;
66 | }
67 |
68 | // IE compatibility mode
69 | // @ts-ignore
70 | if (ie && document && document.documentMode) {
71 | // @ts-ignore
72 | ie = document.documentMode;
73 | }
74 | // grab the "true" ie version from the trident token if available
75 | const trident = /(?:Trident\/(\d+.\d+))/.exec(uas);
76 | ieRealVersion = trident ? parseFloat(trident[1]) + 4 : ie;
77 |
78 | firefox = agent[2] ? parseFloat(agent[2]) : NaN;
79 | opera = agent[3] ? parseFloat(agent[3]) : NaN;
80 | webkit = agent[4] ? parseFloat(agent[4]) : NaN;
81 | if (webkit) {
82 | // We do not add the regexp to the above test, because it will always
83 | // match 'safari' only since 'AppleWebKit' appears before 'Chrome' in
84 | // the userAgent string.
85 | agent = /(?:Chrome\/(\d+\.\d+))/.exec(uas);
86 | chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
87 | } else {
88 | chrome = NaN;
89 | }
90 | } else {
91 | ie = NaN;
92 | firefox = NaN;
93 | opera = NaN;
94 | chrome = NaN;
95 | webkit = NaN;
96 | }
97 |
98 | if (os) {
99 | if (os[1]) {
100 | // Detect OS X version. If no version number matches, set osx to true.
101 | // Version examples: 10, 10_6_1, 10.7
102 | // Parses version number as a float, taking only first two sets of
103 | // digits. If only one set of digits is found, returns just the major
104 | // version number.
105 | const ver = /(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(uas);
106 |
107 | osx = ver ? parseFloat(ver[1].replace('_', '.')) : true;
108 | } else {
109 | osx = false;
110 | }
111 | windows = !!os[2];
112 | linux = !!os[3];
113 | } else {
114 | osx = false;
115 | windows = false;
116 | linux = false;
117 | }
118 | }
119 |
120 | /**
121 | * @deprecated
122 | */
123 | const UserAgent = {
124 | /**
125 | * Check if the UA is Internet Explorer.
126 | *
127 | *
128 | * @return float|NaN Version number (if match) or NaN.
129 | */
130 | ie: (): boolean => populate() || ie,
131 |
132 | /**
133 | * Check if we're in Internet Explorer compatibility mode.
134 | *
135 | * @return bool true if in compatibility mode, false if
136 | * not compatibility mode or not ie
137 | */
138 | ieCompatibilityMode: (): boolean => populate() || ieRealVersion > ie,
139 |
140 | /**
141 | * Whether the browser is 64-bit IE. Really, this is kind of weak sauce; we
142 | * only need this because Skype can't handle 64-bit IE yet. We need to remove
143 | * this when we don't need it -- tracked by #601957.
144 | */
145 | ie64: (): boolean => UserAgent.ie() && win64,
146 |
147 | /**
148 | * Check if the UA is Firefox.
149 | *
150 | *
151 | * @return float|NaN Version number (if match) or NaN.
152 | */
153 | firefox: (): boolean => populate() || firefox,
154 |
155 | /**
156 | * Check if the UA is Opera.
157 | *
158 | *
159 | * @return float|NaN Version number (if match) or NaN.
160 | */
161 | opera: (): boolean => populate() || opera,
162 |
163 | /**
164 | * Check if the UA is WebKit.
165 | *
166 | *
167 | * @return float|NaN Version number (if match) or NaN.
168 | */
169 | webkit: (): boolean => populate() || webkit,
170 |
171 | /**
172 | * For Push
173 | * WILL BE REMOVED VERY SOON. Use UserAgent_DEPRECATED.webkit
174 | */
175 | safari: (): boolean => UserAgent.webkit(),
176 |
177 | /**
178 | * Check if the UA is a Chrome browser.
179 | *
180 | *
181 | * @return float|NaN Version number (if match) or NaN.
182 | */
183 | chrome: (): boolean => populate() || chrome,
184 |
185 | /**
186 | * Check if the user is running Windows.
187 | *
188 | * @return bool `true' if the user's OS is Windows.
189 | */
190 | windows: (): boolean => populate() || windows,
191 |
192 | /**
193 | * Check if the user is running Mac OS X.
194 | *
195 | * @return float|bool Returns a float if a version number is detected,
196 | * otherwise true/false.
197 | */
198 | osx: (): boolean => populate() || osx,
199 |
200 | /**
201 | * Check if the user is running Linux.
202 | *
203 | * @return bool `true' if the user's OS is some flavor of Linux.
204 | */
205 | linux: (): boolean => populate() || linux,
206 |
207 | /**
208 | * Check if the user is running on an iPhone or iPod platform.
209 | *
210 | * @return bool `true' if the user is running some flavor of the
211 | * iPhone OS.
212 | */
213 | iphone: (): boolean => populate() || iphone,
214 | mobile: (): boolean => populate() || iphone || ipad || android || mobile,
215 |
216 | // webviews inside of the native apps
217 | nativeApp: (): boolean => populate() || native,
218 | android: (): boolean => populate() || android,
219 | ipad: (): boolean => populate() || ipad
220 | };
221 |
222 | export default UserAgent;
223 |
--------------------------------------------------------------------------------
/src/utils/camelize.ts:
--------------------------------------------------------------------------------
1 | const hyphenPattern = /-(.)/g;
2 |
3 | /**
4 | * Camelcases a hyphenated string, for example:
5 | *
6 | * > camelize('background-color')
7 | * < "backgroundColor"
8 | *
9 | * @param {string} string
10 | * @return {string}
11 | */
12 | function camelize(string) {
13 | return string.replace(hyphenPattern, (_, character) => character.toUpperCase());
14 | }
15 |
16 | export default camelize;
17 |
--------------------------------------------------------------------------------
/src/utils/camelizeStyleName.ts:
--------------------------------------------------------------------------------
1 | import { camelize } from './stringFormatter';
2 |
3 | const msPattern = /^-ms-/;
4 |
5 | export default function camelizeStyleName(name: string) {
6 | // The `-ms` prefix is converted to lowercase `ms`.
7 | // http://www.andismith.com/blog/2012/02/modernizr-prefixed/
8 | return camelize(name.replace(msPattern, 'ms-'));
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/debounce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author {@link https://github.com/jashkenas/underscore underscorejs}.
3 | * @version 1.7.0
4 | * @see {@link http://underscorejs.org/#debounce underscore.debounce(function, wait, [immediate])}
5 | * @param func
6 | * @param wait
7 | * @param immediate
8 | * @returns {*}
9 | */
10 | /* eslint-disable */
11 | export default function debounce(func, wait, immediate) {
12 |
13 | var timeout, args, context, timestamp, result;
14 |
15 | var _now = Date.now || function () {
16 | return new Date().getTime();
17 | };
18 |
19 | var later = function () {
20 | var last = _now() - timestamp;
21 | if (last < wait && last >= 0) {
22 | timeout = setTimeout(later, wait - last);
23 | } else {
24 | timeout = null;
25 | if (!immediate) {
26 | result = func.apply(context, args);
27 | if (!timeout) {
28 | context = args = null;
29 | }
30 | }
31 | }
32 | };
33 |
34 | return function () {
35 | context = this;
36 | args = arguments;
37 | timestamp = _now();
38 | var callNow = immediate && !timeout;
39 | if (!timeout) {
40 | timeout = setTimeout(later, wait);
41 | }
42 | if (callNow) {
43 | result = func.apply(context, args);
44 | context = args = null;
45 | }
46 | return result;
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/emptyFunction.ts:
--------------------------------------------------------------------------------
1 | function makeEmptyFunction(arg) {
2 | return () => arg;
3 | }
4 |
5 | function emptyFunction() {}
6 |
7 | emptyFunction.thatReturns = makeEmptyFunction;
8 | emptyFunction.thatReturnsFalse = makeEmptyFunction(false);
9 | emptyFunction.thatReturnsTrue = makeEmptyFunction(true);
10 | emptyFunction.thatReturnsNull = makeEmptyFunction(null);
11 | emptyFunction.thatReturnsThis = () => this;
12 | emptyFunction.thatReturnsArgument = arg => arg;
13 |
14 | export default emptyFunction;
15 |
--------------------------------------------------------------------------------
/src/utils/getComputedStyle.ts:
--------------------------------------------------------------------------------
1 | export default (node: Element): CSSStyleDeclaration | null => {
2 | if (!node) {
3 | throw new TypeError('No Element passed to `getComputedStyle()`');
4 | }
5 |
6 | const doc = node.ownerDocument;
7 |
8 | if ('defaultView' in doc) {
9 | if (doc.defaultView.opener) {
10 | return node.ownerDocument.defaultView.getComputedStyle(node, null);
11 | }
12 |
13 | return window.getComputedStyle(node, null);
14 | }
15 |
16 | return null;
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/getGlobal.ts:
--------------------------------------------------------------------------------
1 | // the only reliable means to get the global object is
2 | // `Function('return this')()`
3 | // However, this causes CSP violations in Chrome apps.
4 |
5 | // https://github.com/tc39/proposal-global
6 | function getGlobal() {
7 | if (typeof globalThis !== 'undefined') {
8 | return globalThis;
9 | }
10 |
11 | if (typeof self !== 'undefined') {
12 | return self;
13 | }
14 | if (typeof window !== 'undefined') {
15 | return window;
16 | }
17 |
18 | throw new Error('unable to locate global object');
19 | }
20 |
21 | export default getGlobal;
22 |
--------------------------------------------------------------------------------
/src/utils/getVendorPrefixedName.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from '../canUseDOM';
2 | import { camelize } from './stringFormatter';
3 |
4 | const memoized = {};
5 | const prefixes = ['Webkit', 'ms', 'Moz', 'O'];
6 | const prefixRegex = new RegExp(`^(${prefixes.join('|')})`);
7 | const testStyle = canUseDOM ? document.createElement('div').style : {};
8 |
9 | function getWithPrefix(name) {
10 | for (let i = 0; i < prefixes.length; i += 1) {
11 | const prefixedName = prefixes[i] + name;
12 | if (prefixedName in testStyle) {
13 | return prefixedName;
14 | }
15 | }
16 | return null;
17 | }
18 |
19 | /**
20 | * @param {string} property Name of a css property to check for.
21 | * @return {?string} property name supported in the browser, or null if not
22 | * supported.
23 | */
24 | function getVendorPrefixedName(property: string) {
25 | const name = camelize(property);
26 | if (memoized[name] === undefined) {
27 | const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
28 | if (prefixRegex.test(capitalizedName)) {
29 | throw new Error(
30 | `getVendorPrefixedName must only be called with unprefixed
31 | CSS property names. It was called with ${property}`
32 | );
33 | }
34 | memoized[name] = name in testStyle ? name : getWithPrefix(capitalizedName);
35 | }
36 | return memoized[name] || name;
37 | }
38 |
39 | export default getVendorPrefixedName;
40 |
--------------------------------------------------------------------------------
/src/utils/hyphenateStyleName.ts:
--------------------------------------------------------------------------------
1 | import { hyphenate } from './stringFormatter';
2 |
3 | const msPattern = /^ms-/;
4 |
5 | export default string => hyphenate(string).replace(msPattern, '-ms-');
6 |
--------------------------------------------------------------------------------
/src/utils/isEventSupported.ts:
--------------------------------------------------------------------------------
1 | import canUseDOM from '../canUseDOM';
2 |
3 | let useHasFeature;
4 | if (canUseDOM) {
5 | useHasFeature =
6 | document.implementation &&
7 | document.implementation.hasFeature &&
8 | // always returns true in newer browsers as per the standard.
9 | // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
10 | document.implementation.hasFeature('', '') !== true;
11 | }
12 |
13 | function isEventSupported(eventNameSuffix: string, capture?: boolean) {
14 | if (!canUseDOM || (capture && !('addEventListener' in document))) {
15 | return false;
16 | }
17 |
18 | const eventName = `on${eventNameSuffix}`;
19 | let isSupported = eventName in document;
20 |
21 | if (!isSupported) {
22 | const element = document.createElement('div');
23 | element.setAttribute(eventName, 'return;');
24 | isSupported = typeof element[eventName] === 'function';
25 | }
26 |
27 | if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
28 | // This is the only way to test support for the `wheel` event in IE9+.
29 | isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
30 | }
31 |
32 | return isSupported;
33 | }
34 |
35 | export default isEventSupported;
36 |
--------------------------------------------------------------------------------
/src/utils/normalizeWheel.ts:
--------------------------------------------------------------------------------
1 | import UserAgent from './UserAgent';
2 | import isEventSupported from './isEventSupported';
3 |
4 | // Reasonable defaults
5 | const PIXEL_STEP = 10;
6 | const LINE_HEIGHT = 40;
7 | const PAGE_HEIGHT = 800;
8 |
9 | function normalizeWheel(event: any) {
10 | let sX = 0;
11 | let sY = 0; // spinX, spinY
12 | let pX = 0;
13 | let pY = 0; // pixelX, pixelY
14 |
15 | // Legacy
16 | if ('detail' in event) {
17 | sY = event.detail;
18 | }
19 | if ('wheelDelta' in event) {
20 | sY = -event.wheelDelta / 120;
21 | }
22 | if ('wheelDeltaY' in event) {
23 | sY = -event.wheelDeltaY / 120;
24 | }
25 | if ('wheelDeltaX' in event) {
26 | sX = -event.wheelDeltaX / 120;
27 | }
28 |
29 | // side scrolling on FF with DOMMouseScroll
30 | if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
31 | sX = sY;
32 | sY = 0;
33 | }
34 |
35 | pX = sX * PIXEL_STEP;
36 | pY = sY * PIXEL_STEP;
37 |
38 | if ('deltaY' in event) {
39 | pY = event.deltaY;
40 | }
41 | if ('deltaX' in event) {
42 | pX = event.deltaX;
43 | }
44 |
45 | if ((pX || pY) && event.deltaMode) {
46 | if (event.deltaMode === 1) {
47 | // delta in LINE units
48 | pX *= LINE_HEIGHT;
49 | pY *= LINE_HEIGHT;
50 | } else {
51 | // delta in PAGE units
52 | pX *= PAGE_HEIGHT;
53 | pY *= PAGE_HEIGHT;
54 | }
55 | }
56 |
57 | // Fall-back if spin cannot be determined
58 | if (pX && !sX) {
59 | sX = pX < 1 ? -1 : 1;
60 | }
61 | if (pY && !sY) {
62 | sY = pY < 1 ? -1 : 1;
63 | }
64 |
65 | return {
66 | spinX: sX,
67 | spinY: sY,
68 | pixelX: pX,
69 | pixelY: pY
70 | };
71 | }
72 |
73 | /**
74 | * The best combination if you prefer spinX + spinY normalization. It favors
75 | * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
76 | * 'wheel' event, making spin speed determination impossible.
77 | */
78 | normalizeWheel.getEventType = () => {
79 | if (UserAgent.firefox()) {
80 | return 'DOMMouseScroll';
81 | }
82 |
83 | return isEventSupported('wheel') ? 'wheel' : 'mousewheel';
84 | };
85 |
86 | export default normalizeWheel;
87 |
--------------------------------------------------------------------------------
/src/utils/stringFormatter.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | /**
4 | * @example
5 | * underscoreName('getList');
6 | * => get_list
7 | */
8 |
9 | export function underscore(string) {
10 | return string.replace(/([A-Z])/g, '_$1').toLowerCase();
11 | }
12 |
13 | /**
14 | * @example
15 | * camelize('font-size');
16 | * => fontSize
17 | */
18 | export function camelize(string) {
19 | return string.replace(/\-(\w)/g, char => {
20 | return char.slice(1).toUpperCase();
21 | });
22 | }
23 |
24 | /**
25 | * @example
26 | * camelize('fontSize');
27 | * => font-size
28 | */
29 | export function hyphenate(string) {
30 | return string.replace(/([A-Z])/g, '-$1').toLowerCase();
31 | }
32 |
33 | /**
34 | * @example
35 | * merge('{0} - A front-end {1} ','Suite','framework');
36 | * => Suite - A front-end framework
37 | */
38 | export function merge(pattern) {
39 | var pointer = 0,
40 | i;
41 | for (i = 1; i < arguments.length; i += 1) {
42 | pattern = pattern.split(`{${pointer}}`).join(arguments[i]);
43 | pointer += 1;
44 | }
45 | return pattern;
46 | }
47 |
--------------------------------------------------------------------------------
/src/utils/throttle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author {@link https://github.com/jashkenas/underscore underscorejs}.
3 | * @version 1.7.0
4 | * @see {@link http://underscorejs.org/#throttle underscore.throttle(function, wait, [immediate])}
5 | * @param func
6 | * @param wait
7 | * @param options
8 | * @returns {throttled}
9 | */
10 |
11 | /* eslint-disable */
12 | export default function throttle(func, wait, options) {
13 | var context, args, result;
14 | var timeout = null;
15 | var previous = 0;
16 | var _now =
17 | Date.now ||
18 | function() {
19 | return new Date().getTime();
20 | };
21 | if (!options) {
22 | options = {};
23 | }
24 | var later = function() {
25 | previous = options.leading === false ? 0 : _now();
26 | timeout = null;
27 | result = func.apply(context, args);
28 | if (!timeout) {
29 | context = args = null;
30 | }
31 | };
32 | return function() {
33 | var now = _now();
34 | if (!previous && options.leading === false) {
35 | previous = now;
36 | }
37 | var remaining = wait - (now - previous);
38 | context = this;
39 | args = arguments;
40 | if (remaining <= 0 || remaining > wait) {
41 | if (timeout) {
42 | clearTimeout(timeout);
43 | timeout = null;
44 | }
45 | previous = now;
46 | result = func.apply(context, args);
47 | if (!timeout) {
48 | context = args = null;
49 | }
50 | } else if (!timeout && options.trailing !== false) {
51 | timeout = setTimeout(later, remaining);
52 | }
53 | return result;
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/test/PointerMoveTrackerSpec.js:
--------------------------------------------------------------------------------
1 | import * as lib from '../src';
2 | import simulant from 'simulant';
3 |
4 | describe('PointerMoveTracker', () => {
5 | beforeEach(() => {
6 | document.body.innerHTML = window.__html__['test/html/PointerMoveTracker.html'];
7 | });
8 |
9 | it('Should track for mouse events', done => {
10 | const target = document.getElementById('drag-target');
11 | let tracker = null;
12 |
13 | const handleDragMove = (x, y, e) => {
14 | if (e instanceof MouseEvent) {
15 | if (x && y) {
16 | expect(x).to.equal(100);
17 | expect(y).to.equal(100);
18 | }
19 | }
20 | };
21 |
22 | const handleDragEnd = () => {
23 | tracker.releaseMoves();
24 | tracker = null;
25 | done();
26 | };
27 |
28 | function handleStart(e) {
29 | if (!tracker) {
30 | tracker = new lib.PointerMoveTracker(document.body, {
31 | onMove: handleDragMove,
32 | onMoveEnd: handleDragEnd
33 | });
34 |
35 | tracker.captureMoves(e);
36 | }
37 | }
38 |
39 | target.addEventListener('mousedown', handleStart);
40 |
41 | simulant.fire(target, 'mousedown');
42 | simulant.fire(document.body, 'mousemove', { clientX: 100, clientY: 100 });
43 | simulant.fire(document.body, 'mouseup');
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/test/WheelHandlerSpec.js:
--------------------------------------------------------------------------------
1 | import { WheelHandler } from '../src';
2 |
3 | describe('WheelHandler', () => {
4 | let mockEvent;
5 | let originalNavigator = global.navigator;
6 |
7 | beforeEach(() => {
8 | mockEvent = {
9 | preventDefault: () => {},
10 | deltaX: 10,
11 | deltaY: 0
12 | };
13 |
14 | Object.defineProperty(global, 'navigator', {
15 | value: {
16 | platform: 'MacIntel'
17 | }
18 | });
19 | });
20 |
21 | after(() => {
22 | Object.defineProperty(global, 'navigator', {
23 | value: originalNavigator
24 | });
25 | });
26 |
27 | it('Should return deltaX and deltaY', done => {
28 | const wheelHandler = new WheelHandler(
29 | (dX, dY) => {
30 | expect(dX).to.equal(10);
31 | expect(dY).to.equal(0);
32 | done();
33 | },
34 | true,
35 | true
36 | );
37 |
38 | wheelHandler.onWheel(mockEvent);
39 | });
40 |
41 | it('Should normalize deltas correctly when delta unit is lines', done => {
42 | const wheelHandler = new WheelHandler(
43 | (dX, dY) => {
44 | expect(dX).to.equal(8000);
45 | expect(dY).to.equal(800);
46 | done();
47 | },
48 | true,
49 | true
50 | );
51 | wheelHandler.onWheel({
52 | ...mockEvent,
53 | deltaMode: 2,
54 | deltaX: 10,
55 | deltaY: 1
56 | });
57 | });
58 |
59 | it('Should normalize deltas when delta unit is pages', done => {
60 | const wheelHandler = new WheelHandler(
61 | (dX, dY) => {
62 | expect(dX).to.equal(400);
63 | expect(dY).to.equal(40);
64 | done();
65 | },
66 | true,
67 | true
68 | );
69 | wheelHandler.onWheel({
70 | ...mockEvent,
71 | deltaMode: 1,
72 | deltaX: 10,
73 | deltaY: 1
74 | });
75 | });
76 |
77 | it('Should take horizontal scrolling with shiftKey + wheel into account on non-MacIntel platforms', done => {
78 | Object.defineProperty(global, 'navigator', {
79 | value: {
80 | platform: 'Win64'
81 | }
82 | });
83 |
84 | const wheelHandler = new WheelHandler(
85 | (dX, dY) => {
86 | expect(dX).to.equal(10);
87 | expect(dY).to.equal(0);
88 | done();
89 | },
90 | true,
91 | true
92 | );
93 | wheelHandler.onWheel({
94 | ...mockEvent,
95 | shiftKey: true,
96 | deltaX: 0,
97 | deltaY: 10
98 | });
99 | });
100 |
101 | it('Should not treat as horizontal scrolling when event.shiftKey == true if platform is MacIntel', done => {
102 | Object.defineProperty(global, 'navigator', {
103 | value: {
104 | platform: 'MacIntel'
105 | }
106 | });
107 |
108 | const wheelHandler = new WheelHandler(
109 | (dX, dY) => {
110 | expect(dX).to.equal(0);
111 | expect(dY).to.equal(10);
112 | done();
113 | },
114 | true,
115 | true
116 | );
117 | wheelHandler.onWheel({
118 | ...mockEvent,
119 | shiftKey: true,
120 | deltaX: 0,
121 | deltaY: 10
122 | });
123 | });
124 |
125 | it('Should treat as horizontal scrolling when side scrolling on FF', done => {
126 | const wheelHandler = new WheelHandler(
127 | (dX, dY) => {
128 | expect(dX).to.equal(500);
129 | expect(dY).to.equal(0);
130 | done();
131 | },
132 | true,
133 | true
134 | );
135 | wheelHandler.onWheel({
136 | detail: 50,
137 | axis: 1,
138 | HORIZONTAL_AXIS: 1,
139 | preventDefault: () => {}
140 | });
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/test/classSpec.js:
--------------------------------------------------------------------------------
1 | import { addClass, hasClass, removeClass, toggleClass } from '../src';
2 |
3 | describe('Class', () => {
4 | beforeEach(() => {
5 | document.body.innerHTML = window.__html__['test/html/class.html'];
6 | });
7 |
8 | it('should add a class', () => {
9 | const el = document.getElementById('case-1');
10 | addClass(el, 'custom-class');
11 | expect(el.className).to.contain('custom-class');
12 | });
13 |
14 | it('should remove a class', () => {
15 | const el = document.getElementById('case-2');
16 | removeClass(el, 'test-class');
17 | expect(el.className).to.equal('');
18 | });
19 |
20 | it('should toggle a class', () => {
21 | const el = document.getElementById('case-3');
22 | toggleClass(el, 'test-class');
23 | expect(el.className).to.equal('test-class');
24 | toggleClass(el, 'test-class');
25 | expect(el.className).to.equal('');
26 | });
27 |
28 | it('should check for a class', () => {
29 | expect(hasClass(document.getElementById('case-1'), 'test-class')).to.equal(false);
30 | expect(hasClass(document.getElementById('case-2'), 'test-class')).to.equal(true);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/test/eventSpec.js:
--------------------------------------------------------------------------------
1 | import { on, off } from '../src';
2 | import simulant from 'simulant';
3 |
4 | describe('Events', () => {
5 | beforeEach(() => {
6 | document.body.innerHTML = window.__html__['test/html/events.html'];
7 | });
8 |
9 | it('should add an event listener', done => {
10 | const el = document.getElementById('case-1');
11 | on(el, 'click', () => done());
12 | simulant.fire(el, 'click');
13 | });
14 |
15 | it('should remove an event listener', () => {
16 | const el = document.getElementById('case-1');
17 | function handleEvent() {
18 | throw new Error('event fired');
19 | }
20 | on(el, 'click', handleEvent);
21 | off(el, 'click', handleEvent);
22 | simulant.fire(el, 'click');
23 | });
24 |
25 | it('should remove an event listener', () => {
26 | const el = document.getElementById('case-1');
27 | function handleEvent() {
28 | throw new Error('event fired');
29 | }
30 | on(el, 'click', handleEvent).off();
31 | simulant.fire(el, 'click');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/test/html/PointerMoveTracker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | PointerMoveTracker
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
20 |
21 |
22 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test/html/WheelHandler.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WheelHandler
8 |
9 |
10 |
11 |
12 | 😀 😁 😂 🤣 😃😄 😅 😆 😉 😊😫 😴 😌 😛 😜👆🏻 😒 😓 😔 👇🏻😑 😶 🙄 😏 😣 😞 😟 😤 😢 😭🤑 😲
13 | 🙄 🙁 😖👍 👎 👊 ✊ 🤛🙄 ✋ 🤚 🖐 🖖👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼 ☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦
14 | 💧🐠 🐟 🐬 🐳 🐋😬 😐 😕 😯 😶 😇 😏 😑 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆💛 👐
15 | 👎 👌 💘 👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋 😬 😐 😕 😯
16 | 😶😇 😏 😑 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆 💛 👐 👎 👌 💘 😀 😁 😂 🤣 😃😄
17 | 😅 😆 😉 😊😫 😴 😌 😛 😜👆🏻 😒 😓 😔 👇🏻😑 😶 🙄 😏 😣 😞 😟 😤 😢 😭🤑 😲 🙄 🙁 😖👍 👎 👊
18 | ✊ 🤛🙄 ✋ 🤚 🖐 🖖👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼 ☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋😬
19 | 😐 😕 😯 😶 😇 😏 😑 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆💛 👐 👎 👌 💘 👍🏼 👎🏼 👊🏼
20 | ✊🏼 🤛🏼☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋 😬 😐 😕 😯 😶😇 😏 😑 😓 😵🐥
21 | 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆 💛 👐 👎 👌 💘 😀 😁 😂 🤣 😃😄 😅 😆 😉 😊😫 😴 😌
22 | 😛 😜👆🏻 😒 😓 😔 👇🏻😑 😶 🙄 😏 😣 😞 😟 😤 😢 😭🤑 😲 🙄 🙁 😖👍 👎 👊 ✊ 🤛🙄 ✋ 🤚 🖐 🖖👍🏼
23 | 👎🏼 👊🏼 ✊🏼 🤛🏼 ☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋😬 😐 😕 😯 😶 😇 😏 😑
24 | 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆💛 👐 👎 👌 💘 👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖
25 | 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋 😬 😐 😕 😯 😶😇 😏 😑 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔
26 | ✊ ✋👇 👊 👍 👈 👆 💛 👐 👎 👌 💘 😀 😁 😂 🤣 😃😄 😅 😆 😉 😊😫 😴 😌 😛 😜👆🏻 😒 😓 😔
27 | 👇🏻😑 😶 🙄 😏 😣 😞 😟 😤 😢 😭🤑 😲 🙄 🙁 😖👍 👎 👊 ✊ 🤛🙄 ✋ 🤚 🖐 🖖👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼 ☝🏽
28 | ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋😬 😐 😕 😯 😶 😇 😏 😑 😓 😵🐥 🐣 🐔
29 | 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍 👈 👆💛 👐 👎 👌 💘 👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽🌖 🌗 🌘 🌑 🌒💫
30 | 💥 💢 💦 💧🐠 🐟 🐬 🐳 🐋 😬 😐 😕 😯 😶😇 😏 😑 😓 😵🐥 🐣 🐔 🐛 🐤💪 ✨ 🔔 ✊ ✋👇 👊 👍
31 | 👈 👆 💛 👐 👎 👌 💘
32 |
33 |
34 |
35 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/test/html/class.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/html/events.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/html/query.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/test/html/style.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const testsContext = require.context('.', true, /Spec$/);
2 | testsContext.keys().forEach(testsContext);
3 |
--------------------------------------------------------------------------------
/test/querySpec.js:
--------------------------------------------------------------------------------
1 | import * as lib from '../src';
2 | import $ from 'jquery';
3 |
4 | describe('Query', () => {
5 | beforeEach(() => {
6 | document.body.innerHTML = window.__html__['test/html/query.html'];
7 | });
8 |
9 | it('should get 100 of height', () => {
10 | const el = document.getElementById('case-1');
11 | const height = lib.getHeight(el);
12 | expect(height).to.equal(100);
13 | });
14 |
15 | it('should get 200 of width', () => {
16 | const el = document.getElementById('case-1');
17 | const height = lib.getWidth(el);
18 | expect(height).to.equal(200);
19 | });
20 |
21 | it('should handle fixed position', () => {
22 | const el = document.getElementById('case-2');
23 | const position = lib.getPosition(el);
24 | const $position = $('#case-2').position();
25 | expect(position.left).to.equal($position.left);
26 | expect(position.top).to.equal($position.top);
27 | });
28 |
29 | it('should handle absolute position', () => {
30 | const el = document.getElementById('case-3');
31 | const position = lib.getPosition(el);
32 | const $position = $('#case-3').position();
33 |
34 | expect(position.left).to.equal($position.left);
35 | expect(position.top).to.equal($position.top);
36 | });
37 |
38 | it('should handle scroll position', () => {
39 | const el = document.getElementById('case-4');
40 | lib.scrollTop(el, 100);
41 | lib.scrollLeft(el, 200);
42 |
43 | expect(100).to.equal($('#case-4').scrollTop());
44 | expect(200).to.equal($('#case-4').scrollLeft());
45 | });
46 |
47 | describe('contains(context, node)', () => {
48 | it('should check for contained element', () => {
49 | const el4 = document.getElementById('case-4');
50 | const el5 = document.getElementById('case-5');
51 | const el6 = document.getElementById('case-6');
52 |
53 | expect(lib.contains(el5, el4)).to.equal(false);
54 | expect(lib.contains(el5, el6)).to.equal(true);
55 | });
56 |
57 | it('should return false if node is null', () => {
58 | const el4 = document.getElementById('case-4');
59 | expect(lib.contains(el4, null)).to.be.false;
60 | });
61 | });
62 |
63 | it('should container with offset', () => {
64 | const container = document.getElementById('case-7');
65 | const node = document.getElementById('case-8');
66 |
67 | const posi = lib.getPosition(node, container, false);
68 | expect(posi.top).to.equal(20);
69 | expect(posi.left).to.equal(10);
70 | });
71 |
72 | describe('isFocusable', () => {
73 | function createElement(type, props = {}) {
74 | const element = document.createElement(type, props);
75 | const keys = Object.keys(props);
76 | for (const prop of keys) {
77 | const value = props[prop];
78 | element[prop] = value;
79 | element.setAttribute(prop.toLowerCase(), `${value}`);
80 | }
81 |
82 | document.body.appendChild(element);
83 |
84 | return element;
85 | }
86 |
87 | it('should return true for focusable element', () => {
88 | expect(
89 | lib.isFocusable(document.createElementNS('http://www.w3.org/2000/svg', 'svg'))
90 | ).to.equal(false);
91 | expect(lib.isFocusable(createElement('input'))).to.equal(true);
92 | expect(lib.isFocusable(createElement('input', { tabIndex: -1 }))).to.equal(true);
93 | expect(lib.isFocusable(createElement('input', { hidden: true }))).to.equal(false);
94 | expect(lib.isFocusable(createElement('input', { disabled: true }))).to.equal(false);
95 | expect(lib.isFocusable(createElement('a'))).to.equal(false);
96 | expect(lib.isFocusable(createElement('a', { href: '' }))).to.equal(true);
97 | expect(lib.isFocusable(createElement('audio'))).to.equal(false);
98 | expect(lib.isFocusable(createElement('audio', { controls: true }))).to.equal(true);
99 | expect(lib.isFocusable(createElement('video'))).to.equal(false);
100 | expect(lib.isFocusable(createElement('video', { controls: true }))).to.equal(true);
101 | expect(lib.isFocusable(createElement('div'))).to.equal(false);
102 | expect(lib.isFocusable(createElement('div', { contentEditable: true }))).to.equal(true);
103 | expect(lib.isFocusable(createElement('div', { tabIndex: 0 }))).to.equal(true);
104 | expect(lib.isFocusable(createElement('div', { tabIndex: -1 }))).to.equal(true);
105 | });
106 | });
107 |
108 | describe('getContainer', () => {
109 | it('Should return the container if present', () => {
110 | const container = {};
111 |
112 | expect(lib.getContainer(container)).to.equal(container);
113 | });
114 |
115 | it('Should return the return value of container when container is a function', () => {
116 | const container = {};
117 |
118 | expect(lib.getContainer(() => container)).to.equal(container);
119 | });
120 |
121 | it('Should return defaultContainer if container is null', () => {
122 | const defaultContainer = {};
123 |
124 | expect(lib.getContainer(null, defaultContainer)).to.equal(defaultContainer);
125 | });
126 |
127 | it('Should return defaultContainer if container returns null', () => {
128 | const defaultContainer = {};
129 |
130 | expect(lib.getContainer(() => null, defaultContainer)).to.equal(defaultContainer);
131 | });
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/test/styleSpec.js:
--------------------------------------------------------------------------------
1 | import { getStyle, removeStyle, addStyle, translateDOMPositionXY } from '../src';
2 | import { getTranslateDOMPositionXY } from '../src/translateDOMPositionXY';
3 |
4 | describe('Style', () => {
5 | beforeEach(() => {
6 | document.body.innerHTML = window.__html__['test/html/style.html'];
7 | });
8 |
9 | describe('getStyle', () => {
10 | it('Should return complete style text', () => {
11 | const el = document.getElementById('case-1');
12 | const style = getStyle(el);
13 | expect(style.color).to.contain('rgb(255, 0, 0)');
14 | expect(style.marginLeft).to.contain('1px');
15 | });
16 |
17 | it('Should return style value of specific property', () => {
18 | const el = document.getElementById('case-1');
19 | const color = getStyle(el, 'color');
20 | expect(color).to.contain('rgb(255, 0, 0)');
21 | });
22 | });
23 |
24 | describe('addStyle', () => {
25 | it('Should add a single style property with specific value', () => {
26 | const el = document.getElementById('case-2');
27 | addStyle(el, 'color', '#ffffff');
28 |
29 | const style = getStyle(el);
30 | expect(style.color).to.contain('rgb(255, 255, 255)');
31 | });
32 |
33 | it('Should add multiple style properties with specific values', () => {
34 | const el = document.getElementById('case-2');
35 | addStyle(el, {
36 | background: '#ff0000',
37 | 'margin-left': '4px'
38 | });
39 |
40 | const style = getStyle(el);
41 | expect(style.background).to.contain('rgb(255, 0, 0)');
42 | expect(style.marginLeft).to.contain('4px');
43 | });
44 | });
45 |
46 | describe('removeStyle', () => {
47 | it('Should remove a single style property', () => {
48 | const el = document.getElementById('case-3');
49 | removeStyle(el, 'color');
50 | const style = getStyle(el);
51 | expect(style.color).to.be.empty;
52 | expect(style.background).to.contain('rgb(255, 0, 0)');
53 | });
54 |
55 | it('Should remove multiple style properties', () => {
56 | const el = document.getElementById('case-3');
57 | removeStyle(el, ['margin-left', 'margin-right']);
58 |
59 | const style = getStyle(el);
60 | expect(style.marginLeft).to.be.empty;
61 | expect(style.marginRight).to.be.empty;
62 | expect(style.background).to.contain('rgb(255, 0, 0)');
63 | });
64 | });
65 |
66 | describe('translateDOMPositionXY', () => {
67 | it('Should use translate3d by default', () => {
68 | const style = {};
69 | translateDOMPositionXY(style, 10, 20);
70 |
71 | expect(style.transform).to.contain('translate3d(10px,20px,0)');
72 | expect(style.backfaceVisibility).to.contain('hidden');
73 | });
74 |
75 | it('Should be disable translate3d', () => {
76 | const translateDOMPositionXY = getTranslateDOMPositionXY({ enable3DTransform: false });
77 | const style = {};
78 | translateDOMPositionXY(style, 10, 20);
79 |
80 | expect(style.transform).to.contain('translate(10px,20px)');
81 | });
82 |
83 | it('Should be forced to use translate3d', () => {
84 | const translateDOMPositionXY = getTranslateDOMPositionXY({
85 | forceUseTransform: true,
86 | enable3DTransform: true
87 | });
88 | const style = {};
89 | translateDOMPositionXY(style, 10, 20);
90 |
91 | expect(style.transform).to.contain('translate3d(10px,20px,0)');
92 | expect(style.backfaceVisibility).to.contain('hidden');
93 | });
94 |
95 | it('Should be forced to use translate3d', () => {
96 | const translateDOMPositionXY = getTranslateDOMPositionXY({
97 | forceUseTransform: true,
98 | enable3DTransform: false
99 | });
100 | const style = {};
101 | translateDOMPositionXY(style, 10, 20);
102 |
103 | expect(style.transform).to.contain('translate(10px,20px)');
104 | });
105 | it('Should be use position', () => {
106 | const translateDOMPositionXY = getTranslateDOMPositionXY({
107 | enableTransform: false
108 | });
109 | const style = {};
110 | translateDOMPositionXY(style, 10, 20);
111 |
112 | expect(style.left).to.contain('10px');
113 | expect(style.top).to.contain('20px');
114 | });
115 | });
116 | });
117 |
--------------------------------------------------------------------------------
/test/utilsSpec.js:
--------------------------------------------------------------------------------
1 | import camelizeStyleName from '../src/utils/camelizeStyleName';
2 |
3 | describe('Utils', () => {
4 | describe('camelizeStyleName', () => {
5 | // https://www.andismith.com/blogs/2012/02/modernizr-prefixed/
6 | it('Should return the correct Modernizr prefix', () => {
7 | expect(camelizeStyleName('-ms-transform')).to.equal('msTransform');
8 | expect(camelizeStyleName('-moz-transform')).to.equal('MozTransform');
9 | expect(camelizeStyleName('-o-transform')).to.equal('OTransform');
10 | expect(camelizeStyleName('-webkit-transform')).to.equal('WebkitTransform');
11 | expect(camelizeStyleName('transform')).to.equal('transform');
12 | });
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "allowJs": true,
5 | "allowSyntheticDefaultImports": true,
6 | "esModuleInterop": true,
7 | "noImplicitAny": false,
8 | "noUnusedParameters": true,
9 | "noUnusedLocals": true,
10 | "sourceMap": true,
11 | "moduleResolution": "node",
12 | "target": "esnext"
13 | },
14 | "include": ["./src/**/*.ts"]
15 | }
16 |
--------------------------------------------------------------------------------