Repository: reactjs/react-modal Branch: master Commit: e66ab51b65e6 Files: 79 Total size: 254.8 KB Directory structure: gitextract_tpl2gots/ ├── .babelrc ├── .eslintrc.js ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── test.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── UPGRADE_GUIDE.md ├── bower.json ├── dist/ │ └── react-modal.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 ├── flake.nix ├── karma.conf.js ├── mkdocs.yml ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "env", "react", "stage-2" ], "plugins": [ "add-module-exports" ] } ================================================ FILE: .eslintrc.js ================================================ module.exports = { "env": { "es6": true, "browser": true }, "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 7, "ecmaFeatures": { "jsx": true }, "sourceType": "module" }, "settings": { "react": { "createClass": "createReactClass", "pragma": "React", "version": "15.0" }, "propWrapperFunctions": [ "forbidExtraProps" ], "import/resolver": "webpack" }, "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:import/recommended", "prettier"], "plugins": ["prettier"], "globals": { "process": true }, "rules": { "quotes": [0], "comma-dangle": [2, "only-multiline"], "max-len": [1, {"code": 80}], "no-unused-expressions": [0], "no-continue": [0], "no-plusplus": [0], "func-names": [0], "arrow-parens": [0], "space-before-function-paren": [0], "jsx-a11y/no-static-element-interactions": [0], "prettier/prettier": "error", "react/no-find-dom-node": [0], "react/jsx-closing-bracket-location": [0], "react/require-default-props": 0, "import/no-extraneous-dependencies": [2, { "devDependencies": ["specs/**"] }] } } ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Summary: ### Steps to reproduce: 1. 2. 3. ### Expected behavior: ### Link to example of issue: ### Additional notes: ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Acceptance Checklist: - [ ] Tests - [ ] Documentation and examples (if needed) Fixes #[issue number]. ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: push: branches: - master pull_request: branches: - master - v4 - chore/github-actions jobs: main: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 1 - uses: actions/setup-node@v6 with: node-version: 22 cache: 'npm' cache-dependency-path: '**/package-lock.json' - run: make deps-project tests-ci ================================================ FILE: .gitignore ================================================ .version .branch .changelog_update scripts/__pycache__/ examples/**/*-bundle.js node_modules/ .idea/ .vscode _book *.patch *.diff *.orig *.rej .log examples/__build__ coverage yarn.lock ## Built folders lib ================================================ FILE: .npmignore ================================================ CONTRIBUTING.md .babelrc .travis.yml .babelrc .eslintrc.js *.orig *.rej .log .changelog_update Makefile book.json bootstrap.sh bower.json karma.conf.js yarn.lock webpack.* .idea/* .github/* coverage docs src scripts specs _book examples mkdocs.yml ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "8" cache: yarn services: - xvfb before_script: - export DISPLAY=:99.0 script: - make tests-ci ================================================ FILE: CHANGELOG.md ================================================ 3.16.3 - Tue, 17 Dec 2024 10:38:34 UTC -------------------------------------- - [a5c0cf4](../../commit/a5c0cf4) removing restriction on node engines. 3.16.2 - Tue, 17 Dec 2024 09:11:34 UTC -------------------------------------- - [b91c724](../../commit/b91c724) updade react and react-dom peer dependencies. - [a275399](../../commit/a275399) simplify PR template. - [588f26b](../../commit/588f26b) contributing requirements now just need a corresponding issue... on GitHub board - [449398d](../../commit/449398d) remove discussion note from readme. - [e4841d6](../../commit/e4841d6) chore: update shouldCloseOnOverlayClick doc - [6724a04](../../commit/6724a04) Fix tests - [7c1d947](../../commit/7c1d947) Fix badge - [96a81be](../../commit/96a81be) Comment the ellipsis in code blocks in docs/index.md - [aff8b91](../../commit/aff8b91) [added] add nodejs version restriction to package.json - [321966e](../../commit/321966e) [changed] change Miscellaneous related nodejs version text - [8dc2347](../../commit/8dc2347) [added] add Miscellaneous section to the contributions.md file - [f9bc6a0](../../commit/f9bc6a0) [fixed] strict matching for tabbable nodes - [e7c4a63](../../commit/e7c4a63) downgrade node version on github action. - [1a8f562](../../commit/1a8f562) running tests on github actions 3.16.1 - Mon, 17 Oct 2022 22:07:19 UTC -------------------------------------- - [840a0f6](../../commit/840a0f6) Add tests - [742e9f5](../../commit/742e9f5) [fixed] Revert changes to test helpers - [28ea6b5](../../commit/28ea6b5) [fixed] Fix missing returns in keydown helpers - [15e0711](../../commit/15e0711) [fixed] address review comments - [f3a19fc](../../commit/f3a19fc) [fixed] switched from KeyboardEvent.keyCode to KeyboardEvent.code - [aca5261](../../commit/aca5261) fixing element with display 'contents' is visible and is tabbable - [df0665e](../../commit/df0665e) update broken build badge - [9bf0d4f](../../commit/9bf0d4f) [fixed]: css class added to root document in iframe 3.15.1 - Mon, 11 Apr 2022 22:26:43 UTC -------------------------------------- - [8395a21](../../commit/8395a21) allow react 18 as peer dependency - [68af7ec](../../commit/68af7ec) [added] tabbable support for iframes - [28986ea](../../commit/28986ea) Fix typo in docs 3.14.4 - Wed, 10 Nov 2021 17:53:33 UTC -------------------------------------- - [12ccd6a](../../commit/12ccd6a) [fixed] using concat for finding shadowRoot descendants instead of spread - [f5783ca](../../commit/f5783ca) [fixed] ensuring usage of Web Components functions in all browsers (Safari, specifically) - [b049feb](../../commit/b049feb) Update src/helpers/tabbable.js - [edc4f12](../../commit/edc4f12) [fixed] Ensure we don't check for hidden on Shadow DOM. - [ffbaf0e](../../commit/ffbaf0e) Wrap NODE_ENV conditional code in block - [5eccdb0](../../commit/5eccdb0) Wrap NODE_ENV conditional code in block - [f5d404c](../../commit/f5d404c) Wrap NODE_ENV conditional code in block - [22e80e7](../../commit/22e80e7) [chore] update karma dependency. - [e3bfbf8](../../commit/e3bfbf8) Bump ws from 6.2.1 to 6.2.2 - [fa914c5](../../commit/fa914c5) Bump path-parse from 1.0.6 to 1.0.7 - [9e867d5](../../commit/9e867d5) Bump url-parse from 1.5.1 to 1.5.3 - [fb6bab5](../../commit/fb6bab5) Update readme documentation with CDN installation - [d5ecf0a](../../commit/d5ecf0a) Update documentation by my suggestion in #889 3.14.3 - Tue, 15 Jun 2021 15:08:58 UTC -------------------------------------- - [b33923a](../../commit/b33923a) [changed]: Updated and formatted example in README - [0847049](../../commit/0847049) [fixed] Cancel requested animation frame on unmount. - [fc76b0c](../../commit/fc76b0c) [chore] added link to the discussion for react-modal v4. - [0d99156](../../commit/0d99156) [chore] Don't allow mkdocs.yml be included on releases. 3.14.2 - Tue, 01 Jun 2021 21:42:17 UTC -------------------------------------- - [172879e](../../commit/172879e) [chore] Don't allow .log in on releases. 3.14.1 - Tue, 01 Jun 2021 21:39:02 UTC -------------------------------------- - [fc62ab1](../../commit/fc62ab1) Fixing lint error and PR suggestion change to use double quotes - [ce94d86](../../commit/ce94d86) Working on lint error - [7e732d7](../../commit/7e732d7) Wrapping getComputedStyle in try catch per PR review - [31d59b2](../../commit/31d59b2) Adding a check to see if the element is a prototype of Element before getting the computed style - [827796d](../../commit/827796d) [fixed] Ensure after-open css transitions work in Safari 14 & Mobile Safari - [76df16b](../../commit/76df16b) [chore] regenerate package-lock. - [4fbe228](../../commit/4fbe228) bump prop-types to 15.7.2 - [a5f959a](../../commit/a5f959a) [chore] update packages. - [8050773](../../commit/8050773) [chore] clean up all element leaks between tests. 3.13.1 - Tue, 13 Apr 2021 14:19:44 UTC -------------------------------------- - [5832904](../../commit/5832904) Updated README.md - [d7083c5](../../commit/d7083c5) [added] docs note about setAppElement not pruning removed nodes - [e1807ce](../../commit/e1807ce) [added] support Array, HTMLCollection and NodeList values for appElement - [c9d8e2d](../../commit/c9d8e2d) Bump ini from 1.3.5 to 1.3.8 - [8d4ef84](../../commit/8d4ef84) fixed(documentation): jsx-lexer now requires to generate a css file... - [ab8c44c](../../commit/ab8c44c) fixed(documentation): link to app-element on index. 3.12.1 - Sun, 22 Nov 2020 22:51:08 UTC -------------------------------------- - [029a525](../../commit/029a525) Added react 17 support - [694d425](../../commit/694d425) fix #833 by changing stale link from `README.md` - [9ca3626](../../commit/9ca3626) [fixed] add aria-modal attribute - [b2e58e7](../../commit/b2e58e7) [fixed] extra 'p' character in index.md - [94ad567](../../commit/94ad567) [fixed] don't access ReactDOM.createPortal if DOM not available - [421a1c8](../../commit/421a1c8) chore(lint): run lint. - [c797e9a](../../commit/c797e9a) [added] Added custom overlayElement and contentElement. - [fa98fcc](../../commit/fa98fcc) [fixed] check before react-modal removal from parent element that parent has it - [ff0a7f5](../../commit/ff0a7f5) [added] a preventScroll prop - [2ea6d44](../../commit/2ea6d44) Add '--save' - [eea891c](../../commit/eea891c) Update package.json - [6417a6a](../../commit/6417a6a) fixed(chore): update packages. - [bd07d56](../../commit/bd07d56) Adds testId prop and useage to documentation - [4a120a9](../../commit/4a120a9) fix linting - [206cfe6](../../commit/206cfe6) ensure focus does not scroll the modal - [9a4dde5](../../commit/9a4dde5) [fixed] - Fix broken links of codepen in examples in docs 3.11.2 - Tue, 25 Feb 2020 21:49:35 UTC -------------------------------------- - [8cd47cd](../../commit/8cd47cd) fixed: update package.json version. - [bb76272](../../commit/bb76272) fixed: rules' execution order. - [83c8c23](../../commit/83c8c23) fixed: when building, use test single run rule. - [c6c4d8c](../../commit/c6c4d8c) fixed: removed build and version rules since they were simplified. - [83c5e29](../../commit/83c5e29) fixed: makefile rule to check the working tree. - [5fdcfdd](../../commit/5fdcfdd) [fixed] set correct URL for codepen setAppElement example - [8efaa37](../../commit/8efaa37) [chore] forgot to remote book.json. - [f7e96f3](../../commit/f7e96f3) [fixed] sync package-lock.json. - [4abbc0a](../../commit/4abbc0a) [fixed] passing lint. - [dc57795](../../commit/dc57795) [chore] fixed rule to check if repository is dirty. - [b9cbd40](../../commit/b9cbd40) [chore] rewrite CHANGELOG. - [e5fe406](../../commit/e5fe406) [chore] moving documentation to mkdocs. - [525c35c](../../commit/525c35c) [chore] moving webpack scripts to scripts folder. - [aa822be](../../commit/aa822be) [chore] it's not necessary to make chunks when running the examples. - [eb20444](../../commit/eb20444) [fixed] Focus trap when reentering document (#742) (#791) - [98dd5be](../../commit/98dd5be) [Chore] Update README (example with React Hooks). - [cec8833](../../commit/cec8833) fixed: using variable before declaration... - [5dffbf2](../../commit/5dffbf2) [fixed] Call parent.removeChild only if parent exists (#778) 3.11.1 - Fri, 25 Oct 2019 21:02:39 UTC -------------------------------------- - [ee1a363](../../commit/ee1a363) chore: update webpack* versions. - [4ac3ff4](../../commit/4ac3ff4) [added]: pass overlay and content element references to onAfterOpen fn (#741) - [9be00a9](../../commit/9be00a9) [fixed] some untabbable elements are being returned from findTabbable (#774) - [4dd25ac](../../commit/4dd25ac) chore: changed xvfb to a service. - [2d4f231](../../commit/2d4f231) chore: changed xvfb to a service. 3.10.1 - Wed, 21 Aug 2019 20:50:38 UTC -------------------------------------- - [8a71f71](../../commit/8a71f71) [fixed] onAfterClose prop falsly called on unmount - [1b80146](../../commit/1b80146) Revert "Merge pull request #766 from reactjs/dependabot/npm_and_yarn/webpack-dev-server-3.1.11" - [43e2296](../../commit/43e2296) Revert "[chore] run npm audit fix" - [0b56049](../../commit/0b56049) Revert "[chore] cleanup readme" - [a428d83](../../commit/a428d83) [chore] cleanup readme - [b0eb17f](../../commit/b0eb17f) [chore] run npm audit fix - [9a6edf9](../../commit/9a6edf9) Merge pull request #766 from reactjs/dependabot/npm_and_yarn/webpack-dev-server-3.1.11 - [8f4ea54](../../commit/8f4ea54) Bump webpack-dev-server from 2.11.5 to 3.1.11 3.9.1 - Sun, 14 Jul 2019 16:53:00 UTC ------------------------------------- - [c747c24](../../commit/c747c24) [added] Added an id prop, applied to the modal dialog (content) (#765) 3.8.2 - Sun, 30 Jun 2019 20:48:49 UTC ------------------------------------- - [9fe84df](../../commit/9fe84df) Update `warning` to v4 - [6ff8d85](../../commit/6ff8d85) [fixed] Local development environment (#754) - [a2838bb](../../commit/a2838bb) [chore] update webpack-dev-server dependency. - [ec3f749](../../commit/ec3f749) fix item index - [d56e295](../../commit/d56e295) fix can't close Modal B 3.8.1 - Wed, 19 Dec 2018 00:57:38 UTC ------------------------------------- - [988f55a](../../commit/988f55a) [added] Introduce onAfterClose callback prop (#724) 3.7.1 - Mon, 10 Dec 2018 13:13:29 UTC ------------------------------------- - [2ae092a](../../commit/2ae092a) [fixed] Allow empty classNames for body (#720) - [8d8f476](../../commit/8d8f476) React-Modal: chromeHeadless use - [fc53400](../../commit/fc53400) [fixed] Allow ReactDOM.createPortal to be mocked in tests - [6a6bcf7](../../commit/6a6bcf7) [fixed] Render `testId` property - [1b561fc](../../commit/1b561fc) [fixed] if tabbable element is undefined, focus head or tail based on shiftKey - [86632aa](../../commit/86632aa) [fixed] check if element exists before focusing in scopeTab helper 3.6.1 - Tue, 25 Sep 2018 11:53:39 UTC ------------------------------------- - [a2c38cc](../../commit/a2c38cc) [fixed] set default role for accessibility - [921358e](../../commit/921358e) Add explicit comments as suggested in PR review - [3d74c1b](../../commit/3d74c1b) Update doc to inform v3 users about close transition - [1e349c0](../../commit/1e349c0) [fixed] remove aria-modal attr to prevent browser bugs 3.5.1 - Wed, 04 Jul 2018 10:23:09 UTC ------------------------------------- - [c3e06ab](../../commit/c3e06ab) [added] additional data attributes. - [e5a80d6](../../commit/e5a80d6) [fixed] portal node will be reparented if necessary on props change 3.4.5 - Fri, 01 Jun 2018 11:11:50 UTC ------------------------------------- - [2bf2dd2](../../commit/2bf2dd2) chore: update Makefile. - [73893a2](../../commit/73893a2) [fixed] Safety check for SSR (#668) - [5f92df7](../../commit/5f92df7) very small spelling correction in comment - [92cae36](../../commit/92cae36) [chore] don't allow publish if branch is not master. 3.4.4 - Mon, 23 Apr 2018 23:09:20 UTC ------------------------------------- - [2e619b9](../../commit/2e619b9) [chore] fix incorrect path for module in package.json. 3.4.3 - Mon, 23 Apr 2018 23:07:56 UTC ------------------------------------- - [35c56e6](../../commit/35c56e6) [chore] fix incorrect path for module in package.json. 3.4.2 - Thu, 19 Apr 2018 09:17:14 UTC ------------------------------------- - [529ca33](../../commit/529ca33) Add `testId` prop for use as a test hook - [e294dc7](../../commit/e294dc7) [added] Add module field to package json - [d8fe0dd](../../commit/d8fe0dd) Added default prop for defaultStyles property 3.4.1 - Tue, 17 Apr 2018 09:50:04 UTC ------------------------------------- - [2132488](../../commit/2132488) Add eslint rule to disallow importing devDependencies in lib sources - [4887c69](../../commit/4887c69) Move react-lifecycles-compat to `dependencies` and upgrade it to v3 - [f748406](../../commit/f748406) Remove cWRP usage in ModalPortal - [e91d59a](../../commit/e91d59a) Fix lifecycle method usages in Modal - [0dd7805](../../commit/0dd7805) [chore] update the pull request template... - [fa8e33c](../../commit/fa8e33c) removed un-safe lifecycle methods componentWillMount and componentWillUpdate. Implemented getDerivedStateFromProps and getSnapshotBeforeUpdate lifecycle methods using react-lifecycles-compat polyfill. - [d8c3dad](../../commit/d8c3dad) [fixed] mouse up event on overlay triggered the closing of the modal - [d6f3463](../../commit/d6f3463) [chore] Update transitions.md (#635) - [fa87046](../../commit/fa87046) [Chore] update README.md: added description for setting app element 3.3.2 - Mon, 12 Mar 2018 22:16:58 UTC ------------------------------------- - [eb1ac25](../../commit/eb1ac25) [chore] update list of files that shouldn't be packed. - [d8051f9](../../commit/d8051f9) [chore] improve documentation for shouldCloseOnOverlayClick. - [9012d81](../../commit/9012d81) [chore] add to docs the default value of the html class option. 3.3.1 - Wed, 21 Feb 2018 09:54:52 UTC ------------------------------------- - [0c6d966](../../commit/0c6d966) [added] htmlOpenClassName will follow the same rules like... bodyOpenClassName. - [088e68e](../../commit/088e68e) [added] add class to html when modal is open - [e6159b6](../../commit/e6159b6) [chore] Fix README table of contents - [241b8a6](../../commit/241b8a6) [chore] Move API documentation from README to gitbook - [4c1e590](../../commit/4c1e590) Expand documentation 3.2.1 - Thu, 15 Feb 2018 09:07:59 UTC ------------------------------------- - [0809958](../../commit/0809958) [added] ref for overlay and content - [61b141d](../../commit/61b141d) Fix minor typos in spec 3.1.13 - Fri, 09 Feb 2018 10:28:38 UTC -------------------------------------- - [700a28a](../../commit/700a28a) [fixed] Tab focus escapes modal on shift + tab after opening 3.1.12 - Mon, 05 Feb 2018 08:35:24 UTC -------------------------------------- - [6c4d4ad](../../commit/6c4d4ad) [fixed] management of aria-hidden attribute decoupled from the management of the body open class - [93b2c05](../../commit/93b2c05) [chore] Bump bootstrap example to use 4.0 release - [0bd1505](../../commit/0bd1505) [chore] edits style doc - [c7c928c](../../commit/c7c928c) [chore] updates style page - [f5b9c11](../../commit/f5b9c11) [chore] updates README - styles 3.1.11 - Tue, 16 Jan 2018 12:45:23 UTC -------------------------------------- - [3256671](../../commit/3256671) [fixed] when ModalPortal is clicked, shouldClose is true if shouldCloseOnOverlayClick is true. 3.1.10 - Tue, 19 Dec 2017 17:42:30 UTC -------------------------------------- - [9a3542a](../../commit/9a3542a) [fixed] stop propagating ESC key event. 3.1.9 - Tue, 19 Dec 2017 17:36:51 UTC ------------------------------------- - [b2c347b](../../commit/b2c347b) [fixed] stop propagating ESC key event. 3.1.8 - Tue, 12 Dec 2017 20:45:35 UTC ------------------------------------- - [eb5ea07](../../commit/eb5ea07) [fixed] check if focusLaterElements is empty before popping 3.1.7 - Mon, 04 Dec 2017 14:23:04 UTC ------------------------------------- - [22e8b23](../../commit/22e8b23) [fixed] ignore .babelrc when publishing to npm. - [5693a40](../../commit/5693a40) [chore] typo on word (#574) 3.1.6 - Thu, 30 Nov 2017 10:25:02 UTC ------------------------------------- - [0122238](../../commit/0122238) [chore] added badge to react-modal gitter channel. - [c925763](../../commit/c925763) remove code climate badge from readme. - [38dc8f9](../../commit/38dc8f9) [fixes] don't set aria-hidden if appElement is not defined. 3.1.5 - Mon, 27 Nov 2017 19:57:33 UTC ------------------------------------- - [cae99d9](../../commit/cae99d9) [fixed] shouldCloseOnOverlayClick conflict with text inputs. 3.1.4 - Fri, 24 Nov 2017 14:27:40 UTC ------------------------------------- - [c1e535f](../../commit/c1e535f) [chore] changelog writer. - [a296627](../../commit/a296627) fix: prevent mouse event when shouldCloseOnOverlayClick = false. - [cba31dd](../../commit/cba31dd) Update on_request_close.md 3.1.3 - Wed, 22 Nov 2017 16:38:06 UTC ------------------------------------- - [c434e84](../../commit/c434e84) [fixed] Respect overflow css property when determining whether or not a tabbable node is hidden 3.1.2 - Mon, 06 Nov 2017 19:56:00 UTC ------------------------------------- - [3c86e2d](../../commit/3c86e2d) [fixed] shouldFocusAfterRender and shouldReturnFocusAfterClose flags. - [0f2bf9e](../../commit/0f2bf9e) [fixed] corretly walk when using TAB. - [5cf9326](../../commit/5cf9326) Update README.md - [cdcc1cb](../../commit/cdcc1cb) [chore]: fixed changelog generator. 3.1.0 - Wed, 25 Oct 2017 14:26:17 UTC ------------------------------------- - [42d724c](../../commit/42d724c) [added] shouldReturnFocusAfterClose to control focus. - [400ac13](../../commit/400ac13) [chore] make sure all tests meet line-length requirement. - [18a15eb](../../commit/18a15eb) [fixed] correct property name shouldFocusAfterRender. 3.0.4 - Wed, 18 Oct 2017 19:55:15 UTC ------------------------------------- - [5ec0f7f](../../commit/5ec0f7f) [fixed] Removes body classNames after the modal is closed. - [1fb33d9](../../commit/1fb33d9) [chore] run tests only on node 8. - [59fbdb3](../../commit/59fbdb3) [chore] specifiy the object when overriding class names. 3.0.3 - Sat, 14 Oct 2017 17:38:38 UTC ------------------------------------- - [32441c3](../../commit/32441c3) [fixed] Issue #526 Import PropTypes as default import. 3.0.2 - Sat, 14 Oct 2017 12:04:40 UTC ------------------------------------- - [1d495a6](../../commit/1d495a6) [fixed] Add shouldCloseOnEsc prop - [d98f091](../../commit/d98f091) [chore] update CHANGELOG.md - [95f628a](../../commit/95f628a) [chore] fix prettier linting after merge issue - [47d0d87](../../commit/47d0d87) [chore] prettier all the things - [c0620e0](../../commit/c0620e0) [chore] Use babel-preset-env instead of babel-preset-es2015 - [2a05bd8](../../commit/2a05bd8) [chore] Updated eslint and a few related deps - [b3701f6](../../commit/b3701f6) [fixed] Issue #526 Import PropTypes as default import. 3.0.0 - Fri, 06 Oct 2017 13:29:20 UTC ------------------------------------- - [d0f242b](../../commit/d0f242b) Merged next into master. - [de3c039](../../commit/de3c039) [chore] remove warning about injectCSS. - [f77b53e](../../commit/f77b53e) [chore] use canUseDOM from exenv. - [4fa5628](../../commit/4fa5628) [fixed] Drag stop (mouseup) on Overlay closes Modal - [a712d88](../../commit/a712d88) [chore] update README.md installation for react 16 support. - [f9a2f3f](../../commit/f9a2f3f) [chore] update README.md. 3.0.0-rc2 - Wed, 04 Oct 2017 13:30:44 UTC ----------------------------------------- - [1260850](../../commit/1260850) [fixed] backward compatibility with previous react versions. 3.0.0-rc1 - Wed, 04 Oct 2017 10:34:47 UTC ----------------------------------------- - [d25563c](../../commit/d25563c) [fixed] check for both window and document.createElement. - [2b835d6](../../commit/2b835d6) [fixed] typeof returns a string (canUseDOM). 3.0.0-alpha - Wed, 04 Oct 2017 03:58:27 UTC ------------------------------------------- - [b942504](../../commit/b942504) [feature] initial support for react 16. 2.4.1 - Fri, 06 Oct 2017 12:09:11 UTC ------------------------------------- - [4fa5628](../../commit/4fa5628) [fixed] Drag stop (mouseup) on Overlay closes Modal - [a712d88](../../commit/a712d88) [chore] update README.md installation for react 16 support. - [f9a2f3f](../../commit/f9a2f3f) [chore] update README.md. 2.3.3 - Wed, 04 Oct 2017 01:59:57 UTC ------------------------------------- - [ace2bf0](../../commit/ace2bf0) [chore] added babel-cli to compile and fix dist configuration. - [876972b](../../commit/876972b) [chore] removed depencendy 'react-dom-factory'. - [73db6dd](../../commit/73db6dd) [chore] improve examples style. - [8970956](../../commit/8970956) [chore] remove code climate yml. - [d896241](../../commit/d896241) [chore] fix multiple modal example. - [ce2b34e](../../commit/ce2b34e) [chore] added example with react-router. - [16c3dce](../../commit/16c3dce) [chore] update dependencies, lint rules and refactor tests. - [13dfc4e](../../commit/13dfc4e) [chore] update tests. - [ba81894](../../commit/ba81894) Remove required flag from contentLabel propType in Modal - [f007aeb](../../commit/f007aeb) [chore] Update description for onRequestClose. 2.3.2 - Wed, 06 Sep 2017 16:10:27 UTC ------------------------------------- - [54c59c5](../../commit/54c59c5) Export default property in CommonJS and global object - [ba526cf](../../commit/ba526cf) [chore] fix changelog generator. 2.3.1 - Tue, 05 Sep 2017 16:18:41 UTC ------------------------------------- - [93256e9](../../commit/93256e9) [added] Don't focus after render if we don't want to - [2adb45d](../../commit/2adb45d) [chore] update readme syntax flavour to jsx 2.2.4 - Mon, 14 Aug 2017 09:41:39 UTC ------------------------------------- - [fafa127](../../commit/fafa127) typo fix - [fe1983b](../../commit/fe1983b) fix #466: Dragging inside modal and release outside of modal closes the modal 2.2.3 - Thu, 10 Aug 2017 19:27:47 UTC ------------------------------------- - [1caabed](../../commit/1caabed) [fixed] `Uncaught TypeError: Cannot read property 'state' of null` when unmouting - [92c23b5](../../commit/92c23b5) [chore] Mention shouldCloseOnOverlayClick dependency - [a2d5c4e](../../commit/a2d5c4e) [chore] allow latest version for exenv dependency 2.2.2 - Tue, 11 Jul 2017 14:20:29 UTC ------------------------------------- - [9076eb7](../../commit/9076eb7) [added] Support using multiple document.body classes - [581be77](../../commit/581be77) [chore] added test for default parentSelector. - [e56c414](../../commit/e56c414) [chore] Return null for portal when modal is closed 2.2.1 - Fri, 30 Jun 2017 09:22:10 UTC ------------------------------------- - [7301aa7](../../commit/7301aa7) [chore] Change 'a11y' to 'accessibility' in README.md - [f47e79d](../../commit/f47e79d) [fixed] Modal.removePortal not called when using closeTimeoutMS - [f386aa4](../../commit/f386aa4) [chore] added more examples. 2.2.0 - Wed, 28 Jun 2017 18:56:24 UTC ------------------------------------- - [67ee9f5](../../commit/67ee9f5) [added] allow users to pass aria-* attribute. - [6f73764](../../commit/6f73764) [chore] update installation documentation. 2.1.0 - Mon, 26 Jun 2017 22:11:54 UTC ------------------------------------- - [1baebf4](../../commit/1baebf4) [change] Track open body className appropriately 2.0.7 - Sun, 25 Jun 2017 17:44:29 UTC ------------------------------------- - [d1fe05e](../../commit/d1fe05e) [chore] use local babel instead of requiring a global installation. - [bb69a91](../../commit/bb69a91) [chore] improvements on readme testing section. - [c2f582f](../../commit/c2f582f) [chore] fix typo. - [f8edc2b](../../commit/f8edc2b) [fixed] improvements on setAppElement... - [5641f40](../../commit/5641f40) [chore] update installation section. - [ae258ec](../../commit/ae258ec) [chore] removing active development section. - [f5d95e2](../../commit/f5d95e2) Add codesandbox link to the ISSUE_TEMPLATE 2.0.6 - Tue, 20 Jun 2017 11:23:30 UTC ------------------------------------- - [1676259](../../commit/1676259) removing trailing space. (#2) - [07a2753](../../commit/07a2753) [fixed] check if the modal content is available when async update... (#1) - [cb6504c](../../commit/cb6504c) [fixed] Use bound ref functions - [7da6ec8](../../commit/7da6ec8) [fix] Unnecessary renders when modal is closed - [648cc2f](../../commit/648cc2f) [fixed] update script path on bootstrap example. - [6c780ae](../../commit/6c780ae) Update react-addons-test-utils to react-dom/test-utils - [385a8eb](../../commit/385a8eb) Update react dependencies - [8480042](../../commit/8480042) [chore] cache yarn when running on travis-ci. - [8d87599](../../commit/8d87599) [chore] added documentation for development. - [aaeb310](../../commit/aaeb310) [chore] added patterns on .gitignore. - [4ec7184](../../commit/4ec7184) [chode] Add table of contents - [96fdb90](../../commit/96fdb90) [chore] don't test on node versions < 6.x.y. - [20fcdc3](../../commit/20fcdc3) [chore] update README.md. - [4b57b2a](../../commit/4b57b2a) [chore] added script to regenerate all the changelog. - [21dc212](../../commit/21dc212) [chore] ask before publishing... - [2af9b89](../../commit/2af9b89) chore] improve publish docs commands. 2.0.2 - Fri, 16 Jun 2017 13:10:06 UTC ------------------------------------- - [c1dc7fd](../../commit/c1dc7fd) change PropTypes.String to PropTypes.string 2.0.1 - Fri, 16 Jun 2017 11:30:58 UTC ------------------------------------- - [435ab91](../../commit/435ab91) Update eslint config so it should pass - [b1a28a4](../../commit/b1a28a4) remove yarn.lock per comment - [389a8fa](../../commit/389a8fa) Address review comments - [ab11c36](../../commit/ab11c36) [fixed] removing 'es' for now. - [2f0a1a9](../../commit/2f0a1a9) [fixed] added rules to compile on Makefile. - [e921de8](../../commit/e921de8) [fixed] use the correct babel presets combination. - [92ccf1d](../../commit/92ccf1d) Additional changes to support move from src to lib - [138f8ef](../../commit/138f8ef) Remove unnecessary comma - [e36336a](../../commit/e36336a) Update yarn.lock - [d08b96e](../../commit/d08b96e) Create es and commonjs separate build steps - [d024d3a](../../commit/d024d3a) Transform based on env - [4704fa7](../../commit/4704fa7) Remove unnecessary react-create-class dependency - [a6422f6](../../commit/a6422f6) Use ES module for top level export - [c05e88d](../../commit/c05e88d) Move lib to src so we can use lib for build output 2.0.0 - Thu, 15 Jun 2017 18:16:21 UTC ------------------------------------- - [0374b6b](../../commit/0374b6b) [chore] update makefile to run coverage. - [ba2c124](../../commit/ba2c124) [chore] passing lint... - [a5cc01b](../../commit/a5cc01b) Use callback ref in readme - [933f3a4](../../commit/933f3a4) Modify the sample code to es2015 syntax in README.md (#295) - [8059ded](../../commit/8059ded) Updates License (#303) - [315d1e1](../../commit/315d1e1) Add code climate and code coverage - [1c326a1](../../commit/1c326a1) Add Linting (#293) - [d2fbe55](../../commit/d2fbe55) [chore] added babel stage-2 preset. 1.9.7 - Thu, 15 Jun 2017 13:28:17 UTC ------------------------------------- - [df14528](../../commit/df14528) Added cross-env to run tests on windows 1.9.6 - Thu, 15 Jun 2017 00:57:18 UTC ------------------------------------- - [91e1a67](../../commit/91e1a67) Remove deprecation warning in react 15.6.0 about React.DOM.noscript - [937f835](../../commit/937f835) [chore] removing unnecessary file. 1.9.5 - Wed, 14 Jun 2017 22:57:03 UTC ------------------------------------- - [3139e85](../../commit/3139e85) [added] refresh portalClassName on componentWillUpdate 1.9.4 - Tue, 13 Jun 2017 10:12:34 UTC ------------------------------------- - [0510f62](../../commit/0510f62) Add gzip size badge 1.9.3 - Tue, 13 Jun 2017 09:21:26 UTC ------------------------------------- - [28ecc0b](../../commit/28ecc0b) [fixed] compatibility with unstable_handleError. 1.9.2 - Mon, 12 Jun 2017 21:05:11 UTC ------------------------------------- - [a61f73c](../../commit/a61f73c) fix react proptypes warning 1.9.1 - Mon, 12 Jun 2017 16:27:34 UTC ------------------------------------- - [a12246e](../../commit/a12246e) [changed] use object className and overlayClassName prop to override... 1.8.1 - Mon, 12 Jun 2017 12:37:12 UTC ------------------------------------- - [e5bb415](../../commit/e5bb415) [change] allow to customize the react-modal document.body open class. 1.7.13 - Mon, 12 Jun 2017 10:26:34 UTC -------------------------------------- - [3bc4719](../../commit/3bc4719) [chore] refactoring tests... 1.7.12 - Fri, 09 Jun 2017 22:27:37 UTC -------------------------------------- - [4b69478](../../commit/4b69478) [chore] clean publish resources before start... - [00ea6fe](../../commit/00ea6fe) [chore] refactor and clean up build system. 1.7.11 - Thu, 08 Jun 2017 16:47:56 UTC -------------------------------------- - [a3f69d5](../../commit/a3f69d5) [chore] add travis ci build status on README.md. 1.7.10 - Thu, 08 Jun 2017 16:43:41 UTC -------------------------------------- - [651ce99](../../commit/651ce99) [chore] prevent publish if an error occur. 1.7.9 - Thu, 08 Jun 2017 12:59:39 UTC ------------------------------------- - [99c7e32](../../commit/99c7e32) [fixed] use Object.assign for now. 1.7.8 - Thu, 08 Jun 2017 01:45:46 UTC ------------------------------------- - [14a2fd0](../../commit/14a2fd0) [chore] improving build and publish pipeline. 1.7.7 - Tue, 18 Apr 2017 07:40:29 UTC ------------------------------------- - [889ffde](../../commit/889ffde) [fixed] Removed additional es2015 causing problems 1.7.6 - Thu, 13 Apr 2017 08:41:16 UTC ------------------------------------- - [048ef2d](../../commit/048ef2d) [fixed] remove additional es2015 from refCount 1.7.5 - Thu, 13 Apr 2017 08:20:34 UTC ------------------------------------- - [1db0ee1](../../commit/1db0ee1) [fixed] remove es2015 from v1 branch 1.7.4 - Thu, 13 Apr 2017 07:37:19 UTC ------------------------------------- - [86987d5](../../commit/86987d5) Use create-react-class to avoid React.createClass deprecations - [16efd72](../../commit/16efd72) Use prop-types - [e579a0d](../../commit/e579a0d) [fix] keep references of modals. 1.7.3 - Mon, 13 Mar 2017 19:22:00 UTC ------------------------------------- - [e1df119](../../commit/e1df119) [fixed] remove portal context in timeout (#353) 1.7.2 - Wed, 08 Mar 2017 20:59:52 UTC ------------------------------------- - [185f2b0](../../commit/185f2b0) Remove .bind(this) from removePortal call 1.7.1 - Thu, 02 Mar 2017 07:49:30 UTC ------------------------------------- - [a1d29c6](../../commit/a1d29c6) [fixed] rewrite removePortal as es5 function 1.7.0 - Wed, 01 Mar 2017 20:54:08 UTC ------------------------------------- - [fb3eb5e](../../commit/fb3eb5e) [chore] use afterEach to cleanup modals automatically. - [ea4f37a](../../commit/ea4f37a) [fixed] respect closeTimeoutMS during unmount - [f6768b7](../../commit/f6768b7) [change] improve reliability on focus management. - [4232477](../../commit/4232477) [fixed] Enable click to close in iOS (#301) (#304) (#313) 1.6.5 - Sat, 31 Dec 2016 10:14:28 UTC ------------------------------------- - [c50f19a](../../commit/c50f19a) [fixed] Add file extention to entry point (#294) - [f22c206](../../commit/f22c206) Add v2 development info - [426f5e4](../../commit/426f5e4) Update testing setup - [945919d](../../commit/945919d) Ignore the _book directory - [ff23603](../../commit/ff23603) Move documentation site to GitBook - [08bf920](../../commit/08bf920) [fixed] closeTimeoutMS doesn't work without onRequestClose (#278) - [6c68e95](../../commit/6c68e95) Update CHANGELOG.md 1.6.4 - Wed, 14 Dec 2016 22:48:59 UTC ------------------------------------- - [ad0b071](../../commit/ad0b071) Bumps lodash.assign to 4.2.0 (#277) - [694cb87](../../commit/694cb87) [fixed] updated references from rackt to reactjs. (#244) - [1dea51d](../../commit/1dea51d) Update travis build matrix - [e50dc70](../../commit/e50dc70) Update CHANGELOG.md 1.6.3 - Mon, 12 Dec 2016 07:03:43 UTC ------------------------------------- - [a2e5952](../../commit/a2e5952) [docs] added required props info to README (#274) - [f460c10](../../commit/f460c10) Update CHANGELOG.md 1.6.2 - Sun, 11 Dec 2016 10:32:03 UTC ------------------------------------- - 1.6.1 - Tue, 06 Dec 2016 10:16:10 UTC ------------------------------------- - [62d87e1](../../commit/62d87e1) [fixed] Remove arrow function from ES5 source 1.6.0 - Tue, 06 Dec 2016 08:09:25 UTC ------------------------------------- - [de14816](../../commit/de14816) [added] Ability for modal to be appended to arbitrary elements (#183) - [3fdc672](../../commit/3fdc672) Ensure aria-hidden on appElement is reset on unmount - [e9fd43d](../../commit/e9fd43d) Document ReactModal__Body--open so people dare to use it - [3d8e5a0](../../commit/3d8e5a0) [added] Add contentLabel prop to put aria-label on modal content 1.5.2 - Sat, 08 Oct 2016 08:29:09 UTC ------------------------------------- - [d78428b](../../commit/d78428b) [fixed] Remove remaining reference to role dialog - [b09cdf9](../../commit/b09cdf9) Update CHANGELOG.md 1.5.1 - Fri, 07 Oct 2016 22:11:39 UTC ------------------------------------- - 1.5.0 - Fri, 07 Oct 2016 20:18:52 UTC ------------------------------------- - [919daa3](../../commit/919daa3) [fixed] Remove the default aria role dialog - [c8106f2](../../commit/c8106f2) Update ModalPortal.js (#228) - [2e806c7](../../commit/2e806c7) [added] Make modal portal have the dialog role (#223) - [abe88a8](../../commit/abe88a8) installation instructions (#227) - [5429f7c](../../commit/5429f7c) [fixed] Don't steal focus from a descendent when rendering (#222) - [8e767e9](../../commit/8e767e9) [fixed] Add react-dom as a peer dependency - [ff09b49](../../commit/ff09b49) [fixed] Close modal when mouseDown and MouseUp happen only on the overlay (#217) - [6550b87](../../commit/6550b87) Revert "[fixed] Dont change body class if isOpen not change (#201)" - [8e5f5b7](../../commit/8e5f5b7) [fixed] Fix incorrect details in the README - [e5b0181](../../commit/e5b0181) [added] ability to change default 'ReactModalPortal' class (#208) - [1e29e4f](../../commit/1e29e4f) [fixed] Dont change body class if isOpen not change (#201) - [d347547](../../commit/d347547) [fixed] Updates webpack distribution config to reference the correct externals (#210) - [f0933fd](../../commit/f0933fd) [doc] fix onRequestClose callback in Usage (#195) 1.4.0 - Thu, 30 Jun 2016 13:12:02 UTC ------------------------------------- - [13bd46e](../../commit/13bd46e) [fixed] clear the delayed close timer when modal opens again. (#189) - [70d91eb](../../commit/70d91eb) [fixed] Add missing envify npm dependency. Closes #193 (#194) 1.3.0 - Tue, 17 May 2016 10:04:50 UTC ------------------------------------- - [9089a2d](../../commit/9089a2d) [fixed] Make the modal portal render into body again (#176) - [e9aff7a](../../commit/e9aff7a) Update PULL_REQUEST_TEMPLATE.md 1.2.1 - Sat, 23 Apr 2016 13:09:46 UTC ------------------------------------- - [aa66819](../../commit/aa66819) [fixed] Removes unneeded sanitizeProps function (#169) 1.2.0 - Thu, 21 Apr 2016 16:02:02 UTC ------------------------------------- - [18f5eae](../../commit/18f5eae) fix typo in README :memo: (#168) - [a10683a](../../commit/a10683a) [fixed] Make the non-minified dist build present again (#164) - [04db149](../../commit/04db149) [added] Propagate event on close request (#91) 1.1.2 - Mon, 18 Apr 2016 20:36:05 UTC ------------------------------------- - [4509133](../../commit/4509133) [fixed] moved sanitizeProps out of the render calls. (#162) - [25c1dad](../../commit/25c1dad) Update changelog for 1.1.1 1.1.1 - Thu, 14 Apr 2016 23:30:45 UTC ------------------------------------- - [f1555d9](../../commit/f1555d9) Merge branch 'development-improvements' - [9823bc5](../../commit/9823bc5) Use -p flag in webpack for minification and exclude externals react and react-dom (#159) - [72c8498](../../commit/72c8498) Move to using webpack for building the library 1.1.0 - Tue, 12 Apr 2016 07:03:08 UTC ------------------------------------- - [6c03d17](../../commit/6c03d17) [added] trigger onAfterOpen callback when available. (#154) - [7cf8463](../../commit/7cf8463) [doc] Update docs to include details about CSS classes 1.0.0 - Fri, 08 Apr 2016 23:03:25 UTC ------------------------------------- - [7af8ee5](../../commit/7af8ee5) Update README.md to include testing gotchas (#136) - [e4be332](../../commit/e4be332) Add extra information for contributors (#143) - [4e2447a](../../commit/4e2447a) [changed] Updated to add support for React 15 (#152) - [0d4e600](../../commit/0d4e600) [added] module for default style - [cf70338](../../commit/cf70338) Avoid stopPropagation - [f9871c6](../../commit/f9871c6) Merge pull request #94 from apprennet/remove-body-class-unmount - [cb53bca](../../commit/cb53bca) [fixed] Remove ReactModal__Body--open class when unmounting Modal - [fe46c63](../../commit/fe46c63) Merge pull request #108 from evoyy/pr/override-anchor-to-document-body - [b5e38cf](../../commit/b5e38cf) Merge pull request #141 from everdimension/fix-no-tabbable-focus - [c844719](../../commit/c844719) keep focus on modal if no tabbable elements are within it - [e8749dd](../../commit/e8749dd) Merge pull request #128 from dorsha/master - [93c73f3](../../commit/93c73f3) Merge pull request #140 from everdimension/add_missing_webpack_dependency - [d732041](../../commit/d732041) add missing webpack devDependency - [6282c3e](../../commit/6282c3e) Added the ability to decide whether the modal should be closed when clicking the overlay area. This is an important ability since in some cases we don't want the modal to be closed when users are clicking outside. Added tests and README instructions. - [23eee3b](../../commit/23eee3b) Merge pull request #120 from evoyy/pr/bugfix_empty_tabbable_array - [471ef4c](../../commit/471ef4c) Handle case when no tabbable element exists - [c13fed9](../../commit/c13fed9) Restore Modal.setAppElement() functionality - [06ebde2](../../commit/06ebde2) Merge pull request #121 from evoyy/pr/listen-on-all-interfaces - [597882d](../../commit/597882d) Merge pull request #123 from evoyy/pr/fix_example_css_for_firefox - [980ad5d](../../commit/980ad5d) Merge pull request #132 from shunjikonishi/shunjikonishi-patch-1 - [bc58b9c](../../commit/bc58b9c) Merge pull request #100 from claydiffrient/bugfix/classes-take-precedence - [ef02e29](../../commit/ef02e29) Prevent default behavior of ESC key - [7f631bd](../../commit/7f631bd) Update README.md - [aac1841](../../commit/aac1841) CSS transform for non-Webkit browsers - [3e89412](../../commit/3e89412) dev server listens on all interfaces - [63bee72](../../commit/63bee72) [fixed] Custom classnames override default styles 0.6.1 - Fri, 23 Oct 2015 12:03:54 UTC ------------------------------------- - [e20595e](../../commit/e20595e) Merge pull request #87 from flskif/master - [5705b85](../../commit/5705b85) Ignore react-dom in build 0.6.0 - Wed, 21 Oct 2015 15:39:48 UTC ------------------------------------- - [cd4dd21](../../commit/cd4dd21) Merge pull request #85 from miracle2k/master - [5c59b9f](../../commit/5c59b9f) Use renderSubtreeIntoContainer to keep context. - [c7153d1](../../commit/c7153d1) Merge pull request #84 from existentialism/typos - [78fa9bd](../../commit/78fa9bd) fix a couple typos - [acdcb7c](../../commit/acdcb7c) Merge pull request #83 from roth1002/feature/react-14 - [4b3b885](../../commit/4b3b885) move exenv to dependencies - [c107d02](../../commit/c107d02) Merge pull request #81 from roth1002/feature/react-14 - [7e12d8a](../../commit/7e12d8a) Modify spec Readme.md example to use ReactDOM.render to replace React.render - [496bb0b](../../commit/496bb0b) upgrade react 0.14 - [920d421](../../commit/920d421) Merge pull request #70 from dinodsaurus/master - [4c8ed91](../../commit/4c8ed91) imporved env check - [33d47db](../../commit/33d47db) added suport for isomorphic rendering 0.5.0 - Tue, 22 Sep 2015 13:19:44 UTC ------------------------------------- - [408329f](../../commit/408329f) Updating dependencies - [b24bc4b](../../commit/b24bc4b) Merge pull request #65 from web2style/master - [6b50f7b](../../commit/6b50f7b) Merge pull request #58 from jackofseattle/fix/NoMoreInjectCSS - [4d25989](../../commit/4d25989) [added] Inline CSS for modal and overlay as well as props to override. [changed] injectCSS has been changed to a warning message in preperation for a future removal. lib/components/Modal.js [changed] setAppElement method is now optional. Defaults to document.body and now allows for a css selector to be passed in rather than the whole element. - [acd3c65](../../commit/acd3c65) Merge pull request #53 from ewiner/master - [9e092ae](../../commit/9e092ae) Merge pull request #52 from DelvarWorld/noscript - [8ccf23a](../../commit/8ccf23a) Merge pull request #63 from basarat/patch-1 - [9bd8f68](../../commit/9bd8f68) Update peerDependencies - [9545427](../../commit/9545427) :memo: link to demos - [02cf2c3](../../commit/02cf2c3) [fixed] Clear the closeWithTimeout timer before unmounting - [85a13b8](../../commit/85a13b8) Returning noscript tag instead of null - [0d5e76a](../../commit/0d5e76a) Updating README 0.3.0 - Wed, 15 Jul 2015 00:17:24 UTC ------------------------------------- - [adecf62](../../commit/adecf62) [added] Class name on body when modal is open - [0e94233](../../commit/0e94233) Updating dependencies - [3938e55](../../commit/3938e55) Merge pull request #42 from claydiffrient/patch-1 - [669f3a8](../../commit/669f3a8) Merge pull request #39 from wisely0515/ie8-support - [fbb07d4](../../commit/fbb07d4) Moves classnames to dependencies - [278b9ba](../../commit/278b9ba) fixed 'unknow runtime error' ie IE8 0.2.0 - Fri, 08 May 2015 23:16:40 UTC ------------------------------------- - [1a51bf8](../../commit/1a51bf8) Merge pull request #31 from maisano/patch-1 - [494d7d2](../../commit/494d7d2) Merge pull request #28 from peterjmag/use-classnames-module - [e06e801](../../commit/e06e801) Merge pull request #22 from misuba/bugfix/server-clean - [1829f43](../../commit/1829f43) Merge pull request #27 from claydiffrient/master - [e898b6b](../../commit/e898b6b) Check if modalElement exists in handleFocus. - [930c4ca](../../commit/930c4ca) Use classnames instead of react/lib/cx. - [f5fe537](../../commit/f5fe537) [added] Ability to specify style for the modal contents - [6887b00](../../commit/6887b00) Shim the possibly-absent HTMLElement 0.1.1 - Tue, 31 Mar 2015 09:56:47 UTC ------------------------------------- - [bb57045](../../commit/bb57045) Merge pull request #19 from amccloud/patch-2 - [10e9582](../../commit/10e9582) Merge pull request #18 from amccloud/patch-1 - [f86de0a](../../commit/f86de0a) [fixed] shift+tab closes #23 - [bb218ca](../../commit/bb218ca) ignore node_modules - [c464368](../../commit/c464368) Check for addEventListener before tying to use to support IE 8 - [63b6828](../../commit/63b6828) Remove trailing commas for IE8 support 0.1.0 - Thu, 26 Feb 2015 10:14:27 UTC ------------------------------------- - [db8b725](../../commit/db8b725) Merge pull request #16 from arasmussen/master - [1b8e2d0](../../commit/1b8e2d0) [fixed] ModalPortal's componentWillReceiveProps 0.0.7 - Fri, 02 Jan 2015 23:44:47 UTC ------------------------------------- - [ea31beb](../../commit/ea31beb) Using shared stylesheet - [8e01b27](../../commit/8e01b27) Renaming example - [399b386](../../commit/399b386) Merge pull request #8 from leoasis/update_react_version - [a8faa92](../../commit/a8faa92) Fixing paths so they work on gh-pages - [1024026](../../commit/1024026) Fixing paths so they work on gh-pages - [2d62f51](../../commit/2d62f51) Adding default example - [4a85cd2](../../commit/4a85cd2) Update to React 0.12. Fix warnings. 0.0.6 - Wed, 03 Dec 2014 14:24:45 UTC ------------------------------------- - [2f1973b](../../commit/2f1973b) Merge branch 'master' of https://github.com/rackt/react-modal - [28dbc63](../../commit/28dbc63) [added] Supporting custom overlay className closes #14 - [1038e3b](../../commit/1038e3b) Merge pull request #13 from knomedia/kill-extra-alias-in-build - [6626dae](../../commit/6626dae) [fixed] erroneous alias in webpack build 0.0.5 - Thu, 13 Nov 2014 11:55:47 UTC ------------------------------------- - [edd0dc7](../../commit/edd0dc7) Merge pull request #12 from cavneb/example-bootstrap - [e5cb4e2](../../commit/e5cb4e2) Bootstrap-style modal example - [b15aa82](../../commit/b15aa82) [added] Supporting custom className - [b7a38de](../../commit/b7a38de) [fixed] Warning caused by trying to focus null element closes #11 - [2ac5290](../../commit/2ac5290) Better solution for applying focus 0.0.4 - Tue, 11 Nov 2014 09:08:14 UTC ------------------------------------- - [ebcc11f](../../commit/ebcc11f) s/script/scripts/ - [278cfbc](../../commit/278cfbc) Merge pull request #10 from rackt/bug-9 - [9616a8c](../../commit/9616a8c) Removing console.log - [e57bab5](../../commit/e57bab5) [fixed] Issue with focus being lost - closes #9 - [07541b3](../../commit/07541b3) example displaying bug - [31c160d](../../commit/31c160d) switch to web pack dev server for examples 0.0.3 - Fri, 31 Oct 2014 13:25:20 UTC ------------------------------------- - [cf3e57a](../../commit/cf3e57a) Merge pull request #5 from leoasis/fix_main_package_json - [5ea6651](../../commit/5ea6651) Fix main entry point in package.json - [2277726](../../commit/2277726) Merge pull request #1 from claydiffrient/master - [8f7cefd](../../commit/8f7cefd) Updates keyboard handling to use keyCode - [de0b661](../../commit/de0b661) slightly less junky README - [cde1572](../../commit/cde1572) hang on people, just hang on. 0.0.2 - Wed, 24 Sep 2014 20:36:47 UTC ------------------------------------- - 0.0.1 - Wed, 24 Sep 2014 16:26:40 UTC ------------------------------------- - [f0727db](../../commit/f0727db) add built files ================================================ FILE: CONTRIBUTING.md ================================================ ### Commit Subjects Patches will be only accepted if they have a corresponding issue on GitHub. Having a corresponding issue is better to track and discuss ideas and propose changes. ### Docs Please update the README with any API changes, the code and docs should always be in sync. ### Development - `npm start` runs the dev server to run/develop examples - `npm test` will run the tests. - `scripts/test` same as `npm test` but keeps karma running and watches for changes ## Miscellaneous if you faced the below issue, make sure you use node version < 18 ```node:internal/crypto/hash:71 this[kHandle] = new _Hash(algorithm, xofLen); ^ Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:71:19) at Object.createHash (node:crypto:133:10)``` ================================================ FILE: LICENSE ================================================ Copyright (c) 2017 Ryan Florence Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ NODE=$(shell which node 2> /dev/null) NPM=$(shell which npm 2> /dev/null) YARN=$(shell which yarn 2> /dev/null) JQ=$(shell which jq 2> /dev/null) PKM?=$(if $(YARN),$(YARN),$(shell which npm)) BABEL=./node_modules/.bin/babel COVERALLS=./node_modules/coveralls/bin/coveralls.js REMOTE="git@github.com:reactjs/react-modal" CURRENT_VERSION:=$(shell jq ".version" package.json) COVERAGE?=true BRANCH=$(shell git rev-parse --abbrev-ref HEAD) CURRENT_VERSION:=$(shell jq ".version" package.json) VERSION:=$(if $(RELEASE),$(shell read -p "Release $(CURRENT_VERSION) -> " V && echo $$V),"HEAD") help: info @echo @echo "Current version: $(CURRENT_VERSION)" @echo @echo "List of commands:" @echo @echo " make info - display node, npm and yarn versions..." @echo " make deps - install all dependencies." @echo " make serve - start the server." @echo " make tests - run tests." @echo " make tests-single-run - run tests (used by continuous integration)." @echo " make coveralls - show coveralls." @echo " make lint - run lint." @echo " make docs - build and serve the docs." @echo " make build - build project artifacts." @echo " make publish - build and publish version on npm." @echo " make publish-docs - build the docs and publish to gh-pages." @echo " make publish-all - publish version and docs." info: @[[ ! -z "$(NODE)" ]] && echo node version: `$(NODE) --version` "$(NODE)" @[[ ! -z "$(PKM)" ]] && echo $(shell basename $(PKM)) version: `$(PKM) --version` "$(PKM)" @[[ ! -z "$(JQ)" ]] && echo jq version: `$(JQ) --version` "$(JQ)" deps: deps-project deps-docs deps-project: @$(PKM) install deps-docs: @pip install mkdocs mkdocs-material jsx-lexer # Rules for development serve: @npm start tests: @npm run test tests-single-run: @npm run test -- --single-run coveralls: -cat ./coverage/lcov.info | $(COVERALLS) 2>/dev/null tests-ci: clean lint @COVERAGE=$(COVERAGE) make tests-single-run coveralls lint: @npm run lint docs: build-docs pygmentize -S default -f html -a .codehilite > docs/pygments.css mkdocs serve # Rules for build and publish check-working-tree: @[ -z "`git status -s`" ] && \ echo "Stopping publish. There are change to commit or discard." || echo "Worktree is clean." compile: @echo "[Compiling source]" $(BABEL) src --out-dir lib build: compile @echo "[Building dists]" @npx webpack --config ./scripts/webpack.dist.config.js pre-release-commit: git commit --allow-empty -m "Release v$(VERSION)." changelog: @echo "[Updating CHANGELOG.md $(CURRENT_VERSION) > $(VERSION)]" python ./scripts/changelog.py -a $(VERSION) > CHANGELOG.md update-package-version: cat package.json | jq '.version="$(VERSION)"' > tmp; mv -f tmp package.json release-commit: pre-release-commit update-package-version changelog @git add . @git commit --amend -m "`git log -1 --format=%s`" release-tag: git tag "v$(VERSION)" -m "`python ./scripts/changelog.py -c $(VERSION)`" publish-version: release-commit release-tag @echo "[Publishing]" git push $(REMOTE) "$(BRANCH)" "v$(VERSION)" npm publish pre-publish: clean pre-build: deps-project tests-single-run build publish: check-working-tree pre-publish pre-build publish-version publish-finished publish-finished: clean # Rules for documentation init-docs-repo: @mkdir _book build-docs: @echo "[Building documentation]" @rm -rf _book @mkdocs build pre-publish-docs: clean-docs init-docs-repo deps-docs publish-docs: clean pre-publish-docs build-docs @echo "[Publishing docs]" @make -C _book -f ../Makefile _publish-docs _publish-docs: git init . git commit --allow-empty -m 'update book' git checkout -b gh-pages touch .nojekyll git add . git commit -am 'update book' git push git@github.com:reactjs/react-modal gh-pages --force # Run for a full publish publish-all: publish publish-docs # Rules for clean up clean-docs: @rm -rf _book clean-coverage: @rm -rf ./coverage/* clean-build: @rm -rf lib/* clean: clean-build clean-docs clean-coverage ================================================ FILE: README.md ================================================ # react-modal Accessible modal dialog component for React.JS [![Build Status](https://img.shields.io/github/actions/workflow/status/reactjs/react-modal/test.yml?branch=master)](https://github.com/reactjs/react-modal/actions/workflows/test.yml) [![Coverage Status](https://coveralls.io/repos/github/reactjs/react-modal/badge.svg?branch=master)](https://coveralls.io/github/reactjs/react-modal?branch=master) ![gzip size](http://img.badgesize.io/https://unpkg.com/react-modal/dist/react-modal.min.js?compression=gzip) [![Join the chat at https://gitter.im/react-modal/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/react-modal/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Table of Contents * [Installation](#installation) * [API documentation](#api-documentation) * [Examples](#examples) * [Demos](#demos) ## Installation To install, you can use [npm](https://npmjs.org/) or [yarn](https://yarnpkg.com): $ npm install --save react-modal $ yarn add react-modal To install react-modal in React CDN app: - Add this CDN script tag after React CDN scripts and before your JS files (for example from [cdnjs](https://cdnjs.com/)): - Use `` tag inside your React CDN app. ## API documentation The primary documentation for react-modal is the [reference book](https://reactjs.github.io/react-modal), which describes the API and gives examples of its usage. ## Examples Here is a simple example of react-modal being used in an app with some custom styles and focusable input elements within the modal content: ```jsx import React from 'react'; import ReactDOM from 'react-dom'; import Modal from 'react-modal'; const customStyles = { content: { top: '50%', left: '50%', right: 'auto', bottom: 'auto', marginRight: '-50%', transform: 'translate(-50%, -50%)', }, }; // Make sure to bind modal to your appElement (https://reactcommunity.org/react-modal/accessibility/) Modal.setAppElement('#yourAppElement'); function App() { let subtitle; const [modalIsOpen, setIsOpen] = React.useState(false); function openModal() { setIsOpen(true); } function afterOpenModal() { // references are now sync'd and can be accessed. subtitle.style.color = '#f00'; } function closeModal() { setIsOpen(false); } return (

(subtitle = _subtitle)}>Hello

I am a modal
); } ReactDOM.render(, appElement); ``` You can find more examples in the `examples` directory, which you can run in a local development server using `npm start` or `yarn run start`. ## Demos There are several demos hosted on [CodePen](https://codepen.io) which demonstrate various features of react-modal: * [Minimal example](https://codepen.io/claydiffrient/pen/KNxgav) * [Using setAppElement](https://codepen.io/claydiffrient/pen/ENegGJ) * [Using onRequestClose](https://codepen.io/claydiffrient/pen/KNjVBx) * [Using shouldCloseOnOverlayClick](https://codepen.io/claydiffrient/pen/woLzwo) * [Using inline styles](https://codepen.io/claydiffrient/pen/ZBmyKz) * [Using CSS classes for styling](https://codepen.io/claydiffrient/pen/KNjVrG) * [Customizing the default styles](https://codepen.io/claydiffrient/pen/pNXgqQ) ================================================ FILE: UPGRADE_GUIDE.md ================================================ Upgrade Guide ============= To see discussion around these API changes, please refer to the [changelog](/CHANGELOG.md) and visit the commits and issues they reference. ================================================ FILE: bower.json ================================================ { "name": "react-modal", "version": "3.11.1", "homepage": "https://github.com/reactjs/react-modal", "authors": [ "Ryan Florence", "Michael Jackson" ], "description": "Accessible modal dialog component for React.JS", "main": "dist/react-modal.js", "keywords": [ "react", "modal", "dialog" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "specs", "modules", "examples", "script", "CONTRIBUTING.md", "karma.conf.js", "package.json" ] } ================================================ FILE: dist/react-modal.js ================================================ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["react","react-dom"],t):"object"==typeof exports?exports.ReactModal=t(require("react"),require("react-dom")):e.ReactModal=t(e.React,e.ReactDOM)}(window,(function(e,t){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=8)}([function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.canUseDOM=t.SafeNodeList=t.SafeHTMLCollection=void 0;var o,r=n(21);var a=((o=r)&&o.__esModule?o:{default:o}).default,i=a.canUseDOM?window.HTMLElement:{};t.SafeHTMLCollection=a.canUseDOM?window.HTMLCollection:{},t.SafeNodeList=a.canUseDOM?window.NodeList:{},t.canUseDOM=a.canUseDOM;t.default=i},function(t,n){t.exports=e},function(e,t,n){var o=n(4);e.exports=n(13)(o.isElement,!0)},function(e,t,n){"use strict";e.exports=n(12)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function e(t){return[].slice.call(t.querySelectorAll("*"),0).reduce((function(t,n){return t.concat(n.shadowRoot?e(n.shadowRoot):[n])}),[]).filter(i)}; /*! * Adapted from jQuery UI core * * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/category/ui-core/ */ var o=/^(input|select|textarea|button|object|iframe)$/;function r(e){var t=e.offsetWidth<=0&&e.offsetHeight<=0;if(t&&!e.innerHTML)return!0;try{var n=window.getComputedStyle(e),o=n.getPropertyValue("display");return t?"contents"!==o&&function(e,t){return"visible"!==t.getPropertyValue("overflow")||e.scrollWidth<=0&&e.scrollHeight<=0}(e,n):"none"===o}catch(e){return console.warn("Failed to inspect element style"),!1}}function a(e,t){var n=e.nodeName.toLowerCase();return(o.test(n)&&!e.disabled||"a"===n&&e.href||t)&&function(e){for(var t=e,n=e.getRootNode&&e.getRootNode();t&&t!==document.body;){if(n&&t===n&&(t=n.host.parentNode),r(t))return!1;t=t.parentNode}return!0}(e)}function i(e){var t=e.getAttribute("tabindex");null===t&&(t=void 0);var n=isNaN(t);return(n||t>=0)&&a(e,!n)}e.exports=t.default},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resetState=function(){l&&(l.removeAttribute?l.removeAttribute("aria-hidden"):null!=l.length?l.forEach((function(e){return e.removeAttribute("aria-hidden")})):document.querySelectorAll(l).forEach((function(e){return e.removeAttribute("aria-hidden")})));l=null},t.log=function(){var e=l||{};console.log("ariaAppHider ----------"),console.log(e.nodeName,e.className,e.id),console.log("end ariaAppHider ----------")},t.assertNodeList=s,t.setElement=function(e){var t=e;if("string"==typeof t&&i.canUseDOM){var n=document.querySelectorAll(t);s(n,t),t=n}return l=t||l},t.validateElement=u,t.hide=function(e){var t=!0,n=!1,o=void 0;try{for(var r,a=u(e)[Symbol.iterator]();!(t=(r=a.next()).done);t=!0){r.value.setAttribute("aria-hidden","true")}}catch(e){n=!0,o=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw o}}},t.show=function(e){var t=!0,n=!1,o=void 0;try{for(var r,a=u(e)[Symbol.iterator]();!(t=(r=a.next()).done);t=!0){r.value.removeAttribute("aria-hidden")}}catch(e){n=!0,o=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw o}}},t.documentNotReadyOrSSRTesting=function(){l=null};var o,r=n(20),a=(o=r)&&o.__esModule?o:{default:o},i=n(1);var l=null;function s(e,t){if(!e||!e.length)throw new Error("react-modal: No elements were found for selector "+t+".")}function u(e){var t=e||l;return t?Array.isArray(t)||t instanceof HTMLCollection||t instanceof NodeList?t:[t]:((0,a.default)(!1,["react-modal: App element is not defined.","Please use `Modal.setAppElement(el)` or set `appElement={el}`.","This is needed so screen readers don't see main content","when modal is opened. It is not recommended, but you can opt-out","by setting `ariaHideApp={false}`."].join(" ")),[])}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.log=function(){console.log("portalOpenInstances ----------"),console.log(r.openInstances.length),r.openInstances.forEach((function(e){return console.log(e)})),console.log("end portalOpenInstances ----------")},t.resetState=function(){r=new o};var o=function e(){var t=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.register=function(e){-1===t.openInstances.indexOf(e)?(t.openInstances.push(e),t.emit("register")):console.warn("React-Modal: Cannot register modal instance that's already open")},this.deregister=function(e){var n=t.openInstances.indexOf(e);-1!==n?(t.openInstances.splice(n,1),t.emit("deregister")):console.warn("React-Modal: Unable to deregister "+e+" as it was never registered")},this.subscribe=function(e){t.subscribers.push(e)},this.emit=function(e){t.subscribers.forEach((function(n){return n(e,t.openInstances.slice())}))},this.openInstances=[],this.subscribers=[]},r=new o;t.default=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o,r=n(9),a=(o=r)&&o.__esModule?o:{default:o};t.default=a.default,e.exports=t.default},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.bodyOpenClassName=t.portalClassName=void 0;var o=Object.assign||function(e){for(var t=1;t>";return new p("Invalid "+r+" `"+a+"` of type `"+function(e){if(!e.constructor||!e.constructor.name)return"<>";return e.constructor.name}(t[n])+"` supplied to `"+o+"`, expected instance of `"+i+"`.")}return null}))},node:d((function(e,t,n,o,r){return m(e[t])?null:new p("Invalid "+o+" `"+r+"` supplied to `"+n+"`, expected a ReactNode.")})),objectOf:function(e){return d((function(t,n,o,r,i){if("function"!=typeof e)return new p("Property `"+i+"` of component `"+o+"` has invalid PropType notation inside objectOf.");var s=t[n],u=h(s);if("object"!==u)return new p("Invalid "+r+" `"+i+"` of type `"+u+"` supplied to `"+o+"`, expected an object.");for(var c in s)if(l(s,c)){var f=e(s,c,o,r,i+"."+c,a);if(f instanceof Error)return f}return null}))},oneOf:function(e){if(!Array.isArray(e))return s(arguments.length>1?"Invalid arguments supplied to oneOf, expected an array, got "+arguments.length+" arguments. A common mistake is to write oneOf(x, y, z) instead of oneOf([x, y, z]).":"Invalid argument supplied to oneOf, expected an array."),u;function t(t,n,o,r,a){for(var i=t[n],l=0;l>",f=f||l,d!==a){if(t){var y=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types");throw y.name="Invariant Violation",y}if("undefined"!=typeof console){var m=u+":"+l;!n[m]&&o<3&&(s("You are manually calling a React.PropTypes validation function for the `"+f+"` prop on `"+u+"`. This is deprecated and will throw in the standalone `prop-types` package. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details."),n[m]=!0,o++)}}return null==i[l]?r?null===i[l]?new p("The "+c+" `"+f+"` is marked as required in `"+u+"`, but its value is `null`."):new p("The "+c+" `"+f+"` is marked as required in `"+u+"`, but its value is `undefined`."):null:e(i,l,u,c,f)}var i=r.bind(null,!1);return i.isRequired=r.bind(null,!0),i}function y(e){return d((function(t,n,o,r,a,i){var l=t[n];return h(l)!==e?new p("Invalid "+r+" `"+a+"` of type `"+v(l)+"` supplied to `"+o+"`, expected `"+e+"`."):null}))}function m(t){switch(typeof t){case"number":case"string":case"undefined":return!0;case"boolean":return!t;case"object":if(Array.isArray(t))return t.every(m);if(null===t||e(t))return!0;var o=function(e){var t=e&&(n&&e[n]||e["@@iterator"]);if("function"==typeof t)return t}(t);if(!o)return!1;var r,a=o.call(t);if(o!==t.entries){for(;!(r=a.next()).done;)if(!m(r.value))return!1}else for(;!(r=a.next()).done;){var i=r.value;if(i&&!m(i[1]))return!1}return!0;default:return!1}}function h(e){var t=typeof e;return Array.isArray(e)?"array":e instanceof RegExp?"object":function(e,t){return"symbol"===e||!!t&&("Symbol"===t["@@toStringTag"]||"function"==typeof Symbol&&t instanceof Symbol)}(t,e)?"symbol":t}function v(e){if(null==e)return""+e;var t=h(e);if("object"===t){if(e instanceof Date)return"date";if(e instanceof RegExp)return"regexp"}return t}function b(e){var t=v(e);switch(t){case"array":case"object":return"an "+t;case"boolean":case"date":case"regexp":return"a "+t;default:return t}}return p.prototype=Error.prototype,c.checkPropTypes=i,c.resetWarningCache=i.resetWarningCache,c.PropTypes=c,c}},function(e,t,n){"use strict"; /* object-assign (c) Sindre Sorhus @license MIT */var o=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var o={};return"abcdefghijklmnopqrst".split("").forEach((function(e){o[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},o)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,l,s=i(e),u=1;u0&&0===(b-=1)&&c.show(t),n.props.shouldFocusAfterRender&&(n.props.shouldReturnFocusAfterClose?(s.returnFocus(n.props.preventScroll),s.teardownScopedFocus()):s.popWithoutFocus()),n.props.onAfterClose&&n.props.onAfterClose(),y.default.deregister(n)},n.open=function(){n.beforeOpen(),n.state.afterOpen&&n.state.beforeClose?(clearTimeout(n.closeTimer),n.setState({beforeClose:!1})):(n.props.shouldFocusAfterRender&&(s.setupScopedFocus(n.node),s.markForFocusLater()),n.setState({isOpen:!0},(function(){n.openAnimationFrame=requestAnimationFrame((function(){n.setState({afterOpen:!0}),n.props.isOpen&&n.props.onAfterOpen&&n.props.onAfterOpen({overlayEl:n.overlay,contentEl:n.content})}))})))},n.close=function(){n.props.closeTimeoutMS>0?n.closeWithTimeout():n.closeWithoutTimeout()},n.focusContent=function(){return n.content&&!n.contentHasFocus()&&n.content.focus({preventScroll:!0})},n.closeWithTimeout=function(){var e=Date.now()+n.props.closeTimeoutMS;n.setState({beforeClose:!0,closesAt:e},(function(){n.closeTimer=setTimeout(n.closeWithoutTimeout,n.state.closesAt-Date.now())}))},n.closeWithoutTimeout=function(){n.setState({beforeClose:!1,isOpen:!1,afterOpen:!1,closesAt:null},n.afterClose)},n.handleKeyDown=function(e){(function(e){return"Tab"===e.code||9===e.keyCode})(e)&&(0,u.default)(n.content,e),n.props.shouldCloseOnEsc&&function(e){return"Escape"===e.code||27===e.keyCode}(e)&&(e.stopPropagation(),n.requestClose(e))},n.handleOverlayOnClick=function(e){null===n.shouldClose&&(n.shouldClose=!0),n.shouldClose&&n.props.shouldCloseOnOverlayClick&&(n.ownerHandlesClose()?n.requestClose(e):n.focusContent()),n.shouldClose=null},n.handleContentOnMouseUp=function(){n.shouldClose=!1},n.handleOverlayOnMouseDown=function(e){n.props.shouldCloseOnOverlayClick||e.target!=n.overlay||e.preventDefault()},n.handleContentOnClick=function(){n.shouldClose=!1},n.handleContentOnMouseDown=function(){n.shouldClose=!1},n.requestClose=function(e){return n.ownerHandlesClose()&&n.props.onRequestClose(e)},n.ownerHandlesClose=function(){return n.props.onRequestClose},n.shouldBeClosed=function(){return!n.state.isOpen&&!n.state.beforeClose},n.contentHasFocus=function(){return document.activeElement===n.content||n.content.contains(document.activeElement)},n.buildClassName=function(e,t){var o="object"===(void 0===t?"undefined":r(t))?t:{base:v[e],afterOpen:v[e]+"--after-open",beforeClose:v[e]+"--before-close"},a=o.base;return n.state.afterOpen&&(a=a+" "+o.afterOpen),n.state.beforeClose&&(a=a+" "+o.beforeClose),"string"==typeof t&&t?a+" "+t:a},n.attributesFromObject=function(e,t){return Object.keys(t).reduce((function(n,o){return n[e+"-"+o]=t[o],n}),{})},n.state={afterOpen:!1,beforeClose:!1},n.shouldClose=null,n.moveFromContentToOverlay=null,n}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(t,e),a(t,[{key:"componentDidMount",value:function(){this.props.isOpen&&this.open()}},{key:"componentDidUpdate",value:function(e,t){e.bodyOpenClassName!==this.props.bodyOpenClassName&&console.warn('React-Modal: "bodyOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),e.htmlOpenClassName!==this.props.htmlOpenClassName&&console.warn('React-Modal: "htmlOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),this.props.isOpen&&!e.isOpen?this.open():!this.props.isOpen&&e.isOpen&&this.close(),this.props.shouldFocusAfterRender&&this.state.isOpen&&!t.isOpen&&this.focusContent()}},{key:"componentWillUnmount",value:function(){this.state.isOpen&&this.afterClose(),clearTimeout(this.closeTimer),cancelAnimationFrame(this.openAnimationFrame)}},{key:"beforeOpen",value:function(){var e=this.props,t=e.appElement,n=e.ariaHideApp,o=e.htmlOpenClassName,r=e.bodyOpenClassName,a=e.parentSelector,i=a&&a().ownerDocument||document;r&&f.add(i.body,r),o&&f.add(i.getElementsByTagName("html")[0],o),n&&(b+=1,c.hide(t)),y.default.register(this)}},{key:"render",value:function(){var e=this.props,t=e.id,n=e.className,r=e.overlayClassName,a=e.defaultStyles,i=e.children,l=n?{}:a.content,s=r?{}:a.overlay;if(this.shouldBeClosed())return null;var u={ref:this.setOverlayRef,className:this.buildClassName("overlay",r),style:o({},s,this.props.style.overlay),onClick:this.handleOverlayOnClick,onMouseDown:this.handleOverlayOnMouseDown},c=o({id:t,ref:this.setContentRef,style:o({},l,this.props.style.content),className:this.buildClassName("content",n),tabIndex:"-1",onKeyDown:this.handleKeyDown,onMouseDown:this.handleContentOnMouseDown,onMouseUp:this.handleContentOnMouseUp,onClick:this.handleContentOnClick,role:this.props.role,"aria-label":this.props.contentLabel},this.attributesFromObject("aria",o({modal:!0},this.props.aria)),this.attributesFromObject("data",this.props.data||{}),{"data-testid":this.props.testId}),f=this.props.contentElement(c,i);return this.props.overlayElement(u,f)}}]),t}(i.Component);g.defaultProps={style:{overlay:{},content:{}},defaultStyles:{}},g.propTypes={isOpen:l.default.bool.isRequired,defaultStyles:l.default.shape({content:l.default.object,overlay:l.default.object}),style:l.default.shape({content:l.default.object,overlay:l.default.object}),className:l.default.oneOfType([l.default.string,l.default.object]),overlayClassName:l.default.oneOfType([l.default.string,l.default.object]),parentSelector:l.default.func,bodyOpenClassName:l.default.string,htmlOpenClassName:l.default.string,ariaHideApp:l.default.bool,appElement:l.default.oneOfType([l.default.instanceOf(d.default),l.default.instanceOf(p.SafeHTMLCollection),l.default.instanceOf(p.SafeNodeList),l.default.arrayOf(l.default.instanceOf(d.default))]),onAfterOpen:l.default.func,onAfterClose:l.default.func,onRequestClose:l.default.func,closeTimeoutMS:l.default.number,shouldFocusAfterRender:l.default.bool,shouldCloseOnOverlayClick:l.default.bool,shouldReturnFocusAfterClose:l.default.bool,preventScroll:l.default.bool,role:l.default.string,contentLabel:l.default.string,aria:l.default.object,data:l.default.object,children:l.default.node,shouldCloseOnEsc:l.default.bool,overlayRef:l.default.func,contentRef:l.default.func,id:l.default.string,overlayElement:l.default.func,contentElement:l.default.func,testId:l.default.string},t.default=g,e.exports=t.default},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.resetState=function(){i=[]},t.log=function(){console.log("focusManager ----------"),i.forEach((function(e){var t=e||{};console.log(t.nodeName,t.className,t.id)})),console.log("end focusManager ----------")},t.handleBlur=u,t.handleFocus=c,t.markForFocusLater=function(){i.push(document.activeElement)},t.returnFocus=function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=null;try{return void(0!==i.length&&(t=i.pop()).focus({preventScroll:e}))}catch(e){console.warn(["You tried to return focus to",t,"but it is not in the DOM anymore"].join(" "))}},t.popWithoutFocus=function(){i.length>0&&i.pop()},t.setupScopedFocus=function(e){l=e,window.addEventListener?(window.addEventListener("blur",u,!1),document.addEventListener("focus",c,!0)):(window.attachEvent("onBlur",u),document.attachEvent("onFocus",c))},t.teardownScopedFocus=function(){l=null,window.addEventListener?(window.removeEventListener("blur",u),document.removeEventListener("focus",c)):(window.detachEvent("onBlur",u),document.detachEvent("onFocus",c))};var o,r=n(5),a=(o=r)&&o.__esModule?o:{default:o};var i=[],l=null,s=!1;function u(){s=!0}function c(){if(s){if(s=!1,!l)return;setTimeout((function(){l.contains(document.activeElement)||((0,a.default)(l)[0]||l).focus()}),0)}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){var n=(0,a.default)(e);if(!n.length)return void t.preventDefault();var o=void 0,r=t.shiftKey,i=n[0],l=n[n.length-1],s=function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document;return t.activeElement.shadowRoot?e(t.activeElement.shadowRoot):t.activeElement}();if(e===s){if(!r)return;o=l}l!==s||r||(o=i);i===s&&r&&(o=l);if(o)return t.preventDefault(),void o.focus();var u=/(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent);if(null==u||"Chrome"==u[1]||null!=/\biPod\b|\biPad\b/g.exec(navigator.userAgent))return;var c=n.indexOf(s);c>-1&&(c+=r?-1:1);if(void 0===(o=n[c]))return t.preventDefault(),void(o=r?l:i).focus();t.preventDefault(),o.focus()};var o,r=n(5),a=(o=r)&&o.__esModule?o:{default:o};e.exports=t.default},function(e,t,n){"use strict";var o=function(){},r=function(e,t){var n=arguments.length;t=new Array(n>1?n-1:0);for(var o=1;o2?o-2:0);for(var a=2;a 0 expected")}a.default.subscribe((function(e,t){i||l||((i=document.createElement("div")).setAttribute("data-react-modal-body-trap",""),i.style.position="absolute",i.style.opacity="0",i.setAttribute("tabindex","0"),i.addEventListener("focus",u),(l=i.cloneNode()).addEventListener("focus",u)),(s=t).length>0?(document.body.firstChild!==i&&document.body.insertBefore(i,document.body.firstChild),document.body.lastChild!==l&&document.body.appendChild(l)):(i.parentElement&&i.parentElement.removeChild(i),l.parentElement&&l.parentElement.removeChild(l))}))},function(e,t,n){"use strict";function o(){var e=this.constructor.getDerivedStateFromProps(this.props,this.state);null!=e&&this.setState(e)}function r(e){this.setState(function(t){var n=this.constructor.getDerivedStateFromProps(e,t);return null!=n?n:null}.bind(this))}function a(e,t){try{var n=this.props,o=this.state;this.props=e,this.state=t,this.__reactInternalSnapshotFlag=!0,this.__reactInternalSnapshot=this.getSnapshotBeforeUpdate(n,o)}finally{this.props=n,this.state=o}}function i(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Error("Can only polyfill class components");if("function"!=typeof e.getDerivedStateFromProps&&"function"!=typeof t.getSnapshotBeforeUpdate)return e;var n=null,i=null,l=null;if("function"==typeof t.componentWillMount?n="componentWillMount":"function"==typeof t.UNSAFE_componentWillMount&&(n="UNSAFE_componentWillMount"),"function"==typeof t.componentWillReceiveProps?i="componentWillReceiveProps":"function"==typeof t.UNSAFE_componentWillReceiveProps&&(i="UNSAFE_componentWillReceiveProps"),"function"==typeof t.componentWillUpdate?l="componentWillUpdate":"function"==typeof t.UNSAFE_componentWillUpdate&&(l="UNSAFE_componentWillUpdate"),null!==n||null!==i||null!==l){var s=e.displayName||e.name,u="function"==typeof e.getDerivedStateFromProps?"getDerivedStateFromProps()":"getSnapshotBeforeUpdate()";throw Error("Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n"+s+" uses "+u+" but also contains the following legacy lifecycles:"+(null!==n?"\n "+n:"")+(null!==i?"\n "+i:"")+(null!==l?"\n "+l:"")+"\n\nThe above lifecycles should be removed. Learn more about this warning here:\nhttps://fb.me/react-async-component-lifecycle-hooks")}if("function"==typeof e.getDerivedStateFromProps&&(t.componentWillMount=o,t.componentWillReceiveProps=r),"function"==typeof t.getSnapshotBeforeUpdate){if("function"!=typeof t.componentDidUpdate)throw new Error("Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype");t.componentWillUpdate=a;var c=t.componentDidUpdate;t.componentDidUpdate=function(e,t,n){var o=this.__reactInternalSnapshotFlag?this.__reactInternalSnapshot:n;c.call(this,e,t,o)}}return e}n.r(t),n.d(t,"polyfill",(function(){return i})),o.__suppressDeprecationWarning=!0,r.__suppressDeprecationWarning=!0,a.__suppressDeprecationWarning=!0}])})); ================================================ FILE: docs/accessibility/index.md ================================================ react-modal aims to be fully accessible, using the [WAI-ARIA](https://www.w3.org/WAI/intro/aria) guidelines to support users of assistive technologies. This page describes some of react-modal's accessibility-oriented features, along with their configuration options. ### [The app element](#app-element) It is important for users of screenreaders that other page content be hidden (via the `aria-hidden` attribute) while the modal is open. To allow react-modal to do this, you should call `Modal.setAppElement` with a query selector identifying the root of your app. For example, if your app content is located inside an element with the ID `root`, you could place the following call somewhere in your code before any modals are opened: ```jsx Modal.setAppElement('#root'); ``` You can also pass a DOM element directly, so that the above example could be rewritten: ```jsx Modal.setAppElement(document.getElementById('root')); ``` Using a selector that matches multiple elements or passing a list of DOM elements will hide all of the elements. Note that this list won't be automatically pruned if elements are removed from the DOM, so you may want to call `Modal.setAppElement` when any such changes are made, or pass a live HTMLCollection as the value. If you are already applying the `aria-hidden` attribute to your app content through other means, you can pass the `ariaHideApp={false}` prop to your modal to avoid getting a warning that your app element is not specified. Using `Modal.setAppElement` will not embed react-modal into your react app as a descendent component. It will just help boost up the app accessiblity. ### [Keyboard navigation](#keyboard) When the modal is opened, it restricts keyboard navigation using the tab key to elements within the modal content. This ensures that elements outside the modal (which are not visible while the modal is open) do not receive focus unexpectedly. By default, when the modal is closed, focus will be restored to the element that was focused before the modal was opened. To disable this behavior, you can pass the `shouldReturnFocusAfterClose={false}` prop to your modal. The modal can be closed using the escape key, unless the `shouldCloseOnEsc={false}` prop is passed. Disabling this behavior may cause accessibility issues for keyboard users, however, so it is not recommended. ### [ARIA attributes](#aria) Besides the `aria-hidden` attribute which is applied to the app element when the modal is shown, there are many other ARIA attributes which you can use to make your app more accessible. A complete list of ARIA attributes can be found in the [ARIA specification](https://www.w3.org/TR/wai-aria-1.1/#state_prop_def). One ARIA attribute is given a dedicated prop by react-modal: you should use the `contentLabel` prop to provide a label for the modal content (via `aria-label`) if there is no visible label on the screen. If the modal is already labeled with visible text, you should specify the element including the label with the `aria-labelledby` attribute using the `aria` prop described below. To pass other ARIA attributes to your modal, you can use the `aria` prop, which accepts an object whose keys are the attributes you want to set (without the leading `aria-` prefix). For example, you could have an alert modal with a title as well as a longer description: ```jsx

Alert

Description goes here.

``` ================================================ FILE: docs/contributing/development.md ================================================ `react-modal` uses `make` to build and publish new versions and documentation. It works as a checklist for the future releases to keep everything updated such as `CHANGELOG.md`, `package.json` and `bower.json` and so on. The minimun works as a normal `npm` scripts. #### [Usage](#usage) Once you clone `react-modal`, you can run `sh bootstrap.sh` to check and download dependencies not managed by `react-modal` such as `gitbook-cli`. It will also show information about the current versions of `node`, `npm`, `yarn` and `jq` available. #### [List of `npm` or `yarn` commands](#npm-yarn-commands) $ npm start $ npm run tests $ npm run lint #### [List of `make` commands](#make-commands) $ make help # show all make commands available $ make deps # npm install $ make serve # start a examples' web server $ make tests # use when developing $ make tests-ci # single run $ make lint # execute lint $ make publish # execute the entire pipeline to publish $ make publish-docs # execute the pipeline for docs ================================================ FILE: docs/contributing/index.md ================================================ ### Commit Subjects If your patch **changes the API or fixes a bug** please use one of the following prefixes in your commit subject: - `[fixed] ...` - `[changed] ...` - `[added] ...` - `[removed] ...` That ensures the subject line of your commit makes it into the auto-generated changelog. Do not use these tags if your change doesn't fix a bug and doesn't change the public API. Commits with changed, added, or removed, must be reviewed by another collaborator. #### When using `[changed]` or `[removed]`... Please include an upgrade path with example code in the commit message. If it doesn't make sense to do this, then it doesn't make sense to use `[changed]` or `[removed]` :) ### Docs Please update the README with any API changes, the code and docs should always be in sync. ### Development - `npm start` runs the dev server to run/develop examples - `npm test` will run the tests. - `scripts/test` same as `npm test` but keeps karma running and watches for changes ### Build Please do not include the output of `scripts/build` in your commits, we only do this when we release. (Also, you probably don't need to build anyway unless you are fixing something around our global build.) ================================================ FILE: docs/examples/css_classes.md ================================================ # Using CSS Classes for Styling If you prefer to use CSS to handle styling the modal you can. One thing to note is that by using the className property you will override all default styles. [CSS classes example](https://codepen.io/claydiffrient/pen/KNjVrG) ================================================ FILE: docs/examples/global_overrides.md ================================================ # Global Overrides 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`. [Global overrides example](https://codepen.io/claydiffrient/pen/pNXgqQ) ================================================ FILE: docs/examples/index.md ================================================ The following sub-sections contain several examples of basic usage, hosted on [CodePen](https://codepen.io). The `examples` directory in the project root also contains some examples which you can run locally. To build and run those examples using a local development server, run either $ npm start or $ yarn start and then point your browser to `localhost:8080`. ================================================ FILE: docs/examples/inline_styles.md ================================================ # Using Inline Styles This example shows how to use inline styles to adjust the modal. [inline styles example](https://codepen.io/claydiffrient/pen/ZBmyKz) ================================================ FILE: docs/examples/minimal.md ================================================ # Minimal This example shows the minimal needed to get React Modal to work. [Minimal example](https://codepen.io/claydiffrient/pen/KNxgav) ================================================ FILE: docs/examples/on_request_close.md ================================================ # onRequestClose Callback This example shows how you can use the `onRequestClose` prop with a function to perform actions when closing. This is especially important for handling closing the modal via the escape key. Also more important if `shouldCloseOnOverlayClick` is set to `true`, when clicked on overlay it calls `onRequestClose`. [onRequestClose example](https://codepen.io/claydiffrient/pen/KNjVBx) ================================================ FILE: docs/examples/set_app_element.md ================================================ # Using setAppElement This example shows how to use setAppElement to properly hide your application from screenreaders and other assistive technologies while the modal is open. You'll notice in this example that the aria-hidden attribute is applied to the #main div rather than the document body. [setAppElement example](https://codepen.io/claydiffrient/pen/ENegGJ) ================================================ FILE: docs/examples/should_close_on_overlay_click.md ================================================ # Using shouldCloseOnOverlayClick When `shouldCloseOnOverlayClick` is `true` (default value for this property), it requires the `onRequestClose` to be defined in order to close the . This is due to the fact that the `react-modal` doesn't store the `isOpen` on its state (only for the internal `portal` (see [ModalPortal.js](https://github.com/reactjs/react-modal/blob/master/src/components/ModalPortal.js)). [disable 'close on overlay click', codepen by claydiffrient](https://codepen.io/claydiffrient/pen/woLzwo) [enable 'close on overlay click', codepen by sbgriffi](https://codepen.io/sbgriffi/pen/WMyBaR) ================================================ FILE: docs/index.md ================================================ # react-modal > Accessible modal dialog component for React.JS 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. ## [Installation](#installation) To install the stable version you can use [npm](https://npmjs.org/) or [yarn](https://yarnpkg.com): $ npm install react-modal $ yarn add react-modal To install react-modal in React CDN app: - Add this CDN script tag after React CDN scripts and before your JS files (for example from [cdnjs](https://cdnjs.com/)): - Use `` tag inside your React CDN app. ## [General Usage](#usage) The only required prop for the modal object is `isOpen`, which indicates whether the modal should be displayed. The following is an example of using react-modal specifying all the possible props and options: ```jsx import ReactModal from 'react-modal'; ``` ## [Using a custom parent node](#custom-parent) By default, the modal portal will be appended to the document's body. You can choose a different parent element by providing a function to the `parentSelector` prop that returns the element to be used: ```jsx document.querySelector('#root')}>

Modal Content.

``` If you do this, please ensure that your [app element](accessibility/#app-element) is set correctly. The app element should not be a parent of the modal, to prevent modal content from being hidden to screenreaders while it is open. ## [Refs](#refs) You can use ref callbacks to get the overlay and content DOM nodes directly: ```jsx (this.overlayRef = node)} contentRef={node => (this.contentRef = node)}>

Modal Content.

``` ## [License](#license) MIT ================================================ FILE: docs/pygments.css ================================================ pre { line-height: 125%; } td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .codehilite .hll { background-color: #ffffcc } .codehilite { background: #f8f8f8; } .codehilite .c { color: #408080; font-style: italic } /* Comment */ .codehilite .err { border: 1px solid #FF0000 } /* Error */ .codehilite .k { color: #008000; font-weight: bold } /* Keyword */ .codehilite .o { color: #666666 } /* Operator */ .codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */ .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */ .codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */ .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */ .codehilite .gd { color: #A00000 } /* Generic.Deleted */ .codehilite .ge { font-style: italic } /* Generic.Emph */ .codehilite .gr { color: #FF0000 } /* Generic.Error */ .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .codehilite .gi { color: #00A000 } /* Generic.Inserted */ .codehilite .go { color: #888888 } /* Generic.Output */ .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .codehilite .gs { font-weight: bold } /* Generic.Strong */ .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .codehilite .gt { color: #0044DD } /* Generic.Traceback */ .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ .codehilite .kp { color: #008000 } /* Keyword.Pseudo */ .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ .codehilite .kt { color: #B00040 } /* Keyword.Type */ .codehilite .m { color: #666666 } /* Literal.Number */ .codehilite .s { color: #BA2121 } /* Literal.String */ .codehilite .na { color: #7D9029 } /* Name.Attribute */ .codehilite .nb { color: #008000 } /* Name.Builtin */ .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ .codehilite .no { color: #880000 } /* Name.Constant */ .codehilite .nd { color: #AA22FF } /* Name.Decorator */ .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */ .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ .codehilite .nf { color: #0000FF } /* Name.Function */ .codehilite .nl { color: #A0A000 } /* Name.Label */ .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ .codehilite .nv { color: #19177C } /* Name.Variable */ .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ .codehilite .w { color: #bbbbbb } /* Text.Whitespace */ .codehilite .mb { color: #666666 } /* Literal.Number.Bin */ .codehilite .mf { color: #666666 } /* Literal.Number.Float */ .codehilite .mh { color: #666666 } /* Literal.Number.Hex */ .codehilite .mi { color: #666666 } /* Literal.Number.Integer */ .codehilite .mo { color: #666666 } /* Literal.Number.Oct */ .codehilite .sa { color: #BA2121 } /* Literal.String.Affix */ .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ .codehilite .sc { color: #BA2121 } /* Literal.String.Char */ .codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */ .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ .codehilite .sx { color: #008000 } /* Literal.String.Other */ .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */ .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ .codehilite .ss { color: #19177C } /* Literal.String.Symbol */ .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ .codehilite .fm { color: #0000FF } /* Name.Function.Magic */ .codehilite .vc { color: #19177C } /* Name.Variable.Class */ .codehilite .vg { color: #19177C } /* Name.Variable.Global */ .codehilite .vi { color: #19177C } /* Name.Variable.Instance */ .codehilite .vm { color: #19177C } /* Name.Variable.Magic */ .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ ================================================ FILE: docs/styles/classes.md ================================================ Sometimes it may be preferable to use CSS classes rather than inline styles. react-modal can be configured to use CSS classes to style the modal content and overlay, as well as the document body and the portal within which the modal is mounted. #### For the content and overlay You can use the `className` and `overlayClassName` props to control the CSS classes that are applied to the modal content and the overlay, respectively. Each of these props may be a single string containing the class name to apply to the component. Alternatively, you may pass an object with the `base`, `afterOpen` and `beforeClose` keys, where the value corresponding to each key is a class name. The `base` class will always be applied to the component, the `afterOpen` class will be applied after the modal has been opened and the `beforeClose` class will be applied after the modal has requested to be closed (e.g. when the user presses the escape key or clicks on the overlay). Please note that the `beforeClose` class will have no effect unless the `closeTimeoutMS` prop is set to a non-zero value, since otherwise the modal will be closed immediately when requested. Thus, if you are using the `afterOpen` and `beforeClose` classes to provide transitions, you may want to set `closeTimeoutMS` to the length (in milliseconds) of your closing transition. If you specify `className`, the [default content styles](index.md) will not be applied. Likewise, if you specify `overlayClassName`, the default overlay styles will not be applied. If no class names are specified for the overlay, the default classes `ReactModal__Overlay`, `ReactModal__Overlay--after-open` and `ReactModal__Overlay--before-close` will be applied; the default classes for the content use the analogous prefix `ReactModal__Content`. Please note that any styles applied using these default classes will not override the default styles as they would if specified using the `className` or `overlayClassName` props. #### For the document.body and html tag You can override the default class that is added to `document.body` when the modal is open by defining a property `bodyOpenClassName`. The `bodyOpenClassName` prop must be a *constant string*; otherwise, we would require a complex system to manage which class name should be added to or removed from `document.body` from which modal (if using multiple modals simultaneously). The default value is `ReactModal__Body--open`. `bodyOpenClassName` when set as `null` doesn't add any class to `document.body`. `bodyOpenClassName` can support adding multiple classes to `document.body` when the modal is open. Add as many class names as you desire, delineated by spaces. One potential application for the body class is to remove scrolling on the body when the modal is open. To do this for all modals (except those that specify a non-default `bodyOpenClassName`), you could use the following CSS: ```CSS .ReactModal__Body--open { overflow: hidden; } ``` You can define a class to be added to the html tag, using the `htmlOpenClassName` attribute, which can be helpeful to stop the page to scroll to the top when open a modal. The default value is `null`. This attribute follows the same rules as `bodyOpenClassName`, it must be a *constant string*; Here is an example that can help preventing this behavior: ```CSS .ReactModal__Body--open, .ReactModal__Html--open { overflow: hidden; } ``` #### For the entire portal To specify a class to be applied to the entire portal, you may use the `portalClassName` prop. By default, there are no styles applied to the portal itself. ================================================ FILE: docs/styles/index.md ================================================ Styles passed into the Modal via the `style` prop are merged with the defaults. The default styles are defined in the `Modal.defaultStyles` object and are shown below. ```jsx ``` You can change the default styles by modifying `Modal.defaultStyles`. Please note that specifying a [CSS class](classes.md) for the overlay or the content will disable the default styles for that component. ================================================ FILE: docs/styles/transitions.md ================================================ Using [CSS classes](classes.md), it is possible to implement transitions for when the modal is opened or closed. By placing the following CSS somewhere in your project's styles, you can make the modal content fade in when it is opened and fade out when it is closed: ```css .ReactModal__Overlay { opacity: 0; transition: opacity 2000ms ease-in-out; } .ReactModal__Overlay--after-open{ opacity: 1; } .ReactModal__Overlay--before-close{ opacity: 0; } ``` The above example will apply the fade transition globally, affecting all modals whose `afterOpen` and `beforeClose` classes have not been set via the `className` prop. To apply the transition to one modal only, you can change the above class names and pass an object to your modal's `className` prop as described in the [previous section](classes.md). In order for the fade transition to work, you need to inform the `` about the transition time required for the animation. Like this ```javascript ``` `closeTimeoutMS` is expressed in milliseconds. The `closeTimeoutMS` value and the value used in CSS or `style` prop passed to `` needs to be the same. 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. Do not conditionally render the ``. Instead of this ```javascript { this.state.showModal && this.toggleModal()} >

Add modal content here

} ``` *Do this* ```javascript { this.toggleModal()} >

Add modal content here

} ``` React Modal has adopted the [stable Portal API](https://reactjs.org/docs/portals.html) as exposed in React 16. 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. ================================================ FILE: docs/testing/index.md ================================================ # Testing When using React Test Utils with this library, here are some things to keep in mind: - You need to set `isOpen={true}` on the modal component for it to render its children. - 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. ================================================ FILE: examples/base.css ================================================ h1, h2, h3 { font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif; font-weight: 200; } /* From http://instructure-react.github.io/library/shared.css */ .padbox { padding: 40px; } .branding { border-bottom: 1px solid hsl(200, 0%, 90%); } .btn:not(:last-child) { margin-right: 20px; } .example:not(:last-child) { margin-bottom: 40px; } ================================================ FILE: examples/basic/app.css ================================================ .ReactModal__Overlay { -webkit-perspective: 600; perspective: 600; opacity: 0; } .ReactModal__Overlay--after-open { opacity: 1; transition: opacity 150ms ease-out; } .ReactModal__Content { -webkit-transform: scale(0.5) rotateX(-30deg); transform: scale(0.5) rotateX(-30deg); } .ReactModal__Content--after-open { -webkit-transform: scale(1) rotateX(0deg); transform: scale(1) rotateX(0deg); transition: all 150ms ease-in; } .ReactModal__Overlay--before-close { opacity: 0; } .ReactModal__Content--before-close { -webkit-transform: scale(0.5) rotateX(30deg); transform: scale(0.5) rotateX(30deg); transition: all 150ms ease-in; } .ReactModal__Body--open, .ReactModal__Html--open { overflow: hidden; } ================================================ FILE: examples/basic/app.js ================================================ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Modal from 'react-modal'; import SimpleUsage from './simple_usage'; import MultipleModals from './multiple_modals'; import Forms from './forms'; import ReactRouter from './react-router'; import NestedModals from './nested_modals'; const appElement = document.getElementById('example'); Modal.setAppElement('#example'); const examples = [ SimpleUsage, Forms, MultipleModals, NestedModals, ReactRouter ]; class App extends Component { render() { return (
{examples.map((example, key) => { const ExampleApp = example.app; return (

{`#${key + 1}. ${example.label}`}

); })}
); } } ReactDOM.render(, appElement); ================================================ FILE: examples/basic/forms/index.js ================================================ import React, { Component } from 'react'; import Modal from 'react-modal'; const MODAL_A = 'modal_a'; const MODAL_B = 'modal_b'; const DEFAULT_TITLE = 'Default title'; class Forms extends Component { constructor(props) { super(props); this.state = { isOpen: false }; } toggleModal = event => { console.log(event); const { isOpen } = this.state; this.setState({ isOpen: !isOpen }); } render() { const { isOpen } = this.state; return (

Forms!

This is a description of what it does: nothing :)

Radio buttons
Checkbox buttons
); } } export default { label: "Modal with forms fields.", app: Forms }; ================================================ FILE: examples/basic/index.html ================================================ Basic Example

react-modal

an accessible React modal dialog component

Fork me on GitHub ================================================ FILE: examples/basic/multiple_modals/index.js ================================================ import React, { Component } from 'react'; import Modal from 'react-modal'; class List extends React.Component { render() { return (
{this.props.items.map((x, i) => (
{x}
))}
); } } class MultipleModals extends Component { constructor(props) { super(props); this.state = { listItemsIsOpen: false, currentItem: -1, loading: false, items: [] }; } toggleModal = event => { event.preventDefault(); if (this.state.listItemsIsOpen) { this.handleModalCloseRequest(); return; } this.setState({ items: [], listItemsIsOpen: true, loading: true }); } handleModalCloseRequest = () => { // opportunity to validate something and keep the modal open even if it // requested to be closed this.setState({ listItemsIsOpen: false, loading: false }); } handleOnAfterOpenModal = () => { // when ready, we can access the available refs. (new Promise((resolve, reject) => { setTimeout(() => resolve(true), 500); })).then(res => { this.setState({ items: [1, 2, 3, 4, 5].map(x => `Item ${x}`), loading: false }); }); } onItemClick = index => event => { this.setState({ currentItem: index }); } cleanCurrentItem = () => { this.setState({ currentItem: -1 }); } render() { const { listItemsIsOpen } = this.state; return (

List of items

{this.state.loading ? (

Loading...

) : ( )}
-1} onRequestClose={this.cleanCurrentItem} aria={{ labelledby: "item_title", describedby: "item_info" }}>

Item: {this.state.items[this.state.currentItem]}

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.

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.

); } } export default { label: "Working with many modal.", app: MultipleModals }; ================================================ FILE: examples/basic/nested_modals/index.js ================================================ import React, { Component } from 'react'; import Modal from 'react-modal'; class Item extends Component { constructor(props) { super(props); this.state = { isOpen: false }; } toggleModal = index => event => { console.log("NESTED MODAL ITEM", event); this.setState({ itemNumber: !this.state.isOpen ? index : null, isOpen: !this.state.isOpen }); }; render() { const { isOpen, itemNumber } = this.state; const { number, index } = this.props; const toggleModal = this.toggleModal(index); return (
{number}

Item: {itemNumber + 1}

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.

); } } class List extends Component { render() { return this.props.items.map((n, index) => ( )); } } class NestedModals extends Component { constructor(props) { super(props); this.state = { isOpen: false, currentItem: -1, loading: false, items: [] }; } toggleModal = event => { event.preventDefault(); console.log("NESTEDMODAL", event); this.setState({ items: [], isOpen: !this.state.isOpen, loading: true }); } handleOnAfterOpenModal = () => { // when ready, we can access the available refs. (new Promise((resolve, reject) => { setTimeout(() => resolve(true), 500); })).then(res => { this.setState({ items: [1, 2, 3, 4, 5].map(x => `Item ${x}`), loading: false }); }); } render() { const { isOpen } = this.state; return (

List of items

{this.state.loading ? (

Loading...

) : ( )}
); } } export default { label: "Working with nested modals.", app: NestedModals }; ================================================ FILE: examples/basic/react-router/index.js ================================================ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import createHistory from 'history/createBrowserHistory'; import { Router, Route, Switch } from 'react-router'; import { Link } from 'react-router-dom'; import Modal from 'react-modal'; const history = createHistory(); const Content = label => () =>

{`Content ${label}`}

; const shouldOpenModal = locationPath => /\bmodal\b/.test(locationPath); const ReactRouterModal = props => ( history.push("/basic")}>
Link A
Link B
); class App extends Component { render() { return (
Modal
); } } export default { label: "react-modal and react-router.", app: App }; ================================================ FILE: examples/basic/simple_usage/index.js ================================================ import React, { Component } from 'react'; import Modal from 'react-modal'; import MyModal from './modal'; const MODAL_A = 'modal_a'; const MODAL_B = 'modal_b'; const DEFAULT_TITLE = 'Default title'; class SimpleUsage extends Component { constructor(props) { super(props); this.state = { title1: DEFAULT_TITLE, currentModal: null }; } toggleModal = key => event => { event.preventDefault(); if (this.state.currentModal) { this.handleModalCloseRequest(); return; } this.setState({ ...this.state, currentModal: key, title1: DEFAULT_TITLE }); } handleModalCloseRequest = () => { // opportunity to validate something and keep the modal open even if it // requested to be closed this.setState({ ...this.state, currentModal: null }); } handleInputChange = e => { let text = e.target.value; if (text == '') { text = DEFAULT_TITLE; } this.setState({ ...this.state, title1: text }); } handleOnAfterOpenModal = () => { // when ready, we can access the available refs. this.heading && (this.heading.style.color = '#F00'); } render() { const { currentModal } = this.state; return (

this.heading = h1}>This is the modal 2!

This is a description of what it does: nothing :)

); } } export default { label: "Working with one modal at a time.", app: SimpleUsage }; ================================================ FILE: examples/basic/simple_usage/modal.js ================================================ import React from 'react'; import Modal from 'react-modal'; export default props => { const { title, isOpen, askToClose, onAfterOpen, onRequestClose, onChangeInput } = props; return (

{title}

I am a modal. Use the first input to change the modal's title.

); } ================================================ FILE: examples/bootstrap/app.css ================================================ .ReactModal__Overlay { -webkit-perspective: 600; perspective: 600; opacity: 0; overflow-x: hidden; overflow-y: auto; background-color: rgba(0, 0, 0, 0.5); } .ReactModal__Overlay--after-open { opacity: 1; transition: opacity 150ms ease-out; } .ReactModal__Content { -webkit-transform: scale(0.5) rotateX(-30deg); transform: scale(0.5) rotateX(-30deg); } .ReactModal__Content--after-open { -webkit-transform: scale(1) rotateX(0deg); transform: scale(1) rotateX(0deg); transition: all 150ms ease-in; } .ReactModal__Overlay--before-close { opacity: 0; } .ReactModal__Content--before-close { -webkit-transform: scale(0.5) rotateX(30deg); transform: scale(0.5) rotateX(30deg); transition: all 150ms ease-in; } .ReactModal__Content.modal-dialog { border: none; background-color: transparent; } ================================================ FILE: examples/bootstrap/app.js ================================================ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Modal from 'react-modal'; var appElement = document.getElementById('example'); Modal.setAppElement(appElement); class App extends Component { constructor(props) { super(props); this.state = { modalIsOpen: false }; } openModal = () => { this.setState({modalIsOpen: true}); } closeModal = () => { this.setState({modalIsOpen: false}); } handleModalCloseRequest = () => { // opportunity to validate something and keep the modal open even if it // requested to be closed this.setState({modalIsOpen: false}); } handleSaveClicked = (e) => { alert('Save button was clicked'); } render() { return (

Modal title

Really long content...

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

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

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

); } } ReactDOM.render(, appElement); ================================================ FILE: examples/bootstrap/index.html ================================================ Bootstrap-Style Example

react-modal

an accessible React modal dialog component

Fork me on GitHub ================================================ FILE: examples/index.html ================================================ Basic Example

react-modal

an accessible React modal dialog component

Fork me on GitHub ================================================ FILE: examples/wc/app.css ================================================ .ReactModal__Overlay { -webkit-perspective: 600; perspective: 600; opacity: 0; overflow-x: hidden; overflow-y: auto; background-color: rgba(0, 0, 0, 0.5); } .ReactModal__Overlay--after-open { opacity: 1; transition: opacity 150ms ease-out; } .ReactModal__Content { -webkit-transform: scale(0.5) rotateX(-30deg); transform: scale(0.5) rotateX(-30deg); } .ReactModal__Content--after-open { -webkit-transform: scale(1) rotateX(0deg); transform: scale(1) rotateX(0deg); transition: all 150ms ease-in; } .ReactModal__Overlay--before-close { opacity: 0; } .ReactModal__Content--before-close { -webkit-transform: scale(0.5) rotateX(30deg); transform: scale(0.5) rotateX(30deg); transition: all 150ms ease-in; } .ReactModal__Content.modal-dialog { border: none; background-color: transparent; } ================================================ FILE: examples/wc/app.js ================================================ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Modal from 'react-modal'; import '@webcomponents/custom-elements/src/native-shim'; var appElement = document.getElementById('example'); Modal.setAppElement(appElement); class App extends Component { constructor(props) { super(props); this.state = { modalIsOpen: false }; } openModal = () => { this.setState({modalIsOpen: true}); } closeModal = () => { this.setState({modalIsOpen: false}); } handleModalCloseRequest = () => { // opportunity to validate something and keep the modal open even if it // requested to be closed this.setState({modalIsOpen: false}); } handleSaveClicked = (e) => { alert('Save button was clicked'); } render() { return (

Modal title

Really long content...

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

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

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

); } } ReactDOM.render(, appElement); class AwesomeButton extends HTMLElement { constructor() { super(); } // this shows with no shadow root connectedCallback() { this.innerHTML = ` `; } } customElements.define("awesome-button", AwesomeButton); ================================================ FILE: examples/wc/index.html ================================================ Bootstrap-Style Example

react-modal

an accessible React modal dialog component

Fork me on GitHub ================================================ FILE: flake.nix ================================================ { description = "react-modal flake"; inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.05"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; inputs = with pkgs; [pkg-config nodejs_22 openssl]; in { devShell = pkgs.mkShell { name = "react-modal"; buildInputs = inputs; shellHook = '' export EDITOR=emacs; export NODE_OPTIONS=--openssl-legacy-provider; export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath[ pkgs.openssl ]}; ''; }; }); } ================================================ FILE: karma.conf.js ================================================ let browsers = ['ChromeHeadless']; let coverageType = 'text'; if (process.env.CONTINUOUS_INTEGRATION) { browsers = ['Firefox']; coverageType = 'lcovonly'; } module.exports = function(config) { config.set({ frameworks: ['mocha'], preprocessors: { './src/*.js': ['coverage'], './src/**/*.js': ['coverage'], './specs/index.js': ['webpack', 'sourcemap'] }, files: ['./specs/index.js'], webpack: require('./scripts/webpack.test.config'), webpackMiddleware: { stats: 'errors-only' }, reporters: ['mocha', 'coverage'], mochaReporter: { showDiff: true }, coverageReporter: { type : coverageType, dir : 'coverage/', subdir: '.' }, port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers, // Increase timeouts to prevent the issue with disconnected tests (https://goo.gl/nstA69) captureTimeout: 4 * 60 * 1000, singleRun: (process.env.CONTINUOUS_INTEGRATION) }); }; ================================================ FILE: mkdocs.yml ================================================ site_name: react-modal documentation site_dir: _book nav: - Overview: index.md - Accessibility: accessibility/index.md - Styles: - Inline Styles: styles/index.md - Classes: styles/classes.md - Transitions: styles/transitions.md - Examples: - Run local: examples/index.md - Minimal: examples/minimal.md - setAppElement: examples/set_app_element.md - shouldCloseOnOverlayClick: examples/should_close_on_overlay_click.md - onRequestClose: examples/on_request_close.md - Global Overrides: examples/global_overrides.md - Inline Styles: examples/inline_styles.md - Css Classes: examples/css_classes.md - Testing: testing/index.md - Contributing: - Overview: contributing/index.md - Development setup: contributing/development.md theme: name: 'material' markdown_extensions: - codehilite extra_css: [pygments.css] ================================================ FILE: package.json ================================================ { "name": "react-modal", "version": "3.16.3", "description": "Accessible modal dialog component for React.JS", "main": "./lib/index.js", "module": "./lib/index.js", "repository": { "type": "git", "url": "https://github.com/reactjs/react-modal.git" }, "homepage": "https://github.com/reactjs/react-modal", "bugs": "https://github.com/reactjs/react-modal/issues", "directories": { "example": "examples" }, "scripts": { "start": "npx webpack-dev-server --config ./scripts/webpack.config.js --inline --host 127.0.0.1 --content-base examples/", "test": "cross-env NODE_ENV=test karma start", "lint": "eslint src/" }, "authors": [ "Ryan Florence" ], "license": "MIT", "devDependencies": { "@webcomponents/custom-elements": "^1.5.0", "babel-cli": "^6.26.0", "babel-core": "^6.25.0", "babel-eslint": "^8.0.1", "babel-loader": "^7.1.2", "babel-plugin-add-module-exports": "^0.2.1", "babel-preset-env": "^1.6.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "coveralls": "^3.1.0", "cross-env": "^5.2.1", "eslint": "^4.8.0", "eslint-config-prettier": "^2.6.0", "eslint-import-resolver-webpack": "^0.9.0", "eslint-plugin-import": "^2.23.2", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^2.3.1", "eslint-plugin-react": "^7.23.2", "istanbul-instrumenter-loader": "^3.0.0", "karma": "^6.3.6", "karma-chrome-launcher": "2.2.0", "karma-coverage": "^2.0.3", "karma-firefox-launcher": "1.0.1", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.1", "karma-sourcemap-loader": "^0.3.8", "karma-webpack": "^2.0.4", "mocha": "^8.4.0", "npm-run-all": "^4.1.1", "prettier": "^1.19.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "should": "^13.1.0", "sinon": "v14.0.2", "uglify-js": "3.1.1", "webpack": "^4.46.0", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.2" }, "dependencies": { "exenv": "^1.2.0", "prop-types": "^15.7.2", "react-lifecycles-compat": "^3.0.0", "warning": "^4.0.3" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19", "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19" }, "tags": [ "react", "modal", "dialog" ], "keywords": [ "react", "react-component", "modal", "dialog" ] } ================================================ FILE: scripts/changelog.py ================================================ # Requires python3 to work since, python 3< does not implement %z. import sys sys.path += ["/Users/diasbruno/.local/lib/python3.7/site-packages"] from datetime import datetime from subprocess import Popen, PIPE import semver import functools # 1: version, 2: date, 3: dashes, 4: entries LOG_ENTRY = """{} {} {} """ head_version = "HEAD" def git_exec(args): p = Popen(" ".join(["git"] + args), shell=True, stdout=PIPE, stderr=PIPE) out, err = p.communicate() return out.decode('utf-8') def git_log(args): return git_exec(["log"] + args) def log_entry(entry): log = entry.split(' ') hash = log[0] log = ' '.join(log[1:]) return "- [%s](../../commit/%s) %s" % (hash, hash, log) def get_tags_date(tag): args = [tag, "-1", '--format="%ad"'] date_time = git_log(args).split('\n')[0] if date_time != '': dt = datetime.strptime(date_time, '%a %b %d %H:%M:%S %Y %z') else: dt = datetime.now() dt = dt.strftime('%a, %d %b %Y %H:%M:%S') return dt def log_in_between_versions(t): (a, b, logs) = t v = b and to_version(b) or head_version dt = get_tags_date(v) header = "{} - {} UTC".format(b or head_version, dt) dashes = ("-" * len(header)) def write_log(acc, log): if log[8:8+7] == 'Release' or log[8:8+7] == 'release': return acc acc.append(log_entry(log)) return acc actual_log = list(functools.reduce(write_log, logs.splitlines(), [])) if len(actual_log) == 0: entries = '-\n\n' else: entries = "\n".join(actual_log) return LOG_ENTRY.format(header, dashes, entries) def adjacents(ls, f, res): if len(ls) == 0: return res first = ls[0] if len(ls) == 1: next = None else: next = ls[1] res.append(f(first, next)) return adjacents(ls[1:], f, res) def to_version(tag): if not tag: return "HEAD" if tag.prerelease: return str(tag) return "v{}".format(tag) def logs_between(base, b): to = to_version(b) between = "{}..{}".format(to_version(base), to) logs = git_log([between, "--format='%h %s'"]) return (base, b, logs) def parse_version(version): if version == 'HEAD': return version if version[0] == 'v': version = version[1:] return semver.parse_version_info(version) def get_all_tags(): lines = git_exec(["tag", "-l"]) versions = map(parse_version, lines.splitlines()) return sorted(versions) def generate_current(): versions = get_all_tags() base = versions[-1] logs = logs_between(base, None) return [log_in_between_versions(logs)] def generate_all(): versions = get_all_tags() log_versions = adjacents(versions, logs_between, []) vs = map(log_in_between_versions, log_versions) return list(vs) if __name__ == "__main__": argc = len(sys.argv) if sys.argv[1] == '-a': # all head_version = sys.argv[2] if argc > 2 else "HEAD" log = generate_all() log.reverse() elif sys.argv[1] == '-c': # current head_version = sys.argv[2] log = generate_current() print("\n".join(log)) ================================================ FILE: scripts/defaultConfig.js ================================================ const path = require('path'); module.exports = { mode: 'development', output: { filename: '[name].js', path: path.resolve(__dirname, './examples/__build__'), publicPath: '/__build__/' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } } ] }, resolve: { alias: { "react-modal": path.resolve(__dirname, "../src") } } }; ================================================ FILE: scripts/repo_status ================================================ #!/bin/bash if [ ! -z "`git status -s`" ]; then echo "Working tree is not clean" git status -s read -p "Proceed? [Y/n] " OK if [[ "$OK" -eq "n" || "$OK" -eq "N" || -z "$OK" ]]; then echo "Stopping publish" exit 1 fi fi ================================================ FILE: scripts/version ================================================ #!/bin/sh JQ=$(which jq) if [[ -z "$JQ" ]]; then echo "jq is missing." fi echo "Current version is: $1" read -p "Bump to: " NEW_VERSION if [[ ! -z "$(git tag -l | grep v${NEW_VERSION})" ]]; then echo "Tag $NEW_VERSION already exists." exit 1 fi FILES="package.json bower.json" for F in $FILES; do $JQ ".version = \"${NEW_VERSION}\"" "$F" > up.json cat up.json > "$F" done rm up.json ================================================ FILE: scripts/webpack.config.js ================================================ const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); const defaultConfig = require('./defaultConfig'); var EXAMPLES_DIR = path.resolve(__dirname, '../examples'); function isDirectory(dir) { return fs.lstatSync(dir).isDirectory(); } function buildEntries() { return fs.readdirSync(EXAMPLES_DIR).reduce(function (entries, dir) { if (dir === 'build') return entries; var isDraft = dir.charAt(0) === '_'; if (!isDraft && isDirectory(path.join(EXAMPLES_DIR, dir))) entries[dir] = path.join(EXAMPLES_DIR, dir, 'app.js'); return entries; }, {}); } module.exports = { ...defaultConfig, entry: buildEntries(), }; ================================================ FILE: scripts/webpack.dist.config.js ================================================ const webpack = require('webpack'); const path = require('path'); const defaultConfig = require('./defaultConfig'); const reactExternal = { root: 'React', commonjs2: 'react', commonjs: 'react', amd: 'react' }; const reactDOMExternal = { root: 'ReactDOM', commonjs2: 'react-dom', commonjs: 'react-dom', amd: 'react-dom' }; module.exports = { ...defaultConfig, mode: 'production', entry: { 'react-modal': path.resolve(__dirname, '../src/index.js'), 'react-modal.min': path.resolve(__dirname, '../src/index.js') }, externals: { 'react': reactExternal, 'react-dom': reactDOMExternal }, output: { filename: '[name].js', chunkFilename: '[id].chunk.js', path: path.resolve(__dirname, '../dist'), publicPath: '/', libraryTarget: 'umd', library: 'ReactModal' }, optimization: { minimize: true }, plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) }) ] }; ================================================ FILE: scripts/webpack.test.config.js ================================================ const path = require('path'); const defaultConfig = require('./defaultConfig'); module.exports = { ...defaultConfig, plugins: [], entry: path.resolve(__dirname, '../specs/index.js'), devtool: 'inline-source-map', module: { ...defaultConfig.module, rules: [ { test: /\.js$/, use: { loader: 'istanbul-instrumenter-loader' }, enforce: 'post', include: path.resolve(__dirname, '../src') }, ...defaultConfig.module.rules ] } }; ================================================ FILE: specs/Modal.events.spec.js ================================================ /* eslint-env mocha */ import React from "react"; import ReactDOM from "react-dom"; import "should"; import sinon from "sinon"; import Modal from "react-modal"; import { moverlay, mcontent, clickAt, mouseDownAt, mouseUpAt, escKeyDown, escKeyDownWithCode, tabKeyDown, tabKeyDownWithCode, withModal, withElementCollector, createHTMLElement } from "./helper"; export default () => { it("should trigger the onAfterOpen callback", () => { const afterOpenCallback = sinon.spy(); withElementCollector(() => { const props = { isOpen: true, onAfterOpen: afterOpenCallback }; const node = createHTMLElement("div"); ReactDOM.render(, node); requestAnimationFrame(() => { afterOpenCallback.called.should.be.ok(); ReactDOM.unmountComponentAtNode(node); }); }); }); it("should call onAfterOpen with overlay and content references", () => { const afterOpenCallback = sinon.spy(); withElementCollector(() => { const props = { isOpen: true, onAfterOpen: afterOpenCallback }; const node = createHTMLElement("div"); const modal = ReactDOM.render(, node); requestAnimationFrame(() => { sinon.assert.calledWith(afterOpenCallback, { overlayEl: modal.portal.overlay, contentEl: modal.portal.content }); ReactDOM.unmountComponentAtNode(node); }); }); }); it("should trigger the onAfterClose callback", () => { const onAfterCloseCallback = sinon.spy(); withModal({ isOpen: true, onAfterClose: onAfterCloseCallback }); onAfterCloseCallback.called.should.be.ok(); }); it("should not trigger onAfterClose callback when unmounting a closed modal", () => { const onAfterCloseCallback = sinon.spy(); withModal({ isOpen: false, onAfterClose: onAfterCloseCallback }); onAfterCloseCallback.called.should.not.be.ok(); }); it("should trigger onAfterClose callback when unmounting an opened modal", () => { const onAfterCloseCallback = sinon.spy(); withModal({ isOpen: true, onAfterClose: onAfterCloseCallback }); onAfterCloseCallback.called.should.be.ok(); }); it("keeps focus inside the modal when child has no tabbable elements", () => { let tabPrevented = false; const props = { isOpen: true }; withModal(props, "hello", modal => { const content = mcontent(modal); document.activeElement.should.be.eql(content); tabKeyDown(content, { preventDefault() { tabPrevented = true; } }); tabPrevented.should.be.eql(true); }); }); it("handles case when child has no tabbable elements", () => { const props = { isOpen: true }; withModal(props, "hello", modal => { const content = mcontent(modal); tabKeyDown(content); document.activeElement.should.be.eql(content); }); }); it("traps tab in the modal on shift + tab", () => { const topButton = ; const bottomButton = ; const modalContent = (
{topButton} {bottomButton}
); const props = { isOpen: true }; withModal(props, modalContent, modal => { const content = mcontent(modal); tabKeyDown(content, { shiftKey: true }); document.activeElement.textContent.should.be.eql("bottom"); }); }); it("traps tab in the modal on shift + tab with KeyboardEvent.code", () => { const topButton = ; const bottomButton = ; const modalContent = (
{topButton} {bottomButton}
); const props = { isOpen: true }; withModal(props, modalContent, modal => { const content = mcontent(modal); tabKeyDownWithCode(content, { shiftKey: true }); document.activeElement.textContent.should.be.eql("bottom"); }); }); describe("shouldCloseOnEsc", () => { context("when true", () => { it("should close on Esc key event", () => { const requestCloseCallback = sinon.spy(); withModal( { isOpen: true, shouldCloseOnEsc: true, onRequestClose: requestCloseCallback }, null, modal => { escKeyDown(mcontent(modal)); requestCloseCallback.called.should.be.ok(); // Check if event is passed to onRequestClose callback. const event = requestCloseCallback.getCall(0).args[0]; event.should.be.ok(); } ); }); it("should close on Esc key event with KeyboardEvent.code", () => { const requestCloseCallback = sinon.spy(); withModal( { isOpen: true, shouldCloseOnEsc: true, onRequestClose: requestCloseCallback }, null, modal => { escKeyDownWithCode(mcontent(modal)); requestCloseCallback.called.should.be.ok(); // Check if event is passed to onRequestClose callback. const event = requestCloseCallback.getCall(0).args[0]; event.should.be.ok(); } ); }); }); context("when false", () => { it("should not close on Esc key event", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnEsc: false, onRequestClose: requestCloseCallback }; withModal(props, null, modal => { escKeyDown(mcontent(modal)); requestCloseCallback.called.should.be.false; }); }); }); }); describe("shouldCloseOnoverlayClick", () => { it("when false, click on overlay should not close", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnOverlayClick: false }; withModal(props, null, modal => { const overlay = moverlay(modal); clickAt(overlay); requestCloseCallback.called.should.not.be.ok(); }); }); it("when true, click on overlay must close", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnOverlayClick: true, onRequestClose: requestCloseCallback }; withModal(props, null, modal => { clickAt(moverlay(modal)); requestCloseCallback.called.should.be.ok(); }); }); it("overlay mouse down and content mouse up, should not close", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnOverlayClick: true, onRequestClose: requestCloseCallback }; withModal(props, null, modal => { mouseDownAt(moverlay(modal)); mouseUpAt(mcontent(modal)); requestCloseCallback.called.should.not.be.ok(); }); }); it("content mouse down and overlay mouse up, should not close", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnOverlayClick: true, onRequestClose: requestCloseCallback }; withModal(props, null, modal => { mouseDownAt(mcontent(modal)); mouseUpAt(moverlay(modal)); requestCloseCallback.called.should.not.be.ok(); }); }); }); it("should not stop event propagation", () => { let hasPropagated = false; const props = { isOpen: true, shouldCloseOnOverlayClick: true }; withModal(props, null, modal => { const propagated = () => (hasPropagated = true); window.addEventListener("click", propagated); const event = new MouseEvent("click", { bubbles: true }); moverlay(modal).dispatchEvent(event); hasPropagated.should.be.ok(); window.removeEventListener("click", propagated); }); }); it("verify event passing on overlay click", () => { const requestCloseCallback = sinon.spy(); const props = { isOpen: true, shouldCloseOnOverlayClick: true, onRequestClose: requestCloseCallback }; withModal(props, null, modal => { // click the overlay clickAt(moverlay(modal), { // Used to test that this was the event received fakeData: "ABC" }); requestCloseCallback.called.should.be.ok(); // Check if event is passed to onRequestClose callback. const event = requestCloseCallback.getCall(0).args[0]; event.should.be.ok(); }); }); it("on nested modals, only the topmost should handle ESC key.", () => { const requestCloseCallback = sinon.spy(); const innerRequestCloseCallback = sinon.spy(); let innerModal = null; let innerModalRef = ref => { innerModal = ref; }; withModal( { isOpen: true, onRequestClose: requestCloseCallback }, Test , () => { const content = mcontent(innerModal); escKeyDown(content); innerRequestCloseCallback.called.should.be.ok(); requestCloseCallback.called.should.not.be.ok(); } ); }); }; ================================================ FILE: specs/Modal.helpers.spec.js ================================================ /* eslint-env mocha */ import "should"; import "@webcomponents/custom-elements/src/native-shim"; import tabbable from "../src/helpers/tabbable"; import "sinon"; export default () => { describe("tabbable", () => { describe("without tabbable descendents", () => { it("returns an empty array", () => { const elem = document.createElement("div"); tabbable(elem).should.deepEqual([]); }); }); describe("with tabbable descendents", () => { let elem; beforeEach(() => { elem = document.createElement("div"); document.body.appendChild(elem); }); afterEach(() => { document.body.removeChild(elem); }); it("includes descendent tabbable inputs", () => { const input = document.createElement("input"); elem.appendChild(input); tabbable(elem).should.containEql(input); }); it("includes tabbable non-input elements", () => { const div = document.createElement("div"); div.tabIndex = 1; elem.appendChild(div); tabbable(elem).should.containEql(div); }); it("includes links with an href", () => { const a = document.createElement("a"); a.href = "foobar"; a.innerHTML = "link"; elem.appendChild(a); tabbable(elem).should.containEql(a); }); it("excludes links without an href or a tabindex", () => { const a = document.createElement("a"); elem.appendChild(a); tabbable(elem).should.not.containEql(a); }); it("excludes descendent inputs if they are not tabbable", () => { const input = document.createElement("input"); input.tabIndex = -1; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("excludes descendent inputs if they are disabled", () => { const input = document.createElement("input"); input.disabled = true; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("excludes descendent inputs if they are not displayed", () => { const input = document.createElement("input"); input.style.display = "none"; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("excludes descendent inputs with 0 width and height", () => { const input = document.createElement("input"); input.style.width = "0"; input.style.height = "0"; input.style.border = "0"; input.style.padding = "0"; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("excludes descendents with hidden parents", () => { const input = document.createElement("input"); elem.style.display = "none"; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("excludes inputs with parents that have zero width and height", () => { const input = document.createElement("input"); elem.style.width = "0"; elem.style.height = "0"; elem.style.overflow = "hidden"; elem.appendChild(input); tabbable(elem).should.not.containEql(input); }); it("includes inputs visible because of overflow == visible", () => { const input = document.createElement("input"); input.style.width = "0"; input.style.height = "0"; input.style.overflow = "visible"; elem.appendChild(input); tabbable(elem).should.containEql(input); }); it("excludes elements with overflow == visible if there is no visible content", () => { const button = document.createElement("button"); button.innerHTML = "You can't see me!"; button.style.display = "none"; button.style.overflow = "visible"; elem.appendChild(button); tabbable(elem).should.not.containEql(button); }); it("excludes elements that contain reserved node names", () => { const button = document.createElement("button"); button.innerHTML = "I am a good button"; elem.appendChild(button); const badButton = document.createElement("bad-button"); badButton.innerHTML = "I am a bad button"; elem.appendChild(badButton); tabbable(elem).should.deepEqual([button]); }); it("includes elements that contain reserved node names with tabindex", () => { const trickButton = document.createElement("trick-button"); trickButton.innerHTML = "I am a good button"; trickButton.tabIndex = '0'; elem.appendChild(trickButton); tabbable(elem).should.deepEqual([trickButton]); }); describe("inside Web Components with shadow dom", () => { let wc; let input; class TestWebComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this.attachShadow({ mode: "open" }); this.style.display = "block"; this.style.width = "100px"; this.style.height = "25px"; } } const registerTestComponent = () => { if (window.customElements.get("test-web-component")) { return; } window.customElements.define("test-web-component", TestWebComponent); }; beforeEach(() => { registerTestComponent(); wc = document.createElement("test-web-component"); input = document.createElement("input"); elem.appendChild(input); document.body.appendChild(wc); wc.shadowRoot.appendChild(elem); }); afterEach(() => { // re-add elem to body for the next afterEach document.body.appendChild(elem); // remove Web Component document.body.removeChild(wc); }); it("includes elements when inside a Shadow DOM", () => { tabbable(elem).should.containEql(input); }); it("excludes elements when hidden inside a Shadow DOM", () => { wc.style.display = "none"; tabbable(elem).should.not.containEql(input); }); }); describe("inside Web Components with no shadow dom", () => { let wc; let button; class ButtonWebComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this.innerHTML = ''; this.style.display = "block"; this.style.width = "100px"; this.style.height = "25px"; } } const registerButtonComponent = () => { if (window.customElements.get("button-web-component")) { return; } window.customElements.define("button-web-component", ButtonWebComponent); }; beforeEach(() => { registerButtonComponent(); wc = document.createElement("button-web-component"); elem.appendChild(wc); }); afterEach(() => { // remove Web Component elem.removeChild(wc); }); it("includes only focusable elements", () => { button = wc.querySelector('button'); tabbable(elem).should.deepEqual([button]); }); }); }); }); }; ================================================ FILE: specs/Modal.spec.js ================================================ /* eslint-env mocha */ import should from "should"; import React, { Component } from "react"; import ReactDOM from "react-dom"; import Modal from "react-modal"; import { setElement as ariaAppSetElement, resetState as ariaAppHiderResetState } from "react-modal/helpers/ariaAppHider"; import { resetState as bodyTrapReset } from "react-modal/helpers/bodyTrap"; import { resetState as classListReset } from "react-modal/helpers/classList"; import { resetState as focusManagerReset } from "react-modal/helpers/focusManager"; import { resetState as portalInstancesReset } from "react-modal/helpers/portalOpenInstances"; import { log, isDocumentWithReactModalOpenClass, isHtmlWithReactModalOpenClass, htmlClassList, contentAttribute, mcontent, moverlay, escKeyDown, withModal, documentClassList, withElementCollector, createHTMLElement } from "./helper"; Modal.setCreateHTMLElement(createHTMLElement); export default () => { beforeEach("check for leaks", () => log("before")); afterEach("clean up", () => ( log("after", true), bodyTrapReset(), classListReset(), focusManagerReset(), portalInstancesReset(), ariaAppHiderResetState() )); it("can be open initially", () => { const props = { isOpen: true }; withModal(props, "hello", modal => { mcontent(modal).should.be.ok(); }); }); it("can be closed initially", () => { const props = {}; withModal(props, "hello", modal => { should(ReactDOM.findDOMNode(mcontent(modal))).not.be.ok(); }); }); it("doesn't render the portal if modal is closed", () => { const props = {}; withModal(props, "hello", modal => { should(ReactDOM.findDOMNode(modal.portal)).not.be.ok(); }); }); it("has default props", () => { withElementCollector(() => { // eslint-disable-next-line react/no-render-return-value const modal = ; const props = modal.props; props.isOpen.should.not.be.ok(); props.ariaHideApp.should.be.ok(); props.closeTimeoutMS.should.be.eql(0); props.shouldFocusAfterRender.should.be.ok(); props.shouldCloseOnOverlayClick.should.be.ok(); props.preventScroll.should.be.false(); }); }); it("accepts appElement as a prop", () => { withElementCollector(() => { const el = createHTMLElement("div"); const props = { isOpen: true, ariaHideApp: true, appElement: el }; withModal(props, null, () => { el.getAttribute("aria-hidden").should.be.eql("true"); }); }); }); it("accepts array of appElement as a prop", () => { withElementCollector(() => { const el1 = createHTMLElement("div"); const el2 = createHTMLElement("div"); const node = createHTMLElement("div"); ReactDOM.render(, node); el1.getAttribute("aria-hidden").should.be.eql("true"); el2.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); }); }); it("renders into the body, not in context", () => { withElementCollector(() => { const node = createHTMLElement("div"); Modal.setAppElement(node); ReactDOM.render(, node); document.body .querySelector(".ReactModalPortal") .parentNode.should.be.eql(document.body); ReactDOM.unmountComponentAtNode(node); }); }); it("allow setting appElement of type string", () => { withElementCollector(() => { const node = createHTMLElement("div"); const appElement = "body"; Modal.setAppElement(appElement); ReactDOM.render(, node); document.body .querySelector(".ReactModalPortal") .parentNode.should.be.eql(document.body); ReactDOM.unmountComponentAtNode(node); }); }); // eslint-disable-next-line max-len it("allow setting appElement of type string matching multiple elements", () => { withElementCollector(() => { const el1 = createHTMLElement("div"); el1.id = "id1"; document.body.appendChild(el1); const el2 = createHTMLElement("div"); el2.id = "id2"; document.body.appendChild(el2); const node = createHTMLElement("div"); const appElement = "#id1, #id2"; Modal.setAppElement(appElement); ReactDOM.render(, node); el1.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); }); }); it("default parentSelector should be document.body.", () => { const props = { isOpen: true }; withModal(props, null, (modal) => { modal.props.parentSelector().should.be.eql(document.body); }); }); it("renders the modal content with a dialog aria role when provided ", () => { const child = "I am a child of Modal, and he has sent me here..."; const props = { isOpen: true, role: "dialog" }; withModal(props, child, (modal) => { contentAttribute(modal, "role").should.be.eql("dialog"); }); }); // eslint-disable-next-line max-len it("renders the modal content with the default aria role when not provided", () => { const child = "I am a child of Modal, and he has sent me here..."; const props = { isOpen: true }; withModal(props, child, modal => { contentAttribute(modal, "role").should.be.eql("dialog"); }); }); it("does not render the aria role when provided role with null", () => { const child = "I am a child of Modal, and he has sent me here..."; const props = { isOpen: true, role: null }; withModal(props, child, modal => { should(contentAttribute(modal, "role")).be.eql(null); }); }); it("sets aria-label based on the contentLabel prop", () => { const child = "I am a child of Modal, and he has sent me here..."; withModal( { isOpen: true, contentLabel: "Special Modal" }, child, modal => { contentAttribute(modal, "aria-label").should.be.eql("Special Modal"); } ); }); it("removes the portal node", () => { const props = { isOpen: true }; withModal(props, "hello"); should(document.querySelector(".ReactModalPortal")).not.be.ok(); }); it("removes the portal node after closeTimeoutMS", done => { const closeTimeoutMS = 100; function checkDOM(count) { const portal = document.querySelectorAll(".ReactModalPortal"); portal.length.should.be.eql(count); } const props = { isOpen: true, closeTimeoutMS }; withModal(props, "hello", () => { checkDOM(1); }); setTimeout(() => { // content is unmounted after specified timeout checkDOM(0); done(); }, closeTimeoutMS); }); it("focuses the modal content by default", () => { const props = { isOpen: true }; withModal(props, null, modal => { document.activeElement.should.be.eql(mcontent(modal)); }); }); it("does not focus modal content if shouldFocusAfterRender is false", () => { withModal( { isOpen: true, shouldFocusAfterRender: false }, null, modal => { document.activeElement.should.not.be.eql(mcontent(modal)); } ); }); it("give back focus to previous element or modal.", done => { withModal( { isOpen: true, className: "modal-a", onRequestClose: function() { done(); } }, null, modalA => { const modalContent = mcontent(modalA); document.activeElement.should.be.eql(modalContent); const modalB = withModal( { isOpen: true, className: "modal-b", onRequestClose() { const modalContent = mcontent(modalB); document.activeElement.should.be.eql(mcontent(modalA)); escKeyDown(modalContent); document.activeElement.should.be.eql(modalContent); } }, null ); escKeyDown(modalContent); } ); }); it("does not steel focus when a descendent is already focused", () => { let content; const input = ( { el && el.focus(); content = el; }} /> ); const props = { isOpen: true }; withModal(props, input, () => { document.activeElement.should.be.eql(content); }); }); it("supports id prop", () => { const props = { isOpen: true, id: "id" }; withModal(props, null, modal => { mcontent(modal) .id .should.be.eql("id"); }); }); it("supports portalClassName", () => { const props = { isOpen: true, portalClassName: "myPortalClass" }; withModal(props, null, modal => { modal.node.className.includes("myPortalClass").should.be.ok(); }); }); it("supports custom className", () => { const props = { isOpen: true, className: "myClass" }; withModal(props, null, modal => { mcontent(modal) .className.includes("myClass") .should.be.ok(); }); }); it("supports custom overlayElement", () => { const overlayElement = (props, contentElement) => (
{contentElement}
); const props = { isOpen: true, overlayElement }; withModal(props, null, modal => { const modalOverlay = moverlay(modal); modalOverlay.id.should.eql("custom"); }); }); it("supports custom contentElement", () => { const contentElement = (props, children) => (
{children}
); const props = { isOpen: true, contentElement }; withModal(props, "hello", modal => { const modalContent = mcontent(modal); modalContent.id.should.eql("custom"); modalContent.textContent.should.be.eql("hello"); }); }); it("supports overlayClassName", () => { const props = { isOpen: true, overlayClassName: "myOverlayClass" }; withModal(props, null, modal => { moverlay(modal) .className.includes("myOverlayClass") .should.be.ok(); }); }); it("overrides content classes with custom object className", () => { withElementCollector(() => { const props = { isOpen: true, className: { base: "myClass", afterOpen: "myClass_after-open", beforeClose: "myClass_before-close" } }; const node = createHTMLElement("div"); const modal = ReactDOM.render(, node); const request = requestAnimationFrame(() => { mcontent(modal).className.should.be.eql("myClass myClass_after-open"); ReactDOM.unmountComponentAtNode(node); }); cancelAnimationFrame(request); }); }); it("overrides overlay classes with custom object overlayClassName", () => { withElementCollector(() => { const props = { isOpen: true, overlayClassName: { base: "myOverlayClass", afterOpen: "myOverlayClass_after-open", beforeClose: "myOverlayClass_before-close" } }; const node = createHTMLElement("div"); const modal = ReactDOM.render(, node); const request = requestAnimationFrame(() => { moverlay(modal).className.should.be.eql( "myOverlayClass myOverlayClass_after-open" ); ReactDOM.unmountComponentAtNode(node); }); cancelAnimationFrame(request); }); }); it("supports overriding react modal open class in document.body.", () => { const props = { isOpen: true, bodyOpenClassName: "custom-modal-open" }; withModal(props, null, () => { (document.body.className.indexOf("custom-modal-open") > -1).should.be.ok(); }); }); it("supports setting react modal open class in .", () => { const props = { isOpen: true, htmlOpenClassName: "custom-modal-open" }; withModal(props, null, () => { isHtmlWithReactModalOpenClass("custom-modal-open").should.be.ok(); }); }); // eslint-disable-next-line max-len it("don't append class to document.body if modal is closed.", () => { const props = { isOpen: false }; withModal(props, null, () => { isDocumentWithReactModalOpenClass().should.not.be.ok(); }); }); // eslint-disable-next-line max-len it("don't append any class to document.body when bodyOpenClassName is null.", () => { const props = { isOpen: true, bodyOpenClassName: null }; withModal(props, null, () => { documentClassList().should.be.empty(); }); }); it("don't append class to if modal is closed.", () => { const props = { isOpen: false, htmlOpenClassName: "custom-modal-open" }; withModal(props, null, () => { isHtmlWithReactModalOpenClass().should.not.be.ok(); }); }); it("append class to document.body if modal is open.", () => { const props = { isOpen: true }; withModal(props, null, () => { isDocumentWithReactModalOpenClass().should.be.ok(); }); }); it("don't append class to if not defined.", () => { const props = { isOpen: true }; withModal(props, null, () => { htmlClassList().should.be.empty(); }); }); // eslint-disable-next-line max-len it("removes class from document.body when unmounted without closing", () => { withModal({ isOpen: true }); isDocumentWithReactModalOpenClass().should.not.be.ok(); }); it("remove class from document.body when no modals opened", () => { const propsA = { isOpen: true }; withModal(propsA, null, () => { isDocumentWithReactModalOpenClass().should.be.ok(); }); const propsB = { isOpen: true }; withModal(propsB, null, () => { isDocumentWithReactModalOpenClass().should.be.ok(); }); isDocumentWithReactModalOpenClass().should.not.be.ok(); isHtmlWithReactModalOpenClass().should.not.be.ok(); }); it("supports adding/removing multiple document.body classes", () => { const props = { isOpen: true, bodyOpenClassName: "A B C" }; withModal(props, null, () => { document.body.classList.contains("A", "B", "C").should.be.ok(); }); document.body.classList.contains("A", "B", "C").should.not.be.ok(); ; }); it("does not remove shared classes if more than one modal is open", () => { const props = { isOpen: true, bodyOpenClassName: "A" }; withModal(props, null, () => { isDocumentWithReactModalOpenClass("A").should.be.ok(); withModal({ isOpen: true, bodyOpenClassName: "A B" }, null, () => { isDocumentWithReactModalOpenClass("A B").should.be.ok(); }); isDocumentWithReactModalOpenClass("A").should.be.ok(); }); isDocumentWithReactModalOpenClass("A").should.not.be.ok(); }); it("should not add classes to document.body for unopened modals", () => { const props = { isOpen: true }; withModal(props, null, () => { isDocumentWithReactModalOpenClass().should.be.ok(); }); withModal({ isOpen: false, bodyOpenClassName: "testBodyClass" }); isDocumentWithReactModalOpenClass("testBodyClass").should.not.be.ok(); }); it("should not remove classes from document.body if modal is closed", () => { const props = { isOpen: true }; withModal(props, null, () => { isDocumentWithReactModalOpenClass().should.be.ok(); withModal({ isOpen: false, bodyOpenClassName: "testBodyClass" }, null, () => { isDocumentWithReactModalOpenClass("testBodyClass").should.not.be.ok(); }); isDocumentWithReactModalOpenClass().should.be.ok(); }); }); it("should not remove classes from if modal is closed", () => { const props = { isOpen: false }; withModal(props, null, () => { isHtmlWithReactModalOpenClass().should.not.be.ok(); withModal({ isOpen: true, htmlOpenClassName: "testHtmlClass" }, null, () => { isHtmlWithReactModalOpenClass("testHtmlClass").should.be.ok(); }); isHtmlWithReactModalOpenClass("testHtmlClass").should.not.be.ok(); }); }); it("additional aria attributes", () => { withModal( { isOpen: true, aria: { labelledby: "a" } }, "hello", modal => mcontent(modal) .getAttribute("aria-labelledby") .should.be.eql("a") ); }); it("additional data attributes", () => { withModal( { isOpen: true, data: { background: "green" } }, "hello", modal => mcontent(modal) .getAttribute("data-background") .should.be.eql("green") ); }); it("additional testId attribute", () => { withModal( { isOpen: true, testId: "foo-bar" }, "hello", modal => mcontent(modal) .getAttribute("data-testid") .should.be.eql("foo-bar") ) }); it("raises an exception if the appElement selector does not match", () => { should(() => ariaAppSetElement(".test")).throw(); }); it("removes aria-hidden from appElement when unmounted w/o closing", () => { withElementCollector(() => { const el = createHTMLElement("div"); const node = createHTMLElement("div"); ReactDOM.render(, node); el.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(node); const request = requestAnimationFrame(() => { should(el.getAttribute('aria-hidden')).not.be.ok(); }); cancelAnimationFrame(request); }); }); // eslint-disable-next-line max-len it("removes aria-hidden when closed and another modal with ariaHideApp set to false is open", () => { withElementCollector(() => { const rootNode = createHTMLElement("div"); const appElement = createHTMLElement("div"); document.body.appendChild(rootNode); document.body.appendChild(appElement); Modal.setAppElement(appElement); const initialState = (
); ReactDOM.render(initialState, rootNode); appElement.getAttribute("aria-hidden").should.be.eql("true"); const updatedState = (
); const request = requestAnimationFrame(() => { ReactDOM.render(updatedState, rootNode); should(appElement.getAttribute("aria-hidden")).not.be.ok(); ReactDOM.unmountComponentAtNode(rootNode); }); cancelAnimationFrame(request); }); }); // eslint-disable-next-line max-len it("maintains aria-hidden when closed and another modal with ariaHideApp set to true is open", () => { withElementCollector(() => { const rootNode = createHTMLElement("div"); document.body.appendChild(rootNode); const appElement = createHTMLElement("div"); document.body.appendChild(appElement); Modal.setAppElement(appElement); const initialState = (
); ReactDOM.render(initialState, rootNode); appElement.getAttribute("aria-hidden").should.be.eql("true"); const updatedState = (
); ReactDOM.render(updatedState, rootNode); appElement.getAttribute("aria-hidden").should.be.eql("true"); ReactDOM.unmountComponentAtNode(rootNode); }); }); // eslint-disable-next-line max-len it("removes aria-hidden when unmounted without close and second modal with ariaHideApp=false is open", () => { withElementCollector(() => { const appElement = createHTMLElement("div"); document.body.appendChild(appElement); Modal.setAppElement(appElement); const propsA = { isOpen: true, ariaHideApp: false, id: "test-2-modal-1" }; withModal(propsA, null, () => { should(appElement.getAttribute("aria-hidden")).not.be.ok(); }); const propsB = { isOpen: true, ariaHideApp: true, id: "test-2-modal-2" }; withModal(propsB, null, () => { appElement.getAttribute("aria-hidden").should.be.eql("true"); }); const request = requestAnimationFrame(() => { should(appElement.getAttribute("aria-hidden")).not.be.ok(); }); cancelAnimationFrame(request); }); }); // eslint-disable-next-line max-len it("maintains aria-hidden when unmounted without close and second modal with ariaHideApp=true is open", () => { withElementCollector(() => { const appElement = createHTMLElement("div"); document.body.appendChild(appElement); Modal.setAppElement(appElement); const check = (tobe) => appElement.getAttribute("aria-hidden").should.be.eql(tobe); const props = { isOpen: true, ariaHideApp: true, id: "test-3-modal-1" }; withModal(props, null, () => { check("true"); withModal({ isOpen: true, ariaHideApp: true, id: "test-3-modal-2" }, null, () => { check("true"); }); check("true"); }); const request = requestAnimationFrame(() => { should(appElement.getAttribute("aria-hidden")).not.be.ok(); }); cancelAnimationFrame(request); }); }); it("adds --after-open for animations", () => { withElementCollector(() => { const rg = /--after-open/i; const props = { isOpen: true }; const node = createHTMLElement("div"); const modal = ReactDOM.render(, node); const request = requestAnimationFrame(() => { const contentName = modal.portal.content.className; const overlayName = modal.portal.overlay.className; rg.test(contentName).should.be.ok(); rg.test(overlayName).should.be.ok(); ReactDOM.unmountComponentAtNode(node); }); cancelAnimationFrame(request); }); }); it("adds --before-close for animations", () => { const closeTimeoutMS = 50; const props = { isOpen: true, closeTimeoutMS }; withModal(props, null, modal => { modal.portal.closeWithTimeout(); const rg = /--before-close/i; rg.test(moverlay(modal).className).should.be.ok(); rg.test(mcontent(modal).className).should.be.ok(); modal.portal.closeWithoutTimeout(); }); }); it("should not be open after close with time out and reopen it", () => { const props = { isOpen: true, closeTimeoutMS: 2000, onRequestClose() { } }; withModal(props, null, modal => { modal.portal.closeWithTimeout(); modal.portal.open(); modal.portal.closeWithoutTimeout(); modal.portal.state.isOpen.should.not.be.ok(); }); }); it("verify default prop of shouldCloseOnOverlayClick", () => { const props = { isOpen: true }; withModal(props, null, modal => { modal.props.shouldCloseOnOverlayClick.should.be.ok(); }); }); it("verify prop of shouldCloseOnOverlayClick", () => { const modalOpts = { isOpen: true, shouldCloseOnOverlayClick: false }; withModal(modalOpts, null, modal => { modal.props.shouldCloseOnOverlayClick.should.not.be.ok(); }); }); it("keeps the modal in the DOM until closeTimeoutMS elapses", done => { function checkDOM(count) { const overlay = document.querySelectorAll(".ReactModal__Overlay"); const content = document.querySelectorAll(".ReactModal__Content"); overlay.length.should.be.eql(count); content.length.should.be.eql(count); } withElementCollector(() => { const closeTimeoutMS = 100; const props = { isOpen: true, closeTimeoutMS }; const node = createHTMLElement("div"); const modal = ReactDOM.render(, node); modal.portal.closeWithTimeout(); checkDOM(1); setTimeout(() => { checkDOM(0); ReactDOM.unmountComponentAtNode(node); done(); }, closeTimeoutMS); }); }); it("verify that portalClassName is refreshed on component update", () => { withElementCollector(() => { const node = createHTMLElement("div"); let modal = null; class App extends Component { constructor(props) { super(props); this.state = { classModifier: "" }; } componentDidMount() { modal.node.className.should.be.eql("portal"); this.setState({ classModifier: "-modifier" }); } componentDidUpdate() { modal.node.className.should.be.eql("portal-modifier"); } render() { const { classModifier } = this.state; const portalClassName = `portal${classModifier}`; return (
{ modal = modalComponent; }} isOpen portalClassName={portalClassName} > Test
); } } Modal.setAppElement(node); ReactDOM.render(, node); ReactDOM.unmountComponentAtNode(node); }); }); it("use overlayRef and contentRef", () => { let overlay = null; let content = null; const props = { isOpen: true, overlayRef: node => (overlay = node), contentRef: node => (content = node) }; withModal(props, null, () => { overlay.should.be.instanceOf(HTMLElement); content.should.be.instanceOf(HTMLElement); overlay.classList.contains("ReactModal__Overlay"); content.classList.contains("ReactModal__Content"); }); }); }; ================================================ FILE: specs/Modal.style.spec.js ================================================ /* eslint-env mocha */ import "should"; import Modal from "react-modal"; import { mcontent, moverlay, withModal } from "./helper"; export default () => { it("overrides the default styles when a custom classname is used", () => { const props = { isOpen: true, className: "myClass" }; withModal(props, null, modal => { mcontent(modal).style.top.should.be.eql(""); }); }); it("overrides the default styles when using custom overlayClassName", () => { const overlayClassName = "myOverlayClass"; const props = { isOpen: true, overlayClassName }; withModal(props, null, modal => { moverlay(modal).style.backgroundColor.should.be.eql(""); }); }); it("supports adding style to the modal contents", () => { const style = { content: { width: "20px" } }; const props = { isOpen: true, style }; withModal(props, null, modal => { mcontent(modal).style.width.should.be.eql("20px"); }); }); it("supports overriding style on the modal contents", () => { const style = { content: { position: "static" } }; const props = { isOpen: true, style }; withModal(props, null, modal => { mcontent(modal).style.position.should.be.eql("static"); }); }); it("supports adding style on the modal overlay", () => { const style = { overlay: { width: "75px" } }; const props = { isOpen: true, style }; withModal(props, null, modal => { moverlay(modal).style.width.should.be.eql("75px"); }); }); it("supports overriding style on the modal overlay", () => { const style = { overlay: { position: "static" } }; const props = { isOpen: true, style }; withModal(props, null, modal => { moverlay(modal).style.position.should.be.eql("static"); }); }); it("supports overriding the default styles", () => { const previousStyle = Modal.defaultStyles.content.position; // Just in case the default style is already relative, // check that we can change it const newStyle = previousStyle === "relative" ? "static" : "relative"; Modal.defaultStyles.content.position = newStyle; const props = { isOpen: true }; withModal(props, null, modal => { modal.portal.content.style.position.should.be.eql(newStyle); Modal.defaultStyles.content.position = previousStyle; }); }); }; ================================================ FILE: specs/Modal.testability.spec.js ================================================ /* eslint-env mocha */ import ReactDOM from "react-dom"; import sinon from "sinon"; import { withModal } from "./helper"; export default () => { it("allows ReactDOM.createPortal to be overridden in real-time", () => { const createPortalSpy = sinon.spy(ReactDOM, "createPortal"); const props = { isOpen: true }; withModal(props, "hello"); createPortalSpy.called.should.be.ok(); ReactDOM.createPortal.restore(); }); }; ================================================ FILE: specs/helper.js ================================================ import React from "react"; import ReactDOM from "react-dom"; import Modal, { bodyOpenClassName } from "../src/components/Modal"; import TestUtils from "react-dom/test-utils"; import { log as classListLog } from "../src/helpers/classList"; import { log as focusManagerLog } from "../src/helpers/focusManager"; import { log as ariaAppLog } from "../src/helpers/ariaAppHider"; import { log as bodyTrapLog } from "../src/helpers/bodyTrap"; import { log as portalInstancesLog } from "../src/helpers/portalOpenInstances"; const debug = false; let i = 0; /** * This log is used to see if there are leaks in between tests. */ export function log(label, spaces) { if (!debug) return; console.log(`${label} -----------------`); console.log(document.body.children.length); const logChildren = c => console.log(c.nodeName, c.className, c.id); document.body.children.forEach(logChildren); ariaAppLog(); bodyTrapLog(); classListLog(); focusManagerLog(); portalInstancesLog(); console.log(`end ${label} -----------------` + (!spaces ? '' : ` `)); } let elementPool = []; /** * Every HTMLElement must be requested using this function... * and inside `withElementCollector`. */ export function createHTMLElement(name) { const e = document.createElement(name); elementPool[elementPool.length - 1].push(e); e.className = `element_pool_${name}-${++i}`; return e; } /** * Remove every element from its parent and release the pool. */ export function drainPool(pool) { pool.forEach(e => e.parentNode && e.parentNode.removeChild(e)); } /** * Every HTMLElement must be requested inside this function... * The reason is that it provides a mechanism that disposes * all the elements (built with `createHTMLElement`) after a test. */ export function withElementCollector(work) { let r; let poolIndex = elementPool.length; elementPool[poolIndex] = []; try { r = work(); } finally { drainPool(elementPool[poolIndex]); elementPool = elementPool.slice( 0, poolIndex ); } return r; } /** * Polyfill for String.includes on some node versions. */ if (!String.prototype.includes) { String.prototype.includes = function(search, start) { if (typeof start !== "number") { start = 0; } if (start + search.length > this.length) { return false; } return this.indexOf(search, start) !== -1; }; } /** * Return the class list object from `document.body`. * @return {Array} */ export const documentClassList = () => document.body.classList; /** * Check if the document.body contains the react modal * open class. * @return {Boolean} */ export const isDocumentWithReactModalOpenClass = ( bodyClass = bodyOpenClassName ) => document.body.className.includes(bodyClass); /** * Return the class list object from . * @return {Array} */ export const htmlClassList = () => document.getElementsByTagName("html")[0].classList; /** * Check if the html contains the react modal * open class. * @return {Boolean} */ export const isHtmlWithReactModalOpenClass = htmlClass => htmlClassList().contains(htmlClass); /** * Returns a rendered dom element by class. * @param {React} element A react instance. * @param {String} className A class to find. * @return {DOMElement} */ export const findDOMWithClass = TestUtils.findRenderedDOMComponentWithClass; /** * Returns an attribut of a rendered react tree. * @param {React} component A react instance. * @return {String} */ const getModalAttribute = component => (instance, attr) => modalComponent(component)(instance).getAttribute(attr); /** * Return an element from a react component. * @param {React} A react instance. * @return {DOMElement} */ const modalComponent = component => instance => instance.portal[component]; /** * Returns the modal content. * @param {Modal} modal Modal instance. * @return {DOMElement} */ export const mcontent = modalComponent("content"); /** * Returns the modal overlay. * @param {Modal} modal Modal instance. * @return {DOMElement} */ export const moverlay = modalComponent("overlay"); /** * Return an attribute of modal content. * @param {Modal} modal Modal instance. * @return {String} */ export const contentAttribute = getModalAttribute("content"); /** * Return an attribute of modal overlay. * @param {Modal} modal Modal instance. * @return {String} */ export const overlayAttribute = getModalAttribute("overlay"); const Simulate = TestUtils.Simulate; const dispatchMockEvent = eventCtor => (key, code) => (element, opts) => eventCtor( element, Object.assign( {}, { key: key, which: code }, code, opts ) ); const dispatchMockKeyDownEvent = dispatchMockEvent(Simulate.keyDown); /** * @deprecated will be replaced by `escKeyDownWithCode` when `react-modal` * drops support for React <18. * * Dispatch an 'esc' key down event using the legacy KeyboardEvent.keyCode. */ export const escKeyDown = dispatchMockKeyDownEvent("ESC", { keyCode: 27 }); /** * Dispatch an 'esc' key down event. */ export const escKeyDownWithCode = dispatchMockKeyDownEvent("ESC", { code: "Escape" }); /** * @deprecated will be replaced by `escKeyDownWithCode` when `react-modal` * drops support for React <18. * * Dispatch a 'tab' key down event using the legacy KeyboardEvent.keyCode. */ export const tabKeyDown = dispatchMockKeyDownEvent("TAB", { keyCode: 9 }); /** * Dispatch a 'tab' key down event. */ export const tabKeyDownWithCode = dispatchMockKeyDownEvent("TAB", { code: "Tab" }); /** * Dispatch a 'click' event at a node. */ export const clickAt = Simulate.click; /** * Dispatch a 'mouse up' event at a node. */ export const mouseUpAt = Simulate.mouseUp; /** * Dispatch a 'mouse down' event at a node. */ export const mouseDownAt = Simulate.mouseDown; export const noop = () => {}; /** * Request a managed modal to run the tests on. * */ export const withModal = function(props, children, test = noop) { return withElementCollector(() => { const node = createHTMLElement(); const modalProps = { ariaHideApp: false, ...props }; let modal; try { ReactDOM.render( (modal = m)} {...modalProps}> {children} , node ); test(modal); } finally { ReactDOM.unmountComponentAtNode(node); } }); }; ================================================ FILE: specs/index.js ================================================ /* eslint-env mocha */ import ModalState from "./Modal.spec"; import ModalEvents from "./Modal.events.spec"; import ModalStyle from "./Modal.style.spec"; import ModalHelpers from "./Modal.helpers.spec"; import ModalTestability from "./Modal.testability.spec"; describe("State", ModalState); describe("Style", ModalStyle); describe("Events", ModalEvents); describe("Helpers", ModalHelpers); describe("Testability", ModalTestability); ================================================ FILE: src/components/Modal.js ================================================ import React, { Component } from "react"; import ReactDOM from "react-dom"; import PropTypes from "prop-types"; import ModalPortal from "./ModalPortal"; import * as ariaAppHider from "../helpers/ariaAppHider"; import SafeHTMLElement, { SafeNodeList, SafeHTMLCollection, canUseDOM } from "../helpers/safeHTMLElement"; import { polyfill } from "react-lifecycles-compat"; export const portalClassName = "ReactModalPortal"; export const bodyOpenClassName = "ReactModal__Body--open"; const isReact16 = canUseDOM && ReactDOM.createPortal !== undefined; let createHTMLElement = name => document.createElement(name); const getCreatePortal = () => isReact16 ? ReactDOM.createPortal : ReactDOM.unstable_renderSubtreeIntoContainer; function getParentElement(parentSelector) { return parentSelector(); } class Modal extends Component { static setAppElement(element) { ariaAppHider.setElement(element); } /* eslint-disable react/no-unused-prop-types */ static propTypes = { isOpen: PropTypes.bool.isRequired, style: PropTypes.shape({ content: PropTypes.object, overlay: PropTypes.object }), portalClassName: PropTypes.string, bodyOpenClassName: PropTypes.string, htmlOpenClassName: PropTypes.string, className: PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ base: PropTypes.string.isRequired, afterOpen: PropTypes.string.isRequired, beforeClose: PropTypes.string.isRequired }) ]), overlayClassName: PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ base: PropTypes.string.isRequired, afterOpen: PropTypes.string.isRequired, beforeClose: PropTypes.string.isRequired }) ]), appElement: PropTypes.oneOfType([ PropTypes.instanceOf(SafeHTMLElement), PropTypes.instanceOf(SafeHTMLCollection), PropTypes.instanceOf(SafeNodeList), PropTypes.arrayOf(PropTypes.instanceOf(SafeHTMLElement)) ]), onAfterOpen: PropTypes.func, onRequestClose: PropTypes.func, closeTimeoutMS: PropTypes.number, ariaHideApp: PropTypes.bool, shouldFocusAfterRender: PropTypes.bool, shouldCloseOnOverlayClick: PropTypes.bool, shouldReturnFocusAfterClose: PropTypes.bool, preventScroll: PropTypes.bool, parentSelector: PropTypes.func, aria: PropTypes.object, data: PropTypes.object, role: PropTypes.string, contentLabel: PropTypes.string, shouldCloseOnEsc: PropTypes.bool, overlayRef: PropTypes.func, contentRef: PropTypes.func, id: PropTypes.string, overlayElement: PropTypes.func, contentElement: PropTypes.func }; /* eslint-enable react/no-unused-prop-types */ static defaultProps = { isOpen: false, portalClassName, bodyOpenClassName, role: "dialog", ariaHideApp: true, closeTimeoutMS: 0, shouldFocusAfterRender: true, shouldCloseOnEsc: true, shouldCloseOnOverlayClick: true, shouldReturnFocusAfterClose: true, preventScroll: false, parentSelector: () => document.body, overlayElement: (props, contentEl) =>
{contentEl}
, contentElement: (props, children) =>
{children}
}; static defaultStyles = { overlay: { position: "fixed", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "rgba(255, 255, 255, 0.75)" }, content: { position: "absolute", top: "40px", left: "40px", right: "40px", bottom: "40px", border: "1px solid #ccc", background: "#fff", overflow: "auto", WebkitOverflowScrolling: "touch", borderRadius: "4px", outline: "none", padding: "20px" } }; constructor(props) { super(props); this.removePortalTimer = null; } componentDidMount() { if (!canUseDOM) return; clearTimeout(this.removePortalTimer); if (!isReact16) { this.node = createHTMLElement("div"); } this.node.className = this.props.portalClassName; const parent = getParentElement(this.props.parentSelector); parent.appendChild(this.node); !isReact16 && this.renderPortal(this.props); } getSnapshotBeforeUpdate(prevProps) { const prevParent = getParentElement(prevProps.parentSelector); const nextParent = getParentElement(this.props.parentSelector); return { prevParent, nextParent }; } componentDidUpdate(prevProps, _, snapshot) { if (!canUseDOM) return; const { isOpen, portalClassName } = this.props; if (prevProps.portalClassName !== portalClassName) { this.node.className = portalClassName; } const { prevParent, nextParent } = snapshot; if (nextParent !== prevParent) { prevParent.removeChild(this.node); nextParent.appendChild(this.node); } // Stop unnecessary renders if modal is remaining closed if (!prevProps.isOpen && !isOpen) return; !isReact16 && this.renderPortal(this.props); } componentWillUnmount() { if (!canUseDOM || !this.node || !this.portal) return; const state = this.portal.state; const now = Date.now(); const closesAt = state.isOpen && this.props.closeTimeoutMS && (state.closesAt || now + this.props.closeTimeoutMS); if (closesAt) { if (!state.beforeClose) { this.portal.closeWithTimeout(); } this.removePortalTimer = setTimeout(this.removePortal, closesAt - now); } else { this.removePortal(); } } removePortal = () => { !isReact16 && ReactDOM.unmountComponentAtNode(this.node); const parent = getParentElement(this.props.parentSelector); if (parent && parent.contains(this.node)) { parent.removeChild(this.node); } else { // eslint-disable-next-line no-console console.warn( 'React-Modal: "parentSelector" prop did not returned any DOM ' + "element. Make sure that the parent element is unmounted to " + "avoid any memory leaks." ); } }; portalRef = ref => { this.portal = ref; }; renderPortal = props => { const createPortal = getCreatePortal(); const portal = createPortal( this, , this.node ); this.portalRef(portal); }; render() { if (!canUseDOM || !isReact16) { return null; } if (!this.node && isReact16) { this.node = createHTMLElement("div"); } const createPortal = getCreatePortal(); return createPortal( , this.node ); } } polyfill(Modal); if (process.env.NODE_ENV !== "production") { Modal.setCreateHTMLElement = fn => (createHTMLElement = fn); } export default Modal; ================================================ FILE: src/components/ModalPortal.js ================================================ import { Component } from "react"; import PropTypes from "prop-types"; import * as focusManager from "../helpers/focusManager"; import scopeTab from "../helpers/scopeTab"; import * as ariaAppHider from "../helpers/ariaAppHider"; import * as classList from "../helpers/classList"; import SafeHTMLElement, { SafeHTMLCollection, SafeNodeList } from "../helpers/safeHTMLElement"; import portalOpenInstances from "../helpers/portalOpenInstances"; import "../helpers/bodyTrap"; // so that our CSS is statically analyzable const CLASS_NAMES = { overlay: "ReactModal__Overlay", content: "ReactModal__Content" }; /** * We need to support the deprecated `KeyboardEvent.keyCode` in addition to * `KeyboardEvent.code` for apps that still support IE11. Can be removed when * `react-modal` only supports React >18 (which dropped IE support). */ const isTabKey = event => event.code === "Tab" || event.keyCode === 9; const isEscKey = event => event.code === "Escape" || event.keyCode === 27; let ariaHiddenInstances = 0; export default class ModalPortal extends Component { static defaultProps = { style: { overlay: {}, content: {} }, defaultStyles: {} }; static propTypes = { isOpen: PropTypes.bool.isRequired, defaultStyles: PropTypes.shape({ content: PropTypes.object, overlay: PropTypes.object }), style: PropTypes.shape({ content: PropTypes.object, overlay: PropTypes.object }), className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), overlayClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), parentSelector: PropTypes.func, bodyOpenClassName: PropTypes.string, htmlOpenClassName: PropTypes.string, ariaHideApp: PropTypes.bool, appElement: PropTypes.oneOfType([ PropTypes.instanceOf(SafeHTMLElement), PropTypes.instanceOf(SafeHTMLCollection), PropTypes.instanceOf(SafeNodeList), PropTypes.arrayOf(PropTypes.instanceOf(SafeHTMLElement)) ]), onAfterOpen: PropTypes.func, onAfterClose: PropTypes.func, onRequestClose: PropTypes.func, closeTimeoutMS: PropTypes.number, shouldFocusAfterRender: PropTypes.bool, shouldCloseOnOverlayClick: PropTypes.bool, shouldReturnFocusAfterClose: PropTypes.bool, preventScroll: PropTypes.bool, role: PropTypes.string, contentLabel: PropTypes.string, aria: PropTypes.object, data: PropTypes.object, children: PropTypes.node, shouldCloseOnEsc: PropTypes.bool, overlayRef: PropTypes.func, contentRef: PropTypes.func, id: PropTypes.string, overlayElement: PropTypes.func, contentElement: PropTypes.func, testId: PropTypes.string }; constructor(props) { super(props); this.state = { afterOpen: false, beforeClose: false }; this.shouldClose = null; this.moveFromContentToOverlay = null; } componentDidMount() { if (this.props.isOpen) { this.open(); } } componentDidUpdate(prevProps, prevState) { if (process.env.NODE_ENV !== "production") { if (prevProps.bodyOpenClassName !== this.props.bodyOpenClassName) { // eslint-disable-next-line no-console console.warn( 'React-Modal: "bodyOpenClassName" prop has been modified. ' + "This may cause unexpected behavior when multiple modals are open." ); } if (prevProps.htmlOpenClassName !== this.props.htmlOpenClassName) { // eslint-disable-next-line no-console console.warn( 'React-Modal: "htmlOpenClassName" prop has been modified. ' + "This may cause unexpected behavior when multiple modals are open." ); } } if (this.props.isOpen && !prevProps.isOpen) { this.open(); } else if (!this.props.isOpen && prevProps.isOpen) { this.close(); } // Focus only needs to be set once when the modal is being opened if ( this.props.shouldFocusAfterRender && this.state.isOpen && !prevState.isOpen ) { this.focusContent(); } } componentWillUnmount() { if (this.state.isOpen) { this.afterClose(); } clearTimeout(this.closeTimer); cancelAnimationFrame(this.openAnimationFrame); } setOverlayRef = overlay => { this.overlay = overlay; this.props.overlayRef && this.props.overlayRef(overlay); }; setContentRef = content => { this.content = content; this.props.contentRef && this.props.contentRef(content); }; beforeOpen() { const { appElement, ariaHideApp, htmlOpenClassName, bodyOpenClassName, parentSelector } = this.props; const parentDocument = (parentSelector && parentSelector().ownerDocument) || document; // Add classes. bodyOpenClassName && classList.add(parentDocument.body, bodyOpenClassName); htmlOpenClassName && classList.add( parentDocument.getElementsByTagName("html")[0], htmlOpenClassName ); if (ariaHideApp) { ariaHiddenInstances += 1; ariaAppHider.hide(appElement); } portalOpenInstances.register(this); } afterClose = () => { const { appElement, ariaHideApp, htmlOpenClassName, bodyOpenClassName, parentSelector } = this.props; const parentDocument = (parentSelector && parentSelector().ownerDocument) || document; // Remove classes. bodyOpenClassName && classList.remove(parentDocument.body, bodyOpenClassName); htmlOpenClassName && classList.remove( parentDocument.getElementsByTagName("html")[0], htmlOpenClassName ); // Reset aria-hidden attribute if all modals have been removed if (ariaHideApp && ariaHiddenInstances > 0) { ariaHiddenInstances -= 1; if (ariaHiddenInstances === 0) { ariaAppHider.show(appElement); } } if (this.props.shouldFocusAfterRender) { if (this.props.shouldReturnFocusAfterClose) { focusManager.returnFocus(this.props.preventScroll); focusManager.teardownScopedFocus(); } else { focusManager.popWithoutFocus(); } } if (this.props.onAfterClose) { this.props.onAfterClose(); } portalOpenInstances.deregister(this); }; open = () => { this.beforeOpen(); if (this.state.afterOpen && this.state.beforeClose) { clearTimeout(this.closeTimer); this.setState({ beforeClose: false }); } else { if (this.props.shouldFocusAfterRender) { focusManager.setupScopedFocus(this.node); focusManager.markForFocusLater(); } this.setState({ isOpen: true }, () => { this.openAnimationFrame = requestAnimationFrame(() => { this.setState({ afterOpen: true }); if (this.props.isOpen && this.props.onAfterOpen) { this.props.onAfterOpen({ overlayEl: this.overlay, contentEl: this.content }); } }); }); } }; close = () => { if (this.props.closeTimeoutMS > 0) { this.closeWithTimeout(); } else { this.closeWithoutTimeout(); } }; // Don't steal focus from inner elements focusContent = () => this.content && !this.contentHasFocus() && this.content.focus({ preventScroll: true }); closeWithTimeout = () => { const closesAt = Date.now() + this.props.closeTimeoutMS; this.setState({ beforeClose: true, closesAt }, () => { this.closeTimer = setTimeout( this.closeWithoutTimeout, this.state.closesAt - Date.now() ); }); }; closeWithoutTimeout = () => { this.setState( { beforeClose: false, isOpen: false, afterOpen: false, closesAt: null }, this.afterClose ); }; handleKeyDown = event => { if (isTabKey(event)) { scopeTab(this.content, event); } if (this.props.shouldCloseOnEsc && isEscKey(event)) { event.stopPropagation(); this.requestClose(event); } }; handleOverlayOnClick = event => { if (this.shouldClose === null) { this.shouldClose = true; } if (this.shouldClose && this.props.shouldCloseOnOverlayClick) { if (this.ownerHandlesClose()) { this.requestClose(event); } else { this.focusContent(); } } this.shouldClose = null; }; handleContentOnMouseUp = () => { this.shouldClose = false; }; handleOverlayOnMouseDown = event => { if (!this.props.shouldCloseOnOverlayClick && event.target == this.overlay) { event.preventDefault(); } }; handleContentOnClick = () => { this.shouldClose = false; }; handleContentOnMouseDown = () => { this.shouldClose = false; }; requestClose = event => this.ownerHandlesClose() && this.props.onRequestClose(event); ownerHandlesClose = () => this.props.onRequestClose; shouldBeClosed = () => !this.state.isOpen && !this.state.beforeClose; contentHasFocus = () => document.activeElement === this.content || this.content.contains(document.activeElement); buildClassName = (which, additional) => { const classNames = typeof additional === "object" ? additional : { base: CLASS_NAMES[which], afterOpen: `${CLASS_NAMES[which]}--after-open`, beforeClose: `${CLASS_NAMES[which]}--before-close` }; let className = classNames.base; if (this.state.afterOpen) { className = `${className} ${classNames.afterOpen}`; } if (this.state.beforeClose) { className = `${className} ${classNames.beforeClose}`; } return typeof additional === "string" && additional ? `${className} ${additional}` : className; }; attributesFromObject = (prefix, items) => Object.keys(items).reduce((acc, name) => { acc[`${prefix}-${name}`] = items[name]; return acc; }, {}); render() { const { id, className, overlayClassName, defaultStyles, children } = this.props; const contentStyles = className ? {} : defaultStyles.content; const overlayStyles = overlayClassName ? {} : defaultStyles.overlay; if (this.shouldBeClosed()) { return null; } const overlayProps = { ref: this.setOverlayRef, className: this.buildClassName("overlay", overlayClassName), style: { ...overlayStyles, ...this.props.style.overlay }, onClick: this.handleOverlayOnClick, onMouseDown: this.handleOverlayOnMouseDown }; const contentProps = { id, ref: this.setContentRef, style: { ...contentStyles, ...this.props.style.content }, className: this.buildClassName("content", className), tabIndex: "-1", onKeyDown: this.handleKeyDown, onMouseDown: this.handleContentOnMouseDown, onMouseUp: this.handleContentOnMouseUp, onClick: this.handleContentOnClick, role: this.props.role, "aria-label": this.props.contentLabel, ...this.attributesFromObject("aria", { modal: true, ...this.props.aria }), ...this.attributesFromObject("data", this.props.data || {}), "data-testid": this.props.testId }; const contentElement = this.props.contentElement(contentProps, children); return this.props.overlayElement(overlayProps, contentElement); } } ================================================ FILE: src/helpers/ariaAppHider.js ================================================ import warning from "warning"; import { canUseDOM } from "./safeHTMLElement"; let globalElement = null; /* eslint-disable no-console */ /* istanbul ignore next */ export function resetState() { if (globalElement) { if (globalElement.removeAttribute) { globalElement.removeAttribute("aria-hidden"); } else if (globalElement.length != null) { globalElement.forEach(element => element.removeAttribute("aria-hidden")); } else { document .querySelectorAll(globalElement) .forEach(element => element.removeAttribute("aria-hidden")); } } globalElement = null; } /* istanbul ignore next */ export function log() { if (process.env.NODE_ENV !== "production") { var check = globalElement || {}; console.log("ariaAppHider ----------"); console.log(check.nodeName, check.className, check.id); console.log("end ariaAppHider ----------"); } } /* eslint-enable no-console */ export function assertNodeList(nodeList, selector) { if (!nodeList || !nodeList.length) { throw new Error( `react-modal: No elements were found for selector ${selector}.` ); } } export function setElement(element) { let useElement = element; if (typeof useElement === "string" && canUseDOM) { const el = document.querySelectorAll(useElement); assertNodeList(el, useElement); useElement = el; } globalElement = useElement || globalElement; return globalElement; } export function validateElement(appElement) { const el = appElement || globalElement; if (el) { return Array.isArray(el) || el instanceof HTMLCollection || el instanceof NodeList ? el : [el]; } else { warning( false, [ "react-modal: App element is not defined.", "Please use `Modal.setAppElement(el)` or set `appElement={el}`.", "This is needed so screen readers don't see main content", "when modal is opened. It is not recommended, but you can opt-out", "by setting `ariaHideApp={false}`." ].join(" ") ); return []; } } export function hide(appElement) { for (let el of validateElement(appElement)) { el.setAttribute("aria-hidden", "true"); } } export function show(appElement) { for (let el of validateElement(appElement)) { el.removeAttribute("aria-hidden"); } } export function documentNotReadyOrSSRTesting() { globalElement = null; } ================================================ FILE: src/helpers/bodyTrap.js ================================================ import portalOpenInstances from "./portalOpenInstances"; // Body focus trap see Issue #742 let before, after, instances = []; /* eslint-disable no-console */ /* istanbul ignore next */ export function resetState() { for (let item of [before, after]) { if (!item) continue; item.parentNode && item.parentNode.removeChild(item); } before = after = null; instances = []; } /* istanbul ignore next */ export function log() { console.log("bodyTrap ----------"); console.log(instances.length); for (let item of [before, after]) { let check = item || {}; console.log(check.nodeName, check.className, check.id); } console.log("edn bodyTrap ----------"); } /* eslint-enable no-console */ function focusContent() { if (instances.length === 0) { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.warn(`React-Modal: Open instances > 0 expected`); } return; } instances[instances.length - 1].focusContent(); } function bodyTrap(eventType, openInstances) { if (!before && !after) { before = document.createElement("div"); before.setAttribute("data-react-modal-body-trap", ""); before.style.position = "absolute"; before.style.opacity = "0"; before.setAttribute("tabindex", "0"); before.addEventListener("focus", focusContent); after = before.cloneNode(); after.addEventListener("focus", focusContent); } instances = openInstances; if (instances.length > 0) { // Add focus trap if (document.body.firstChild !== before) { document.body.insertBefore(before, document.body.firstChild); } if (document.body.lastChild !== after) { document.body.appendChild(after); } } else { // Remove focus trap if (before.parentElement) { before.parentElement.removeChild(before); } if (after.parentElement) { after.parentElement.removeChild(after); } } } portalOpenInstances.subscribe(bodyTrap); ================================================ FILE: src/helpers/classList.js ================================================ let htmlClassList = {}; let docBodyClassList = {}; /* eslint-disable no-console */ /* istanbul ignore next */ function removeClass(at, cls) { at.classList.remove(cls); } /* istanbul ignore next */ export function resetState() { const htmlElement = document.getElementsByTagName("html")[0]; for (let cls in htmlClassList) { removeClass(htmlElement, htmlClassList[cls]); } const body = document.body; for (let cls in docBodyClassList) { removeClass(body, docBodyClassList[cls]); } htmlClassList = {}; docBodyClassList = {}; } /* istanbul ignore next */ export function log() { if (process.env.NODE_ENV !== "production") { let classes = document.getElementsByTagName("html")[0].className; let buffer = "Show tracked classes:\n\n"; buffer += ` (${classes}): `; for (let x in htmlClassList) { buffer += ` ${x} ${htmlClassList[x]} `; } classes = document.body.className; buffer += `\n\ndoc.body (${classes}): `; for (let x in docBodyClassList) { buffer += ` ${x} ${docBodyClassList[x]} `; } buffer += "\n"; console.log(buffer); } } /* eslint-enable no-console */ /** * Track the number of reference of a class. * @param {object} poll The poll to receive the reference. * @param {string} className The class name. * @return {string} */ const incrementReference = (poll, className) => { if (!poll[className]) { poll[className] = 0; } poll[className] += 1; return className; }; /** * Drop the reference of a class. * @param {object} poll The poll to receive the reference. * @param {string} className The class name. * @return {string} */ const decrementReference = (poll, className) => { if (poll[className]) { poll[className] -= 1; } return className; }; /** * Track a class and add to the given class list. * @param {Object} classListRef A class list of an element. * @param {Object} poll The poll to be used. * @param {Array} classes The list of classes to be tracked. */ const trackClass = (classListRef, poll, classes) => { classes.forEach(className => { incrementReference(poll, className); classListRef.add(className); }); }; /** * Untrack a class and remove from the given class list if the reference * reaches 0. * @param {Object} classListRef A class list of an element. * @param {Object} poll The poll to be used. * @param {Array} classes The list of classes to be untracked. */ const untrackClass = (classListRef, poll, classes) => { classes.forEach(className => { decrementReference(poll, className); poll[className] === 0 && classListRef.remove(className); }); }; /** * Public inferface to add classes to the document.body. * @param {string} bodyClass The class string to be added. * It may contain more then one class * with ' ' as separator. */ export const add = (element, classString) => trackClass( element.classList, element.nodeName.toLowerCase() == "html" ? htmlClassList : docBodyClassList, classString.split(" ") ); /** * Public inferface to remove classes from the document.body. * @param {string} bodyClass The class string to be added. * It may contain more then one class * with ' ' as separator. */ export const remove = (element, classString) => untrackClass( element.classList, element.nodeName.toLowerCase() == "html" ? htmlClassList : docBodyClassList, classString.split(" ") ); ================================================ FILE: src/helpers/focusManager.js ================================================ import findTabbable from "../helpers/tabbable"; let focusLaterElements = []; let modalElement = null; let needToFocus = false; /* eslint-disable no-console */ /* istanbul ignore next */ export function resetState() { focusLaterElements = []; } /* istanbul ignore next */ export function log() { if (process.env.NODE_ENV !== "production") { console.log("focusManager ----------"); focusLaterElements.forEach(f => { const check = f || {}; console.log(check.nodeName, check.className, check.id); }); console.log("end focusManager ----------"); } } /* eslint-enable no-console */ export function handleBlur() { needToFocus = true; } export function handleFocus() { if (needToFocus) { needToFocus = false; if (!modalElement) { return; } // need to see how jQuery shims document.on('focusin') so we don't need the // setTimeout, firefox doesn't support focusin, if it did, we could focus // the element outside of a setTimeout. Side-effect of this implementation // is that the document.body gets focus, and then we focus our element right // after, seems fine. setTimeout(() => { if (modalElement.contains(document.activeElement)) { return; } const el = findTabbable(modalElement)[0] || modalElement; el.focus(); }, 0); } } export function markForFocusLater() { focusLaterElements.push(document.activeElement); } /* eslint-disable no-console */ export function returnFocus(preventScroll = false) { let toFocus = null; try { if (focusLaterElements.length !== 0) { toFocus = focusLaterElements.pop(); toFocus.focus({ preventScroll }); } return; } catch (e) { console.warn( [ "You tried to return focus to", toFocus, "but it is not in the DOM anymore" ].join(" ") ); } } /* eslint-enable no-console */ export function popWithoutFocus() { focusLaterElements.length > 0 && focusLaterElements.pop(); } export function setupScopedFocus(element) { modalElement = element; if (window.addEventListener) { window.addEventListener("blur", handleBlur, false); document.addEventListener("focus", handleFocus, true); } else { window.attachEvent("onBlur", handleBlur); document.attachEvent("onFocus", handleFocus); } } export function teardownScopedFocus() { modalElement = null; if (window.addEventListener) { window.removeEventListener("blur", handleBlur); document.removeEventListener("focus", handleFocus); } else { window.detachEvent("onBlur", handleBlur); document.detachEvent("onFocus", handleFocus); } } ================================================ FILE: src/helpers/portalOpenInstances.js ================================================ // Tracks portals that are open and emits events to subscribers class PortalOpenInstances { constructor() { this.openInstances = []; this.subscribers = []; } register = openInstance => { if (this.openInstances.indexOf(openInstance) !== -1) { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.warn( `React-Modal: Cannot register modal instance that's already open` ); } return; } this.openInstances.push(openInstance); this.emit("register"); }; deregister = openInstance => { const index = this.openInstances.indexOf(openInstance); if (index === -1) { if (process.env.NODE_ENV !== "production") { // eslint-disable-next-line no-console console.warn( `React-Modal: Unable to deregister ${openInstance} as ` + `it was never registered` ); } return; } this.openInstances.splice(index, 1); this.emit("deregister"); }; subscribe = callback => { this.subscribers.push(callback); }; emit = eventType => { this.subscribers.forEach(subscriber => subscriber( eventType, // shallow copy to avoid accidental mutation this.openInstances.slice() ) ); }; } let portalOpenInstances = new PortalOpenInstances(); /* eslint-disable no-console */ /* istanbul ignore next */ export function log() { console.log("portalOpenInstances ----------"); console.log(portalOpenInstances.openInstances.length); portalOpenInstances.openInstances.forEach(p => console.log(p)); console.log("end portalOpenInstances ----------"); } /* istanbul ignore next */ export function resetState() { portalOpenInstances = new PortalOpenInstances(); } /* eslint-enable no-console */ export default portalOpenInstances; ================================================ FILE: src/helpers/safeHTMLElement.js ================================================ import ExecutionEnvironment from "exenv"; const EE = ExecutionEnvironment; const SafeHTMLElement = EE.canUseDOM ? window.HTMLElement : {}; export const SafeHTMLCollection = EE.canUseDOM ? window.HTMLCollection : {}; export const SafeNodeList = EE.canUseDOM ? window.NodeList : {}; export const canUseDOM = EE.canUseDOM; export default SafeHTMLElement; ================================================ FILE: src/helpers/scopeTab.js ================================================ import findTabbable from "./tabbable"; function getActiveElement(el = document) { return el.activeElement.shadowRoot ? getActiveElement(el.activeElement.shadowRoot) : el.activeElement; } export default function scopeTab(node, event) { const tabbable = findTabbable(node); if (!tabbable.length) { // Do nothing, since there are no elements that can receive focus. event.preventDefault(); return; } let target; const shiftKey = event.shiftKey; const head = tabbable[0]; const tail = tabbable[tabbable.length - 1]; const activeElement = getActiveElement(); // proceed with default browser behavior on tab. // Focus on last element on shift + tab. if (node === activeElement) { if (!shiftKey) return; target = tail; } if (tail === activeElement && !shiftKey) { target = head; } if (head === activeElement && shiftKey) { target = tail; } if (target) { event.preventDefault(); target.focus(); return; } // Safari radio issue. // // Safari does not move the focus to the radio button, // so we need to force it to really walk through all elements. // // This is very error prone, since we are trying to guess // if it is a safari browser from the first occurence between // chrome or safari. // // The chrome user agent contains the first ocurrence // as the 'chrome/version' and later the 'safari/version'. const checkSafari = /(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent); const isSafariDesktop = checkSafari != null && checkSafari[1] != "Chrome" && /\biPod\b|\biPad\b/g.exec(navigator.userAgent) == null; // If we are not in safari desktop, let the browser control // the focus if (!isSafariDesktop) return; var x = tabbable.indexOf(activeElement); if (x > -1) { x += shiftKey ? -1 : 1; } target = tabbable[x]; // If the tabbable element does not exist, // focus head/tail based on shiftKey if (typeof target === "undefined") { event.preventDefault(); target = shiftKey ? tail : head; target.focus(); return; } event.preventDefault(); target.focus(); } ================================================ FILE: src/helpers/tabbable.js ================================================ /*! * Adapted from jQuery UI core * * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/category/ui-core/ */ const DISPLAY_NONE = "none"; const DISPLAY_CONTENTS = "contents"; // match the whole word to prevent fuzzy searching const tabbableNode = /^(input|select|textarea|button|object|iframe)$/; function isNotOverflowing(element, style) { return ( style.getPropertyValue("overflow") !== "visible" || // if 'overflow: visible' set, check if there is actually any overflow (element.scrollWidth <= 0 && element.scrollHeight <= 0) ); } function hidesContents(element) { const zeroSize = element.offsetWidth <= 0 && element.offsetHeight <= 0; // If the node is empty, this is good enough if (zeroSize && !element.innerHTML) return true; try { // Otherwise we need to check some styles const style = window.getComputedStyle(element); const displayValue = style.getPropertyValue("display"); return zeroSize ? displayValue !== DISPLAY_CONTENTS && isNotOverflowing(element, style) : displayValue === DISPLAY_NONE; } catch (exception) { // eslint-disable-next-line no-console console.warn("Failed to inspect element style"); return false; } } function visible(element) { let parentElement = element; let rootNode = element.getRootNode && element.getRootNode(); while (parentElement) { if (parentElement === document.body) break; // if we are not hidden yet, skip to checking outside the Web Component if (rootNode && parentElement === rootNode) parentElement = rootNode.host.parentNode; if (hidesContents(parentElement)) return false; parentElement = parentElement.parentNode; } return true; } function focusable(element, isTabIndexNotNaN) { const nodeName = element.nodeName.toLowerCase(); const res = (tabbableNode.test(nodeName) && !element.disabled) || (nodeName === "a" ? element.href || isTabIndexNotNaN : isTabIndexNotNaN); return res && visible(element); } function tabbable(element) { let tabIndex = element.getAttribute("tabindex"); if (tabIndex === null) tabIndex = undefined; const isTabIndexNaN = isNaN(tabIndex); return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN); } export default function findTabbableDescendants(element) { const descendants = [].slice .call(element.querySelectorAll("*"), 0) .reduce( (finished, el) => finished.concat( !el.shadowRoot ? [el] : findTabbableDescendants(el.shadowRoot) ), [] ); return descendants.filter(tabbable); } ================================================ FILE: src/index.js ================================================ import Modal from "./components/Modal"; export default Modal;