├── .babelrc
├── .codeclimate.yml
├── .codesandbox
└── ci.json
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo
├── .prettierrc
├── package.json
├── public
│ └── index.html
├── src
│ ├── App.tsx
│ ├── GitHubRepo.tsx
│ ├── components.tsx
│ ├── index.tsx
│ └── styles.css
└── tsconfig.json
├── jest.config.ts
├── package-lock.json
├── package.json
├── src
└── index.ts
├── test
├── __setup__
│ └── index.ts
└── index.spec.ts
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "corejs": 3,
7 | "useBuiltIns": "entry",
8 | "modules": false
9 | }
10 | ]
11 | ],
12 | "plugins": [
13 | "@babel/plugin-proposal-class-properties",
14 | [
15 | "@babel/plugin-proposal-decorators",
16 | {
17 | "legacy": true
18 | }
19 | ],
20 | "@babel/plugin-proposal-do-expressions",
21 | "@babel/plugin-proposal-export-default-from",
22 | "@babel/plugin-proposal-export-namespace-from",
23 | "@babel/plugin-proposal-function-sent",
24 | "@babel/plugin-proposal-json-strings",
25 | "@babel/plugin-proposal-logical-assignment-operators",
26 | "@babel/plugin-proposal-nullish-coalescing-operator",
27 | "@babel/plugin-proposal-numeric-separator",
28 | "@babel/plugin-proposal-optional-chaining",
29 | [
30 | "@babel/plugin-proposal-pipeline-operator",
31 | {
32 | "proposal": "minimal"
33 | }
34 | ],
35 | "@babel/plugin-proposal-throw-expressions",
36 | "@babel/plugin-syntax-dynamic-import",
37 | "@babel/plugin-syntax-import-meta"
38 | ],
39 | "compact": false,
40 | "env": {
41 | "production": {
42 | "plugins": [
43 | "array-includes"
44 | ]
45 | },
46 | "commonjs": {
47 | "plugins": [
48 | [
49 | "@babel/plugin-transform-modules-commonjs",
50 | {
51 | "loose": true
52 | }
53 | ]
54 | ]
55 | },
56 | "test": {
57 | "plugins": [
58 | "@babel/plugin-transform-modules-commonjs"
59 | ],
60 | "sourceMaps": "both"
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: "2"
3 | checks:
4 | argument-count:
5 | config:
6 | threshold: 4
7 | complex-logic:
8 | config:
9 | threshold: 4
10 | file-lines:
11 | config:
12 | threshold: 500
13 | method-complexity:
14 | config:
15 | threshold: 20
16 | method-count:
17 | config:
18 | threshold: 20
19 | method-lines:
20 | config:
21 | threshold: 50
22 | nested-control-flow:
23 | config:
24 | threshold: 4
25 | return-statements:
26 | config:
27 | threshold: 4
28 | similar-code:
29 | config:
30 | threshold: 100
31 | identical-code:
32 | config:
33 | threshold: 20
34 | exclude_patterns:
35 | - coverage/**/*
36 | - demo/**/*
37 | - esm/**/*
38 | - lib/**/*
39 | - node_modules/**/*
40 | - test/**/*
41 |
--------------------------------------------------------------------------------
/.codesandbox/ci.json:
--------------------------------------------------------------------------------
1 | {
2 | "sandboxes": ["/demo"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | # change these settings to your own preference
9 | indent_style = space
10 | indent_size = 2
11 |
12 | # we recommend you to keep these unchanged
13 | end_of_line = lf
14 | charset = utf-8
15 | trim_trailing_whitespace = true
16 | insert_final_newline = true
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "airbnb-base",
4 | "plugin:@typescript-eslint/recommended",
5 | "plugin:import/typescript",
6 | "plugin:prettier/recommended",
7 | "prettier/standard"
8 | ],
9 | "parser": "@typescript-eslint/parser",
10 | "env": {
11 | "browser": true,
12 | "jest": true
13 | },
14 | "plugins": ["@typescript-eslint/eslint-plugin", "prettier"],
15 | "overrides": [
16 | {
17 | "files": ["test/**/*.ts?(x)"],
18 | "rules": {
19 | "@typescript-eslint/ban-ts-comment": "off",
20 | "@typescript-eslint/no-explicit-any": "off"
21 | }
22 | }
23 | ],
24 | "rules": {
25 | "lines-between-class-members": "off",
26 | "import/extensions": ["warn", "never"]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | esm/
3 | lib/
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '12'
4 | cache:
5 | directories:
6 | - "node_modules"
7 | before_script:
8 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
9 | - chmod +x ./cc-test-reporter
10 | - ./cc-test-reporter before-build
11 | script:
12 | - npm run validate
13 | after_success:
14 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Gil Barbara
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | disable scroll
2 | ===
3 |
4 | [](https://www.npmjs.com/package/disable-scroll) [](https://travis-ci.org/gilbarbara/disable-scroll) [](https://codeclimate.com/github/gilbarbara/disable-scroll/maintainability) [](https://codeclimate.com/github/gilbarbara/disable-scroll/test_coverage)
5 |
6 | **Prevent page scrolling like a boss.**
7 | Supports `scroll, wheel, touchmove, keydown` events.
8 |
9 | [Demo](https://codesandbox.io/s/github/gilbarbara/disable-scroll/tree/master/demo)
10 |
11 | ## Setup
12 |
13 | ### npm
14 | ```bash
15 | npm install --save disable-scroll
16 | ```
17 |
18 | and import it
19 |
20 | ```typescript
21 | import disableScroll from 'disable-scroll';
22 | ```
23 |
24 | ## Usage
25 |
26 | ```typescript
27 | disableScroll.on(); // prevent scrolling
28 | ...
29 | disableScroll.off(); // re-enable scroll
30 | ```
31 |
32 | > If you need to support legacy browsers you need to include the scrollingelement polyfill.
33 |
34 | ## API
35 |
36 | ### .on(element?: Element, options?: Options)
37 | Disable page scroll by adding event listeners and locking the scroll position.
38 |
39 | Options defaults to:
40 |
41 | ```typescript
42 | {
43 | authorizedInInputs: [32, 37, 38, 39, 40],
44 | disableKeys: true,
45 | disableScroll: true,
46 | disableWheel: true,
47 | keyboardKeys: [32, 33, 34, 35, 36, 37, 38, 39, 40],
48 | }
49 | ```
50 |
51 | ### .off()
52 | Re-enable page scrolling and remove the listeners.
53 |
54 | ## Issues
55 |
56 | If you find a bug, please file an issue on [our issue tracker on GitHub](https://github.com/gilbarbara/disable-scroll/issues).
57 |
58 | ## License
59 |
60 | MIT
61 |
62 | ---
63 |
64 | Inspired by [jquery-disablescroll](https://github.com/ultrapasty/jquery-disablescroll)
65 |
--------------------------------------------------------------------------------
/demo/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "printWidth": 100,
4 | "singleQuote": true,
5 | "trailingComma": "all"
6 | }
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "disable-scroll-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.tsx",
7 | "dependencies": {
8 | "disable-scroll": "0.6.0",
9 | "react": "17.0.1",
10 | "react-dom": "17.0.1",
11 | "react-scripts": "4.0.1",
12 | "styled-components": "5.2.1"
13 | },
14 | "devDependencies": {
15 | "@types/react": "16.9.19",
16 | "@types/react-dom": "16.9.5",
17 | "@types/styled-components": "5.1.4",
18 | "typescript": "3.7.5"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test --env=jsdom",
24 | "eject": "react-scripts eject"
25 | },
26 | "browserslist": [
27 | ">0.2%",
28 | "not dead",
29 | "not ie <= 11",
30 | "not op_mini all"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import disableScroll from 'disable-scroll';
3 |
4 | import { Button, Header, H1, Input, Step, Steps, Wrapper } from './components';
5 | import GitHubRepo from "./GitHubRepo";
6 |
7 | import './styles.css';
8 |
9 | class App extends React.Component {
10 | state = {
11 | isDisabled: false,
12 | };
13 |
14 | handleClick = () => {
15 | const { isDisabled } = this.state;
16 |
17 | disableScroll[isDisabled ? 'off' : 'on']();
18 | this.setState({ isDisabled: !isDisabled });
19 | };
20 |
21 | renderSteps() {
22 | const colors = [
23 | '#5bc0eb',
24 | '#fde74c',
25 | '#9bc53d',
26 | '#e55934',
27 | '#fa7921',
28 | '#8F7892',
29 | '#f25f5c',
30 | '#ffe066',
31 | '#247ba0',
32 | '#98A584',
33 | ];
34 | const steps = [];
35 | let colorIdx = 0;
36 |
37 | for (let i = 200; i <= 5000; ) {
38 | steps.push(
39 |
45 | {i}
46 | ,
47 | );
48 | i += 200;
49 | colorIdx++;
50 | }
51 |
52 | return {steps};
53 | }
54 |
55 | render() {
56 | const { isDisabled } = this.state;
57 |
58 | return (
59 |
60 |
61 |
62 |
81 | {this.renderSteps()}
82 |
83 | );
84 | }
85 | }
86 |
87 | export default App;
88 |
--------------------------------------------------------------------------------
/demo/src/GitHubRepo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Wrapper = styled.a`
5 | position: fixed;
6 | top: 0;
7 | right: 0;
8 | z-index: 100;
9 |
10 | &:hover {
11 | .octo-arm {
12 | animation: octocat-wave 560ms ease-in-out;
13 | }
14 | }
15 |
16 | svg {
17 | fill: #000;
18 | color: #fff;
19 | }
20 |
21 | .octo-arm {
22 | transform-origin: 130px 106px;
23 | }
24 |
25 | @keyframes octocat-wave {
26 | 0%,
27 | 100% {
28 | transform: rotate(0);
29 | }
30 | 20%,
31 | 60% {
32 | transform: rotate(-25deg);
33 | }
34 | 40%,
35 | 80% {
36 | transform: rotate(10deg);
37 | }
38 | }
39 | `;
40 |
41 | const GitHubRepo = () => {
42 | return (
43 |
50 |
63 |
64 | );
65 | };
66 |
67 | export default GitHubRepo;
68 |
--------------------------------------------------------------------------------
/demo/src/components.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.main`
4 | font-family: sans-serif;
5 | text-align: center;
6 | `;
7 |
8 | export const Steps = styled.div`
9 | margin-top: 200px;
10 | `;
11 |
12 | export const Step = styled.div`
13 | align-items: center;
14 | color: rgba(0, 0, 0, 0.4);
15 | display: flex;
16 | font-size: 60px;
17 | height: 200px;
18 | justify-content: center;
19 | padding: 20px;
20 | `;
21 |
22 | export const Button = styled.button`
23 | -webkit-appearance: none;
24 | background-color: #000;
25 | border-radius: 3px;
26 | border: 0;
27 | color: #fff;
28 | font-size: 12px;
29 | font-weight: bold;
30 | line-height: 1;
31 | outline: none;
32 | padding: 6px 10px;
33 | white-space: nowrap;
34 |
35 | &:disabled {
36 | background-color: #999;
37 | font-weight: normal;
38 | pointer-events: none;
39 | }
40 | `;
41 |
42 | export const Header = styled.header`
43 | height: 200px;
44 | left: 0;
45 | padding-top: 50px;
46 | position: fixed;
47 | text-align: center;
48 | top: 0;
49 | right: 0;
50 | `;
51 |
52 | export const H1 = styled.h1`
53 | margin-top: 0;
54 | `;
55 |
56 | export const Input = styled.input`
57 | border: 1px solid #ccc;
58 | display: block;
59 | font-size: 16px;
60 | line-height: 1;
61 | margin: 15px auto 0;
62 | padding: 4px;
63 | text-align: center;
64 | width: 200px;
65 |
66 | &::placeholder {
67 | color: #ccc;
68 | }
69 | `;
70 |
--------------------------------------------------------------------------------
/demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { render } from "react-dom";
3 |
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | render(, rootElement);
8 |
--------------------------------------------------------------------------------
/demo/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #f7f7f7;
3 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | * {
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "./src/**/*"
4 | ],
5 | "compilerOptions": {
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "lib": [
9 | "dom",
10 | "es2015"
11 | ],
12 | "jsx": "react"
13 | }
14 | }
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverage: false,
3 | collectCoverageFrom: ['src/**/*.ts'],
4 | coverageThreshold: {
5 | global: {
6 | branches: 80,
7 | functions: 90,
8 | lines: 90,
9 | statements: 90,
10 | },
11 | },
12 | globals: {
13 | 'ts-jest': {
14 | tsconfig: 'tsconfig.json',
15 | },
16 | },
17 | moduleDirectories: ['node_modules', 'src', './'],
18 | moduleFileExtensions: ['js', 'json', 'ts'],
19 | preset: 'ts-jest',
20 | setupFiles: ['/test/__setup__/index.ts'],
21 | testRegex: '/test/.*?\\.(test|spec)\\.ts$',
22 | testURL: 'http://localhost:3000',
23 | verbose: false,
24 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
25 | };
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "disable-scroll",
3 | "version": "0.6.0",
4 | "description": "Prevent page scroll",
5 | "author": "Gil Barbara ",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/gilbarbara/disable-scroll.git"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/gilbarbara/disable-scroll/issues"
12 | },
13 | "main": "lib/index.js",
14 | "module": "esm/index.js",
15 | "files": [
16 | "esm",
17 | "lib",
18 | "src"
19 | ],
20 | "types": "esm",
21 | "sideEffects": true,
22 | "license": "MIT",
23 | "keywords": [
24 | "scroll"
25 | ],
26 | "dependencies": {},
27 | "devDependencies": {
28 | "@gilbarbara/tsconfig": "^0.1.0",
29 | "@size-limit/preset-small-lib": "^4.9.1",
30 | "@types/jest": "^26.0.16",
31 | "@types/node": "^14.14.10",
32 | "@typescript-eslint/eslint-plugin": "^4.9.0",
33 | "@typescript-eslint/parser": "^4.9.0",
34 | "del-cli": "^3.0.1",
35 | "eslint": "^7.14.0",
36 | "eslint-config-airbnb-base": "^14.2.1",
37 | "eslint-config-prettier": "^6.15.0",
38 | "eslint-plugin-babel": "^5.3.1",
39 | "eslint-plugin-import": "^2.22.1",
40 | "eslint-plugin-prettier": "^3.1.4",
41 | "husky": "^4.3.0",
42 | "jest": "^26.6.3",
43 | "jest-extended": "^0.11.5",
44 | "jest-watch-typeahead": "^0.6.1",
45 | "prettier": "^2.2.1",
46 | "repo-tools": "^0.2.0",
47 | "size-limit": "^4.9.1",
48 | "ts-jest": "^26.4.4",
49 | "ts-node": "^9.1.0",
50 | "typescript": "^4.1.2"
51 | },
52 | "scripts": {
53 | "build": "npm run clean && npm run build:cjs && npm run build:esm",
54 | "build:cjs": "tsc",
55 | "build:esm": "tsc -m es6 --outDir esm",
56 | "clean": "del lib/* && del esm/*",
57 | "watch:cjs": "npm run build:cjs -- -w",
58 | "watch:esm": "npm run build:esm -- -w",
59 | "lint": "eslint --ext .ts src test",
60 | "test": "jest --bail --coverage",
61 | "test:watch": "jest --watchAll --verbose",
62 | "validate": "npm run lint && npm test && npm run build && npm run size",
63 | "size": "size-limit",
64 | "prepublishOnly": "npm run validate"
65 | },
66 | "prettier": {
67 | "jsxBracketSameLine": false,
68 | "printWidth": 100,
69 | "singleQuote": true,
70 | "trailingComma": "all"
71 | },
72 | "size-limit": [
73 | {
74 | "path": "./esm/index.js",
75 | "limit": "1 kB"
76 | },
77 | {
78 | "path": "./lib/index.js",
79 | "limit": "1 kB"
80 | }
81 | ],
82 | "husky": {
83 | "hooks": {
84 | "post-merge": "repo-tools install-packages",
85 | "pre-commit": "repo-tools check-remote && npm run validate"
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | const canUseDOM = () =>
2 | typeof window !== 'undefined' && !!window.document && !!window.document.createElement;
3 |
4 | interface Options {
5 | authorizedInInputs: number[];
6 | disableKeys: boolean;
7 | disableScroll: boolean;
8 | disableWheel: boolean;
9 | keyboardKeys: number[];
10 | }
11 |
12 | class DisableScroll {
13 | element: Element | null;
14 | lockToScrollPos: [number, number];
15 | options: Options;
16 |
17 | constructor() {
18 | this.element = null;
19 | this.lockToScrollPos = [0, 0];
20 | this.options = {
21 | authorizedInInputs: [32, 37, 38, 39, 40],
22 | disableKeys: true,
23 | disableScroll: true,
24 | disableWheel: true,
25 | keyboardKeys: [32, 33, 34, 35, 36, 37, 38, 39, 40],
26 | // space: 32, page up: 33, page down: 34, end: 35, home: 36
27 | // left: 37, up: 38, right: 39, down: 40
28 | };
29 |
30 | /* istanbul ignore else */
31 | if (canUseDOM()) {
32 | this.element = document.scrollingElement;
33 | }
34 | }
35 |
36 | /**
37 | * Disable Page Scroll
38 | */
39 | on(element?: Element, options?: Partial) {
40 | if (!canUseDOM()) return;
41 |
42 | this.element = element || this.element;
43 | this.options = {
44 | ...this.options,
45 | ...options,
46 | };
47 |
48 | const { disableKeys, disableScroll, disableWheel } = this.options;
49 |
50 | /* istanbul ignore else */
51 | if (disableWheel) {
52 | document.addEventListener('wheel', this.handleWheel, { passive: false });
53 | document.addEventListener('touchmove', this.handleWheel, { passive: false });
54 | }
55 |
56 | /* istanbul ignore else */
57 | if (disableScroll) {
58 | this.lockToScrollPos = [this.element?.scrollLeft ?? 0, this.element?.scrollTop ?? 0];
59 | document.addEventListener('scroll', this.handleScroll, { passive: false });
60 | }
61 |
62 | /* istanbul ignore else */
63 | if (disableKeys) {
64 | document.addEventListener('keydown', this.handleKeydown, { passive: false });
65 | }
66 | }
67 |
68 | /**
69 | * Re-enable page scrolls
70 | */
71 | off() {
72 | if (!canUseDOM()) return;
73 |
74 | document.removeEventListener('wheel', this.handleWheel);
75 | document.removeEventListener('touchmove', this.handleWheel);
76 | document.removeEventListener('scroll', this.handleScroll);
77 | document.removeEventListener('keydown', this.handleKeydown);
78 | }
79 |
80 | handleWheel = (e: WheelEvent | TouchEvent) => {
81 | e.preventDefault();
82 | };
83 |
84 | handleScroll = () => {
85 | window.scrollTo(...this.lockToScrollPos);
86 | };
87 |
88 | handleKeydown = (e: KeyboardEvent) => {
89 | let keys = this.options.keyboardKeys;
90 |
91 | /* istanbul ignore else */
92 | if (
93 | ['INPUT', 'TEXTAREA'].includes((e.target as HTMLInputElement | HTMLTextAreaElement).tagName)
94 | ) {
95 | keys = keys.filter((key) => !this.options.authorizedInInputs.includes(key));
96 | }
97 |
98 | /* istanbul ignore else */
99 | if (keys.includes(e.keyCode)) {
100 | e.preventDefault();
101 | }
102 | };
103 | }
104 |
105 | export default new DisableScroll();
106 |
--------------------------------------------------------------------------------
/test/__setup__/index.ts:
--------------------------------------------------------------------------------
1 | declare const global: any;
2 |
3 | global.requestAnimationFrame = (callback: () => void) => {
4 | setTimeout(callback, 0);
5 | };
6 |
7 | global.matchMedia = () => ({
8 | matches: false,
9 | addListener: () => undefined,
10 | removeListener: () => undefined,
11 | });
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/test/index.spec.ts:
--------------------------------------------------------------------------------
1 | import disableScroll from '../src';
2 |
3 | global.scrollTo = () => undefined;
4 |
5 | function dispatchEvents() {
6 | document.dispatchEvent(new Event('scroll'));
7 | document.dispatchEvent(new Event('wheel'));
8 | document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 32 }));
9 | }
10 |
11 | describe('disable-scroll', () => {
12 | const scrollListener = jest.spyOn(disableScroll, 'handleScroll');
13 | const wheelListener = jest.spyOn(disableScroll, 'handleWheel');
14 | const keyboardListener = jest.spyOn(disableScroll, 'handleKeydown');
15 |
16 | describe('with DOM', () => {
17 | afterEach(() => {
18 | jest.clearAllMocks();
19 | disableScroll.off();
20 | });
21 |
22 | it('should have initialized', () => {
23 | expect(disableScroll.element).toBe(document.scrollingElement);
24 | });
25 |
26 | it('should have added the listeners when turning it ON', () => {
27 | disableScroll.on();
28 | dispatchEvents();
29 |
30 | expect(scrollListener).toHaveBeenCalledTimes(1);
31 | expect(wheelListener).toHaveBeenCalledTimes(1);
32 | expect(keyboardListener).toHaveBeenCalledTimes(1);
33 | });
34 |
35 | it('should have removed the listeners ', () => {
36 | disableScroll.off();
37 | dispatchEvents();
38 |
39 | expect(scrollListener).toHaveBeenCalledTimes(0);
40 | expect(wheelListener).toHaveBeenCalledTimes(0);
41 | expect(keyboardListener).toHaveBeenCalledTimes(0);
42 | });
43 |
44 | it('should not prevent space in input', () => {
45 | disableScroll.on();
46 |
47 | const input = document.createElement('input');
48 | input.setAttribute('type', 'text');
49 | input.setAttribute('name', 'test-input');
50 | document.body.appendChild(input);
51 |
52 | const event = input.dispatchEvent(
53 | new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 32 }),
54 | );
55 |
56 | expect(keyboardListener).toHaveBeenCalledTimes(1);
57 | expect(event).toEqual(true);
58 | });
59 |
60 | it('should prevent page down in input', () => {
61 | disableScroll.on();
62 |
63 | const input = document.createElement('input');
64 | input.setAttribute('type', 'text');
65 | input.setAttribute('name', 'test-input');
66 | document.body.appendChild(input);
67 |
68 | const event = input.dispatchEvent(
69 | new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 34 }),
70 | );
71 |
72 | expect(keyboardListener).toHaveBeenCalledTimes(1);
73 | expect(event).toEqual(false);
74 | });
75 | });
76 |
77 | describe('without DOM', () => {
78 | const { createElement } = window.document;
79 |
80 | beforeAll(() => {
81 | jest.clearAllMocks();
82 |
83 | // @ts-ignore
84 | window.document.createElement = null;
85 | });
86 |
87 | afterAll(() => {
88 | window.document.createElement = createElement;
89 | });
90 |
91 | it('should not have added the listeners when turning it ON', () => {
92 | disableScroll.on();
93 | dispatchEvents();
94 |
95 | expect(scrollListener).toHaveBeenCalledTimes(0);
96 | expect(wheelListener).toHaveBeenCalledTimes(0);
97 | expect(keyboardListener).toHaveBeenCalledTimes(0);
98 | });
99 |
100 | it('should not have removed the listeners when turning it OFF', () => {
101 | disableScroll.off();
102 | dispatchEvents();
103 |
104 | expect(scrollListener).toHaveBeenCalledTimes(0);
105 | expect(wheelListener).toHaveBeenCalledTimes(0);
106 | expect(keyboardListener).toHaveBeenCalledTimes(0);
107 | });
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@gilbarbara/tsconfig",
3 | "compilerOptions": {
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "outDir": "./lib",
6 | "target": "es5",
7 | "skipLibCheck": true
8 | },
9 | "include": ["src/**/*"]
10 | }
11 |
--------------------------------------------------------------------------------