├── .babelrc
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── test.yml
├── .gitignore
├── .log
├── ti-16339.log
├── ti-28128.log
├── ti-39437.log
├── ti-88235.log
└── tsserver.log
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── UPGRADE_GUIDE.md
├── bower.json
├── dist
├── react-modal.js
└── react-modal.min.js
├── docs
├── accessibility
│ └── index.md
├── contributing
│ ├── development.md
│ └── index.md
├── examples
│ ├── css_classes.md
│ ├── global_overrides.md
│ ├── index.md
│ ├── inline_styles.md
│ ├── minimal.md
│ ├── on_request_close.md
│ ├── set_app_element.md
│ └── should_close_on_overlay_click.md
├── index.md
├── pygments.css
├── styles
│ ├── classes.md
│ ├── index.md
│ └── transitions.md
└── testing
│ └── index.md
├── examples
├── base.css
├── basic
│ ├── app.css
│ ├── app.js
│ ├── forms
│ │ └── index.js
│ ├── index.html
│ ├── multiple_modals
│ │ └── index.js
│ ├── nested_modals
│ │ └── index.js
│ ├── react-router
│ │ └── index.js
│ └── simple_usage
│ │ ├── index.js
│ │ └── modal.js
├── bootstrap
│ ├── app.css
│ ├── app.js
│ └── index.html
├── index.html
└── wc
│ ├── app.css
│ ├── app.js
│ └── index.html
├── karma.conf.js
├── mkdocs.yml
├── package-lock.json
├── package.json
├── scripts
├── changelog.py
├── defaultConfig.js
├── repo_status
├── version
├── webpack.config.js
├── webpack.dist.config.js
└── webpack.test.config.js
├── specs
├── Modal.events.spec.js
├── Modal.helpers.spec.js
├── Modal.spec.js
├── Modal.style.spec.js
├── Modal.testability.spec.js
├── helper.js
└── index.js
└── src
├── components
├── Modal.js
└── ModalPortal.js
├── helpers
├── ariaAppHider.js
├── bodyTrap.js
├── classList.js
├── focusManager.js
├── portalOpenInstances.js
├── safeHTMLElement.js
├── scopeTab.js
└── tabbable.js
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "react",
5 | "stage-2"
6 | ],
7 | "plugins": [
8 | "add-module-exports"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "es6": true,
4 | "browser": true
5 | },
6 |
7 | "parser": "babel-eslint",
8 |
9 | "parserOptions": {
10 | "ecmaVersion": 7,
11 | "ecmaFeatures": {
12 | "jsx": true
13 | },
14 | "sourceType": "module"
15 | },
16 |
17 | "settings": {
18 | "react": {
19 | "createClass": "createReactClass",
20 | "pragma": "React",
21 | "version": "15.0"
22 | },
23 | "propWrapperFunctions": [ "forbidExtraProps" ],
24 | "import/resolver": "webpack"
25 | },
26 |
27 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:import/recommended", "prettier"],
28 |
29 | "plugins": ["prettier"],
30 |
31 | "globals": {
32 | "process": true
33 | },
34 |
35 | "rules": {
36 | "quotes": [0],
37 | "comma-dangle": [2, "only-multiline"],
38 | "max-len": [1, {"code": 80}],
39 | "no-unused-expressions": [0],
40 | "no-continue": [0],
41 | "no-plusplus": [0],
42 | "func-names": [0],
43 | "arrow-parens": [0],
44 | "space-before-function-paren": [0],
45 | "jsx-a11y/no-static-element-interactions": [0],
46 | "prettier/prettier": "error",
47 | "react/no-find-dom-node": [0],
48 | "react/jsx-closing-bracket-location": [0],
49 | "react/require-default-props": 0,
50 | "import/no-extraneous-dependencies": [2, {
51 | "devDependencies": ["specs/**"]
52 | }]
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Summary:
2 |
3 | ### Steps to reproduce:
4 |
5 | 1.
6 | 2.
7 | 3.
8 |
9 | ### Expected behavior:
10 |
11 | ### Link to example of issue:
12 |
16 |
17 | ### Additional notes:
18 |
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Acceptance Checklist:
2 | - [ ] Tests
3 | - [ ] Documentation and examples (if needed)
4 |
5 | Fixes #[issue number].
6 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | - v4
11 | - chore/github-actions
12 |
13 | jobs:
14 | main:
15 | name: Test
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 1
21 |
22 | - uses: actions/setup-node@v2
23 | with:
24 | node-version: 16
25 | cache: 'npm'
26 | cache-dependency-path: '**/package-lock.json'
27 | - run: make deps-project tests-ci
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .version
2 | .branch
3 | .changelog_update
4 | scripts/__pycache__/
5 | examples/**/*-bundle.js
6 | node_modules/
7 | .idea/
8 | .vscode
9 | _book
10 | *.patch
11 | *.diff
12 | *.orig
13 | *.rej
14 | .log
15 | examples/__build__
16 | coverage
17 | yarn.lock
18 |
19 | ## Built folders
20 | lib
21 |
--------------------------------------------------------------------------------
/.log/ti-28128.log:
--------------------------------------------------------------------------------
1 | [20:30:21.552] Global cache location '/Users/diasbruno/Library/Caches/typescript/4.2', safe file path '/users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/typingsafelist.json', types map path /users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/typesmap.json
2 | [20:30:21.555] Processing cache location '/Users/diasbruno/Library/Caches/typescript/4.2'
3 | [20:30:21.555] Trying to find '/Users/diasbruno/Library/Caches/typescript/4.2/package.json'...
4 | [20:30:21.557] Loaded content of '/Users/diasbruno/Library/Caches/typescript/4.2/package.json': {"private":true,"dependencies":{"types-registry":"^0.1.541"},"devDependencies":{"@types/brace-expansion":"^1.1.0","@types/d":"^1.0.0","@types/exenv":"^1.2.0","@types/lodash.get":"^4.4.6","@types/lolex":"^5.1.0","@types/minimatch":"^3.0.4","@types/nise":"^1.4.0","@types/node":"^15.3.0","@types/object-assign":"^4.0.30","@types/prop-types":"^15.7.3","@types/react":"^17.0.5","@types/react-dom":"^17.0.5","@types/react-is":"^17.0.0","@types/react-lifecycles-compat":"^3.0.1","@types/react-modal":"^3.12.0","@types/scheduler":"^0.16.1","@types/sinon":"^10.0.0","@types/warning":"^3.0.0"}}
5 | [20:30:21.557] Loaded content of '/Users/diasbruno/Library/Caches/typescript/4.2/package-lock.json'
6 | [20:30:21.571] Adding entry into typings cache: 'brace-expansion' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/brace-expansion/index.d.ts'
7 | [20:30:21.576] Adding entry into typings cache: 'd' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/d/index.d.ts'
8 | [20:30:21.579] Adding entry into typings cache: 'exenv' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/exenv/index.d.ts'
9 | [20:30:21.583] Adding entry into typings cache: 'lodash.get' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/lodash.get/index.d.ts'
10 | [20:30:21.590] Adding entry into typings cache: 'lolex' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/lolex/index.d.ts'
11 | [20:30:21.594] Adding entry into typings cache: 'minimatch' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/minimatch/index.d.ts'
12 | [20:30:21.597] Adding entry into typings cache: 'nise' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/nise/index.d.ts'
13 | [20:30:21.607] Adding entry into typings cache: 'node' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/node/index.d.ts'
14 | [20:30:21.610] Adding entry into typings cache: 'object-assign' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/object-assign/index.d.ts'
15 | [20:30:21.614] Adding entry into typings cache: 'prop-types' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/prop-types/index.d.ts'
16 | [20:30:21.618] Adding entry into typings cache: 'react' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react/index.d.ts'
17 | [20:30:21.621] Adding entry into typings cache: 'react-dom' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-dom/index.d.ts'
18 | [20:30:21.623] Adding entry into typings cache: 'react-is' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-is/index.d.ts'
19 | [20:30:21.626] Adding entry into typings cache: 'react-lifecycles-compat' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-lifecycles-compat/index.d.ts'
20 | [20:30:21.629] Adding entry into typings cache: 'react-modal' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-modal/index.d.ts'
21 | [20:30:21.632] Adding entry into typings cache: 'scheduler' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/scheduler/index.d.ts'
22 | [20:30:21.634] Adding entry into typings cache: 'sinon' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/sinon/index.d.ts'
23 | [20:30:21.638] Adding entry into typings cache: 'warning' => '/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/warning/index.d.ts'
24 | [20:30:21.638] Finished processing cache location '/Users/diasbruno/Library/Caches/typescript/4.2'
25 | [20:30:21.639] Process id: 28129
26 | [20:30:21.639] NPM location: /nix/store/lnfcmzafcsnv7jwk1zpvqhs8rzfq8xqg-nodejs-14.16.1/bin/npm (explicit '--npmLocation' not provided)
27 | [20:30:21.639] validateDefaultNpmLocation: false
28 | [20:30:21.640] Npm config file: /Users/diasbruno/Library/Caches/typescript/4.2/package.json
29 | [20:30:21.640] Updating types-registry npm package...
30 | [20:30:21.640] Exec: /nix/store/lnfcmzafcsnv7jwk1zpvqhs8rzfq8xqg-nodejs-14.16.1/bin/npm install --ignore-scripts types-registry@latest
31 | [20:30:23.453] Succeeded. stdout:
32 | + types-registry@0.1.541
33 | updated 1 package and audited 25 packages in 0.952s
34 | found 0 vulnerabilities
35 |
36 |
37 | [20:30:23.454] Updated types-registry npm package
38 | [20:30:24.054] Got install request {"projectName":"/dev/null/inferredProject1*","fileNames":["/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es5.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.scripthost.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.full.d.ts","/usr/local/src/react-modal/src/helpers/tabbable.js","/usr/local/src/react-modal/src/helpers/focusManager.js","/usr/local/src/react-modal/src/helpers/scopeTab.js","/usr/local/src/react-modal/src/helpers/safeHTMLElement.js","/usr/local/src/react-modal/src/helpers/ariaAppHider.js","/usr/local/src/react-modal/src/helpers/classList.js","/usr/local/src/react-modal/src/helpers/portalOpenInstances.js","/usr/local/src/react-modal/src/helpers/bodyTrap.js","/usr/local/src/react-modal/src/components/ModalPortal.js","/usr/local/src/react-modal/src/components/Modal.js","/usr/local/src/react-modal/specs/helper.js","/usr/local/src/react-modal/specs/Modal.events.spec.js"],"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typeAcquisition":{"enable":true,"include":[],"exclude":[]},"unresolvedImports":[],"projectRootPath":"/usr/local/src/react-modal/specs","kind":"discover"}
39 | [20:30:24.080] Loaded safelist from types map file '/users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/typesmap.json'
40 | [20:30:24.082] Explicitly included types: []
41 | [20:30:24.084] Inferred typings from unresolved imports: []
42 | [20:30:24.084] Result: {"cachedTypingPaths":[],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
43 | [20:30:24.084] Finished typings discovery: {"cachedTypingPaths":[],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
44 | [20:30:24.085] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components
45 | [20:30:24.086] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
46 | [20:30:24.090] Elapsed:: 4.4382399916648865ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
47 | [20:30:24.091] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules
48 | [20:30:24.091] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
49 | [20:30:24.091] Elapsed:: 0.16435399651527405ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
50 | [20:30:24.092] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components
51 | [20:30:24.092] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
52 | [20:30:24.092] Elapsed:: 0.21012499928474426ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
53 | [20:30:24.092] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules
54 | [20:30:24.093] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
55 | [20:30:24.093] Elapsed:: 0.14466500282287598ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
56 | [20:30:24.093] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components
57 | [20:30:24.093] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
58 | [20:30:24.093] Elapsed:: 0.13554999232292175ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
59 | [20:30:24.094] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules
60 | [20:30:24.094] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
61 | [20:30:24.095] Elapsed:: 0.20177200436592102ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules 1 undefined Project: /dev/null/inferredProject1* watcher already invoked: false
62 | [20:30:24.095] Sending response:
63 | {"projectName":"/dev/null/inferredProject1*","typeAcquisition":{"enable":true,"include":[],"exclude":[]},"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typings":[],"unresolvedImports":[],"kind":"action::set"}
64 | [20:30:24.096] Response has been sent.
65 | [20:30:24.096] No new typings were requested as a result of typings discovery
66 | [20:50:30.241] Got install request {"projectName":"/dev/null/inferredProject2*","fileNames":["/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es5.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.scripthost.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.full.d.ts","/usr/local/src/react-modal/src/helpers/tabbable.js","/usr/local/src/react-modal/src/helpers/focusManager.js","/usr/local/src/react-modal/src/helpers/scopeTab.js","/usr/local/src/react-modal/src/helpers/safeHTMLElement.js","/usr/local/src/react-modal/src/helpers/ariaAppHider.js","/usr/local/src/react-modal/src/helpers/classList.js","/usr/local/src/react-modal/src/helpers/portalOpenInstances.js","/usr/local/src/react-modal/src/helpers/bodyTrap.js","/usr/local/src/react-modal/src/components/ModalPortal.js","/usr/local/src/react-modal/src/components/Modal.js","/usr/local/src/react-modal/specs/helper.js","/usr/local/src/react-modal/specs/Modal.spec.js"],"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typeAcquisition":{"enable":true,"include":[],"exclude":[]},"unresolvedImports":["react-modal"],"projectRootPath":"/usr/local/src/react-modal/specs","kind":"discover"}
67 | [20:50:30.242] Explicitly included types: []
68 | [20:50:30.246] Inferred typings from unresolved imports: ["react-modal"]
69 | [20:50:30.246] Result: {"cachedTypingPaths":["/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-modal/index.d.ts"],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
70 | [20:50:30.246] Finished typings discovery: {"cachedTypingPaths":["/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-modal/index.d.ts"],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
71 | [20:50:30.247] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components
72 | [20:50:30.247] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
73 | [20:50:30.247] Elapsed:: 0.11894398927688599ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
74 | [20:50:30.247] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules
75 | [20:50:30.248] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
76 | [20:50:30.249] Elapsed:: 0.12288302183151245ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/helpers/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
77 | [20:50:30.249] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components
78 | [20:50:30.249] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
79 | [20:50:30.249] Elapsed:: 0.09933501482009888ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
80 | [20:50:30.250] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules
81 | [20:50:30.250] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
82 | [20:50:30.250] Elapsed:: 0.14720699191093445ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/src/components/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
83 | [20:50:30.250] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components
84 | [20:50:30.251] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
85 | [20:50:30.251] Elapsed:: 0.13086000084877014ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/bower_components 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
86 | [20:50:30.251] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules
87 | [20:50:30.251] DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
88 | [20:50:30.252] Elapsed:: 0.1500920057296753ms DirectoryWatcher:: Added:: WatchInfo: /usr/local/src/react-modal/specs/node_modules 1 undefined Project: /dev/null/inferredProject2* watcher already invoked: false
89 | [20:50:30.252] Sending response:
90 | {"projectName":"/dev/null/inferredProject2*","typeAcquisition":{"enable":true,"include":[],"exclude":[]},"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typings":["/Users/diasbruno/Library/Caches/typescript/4.2/node_modules/@types/react-modal/index.d.ts"],"unresolvedImports":["react-modal"],"kind":"action::set"}
91 | [20:50:30.252] Response has been sent.
92 | [20:50:30.252] No new typings were requested as a result of typings discovery
93 | [21:08:31.182] Got install request {"projectName":"/dev/null/inferredProject1*","fileNames":["/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es5.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.scripthost.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.full.d.ts","/usr/local/src/react-modal/src/helpers/tabbable.js","/usr/local/src/react-modal/src/helpers/focusManager.js","/usr/local/src/react-modal/src/helpers/scopeTab.js","/usr/local/src/react-modal/src/helpers/safeHTMLElement.js","/usr/local/src/react-modal/src/helpers/ariaAppHider.js","/usr/local/src/react-modal/src/helpers/classList.js","/usr/local/src/react-modal/src/helpers/portalOpenInstances.js","/usr/local/src/react-modal/src/helpers/bodyTrap.js","/usr/local/src/react-modal/src/components/ModalPortal.js","/usr/local/src/react-modal/src/components/Modal.js","/usr/local/src/react-modal/specs/helper.js","/usr/local/src/react-modal/specs/Modal.events.spec.js"],"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typeAcquisition":{"enable":true,"include":[],"exclude":[]},"unresolvedImports":["react-"],"projectRootPath":"/usr/local/src/react-modal/specs","kind":"discover"}
94 | [21:08:31.187] Explicitly included types: []
95 | [21:08:31.188] Inferred typings from unresolved imports: ["react-"]
96 | [21:08:31.188] Result: {"cachedTypingPaths":[],"newTypingNames":["react-"],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
97 | [21:08:31.188] Finished typings discovery: {"cachedTypingPaths":[],"newTypingNames":["react-"],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
98 | [21:08:31.189] Installing typings ["react-"]
99 | [21:08:31.189] 'react-':: Entry for package 'react-' does not exist in local types registry - skipping...
100 | [21:08:31.190] All typings are known to be missing or invalid - no need to install more typings
101 | [21:08:31.190] Sending response:
102 | {"projectName":"/dev/null/inferredProject1*","typeAcquisition":{"enable":true,"include":[],"exclude":[]},"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typings":[],"unresolvedImports":["react-"],"kind":"action::set"}
103 | [21:08:31.190] Response has been sent.
104 | [21:08:31.686] Got install request {"projectName":"/dev/null/inferredProject1*","fileNames":["/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es5.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.dom.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.scripthost.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.core.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.collection.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.generator.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.iterable.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.promise.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.proxy.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.reflect.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.array.include.d.ts","/Users/diasbruno/.emacs.d/.cache/lsp/npm/typescript/lib/node_modules/typescript/lib/lib.es2016.full.d.ts","/usr/local/src/react-modal/src/helpers/tabbable.js","/usr/local/src/react-modal/src/helpers/focusManager.js","/usr/local/src/react-modal/src/helpers/scopeTab.js","/usr/local/src/react-modal/src/helpers/safeHTMLElement.js","/usr/local/src/react-modal/src/helpers/ariaAppHider.js","/usr/local/src/react-modal/src/helpers/classList.js","/usr/local/src/react-modal/src/helpers/portalOpenInstances.js","/usr/local/src/react-modal/src/helpers/bodyTrap.js","/usr/local/src/react-modal/src/components/ModalPortal.js","/usr/local/src/react-modal/src/components/Modal.js","/usr/local/src/react-modal/specs/helper.js","/usr/local/src/react-modal/specs/Modal.events.spec.js"],"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typeAcquisition":{"enable":true,"include":[],"exclude":[]},"unresolvedImports":[],"projectRootPath":"/usr/local/src/react-modal/specs","kind":"discover"}
105 | [21:08:31.686] Explicitly included types: []
106 | [21:08:31.688] Inferred typings from unresolved imports: []
107 | [21:08:31.688] Result: {"cachedTypingPaths":[],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
108 | [21:08:31.689] Finished typings discovery: {"cachedTypingPaths":[],"newTypingNames":[],"filesToWatch":["/usr/local/src/react-modal/src/helpers/bower_components","/usr/local/src/react-modal/src/helpers/node_modules","/usr/local/src/react-modal/src/components/bower_components","/usr/local/src/react-modal/src/components/node_modules","/usr/local/src/react-modal/specs/bower_components","/usr/local/src/react-modal/specs/node_modules"]}
109 | [21:08:31.689] Sending response:
110 | {"projectName":"/dev/null/inferredProject1*","typeAcquisition":{"enable":true,"include":[],"exclude":[]},"compilerOptions":{"module":1,"target":3,"jsx":1,"allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true,"noEmitForJsFiles":true,"maxNodeModuleJsDepth":2},"typings":[],"unresolvedImports":[],"kind":"action::set"}
111 | [21:08:31.690] Response has been sent.
112 | [21:08:31.690] No new typings were requested as a result of typings discovery
113 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | CONTRIBUTING.md
2 | .babelrc
3 | .travis.yml
4 | .babelrc
5 | .eslintrc.js
6 | *.orig
7 | *.rej
8 | .log
9 | .changelog_update
10 | Makefile
11 | book.json
12 | bootstrap.sh
13 | bower.json
14 | karma.conf.js
15 | yarn.lock
16 | webpack.*
17 | .idea/*
18 | .github/*
19 | coverage
20 | docs
21 | src
22 | scripts
23 | specs
24 | _book
25 | examples
26 | mkdocs.yml
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | cache: yarn
5 | services:
6 | - xvfb
7 | before_script:
8 | - export DISPLAY=:99.0
9 | script:
10 | - make tests-ci
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ### Commit Subjects
2 |
3 | Patches will be only accepted if they have a corresponding issue
4 | on GitHub.
5 |
6 | Having a corresponding issue is better to track
7 | and discuss ideas and propose changes.
8 |
9 | ### Docs
10 |
11 | Please update the README with any API changes, the code and docs should
12 | always be in sync.
13 |
14 | ### Development
15 |
16 | - `npm start` runs the dev server to run/develop examples
17 | - `npm test` will run the tests.
18 | - `scripts/test` same as `npm test` but keeps karma running and watches
19 | for changes
20 |
21 | ## Miscellaneous
22 |
23 | if you faced the below issue, make sure you use node version < 18
24 | ```node:internal/crypto/hash:71
25 | this[kHandle] = new _Hash(algorithm, xofLen);
26 | ^
27 |
28 | Error: error:0308010C:digital envelope routines::unsupported
29 | at new Hash (node:internal/crypto/hash:71:19)
30 | at Object.createHash (node:crypto:133:10)```
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Ryan Florence
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NODE=$(shell which node 2> /dev/null)
2 | NPM=$(shell which npm 2> /dev/null)
3 | YARN=$(shell which yarn 2> /dev/null)
4 | JQ=$(shell which jq 2> /dev/null)
5 |
6 | PKM?=$(if $(YARN),$(YARN),$(shell which npm))
7 |
8 | BABEL=./node_modules/.bin/babel
9 | COVERALLS=./node_modules/coveralls/bin/coveralls.js
10 | REMOTE="git@github.com:reactjs/react-modal"
11 | CURRENT_VERSION:=$(shell jq ".version" package.json)
12 | COVERAGE?=true
13 |
14 | BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
15 | CURRENT_VERSION:=$(shell jq ".version" package.json)
16 |
17 | VERSION:=$(if $(RELEASE),$(shell read -p "Release $(CURRENT_VERSION) -> " V && echo $$V),"HEAD")
18 |
19 | help: info
20 | @echo
21 | @echo "Current version: $(CURRENT_VERSION)"
22 | @echo
23 | @echo "List of commands:"
24 | @echo
25 | @echo " make info - display node, npm and yarn versions..."
26 | @echo " make deps - install all dependencies."
27 | @echo " make serve - start the server."
28 | @echo " make tests - run tests."
29 | @echo " make tests-single-run - run tests (used by continuous integration)."
30 | @echo " make coveralls - show coveralls."
31 | @echo " make lint - run lint."
32 | @echo " make docs - build and serve the docs."
33 | @echo " make build - build project artifacts."
34 | @echo " make publish - build and publish version on npm."
35 | @echo " make publish-docs - build the docs and publish to gh-pages."
36 | @echo " make publish-all - publish version and docs."
37 |
38 | info:
39 | @[[ ! -z "$(NODE)" ]] && echo node version: `$(NODE) --version` "$(NODE)"
40 | @[[ ! -z "$(PKM)" ]] && echo $(shell basename $(PKM)) version: `$(PKM) --version` "$(PKM)"
41 | @[[ ! -z "$(JQ)" ]] && echo jq version: `$(JQ) --version` "$(JQ)"
42 |
43 | deps: deps-project deps-docs
44 |
45 | deps-project:
46 | @$(PKM) install
47 |
48 | deps-docs:
49 | @pip install mkdocs mkdocs-material jsx-lexer
50 |
51 | # Rules for development
52 |
53 | serve:
54 | @npm start
55 |
56 | tests:
57 | @npm run test
58 |
59 | tests-single-run:
60 | @npm run test -- --single-run
61 |
62 | coveralls:
63 | -cat ./coverage/lcov.info | $(COVERALLS) 2>/dev/null
64 |
65 | tests-ci: clean lint
66 | @COVERAGE=$(COVERAGE) make tests-single-run coveralls
67 |
68 | lint:
69 | @npm run lint
70 |
71 | docs: build-docs
72 | pygmentize -S default -f html -a .codehilite > docs/pygments.css
73 | mkdocs serve
74 |
75 | # Rules for build and publish
76 |
77 | check-working-tree:
78 | @[ -z "`git status -s`" ] && \
79 | echo "Stopping publish. There are change to commit or discard." || echo "Worktree is clean."
80 |
81 | compile:
82 | @echo "[Compiling source]"
83 | $(BABEL) src --out-dir lib
84 |
85 | build: compile
86 | @echo "[Building dists]"
87 | @npx webpack --config ./scripts/webpack.dist.config.js
88 |
89 | pre-release-commit:
90 | git commit --allow-empty -m "Release v$(VERSION)."
91 |
92 | changelog:
93 | @echo "[Updating CHANGELOG.md $(CURRENT_VERSION) > $(VERSION)]"
94 | python ./scripts/changelog.py -a $(VERSION) > CHANGELOG.md
95 |
96 | update-package-version:
97 | cat package.json | jq '.version="$(VERSION)"' > tmp; mv -f tmp package.json
98 |
99 | release-commit: pre-release-commit update-package-version changelog
100 | @git add .
101 | @git commit --amend -m "`git log -1 --format=%s`"
102 |
103 | release-tag:
104 | git tag "v$(VERSION)" -m "`python ./scripts/changelog.py -c $(VERSION)`"
105 |
106 | publish-version: release-commit release-tag
107 | @echo "[Publishing]"
108 | git push $(REMOTE) "$(BRANCH)" "v$(VERSION)"
109 | npm publish
110 |
111 | pre-publish: clean
112 | pre-build: deps-project tests-single-run build
113 |
114 | publish: check-working-tree pre-publish pre-build publish-version publish-finished
115 |
116 | publish-finished: clean
117 |
118 | # Rules for documentation
119 |
120 | init-docs-repo:
121 | @mkdir _book
122 |
123 | build-docs:
124 | @echo "[Building documentation]"
125 | @rm -rf _book
126 | @mkdocs build
127 |
128 | pre-publish-docs: clean-docs init-docs-repo deps-docs
129 |
130 | publish-docs: clean pre-publish-docs build-docs
131 | @echo "[Publishing docs]"
132 | @make -C _book -f ../Makefile _publish-docs
133 |
134 | _publish-docs:
135 | git init .
136 | git commit --allow-empty -m 'update book'
137 | git checkout -b gh-pages
138 | touch .nojekyll
139 | git add .
140 | git commit -am 'update book'
141 | git push git@github.com:reactjs/react-modal gh-pages --force
142 |
143 | # Run for a full publish
144 |
145 | publish-all: publish publish-docs
146 |
147 | # Rules for clean up
148 |
149 | clean-docs:
150 | @rm -rf _book
151 |
152 | clean-coverage:
153 | @rm -rf ./coverage/*
154 |
155 | clean-build:
156 | @rm -rf lib/*
157 |
158 | clean: clean-build clean-docs clean-coverage
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-modal
2 |
3 | Accessible modal dialog component for React.JS
4 |
5 | [](https://github.com/reactjs/react-modal/actions/workflows/test.yml)
6 | [](https://coveralls.io/github/reactjs/react-modal?branch=master)
7 | 
8 | [](https://gitter.im/react-modal/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
9 |
10 | ## Table of Contents
11 |
12 | * [Installation](#installation)
13 | * [API documentation](#api-documentation)
14 | * [Examples](#examples)
15 | * [Demos](#demos)
16 |
17 | ## Installation
18 |
19 | To install, you can use [npm](https://npmjs.org/) or [yarn](https://yarnpkg.com):
20 |
21 |
22 | $ npm install --save react-modal
23 | $ yarn add react-modal
24 |
25 | To install react-modal in React CDN app:
26 |
27 | - Add this CDN script tag after React CDN scripts and before your JS files (for example from [cdnjs](https://cdnjs.com/)):
28 |
29 |
33 |
34 | - Use `` tag inside your React CDN app.
35 |
36 |
37 | ## API documentation
38 |
39 | The primary documentation for react-modal is the
40 | [reference book](https://reactjs.github.io/react-modal), which describes the API
41 | and gives examples of its usage.
42 |
43 | ## Examples
44 |
45 | Here is a simple example of react-modal being used in an app with some custom
46 | styles and focusable input elements within the modal content:
47 |
48 | ```jsx
49 | import React from 'react';
50 | import ReactDOM from 'react-dom';
51 | import Modal from 'react-modal';
52 |
53 | const customStyles = {
54 | content: {
55 | top: '50%',
56 | left: '50%',
57 | right: 'auto',
58 | bottom: 'auto',
59 | marginRight: '-50%',
60 | transform: 'translate(-50%, -50%)',
61 | },
62 | };
63 |
64 | // Make sure to bind modal to your appElement (https://reactcommunity.org/react-modal/accessibility/)
65 | Modal.setAppElement('#yourAppElement');
66 |
67 | function App() {
68 | let subtitle;
69 | const [modalIsOpen, setIsOpen] = React.useState(false);
70 |
71 | function openModal() {
72 | setIsOpen(true);
73 | }
74 |
75 | function afterOpenModal() {
76 | // references are now sync'd and can be accessed.
77 | subtitle.style.color = '#f00';
78 | }
79 |
80 | function closeModal() {
81 | setIsOpen(false);
82 | }
83 |
84 | return (
85 |
86 |
Open Modal
87 |
94 | (subtitle = _subtitle)}>Hello
95 | close
96 | I am a modal
97 |
104 |
105 |
106 | );
107 | }
108 |
109 | ReactDOM.render( , appElement);
110 | ```
111 |
112 | You can find more examples in the `examples` directory, which you can run in a
113 | local development server using `npm start` or `yarn run start`.
114 |
115 | ## Demos
116 |
117 | There are several demos hosted on [CodePen](https://codepen.io) which
118 | demonstrate various features of react-modal:
119 |
120 | * [Minimal example](https://codepen.io/claydiffrient/pen/KNxgav)
121 | * [Using setAppElement](https://codepen.io/claydiffrient/pen/ENegGJ)
122 | * [Using onRequestClose](https://codepen.io/claydiffrient/pen/KNjVBx)
123 | * [Using shouldCloseOnOverlayClick](https://codepen.io/claydiffrient/pen/woLzwo)
124 | * [Using inline styles](https://codepen.io/claydiffrient/pen/ZBmyKz)
125 | * [Using CSS classes for styling](https://codepen.io/claydiffrient/pen/KNjVrG)
126 | * [Customizing the default styles](https://codepen.io/claydiffrient/pen/pNXgqQ)
127 |
--------------------------------------------------------------------------------
/UPGRADE_GUIDE.md:
--------------------------------------------------------------------------------
1 | Upgrade Guide
2 | =============
3 |
4 | To see discussion around these API changes, please refer to the
5 | [changelog](/CHANGELOG.md) and visit the commits and issues they
6 | reference.
7 |
8 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-modal",
3 | "version": "3.11.1",
4 | "homepage": "https://github.com/reactjs/react-modal",
5 | "authors": [
6 | "Ryan Florence",
7 | "Michael Jackson"
8 | ],
9 | "description": "Accessible modal dialog component for React.JS",
10 | "main": "dist/react-modal.js",
11 | "keywords": [
12 | "react",
13 | "modal",
14 | "dialog"
15 | ],
16 | "license": "MIT",
17 | "ignore": [
18 | "**/.*",
19 | "node_modules",
20 | "bower_components",
21 | "specs",
22 | "modules",
23 | "examples",
24 | "script",
25 | "CONTRIBUTING.md",
26 | "karma.conf.js",
27 | "package.json"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/docs/accessibility/index.md:
--------------------------------------------------------------------------------
1 | react-modal aims to be fully accessible, using the
2 | [WAI-ARIA](https://www.w3.org/WAI/intro/aria) guidelines to support users of
3 | assistive technologies. This page describes some of react-modal's
4 | accessibility-oriented features, along with their configuration options.
5 |
6 | ### [The app element](#app-element)
7 |
8 | It is important for users of screenreaders that other page content be hidden
9 | (via the `aria-hidden` attribute) while the modal is open. To allow
10 | react-modal to do this, you should call `Modal.setAppElement` with a query
11 | selector identifying the root of your app. For example, if your app content is
12 | located inside an element with the ID `root`, you could place the following
13 | call somewhere in your code before any modals are opened:
14 |
15 | ```jsx
16 | Modal.setAppElement('#root');
17 | ```
18 |
19 | You can also pass a DOM element directly, so that the above example could be
20 | rewritten:
21 |
22 | ```jsx
23 | Modal.setAppElement(document.getElementById('root'));
24 | ```
25 |
26 | Using a selector that matches multiple elements or passing a list of DOM
27 | elements will hide all of the elements. Note that this list won't be
28 | automatically pruned if elements are removed from the DOM, so you may want to
29 | call `Modal.setAppElement` when any such changes are made, or pass a live
30 | HTMLCollection as the value.
31 |
32 | If you are already applying the `aria-hidden` attribute to your app content
33 | through other means, you can pass the `ariaHideApp={false}` prop to your modal
34 | to avoid getting a warning that your app element is not specified.
35 |
36 | Using `Modal.setAppElement` will not embed react-modal into your react app as
37 | a descendent component. It will just help boost up the app accessiblity.
38 |
39 | ### [Keyboard navigation](#keyboard)
40 |
41 | When the modal is opened, it restricts keyboard navigation using the tab key to
42 | elements within the modal content. This ensures that elements outside the
43 | modal (which are not visible while the modal is open) do not receive focus
44 | unexpectedly.
45 |
46 | By default, when the modal is closed, focus will be restored to the element
47 | that was focused before the modal was opened. To disable this behavior, you
48 | can pass the `shouldReturnFocusAfterClose={false}` prop to your modal.
49 |
50 | The modal can be closed using the escape key, unless the
51 | `shouldCloseOnEsc={false}` prop is passed. Disabling this behavior may cause
52 | accessibility issues for keyboard users, however, so it is not recommended.
53 |
54 | ### [ARIA attributes](#aria)
55 |
56 | Besides the `aria-hidden` attribute which is applied to the app element when
57 | the modal is shown, there are many other ARIA attributes which you can use to
58 | make your app more accessible. A complete list of ARIA attributes can be found
59 | in the [ARIA specification](https://www.w3.org/TR/wai-aria-1.1/#state_prop_def).
60 |
61 | One ARIA attribute is given a dedicated prop by react-modal: you should use the
62 | `contentLabel` prop to provide a label for the modal content (via `aria-label`)
63 | if there is no visible label on the screen. If the modal is already labeled
64 | with visible text, you should specify the element including the label with the
65 | `aria-labelledby` attribute using the `aria` prop described below.
66 |
67 | To pass other ARIA attributes to your modal, you can use the `aria` prop, which
68 | accepts an object whose keys are the attributes you want to set (without the
69 | leading `aria-` prefix). For example, you could have an alert modal with a
70 | title as well as a longer description:
71 |
72 | ```jsx
73 |
79 | Alert
80 |
81 |
Description goes here.
82 |
83 |
84 | ```
85 |
--------------------------------------------------------------------------------
/docs/contributing/development.md:
--------------------------------------------------------------------------------
1 | `react-modal` uses `make` to build and publish new versions and documentation.
2 |
3 | It works as a checklist for the future releases to keep everything updated such as
4 | `CHANGELOG.md`, `package.json` and `bower.json` and so on.
5 |
6 | The minimun works as a normal `npm` scripts.
7 |
8 | #### [Usage](#usage)
9 |
10 | Once you clone `react-modal`, you can run `sh bootstrap.sh` to check
11 | and download dependencies not managed by `react-modal` such as `gitbook-cli`.
12 |
13 | It will also show information about the current versions of `node`, `npm`,
14 | `yarn` and `jq` available.
15 |
16 | #### [List of `npm` or `yarn` commands](#npm-yarn-commands)
17 |
18 | $ npm start
19 | $ npm run tests
20 | $ npm run lint
21 |
22 | #### [List of `make` commands](#make-commands)
23 |
24 | $ make help # show all make commands available
25 | $ make deps # npm install
26 | $ make serve # start a examples' web server
27 | $ make tests # use when developing
28 | $ make tests-ci # single run
29 | $ make lint # execute lint
30 | $ make publish # execute the entire pipeline to publish
31 | $ make publish-docs # execute the pipeline for docs
32 |
--------------------------------------------------------------------------------
/docs/contributing/index.md:
--------------------------------------------------------------------------------
1 | ### Commit Subjects
2 |
3 | If your patch **changes the API or fixes a bug** please use one of the
4 | following prefixes in your commit subject:
5 |
6 | - `[fixed] ...`
7 | - `[changed] ...`
8 | - `[added] ...`
9 | - `[removed] ...`
10 |
11 | That ensures the subject line of your commit makes it into the
12 | auto-generated changelog. Do not use these tags if your change doesn't
13 | fix a bug and doesn't change the public API.
14 |
15 | Commits with changed, added, or removed, must be reviewed by another
16 | collaborator.
17 |
18 | #### When using `[changed]` or `[removed]`...
19 |
20 | Please include an upgrade path with example code in the commit message.
21 | If it doesn't make sense to do this, then it doesn't make sense to use
22 | `[changed]` or `[removed]` :)
23 |
24 | ### Docs
25 |
26 | Please update the README with any API changes, the code and docs should
27 | always be in sync.
28 |
29 | ### Development
30 |
31 | - `npm start` runs the dev server to run/develop examples
32 | - `npm test` will run the tests.
33 | - `scripts/test` same as `npm test` but keeps karma running and watches
34 | for changes
35 |
36 | ### Build
37 |
38 | Please do not include the output of `scripts/build` in your commits, we
39 | only do this when we release. (Also, you probably don't need to build
40 | anyway unless you are fixing something around our global build.)
41 |
--------------------------------------------------------------------------------
/docs/examples/css_classes.md:
--------------------------------------------------------------------------------
1 | # Using CSS Classes for Styling
2 |
3 | If you prefer to use CSS to handle styling the modal you can.
4 |
5 | One thing to note is that by using the className property you will override all default styles.
6 |
7 | [CSS classes example](https://codepen.io/claydiffrient/pen/KNjVrG)
8 |
--------------------------------------------------------------------------------
/docs/examples/global_overrides.md:
--------------------------------------------------------------------------------
1 | # Global Overrides
2 |
3 | If you'll be using several modals and want to adjust styling for all of them in one location you can by modifying `Modal.defaultStyles`.
4 |
5 | [Global overrides example](https://codepen.io/claydiffrient/pen/pNXgqQ)
6 |
--------------------------------------------------------------------------------
/docs/examples/index.md:
--------------------------------------------------------------------------------
1 | The following sub-sections contain several examples of basic usage, hosted on
2 | [CodePen](https://codepen.io).
3 |
4 | The `examples` directory in the project root also contains some examples which
5 | you can run locally. To build and run those examples using a local development
6 | server, run either
7 |
8 | $ npm start
9 |
10 | or
11 |
12 | $ yarn start
13 |
14 |
15 | and then point your browser to `localhost:8080`.
16 |
--------------------------------------------------------------------------------
/docs/examples/inline_styles.md:
--------------------------------------------------------------------------------
1 | # Using Inline Styles
2 |
3 | This example shows how to use inline styles to adjust the modal.
4 |
5 | [inline styles example](https://codepen.io/claydiffrient/pen/ZBmyKz)
6 |
--------------------------------------------------------------------------------
/docs/examples/minimal.md:
--------------------------------------------------------------------------------
1 | # Minimal
2 |
3 | This example shows the minimal needed to get React Modal to work.
4 |
5 | [Minimal example](https://codepen.io/claydiffrient/pen/KNxgav)
6 |
--------------------------------------------------------------------------------
/docs/examples/on_request_close.md:
--------------------------------------------------------------------------------
1 | # onRequestClose Callback
2 |
3 | This example shows how you can use the `onRequestClose` prop with a function to perform actions when closing.
4 |
5 | This is especially important for handling closing the modal via the escape key.
6 |
7 | Also more important if `shouldCloseOnOverlayClick` is set to `true`, when clicked on overlay it calls `onRequestClose`.
8 |
9 | [onRequestClose example](https://codepen.io/claydiffrient/pen/KNjVBx)
10 |
--------------------------------------------------------------------------------
/docs/examples/set_app_element.md:
--------------------------------------------------------------------------------
1 | # Using setAppElement
2 |
3 | This example shows how to use setAppElement to properly hide your application from screenreaders and other assistive technologies while the modal is open.
4 |
5 | You'll notice in this example that the aria-hidden attribute is applied to the #main div rather than the document body.
6 |
7 | [setAppElement example](https://codepen.io/claydiffrient/pen/ENegGJ)
8 |
--------------------------------------------------------------------------------
/docs/examples/should_close_on_overlay_click.md:
--------------------------------------------------------------------------------
1 | # Using shouldCloseOnOverlayClick
2 |
3 | When `shouldCloseOnOverlayClick` is `true` (default value for this property),
4 | it requires the `onRequestClose` to be defined in order to close the .
5 | This is due to the fact that the `react-modal` doesn't store the `isOpen`
6 | on its state (only for the internal `portal` (see [ModalPortal.js](https://github.com/reactjs/react-modal/blob/master/src/components/ModalPortal.js)).
7 |
8 | [disable 'close on overlay click', codepen by claydiffrient](https://codepen.io/claydiffrient/pen/woLzwo)
9 |
10 | [enable 'close on overlay click', codepen by sbgriffi](https://codepen.io/sbgriffi/pen/WMyBaR)
11 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # react-modal
2 |
3 | > Accessible modal dialog component for React.JS
4 |
5 | We maintain that accessibility is a key component of any modern web application. As such, we have created this modal in such a way that it fulfills the accessibility requirements of the modern web. We seek to keep the focus on accessibility while providing a functional, capable modal component for general use.
6 |
7 | ## [Installation](#installation)
8 |
9 | To install the stable version you can use [npm](https://npmjs.org/) or [yarn](https://yarnpkg.com):
10 |
11 |
12 | $ npm install react-modal
13 | $ yarn add react-modal
14 |
15 | To install react-modal in React CDN app:
16 |
17 | - Add this CDN script tag after React CDN scripts and before your JS files (for example from [cdnjs](https://cdnjs.com/)):
18 |
19 |
23 |
24 | - Use `` tag inside your React CDN app.
25 |
26 |
27 | ## [General Usage](#usage)
28 |
29 | The only required prop for the modal object is `isOpen`, which indicates
30 | whether the modal should be displayed. The following is an example of using
31 | react-modal specifying all the possible props and options:
32 |
33 | ```jsx
34 | import ReactModal from 'react-modal';
35 |
36 | document.body
142 | /* Function that will be called to get the parent element
143 | that the modal will be attached to. */}
144 |
145 | aria={
146 | {
147 | labelledby: "heading",
148 | describedby: "full_description"
149 | }
150 | /* Additional aria attributes (optional). */}
151 |
152 | data={
153 | { background: "green" }
154 | /* Additional data attributes (optional). */}
155 |
156 | testId={
157 | ""
158 | /* String testId that renders a data-testid attribute in the DOM,
159 | useful for testing. */}
160 |
161 | overlayRef={
162 | setOverlayRef
163 | /* Overlay ref callback. */}
164 |
165 | contentRef={
166 | setContentRef
167 | /* Content ref callback. */}
168 |
169 | overlayElement={
170 | (props, contentElement) => {contentElement}
171 | /* Custom Overlay element. */}
172 |
173 | contentElement={
174 | (props, children) => {children}
175 | /* Custom Content element. */}
176 | >
177 | Modal Content
178 |
179 | ```
180 |
181 | ## [Using a custom parent node](#custom-parent)
182 |
183 | By default, the modal portal will be appended to the document's body. You can
184 | choose a different parent element by providing a function to the
185 | `parentSelector` prop that returns the element to be used:
186 |
187 | ```jsx
188 | document.querySelector('#root')}>
191 | Modal Content.
192 |
193 | ```
194 | If you do this, please ensure that your
195 | [app element](accessibility/#app-element) is set correctly. The app
196 | element should not be a parent of the modal, to prevent modal content from
197 | being hidden to screenreaders while it is open.
198 |
199 | ## [Refs](#refs)
200 |
201 | You can use ref callbacks to get the overlay and content DOM nodes directly:
202 |
203 | ```jsx
204 | (this.overlayRef = node)}
207 | contentRef={node => (this.contentRef = node)}>
208 | Modal Content.
209 |
210 | ```
211 |
212 | ## [License](#license)
213 |
214 | MIT
215 |
--------------------------------------------------------------------------------
/docs/pygments.css:
--------------------------------------------------------------------------------
1 | pre { line-height: 125%; }
2 | td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; }
3 | span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; }
4 | td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
6 | .codehilite .hll { background-color: #ffffcc }
7 | .codehilite { background: #f8f8f8; }
8 | .codehilite .c { color: #408080; font-style: italic } /* Comment */
9 | .codehilite .err { border: 1px solid #FF0000 } /* Error */
10 | .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
11 | .codehilite .o { color: #666666 } /* Operator */
12 | .codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
13 | .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
14 | .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
15 | .codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
16 | .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
17 | .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
18 | .codehilite .gd { color: #A00000 } /* Generic.Deleted */
19 | .codehilite .ge { font-style: italic } /* Generic.Emph */
20 | .codehilite .gr { color: #FF0000 } /* Generic.Error */
21 | .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
22 | .codehilite .gi { color: #00A000 } /* Generic.Inserted */
23 | .codehilite .go { color: #888888 } /* Generic.Output */
24 | .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
25 | .codehilite .gs { font-weight: bold } /* Generic.Strong */
26 | .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
27 | .codehilite .gt { color: #0044DD } /* Generic.Traceback */
28 | .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
29 | .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
30 | .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
31 | .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
32 | .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
33 | .codehilite .kt { color: #B00040 } /* Keyword.Type */
34 | .codehilite .m { color: #666666 } /* Literal.Number */
35 | .codehilite .s { color: #BA2121 } /* Literal.String */
36 | .codehilite .na { color: #7D9029 } /* Name.Attribute */
37 | .codehilite .nb { color: #008000 } /* Name.Builtin */
38 | .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
39 | .codehilite .no { color: #880000 } /* Name.Constant */
40 | .codehilite .nd { color: #AA22FF } /* Name.Decorator */
41 | .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
42 | .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
43 | .codehilite .nf { color: #0000FF } /* Name.Function */
44 | .codehilite .nl { color: #A0A000 } /* Name.Label */
45 | .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
46 | .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
47 | .codehilite .nv { color: #19177C } /* Name.Variable */
48 | .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
49 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
50 | .codehilite .mb { color: #666666 } /* Literal.Number.Bin */
51 | .codehilite .mf { color: #666666 } /* Literal.Number.Float */
52 | .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
53 | .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
54 | .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
55 | .codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
56 | .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
57 | .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
58 | .codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
59 | .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
60 | .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
61 | .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
62 | .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
63 | .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
64 | .codehilite .sx { color: #008000 } /* Literal.String.Other */
65 | .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
66 | .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
67 | .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
68 | .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
69 | .codehilite .fm { color: #0000FF } /* Name.Function.Magic */
70 | .codehilite .vc { color: #19177C } /* Name.Variable.Class */
71 | .codehilite .vg { color: #19177C } /* Name.Variable.Global */
72 | .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
73 | .codehilite .vm { color: #19177C } /* Name.Variable.Magic */
74 | .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
75 |
--------------------------------------------------------------------------------
/docs/styles/classes.md:
--------------------------------------------------------------------------------
1 | Sometimes it may be preferable to use CSS classes rather than inline styles.
2 | react-modal can be configured to use CSS classes to style the modal content and
3 | overlay, as well as the document body and the portal within which the modal is
4 | mounted.
5 |
6 | #### For the content and overlay
7 |
8 | You can use the `className` and `overlayClassName` props to control the CSS
9 | classes that are applied to the modal content and the overlay, respectively.
10 | Each of these props may be a single string containing the class name to apply
11 | to the component.
12 |
13 | Alternatively, you may pass an object with the `base`, `afterOpen` and
14 | `beforeClose` keys, where the value corresponding to each key is a class name.
15 | The `base` class will always be applied to the component, the `afterOpen` class
16 | will be applied after the modal has been opened and the `beforeClose` class
17 | will be applied after the modal has requested to be closed (e.g. when the user
18 | presses the escape key or clicks on the overlay).
19 |
20 | Please note that the `beforeClose` class will have no effect unless the
21 | `closeTimeoutMS` prop is set to a non-zero value, since otherwise the modal
22 | will be closed immediately when requested. Thus, if you are using the
23 | `afterOpen` and `beforeClose` classes to provide transitions, you may want to
24 | set `closeTimeoutMS` to the length (in milliseconds) of your closing
25 | transition.
26 |
27 | If you specify `className`, the [default content styles](index.md) will not be
28 | applied. Likewise, if you specify `overlayClassName`, the default overlay
29 | styles will not be applied.
30 |
31 | If no class names are specified for the overlay, the default classes
32 | `ReactModal__Overlay`, `ReactModal__Overlay--after-open` and
33 | `ReactModal__Overlay--before-close` will be applied; the default classes for
34 | the content use the analogous prefix `ReactModal__Content`. Please note that
35 | any styles applied using these default classes will not override the default
36 | styles as they would if specified using the `className` or `overlayClassName`
37 | props.
38 |
39 | #### For the document.body and html tag
40 |
41 | You can override the default class that is added to `document.body` when the
42 | modal is open by defining a property `bodyOpenClassName`.
43 |
44 | The `bodyOpenClassName` prop must be a *constant string*; otherwise, we would
45 | require a complex system to manage which class name should be added to or
46 | removed from `document.body` from which modal (if using multiple modals
47 | simultaneously). The default value is `ReactModal__Body--open`.
48 |
49 | `bodyOpenClassName` when set as `null` doesn't add any class to `document.body`.
50 |
51 | `bodyOpenClassName` can support adding multiple classes to `document.body` when
52 | the modal is open. Add as many class names as you desire, delineated by spaces.
53 |
54 | One potential application for the body class is to remove scrolling on the body
55 | when the modal is open. To do this for all modals (except those that specify a
56 | non-default `bodyOpenClassName`), you could use the following CSS:
57 |
58 | ```CSS
59 | .ReactModal__Body--open {
60 | overflow: hidden;
61 | }
62 | ```
63 |
64 | You can define a class to be added to the html tag, using the `htmlOpenClassName`
65 | attribute, which can be helpeful to stop the page to scroll to the top when open
66 | a modal. The default value is `null`.
67 |
68 | This attribute follows the same rules as `bodyOpenClassName`, it must be a *constant string*;
69 |
70 | Here is an example that can help preventing this behavior:
71 |
72 | ```CSS
73 | .ReactModal__Body--open,
74 | .ReactModal__Html--open {
75 | overflow: hidden;
76 | }
77 | ```
78 |
79 | #### For the entire portal
80 |
81 | To specify a class to be applied to the entire portal, you may use the
82 | `portalClassName` prop. By default, there are no styles applied to the portal
83 | itself.
84 |
--------------------------------------------------------------------------------
/docs/styles/index.md:
--------------------------------------------------------------------------------
1 | Styles passed into the Modal via the `style` prop are merged with the defaults.
2 | The default styles are defined in the `Modal.defaultStyles` object and are
3 | shown below.
4 |
5 | ```jsx
6 |
34 | ```
35 |
36 | You can change the default styles by modifying `Modal.defaultStyles`. Please
37 | note that specifying a [CSS class](classes.md) for the overlay or the content
38 | will disable the default styles for that component.
39 |
--------------------------------------------------------------------------------
/docs/styles/transitions.md:
--------------------------------------------------------------------------------
1 | Using [CSS classes](classes.md), it is possible to implement transitions for
2 | when the modal is opened or closed. By placing the following CSS somewhere in
3 | your project's styles, you can make the modal content fade in when it is opened
4 | and fade out when it is closed:
5 |
6 | ```css
7 | .ReactModal__Overlay {
8 | opacity: 0;
9 | transition: opacity 2000ms ease-in-out;
10 | }
11 |
12 | .ReactModal__Overlay--after-open{
13 | opacity: 1;
14 | }
15 |
16 | .ReactModal__Overlay--before-close{
17 | opacity: 0;
18 | }
19 | ```
20 |
21 |
22 | The above example will apply the fade transition globally, affecting all modals
23 | whose `afterOpen` and `beforeClose` classes have not been set via the
24 | `className` prop. To apply the transition to one modal only, you can change
25 | the above class names and pass an object to your modal's `className` prop as
26 | described in the [previous section](classes.md).
27 |
28 | In order for the fade transition to work, you need to inform the ` ` about the transition time required for the animation.
29 |
30 | Like this
31 |
32 | ```javascript
33 |
34 | ```
35 |
36 | `closeTimeoutMS` is expressed in milliseconds.
37 |
38 | The `closeTimeoutMS` value and the value used in CSS or `style` prop passed to ` ` needs to be the same.
39 |
40 | Warning: if you are using **React 16**, the close transition works [only if you use](https://github.com/reactjs/react-modal/issues/530#issuecomment-335208533) the `isOpen` prop to toggle the visibility of the modal.
41 |
42 | Do not conditionally render the ` `.
43 |
44 | Instead of this
45 |
46 | ```javascript
47 | {
48 | this.state.showModal &&
49 | this.toggleModal()}
54 | >
55 | Add modal content here
56 |
57 | }
58 | ```
59 |
60 | *Do this*
61 |
62 | ```javascript
63 | {
64 | this.toggleModal()}
69 | >
70 | Add modal content here
71 |
72 | }
73 | ```
74 |
75 | React Modal has adopted the [stable Portal API](https://reactjs.org/docs/portals.html) as exposed in React 16.
76 |
77 | And `createProtal` API from React 16 [no longer allow](https://github.com/facebook/react/issues/10826#issuecomment-355719729) developers to intervene the unmounting of the portal component.
78 |
--------------------------------------------------------------------------------
/docs/testing/index.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | When using React Test Utils with this library, here are some things to keep in mind:
4 |
5 | - You need to set `isOpen={true}` on the modal component for it to render its children.
6 | - You need to use the `.portal` property, as in `ReactDOM.findDOMNode(renderedModal.portal)` or `TestUtils.scryRenderedDOMComponentsWithClass(Modal.portal, 'my-modal-class')` to acquire a handle to the inner contents of your modal.
7 |
--------------------------------------------------------------------------------
/examples/base.css:
--------------------------------------------------------------------------------
1 | h1, h2, h3 {
2 | font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
3 | font-weight: 200;
4 | }
5 |
6 | /* From http://instructure-react.github.io/library/shared.css */
7 |
8 | .padbox {
9 | padding: 40px;
10 | }
11 |
12 | .branding {
13 | border-bottom: 1px solid hsl(200, 0%, 90%);
14 | }
15 |
16 | .btn:not(:last-child) {
17 | margin-right: 20px;
18 | }
19 |
20 | .example:not(:last-child) {
21 | margin-bottom: 40px;
22 | }
23 |
--------------------------------------------------------------------------------
/examples/basic/app.css:
--------------------------------------------------------------------------------
1 | .ReactModal__Overlay {
2 | -webkit-perspective: 600;
3 | perspective: 600;
4 | opacity: 0;
5 | }
6 |
7 | .ReactModal__Overlay--after-open {
8 | opacity: 1;
9 | transition: opacity 150ms ease-out;
10 | }
11 |
12 | .ReactModal__Content {
13 | -webkit-transform: scale(0.5) rotateX(-30deg);
14 | transform: scale(0.5) rotateX(-30deg);
15 | }
16 |
17 | .ReactModal__Content--after-open {
18 | -webkit-transform: scale(1) rotateX(0deg);
19 | transform: scale(1) rotateX(0deg);
20 | transition: all 150ms ease-in;
21 | }
22 |
23 | .ReactModal__Overlay--before-close {
24 | opacity: 0;
25 | }
26 |
27 | .ReactModal__Content--before-close {
28 | -webkit-transform: scale(0.5) rotateX(30deg);
29 | transform: scale(0.5) rotateX(30deg);
30 | transition: all 150ms ease-in;
31 | }
32 |
33 | .ReactModal__Body--open,
34 | .ReactModal__Html--open {
35 | overflow: hidden;
36 | }
37 |
--------------------------------------------------------------------------------
/examples/basic/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Modal from 'react-modal';
4 | import SimpleUsage from './simple_usage';
5 | import MultipleModals from './multiple_modals';
6 | import Forms from './forms';
7 | import ReactRouter from './react-router';
8 | import NestedModals from './nested_modals';
9 |
10 | const appElement = document.getElementById('example');
11 |
12 | Modal.setAppElement('#example');
13 |
14 | const examples = [
15 | SimpleUsage,
16 | Forms,
17 | MultipleModals,
18 | NestedModals,
19 | ReactRouter
20 | ];
21 |
22 | class App extends Component {
23 | render() {
24 | return (
25 |
26 | {examples.map((example, key) => {
27 | const ExampleApp = example.app;
28 | return (
29 |
30 |
{`#${key + 1}. ${example.label}`}
31 |
32 |
33 | );
34 | })}
35 |
36 | );
37 | }
38 | }
39 |
40 | ReactDOM.render( , appElement);
41 |
--------------------------------------------------------------------------------
/examples/basic/forms/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Modal from 'react-modal';
3 |
4 | const MODAL_A = 'modal_a';
5 | const MODAL_B = 'modal_b';
6 |
7 | const DEFAULT_TITLE = 'Default title';
8 |
9 | class Forms extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = { isOpen: false };
14 | }
15 |
16 | toggleModal = event => {
17 | console.log(event);
18 | const { isOpen } = this.state;
19 | this.setState({ isOpen: !isOpen });
20 | }
21 |
22 | render() {
23 | const { isOpen } = this.state;
24 |
25 | return (
26 |
27 |
Open Modal
28 |
39 | Forms!
40 |
41 |
This is a description of what it does: nothing :)
42 |
67 |
68 |
69 |
70 | );
71 | }
72 | }
73 |
74 | export default {
75 | label: "Modal with forms fields.",
76 | app: Forms
77 | };
78 |
--------------------------------------------------------------------------------
/examples/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Basic Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | react-modal
13 | an accessible React modal dialog component
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/basic/multiple_modals/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Modal from 'react-modal';
3 |
4 | class List extends React.Component {
5 | render() {
6 | return (
7 |
8 | {this.props.items.map((x, i) => (
9 |
))}
12 |
13 | );
14 | }
15 | }
16 |
17 | class MultipleModals extends Component {
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | listItemsIsOpen: false,
22 | currentItem: -1,
23 | loading: false,
24 | items: []
25 | };
26 | }
27 |
28 | toggleModal = event => {
29 | event.preventDefault();
30 | if (this.state.listItemsIsOpen) {
31 | this.handleModalCloseRequest();
32 | return;
33 | }
34 | this.setState({
35 | items: [],
36 | listItemsIsOpen: true,
37 | loading: true
38 | });
39 | }
40 |
41 | handleModalCloseRequest = () => {
42 | // opportunity to validate something and keep the modal open even if it
43 | // requested to be closed
44 | this.setState({
45 | listItemsIsOpen: false,
46 | loading: false
47 | });
48 | }
49 |
50 | handleOnAfterOpenModal = () => {
51 | // when ready, we can access the available refs.
52 | (new Promise((resolve, reject) => {
53 | setTimeout(() => resolve(true), 500);
54 | })).then(res => {
55 | this.setState({
56 | items: [1, 2, 3, 4, 5].map(x => `Item ${x}`),
57 | loading: false
58 | });
59 | });
60 | }
61 |
62 | onItemClick = index => event => {
63 | this.setState({ currentItem: index });
64 | }
65 |
66 | cleanCurrentItem = () => {
67 | this.setState({ currentItem: -1 });
68 | }
69 |
70 | render() {
71 | const { listItemsIsOpen } = this.state;
72 | return (
73 |
74 |
Open Modal A
75 |
82 | List of items
83 | {this.state.loading ? (
84 | Loading...
85 | ) : (
86 |
87 | )}
88 |
89 |
-1}
94 | onRequestClose={this.cleanCurrentItem}
95 | aria={{
96 | labelledby: "item_title",
97 | describedby: "item_info"
98 | }}>
99 | Item: {this.state.items[this.state.currentItem]}
100 |
101 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur pulvinar varius auctor. Aliquam maximus et justo ut faucibus. Nullam sit amet urna molestie turpis bibendum accumsan a id sem. Proin ullamcorper nisl sapien, gravida dictum nibh congue vel. Vivamus convallis dolor vitae ipsum ultricies, vitae pulvinar justo tincidunt. Maecenas a nunc elit. Phasellus fermentum, tellus ut consectetur scelerisque, eros nunc lacinia eros, aliquet efficitur tellus arcu a nibh. Praesent quis consequat nulla. Etiam dapibus ac sem vel efficitur. Nunc faucibus efficitur leo vitae vulputate. Nunc at quam vitae felis pretium vehicula vel eu quam. Quisque sapien mauris, condimentum eget dictum ut, congue id dolor. Donec vitae varius orci, eu faucibus turpis. Morbi eleifend orci non urna bibendum, ac scelerisque augue efficitur.
102 |
103 |
Maecenas justo justo, laoreet vitae odio quis, lacinia porttitor arcu. Nunc nisl est, ultricies sed laoreet eu, semper in nisi. Phasellus lacinia porta purus, eu luctus neque. Nullam quis mi malesuada, vestibulum sem id, rhoncus purus. Aliquam erat volutpat. Duis nec turpis mi. Pellentesque eleifend nisl sed risus aliquet, eu feugiat elit auctor. Suspendisse ac neque vitae ligula consequat aliquam. Vivamus sit amet eros et ante mollis porta.
104 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | export default {
112 | label: "Working with many modal.",
113 | app: MultipleModals
114 | };
115 |
--------------------------------------------------------------------------------
/examples/basic/nested_modals/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Modal from 'react-modal';
3 |
4 | class Item extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | isOpen: false
9 | };
10 | }
11 |
12 | toggleModal = index => event => {
13 | console.log("NESTED MODAL ITEM", event);
14 | this.setState({
15 | itemNumber: !this.state.isOpen ? index : null,
16 | isOpen: !this.state.isOpen
17 | });
18 | };
19 |
20 | render() {
21 | const { isOpen, itemNumber } = this.state;
22 | const { number, index } = this.props;
23 |
24 | const toggleModal = this.toggleModal(index);
25 |
26 | return (
27 |
28 |
{number}
29 |
37 | Item: {itemNumber + 1}
38 |
39 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur pulvinar varius auctor. Aliquam maximus et justo ut faucibus. Nullam sit amet urna molestie turpis bibendum accumsan a id sem. Proin ullamcorper nisl sapien, gravida dictum nibh congue vel. Vivamus convallis dolor vitae ipsum ultricies, vitae pulvinar justo tincidunt. Maecenas a nunc elit. Phasellus fermentum, tellus ut consectetur scelerisque, eros nunc lacinia eros, aliquet efficitur tellus arcu a nibh. Praesent quis consequat nulla. Etiam dapibus ac sem vel efficitur. Nunc faucibus efficitur leo vitae vulputate. Nunc at quam vitae felis pretium vehicula vel eu quam. Quisque sapien mauris, condimentum eget dictum ut, congue id dolor. Donec vitae varius orci, eu faucibus turpis. Morbi eleifend orci non urna bibendum, ac scelerisque augue efficitur.
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | class List extends Component {
48 | render() {
49 | return this.props.items.map((n, index) => (
50 |
51 | ));
52 | }
53 | }
54 |
55 |
56 | class NestedModals extends Component {
57 | constructor(props) {
58 | super(props);
59 |
60 | this.state = {
61 | isOpen: false,
62 | currentItem: -1,
63 | loading: false,
64 | items: []
65 | };
66 | }
67 |
68 | toggleModal = event => {
69 | event.preventDefault();
70 | console.log("NESTEDMODAL", event);
71 | this.setState({
72 | items: [],
73 | isOpen: !this.state.isOpen,
74 | loading: true
75 | });
76 | }
77 |
78 | handleOnAfterOpenModal = () => {
79 | // when ready, we can access the available refs.
80 | (new Promise((resolve, reject) => {
81 | setTimeout(() => resolve(true), 500);
82 | })).then(res => {
83 | this.setState({
84 | items: [1, 2, 3, 4, 5].map(x => `Item ${x}`),
85 | loading: false
86 | });
87 | });
88 | }
89 |
90 | render() {
91 | const { isOpen } = this.state;
92 | return (
93 |
94 |
Open Modal A
95 |
102 | List of items
103 | {this.state.loading ? (
104 | Loading...
105 | ) : (
106 |
107 | )}
108 |
109 |
110 | );
111 | }
112 | }
113 |
114 | export default {
115 | label: "Working with nested modals.",
116 | app: NestedModals
117 | };
118 |
--------------------------------------------------------------------------------
/examples/basic/react-router/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React, { Component } from 'react';
3 | import createHistory from 'history/createBrowserHistory';
4 | import { Router, Route, Switch } from 'react-router';
5 | import { Link } from 'react-router-dom';
6 | import Modal from 'react-modal';
7 |
8 | const history = createHistory();
9 |
10 | const Content = label => () => {`Content ${label}`}
;
11 |
12 | const shouldOpenModal = locationPath => /\bmodal\b/.test(locationPath);
13 |
14 | const ReactRouterModal = props => (
15 | history.push("/basic")}>
18 |
19 |
Link A
20 |
Link B
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
31 | class App extends Component {
32 | render() {
33 | return (
34 |
35 |
36 | Modal
37 |
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | export default {
45 | label: "react-modal and react-router.",
46 | app: App
47 | };
48 |
--------------------------------------------------------------------------------
/examples/basic/simple_usage/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Modal from 'react-modal';
3 | import MyModal from './modal';
4 |
5 | const MODAL_A = 'modal_a';
6 | const MODAL_B = 'modal_b';
7 |
8 | const DEFAULT_TITLE = 'Default title';
9 |
10 | class SimpleUsage extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | title1: DEFAULT_TITLE,
15 | currentModal: null
16 | };
17 | }
18 |
19 | toggleModal = key => event => {
20 | event.preventDefault();
21 | if (this.state.currentModal) {
22 | this.handleModalCloseRequest();
23 | return;
24 | }
25 |
26 | this.setState({
27 | ...this.state,
28 | currentModal: key,
29 | title1: DEFAULT_TITLE
30 | });
31 | }
32 |
33 | handleModalCloseRequest = () => {
34 | // opportunity to validate something and keep the modal open even if it
35 | // requested to be closed
36 | this.setState({
37 | ...this.state,
38 | currentModal: null
39 | });
40 | }
41 |
42 | handleInputChange = e => {
43 | let text = e.target.value;
44 | if (text == '') {
45 | text = DEFAULT_TITLE;
46 | }
47 | this.setState({ ...this.state, title1: text });
48 | }
49 |
50 | handleOnAfterOpenModal = () => {
51 | // when ready, we can access the available refs.
52 | this.heading && (this.heading.style.color = '#F00');
53 | }
54 |
55 | render() {
56 | const { currentModal } = this.state;
57 |
58 | return (
59 |
60 |
Open Modal A
61 |
Open Modal B
62 |
69 |
82 | this.heading = h1}>This is the modal 2!
83 |
84 |
This is a description of what it does: nothing :)
85 |
close
86 |
87 |
88 |
89 | );
90 | }
91 | }
92 |
93 | export default {
94 | label: "Working with one modal at a time.",
95 | app: SimpleUsage
96 | };
97 |
--------------------------------------------------------------------------------
/examples/basic/simple_usage/modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-modal';
3 |
4 | export default props => {
5 | const {
6 | title, isOpen, askToClose,
7 | onAfterOpen, onRequestClose, onChangeInput
8 | } = props;
9 |
10 | return (
11 |
18 | {title}
19 | close
20 | I am a modal. Use the first input to change the modal's title.
21 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/examples/bootstrap/app.css:
--------------------------------------------------------------------------------
1 | .ReactModal__Overlay {
2 | -webkit-perspective: 600;
3 | perspective: 600;
4 | opacity: 0;
5 | overflow-x: hidden;
6 | overflow-y: auto;
7 | background-color: rgba(0, 0, 0, 0.5);
8 | }
9 |
10 | .ReactModal__Overlay--after-open {
11 | opacity: 1;
12 | transition: opacity 150ms ease-out;
13 | }
14 |
15 | .ReactModal__Content {
16 | -webkit-transform: scale(0.5) rotateX(-30deg);
17 | transform: scale(0.5) rotateX(-30deg);
18 | }
19 |
20 | .ReactModal__Content--after-open {
21 | -webkit-transform: scale(1) rotateX(0deg);
22 | transform: scale(1) rotateX(0deg);
23 | transition: all 150ms ease-in;
24 | }
25 |
26 | .ReactModal__Overlay--before-close {
27 | opacity: 0;
28 | }
29 |
30 | .ReactModal__Content--before-close {
31 | -webkit-transform: scale(0.5) rotateX(30deg);
32 | transform: scale(0.5) rotateX(30deg);
33 | transition: all 150ms ease-in;
34 | }
35 |
36 | .ReactModal__Content.modal-dialog {
37 | border: none;
38 | background-color: transparent;
39 | }
40 |
--------------------------------------------------------------------------------
/examples/bootstrap/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Modal from 'react-modal';
4 |
5 | var appElement = document.getElementById('example');
6 |
7 | Modal.setAppElement(appElement);
8 |
9 | class App extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = { modalIsOpen: false };
13 | }
14 |
15 | openModal = () => {
16 | this.setState({modalIsOpen: true});
17 | }
18 |
19 | closeModal = () => {
20 | this.setState({modalIsOpen: false});
21 | }
22 |
23 | handleModalCloseRequest = () => {
24 | // opportunity to validate something and keep the modal open even if it
25 | // requested to be closed
26 | this.setState({modalIsOpen: false});
27 | }
28 |
29 | handleSaveClicked = (e) => {
30 | alert('Save button was clicked');
31 | }
32 |
33 | render() {
34 | return (
35 |
36 |
Open Modal
37 |
43 |
44 |
45 |
Modal title
46 |
47 | ×
48 | Close
49 |
50 |
51 |
52 |
Really long content...
53 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
54 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
55 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
56 |
57 |
58 | Close
59 | Save changes
60 |
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | ReactDOM.render( , appElement);
69 |
--------------------------------------------------------------------------------
/examples/bootstrap/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bootstrap-Style Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | react-modal
13 | an accessible React modal dialog component
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Basic Example
5 |
6 |
7 |
8 |
9 |
10 |
11 | react-modal
12 | an accessible React modal dialog component
13 |
14 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/wc/app.css:
--------------------------------------------------------------------------------
1 | .ReactModal__Overlay {
2 | -webkit-perspective: 600;
3 | perspective: 600;
4 | opacity: 0;
5 | overflow-x: hidden;
6 | overflow-y: auto;
7 | background-color: rgba(0, 0, 0, 0.5);
8 | }
9 |
10 | .ReactModal__Overlay--after-open {
11 | opacity: 1;
12 | transition: opacity 150ms ease-out;
13 | }
14 |
15 | .ReactModal__Content {
16 | -webkit-transform: scale(0.5) rotateX(-30deg);
17 | transform: scale(0.5) rotateX(-30deg);
18 | }
19 |
20 | .ReactModal__Content--after-open {
21 | -webkit-transform: scale(1) rotateX(0deg);
22 | transform: scale(1) rotateX(0deg);
23 | transition: all 150ms ease-in;
24 | }
25 |
26 | .ReactModal__Overlay--before-close {
27 | opacity: 0;
28 | }
29 |
30 | .ReactModal__Content--before-close {
31 | -webkit-transform: scale(0.5) rotateX(30deg);
32 | transform: scale(0.5) rotateX(30deg);
33 | transition: all 150ms ease-in;
34 | }
35 |
36 | .ReactModal__Content.modal-dialog {
37 | border: none;
38 | background-color: transparent;
39 | }
40 |
--------------------------------------------------------------------------------
/examples/wc/app.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Modal from 'react-modal';
4 |
5 | import '@webcomponents/custom-elements/src/native-shim';
6 |
7 | var appElement = document.getElementById('example');
8 |
9 | Modal.setAppElement(appElement);
10 |
11 | class App extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { modalIsOpen: false };
15 | }
16 |
17 | openModal = () => {
18 | this.setState({modalIsOpen: true});
19 | }
20 |
21 | closeModal = () => {
22 | this.setState({modalIsOpen: false});
23 | }
24 |
25 | handleModalCloseRequest = () => {
26 | // opportunity to validate something and keep the modal open even if it
27 | // requested to be closed
28 | this.setState({modalIsOpen: false});
29 | }
30 |
31 | handleSaveClicked = (e) => {
32 | alert('Save button was clicked');
33 | }
34 |
35 | render() {
36 | return (
37 |
38 |
Open Modal
39 |
45 |
46 |
47 |
Modal title
48 |
49 |
50 |
51 | ×
52 | Close
53 |
54 |
55 |
56 |
57 |
Really long content...
58 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
59 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
60 |
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus
61 |
62 |
63 | Close
64 | Save changes
65 |
66 |
67 |
68 |
69 | );
70 | }
71 | }
72 |
73 | ReactDOM.render( , appElement);
74 |
75 | class AwesomeButton extends HTMLElement {
76 | constructor() {
77 | super();
78 | }
79 |
80 | // this shows with no shadow root
81 | connectedCallback() {
82 | this.innerHTML = `
83 | I'm Awesome!
84 | `;
85 | }
86 | }
87 |
88 | customElements.define("awesome-button", AwesomeButton);
89 |
--------------------------------------------------------------------------------
/examples/wc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bootstrap-Style Example
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | react-modal
13 | an accessible React modal dialog component
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | let browsers = ['ChromeHeadless'];
2 | let coverageType = 'text';
3 |
4 | if (process.env.CONTINUOUS_INTEGRATION) {
5 | browsers = ['Firefox'];
6 | coverageType = 'lcovonly';
7 | }
8 |
9 | module.exports = function(config) {
10 | config.set({
11 | frameworks: ['mocha'],
12 |
13 | preprocessors: {
14 | './src/*.js': ['coverage'],
15 | './src/**/*.js': ['coverage'],
16 | './specs/index.js': ['webpack', 'sourcemap']
17 | },
18 |
19 | files: ['./specs/index.js'],
20 |
21 | webpack: require('./scripts/webpack.test.config'),
22 |
23 | webpackMiddleware: { stats: 'errors-only' },
24 |
25 | reporters: ['mocha', 'coverage'],
26 |
27 | mochaReporter: { showDiff: true },
28 |
29 | coverageReporter: {
30 | type : coverageType,
31 | dir : 'coverage/',
32 | subdir: '.'
33 | },
34 |
35 | port: 9876,
36 |
37 | colors: true,
38 |
39 | logLevel: config.LOG_INFO,
40 |
41 | autoWatch: true,
42 |
43 | browsers,
44 |
45 | // Increase timeouts to prevent the issue with disconnected tests (https://goo.gl/nstA69)
46 | captureTimeout: 4 * 60 * 1000,
47 |
48 | singleRun: (process.env.CONTINUOUS_INTEGRATION)
49 | });
50 | };
51 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: react-modal documentation
2 | site_dir: _book
3 | nav:
4 | - Overview: index.md
5 | - Accessibility: accessibility/index.md
6 | - Styles:
7 | - Inline Styles: styles/index.md
8 | - Classes: styles/classes.md
9 | - Transitions: styles/transitions.md
10 | - Examples:
11 | - Run local: examples/index.md
12 | - Minimal: examples/minimal.md
13 | - setAppElement: examples/set_app_element.md
14 | - shouldCloseOnOverlayClick: examples/should_close_on_overlay_click.md
15 | - onRequestClose: examples/on_request_close.md
16 | - Global Overrides: examples/global_overrides.md
17 | - Inline Styles: examples/inline_styles.md
18 | - Css Classes: examples/css_classes.md
19 | - Testing: testing/index.md
20 | - Contributing:
21 | - Overview: contributing/index.md
22 | - Development setup: contributing/development.md
23 | theme:
24 | name: 'material'
25 | markdown_extensions:
26 | - codehilite
27 | extra_css: [pygments.css]
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-modal",
3 | "version": "3.16.3",
4 | "description": "Accessible modal dialog component for React.JS",
5 | "main": "./lib/index.js",
6 | "module": "./lib/index.js",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/reactjs/react-modal.git"
10 | },
11 | "homepage": "https://github.com/reactjs/react-modal",
12 | "bugs": "https://github.com/reactjs/react-modal/issues",
13 | "directories": {
14 | "example": "examples"
15 | },
16 | "scripts": {
17 | "start": "npx webpack-dev-server --config ./scripts/webpack.config.js --inline --host 127.0.0.1 --content-base examples/",
18 | "test": "cross-env NODE_ENV=test karma start",
19 | "lint": "eslint src/"
20 | },
21 | "authors": [
22 | "Ryan Florence"
23 | ],
24 | "license": "MIT",
25 | "devDependencies": {
26 | "@webcomponents/custom-elements": "^1.5.0",
27 | "babel-cli": "^6.26.0",
28 | "babel-core": "^6.25.0",
29 | "babel-eslint": "^8.0.1",
30 | "babel-loader": "^7.1.2",
31 | "babel-plugin-add-module-exports": "^0.2.1",
32 | "babel-preset-env": "^1.6.0",
33 | "babel-preset-react": "^6.24.1",
34 | "babel-preset-stage-2": "^6.24.1",
35 | "coveralls": "^3.1.0",
36 | "cross-env": "^5.2.1",
37 | "eslint": "^4.8.0",
38 | "eslint-config-prettier": "^2.6.0",
39 | "eslint-import-resolver-webpack": "^0.9.0",
40 | "eslint-plugin-import": "^2.23.2",
41 | "eslint-plugin-jsx-a11y": "^6.4.1",
42 | "eslint-plugin-prettier": "^2.3.1",
43 | "eslint-plugin-react": "^7.23.2",
44 | "istanbul-instrumenter-loader": "^3.0.0",
45 | "karma": "^6.3.6",
46 | "karma-chrome-launcher": "2.2.0",
47 | "karma-coverage": "^2.0.3",
48 | "karma-firefox-launcher": "1.0.1",
49 | "karma-mocha": "^2.0.1",
50 | "karma-mocha-reporter": "^2.2.1",
51 | "karma-sourcemap-loader": "^0.3.8",
52 | "karma-webpack": "^2.0.4",
53 | "mocha": "^8.4.0",
54 | "npm-run-all": "^4.1.1",
55 | "prettier": "^1.19.1",
56 | "react": "^17.0.2",
57 | "react-dom": "^17.0.2",
58 | "react-router": "^4.2.0",
59 | "react-router-dom": "^4.2.2",
60 | "should": "^13.1.0",
61 | "sinon": "next",
62 | "uglify-js": "3.1.1",
63 | "webpack": "^4.46.0",
64 | "webpack-cli": "^3.3.12",
65 | "webpack-dev-server": "^3.11.2"
66 | },
67 | "dependencies": {
68 | "exenv": "^1.2.0",
69 | "prop-types": "^15.7.2",
70 | "react-lifecycles-compat": "^3.0.0",
71 | "warning": "^4.0.3"
72 | },
73 | "peerDependencies": {
74 | "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19",
75 | "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19"
76 | },
77 | "tags": [
78 | "react",
79 | "modal",
80 | "dialog"
81 | ],
82 | "keywords": [
83 | "react",
84 | "react-component",
85 | "modal",
86 | "dialog"
87 | ]
88 | }
89 |
--------------------------------------------------------------------------------
/scripts/changelog.py:
--------------------------------------------------------------------------------
1 | # Requires python3 to work since, python 3< does not implement %z.
2 |
3 | import sys
4 |
5 | sys.path += ["/Users/diasbruno/.local/lib/python3.7/site-packages"]
6 |
7 | from datetime import datetime
8 | from subprocess import Popen, PIPE
9 | import semver
10 | import functools
11 |
12 |
13 | # 1: version, 2: date, 3: dashes, 4: entries
14 | LOG_ENTRY = """{}
15 | {}
16 |
17 | {}
18 | """
19 |
20 |
21 | head_version = "HEAD"
22 |
23 |
24 | def git_exec(args):
25 | p = Popen(" ".join(["git"] + args), shell=True, stdout=PIPE, stderr=PIPE)
26 | out, err = p.communicate()
27 | return out.decode('utf-8')
28 |
29 |
30 | def git_log(args):
31 | return git_exec(["log"] + args)
32 |
33 |
34 | def log_entry(entry):
35 | log = entry.split(' ')
36 | hash = log[0]
37 | log = ' '.join(log[1:])
38 |
39 | return "- [%s](../../commit/%s) %s" % (hash, hash, log)
40 |
41 |
42 | def get_tags_date(tag):
43 | args = [tag, "-1", '--format="%ad"']
44 | date_time = git_log(args).split('\n')[0]
45 |
46 | if date_time != '':
47 | dt = datetime.strptime(date_time, '%a %b %d %H:%M:%S %Y %z')
48 | else:
49 | dt = datetime.now()
50 | dt = dt.strftime('%a, %d %b %Y %H:%M:%S')
51 | return dt
52 |
53 |
54 | def log_in_between_versions(t):
55 | (a, b, logs) = t
56 |
57 | v = b and to_version(b) or head_version
58 | dt = get_tags_date(v)
59 |
60 | header = "{} - {} UTC".format(b or head_version, dt)
61 | dashes = ("-" * len(header))
62 |
63 | def write_log(acc, log):
64 | if log[8:8+7] == 'Release' or log[8:8+7] == 'release':
65 | return acc
66 | acc.append(log_entry(log))
67 | return acc
68 |
69 | actual_log = list(functools.reduce(write_log,
70 | logs.splitlines(),
71 | []))
72 |
73 | if len(actual_log) == 0:
74 | entries = '-\n\n'
75 | else:
76 | entries = "\n".join(actual_log)
77 |
78 | return LOG_ENTRY.format(header, dashes, entries)
79 |
80 |
81 | def adjacents(ls, f, res):
82 | if len(ls) == 0:
83 | return res
84 |
85 | first = ls[0]
86 | if len(ls) == 1:
87 | next = None
88 | else:
89 | next = ls[1]
90 |
91 | res.append(f(first, next))
92 | return adjacents(ls[1:], f, res)
93 |
94 |
95 | def to_version(tag):
96 | if not tag:
97 | return "HEAD"
98 | if tag.prerelease:
99 | return str(tag)
100 | return "v{}".format(tag)
101 |
102 |
103 | def logs_between(base, b):
104 | to = to_version(b)
105 | between = "{}..{}".format(to_version(base), to)
106 | logs = git_log([between, "--format='%h %s'"])
107 | return (base, b, logs)
108 |
109 |
110 | def parse_version(version):
111 | if version == 'HEAD':
112 | return version
113 | if version[0] == 'v':
114 | version = version[1:]
115 | return semver.parse_version_info(version)
116 |
117 |
118 | def get_all_tags():
119 | lines = git_exec(["tag", "-l"])
120 | versions = map(parse_version, lines.splitlines())
121 | return sorted(versions)
122 |
123 |
124 | def generate_current():
125 | versions = get_all_tags()
126 | base = versions[-1]
127 | logs = logs_between(base, None)
128 | return [log_in_between_versions(logs)]
129 |
130 |
131 | def generate_all():
132 | versions = get_all_tags()
133 | log_versions = adjacents(versions, logs_between, [])
134 | vs = map(log_in_between_versions, log_versions)
135 | return list(vs)
136 |
137 |
138 | if __name__ == "__main__":
139 | argc = len(sys.argv)
140 |
141 | if sys.argv[1] == '-a': # all
142 | head_version = sys.argv[2] if argc > 2 else "HEAD"
143 | log = generate_all()
144 | log.reverse()
145 |
146 | elif sys.argv[1] == '-c': # current
147 | head_version = sys.argv[2]
148 | log = generate_current()
149 |
150 | print("\n".join(log))
151 |
--------------------------------------------------------------------------------
/scripts/defaultConfig.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | output: {
6 | filename: '[name].js',
7 | path: path.resolve(__dirname, './examples/__build__'),
8 | publicPath: '/__build__/'
9 | },
10 | module: {
11 | rules: [
12 | { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } }
13 | ]
14 | },
15 | resolve: {
16 | alias: {
17 | "react-modal": path.resolve(__dirname, "../src")
18 | }
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/scripts/repo_status:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ ! -z "`git status -s`" ]; then
4 | echo "Working tree is not clean"
5 | git status -s
6 | read -p "Proceed? [Y/n] " OK
7 | if [[ "$OK" -eq "n" || "$OK" -eq "N" || -z "$OK" ]]; then
8 | echo "Stopping publish"
9 | exit 1
10 | fi
11 | fi
12 |
--------------------------------------------------------------------------------
/scripts/version:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | JQ=$(which jq)
4 |
5 | if [[ -z "$JQ" ]]; then
6 | echo "jq is missing."
7 | fi
8 |
9 | echo "Current version is: $1"
10 |
11 | read -p "Bump to: " NEW_VERSION
12 |
13 | if [[ ! -z "$(git tag -l | grep v${NEW_VERSION})" ]]; then
14 | echo "Tag $NEW_VERSION already exists."
15 | exit 1
16 | fi
17 |
18 | FILES="package.json bower.json"
19 |
20 | for F in $FILES; do
21 | $JQ ".version = \"${NEW_VERSION}\"" "$F" > up.json
22 | cat up.json > "$F"
23 | done
24 |
25 | rm up.json
26 |
--------------------------------------------------------------------------------
/scripts/webpack.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const webpack = require('webpack');
4 | const defaultConfig = require('./defaultConfig');
5 |
6 | var EXAMPLES_DIR = path.resolve(__dirname, '../examples');
7 |
8 | function isDirectory(dir) {
9 | return fs.lstatSync(dir).isDirectory();
10 | }
11 |
12 | function buildEntries() {
13 | return fs.readdirSync(EXAMPLES_DIR).reduce(function (entries, dir) {
14 | if (dir === 'build')
15 | return entries;
16 |
17 | var isDraft = dir.charAt(0) === '_';
18 |
19 | if (!isDraft && isDirectory(path.join(EXAMPLES_DIR, dir)))
20 | entries[dir] = path.join(EXAMPLES_DIR, dir, 'app.js');
21 |
22 | return entries;
23 | }, {});
24 | }
25 |
26 | module.exports = {
27 | ...defaultConfig,
28 | entry: buildEntries(),
29 | };
30 |
--------------------------------------------------------------------------------
/scripts/webpack.dist.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const defaultConfig = require('./defaultConfig');
4 |
5 | const reactExternal = {
6 | root: 'React',
7 | commonjs2: 'react',
8 | commonjs: 'react',
9 | amd: 'react'
10 | };
11 | const reactDOMExternal = {
12 | root: 'ReactDOM',
13 | commonjs2: 'react-dom',
14 | commonjs: 'react-dom',
15 | amd: 'react-dom'
16 | };
17 |
18 | module.exports = {
19 | ...defaultConfig,
20 | mode: 'production',
21 | entry: {
22 | 'react-modal': path.resolve(__dirname, '../src/index.js'),
23 | 'react-modal.min': path.resolve(__dirname, '../src/index.js')
24 | },
25 | externals: {
26 | 'react': reactExternal,
27 | 'react-dom': reactDOMExternal
28 | },
29 | output: {
30 | filename: '[name].js',
31 | chunkFilename: '[id].chunk.js',
32 | path: path.resolve(__dirname, '../dist'),
33 | publicPath: '/',
34 | libraryTarget: 'umd',
35 | library: 'ReactModal'
36 | },
37 | optimization: {
38 | minimize: true
39 | },
40 | plugins: [
41 | new webpack.DefinePlugin({
42 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
43 | })
44 | ]
45 | };
46 |
--------------------------------------------------------------------------------
/scripts/webpack.test.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const defaultConfig = require('./defaultConfig');
3 |
4 | module.exports = {
5 | ...defaultConfig,
6 | plugins: [],
7 | entry: path.resolve(__dirname, '../specs/index.js'),
8 | devtool: 'inline-source-map',
9 | module: {
10 | ...defaultConfig.module,
11 | rules: [
12 | {
13 | test: /\.js$/,
14 | use: { loader: 'istanbul-instrumenter-loader' },
15 | enforce: 'post',
16 | include: path.resolve(__dirname, '../src')
17 | },
18 | ...defaultConfig.module.rules
19 | ]
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/specs/Modal.events.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import "should";
5 | import sinon from "sinon";
6 | import Modal from "react-modal";
7 | import {
8 | moverlay,
9 | mcontent,
10 | clickAt,
11 | mouseDownAt,
12 | mouseUpAt,
13 | escKeyDown,
14 | escKeyDownWithCode,
15 | tabKeyDown,
16 | tabKeyDownWithCode,
17 | withModal,
18 | withElementCollector,
19 | createHTMLElement
20 | } from "./helper";
21 |
22 | export default () => {
23 | it("should trigger the onAfterOpen callback", () => {
24 | const afterOpenCallback = sinon.spy();
25 | withElementCollector(() => {
26 | const props = { isOpen: true, onAfterOpen: afterOpenCallback };
27 | const node = createHTMLElement("div");
28 | ReactDOM.render( , node);
29 | requestAnimationFrame(() => {
30 | afterOpenCallback.called.should.be.ok();
31 | ReactDOM.unmountComponentAtNode(node);
32 | });
33 | });
34 | });
35 |
36 | it("should call onAfterOpen with overlay and content references", () => {
37 | const afterOpenCallback = sinon.spy();
38 | withElementCollector(() => {
39 | const props = { isOpen: true, onAfterOpen: afterOpenCallback };
40 | const node = createHTMLElement("div");
41 | const modal = ReactDOM.render( , node);
42 | requestAnimationFrame(() => {
43 | sinon.assert.calledWith(afterOpenCallback, {
44 | overlayEl: modal.portal.overlay,
45 | contentEl: modal.portal.content
46 | });
47 | ReactDOM.unmountComponentAtNode(node);
48 | });
49 | });
50 | });
51 |
52 | it("should trigger the onAfterClose callback", () => {
53 | const onAfterCloseCallback = sinon.spy();
54 | withModal({
55 | isOpen: true,
56 | onAfterClose: onAfterCloseCallback
57 | });
58 | onAfterCloseCallback.called.should.be.ok();
59 | });
60 |
61 | it("should not trigger onAfterClose callback when unmounting a closed modal", () => {
62 | const onAfterCloseCallback = sinon.spy();
63 | withModal({ isOpen: false, onAfterClose: onAfterCloseCallback });
64 | onAfterCloseCallback.called.should.not.be.ok();
65 | });
66 |
67 | it("should trigger onAfterClose callback when unmounting an opened modal", () => {
68 | const onAfterCloseCallback = sinon.spy();
69 | withModal({ isOpen: true, onAfterClose: onAfterCloseCallback });
70 | onAfterCloseCallback.called.should.be.ok();
71 | });
72 |
73 | it("keeps focus inside the modal when child has no tabbable elements", () => {
74 | let tabPrevented = false;
75 | const props = { isOpen: true };
76 | withModal(props, "hello", modal => {
77 | const content = mcontent(modal);
78 | document.activeElement.should.be.eql(content);
79 | tabKeyDown(content, {
80 | preventDefault() {
81 | tabPrevented = true;
82 | }
83 | });
84 | tabPrevented.should.be.eql(true);
85 | });
86 | });
87 |
88 | it("handles case when child has no tabbable elements", () => {
89 | const props = { isOpen: true };
90 | withModal(props, "hello", modal => {
91 | const content = mcontent(modal);
92 | tabKeyDown(content);
93 | document.activeElement.should.be.eql(content);
94 | });
95 | });
96 |
97 | it("traps tab in the modal on shift + tab", () => {
98 | const topButton = top ;
99 | const bottomButton = bottom ;
100 | const modalContent = (
101 |
102 | {topButton}
103 | {bottomButton}
104 |
105 | );
106 | const props = { isOpen: true };
107 | withModal(props, modalContent, modal => {
108 | const content = mcontent(modal);
109 | tabKeyDown(content, { shiftKey: true });
110 | document.activeElement.textContent.should.be.eql("bottom");
111 | });
112 | });
113 |
114 | it("traps tab in the modal on shift + tab with KeyboardEvent.code", () => {
115 | const topButton = top ;
116 | const bottomButton = bottom ;
117 | const modalContent = (
118 |
119 | {topButton}
120 | {bottomButton}
121 |
122 | );
123 | const props = { isOpen: true };
124 | withModal(props, modalContent, modal => {
125 | const content = mcontent(modal);
126 | tabKeyDownWithCode(content, { shiftKey: true });
127 | document.activeElement.textContent.should.be.eql("bottom");
128 | });
129 | });
130 |
131 | describe("shouldCloseOnEsc", () => {
132 | context("when true", () => {
133 | it("should close on Esc key event", () => {
134 | const requestCloseCallback = sinon.spy();
135 | withModal(
136 | {
137 | isOpen: true,
138 | shouldCloseOnEsc: true,
139 | onRequestClose: requestCloseCallback
140 | },
141 | null,
142 | modal => {
143 | escKeyDown(mcontent(modal));
144 | requestCloseCallback.called.should.be.ok();
145 | // Check if event is passed to onRequestClose callback.
146 | const event = requestCloseCallback.getCall(0).args[0];
147 | event.should.be.ok();
148 | }
149 | );
150 | });
151 |
152 | it("should close on Esc key event with KeyboardEvent.code", () => {
153 | const requestCloseCallback = sinon.spy();
154 | withModal(
155 | {
156 | isOpen: true,
157 | shouldCloseOnEsc: true,
158 | onRequestClose: requestCloseCallback
159 | },
160 | null,
161 | modal => {
162 | escKeyDownWithCode(mcontent(modal));
163 | requestCloseCallback.called.should.be.ok();
164 | // Check if event is passed to onRequestClose callback.
165 | const event = requestCloseCallback.getCall(0).args[0];
166 | event.should.be.ok();
167 | }
168 | );
169 | });
170 | });
171 |
172 | context("when false", () => {
173 | it("should not close on Esc key event", () => {
174 | const requestCloseCallback = sinon.spy();
175 | const props = {
176 | isOpen: true,
177 | shouldCloseOnEsc: false,
178 | onRequestClose: requestCloseCallback
179 | };
180 | withModal(props, null, modal => {
181 | escKeyDown(mcontent(modal));
182 | requestCloseCallback.called.should.be.false;
183 | });
184 | });
185 | });
186 | });
187 |
188 | describe("shouldCloseOnoverlayClick", () => {
189 | it("when false, click on overlay should not close", () => {
190 | const requestCloseCallback = sinon.spy();
191 | const props = {
192 | isOpen: true,
193 | shouldCloseOnOverlayClick: false
194 | };
195 | withModal(props, null, modal => {
196 | const overlay = moverlay(modal);
197 | clickAt(overlay);
198 | requestCloseCallback.called.should.not.be.ok();
199 | });
200 | });
201 |
202 | it("when true, click on overlay must close", () => {
203 | const requestCloseCallback = sinon.spy();
204 | const props = {
205 | isOpen: true,
206 | shouldCloseOnOverlayClick: true,
207 | onRequestClose: requestCloseCallback
208 | };
209 | withModal(props, null, modal => {
210 | clickAt(moverlay(modal));
211 | requestCloseCallback.called.should.be.ok();
212 | });
213 | });
214 |
215 | it("overlay mouse down and content mouse up, should not close", () => {
216 | const requestCloseCallback = sinon.spy();
217 | const props = {
218 | isOpen: true,
219 | shouldCloseOnOverlayClick: true,
220 | onRequestClose: requestCloseCallback
221 | };
222 | withModal(props, null, modal => {
223 | mouseDownAt(moverlay(modal));
224 | mouseUpAt(mcontent(modal));
225 | requestCloseCallback.called.should.not.be.ok();
226 | });
227 | });
228 |
229 | it("content mouse down and overlay mouse up, should not close", () => {
230 | const requestCloseCallback = sinon.spy();
231 | const props = {
232 | isOpen: true,
233 | shouldCloseOnOverlayClick: true,
234 | onRequestClose: requestCloseCallback
235 | };
236 | withModal(props, null, modal => {
237 | mouseDownAt(mcontent(modal));
238 | mouseUpAt(moverlay(modal));
239 | requestCloseCallback.called.should.not.be.ok();
240 | });
241 | });
242 | });
243 |
244 | it("should not stop event propagation", () => {
245 | let hasPropagated = false;
246 | const props = {
247 | isOpen: true,
248 | shouldCloseOnOverlayClick: true
249 | };
250 | withModal(props, null, modal => {
251 | const propagated = () => (hasPropagated = true);
252 | window.addEventListener("click", propagated);
253 | const event = new MouseEvent("click", { bubbles: true });
254 | moverlay(modal).dispatchEvent(event);
255 | hasPropagated.should.be.ok();
256 | window.removeEventListener("click", propagated);
257 | });
258 | });
259 |
260 | it("verify event passing on overlay click", () => {
261 | const requestCloseCallback = sinon.spy();
262 | const props = {
263 | isOpen: true,
264 | shouldCloseOnOverlayClick: true,
265 | onRequestClose: requestCloseCallback
266 | };
267 | withModal(props, null, modal => {
268 | // click the overlay
269 | clickAt(moverlay(modal), {
270 | // Used to test that this was the event received
271 | fakeData: "ABC"
272 | });
273 | requestCloseCallback.called.should.be.ok();
274 | // Check if event is passed to onRequestClose callback.
275 | const event = requestCloseCallback.getCall(0).args[0];
276 | event.should.be.ok();
277 | });
278 | });
279 |
280 | it("on nested modals, only the topmost should handle ESC key.", () => {
281 | const requestCloseCallback = sinon.spy();
282 | const innerRequestCloseCallback = sinon.spy();
283 | let innerModal = null;
284 | let innerModalRef = ref => {
285 | innerModal = ref;
286 | };
287 |
288 | withModal(
289 | {
290 | isOpen: true,
291 | onRequestClose: requestCloseCallback
292 | },
293 |
298 | Test
299 | ,
300 | () => {
301 | const content = mcontent(innerModal);
302 | escKeyDown(content);
303 | innerRequestCloseCallback.called.should.be.ok();
304 | requestCloseCallback.called.should.not.be.ok();
305 | }
306 | );
307 | });
308 | };
309 |
--------------------------------------------------------------------------------
/specs/Modal.helpers.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import "should";
3 | import "@webcomponents/custom-elements/src/native-shim";
4 | import tabbable from "../src/helpers/tabbable";
5 | import "sinon";
6 |
7 | export default () => {
8 | describe("tabbable", () => {
9 | describe("without tabbable descendents", () => {
10 | it("returns an empty array", () => {
11 | const elem = document.createElement("div");
12 | tabbable(elem).should.deepEqual([]);
13 | });
14 | });
15 |
16 | describe("with tabbable descendents", () => {
17 | let elem;
18 | beforeEach(() => {
19 | elem = document.createElement("div");
20 | document.body.appendChild(elem);
21 | });
22 |
23 | afterEach(() => {
24 | document.body.removeChild(elem);
25 | });
26 |
27 | it("includes descendent tabbable inputs", () => {
28 | const input = document.createElement("input");
29 | elem.appendChild(input);
30 | tabbable(elem).should.containEql(input);
31 | });
32 |
33 | it("includes tabbable non-input elements", () => {
34 | const div = document.createElement("div");
35 | div.tabIndex = 1;
36 | elem.appendChild(div);
37 | tabbable(elem).should.containEql(div);
38 | });
39 |
40 | it("includes links with an href", () => {
41 | const a = document.createElement("a");
42 | a.href = "foobar";
43 | a.innerHTML = "link";
44 | elem.appendChild(a);
45 | tabbable(elem).should.containEql(a);
46 | });
47 |
48 | it("excludes links without an href or a tabindex", () => {
49 | const a = document.createElement("a");
50 | elem.appendChild(a);
51 | tabbable(elem).should.not.containEql(a);
52 | });
53 |
54 | it("excludes descendent inputs if they are not tabbable", () => {
55 | const input = document.createElement("input");
56 | input.tabIndex = -1;
57 | elem.appendChild(input);
58 | tabbable(elem).should.not.containEql(input);
59 | });
60 |
61 | it("excludes descendent inputs if they are disabled", () => {
62 | const input = document.createElement("input");
63 | input.disabled = true;
64 | elem.appendChild(input);
65 | tabbable(elem).should.not.containEql(input);
66 | });
67 |
68 | it("excludes descendent inputs if they are not displayed", () => {
69 | const input = document.createElement("input");
70 | input.style.display = "none";
71 | elem.appendChild(input);
72 | tabbable(elem).should.not.containEql(input);
73 | });
74 |
75 | it("excludes descendent inputs with 0 width and height", () => {
76 | const input = document.createElement("input");
77 | input.style.width = "0";
78 | input.style.height = "0";
79 | input.style.border = "0";
80 | input.style.padding = "0";
81 | elem.appendChild(input);
82 | tabbable(elem).should.not.containEql(input);
83 | });
84 |
85 | it("excludes descendents with hidden parents", () => {
86 | const input = document.createElement("input");
87 | elem.style.display = "none";
88 | elem.appendChild(input);
89 | tabbable(elem).should.not.containEql(input);
90 | });
91 |
92 | it("excludes inputs with parents that have zero width and height", () => {
93 | const input = document.createElement("input");
94 | elem.style.width = "0";
95 | elem.style.height = "0";
96 | elem.style.overflow = "hidden";
97 | elem.appendChild(input);
98 | tabbable(elem).should.not.containEql(input);
99 | });
100 |
101 | it("includes inputs visible because of overflow == visible", () => {
102 | const input = document.createElement("input");
103 | input.style.width = "0";
104 | input.style.height = "0";
105 | input.style.overflow = "visible";
106 | elem.appendChild(input);
107 | tabbable(elem).should.containEql(input);
108 | });
109 |
110 | it("excludes elements with overflow == visible if there is no visible content", () => {
111 | const button = document.createElement("button");
112 | button.innerHTML = "You can't see me!";
113 | button.style.display = "none";
114 | button.style.overflow = "visible";
115 | elem.appendChild(button);
116 | tabbable(elem).should.not.containEql(button);
117 | });
118 |
119 | it("excludes elements that contain reserved node names", () => {
120 | const button = document.createElement("button");
121 | button.innerHTML = "I am a good button";
122 | elem.appendChild(button);
123 |
124 | const badButton = document.createElement("bad-button");
125 | badButton.innerHTML = "I am a bad button";
126 | elem.appendChild(badButton);
127 |
128 | tabbable(elem).should.deepEqual([button]);
129 | });
130 |
131 | it("includes elements that contain reserved node names with tabindex", () => {
132 | const trickButton = document.createElement("trick-button");
133 | trickButton.innerHTML = "I am a good button";
134 | trickButton.tabIndex = '0';
135 | elem.appendChild(trickButton);
136 |
137 | tabbable(elem).should.deepEqual([trickButton]);
138 | });
139 |
140 | describe("inside Web Components with shadow dom", () => {
141 | let wc;
142 | let input;
143 | class TestWebComponent extends HTMLElement {
144 | constructor() {
145 | super();
146 | }
147 |
148 | connectedCallback() {
149 | this.attachShadow({
150 | mode: "open"
151 | });
152 | this.style.display = "block";
153 | this.style.width = "100px";
154 | this.style.height = "25px";
155 | }
156 | }
157 |
158 | const registerTestComponent = () => {
159 | if (window.customElements.get("test-web-component")) {
160 | return;
161 | }
162 | window.customElements.define("test-web-component", TestWebComponent);
163 | };
164 |
165 | beforeEach(() => {
166 | registerTestComponent();
167 | wc = document.createElement("test-web-component");
168 |
169 | input = document.createElement("input");
170 | elem.appendChild(input);
171 |
172 | document.body.appendChild(wc);
173 | wc.shadowRoot.appendChild(elem);
174 | });
175 |
176 | afterEach(() => {
177 | // re-add elem to body for the next afterEach
178 | document.body.appendChild(elem);
179 |
180 | // remove Web Component
181 | document.body.removeChild(wc);
182 | });
183 |
184 | it("includes elements when inside a Shadow DOM", () => {
185 | tabbable(elem).should.containEql(input);
186 | });
187 |
188 | it("excludes elements when hidden inside a Shadow DOM", () => {
189 | wc.style.display = "none";
190 | tabbable(elem).should.not.containEql(input);
191 | });
192 | });
193 |
194 | describe("inside Web Components with no shadow dom", () => {
195 | let wc;
196 | let button;
197 | class ButtonWebComponent extends HTMLElement {
198 | constructor() {
199 | super();
200 | }
201 |
202 | connectedCallback() {
203 | this.innerHTML = 'Normal button ';
204 | this.style.display = "block";
205 | this.style.width = "100px";
206 | this.style.height = "25px";
207 | }
208 | }
209 |
210 | const registerButtonComponent = () => {
211 | if (window.customElements.get("button-web-component")) {
212 | return;
213 | }
214 | window.customElements.define("button-web-component", ButtonWebComponent);
215 | };
216 |
217 | beforeEach(() => {
218 | registerButtonComponent();
219 | wc = document.createElement("button-web-component");
220 |
221 | elem.appendChild(wc);
222 | });
223 |
224 | afterEach(() => {
225 | // remove Web Component
226 | elem.removeChild(wc);
227 | });
228 |
229 | it("includes only focusable elements", () => {
230 | button = wc.querySelector('button');
231 |
232 | tabbable(elem).should.deepEqual([button]);
233 | });
234 | });
235 | });
236 | });
237 | };
238 |
--------------------------------------------------------------------------------
/specs/Modal.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import should from "should";
3 | import React, { Component } from "react";
4 | import ReactDOM from "react-dom";
5 | import Modal from "react-modal";
6 | import {
7 | setElement as ariaAppSetElement,
8 | resetState as ariaAppHiderResetState
9 | } from "react-modal/helpers/ariaAppHider";
10 | import { resetState as bodyTrapReset } from "react-modal/helpers/bodyTrap";
11 | import { resetState as classListReset } from "react-modal/helpers/classList";
12 | import { resetState as focusManagerReset } from "react-modal/helpers/focusManager";
13 | import { resetState as portalInstancesReset } from "react-modal/helpers/portalOpenInstances";
14 | import {
15 | log,
16 | isDocumentWithReactModalOpenClass,
17 | isHtmlWithReactModalOpenClass,
18 | htmlClassList,
19 | contentAttribute,
20 | mcontent,
21 | moverlay,
22 | escKeyDown,
23 | withModal,
24 | documentClassList,
25 | withElementCollector,
26 | createHTMLElement
27 | } from "./helper";
28 |
29 | Modal.setCreateHTMLElement(createHTMLElement);
30 |
31 | export default () => {
32 | beforeEach("check for leaks", () => log("before"));
33 | afterEach("clean up", () => (
34 | log("after", true),
35 | bodyTrapReset(),
36 | classListReset(),
37 | focusManagerReset(),
38 | portalInstancesReset(),
39 | ariaAppHiderResetState()
40 | ));
41 |
42 | it("can be open initially", () => {
43 | const props = { isOpen: true };
44 | withModal(props, "hello", modal => {
45 | mcontent(modal).should.be.ok();
46 | });
47 | });
48 |
49 | it("can be closed initially", () => {
50 | const props = {};
51 | withModal(props, "hello", modal => {
52 | should(ReactDOM.findDOMNode(mcontent(modal))).not.be.ok();
53 | });
54 | });
55 |
56 | it("doesn't render the portal if modal is closed", () => {
57 | const props = {};
58 | withModal(props, "hello", modal => {
59 | should(ReactDOM.findDOMNode(modal.portal)).not.be.ok();
60 | });
61 | });
62 |
63 | it("has default props", () => {
64 | withElementCollector(() => {
65 | // eslint-disable-next-line react/no-render-return-value
66 | const modal = ;
67 | const props = modal.props;
68 | props.isOpen.should.not.be.ok();
69 | props.ariaHideApp.should.be.ok();
70 | props.closeTimeoutMS.should.be.eql(0);
71 | props.shouldFocusAfterRender.should.be.ok();
72 | props.shouldCloseOnOverlayClick.should.be.ok();
73 | props.preventScroll.should.be.false();
74 | });
75 | });
76 |
77 | it("accepts appElement as a prop", () => {
78 | withElementCollector(() => {
79 | const el = createHTMLElement("div");
80 | const props = {
81 | isOpen: true,
82 | ariaHideApp: true,
83 | appElement: el
84 | };
85 | withModal(props, null, () => {
86 | el.getAttribute("aria-hidden").should.be.eql("true");
87 | });
88 | });
89 | });
90 |
91 | it("accepts array of appElement as a prop", () => {
92 | withElementCollector(() => {
93 | const el1 = createHTMLElement("div");
94 | const el2 = createHTMLElement("div");
95 | const node = createHTMLElement("div");
96 | ReactDOM.render( , node);
97 | el1.getAttribute("aria-hidden").should.be.eql("true");
98 | el2.getAttribute("aria-hidden").should.be.eql("true");
99 | ReactDOM.unmountComponentAtNode(node);
100 | });
101 | });
102 |
103 | it("renders into the body, not in context", () => {
104 | withElementCollector(() => {
105 | const node = createHTMLElement("div");
106 | Modal.setAppElement(node);
107 | ReactDOM.render( , node);
108 | document.body
109 | .querySelector(".ReactModalPortal")
110 | .parentNode.should.be.eql(document.body);
111 | ReactDOM.unmountComponentAtNode(node);
112 | });
113 | });
114 |
115 | it("allow setting appElement of type string", () => {
116 | withElementCollector(() => {
117 | const node = createHTMLElement("div");
118 | const appElement = "body";
119 | Modal.setAppElement(appElement);
120 | ReactDOM.render( , node);
121 | document.body
122 | .querySelector(".ReactModalPortal")
123 | .parentNode.should.be.eql(document.body);
124 | ReactDOM.unmountComponentAtNode(node);
125 | });
126 | });
127 |
128 | // eslint-disable-next-line max-len
129 | it("allow setting appElement of type string matching multiple elements", () => {
130 | withElementCollector(() => {
131 | const el1 = createHTMLElement("div");
132 | el1.id = "id1";
133 | document.body.appendChild(el1);
134 | const el2 = createHTMLElement("div");
135 | el2.id = "id2";
136 | document.body.appendChild(el2);
137 | const node = createHTMLElement("div");
138 | const appElement = "#id1, #id2";
139 | Modal.setAppElement(appElement);
140 | ReactDOM.render( , node);
141 | el1.getAttribute("aria-hidden").should.be.eql("true");
142 | ReactDOM.unmountComponentAtNode(node);
143 | });
144 | });
145 |
146 | it("default parentSelector should be document.body.", () => {
147 | const props = { isOpen: true };
148 | withModal(props, null, (modal) => {
149 | modal.props.parentSelector().should.be.eql(document.body);
150 | });
151 | });
152 |
153 | it("renders the modal content with a dialog aria role when provided ", () => {
154 | const child = "I am a child of Modal, and he has sent me here...";
155 | const props = { isOpen: true, role: "dialog" };
156 | withModal(props, child, (modal) => {
157 | contentAttribute(modal, "role").should.be.eql("dialog");
158 | });
159 | });
160 |
161 | // eslint-disable-next-line max-len
162 | it("renders the modal content with the default aria role when not provided", () => {
163 | const child = "I am a child of Modal, and he has sent me here...";
164 | const props = { isOpen: true };
165 | withModal(props, child, modal => {
166 | contentAttribute(modal, "role").should.be.eql("dialog");
167 | });
168 | });
169 |
170 | it("does not render the aria role when provided role with null", () => {
171 | const child = "I am a child of Modal, and he has sent me here...";
172 | const props = { isOpen: true, role: null };
173 | withModal(props, child, modal => {
174 | should(contentAttribute(modal, "role")).be.eql(null);
175 | });
176 | });
177 |
178 | it("sets aria-label based on the contentLabel prop", () => {
179 | const child = "I am a child of Modal, and he has sent me here...";
180 | withModal(
181 | {
182 | isOpen: true,
183 | contentLabel: "Special Modal"
184 | },
185 | child,
186 | modal => {
187 | contentAttribute(modal, "aria-label").should.be.eql("Special Modal");
188 | }
189 | );
190 | });
191 |
192 | it("removes the portal node", () => {
193 | const props = { isOpen: true };
194 | withModal(props, "hello");
195 | should(document.querySelector(".ReactModalPortal")).not.be.ok();
196 | });
197 |
198 | it("removes the portal node after closeTimeoutMS", done => {
199 | const closeTimeoutMS = 100;
200 |
201 | function checkDOM(count) {
202 | const portal = document.querySelectorAll(".ReactModalPortal");
203 | portal.length.should.be.eql(count);
204 | }
205 |
206 | const props = { isOpen: true, closeTimeoutMS };
207 | withModal(props, "hello", () => {
208 | checkDOM(1);
209 | });
210 |
211 | setTimeout(() => {
212 | // content is unmounted after specified timeout
213 | checkDOM(0);
214 | done();
215 | }, closeTimeoutMS);
216 | });
217 |
218 | it("focuses the modal content by default", () => {
219 | const props = { isOpen: true };
220 | withModal(props, null, modal => {
221 | document.activeElement.should.be.eql(mcontent(modal));
222 | });
223 | });
224 |
225 | it("does not focus modal content if shouldFocusAfterRender is false", () => {
226 | withModal(
227 | { isOpen: true, shouldFocusAfterRender: false },
228 | null,
229 | modal => {
230 | document.activeElement.should.not.be.eql(mcontent(modal));
231 | }
232 | );
233 | });
234 |
235 | it("give back focus to previous element or modal.", done => {
236 | withModal(
237 | {
238 | isOpen: true,
239 | className: "modal-a",
240 | onRequestClose: function() { done(); }
241 | },
242 | null,
243 | modalA => {
244 | const modalContent = mcontent(modalA);
245 | document.activeElement.should.be.eql(modalContent);
246 |
247 | const modalB = withModal(
248 | {
249 | isOpen: true,
250 | className: "modal-b",
251 | onRequestClose() {
252 | const modalContent = mcontent(modalB);
253 | document.activeElement.should.be.eql(mcontent(modalA));
254 | escKeyDown(modalContent);
255 | document.activeElement.should.be.eql(modalContent);
256 | }
257 | },
258 | null
259 | );
260 | escKeyDown(modalContent);
261 | }
262 | );
263 | });
264 |
265 | it("does not steel focus when a descendent is already focused", () => {
266 | let content;
267 | const input = (
268 | {
270 | el && el.focus();
271 | content = el;
272 | }}
273 | />
274 | );
275 | const props = { isOpen: true };
276 | withModal(props, input, () => {
277 | document.activeElement.should.be.eql(content);
278 | });
279 | });
280 |
281 | it("supports id prop", () => {
282 | const props = { isOpen: true, id: "id" };
283 | withModal(props, null, modal => {
284 | mcontent(modal)
285 | .id
286 | .should.be.eql("id");
287 | });
288 | });
289 |
290 | it("supports portalClassName", () => {
291 | const props = {
292 | isOpen: true,
293 | portalClassName: "myPortalClass"
294 | };
295 | withModal(props, null, modal => {
296 | modal.node.className.includes("myPortalClass").should.be.ok();
297 | });
298 | });
299 |
300 | it("supports custom className", () => {
301 | const props = { isOpen: true, className: "myClass" };
302 | withModal(props, null, modal => {
303 | mcontent(modal)
304 | .className.includes("myClass")
305 | .should.be.ok();
306 | });
307 | });
308 |
309 | it("supports custom overlayElement", () => {
310 | const overlayElement = (props, contentElement) => (
311 |
312 | {contentElement}
313 |
314 | );
315 |
316 | const props = { isOpen: true, overlayElement };
317 | withModal(props, null, modal => {
318 | const modalOverlay = moverlay(modal);
319 | modalOverlay.id.should.eql("custom");
320 | });
321 | });
322 |
323 | it("supports custom contentElement", () => {
324 | const contentElement = (props, children) => (
325 |
326 | {children}
327 |
328 | );
329 |
330 | const props = { isOpen: true, contentElement };
331 | withModal(props, "hello", modal => {
332 | const modalContent = mcontent(modal);
333 | modalContent.id.should.eql("custom");
334 | modalContent.textContent.should.be.eql("hello");
335 | });
336 | });
337 |
338 | it("supports overlayClassName", () => {
339 | const props = {
340 | isOpen: true,
341 | overlayClassName: "myOverlayClass"
342 | };
343 | withModal(props, null, modal => {
344 | moverlay(modal)
345 | .className.includes("myOverlayClass")
346 | .should.be.ok();
347 | });
348 | });
349 |
350 | it("overrides content classes with custom object className", () => {
351 | withElementCollector(() => {
352 | const props = {
353 | isOpen: true,
354 | className: {
355 | base: "myClass",
356 | afterOpen: "myClass_after-open",
357 | beforeClose: "myClass_before-close"
358 | }
359 | };
360 | const node = createHTMLElement("div");
361 | const modal = ReactDOM.render( , node);
362 | const request = requestAnimationFrame(() => {
363 | mcontent(modal).className.should.be.eql("myClass myClass_after-open");
364 | ReactDOM.unmountComponentAtNode(node);
365 | });
366 | cancelAnimationFrame(request);
367 | });
368 | });
369 |
370 | it("overrides overlay classes with custom object overlayClassName", () => {
371 | withElementCollector(() => {
372 | const props = {
373 | isOpen: true,
374 | overlayClassName: {
375 | base: "myOverlayClass",
376 | afterOpen: "myOverlayClass_after-open",
377 | beforeClose: "myOverlayClass_before-close"
378 | }
379 | };
380 | const node = createHTMLElement("div");
381 | const modal = ReactDOM.render( , node);
382 | const request = requestAnimationFrame(() => {
383 | moverlay(modal).className.should.be.eql(
384 | "myOverlayClass myOverlayClass_after-open"
385 | );
386 | ReactDOM.unmountComponentAtNode(node);
387 | });
388 | cancelAnimationFrame(request);
389 | });
390 | });
391 |
392 | it("supports overriding react modal open class in document.body.", () => {
393 | const props = { isOpen: true, bodyOpenClassName: "custom-modal-open" };
394 | withModal(props, null, () => {
395 | (document.body.className.indexOf("custom-modal-open") > -1).should.be.ok();
396 | });
397 | });
398 |
399 | it("supports setting react modal open class in .", () => {
400 | const props = { isOpen: true, htmlOpenClassName: "custom-modal-open" };
401 | withModal(props, null, () => {
402 | isHtmlWithReactModalOpenClass("custom-modal-open").should.be.ok();
403 | });
404 | });
405 |
406 | // eslint-disable-next-line max-len
407 | it("don't append class to document.body if modal is closed.", () => {
408 | const props = { isOpen: false };
409 | withModal(props, null, () => {
410 | isDocumentWithReactModalOpenClass().should.not.be.ok();
411 | });
412 | });
413 |
414 | // eslint-disable-next-line max-len
415 | it("don't append any class to document.body when bodyOpenClassName is null.", () => {
416 | const props = { isOpen: true, bodyOpenClassName: null };
417 | withModal(props, null, () => {
418 | documentClassList().should.be.empty();
419 | });
420 | });
421 |
422 | it("don't append class to if modal is closed.", () => {
423 | const props = { isOpen: false, htmlOpenClassName: "custom-modal-open" };
424 | withModal(props, null, () => {
425 | isHtmlWithReactModalOpenClass().should.not.be.ok();
426 | });
427 | });
428 |
429 | it("append class to document.body if modal is open.", () => {
430 | const props = { isOpen: true };
431 | withModal(props, null, () => {
432 | isDocumentWithReactModalOpenClass().should.be.ok();
433 | });
434 | });
435 |
436 | it("don't append class to if not defined.", () => {
437 | const props = { isOpen: true };
438 | withModal(props, null, () => {
439 | htmlClassList().should.be.empty();
440 | });
441 | });
442 |
443 | // eslint-disable-next-line max-len
444 | it("removes class from document.body when unmounted without closing", () => {
445 | withModal({ isOpen: true });
446 | isDocumentWithReactModalOpenClass().should.not.be.ok();
447 | });
448 |
449 | it("remove class from document.body when no modals opened", () => {
450 | const propsA = { isOpen: true };
451 | withModal(propsA, null, () => {
452 | isDocumentWithReactModalOpenClass().should.be.ok();
453 | });
454 | const propsB = { isOpen: true };
455 | withModal(propsB, null, () => {
456 | isDocumentWithReactModalOpenClass().should.be.ok();
457 | });
458 | isDocumentWithReactModalOpenClass().should.not.be.ok();
459 | isHtmlWithReactModalOpenClass().should.not.be.ok();
460 | });
461 |
462 | it("supports adding/removing multiple document.body classes", () => {
463 | const props = {
464 | isOpen: true,
465 | bodyOpenClassName: "A B C"
466 | };
467 | withModal(props, null, () => {
468 | document.body.classList.contains("A", "B", "C").should.be.ok();
469 | });
470 | document.body.classList.contains("A", "B", "C").should.not.be.ok();
471 | ;
472 | });
473 |
474 | it("does not remove shared classes if more than one modal is open", () => {
475 | const props = {
476 | isOpen: true,
477 | bodyOpenClassName: "A"
478 | };
479 | withModal(props, null, () => {
480 | isDocumentWithReactModalOpenClass("A").should.be.ok();
481 | withModal({
482 | isOpen: true,
483 | bodyOpenClassName: "A B"
484 | }, null, () => {
485 | isDocumentWithReactModalOpenClass("A B").should.be.ok();
486 | });
487 | isDocumentWithReactModalOpenClass("A").should.be.ok();
488 | });
489 | isDocumentWithReactModalOpenClass("A").should.not.be.ok();
490 | });
491 |
492 | it("should not add classes to document.body for unopened modals", () => {
493 | const props = { isOpen: true };
494 | withModal(props, null, () => {
495 | isDocumentWithReactModalOpenClass().should.be.ok();
496 | });
497 | withModal({ isOpen: false, bodyOpenClassName: "testBodyClass" });
498 | isDocumentWithReactModalOpenClass("testBodyClass").should.not.be.ok();
499 | });
500 |
501 | it("should not remove classes from document.body if modal is closed", () => {
502 | const props = { isOpen: true };
503 | withModal(props, null, () => {
504 | isDocumentWithReactModalOpenClass().should.be.ok();
505 | withModal({ isOpen: false, bodyOpenClassName: "testBodyClass" }, null, () => {
506 | isDocumentWithReactModalOpenClass("testBodyClass").should.not.be.ok();
507 | });
508 | isDocumentWithReactModalOpenClass().should.be.ok();
509 | });
510 | });
511 |
512 | it("should not remove classes from
if modal is closed", () => {
513 | const props = { isOpen: false };
514 | withModal(props, null, () => {
515 | isHtmlWithReactModalOpenClass().should.not.be.ok();
516 | withModal({
517 | isOpen: true,
518 | htmlOpenClassName: "testHtmlClass"
519 | }, null, () => {
520 | isHtmlWithReactModalOpenClass("testHtmlClass").should.be.ok();
521 | });
522 | isHtmlWithReactModalOpenClass("testHtmlClass").should.not.be.ok();
523 | });
524 | });
525 |
526 | it("additional aria attributes", () => {
527 | withModal(
528 | { isOpen: true, aria: { labelledby: "a" } },
529 | "hello",
530 | modal => mcontent(modal)
531 | .getAttribute("aria-labelledby")
532 | .should.be.eql("a")
533 | );
534 | });
535 |
536 | it("additional data attributes", () => {
537 | withModal(
538 | { isOpen: true, data: { background: "green" } },
539 | "hello",
540 | modal => mcontent(modal)
541 | .getAttribute("data-background")
542 | .should.be.eql("green")
543 | );
544 | });
545 |
546 | it("additional testId attribute", () => {
547 | withModal(
548 | { isOpen: true, testId: "foo-bar" },
549 | "hello",
550 | modal => mcontent(modal)
551 | .getAttribute("data-testid")
552 | .should.be.eql("foo-bar")
553 | )
554 | });
555 |
556 | it("raises an exception if the appElement selector does not match", () => {
557 | should(() => ariaAppSetElement(".test")).throw();
558 | });
559 |
560 | it("removes aria-hidden from appElement when unmounted w/o closing", () => {
561 | withElementCollector(() => {
562 | const el = createHTMLElement("div");
563 | const node = createHTMLElement("div");
564 | ReactDOM.render( , node);
565 | el.getAttribute("aria-hidden").should.be.eql("true");
566 | ReactDOM.unmountComponentAtNode(node);
567 | const request = requestAnimationFrame(() => {
568 | should(el.getAttribute('aria-hidden')).not.be.ok();
569 | });
570 | cancelAnimationFrame(request);
571 | });
572 | });
573 |
574 | // eslint-disable-next-line max-len
575 | it("removes aria-hidden when closed and another modal with ariaHideApp set to false is open", () => {
576 | withElementCollector(() => {
577 | const rootNode = createHTMLElement("div");
578 | const appElement = createHTMLElement("div");
579 | document.body.appendChild(rootNode);
580 | document.body.appendChild(appElement);
581 |
582 | Modal.setAppElement(appElement);
583 |
584 | const initialState = (
585 |
586 |
587 |
588 |
589 | );
590 |
591 | ReactDOM.render(initialState, rootNode);
592 | appElement.getAttribute("aria-hidden").should.be.eql("true");
593 |
594 | const updatedState = (
595 |
596 |
597 |
598 |
599 | );
600 |
601 | const request = requestAnimationFrame(() => {
602 | ReactDOM.render(updatedState, rootNode);
603 | should(appElement.getAttribute("aria-hidden")).not.be.ok();
604 |
605 | ReactDOM.unmountComponentAtNode(rootNode);
606 | });
607 | cancelAnimationFrame(request);
608 | });
609 | });
610 |
611 | // eslint-disable-next-line max-len
612 | it("maintains aria-hidden when closed and another modal with ariaHideApp set to true is open", () => {
613 | withElementCollector(() => {
614 | const rootNode = createHTMLElement("div");
615 | document.body.appendChild(rootNode);
616 |
617 | const appElement = createHTMLElement("div");
618 | document.body.appendChild(appElement);
619 |
620 | Modal.setAppElement(appElement);
621 |
622 | const initialState = (
623 |
624 |
625 |
626 |
627 | );
628 |
629 | ReactDOM.render(initialState, rootNode);
630 | appElement.getAttribute("aria-hidden").should.be.eql("true");
631 |
632 | const updatedState = (
633 |
634 |
635 |
636 |
637 | );
638 |
639 | ReactDOM.render(updatedState, rootNode);
640 | appElement.getAttribute("aria-hidden").should.be.eql("true");
641 |
642 | ReactDOM.unmountComponentAtNode(rootNode);
643 | });
644 | });
645 |
646 | // eslint-disable-next-line max-len
647 | it("removes aria-hidden when unmounted without close and second modal with ariaHideApp=false is open", () => {
648 | withElementCollector(() => {
649 | const appElement = createHTMLElement("div");
650 | document.body.appendChild(appElement);
651 | Modal.setAppElement(appElement);
652 |
653 | const propsA = { isOpen: true, ariaHideApp: false, id: "test-2-modal-1" };
654 | withModal(propsA, null, () => {
655 | should(appElement.getAttribute("aria-hidden")).not.be.ok();
656 | });
657 |
658 | const propsB = { isOpen: true, ariaHideApp: true, id: "test-2-modal-2" };
659 | withModal(propsB, null, () => {
660 | appElement.getAttribute("aria-hidden").should.be.eql("true");
661 | });
662 |
663 | const request = requestAnimationFrame(() => {
664 | should(appElement.getAttribute("aria-hidden")).not.be.ok();
665 | });
666 | cancelAnimationFrame(request);
667 | });
668 | });
669 |
670 | // eslint-disable-next-line max-len
671 | it("maintains aria-hidden when unmounted without close and second modal with ariaHideApp=true is open", () => {
672 | withElementCollector(() => {
673 | const appElement = createHTMLElement("div");
674 | document.body.appendChild(appElement);
675 | Modal.setAppElement(appElement);
676 |
677 | const check = (tobe) => appElement.getAttribute("aria-hidden").should.be.eql(tobe);
678 |
679 | const props = { isOpen: true, ariaHideApp: true, id: "test-3-modal-1" };
680 | withModal(props, null, () => {
681 | check("true");
682 | withModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-2" }, null, () => {
683 | check("true");
684 | });
685 | check("true");
686 | });
687 |
688 | const request = requestAnimationFrame(() => {
689 | should(appElement.getAttribute("aria-hidden")).not.be.ok();
690 | });
691 | cancelAnimationFrame(request);
692 | });
693 | });
694 |
695 | it("adds --after-open for animations", () => {
696 | withElementCollector(() => {
697 | const rg = /--after-open/i;
698 | const props = { isOpen: true };
699 | const node = createHTMLElement("div");
700 | const modal = ReactDOM.render( , node);
701 | const request = requestAnimationFrame(() => {
702 | const contentName = modal.portal.content.className;
703 | const overlayName = modal.portal.overlay.className;
704 | rg.test(contentName).should.be.ok();
705 | rg.test(overlayName).should.be.ok();
706 | ReactDOM.unmountComponentAtNode(node);
707 | });
708 | cancelAnimationFrame(request);
709 | });
710 | });
711 |
712 | it("adds --before-close for animations", () => {
713 | const closeTimeoutMS = 50;
714 | const props = {
715 | isOpen: true,
716 | closeTimeoutMS
717 | };
718 | withModal(props, null, modal => {
719 | modal.portal.closeWithTimeout();
720 |
721 | const rg = /--before-close/i;
722 | rg.test(moverlay(modal).className).should.be.ok();
723 | rg.test(mcontent(modal).className).should.be.ok();
724 |
725 | modal.portal.closeWithoutTimeout();
726 | });
727 | });
728 |
729 | it("should not be open after close with time out and reopen it", () => {
730 | const props = {
731 | isOpen: true,
732 | closeTimeoutMS: 2000,
733 | onRequestClose() { }
734 | };
735 | withModal(props, null, modal => {
736 | modal.portal.closeWithTimeout();
737 | modal.portal.open();
738 | modal.portal.closeWithoutTimeout();
739 | modal.portal.state.isOpen.should.not.be.ok();
740 | });
741 | });
742 |
743 | it("verify default prop of shouldCloseOnOverlayClick", () => {
744 | const props = { isOpen: true };
745 | withModal(props, null, modal => {
746 | modal.props.shouldCloseOnOverlayClick.should.be.ok();
747 | });
748 | });
749 |
750 | it("verify prop of shouldCloseOnOverlayClick", () => {
751 | const modalOpts = { isOpen: true, shouldCloseOnOverlayClick: false };
752 | withModal(modalOpts, null, modal => {
753 | modal.props.shouldCloseOnOverlayClick.should.not.be.ok();
754 | });
755 | });
756 |
757 | it("keeps the modal in the DOM until closeTimeoutMS elapses", done => {
758 | function checkDOM(count) {
759 | const overlay = document.querySelectorAll(".ReactModal__Overlay");
760 | const content = document.querySelectorAll(".ReactModal__Content");
761 | overlay.length.should.be.eql(count);
762 | content.length.should.be.eql(count);
763 | }
764 | withElementCollector(() => {
765 | const closeTimeoutMS = 100;
766 | const props = { isOpen: true, closeTimeoutMS };
767 | const node = createHTMLElement("div");
768 | const modal = ReactDOM.render( , node);
769 |
770 | modal.portal.closeWithTimeout();
771 | checkDOM(1);
772 |
773 | setTimeout(() => {
774 | checkDOM(0);
775 | ReactDOM.unmountComponentAtNode(node);
776 | done();
777 | }, closeTimeoutMS);
778 | });
779 | });
780 |
781 | it("verify that portalClassName is refreshed on component update", () => {
782 | withElementCollector(() => {
783 | const node = createHTMLElement("div");
784 | let modal = null;
785 |
786 | class App extends Component {
787 | constructor(props) {
788 | super(props);
789 | this.state = { classModifier: "" };
790 | }
791 |
792 | componentDidMount() {
793 | modal.node.className.should.be.eql("portal");
794 |
795 | this.setState({ classModifier: "-modifier" });
796 | }
797 |
798 | componentDidUpdate() {
799 | modal.node.className.should.be.eql("portal-modifier");
800 | }
801 |
802 | render() {
803 | const { classModifier } = this.state;
804 | const portalClassName = `portal${classModifier}`;
805 |
806 | return (
807 |
808 | {
810 | modal = modalComponent;
811 | }}
812 | isOpen
813 | portalClassName={portalClassName}
814 | >
815 | Test
816 |
817 |
818 | );
819 | }
820 | }
821 |
822 | Modal.setAppElement(node);
823 | ReactDOM.render( , node);
824 | ReactDOM.unmountComponentAtNode(node);
825 | });
826 | });
827 |
828 | it("use overlayRef and contentRef", () => {
829 | let overlay = null;
830 | let content = null;
831 |
832 | const props = {
833 | isOpen: true,
834 | overlayRef: node => (overlay = node),
835 | contentRef: node => (content = node)
836 | };
837 | withModal(props, null, () => {
838 | overlay.should.be.instanceOf(HTMLElement);
839 | content.should.be.instanceOf(HTMLElement);
840 | overlay.classList.contains("ReactModal__Overlay");
841 | content.classList.contains("ReactModal__Content");
842 | });
843 | });
844 | };
845 |
--------------------------------------------------------------------------------
/specs/Modal.style.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import "should";
3 | import Modal from "react-modal";
4 | import { mcontent, moverlay, withModal } from "./helper";
5 |
6 | export default () => {
7 | it("overrides the default styles when a custom classname is used", () => {
8 | const props = { isOpen: true, className: "myClass" };
9 | withModal(props, null, modal => {
10 | mcontent(modal).style.top.should.be.eql("");
11 | });
12 | });
13 |
14 | it("overrides the default styles when using custom overlayClassName", () => {
15 | const overlayClassName = "myOverlayClass";
16 | const props = { isOpen: true, overlayClassName };
17 | withModal(props, null, modal => {
18 | moverlay(modal).style.backgroundColor.should.be.eql("");
19 | });
20 | });
21 |
22 | it("supports adding style to the modal contents", () => {
23 | const style = { content: { width: "20px" } };
24 | const props = { isOpen: true, style };
25 | withModal(props, null, modal => {
26 | mcontent(modal).style.width.should.be.eql("20px");
27 | });
28 | });
29 |
30 | it("supports overriding style on the modal contents", () => {
31 | const style = { content: { position: "static" } };
32 | const props = { isOpen: true, style };
33 | withModal(props, null, modal => {
34 | mcontent(modal).style.position.should.be.eql("static");
35 | });
36 | });
37 |
38 | it("supports adding style on the modal overlay", () => {
39 | const style = { overlay: { width: "75px" } };
40 | const props = { isOpen: true, style };
41 | withModal(props, null, modal => {
42 | moverlay(modal).style.width.should.be.eql("75px");
43 | });
44 | });
45 |
46 | it("supports overriding style on the modal overlay", () => {
47 | const style = { overlay: { position: "static" } };
48 | const props = { isOpen: true, style };
49 | withModal(props, null, modal => {
50 | moverlay(modal).style.position.should.be.eql("static");
51 | });
52 | });
53 |
54 | it("supports overriding the default styles", () => {
55 | const previousStyle = Modal.defaultStyles.content.position;
56 | // Just in case the default style is already relative,
57 | // check that we can change it
58 | const newStyle = previousStyle === "relative" ? "static" : "relative";
59 | Modal.defaultStyles.content.position = newStyle;
60 | const props = { isOpen: true };
61 | withModal(props, null, modal => {
62 | modal.portal.content.style.position.should.be.eql(newStyle);
63 | Modal.defaultStyles.content.position = previousStyle;
64 | });
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/specs/Modal.testability.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 | import ReactDOM from "react-dom";
3 | import sinon from "sinon";
4 | import { withModal } from "./helper";
5 |
6 | export default () => {
7 | it("allows ReactDOM.createPortal to be overridden in real-time", () => {
8 | const createPortalSpy = sinon.spy(ReactDOM, "createPortal");
9 | const props = { isOpen: true };
10 | withModal(props, "hello");
11 | createPortalSpy.called.should.be.ok();
12 | ReactDOM.createPortal.restore();
13 | });
14 | };
15 |
--------------------------------------------------------------------------------
/specs/helper.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Modal, { bodyOpenClassName } from "../src/components/Modal";
4 | import TestUtils from "react-dom/test-utils";
5 | import { log as classListLog } from "../src/helpers/classList";
6 | import { log as focusManagerLog } from "../src/helpers/focusManager";
7 | import { log as ariaAppLog } from "../src/helpers/ariaAppHider";
8 | import { log as bodyTrapLog } from "../src/helpers/bodyTrap";
9 | import { log as portalInstancesLog } from "../src/helpers/portalOpenInstances";
10 |
11 | const debug = false;
12 |
13 | let i = 0;
14 |
15 | /**
16 | * This log is used to see if there are leaks in between tests.
17 | */
18 | export function log(label, spaces) {
19 | if (!debug) return;
20 |
21 | console.log(`${label} -----------------`);
22 | console.log(document.body.children.length);
23 | const logChildren = c => console.log(c.nodeName, c.className, c.id);
24 | document.body.children.forEach(logChildren);
25 |
26 | ariaAppLog();
27 | bodyTrapLog();
28 | classListLog();
29 | focusManagerLog();
30 | portalInstancesLog();
31 |
32 | console.log(`end ${label} -----------------` + (!spaces ? '' : `
33 |
34 |
35 | `));
36 | }
37 |
38 | let elementPool = [];
39 |
40 | /**
41 | * Every HTMLElement must be requested using this function...
42 | * and inside `withElementCollector`.
43 | */
44 | export function createHTMLElement(name) {
45 | const e = document.createElement(name);
46 | elementPool[elementPool.length - 1].push(e);
47 | e.className = `element_pool_${name}-${++i}`;
48 | return e;
49 | }
50 |
51 | /**
52 | * Remove every element from its parent and release the pool.
53 | */
54 | export function drainPool(pool) {
55 | pool.forEach(e => e.parentNode && e.parentNode.removeChild(e));
56 | }
57 |
58 | /**
59 | * Every HTMLElement must be requested inside this function...
60 | * The reason is that it provides a mechanism that disposes
61 | * all the elements (built with `createHTMLElement`) after a test.
62 | */
63 | export function withElementCollector(work) {
64 | let r;
65 | let poolIndex = elementPool.length;
66 | elementPool[poolIndex] = [];
67 | try {
68 | r = work();
69 | } finally {
70 | drainPool(elementPool[poolIndex]);
71 | elementPool = elementPool.slice(
72 | 0, poolIndex
73 | );
74 | }
75 | return r;
76 | }
77 |
78 | /**
79 | * Polyfill for String.includes on some node versions.
80 | */
81 | if (!String.prototype.includes) {
82 | String.prototype.includes = function(search, start) {
83 | if (typeof start !== "number") {
84 | start = 0;
85 | }
86 |
87 | if (start + search.length > this.length) {
88 | return false;
89 | }
90 |
91 | return this.indexOf(search, start) !== -1;
92 | };
93 | }
94 |
95 | /**
96 | * Return the class list object from `document.body`.
97 | * @return {Array}
98 | */
99 | export const documentClassList = () => document.body.classList;
100 |
101 | /**
102 | * Check if the document.body contains the react modal
103 | * open class.
104 | * @return {Boolean}
105 | */
106 | export const isDocumentWithReactModalOpenClass = (
107 | bodyClass = bodyOpenClassName
108 | ) => document.body.className.includes(bodyClass);
109 |
110 | /**
111 | * Return the class list object from