├── .github ├── ISSUE_TEMPLATE │ ├── 1.Bug_report.md │ ├── 2.Feature_request.md │ └── config.yml └── workflows │ ├── main.yml │ ├── release-please.yml │ └── size.yml ├── .gitignore ├── .prettierignore ├── .yarn ├── plugins │ └── @yarnpkg │ │ ├── plugin-interactive-tools.cjs │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.3.0.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── netlify.toml ├── package.json ├── react-responsive-modal ├── CHANGELOG.md ├── README.md ├── __tests__ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ ├── index.test.tsx │ └── setupTests.ts ├── cypress.json ├── cypress │ ├── integration │ │ └── modal.spec.ts │ ├── plugins │ │ └── index.js │ ├── support │ │ ├── commands.js │ │ └── index.js │ └── tsconfig.json ├── package.json ├── src │ ├── CloseIcon.tsx │ ├── FocusTrap.tsx │ ├── index.tsx │ ├── lib │ │ └── focusTrapJs.ts │ ├── modalManager.ts │ ├── useScrollLock.ts │ └── utils.ts ├── styles.css └── tsconfig.json ├── renovate.json ├── website ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── src │ ├── components │ │ ├── ExampleRendered.tsx │ │ ├── Footer.tsx │ │ └── Header.tsx │ ├── config.ts │ ├── docs │ │ └── index.mdx │ ├── examples │ │ ├── CustomAnimation.tsx │ │ ├── CustomCloseIcon.tsx │ │ ├── CustomContainer.tsx │ │ ├── CustomCssStyle.tsx │ │ ├── FocusTrapped.tsx │ │ ├── FocusTrappedInitialFocus.tsx │ │ ├── LongContent.tsx │ │ ├── Multiple.tsx │ │ ├── Simple.tsx │ │ ├── custom-animation.css │ │ └── custom-styling.css │ ├── pages │ │ ├── _app.tsx │ │ └── index.tsx │ └── styles │ │ ├── atom-one-light.css │ │ └── index.css ├── tailwind.config.js └── tsconfig.json └── yarn.lock /.github/ISSUE_TEMPLATE/1.Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a bug report 4 | labels: 'bug' 5 | --- 6 | 7 | 11 | 12 | # Bug report 13 | 14 | ## Describe the bug 15 | 16 | A clear and concise description of what the bug is. 17 | 18 | ## To Reproduce 19 | 20 | Steps to reproduce the behavior, please provide code snippets or a repository. 21 | 22 | ## Expected behavior 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ## Screenshots 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | ## System information 31 | 32 | - Version of react-responsive-modal: [e.g. 4.0.1] 33 | - Version of react: [e.g. 16.8.1] 34 | - Browser version: [e.g. Safari 16.8.1] 35 | 36 | ## Additional context 37 | 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ⚡ Feature request 3 | about: Create a feature request 4 | labels: 'enhancement' 5 | --- 6 | 7 | # Feature request 8 | 9 | ## Is your feature request related to a problem? Please describe. 10 | 11 | A clear and concise description of what you want and what your use case is. 12 | 13 | ## Describe the solution you'd like 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## Additional context 18 | 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Begin CI... 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node 12 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 12.x 21 | 22 | - name: Get yarn cache directory path 23 | id: yarn-cache-dir-path 24 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 25 | 26 | - uses: actions/cache@v2 27 | id: yarn-cache 28 | with: 29 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 30 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-yarn- 33 | 34 | - name: Install dependencies 35 | run: yarn install --immutable 36 | 37 | - name: Lint 38 | run: yarn workspace react-responsive-modal lint 39 | 40 | - name: Test 41 | run: yarn workspace react-responsive-modal test --ci --coverage --maxWorkers=2 42 | 43 | - name: Report coverage 44 | uses: codecov/codecov-action@v1 45 | 46 | - name: Build 47 | run: yarn workspace react-responsive-modal build 48 | 49 | - name: Build docs 50 | run: yarn workspace website build 51 | 52 | - name: Cypress run 53 | uses: cypress-io/github-action@v2 54 | with: 55 | # Dependencies already installed before 56 | install: false 57 | # Use monorepo 58 | project: ./react-responsive-modal 59 | start: yarn workspace website start -p 3000 60 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v3.2 13 | id: release 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | release-type: node 17 | package-name: react-responsive-modal 18 | changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":false},{"type":"docs","section":"Documentation","hidden":false},{"type":"test","section":"Tests","hidden":false}]' 19 | extra-files: | 20 | react-responsive-modal/package.json 21 | 22 | - uses: actions/checkout@v2 23 | if: ${{ steps.release.outputs.release_created }} 24 | 25 | - name: Use Node 16 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: 16.x 29 | if: ${{ steps.release.outputs.release_created }} 30 | 31 | - name: Install dependencies 32 | run: yarn install --immutable 33 | if: ${{ steps.release.outputs.release_created }} 34 | 35 | - name: Build react-responsive-modal 36 | run: yarn workspace react-responsive-modal build 37 | if: ${{ steps.release.outputs.release_created }} 38 | 39 | - name: Publish react-responsive-modal npm package 40 | run: yarn workspace react-responsive-modal npm publish 41 | env: 42 | NPM_TOKEN: ${{secrets.NPM_TOKEN}} 43 | if: ${{ steps.release.outputs.release_created }} 44 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | size: 7 | runs-on: ubuntu-latest 8 | env: 9 | CI_JOB_NUMBER: 1 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Use Node 12 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 12.x 17 | 18 | - name: Get yarn cache directory path 19 | id: yarn-cache-dir-path 20 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 21 | 22 | - uses: actions/cache@v2 23 | id: yarn-cache 24 | with: 25 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 26 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-yarn- 29 | 30 | - uses: andresz1/size-limit-action@v1 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | directory: react-responsive-modal 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | coverage 7 | .next 8 | out 9 | .yarn/* 10 | !.yarn/releases 11 | !.yarn/plugins 12 | .pnp.* 13 | .next 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .next 3 | out 4 | coverage 5 | .yarn 6 | CHANGELOG.md 7 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-workspace-tools", 5 | factory: function (require) { 6 | var plugin=(()=>{var _r=Object.create;var we=Object.defineProperty;var Er=Object.getOwnPropertyDescriptor;var br=Object.getOwnPropertyNames;var xr=Object.getPrototypeOf,Cr=Object.prototype.hasOwnProperty;var W=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var q=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),wr=(e,r)=>{for(var t in r)we(e,t,{get:r[t],enumerable:!0})},Je=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of br(r))!Cr.call(e,s)&&s!==t&&we(e,s,{get:()=>r[s],enumerable:!(n=Er(r,s))||n.enumerable});return e};var Be=(e,r,t)=>(t=e!=null?_r(xr(e)):{},Je(r||!e||!e.__esModule?we(t,"default",{value:e,enumerable:!0}):t,e)),Sr=e=>Je(we({},"__esModule",{value:!0}),e);var ve=q(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,r)=>e.nodes.find(t=>t.type===r);ee.exceedsLimit=(e,r,t=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(r)?!1:(Number(r)-Number(e))/Number(t)>=n;ee.escapeNode=(e,r=0,t)=>{let n=e.nodes[r];!n||(t&&n.type===t||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0===0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0===0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((r,t)=>(t.type==="text"&&r.push(t.value),t.type==="range"&&(t.type="text"),r),[]);ee.flatten=(...e)=>{let r=[],t=n=>{for(let s=0;s{"use strict";var tt=ve();rt.exports=(e,r={})=>{let t=(n,s={})=>{let i=r.escapeInvalid&&tt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c="";if(n.value)return(i||a)&&tt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let p of n.nodes)c+=t(p);return c};return t(e)}});var st=q((Jn,nt)=>{"use strict";nt.exports=function(e){return typeof e=="number"?e-e===0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var ht=q((es,pt)=>{"use strict";var at=st(),le=(e,r,t)=>{if(at(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(r===void 0||e===r)return String(e);if(at(r)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n={relaxZeros:!0,...t};typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),i=String(n.shorthand),a=String(n.capture),c=String(n.wrap),p=e+":"+r+"="+s+i+a+c;if(le.cache.hasOwnProperty(p))return le.cache[p].result;let m=Math.min(e,r),h=Math.max(e,r);if(Math.abs(m-h)===1){let y=e+"|"+r;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=ft(e)||ft(r),f={min:e,max:r,a:m,b:h},$=[],_=[];if(R&&(f.isPadded=R,f.maxLen=String(f.max).length),m<0){let y=h<0?Math.abs(h):1;_=it(y,Math.abs(m),f,n),m=f.a=0}return h>=0&&($=it(m,h,f,n)),f.negatives=_,f.positives=$,f.result=vr(_,$,n),n.capture===!0?f.result=`(${f.result})`:n.wrap!==!1&&$.length+_.length>1&&(f.result=`(?:${f.result})`),le.cache[p]=f,f.result};function vr(e,r,t){let n=Me(e,r,"-",!1,t)||[],s=Me(r,e,"",!1,t)||[],i=Me(e,r,"-?",!0,t)||[];return n.concat(i).concat(s).join("|")}function Hr(e,r){let t=1,n=1,s=ut(e,t),i=new Set([r]);for(;e<=s&&s<=r;)i.add(s),t+=1,s=ut(e,t);for(s=ct(r+1,n)-1;e1&&c.count.pop(),c.count.push(h.count[0]),c.string=c.pattern+lt(c.count),a=m+1;continue}t.isPadded&&(R=Or(m,t,n)),h.string=R+h.pattern+lt(h.count),i.push(h),a=m+1,c=h}return i}function Me(e,r,t,n,s){let i=[];for(let a of e){let{string:c}=a;!n&&!ot(r,"string",c)&&i.push(t+c),n&&ot(r,"string",c)&&i.push(t+c)}return i}function Tr(e,r){let t=[];for(let n=0;nr?1:r>e?-1:0}function ot(e,r,t){return e.some(n=>n[r]===t)}function ut(e,r){return Number(String(e).slice(0,-r)+"9".repeat(r))}function ct(e,r){return e-e%Math.pow(10,r)}function lt(e){let[r=0,t=""]=e;return t||r>1?`{${r+(t?","+t:"")}}`:""}function Lr(e,r,t){return`[${e}${r-e===1?"":"-"}${r}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Or(e,r,t){if(!r.isPadded)return e;let n=Math.abs(r.maxLen-String(e).length),s=t.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}le.cache={};le.clearCache=()=>le.cache={};pt.exports=le});var Ue=q((ts,Et)=>{"use strict";var Nr=W("util"),At=ht(),dt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Ir=e=>r=>e===!0?Number(r):String(r),Pe=e=>typeof e=="number"||typeof e=="string"&&e!=="",Ae=e=>Number.isInteger(+e),De=e=>{let r=`${e}`,t=-1;if(r[0]==="-"&&(r=r.slice(1)),r==="0")return!1;for(;r[++t]==="0";);return t>0},Br=(e,r,t)=>typeof e=="string"||typeof r=="string"?!0:t.stringify===!0,Mr=(e,r,t)=>{if(r>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?r-1:r,"0")}return t===!1?String(e):e},gt=(e,r)=>{let t=e[0]==="-"?"-":"";for(t&&(e=e.slice(1),r--);e.length{e.negatives.sort((a,c)=>ac?1:0),e.positives.sort((a,c)=>ac?1:0);let t=r.capture?"":"?:",n="",s="",i;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${t}${e.negatives.join("|")})`),n&&s?i=`${n}|${s}`:i=n||s,r.wrap?`(${t}${i})`:i},mt=(e,r,t,n)=>{if(t)return At(e,r,{wrap:!1,...n});let s=String.fromCharCode(e);if(e===r)return s;let i=String.fromCharCode(r);return`[${s}-${i}]`},Rt=(e,r,t)=>{if(Array.isArray(e)){let n=t.wrap===!0,s=t.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return At(e,r,t)},yt=(...e)=>new RangeError("Invalid range arguments: "+Nr.inspect(...e)),_t=(e,r,t)=>{if(t.strictRanges===!0)throw yt([e,r]);return[]},Dr=(e,r)=>{if(r.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Ur=(e,r,t=1,n={})=>{let s=Number(e),i=Number(r);if(!Number.isInteger(s)||!Number.isInteger(i)){if(n.strictRanges===!0)throw yt([e,r]);return[]}s===0&&(s=0),i===0&&(i=0);let a=s>i,c=String(e),p=String(r),m=String(t);t=Math.max(Math.abs(t),1);let h=De(c)||De(p)||De(m),R=h?Math.max(c.length,p.length,m.length):0,f=h===!1&&Br(e,r,n)===!1,$=n.transform||Ir(f);if(n.toRegex&&t===1)return mt(gt(e,R),gt(r,R),!0,n);let _={negatives:[],positives:[]},y=T=>_[T<0?"negatives":"positives"].push(Math.abs(T)),E=[],S=0;for(;a?s>=i:s<=i;)n.toRegex===!0&&t>1?y(s):E.push(Mr($(s,S),R,f)),s=a?s-t:s+t,S++;return n.toRegex===!0?t>1?Pr(_,n):Rt(E,null,{wrap:!1,...n}):E},Gr=(e,r,t=1,n={})=>{if(!Ae(e)&&e.length>1||!Ae(r)&&r.length>1)return _t(e,r,n);let s=n.transform||(f=>String.fromCharCode(f)),i=`${e}`.charCodeAt(0),a=`${r}`.charCodeAt(0),c=i>a,p=Math.min(i,a),m=Math.max(i,a);if(n.toRegex&&t===1)return mt(p,m,!1,n);let h=[],R=0;for(;c?i>=a:i<=a;)h.push(s(i,R)),i=c?i-t:i+t,R++;return n.toRegex===!0?Rt(h,null,{wrap:!1,options:n}):h},$e=(e,r,t,n={})=>{if(r==null&&Pe(e))return[e];if(!Pe(e)||!Pe(r))return _t(e,r,n);if(typeof t=="function")return $e(e,r,1,{transform:t});if(dt(t))return $e(e,r,0,t);let s={...n};return s.capture===!0&&(s.wrap=!0),t=t||s.step||1,Ae(t)?Ae(e)&&Ae(r)?Ur(e,r,t,s):Gr(e,r,Math.max(Math.abs(t),1),s):t!=null&&!dt(t)?Dr(t,s):$e(e,r,1,t)};Et.exports=$e});var Ct=q((rs,xt)=>{"use strict";var qr=Ue(),bt=ve(),Kr=(e,r={})=>{let t=(n,s={})=>{let i=bt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c=i===!0||a===!0,p=r.escapeInvalid===!0?"\\":"",m="";if(n.isOpen===!0||n.isClose===!0)return p+n.value;if(n.type==="open")return c?p+n.value:"(";if(n.type==="close")return c?p+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":c?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let h=bt.reduce(n.nodes),R=qr(...h,{...r,wrap:!1,toRegex:!0});if(R.length!==0)return h.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let h of n.nodes)m+=t(h,n);return m};return t(e)};xt.exports=Kr});var vt=q((ns,St)=>{"use strict";var Wr=Ue(),wt=He(),he=ve(),fe=(e="",r="",t=!1)=>{let n=[];if(e=[].concat(e),r=[].concat(r),!r.length)return e;if(!e.length)return t?he.flatten(r).map(s=>`{${s}}`):r;for(let s of e)if(Array.isArray(s))for(let i of s)n.push(fe(i,r,t));else for(let i of r)t===!0&&typeof i=="string"&&(i=`{${i}}`),n.push(Array.isArray(i)?fe(s,i,t):s+i);return he.flatten(n)},jr=(e,r={})=>{let t=r.rangeLimit===void 0?1e3:r.rangeLimit,n=(s,i={})=>{s.queue=[];let a=i,c=i.queue;for(;a.type!=="brace"&&a.type!=="root"&&a.parent;)a=a.parent,c=a.queue;if(s.invalid||s.dollar){c.push(fe(c.pop(),wt(s,r)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){c.push(fe(c.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=he.reduce(s.nodes);if(he.exceedsLimit(...R,r.step,t))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let f=Wr(...R,r);f.length===0&&(f=wt(s,r)),c.push(fe(c.pop(),f)),s.nodes=[];return}let p=he.encloseBrace(s),m=s.queue,h=s;for(;h.type!=="brace"&&h.type!=="root"&&h.parent;)h=h.parent,m=h.queue;for(let R=0;R{"use strict";Ht.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` 7 | `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nt=q((as,Ot)=>{"use strict";var Fr=He(),{MAX_LENGTH:Tt,CHAR_BACKSLASH:Ge,CHAR_BACKTICK:Qr,CHAR_COMMA:Xr,CHAR_DOT:Zr,CHAR_LEFT_PARENTHESES:Yr,CHAR_RIGHT_PARENTHESES:zr,CHAR_LEFT_CURLY_BRACE:Vr,CHAR_RIGHT_CURLY_BRACE:Jr,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Lt,CHAR_DOUBLE_QUOTE:en,CHAR_SINGLE_QUOTE:tn,CHAR_NO_BREAK_SPACE:rn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:nn}=$t(),sn=(e,r={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let t=r||{},n=typeof t.maxLength=="number"?Math.min(Tt,t.maxLength):Tt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},i=[s],a=s,c=s,p=0,m=e.length,h=0,R=0,f,$={},_=()=>e[h++],y=E=>{if(E.type==="text"&&c.type==="dot"&&(c.type="text"),c&&c.type==="text"&&E.type==="text"){c.value+=E.value;return}return a.nodes.push(E),E.parent=a,E.prev=c,c=E,E};for(y({type:"bos"});h0){if(a.ranges>0){a.ranges=0;let E=a.nodes.shift();a.nodes=[E,{type:"text",value:Fr(a)}]}y({type:"comma",value:f}),a.commas++;continue}if(f===Zr&&R>0&&a.commas===0){let E=a.nodes;if(R===0||E.length===0){y({type:"text",value:f});continue}if(c.type==="dot"){if(a.range=[],c.value+=f,c.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,c.type="text";continue}a.ranges++,a.args=[];continue}if(c.type==="range"){E.pop();let S=E[E.length-1];S.value+=c.value+f,c=S,a.ranges--;continue}y({type:"dot",value:f});continue}y({type:"text",value:f})}do if(a=i.pop(),a.type!=="root"){a.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let E=i[i.length-1],S=E.nodes.indexOf(a);E.nodes.splice(S,1,...a.nodes)}while(i.length>0);return y({type:"eos"}),s};Ot.exports=sn});var Mt=q((is,Bt)=>{"use strict";var It=He(),an=Ct(),on=vt(),un=Nt(),X=(e,r={})=>{let t=[];if(Array.isArray(e))for(let n of e){let s=X.create(n,r);Array.isArray(s)?t.push(...s):t.push(s)}else t=[].concat(X.create(e,r));return r&&r.expand===!0&&r.nodupes===!0&&(t=[...new Set(t)]),t};X.parse=(e,r={})=>un(e,r);X.stringify=(e,r={})=>It(typeof e=="string"?X.parse(e,r):e,r);X.compile=(e,r={})=>(typeof e=="string"&&(e=X.parse(e,r)),an(e,r));X.expand=(e,r={})=>{typeof e=="string"&&(e=X.parse(e,r));let t=on(e,r);return r.noempty===!0&&(t=t.filter(Boolean)),r.nodupes===!0&&(t=[...new Set(t)]),t};X.create=(e,r={})=>e===""||e.length<3?[e]:r.expand!==!0?X.compile(e,r):X.expand(e,r);Bt.exports=X});var me=q((os,qt)=>{"use strict";var cn=W("path"),se="\\\\/",Pt=`[^${se}]`,ie="\\.",ln="\\+",fn="\\?",Te="\\/",pn="(?=.)",Dt="[^/]",qe=`(?:${Te}|$)`,Ut=`(?:^|${Te})`,Ke=`${ie}{1,2}${qe}`,hn=`(?!${ie})`,dn=`(?!${Ut}${Ke})`,gn=`(?!${ie}{0,1}${qe})`,An=`(?!${Ke})`,mn=`[^.${Te}]`,Rn=`${Dt}*?`,Gt={DOT_LITERAL:ie,PLUS_LITERAL:ln,QMARK_LITERAL:fn,SLASH_LITERAL:Te,ONE_CHAR:pn,QMARK:Dt,END_ANCHOR:qe,DOTS_SLASH:Ke,NO_DOT:hn,NO_DOTS:dn,NO_DOT_SLASH:gn,NO_DOTS_SLASH:An,QMARK_NO_DOT:mn,STAR:Rn,START_ANCHOR:Ut},yn={...Gt,SLASH_LITERAL:`[${se}]`,QMARK:Pt,STAR:`${Pt}*?`,DOTS_SLASH:`${ie}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ie})`,NO_DOTS:`(?!(?:^|[${se}])${ie}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ie}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ie}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`},_n={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:_n,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:cn.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?yn:Gt}}});var Re=q(F=>{"use strict";var En=W("path"),bn=process.platform==="win32",{REGEX_BACKSLASH:xn,REGEX_REMOVE_BACKSLASH:Cn,REGEX_SPECIAL_CHARS:wn,REGEX_SPECIAL_CHARS_GLOBAL:Sn}=me();F.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);F.hasRegexChars=e=>wn.test(e);F.isRegexChar=e=>e.length===1&&F.hasRegexChars(e);F.escapeRegex=e=>e.replace(Sn,"\\$1");F.toPosixSlashes=e=>e.replace(xn,"/");F.removeBackslashes=e=>e.replace(Cn,r=>r==="\\"?"":r);F.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};F.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:bn===!0||En.sep==="\\";F.escapeLast=(e,r,t)=>{let n=e.lastIndexOf(r,t);return n===-1?e:e[n-1]==="\\"?F.escapeLast(e,r,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};F.removePrefix=(e,r={})=>{let t=e;return t.startsWith("./")&&(t=t.slice(2),r.prefix="./"),t};F.wrapOutput=(e,r={},t={})=>{let n=t.contains?"":"^",s=t.contains?"":"$",i=`${n}(?:${e})${s}`;return r.negated===!0&&(i=`(?:^(?!${i}).*$)`),i}});var Yt=q((cs,Zt)=>{"use strict";var Kt=Re(),{CHAR_ASTERISK:We,CHAR_AT:vn,CHAR_BACKWARD_SLASH:ye,CHAR_COMMA:Hn,CHAR_DOT:je,CHAR_EXCLAMATION_MARK:Fe,CHAR_FORWARD_SLASH:Xt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:$n,CHAR_PLUS:Tn,CHAR_QUESTION_MARK:Wt,CHAR_RIGHT_CURLY_BRACE:kn,CHAR_RIGHT_PARENTHESES:jt,CHAR_RIGHT_SQUARE_BRACKET:Ln}=me(),Ft=e=>e===Xt||e===ye,Qt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?1/0:1)},On=(e,r)=>{let t=r||{},n=e.length-1,s=t.parts===!0||t.scanToEnd===!0,i=[],a=[],c=[],p=e,m=-1,h=0,R=0,f=!1,$=!1,_=!1,y=!1,E=!1,S=!1,T=!1,L=!1,z=!1,I=!1,re=0,K,g,v={value:"",depth:0,isGlob:!1},k=()=>m>=n,l=()=>p.charCodeAt(m+1),H=()=>(K=g,p.charCodeAt(++m));for(;m0&&(B=p.slice(0,h),p=p.slice(h),R-=h),w&&_===!0&&R>0?(w=p.slice(0,R),o=p.slice(R)):_===!0?(w="",o=p):w=p,w&&w!==""&&w!=="/"&&w!==p&&Ft(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),t.unescape===!0&&(o&&(o=Kt.removeBackslashes(o)),w&&T===!0&&(w=Kt.removeBackslashes(w)));let u={prefix:B,input:e,start:h,base:w,glob:o,isBrace:f,isBracket:$,isGlob:_,isExtglob:y,isGlobstar:E,negated:L,negatedExtglob:z};if(t.tokens===!0&&(u.maxDepth=0,Ft(g)||a.push(v),u.tokens=a),t.parts===!0||t.tokens===!0){let M;for(let b=0;b{"use strict";var ke=me(),Z=Re(),{MAX_LENGTH:Le,POSIX_REGEX_SOURCE:Nn,REGEX_NON_SPECIAL_CHARS:In,REGEX_SPECIAL_CHARS_BACKREF:Bn,REPLACEMENTS:zt}=ke,Mn=(e,r)=>{if(typeof r.expandRange=="function")return r.expandRange(...e,r);e.sort();let t=`[${e.join("-")}]`;try{new RegExp(t)}catch{return e.map(s=>Z.escapeRegex(s)).join("..")}return t},de=(e,r)=>`Missing ${e}: "${r}" - use "\\\\${r}" to match literal characters`,Vt=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=zt[e]||e;let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let i={type:"bos",value:"",output:t.prepend||""},a=[i],c=t.capture?"":"?:",p=Z.isWindows(r),m=ke.globChars(p),h=ke.extglobChars(m),{DOT_LITERAL:R,PLUS_LITERAL:f,SLASH_LITERAL:$,ONE_CHAR:_,DOTS_SLASH:y,NO_DOT:E,NO_DOT_SLASH:S,NO_DOTS_SLASH:T,QMARK:L,QMARK_NO_DOT:z,STAR:I,START_ANCHOR:re}=m,K=A=>`(${c}(?:(?!${re}${A.dot?y:R}).)*?)`,g=t.dot?"":E,v=t.dot?L:z,k=t.bash===!0?K(t):I;t.capture&&(k=`(${k})`),typeof t.noext=="boolean"&&(t.noextglob=t.noext);let l={input:e,index:-1,start:0,dot:t.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};e=Z.removePrefix(e,l),s=e.length;let H=[],w=[],B=[],o=i,u,M=()=>l.index===s-1,b=l.peek=(A=1)=>e[l.index+A],V=l.advance=()=>e[++l.index]||"",J=()=>e.slice(l.index+1),Q=(A="",O=0)=>{l.consumed+=A,l.index+=O},Ee=A=>{l.output+=A.output!=null?A.output:A.value,Q(A.value)},Rr=()=>{let A=1;for(;b()==="!"&&(b(2)!=="("||b(3)==="?");)V(),l.start++,A++;return A%2===0?!1:(l.negated=!0,l.start++,!0)},be=A=>{l[A]++,B.push(A)},oe=A=>{l[A]--,B.pop()},C=A=>{if(o.type==="globstar"){let O=l.braces>0&&(A.type==="comma"||A.type==="brace"),d=A.extglob===!0||H.length&&(A.type==="pipe"||A.type==="paren");A.type!=="slash"&&A.type!=="paren"&&!O&&!d&&(l.output=l.output.slice(0,-o.output.length),o.type="star",o.value="*",o.output=k,l.output+=o.output)}if(H.length&&A.type!=="paren"&&(H[H.length-1].inner+=A.value),(A.value||A.output)&&Ee(A),o&&o.type==="text"&&A.type==="text"){o.value+=A.value,o.output=(o.output||"")+A.value;return}A.prev=o,a.push(A),o=A},xe=(A,O)=>{let d={...h[O],conditions:1,inner:""};d.prev=o,d.parens=l.parens,d.output=l.output;let x=(t.capture?"(":"")+d.open;be("parens"),C({type:A,value:O,output:l.output?"":_}),C({type:"paren",extglob:!0,value:V(),output:x}),H.push(d)},yr=A=>{let O=A.close+(t.capture?")":""),d;if(A.type==="negate"){let x=k;A.inner&&A.inner.length>1&&A.inner.includes("/")&&(x=K(t)),(x!==k||M()||/^\)+$/.test(J()))&&(O=A.close=`)$))${x}`),A.inner.includes("*")&&(d=J())&&/^\.[^\\/.]+$/.test(d)&&(O=A.close=`)${d})${x})`),A.prev.type==="bos"&&(l.negatedExtglob=!0)}C({type:"paren",extglob:!0,value:u,output:O}),oe("parens")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,O=e.replace(Bn,(d,x,P,j,G,Ie)=>j==="\\"?(A=!0,d):j==="?"?x?x+j+(G?L.repeat(G.length):""):Ie===0?v+(G?L.repeat(G.length):""):L.repeat(P.length):j==="."?R.repeat(P.length):j==="*"?x?x+j+(G?k:""):k:x?d:`\\${d}`);return A===!0&&(t.unescape===!0?O=O.replace(/\\/g,""):O=O.replace(/\\+/g,d=>d.length%2===0?"\\\\":d?"\\":"")),O===e&&t.contains===!0?(l.output=e,l):(l.output=Z.wrapOutput(O,l,r),l)}for(;!M();){if(u=V(),u==="\0")continue;if(u==="\\"){let d=b();if(d==="/"&&t.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",C({type:"text",value:u});continue}let x=/^\\+/.exec(J()),P=0;if(x&&x[0].length>2&&(P=x[0].length,l.index+=P,P%2!==0&&(u+="\\")),t.unescape===!0?u=V():u+=V(),l.brackets===0){C({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||o.value==="["||o.value==="[^")){if(t.posix!==!1&&u===":"){let d=o.value.slice(1);if(d.includes("[")&&(o.posix=!0,d.includes(":"))){let x=o.value.lastIndexOf("["),P=o.value.slice(0,x),j=o.value.slice(x+2),G=Nn[j];if(G){o.value=P+G,l.backtrack=!0,V(),!i.output&&a.indexOf(o)===1&&(i.output=_);continue}}}(u==="["&&b()!==":"||u==="-"&&b()==="]")&&(u=`\\${u}`),u==="]"&&(o.value==="["||o.value==="[^")&&(u=`\\${u}`),t.posix===!0&&u==="!"&&o.value==="["&&(u="^"),o.value+=u,Ee({value:u});continue}if(l.quotes===1&&u!=='"'){u=Z.escapeRegex(u),o.value+=u,Ee({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,t.keepQuotes===!0&&C({type:"text",value:u});continue}if(u==="("){be("parens"),C({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&t.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){yr(H.pop());continue}C({type:"paren",value:u,output:l.parens?")":"\\)"}),oe("parens");continue}if(u==="["){if(t.nobracket===!0||!J().includes("]")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else be("brackets");C({type:"bracket",value:u});continue}if(u==="]"){if(t.nobracket===!0||o&&o.type==="bracket"&&o.value.length===1){C({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(de("opening","["));C({type:"text",value:u,output:`\\${u}`});continue}oe("brackets");let d=o.value.slice(1);if(o.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),o.value+=u,Ee({value:u}),t.literalBrackets===!1||Z.hasRegexChars(d))continue;let x=Z.escapeRegex(o.value);if(l.output=l.output.slice(0,-o.value.length),t.literalBrackets===!0){l.output+=x,o.value=x;continue}o.value=`(${c}${x}|${o.value})`,l.output+=o.value;continue}if(u==="{"&&t.nobrace!==!0){be("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),C(d);continue}if(u==="}"){let d=w[w.length-1];if(t.nobrace===!0||!d){C({type:"text",value:u,output:u});continue}let x=")";if(d.dots===!0){let P=a.slice(),j=[];for(let G=P.length-1;G>=0&&(a.pop(),P[G].type!=="brace");G--)P[G].type!=="dots"&&j.unshift(P[G].value);x=Mn(j,t),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),j=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=x="\\}",l.output=P;for(let G of j)l.output+=G.output||G.value}C({type:"brace",value:u,output:x}),oe("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,C({type:"text",value:u});continue}if(u===","){let d=u,x=w[w.length-1];x&&B[B.length-1]==="braces"&&(x.comma=!0,d="|"),C({type:"comma",value:u,output:d});continue}if(u==="/"){if(o.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",a.pop(),o=i;continue}C({type:"slash",value:u,output:$});continue}if(u==="."){if(l.braces>0&&o.type==="dot"){o.value==="."&&(o.output=R);let d=w[w.length-1];o.type="dots",o.output+=u,o.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&o.type!=="bos"&&o.type!=="slash"){C({type:"text",value:u,output:R});continue}C({type:"dot",value:u,output:R});continue}if(u==="?"){if(!(o&&o.value==="(")&&t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("qmark",u);continue}if(o&&o.type==="paren"){let x=b(),P=u;if(x==="<"&&!Z.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(o.value==="("&&!/[!=<:]/.test(x)||x==="<"&&!/<([!=]|\w+>)/.test(J()))&&(P=`\\${u}`),C({type:"text",value:u,output:P});continue}if(t.dot!==!0&&(o.type==="slash"||o.type==="bos")){C({type:"qmark",value:u,output:z});continue}C({type:"qmark",value:u,output:L});continue}if(u==="!"){if(t.noextglob!==!0&&b()==="("&&(b(2)!=="?"||!/[!=<:]/.test(b(3)))){xe("negate",u);continue}if(t.nonegate!==!0&&l.index===0){Rr();continue}}if(u==="+"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("plus",u);continue}if(o&&o.value==="("||t.regex===!1){C({type:"plus",value:u,output:f});continue}if(o&&(o.type==="bracket"||o.type==="paren"||o.type==="brace")||l.parens>0){C({type:"plus",value:u});continue}C({type:"plus",value:f});continue}if(u==="@"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){C({type:"at",extglob:!0,value:u,output:""});continue}C({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=In.exec(J());d&&(u+=d[0],l.index+=d[0].length),C({type:"text",value:u});continue}if(o&&(o.type==="globstar"||o.star===!0)){o.type="star",o.star=!0,o.value+=u,o.output=k,l.backtrack=!0,l.globstar=!0,Q(u);continue}let A=J();if(t.noextglob!==!0&&/^\([^?]/.test(A)){xe("star",u);continue}if(o.type==="star"){if(t.noglobstar===!0){Q(u);continue}let d=o.prev,x=d.prev,P=d.type==="slash"||d.type==="bos",j=x&&(x.type==="star"||x.type==="globstar");if(t.bash===!0&&(!P||A[0]&&A[0]!=="/")){C({type:"star",value:u,output:""});continue}let G=l.braces>0&&(d.type==="comma"||d.type==="brace"),Ie=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!G&&!Ie){C({type:"star",value:u,output:""});continue}for(;A.slice(0,3)==="/**";){let Ce=e[l.index+4];if(Ce&&Ce!=="/")break;A=A.slice(3),Q("/**",3)}if(d.type==="bos"&&M()){o.type="globstar",o.value+=u,o.output=K(t),l.output=o.output,l.globstar=!0,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!j&&M()){l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=K(t)+(t.strictSlashes?")":"|$)"),o.value+=u,l.globstar=!0,l.output+=d.output+o.output,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&A[0]==="/"){let Ce=A[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=`${K(t)}${$}|${$}${Ce})`,o.value+=u,l.output+=d.output+o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&A[0]==="/"){o.type="globstar",o.value+=u,o.output=`(?:^|${$}|${K(t)}${$})`,l.output=o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-o.output.length),o.type="globstar",o.output=K(t),o.value+=u,l.output+=o.output,l.globstar=!0,Q(u);continue}let O={type:"star",value:u,output:k};if(t.bash===!0){O.output=".*?",(o.type==="bos"||o.type==="slash")&&(O.output=g+O.output),C(O);continue}if(o&&(o.type==="bracket"||o.type==="paren")&&t.regex===!0){O.output=u,C(O);continue}(l.index===l.start||o.type==="slash"||o.type==="dot")&&(o.type==="dot"?(l.output+=S,o.output+=S):t.dot===!0?(l.output+=T,o.output+=T):(l.output+=g,o.output+=g),b()!=="*"&&(l.output+=_,o.output+=_)),C(O)}for(;l.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=Z.escapeLast(l.output,"["),oe("brackets")}for(;l.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=Z.escapeLast(l.output,"("),oe("parens")}for(;l.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=Z.escapeLast(l.output,"{"),oe("braces")}if(t.strictSlashes!==!0&&(o.type==="star"||o.type==="bracket")&&C({type:"maybe_slash",value:"",output:`${$}?`}),l.backtrack===!0){l.output="";for(let A of l.tokens)l.output+=A.output!=null?A.output:A.value,A.suffix&&(l.output+=A.suffix)}return l};Vt.fastpaths=(e,r)=>{let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=zt[e]||e;let i=Z.isWindows(r),{DOT_LITERAL:a,SLASH_LITERAL:c,ONE_CHAR:p,DOTS_SLASH:m,NO_DOT:h,NO_DOTS:R,NO_DOTS_SLASH:f,STAR:$,START_ANCHOR:_}=ke.globChars(i),y=t.dot?R:h,E=t.dot?f:h,S=t.capture?"":"?:",T={negated:!1,prefix:""},L=t.bash===!0?".*?":$;t.capture&&(L=`(${L})`);let z=g=>g.noglobstar===!0?L:`(${S}(?:(?!${_}${g.dot?m:a}).)*?)`,I=g=>{switch(g){case"*":return`${y}${p}${L}`;case".*":return`${a}${p}${L}`;case"*.*":return`${y}${L}${a}${p}${L}`;case"*/*":return`${y}${L}${c}${p}${E}${L}`;case"**":return y+z(t);case"**/*":return`(?:${y}${z(t)}${c})?${E}${p}${L}`;case"**/*.*":return`(?:${y}${z(t)}${c})?${E}${L}${a}${p}${L}`;case"**/.*":return`(?:${y}${z(t)}${c})?${a}${p}${L}`;default:{let v=/^(.*?)\.(\w+)$/.exec(g);if(!v)return;let k=I(v[1]);return k?k+a+v[2]:void 0}}},re=Z.removePrefix(e,T),K=I(re);return K&&t.strictSlashes!==!0&&(K+=`${c}?`),K};Jt.exports=Vt});var rr=q((fs,tr)=>{"use strict";var Pn=W("path"),Dn=Yt(),Ze=er(),Ye=Re(),Un=me(),Gn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,r,t=!1)=>{if(Array.isArray(e)){let h=e.map(f=>D(f,r,t));return f=>{for(let $ of h){let _=$(f);if(_)return _}return!1}}let n=Gn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=r||{},i=Ye.isWindows(r),a=n?D.compileRe(e,r):D.makeRe(e,r,!1,!0),c=a.state;delete a.state;let p=()=>!1;if(s.ignore){let h={...r,ignore:null,onMatch:null,onResult:null};p=D(s.ignore,h,t)}let m=(h,R=!1)=>{let{isMatch:f,match:$,output:_}=D.test(h,a,r,{glob:e,posix:i}),y={glob:e,state:c,regex:a,posix:i,input:h,output:_,match:$,isMatch:f};return typeof s.onResult=="function"&&s.onResult(y),f===!1?(y.isMatch=!1,R?y:!1):p(h)?(typeof s.onIgnore=="function"&&s.onIgnore(y),y.isMatch=!1,R?y:!1):(typeof s.onMatch=="function"&&s.onMatch(y),R?y:!0)};return t&&(m.state=c),m};D.test=(e,r,t,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let i=t||{},a=i.format||(s?Ye.toPosixSlashes:null),c=e===n,p=c&&a?a(e):e;return c===!1&&(p=a?a(e):e,c=p===n),(c===!1||i.capture===!0)&&(i.matchBase===!0||i.basename===!0?c=D.matchBase(e,r,t,s):c=r.exec(p)),{isMatch:Boolean(c),match:c,output:p}};D.matchBase=(e,r,t,n=Ye.isWindows(t))=>(r instanceof RegExp?r:D.makeRe(r,t)).test(Pn.basename(e));D.isMatch=(e,r,t)=>D(r,t)(e);D.parse=(e,r)=>Array.isArray(e)?e.map(t=>D.parse(t,r)):Ze(e,{...r,fastpaths:!1});D.scan=(e,r)=>Dn(e,r);D.compileRe=(e,r,t=!1,n=!1)=>{if(t===!0)return e.output;let s=r||{},i=s.contains?"":"^",a=s.contains?"":"$",c=`${i}(?:${e.output})${a}`;e&&e.negated===!0&&(c=`^(?!${c}).*$`);let p=D.toRegex(c,r);return n===!0&&(p.state=e),p};D.makeRe=(e,r={},t=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return r.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ze.fastpaths(e,r)),s.output||(s=Ze(e,r)),D.compileRe(s,r,t,n)};D.toRegex=(e,r)=>{try{let t=r||{};return new RegExp(e,t.flags||(t.nocase?"i":""))}catch(t){if(r&&r.debug===!0)throw t;return/$^/}};D.constants=Un;tr.exports=D});var sr=q((ps,nr)=>{"use strict";nr.exports=rr()});var cr=q((hs,ur)=>{"use strict";var ir=W("util"),or=Mt(),ae=sr(),ze=Re(),ar=e=>e===""||e==="./",N=(e,r,t)=>{r=[].concat(r),e=[].concat(e);let n=new Set,s=new Set,i=new Set,a=0,c=h=>{i.add(h.output),t&&t.onResult&&t.onResult(h)};for(let h=0;h!n.has(h));if(t&&m.length===0){if(t.failglob===!0)throw new Error(`No matches found for "${r.join(", ")}"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?r.map(h=>h.replace(/\\/g,"")):r}return m};N.match=N;N.matcher=(e,r)=>ae(e,r);N.isMatch=(e,r,t)=>ae(r,t)(e);N.any=N.isMatch;N.not=(e,r,t={})=>{r=[].concat(r).map(String);let n=new Set,s=[],a=N(e,r,{...t,onResult:c=>{t.onResult&&t.onResult(c),s.push(c.output)}});for(let c of s)a.includes(c)||n.add(c);return[...n]};N.contains=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);if(Array.isArray(r))return r.some(n=>N.contains(e,n,t));if(typeof r=="string"){if(ar(e)||ar(r))return!1;if(e.includes(r)||e.startsWith("./")&&e.slice(2).includes(r))return!0}return N.isMatch(e,r,{...t,contains:!0})};N.matchKeys=(e,r,t)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),r,t),s={};for(let i of n)s[i]=e[i];return s};N.some=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(n.some(a=>i(a)))return!0}return!1};N.every=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(!n.every(a=>i(a)))return!1}return!0};N.all=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);return[].concat(r).every(n=>ae(n,t)(e))};N.capture=(e,r,t)=>{let n=ze.isWindows(t),i=ae.makeRe(String(e),{...t,capture:!0}).exec(n?ze.toPosixSlashes(r):r);if(i)return i.slice(1).map(a=>a===void 0?"":a)};N.makeRe=(...e)=>ae.makeRe(...e);N.scan=(...e)=>ae.scan(...e);N.parse=(e,r)=>{let t=[];for(let n of[].concat(e||[]))for(let s of or(String(n),r))t.push(ae.parse(s,r));return t};N.braces=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return r&&r.nobrace===!0||!/\{.*\}/.test(e)?[e]:or(e,r)};N.braceExpand=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,{...r,expand:!0})};ur.exports=N});var fr=q((ds,lr)=>{"use strict";lr.exports=(e,...r)=>new Promise(t=>{t(e(...r))})});var hr=q((gs,Ve)=>{"use strict";var qn=fr(),pr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let r=[],t=0,n=()=>{t--,r.length>0&&r.shift()()},s=(c,p,...m)=>{t++;let h=qn(c,...m);p(h),h.then(n,n)},i=(c,p,...m)=>{tnew Promise(m=>i(c,m,...p));return Object.defineProperties(a,{activeCount:{get:()=>t},pendingCount:{get:()=>r.length}}),a};Ve.exports=pr;Ve.exports.default=pr});var Fn={};wr(Fn,{default:()=>jn});var Se=W("@yarnpkg/cli"),ne=W("@yarnpkg/core"),et=W("@yarnpkg/core"),ue=W("clipanion"),ce=class extends Se.BaseCommand{constructor(){super(...arguments);this.json=ue.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ue.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ue.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ue.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ne.Project.find(t,this.context.cwd),i=await ne.Cache.find(t);await n.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(n.workspaces);else if(this.workspaces.length===0){if(!s)throw new Se.WorkspaceRequiredError(n.cwd,this.context.cwd);a=new Set([s])}else a=new Set(this.workspaces.map(p=>n.getWorkspaceByIdent(et.structUtils.parseIdent(p))));for(let p of a)for(let m of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let h of p.manifest.getForScope(m).values()){let R=n.tryWorkspaceByDescriptor(h);R!==null&&a.add(R)}for(let p of n.workspaces)a.has(p)?this.production&&p.manifest.devDependencies.clear():(p.manifest.installConfig=p.manifest.installConfig||{},p.manifest.installConfig.selfReferences=!1,p.manifest.dependencies.clear(),p.manifest.devDependencies.clear(),p.manifest.peerDependencies.clear(),p.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async p=>{await n.install({cache:i,report:p,persistProject:!1})})).exitCode()}};ce.paths=[["workspaces","focus"]],ce.usage=ue.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var Ne=W("@yarnpkg/cli"),ge=W("@yarnpkg/core"),_e=W("@yarnpkg/core"),Y=W("@yarnpkg/core"),gr=W("@yarnpkg/plugin-git"),U=W("clipanion"),Oe=Be(cr()),Ar=W("os"),mr=Be(hr()),te=Be(W("typanion")),pe=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:te.isOneOf([te.isEnum(["unlimited"]),te.applyCascade(te.isNumber(),[te.isInteger(),te.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!s)throw new Ne.WorkspaceRequiredError(n.cwd,this.context.cwd);await n.restoreInstallState();let i=this.cli.process([this.commandName,...this.args]),a=i.path.length===1&&i.path[0]==="run"&&typeof i.scriptName<"u"?i.scriptName:null;if(i.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let c=this.all?n.topLevelWorkspace:s,p=this.since?Array.from(await gr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:n})):[c,...this.from.length>0?c.getRecursiveWorkspaceChildren():[]],m=g=>Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.from),h=this.from.length>0?p.filter(m):p,R=new Set([...h,...h.map(g=>[...this.recursive?this.since?g.getRecursiveWorkspaceDependents():g.getRecursiveWorkspaceDependencies():g.getRecursiveWorkspaceChildren()]).flat()]),f=[],$=!1;if(a!=null&&a.includes(":")){for(let g of n.workspaces)if(g.manifest.scripts.has(a)&&($=!$,$===!1))break}for(let g of R)a&&!g.manifest.scripts.has(a)&&!$&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(g)).has(a)||a===process.env.npm_lifecycle_event&&g.cwd===s.cwd||this.include.length>0&&!Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.include)||this.exclude.length>0&&Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.exclude)||this.publicOnly&&g.manifest.private===!0||f.push(g);let _=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.max(1,(0,Ar.cpus)().length/2):1,y=_===1?!1:this.parallel,E=y?this.interlaced:!0,S=(0,mr.default)(_),T=new Map,L=new Set,z=0,I=null,re=!1,K=await _e.StreamReport.start({configuration:t,stdout:this.context.stdout},async g=>{let v=async(k,{commandIndex:l})=>{if(re)return-1;!y&&this.verbose&&l>1&&g.reportSeparator();let H=Kn(k,{configuration:t,verbose:this.verbose,commandIndex:l}),[w,B]=dr(g,{prefix:H,interlaced:E}),[o,u]=dr(g,{prefix:H,interlaced:E});try{this.verbose&&g.reportInfo(null,`${H} Process started`);let M=Date.now(),b=await this.cli.run([this.commandName,...this.args],{cwd:k.cwd,stdout:w,stderr:o})||0;w.end(),o.end(),await B,await u;let V=Date.now();if(this.verbose){let J=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,V-M,Y.formatUtils.Type.DURATION)}`:"";g.reportInfo(null,`${H} Process exited (exit code ${b})${J}`)}return b===130&&(re=!0,I=b),b}catch(M){throw w.end(),o.end(),await B,await u,M}};for(let k of f)T.set(k.anchoredLocator.locatorHash,k);for(;T.size>0&&!g.hasErrors();){let k=[];for(let[w,B]of T){if(L.has(B.anchoredDescriptor.descriptorHash))continue;let o=!0;if(this.topological||this.topologicalDev){let u=this.topologicalDev?new Map([...B.manifest.dependencies,...B.manifest.devDependencies]):B.manifest.dependencies;for(let M of u.values()){let b=n.tryWorkspaceByDescriptor(M);if(o=b===null||!T.has(b.anchoredLocator.locatorHash),!o)break}}if(!!o&&(L.add(B.anchoredDescriptor.descriptorHash),k.push(S(async()=>{let u=await v(B,{commandIndex:++z});return T.delete(w),L.delete(B.anchoredDescriptor.descriptorHash),u})),!y))break}if(k.length===0){let w=Array.from(T.values()).map(B=>Y.structUtils.prettyLocator(t,B.anchoredLocator)).join(", ");g.reportError(_e.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${w})`);return}let H=(await Promise.all(k)).find(w=>w!==0);I===null&&(I=typeof H<"u"?1:I),(this.topological||this.topologicalDev)&&typeof H<"u"&&g.reportError(_e.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return I!==null?I:K.exitCode()}};pe.paths=[["workspaces","foreach"]],pe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});function dr(e,{prefix:r,interlaced:t}){let n=e.createStreamReporter(r),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let i=new Promise(c=>{n.on("finish",()=>{c(s.active)})});if(t)return[s,i];let a=new Y.miscUtils.BufferStream;return a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()}),[a,i]}function Kn(e,{configuration:r,commandIndex:t,verbose:n}){if(!n)return null;let i=`[${Y.structUtils.stringifyIdent(e.locator)}]:`,a=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],c=a[t%a.length];return Y.formatUtils.pretty(r,i,c)}var Wn={commands:[ce,pe]},jn=Wn;return Sr(Fn);})(); 8 | /*! 9 | * fill-range 10 | * 11 | * Copyright (c) 2014-present, Jon Schlinkert. 12 | * Licensed under the MIT License. 13 | */ 14 | /*! 15 | * is-number 16 | * 17 | * Copyright (c) 2014-present, Jon Schlinkert. 18 | * Released under the MIT License. 19 | */ 20 | /*! 21 | * to-regex-range 22 | * 23 | * Copyright (c) 2015-present, Jon Schlinkert. 24 | * Released under the MIT License. 25 | */ 26 | return plugin; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmAuthToken: "${NPM_TOKEN-''}" 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 7 | spec: '@yarnpkg/plugin-interactive-tools' 8 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 9 | spec: '@yarnpkg/plugin-workspace-tools' 10 | 11 | yarnPath: .yarn/releases/yarn-3.3.0.cjs 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [6.4.2](https://github.com/pradel/react-responsive-modal/compare/v6.4.1...v6.4.2) (2023-06-19) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * fix screen glitch issue when modal is closed ([#519](https://github.com/pradel/react-responsive-modal/issues/519)) ([7c1dad2](https://github.com/pradel/react-responsive-modal/commit/7c1dad21018e5fcec6f7cfd310bd9b0e07e39a74)) 9 | 10 | 11 | ### Miscellaneous 12 | 13 | * **master:** release 6.4.1 ([16aad65](https://github.com/pradel/react-responsive-modal/commit/16aad65b5be9ebf0b7c2d03cc29cb7aa7f67dd86)) 14 | 15 | ## [6.4.1](https://github.com/pradel/react-responsive-modal/compare/v6.4.0...v6.4.1) (2022-12-12) 16 | 17 | 18 | ### Documentation 19 | 20 | * add missing containerId prop ([#494](https://github.com/pradel/react-responsive-modal/issues/494)) ([2f6454c](https://github.com/pradel/react-responsive-modal/commit/2f6454cc2eac7a18a512266e652bc3ce469f1cd5)) 21 | 22 | ## [6.4.0](https://github.com/pradel/react-responsive-modal/compare/v6.3.2...v6.4.0) (2022-12-12) 23 | 24 | 25 | ### Features 26 | 27 | * support for React 18 ([#498](https://github.com/pradel/react-responsive-modal/issues/498)) ([25e6527](https://github.com/pradel/react-responsive-modal/commit/25e6527170eacb232f3e5171572c1d2c9a8bad35)) 28 | 29 | 30 | ### Miscellaneous 31 | 32 | * fix publishing process ([6fd2540](https://github.com/pradel/react-responsive-modal/commit/6fd25408058b37b4128ed5621425e037a6f2dd2d)) 33 | * upgrade deps ([#509](https://github.com/pradel/react-responsive-modal/issues/509)) ([d08b672](https://github.com/pradel/react-responsive-modal/commit/d08b67205e9d9c0ae1fdb628ca5d0e1a1f14e390)) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Léo Pradel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./react-responsive-modal/README.md -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | 2 | [build] 3 | publish = "website/out" 4 | command = "yarn workspace react-responsive-modal build && yarn workspace website build && yarn workspace website export" 5 | 6 | [build.environment] 7 | NODE_VERSION = "12" 8 | YARN_VERSION = "1.22.5" 9 | YARN_FLAGS = "--immutable" 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "prettier": "prettier --write \"**/*.{js,ts,tsx,css,scss,json,md,mdx,yml}\"" 5 | }, 6 | "workspaces": [ 7 | "react-responsive-modal", 8 | "website" 9 | ], 10 | "prettier": { 11 | "singleQuote": true 12 | }, 13 | "husky": { 14 | "hooks": { 15 | "pre-commit": "lint-staged" 16 | } 17 | }, 18 | "lint-staged": { 19 | "*.{js,ts,tsx,css,scss,json,md,mdx,yml}": "prettier --write" 20 | }, 21 | "devDependencies": { 22 | "husky": "4.3.0", 23 | "lint-staged": "10.5.1", 24 | "prettier": "2.1.2" 25 | }, 26 | "version": "6.4.2", 27 | "packageManager": "yarn@3.3.0" 28 | } 29 | -------------------------------------------------------------------------------- /react-responsive-modal/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [6.3.2](https://github.com/pradel/react-responsive-modal/compare/v6.3.1...v6.3.2) (2022-07-21) 4 | 5 | 6 | ### Miscellaneous 7 | 8 | * fix publishing process ([2cec9a2](https://github.com/pradel/react-responsive-modal/commit/2cec9a28a4a2eff8de7de2d1550d0e9550824bbe)) 9 | 10 | ### [6.3.1](https://www.github.com/pradel/react-responsive-modal/compare/v6.3.0...v6.3.1) (2022-04-25) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * fix publishing process ([#492](https://www.github.com/pradel/react-responsive-modal/issues/492)) ([2ecd108](https://www.github.com/pradel/react-responsive-modal/commit/2ecd1084a0398a0ae0d7baa40b9b2b4e03f3d91c)) 16 | 17 | ## [6.3.0](https://www.github.com/pradel/react-responsive-modal/compare/v6.2.0...v6.3.0) (2022-04-25) 18 | 19 | 20 | ### Features 21 | 22 | * ability to specify id for container ([#489](https://www.github.com/pradel/react-responsive-modal/issues/489)) ([3e09b5c](https://www.github.com/pradel/react-responsive-modal/commit/3e09b5c668e5cbe6127c5b439d57a80a3d24bd33)) 23 | 24 | ## [6.2.0](https://www.github.com/pradel/react-responsive-modal/compare/v6.1.0...v6.2.0) (2021-12-14) 25 | 26 | 27 | ### Features 28 | 29 | * add optional reserveScrollBarGap ([#484](https://www.github.com/pradel/react-responsive-modal/issues/484)) ([69249f8](https://www.github.com/pradel/react-responsive-modal/commit/69249f8f97d02e4eaf07bedb52cb4ff1b1d4f636)) 30 | 31 | ## [6.1.0](https://www.github.com/pradel/react-responsive-modal/compare/v6.0.1...v6.1.0) (2021-06-01) 32 | 33 | 34 | ### Features 35 | 36 | * add options to set initial focus within modal ([#476](https://www.github.com/pradel/react-responsive-modal/issues/476)) ([5bdc362](https://www.github.com/pradel/react-responsive-modal/commit/5bdc362521a6db00030d723015c8abd2e76f19c7)) 37 | 38 | 39 | ### Documentation 40 | 41 | * fix typo in menu ([#473](https://www.github.com/pradel/react-responsive-modal/issues/473)) ([23cccc6](https://www.github.com/pradel/react-responsive-modal/commit/23cccc60a71342e3c122f968888118b911387056)) 42 | 43 | ### [6.0.1](https://www.github.com/pradel/react-responsive-modal/compare/v6.0.0...v6.0.1) (2021-01-08) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * **docs:** fix website dependencies ([222c6ed](https://www.github.com/pradel/react-responsive-modal/commit/222c6edef3851d2939a4fafa4d6c96e19ef35b1a)) 49 | * fix iOS problem with scroll not working in Safari ([#464](https://www.github.com/pradel/react-responsive-modal/issues/464)) ([0e38605](https://www.github.com/pradel/react-responsive-modal/commit/0e38605e37fa0e9d67ff5104e79fadfde5941bb0)) 50 | * fix release-script publishing script ([9347ad5](https://www.github.com/pradel/react-responsive-modal/commit/9347ad57d781aeca637b0527e89e34acd1cf6b3a)) 51 | * update changelogs path ([f6f3a65](https://www.github.com/pradel/react-responsive-modal/commit/f6f3a655b4d4a5ffc7f208684f439af9b20ef897)) 52 | * update README path ([fce1829](https://www.github.com/pradel/react-responsive-modal/commit/fce1829fe051ab5bef85811ad6d1d34d68bbfc5a)) 53 | 54 | 55 | ### Miscellaneous 56 | 57 | * remove unused dev dependencies ([38d2f1b](https://www.github.com/pradel/react-responsive-modal/commit/38d2f1bbda80641e857ce80ba71e995d8c44c438)) 58 | * upgrade yarn ([523536e](https://www.github.com/pradel/react-responsive-modal/commit/523536e783f82d69b8af0af9ec7dd2062af15349)) 59 | * uppdate release-please-action ([2172030](https://www.github.com/pradel/react-responsive-modal/commit/2172030427023c068644c71d8cbbe88c389ccf18)) 60 | 61 | ## [6.0.0](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.6...v6.0.0) (2020-11-15) 62 | 63 | 64 | ### ⚠ BREAKING CHANGES 65 | 66 | * fix rendering issues in Safari, Firefox by changing the structure 67 | 68 | ### Features 69 | 70 | * create E2E suite to test on real browser ([#449](https://www.github.com/pradel/react-responsive-modal/issues/449)) ([847ab1c](https://www.github.com/pradel/react-responsive-modal/commit/847ab1cac2044a6e11e3474f5fc34d7af69250bc)) 71 | * new documentation website ✨ ([#450](https://www.github.com/pradel/react-responsive-modal/issues/450)) ([3f620aa](https://www.github.com/pradel/react-responsive-modal/commit/3f620aa058c57ee251c968816a790a390edeba6e)) 72 | * switch project to monorepo ([#451](https://www.github.com/pradel/react-responsive-modal/issues/451)) ([ce59bad](https://www.github.com/pradel/react-responsive-modal/commit/ce59bad87178986bd1a87f80fd6a4489e066e614)) 73 | * upgrade focus-trap-js to support focus on radio elements ([#447](https://www.github.com/pradel/react-responsive-modal/issues/447)) ([d51f8e0](https://www.github.com/pradel/react-responsive-modal/commit/d51f8e06a81694b753d4e7777f5388bb05b69423)) 74 | * use body-scroll-lock instead of no-scroll ([#455](https://www.github.com/pradel/react-responsive-modal/issues/455)) ([033f901](https://www.github.com/pradel/react-responsive-modal/commit/033f9014b9951112da610435e0360f5ce463232b)) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * fix rendering issues in Safari, Firefox by changing the structure ([5727913](https://www.github.com/pradel/react-responsive-modal/commit/572791340fcc7b0f66e519fcbb7d4be9b998e088)) 80 | 81 | 82 | ### Tests 83 | 84 | * test that scroll is not blocked when blockScroll is false ([3d909b6](https://www.github.com/pradel/react-responsive-modal/commit/3d909b6c90261e6bd1a40de0a522ac4f85a487a8)) 85 | * test that scroll is unblocked when multiple modals are closed ([fba3593](https://www.github.com/pradel/react-responsive-modal/commit/fba35933ec6f270bbeb1fd779a6feef97b65bb82)) 86 | * test that scroll is unblocked when second modal has blockScroll set to false ([cf4b6b3](https://www.github.com/pradel/react-responsive-modal/commit/cf4b6b37ec55c24003d085cd5ad4d3bccc031bec)) 87 | 88 | 89 | ### Miscellaneous 90 | 91 | * delete `xmlns` prop from svg close icon ([#429](https://www.github.com/pradel/react-responsive-modal/issues/429)) ([8743327](https://www.github.com/pradel/react-responsive-modal/commit/87433278e10dc7077a7fddeaf6d2d088a3227bc9)) 92 | * use hook with modal manager ([#453](https://www.github.com/pradel/react-responsive-modal/issues/453)) ([b016ec4](https://www.github.com/pradel/react-responsive-modal/commit/b016ec41ff1208f0a56713c30734aae482abf3d6)) 93 | 94 | 95 | ### Documentation 96 | 97 | * **readme:** add integration tips ([4ad6d1d](https://www.github.com/pradel/react-responsive-modal/commit/4ad6d1d005fc441875cd680e4e42e1e0fb4b62cc)) 98 | * **readme:** remove dependencies badge ([5f7d0ad](https://www.github.com/pradel/react-responsive-modal/commit/5f7d0adc66783ed11b1bb0ed7610318c53dde17f)) 99 | * **readme:** remove example links ([9a06a62](https://www.github.com/pradel/react-responsive-modal/commit/9a06a62d7566380c74febf2b3d7a3e8b4268f71f)) 100 | * **readme:** update features ([ce05bd2](https://www.github.com/pradel/react-responsive-modal/commit/ce05bd2bab1605c14c4e63e8817bb81fd1aa35d4)) 101 | 102 | ### [5.2.6](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.5...v5.2.6) (2020-11-07) 103 | 104 | 105 | ### Miscellaneous 106 | 107 | * fix publishing ([f024bd5](https://www.github.com/pradel/react-responsive-modal/commit/f024bd588ff315f440cc090eb90595d6f165fb98)) 108 | 109 | ### [5.2.5](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.4...v5.2.5) (2020-11-07) 110 | 111 | 112 | ### Miscellaneous 113 | 114 | * fix build ([1a5f07c](https://www.github.com/pradel/react-responsive-modal/commit/1a5f07cb7a6f6682c01d487129309152e41b23c0)) 115 | 116 | ### [5.2.4](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.3...v5.2.4) (2020-11-07) 117 | 118 | 119 | ### Miscellaneous 120 | 121 | * trigger release ([d14af23](https://www.github.com/pradel/react-responsive-modal/commit/d14af2334292d9aaf81385ccfdcd0b7ff506a7cb)) 122 | 123 | ### [5.2.3](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.2...v5.2.3) (2020-11-07) 124 | 125 | 126 | ### Miscellaneous 127 | 128 | * upgrade dev dependencies ([6745a09](https://www.github.com/pradel/react-responsive-modal/commit/6745a09ddd26ac938f77615afc7ced8ff1703e62)) 129 | 130 | ### [5.2.2](https://www.github.com/pradel/react-responsive-modal/compare/v5.2.1...v5.2.2) (2020-11-07) 131 | 132 | 133 | ### Bug Fixes 134 | 135 | * fix closing modal via close icon ([#437](https://www.github.com/pradel/react-responsive-modal/issues/437)) ([f13ee4a](https://www.github.com/pradel/react-responsive-modal/commit/f13ee4abfce63b156f64a8cf5ea5ea50dfff4e19)) 136 | 137 | 138 | ### Tests 139 | 140 | * add tests for body scroll blocking ([#438](https://www.github.com/pradel/react-responsive-modal/issues/438)) ([f4077b8](https://www.github.com/pradel/react-responsive-modal/commit/f4077b8f0f24d9e4b12107d8ebe7382d5dafbfef)) 141 | -------------------------------------------------------------------------------- /react-responsive-modal/README.md: -------------------------------------------------------------------------------- 1 | # react-responsive-modal 2 | 3 | [![npm version](https://img.shields.io/npm/v/react-responsive-modal.svg)](https://www.npmjs.com/package/react-responsive-modal) 4 | [![npm downloads per month](https://img.shields.io/npm/dm/react-responsive-modal.svg)](https://www.npmjs.com/package/react-responsive-modal) 5 | [![codecov](https://img.shields.io/codecov/c/github/pradel/react-responsive-modal/master.svg)](https://codecov.io/gh/pradel/react-responsive-modal) 6 | 7 | A simple responsive and accessible react modal. 8 | 9 | - Focus trap inside the modal. 10 | - Centered modals. 11 | - Scrolling modals. 12 | - Multiple modals. 13 | - Accessible modals. 14 | - Easily customizable via props. 15 | - Typescript support 16 | - [Small bundle size](https://bundlephobia.com/result?p=react-responsive-modal) 17 | 18 | ## Documentation 19 | 20 | - [Getting started](https://react-responsive-modal.leopradel.com/) 21 | - [Installation](https://react-responsive-modal.leopradel.com/#installation) 22 | - [Usage](https://react-responsive-modal.leopradel.com/#usage) 23 | - [Props](https://react-responsive-modal.leopradel.com/#props) 24 | - [Licence](https://react-responsive-modal.leopradel.com/#license) 25 | 26 | ## Installation 27 | 28 | With npm: `npm install react-responsive-modal --save` 29 | 30 | Or with yarn: `yarn add react-responsive-modal` 31 | 32 | ## Usage 33 | 34 | [![Edit react-responsive-modal](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/9jxp669j2o) 35 | 36 | ```javascript 37 | import React, { useState } from 'react'; 38 | import ReactDOM from 'react-dom'; 39 | import 'react-responsive-modal/styles.css'; 40 | import { Modal } from 'react-responsive-modal'; 41 | 42 | const App = () => { 43 | const [open, setOpen] = useState(false); 44 | 45 | const onOpenModal = () => setOpen(true); 46 | const onCloseModal = () => setOpen(false); 47 | 48 | return ( 49 |
50 | 51 | 52 |

Simple centered modal

53 |
54 |
55 | ); 56 | }; 57 | 58 | ReactDOM.render(, document.getElementById('app')); 59 | ``` 60 | 61 | ## Props 62 | 63 | Check the documentation: https://react-responsive-modal.leopradel.com/#props. 64 | 65 | ## License 66 | 67 | MIT © [Léo Pradel](https://www.leopradel.com/) 68 | -------------------------------------------------------------------------------- /react-responsive-modal/__tests__/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`modal closeIcon should render the closeIcon by default 1`] = ` 4 | 19 | `; 20 | 21 | exports[`modal prop: closeIcon should render custom icon instead of the default one 1`] = ` 22 |
25 | custom icon 26 |
27 | `; 28 | -------------------------------------------------------------------------------- /react-responsive-modal/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { fireEvent, render, waitFor } from '@testing-library/react'; 3 | import { Modal } from '../src'; 4 | 5 | describe('modal', () => { 6 | describe('overlay', () => { 7 | it('should call onClose when click on the overlay', () => { 8 | const onClose = jest.fn(); 9 | const { getByTestId } = render( 10 | 11 |
modal content
12 |
13 | ); 14 | 15 | fireEvent.click(getByTestId('modal-container')); 16 | expect(onClose).toHaveBeenCalledTimes(1); 17 | }); 18 | 19 | it('should disable the handler when closeOnOverlayClick is false', () => { 20 | const onClose = jest.fn(); 21 | const { getByTestId } = render( 22 | 23 |
modal content
24 |
25 | ); 26 | 27 | fireEvent.click(getByTestId('modal-container')); 28 | expect(onClose).not.toHaveBeenCalled(); 29 | }); 30 | 31 | it('should ignore the overlay click if the event does not come from the overlay', () => { 32 | const onClose = jest.fn(); 33 | const { getByTestId } = render( 34 | 35 |
modal content
36 |
37 | ); 38 | 39 | fireEvent.click(getByTestId('modal')); 40 | expect(onClose).not.toHaveBeenCalled(); 41 | }); 42 | }); 43 | 44 | describe('key events', () => { 45 | it('an invalid event should not call onClose', () => { 46 | const onClose = jest.fn(); 47 | const { container } = render( 48 | 49 |
modal content
50 |
51 | ); 52 | 53 | fireEvent.keyDown(container, { key: 'Enter', keyCode: 13 }); 54 | expect(onClose).not.toHaveBeenCalled(); 55 | }); 56 | 57 | it('should not call onClose when closeOnEsc is false', () => { 58 | const onClose = jest.fn(); 59 | const { container } = render( 60 | 61 |
modal content
62 |
63 | ); 64 | 65 | fireEvent.keyDown(container, { keyCode: 27 }); 66 | expect(onClose).not.toHaveBeenCalled(); 67 | }); 68 | 69 | it('should call onClose when pressing esc key', () => { 70 | const onClose = jest.fn(); 71 | const { container } = render( 72 | 73 |
modal content
74 |
75 | ); 76 | 77 | fireEvent.keyDown(container, { keyCode: 27 }); 78 | expect(onClose).toHaveBeenCalledTimes(1); 79 | }); 80 | 81 | it('should call onClose of last modal only when pressing esc key when multiple modals are opened', () => { 82 | const onClose = jest.fn(); 83 | const onClose2 = jest.fn(); 84 | const { container } = render( 85 | <> 86 | 87 |
modal content
88 |
89 | 90 |
modal content
91 |
92 | 93 | ); 94 | 95 | fireEvent.keyDown(container, { keyCode: 27 }); 96 | expect(onClose).not.toHaveBeenCalled(); 97 | expect(onClose2).toHaveBeenCalledTimes(1); 98 | }); 99 | }); 100 | 101 | describe('body scroll', () => { 102 | it('should not block the scroll when modal is rendered closed', () => { 103 | render( 104 | null}> 105 |
modal content
106 |
107 | ); 108 | expect(document.body.style.overflow).toBe(''); 109 | }); 110 | 111 | it('should block the scroll when modal is rendered open', () => { 112 | render( 113 | null}> 114 |
modal content
115 |
116 | ); 117 | expect(document.body.style.overflow).toBe('hidden'); 118 | }); 119 | 120 | it('should block scroll when prop open change to true', () => { 121 | const { rerender } = render( 122 | null}> 123 |
modal content
124 |
125 | ); 126 | expect(document.body.style.overflow).toBe(''); 127 | 128 | rerender( 129 | null}> 130 |
modal content
131 |
132 | ); 133 | expect(document.body.style.overflow).toBe('hidden'); 134 | }); 135 | 136 | it('should unblock scroll when prop open change to false', async () => { 137 | const { rerender, queryByTestId, getByTestId } = render( 138 | null}> 139 |
modal content
140 |
141 | ); 142 | expect(document.body.style.overflow).toBe('hidden'); 143 | 144 | rerender( 145 | null} animationDuration={0}> 146 |
modal content
147 |
148 | ); 149 | // Simulate the browser animation end 150 | fireEvent.animationEnd(getByTestId('modal')); 151 | await waitFor( 152 | () => { 153 | expect(queryByTestId('modal')).not.toBeInTheDocument(); 154 | }, 155 | { timeout: 1 } 156 | ); 157 | 158 | expect(document.body.style.overflow).toBe(''); 159 | }); 160 | 161 | it('should unblock scroll when unmounted directly', async () => { 162 | const { unmount } = render( 163 | null}> 164 |
modal content
165 |
166 | ); 167 | expect(document.body.style.overflow).toBe('hidden'); 168 | 169 | unmount(); 170 | expect(document.body.style.overflow).toBe(''); 171 | }); 172 | 173 | it('should unblock scroll when multiple modals are opened and then closed', async () => { 174 | const { rerender, getAllByTestId, queryByText } = render( 175 | 176 | null}> 177 |
first modal
178 |
179 | null}> 180 |
second modal
181 |
182 |
183 | ); 184 | expect(document.body.style.overflow).toBe('hidden'); 185 | 186 | // We close one modal, the scroll should be locked 187 | rerender( 188 | 189 | null}> 190 |
first modal
191 |
192 | null}> 193 |
second modal
194 |
195 |
196 | ); 197 | 198 | fireEvent.animationEnd(getAllByTestId('modal')[1]); 199 | await waitFor( 200 | () => { 201 | expect(queryByText(/second modal/)).not.toBeInTheDocument(); 202 | }, 203 | { timeout: 1 } 204 | ); 205 | expect(document.body.style.overflow).toBe('hidden'); 206 | 207 | // We close the second modal, the scroll should be unlocked 208 | rerender( 209 | 210 | null}> 211 |
first modal
212 |
213 | null}> 214 |
second modal
215 |
216 |
217 | ); 218 | 219 | fireEvent.animationEnd(getAllByTestId('modal')[0]); 220 | await waitFor( 221 | () => { 222 | expect(queryByText(/first modal/)).not.toBeInTheDocument(); 223 | }, 224 | { timeout: 1 } 225 | ); 226 | expect(document.body.style.overflow).toBe(''); 227 | }); 228 | 229 | it('should unblock scroll when one modal is closed and the one still open has blockScroll set to false', async () => { 230 | const { rerender, getAllByTestId, queryByText } = render( 231 | 232 | null}> 233 |
first modal
234 |
235 | null}> 236 |
second modal
237 |
238 |
239 | ); 240 | expect(document.body.style.overflow).toBe('hidden'); 241 | 242 | // We close one modal, the scroll should be unlocked as remaining modal is not locking the scroll 243 | rerender( 244 | 245 | null}> 246 |
first modal
247 |
248 | null}> 249 |
second modal
250 |
251 |
252 | ); 253 | 254 | fireEvent.animationEnd(getAllByTestId('modal')[1]); 255 | await waitFor( 256 | () => { 257 | expect(queryByText(/second modal/)).not.toBeInTheDocument(); 258 | }, 259 | { timeout: 1 } 260 | ); 261 | expect(document.body.style.overflow).toBe(''); 262 | }); 263 | it('should reserve scroll bar gap', () => { 264 | const scrollBarWidth = 42; 265 | const innerWidth = 500; 266 | Object.defineProperty(window, 'innerWidth', { 267 | writable: true, 268 | configurable: true, 269 | value: innerWidth, 270 | }); 271 | Object.defineProperty(document.documentElement, 'clientWidth', { 272 | writable: true, 273 | configurable: true, 274 | value: innerWidth - scrollBarWidth, 275 | }); 276 | render( 277 | null} reserveScrollBarGap={true}> 278 |
modal content
279 |
280 | ); 281 | expect(document.body.style.paddingRight).toBe(`${scrollBarWidth}px`); 282 | }); 283 | }); 284 | 285 | describe('closeIcon', () => { 286 | it('should render the closeIcon by default', () => { 287 | const { getByTestId } = render( 288 | null}> 289 |
modal content
290 |
291 | ); 292 | 293 | expect(getByTestId('close-button')).toMatchSnapshot(); 294 | }); 295 | 296 | it('should hide closeIcon when showCloseIcon is false', () => { 297 | const { queryByTestId } = render( 298 | null} showCloseIcon={false}> 299 |
modal content
300 |
301 | ); 302 | 303 | expect(queryByTestId('close-button')).toBeNull(); 304 | }); 305 | 306 | it('should call onClose when clicking on the icon', () => { 307 | const onClose = jest.fn(); 308 | const { getByTestId } = render( 309 | 310 |
modal content
311 |
312 | ); 313 | 314 | fireEvent.click(getByTestId('close-button')); 315 | expect(onClose).toHaveBeenCalledTimes(1); 316 | }); 317 | }); 318 | 319 | describe('render', () => { 320 | it('should render null when then modal is not open', () => { 321 | const { queryByText } = render( 322 | null}> 323 |
modal content
324 |
325 | ); 326 | expect(queryByText(/modal content/)).toBeNull(); 327 | }); 328 | 329 | it('should render the content when modal is open', () => { 330 | const { queryByText } = render( 331 | null}> 332 |
modal content
333 |
334 | ); 335 | expect(queryByText(/modal content/)).toBeTruthy(); 336 | }); 337 | }); 338 | 339 | describe('lifecycle', () => { 340 | it('should show modal when prop open change to true', () => { 341 | const { queryByTestId, rerender } = render( 342 | null}> 343 |
modal content
344 |
345 | ); 346 | expect(queryByTestId('modal')).toBeNull(); 347 | rerender( 348 | null}> 349 |
modal content
350 |
351 | ); 352 | expect(queryByTestId('modal')).toBeTruthy(); 353 | }); 354 | 355 | it('should hide modal when prop open change to false', async () => { 356 | const { getByTestId, queryByTestId, rerender } = render( 357 | null} animationDuration={0.01}> 358 |
modal content
359 |
360 | ); 361 | expect(queryByTestId('modal')).toBeTruthy(); 362 | rerender( 363 | null} animationDuration={0.01}> 364 |
modal content
365 |
366 | ); 367 | fireEvent.animationEnd(getByTestId('modal')); 368 | expect(queryByTestId('modal')).toBeNull(); 369 | }); 370 | }); 371 | 372 | describe('prop: center', () => { 373 | it('should not apply center class by default', async () => { 374 | const { getByTestId } = render( 375 | null}> 376 |
modal content
377 |
378 | ); 379 | 380 | expect(getByTestId('modal-container').classList.length).toBe(1); 381 | expect( 382 | getByTestId('modal-container').classList.contains( 383 | 'react-responsive-modal-containerCenter' 384 | ) 385 | ).toBeFalsy(); 386 | }); 387 | 388 | it('should apply center class to modal', async () => { 389 | const { getByTestId } = render( 390 | null} center> 391 |
modal content
392 |
393 | ); 394 | 395 | expect(getByTestId('modal-container').classList.length).toBe(2); 396 | expect( 397 | getByTestId('modal-container').classList.contains( 398 | 'react-responsive-modal-containerCenter' 399 | ) 400 | ).toBeTruthy(); 401 | }); 402 | }); 403 | 404 | describe('prop: closeIcon', () => { 405 | it('should render custom icon instead of the default one', async () => { 406 | const { queryByTestId, getByTestId } = render( 407 | null} 410 | closeIcon={
custom icon
} 411 | > 412 |
modal content
413 |
414 | ); 415 | 416 | expect(queryByTestId('close-icon')).toBeNull(); 417 | expect(getByTestId('custom-icon')).toMatchSnapshot(); 418 | }); 419 | }); 420 | 421 | describe('prop: classNames', () => { 422 | it('should apply custom classes to the modal', async () => { 423 | const { getByTestId } = render( 424 | null} 427 | classNames={{ 428 | overlay: 'custom-overlay', 429 | modal: 'custom-modal', 430 | closeButton: 'custom-closeButton', 431 | closeIcon: 'custom-closeIcon', 432 | }} 433 | > 434 |
modal content
435 |
436 | ); 437 | 438 | expect( 439 | getByTestId('overlay').classList.contains('custom-overlay') 440 | ).toBeTruthy(); 441 | expect( 442 | getByTestId('modal').classList.contains('custom-modal') 443 | ).toBeTruthy(); 444 | expect( 445 | getByTestId('close-button').classList.contains('custom-closeButton') 446 | ).toBeTruthy(); 447 | expect( 448 | getByTestId('close-icon').classList.contains('custom-closeIcon') 449 | ).toBeTruthy(); 450 | }); 451 | }); 452 | 453 | describe('prop: blockScroll', () => { 454 | it('should not block the scroll when modal is opened and blockScroll is false', () => { 455 | render( 456 | null}> 457 |
modal content
458 |
459 | ); 460 | expect(document.body.style.overflow).toBe(''); 461 | }); 462 | }); 463 | 464 | describe('prop: onEscKeyDown', () => { 465 | it('should be called when esc key is pressed', async () => { 466 | const onEscKeyDown = jest.fn(); 467 | const { container } = render( 468 | null} onEscKeyDown={onEscKeyDown}> 469 |
modal content
470 |
471 | ); 472 | 473 | fireEvent.keyDown(container, { keyCode: 27 }); 474 | expect(onEscKeyDown).toHaveBeenCalledTimes(1); 475 | }); 476 | }); 477 | 478 | describe('prop: onOverlayClick', () => { 479 | it('should be called when user click on overlay', async () => { 480 | const onOverlayClick = jest.fn(); 481 | const { getByTestId } = render( 482 | null} onOverlayClick={onOverlayClick}> 483 |
modal content
484 |
485 | ); 486 | 487 | fireEvent.click(getByTestId('modal-container')); 488 | expect(onOverlayClick).toHaveBeenCalledTimes(1); 489 | }); 490 | }); 491 | 492 | describe('prop: onAnimationEnd', () => { 493 | it('should be called when the animation is finished', async () => { 494 | const onAnimationEnd = jest.fn(); 495 | const { getByTestId } = render( 496 | null} onAnimationEnd={onAnimationEnd}> 497 |
modal content
498 |
499 | ); 500 | 501 | fireEvent.animationEnd(getByTestId('modal')); 502 | expect(onAnimationEnd).toHaveBeenCalledTimes(1); 503 | }); 504 | }); 505 | 506 | describe('prop: containerId', () => { 507 | it('should renders container div with id', async () => { 508 | const containerId = 'container-id'; 509 | const { getByTestId } = render( 510 | null} containerId={containerId}> 511 |
modal content
512 |
513 | ); 514 | 515 | const containerModal = getByTestId('modal-container'); 516 | expect(containerModal.getAttribute('id')).toBe(containerId); 517 | expect(document.getElementById(containerId)).toBeInTheDocument(); 518 | }); 519 | }); 520 | 521 | describe('prop: modalId', () => { 522 | it('should renders modal div with id', async () => { 523 | const modalId = 'modal-id'; 524 | const { getByTestId } = render( 525 | null} modalId={modalId}> 526 |
modal content
527 |
528 | ); 529 | 530 | const modal = getByTestId('modal'); 531 | expect(modal.getAttribute('id')).toBe(modalId); 532 | expect(document.getElementById(modalId)).toBeInTheDocument(); 533 | }); 534 | }); 535 | }); 536 | -------------------------------------------------------------------------------- /react-responsive-modal/__tests__/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | 3 | window.scroll = jest.fn(); 4 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress/integration/modal.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('simple modal', () => { 4 | beforeEach(() => { 5 | cy.visit('http://localhost:3000'); 6 | // Page is heavy to load so we wait for it to be loaded 7 | cy.wait(500); 8 | }); 9 | 10 | it('should open modal when clicking open button', () => { 11 | cy.get('button').eq(0).click(); 12 | cy.get('[data-testid=modal]').should('exist'); 13 | }); 14 | 15 | // TODO overlay not working, see how to fix 16 | // it('should close modal when clicking overlay', () => { 17 | // cy.get('button').eq(0).click(); 18 | // cy.get('[data-testid=overlay]').click(); 19 | // cy.get('[data-testid=modal]').should('not.exist'); 20 | // }); 21 | 22 | it('should close modal when clicking the close icon', () => { 23 | cy.get('button').eq(0).click(); 24 | cy.get('[data-testid=close-button]').click(); 25 | cy.get('[data-testid=modal]').should('not.exist'); 26 | }); 27 | 28 | it('should close modal when pressing esc key', () => { 29 | cy.get('button').eq(0).click(); 30 | cy.get('body').type('{esc}'); 31 | cy.get('[data-testid=modal]').should('not.exist'); 32 | }); 33 | 34 | it('should close only last modal when pressing esc key when multiple modals are opened', () => { 35 | cy.get('button').eq(1).click(); 36 | cy.get('[data-testid=modal] button').eq(0).click(); 37 | cy.get('[data-testid=modal]').should('have.length', 2); 38 | cy.get('body').type('{esc}'); 39 | cy.get('[data-testid=modal]').should('have.length', 1); 40 | cy.get('body').type('{esc}'); 41 | cy.get('[data-testid=modal]').should('not.exist'); 42 | }); 43 | 44 | it('should block the scroll when modal is opened', () => { 45 | cy.get('button').eq(0).click(); 46 | cy.get('body').should('have.css', 'overflow', 'hidden'); 47 | }); 48 | 49 | it('should unblock the scroll when modal is closed', () => { 50 | cy.get('button').eq(0).click(); 51 | cy.get('body').should('have.css', 'overflow', 'hidden'); 52 | cy.get('body').type('{esc}'); 53 | cy.get('body').should('not.have.css', 'overflow', 'hidden'); 54 | }); 55 | 56 | it('should unblock scroll only after last modal is closed when multiple modals are opened', () => { 57 | cy.get('button').eq(1).click(); 58 | cy.get('[data-testid=modal] button').eq(0).click(); 59 | cy.get('[data-testid=modal]').should('have.length', 2); 60 | cy.get('body').should('have.css', 'overflow', 'hidden'); 61 | cy.get('body').type('{esc}'); 62 | cy.get('[data-testid=modal]').should('have.length', 1); 63 | cy.get('body').should('have.css', 'overflow', 'hidden'); 64 | cy.get('body').type('{esc}'); 65 | cy.get('[data-testid=modal]').should('not.exist'); 66 | cy.get('body').should('not.have.css', 'overflow', 'hidden'); 67 | }); 68 | 69 | it('should focus first element within modal', () => { 70 | cy.get('button').eq(3).click(); 71 | cy.get('[data-testid=modal] input').first().should('have.focus'); 72 | }); 73 | 74 | it('should focus on modal root', () => { 75 | cy.get('button').eq(4).click(); 76 | cy.get('[data-testid=modal]').should('have.focus'); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | }; 22 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /react-responsive-modal/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /react-responsive-modal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-responsive-modal", 3 | "version": "6.4.1", 4 | "description": "A simple responsive and accessible react modal", 5 | "license": "MIT", 6 | "main": "dist/index.js", 7 | "module": "dist/react-responsive-modal.esm.js", 8 | "typings": "dist/index.d.ts", 9 | "scripts": { 10 | "start": "tsdx watch", 11 | "build": "tsdx build", 12 | "test": "tsdx test --passWithNoTests", 13 | "lint": "tsdx lint", 14 | "prepare": "tsdx build", 15 | "size": "size-limit" 16 | }, 17 | "files": [ 18 | "dist", 19 | "src", 20 | "styles.css" 21 | ], 22 | "jest": { 23 | "setupFilesAfterEnv": [ 24 | "./__tests__/setupTests.ts" 25 | ], 26 | "modulePathIgnorePatterns": [ 27 | "cypress" 28 | ], 29 | "coveragePathIgnorePatterns": [ 30 | "src/lib" 31 | ] 32 | }, 33 | "keywords": [ 34 | "react", 35 | "responsive", 36 | "modal", 37 | "mobile", 38 | "flex" 39 | ], 40 | "repository": "git+https://github.com/pradel/react-responsive-modal.git", 41 | "author": "Léo Pradel", 42 | "bugs": { 43 | "url": "https://github.com/pradel/react-responsive-modal/issues" 44 | }, 45 | "homepage": "https://react-responsive-modal.leopradel.com/", 46 | "funding": "https://github.com/sponsors/pradel", 47 | "size-limit": [ 48 | { 49 | "path": "dist/react-responsive-modal.cjs.production.min.js", 50 | "limit": "4.1 KB" 51 | }, 52 | { 53 | "path": "dist/react-responsive-modal.esm.js", 54 | "limit": "4.1 KB" 55 | } 56 | ], 57 | "dependencies": { 58 | "@bedrock-layout/use-forwarded-ref": "^1.3.1", 59 | "body-scroll-lock": "^3.1.5", 60 | "classnames": "^2.3.1" 61 | }, 62 | "peerDependencies": { 63 | "react": "^16.8.0 || ^17 || ^18", 64 | "react-dom": "^16.8.0 || ^17 || ^18" 65 | }, 66 | "devDependencies": { 67 | "@size-limit/preset-small-lib": "4.7.0", 68 | "@testing-library/jest-dom": "5.11.6", 69 | "@testing-library/react": "11.1.2", 70 | "@types/body-scroll-lock": "2.6.1", 71 | "@types/classnames": "2.2.11", 72 | "@types/node": "14.14.7", 73 | "@types/react": "16.9.56", 74 | "@types/react-dom": "16.9.9", 75 | "babel-jest": "26.6.3", 76 | "cypress": "5.6.0", 77 | "husky": "4.3.0", 78 | "prettier": "2.1.2", 79 | "react": "17.0.1", 80 | "react-dom": "17.0.1", 81 | "size-limit": "4.7.0", 82 | "tsdx": "0.14.1", 83 | "tslib": "2.0.3", 84 | "typescript": "4.0.5" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /react-responsive-modal/src/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cx from 'classnames'; 3 | 4 | interface CloseIconProps { 5 | id?: string; 6 | closeIcon?: React.ReactNode; 7 | styles?: { 8 | closeButton?: React.CSSProperties; 9 | closeIcon?: React.CSSProperties; 10 | }; 11 | classNames?: { 12 | closeButton?: string; 13 | closeIcon?: string; 14 | }; 15 | classes: { 16 | closeButton?: string; 17 | }; 18 | onClick: () => void; 19 | } 20 | 21 | const CloseIcon = ({ 22 | classes, 23 | classNames, 24 | styles, 25 | id, 26 | closeIcon, 27 | onClick, 28 | }: CloseIconProps) => ( 29 | 51 | ); 52 | 53 | export default CloseIcon; 54 | -------------------------------------------------------------------------------- /react-responsive-modal/src/FocusTrap.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { isBrowser } from './utils'; 3 | import { 4 | tabTrappingKey, 5 | candidateSelectors, 6 | getAllTabbingElements, 7 | } from './lib/focusTrapJs'; 8 | 9 | interface FocusTrapProps { 10 | container?: React.RefObject | null; 11 | initialFocusRef?: React.RefObject; 12 | } 13 | 14 | export const FocusTrap = ({ container, initialFocusRef }: FocusTrapProps) => { 15 | const refLastFocus = useRef(); 16 | /** 17 | * Handle focus lock on the modal 18 | */ 19 | useEffect(() => { 20 | const handleKeyEvent = (event: KeyboardEvent) => { 21 | if (container?.current) { 22 | tabTrappingKey(event, container.current); 23 | } 24 | }; 25 | 26 | if (isBrowser) { 27 | document.addEventListener('keydown', handleKeyEvent); 28 | } 29 | // On mount we focus on the first focusable element in the modal if there is one 30 | if (isBrowser && container?.current) { 31 | const savePreviousFocus = () => { 32 | // First we save the last focused element 33 | // only if it's a focusable element 34 | if ( 35 | candidateSelectors.findIndex((selector) => 36 | document.activeElement?.matches(selector) 37 | ) !== -1 38 | ) { 39 | refLastFocus.current = document.activeElement as HTMLElement; 40 | } 41 | }; 42 | 43 | if (initialFocusRef) { 44 | savePreviousFocus(); 45 | // We need to schedule focusing on a next frame - this allows to focus on the modal root 46 | requestAnimationFrame(() => { 47 | initialFocusRef.current?.focus(); 48 | }); 49 | } else { 50 | const allTabbingElements = getAllTabbingElements(container.current); 51 | if (allTabbingElements[0]) { 52 | savePreviousFocus(); 53 | allTabbingElements[0].focus(); 54 | } 55 | } 56 | } 57 | return () => { 58 | if (isBrowser) { 59 | document.removeEventListener('keydown', handleKeyEvent); 60 | // On unmount we restore the focus to the last focused element 61 | refLastFocus.current?.focus(); 62 | } 63 | }; 64 | }, [container, initialFocusRef]); 65 | 66 | return null; 67 | }; 68 | -------------------------------------------------------------------------------- /react-responsive-modal/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import cx from 'classnames'; 4 | import CloseIcon from './CloseIcon'; 5 | import { FocusTrap } from './FocusTrap'; 6 | import { modalManager, useModalManager } from './modalManager'; 7 | import { useScrollLock } from './useScrollLock'; 8 | import { isBrowser } from './utils'; 9 | import useForwardedRef from '@bedrock-layout/use-forwarded-ref'; 10 | 11 | const classes = { 12 | root: 'react-responsive-modal-root', 13 | overlay: 'react-responsive-modal-overlay', 14 | overlayAnimationIn: 'react-responsive-modal-overlay-in', 15 | overlayAnimationOut: 'react-responsive-modal-overlay-out', 16 | modalContainer: 'react-responsive-modal-container', 17 | modalContainerCenter: 'react-responsive-modal-containerCenter', 18 | modal: 'react-responsive-modal-modal', 19 | modalAnimationIn: 'react-responsive-modal-modal-in', 20 | modalAnimationOut: 'react-responsive-modal-modal-out', 21 | closeButton: 'react-responsive-modal-closeButton', 22 | }; 23 | 24 | export interface ModalProps { 25 | /** 26 | * Control if the modal is open or not. 27 | */ 28 | open: boolean; 29 | /** 30 | * Should the dialog be centered. 31 | * 32 | * Default to false. 33 | */ 34 | center?: boolean; 35 | /** 36 | * Is the modal closable when user press esc key. 37 | * 38 | * Default to true. 39 | */ 40 | closeOnEsc?: boolean; 41 | /** 42 | * Is the modal closable when user click on overlay. 43 | * 44 | * Default to true. 45 | */ 46 | closeOnOverlayClick?: boolean; 47 | /** 48 | * Whether to block scrolling when dialog is open. 49 | * 50 | * Default to true. 51 | */ 52 | blockScroll?: boolean; 53 | /** 54 | * Show the close icon. 55 | * 56 | * Default to true. 57 | */ 58 | showCloseIcon?: boolean; 59 | /** 60 | * id attribute for the close icon button. 61 | */ 62 | closeIconId?: string; 63 | /** 64 | * Custom icon to render (svg, img, etc...). 65 | */ 66 | closeIcon?: React.ReactNode; 67 | /** 68 | * When the modal is open, trap focus within it. 69 | * 70 | * Default to true. 71 | */ 72 | focusTrapped?: boolean; 73 | /** 74 | * Element to focus when focus trap is used. 75 | * 76 | * Default to undefined. 77 | */ 78 | initialFocusRef?: React.RefObject; 79 | /** 80 | * You can specify a container prop which should be of type `Element`. 81 | * The portal will be rendered inside that element. 82 | * The default behavior will create a div node and render it at the at the end of document.body. 83 | */ 84 | container?: Element | null; 85 | /** 86 | * An object containing classNames to style the modal. 87 | */ 88 | classNames?: { 89 | root?: string; 90 | overlay?: string; 91 | overlayAnimationIn?: string; 92 | overlayAnimationOut?: string; 93 | modalContainer?: string; 94 | modal?: string; 95 | modalAnimationIn?: string; 96 | modalAnimationOut?: string; 97 | closeButton?: string; 98 | closeIcon?: string; 99 | }; 100 | /** 101 | * An object containing the styles objects to style the modal. 102 | */ 103 | styles?: { 104 | root?: React.CSSProperties; 105 | overlay?: React.CSSProperties; 106 | modalContainer?: React.CSSProperties; 107 | modal?: React.CSSProperties; 108 | closeButton?: React.CSSProperties; 109 | closeIcon?: React.CSSProperties; 110 | }; 111 | /** 112 | * Animation duration in milliseconds. 113 | * 114 | * Default to 300. 115 | */ 116 | animationDuration?: number; 117 | /** 118 | * ARIA role for modal 119 | * 120 | * Default to 'dialog'. 121 | */ 122 | role?: string; 123 | /** 124 | * ARIA label for modal 125 | */ 126 | ariaLabelledby?: string; 127 | /** 128 | * ARIA description for modal 129 | */ 130 | ariaDescribedby?: string; 131 | /** 132 | * Avoid unpleasant flickering effect when body overflow is hidden. For more information see https://www.npmjs.com/package/body-scroll-lock 133 | */ 134 | reserveScrollBarGap?: boolean; 135 | /** 136 | * id attribute for modal container 137 | */ 138 | containerId?: string; 139 | /** 140 | * id attribute for modal 141 | */ 142 | modalId?: string; 143 | /** 144 | * Callback fired when the Modal is requested to be closed by a click on the overlay or when user press esc key. 145 | */ 146 | onClose: () => void; 147 | /** 148 | * Callback fired when the escape key is pressed. 149 | */ 150 | onEscKeyDown?: (event: KeyboardEvent) => void; 151 | /** 152 | * Callback fired when the overlay is clicked. 153 | */ 154 | onOverlayClick?: ( 155 | event: React.MouseEvent 156 | ) => void; 157 | /** 158 | * Callback fired when the Modal has exited and the animation is finished. 159 | */ 160 | onAnimationEnd?: () => void; 161 | children?: React.ReactNode; 162 | } 163 | 164 | export const Modal = React.forwardRef( 165 | ( 166 | { 167 | open, 168 | center, 169 | blockScroll = true, 170 | closeOnEsc = true, 171 | closeOnOverlayClick = true, 172 | container, 173 | showCloseIcon = true, 174 | closeIconId, 175 | closeIcon, 176 | focusTrapped = true, 177 | initialFocusRef = undefined, 178 | animationDuration = 300, 179 | classNames, 180 | styles, 181 | role = 'dialog', 182 | ariaDescribedby, 183 | ariaLabelledby, 184 | containerId, 185 | modalId, 186 | onClose, 187 | onEscKeyDown, 188 | onOverlayClick, 189 | onAnimationEnd, 190 | children, 191 | reserveScrollBarGap, 192 | }: ModalProps, 193 | ref: React.ForwardedRef 194 | ) => { 195 | const refDialog = useForwardedRef(ref); 196 | const refModal = useRef(null); 197 | const refShouldClose = useRef(null); 198 | const refContainer = useRef(null); 199 | // Lazily create the ref instance 200 | // https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily 201 | if (refContainer.current === null && isBrowser) { 202 | refContainer.current = document.createElement('div'); 203 | } 204 | 205 | // The value should be false for srr, that way when the component is hydrated client side, 206 | // it will match the server rendered content 207 | const [showPortal, setShowPortal] = useState(false); 208 | 209 | // Hook used to manage multiple modals opened at the same time 210 | useModalManager(refModal, open); 211 | 212 | // Hook used to manage the scroll 213 | useScrollLock(refModal, open, showPortal, blockScroll, reserveScrollBarGap); 214 | 215 | const handleOpen = () => { 216 | if ( 217 | refContainer.current && 218 | !container && 219 | !document.body.contains(refContainer.current) 220 | ) { 221 | document.body.appendChild(refContainer.current); 222 | } 223 | 224 | document.addEventListener('keydown', handleKeydown); 225 | }; 226 | 227 | const handleClose = () => { 228 | if ( 229 | refContainer.current && 230 | !container && 231 | document.body.contains(refContainer.current) 232 | ) { 233 | document.body.removeChild(refContainer.current); 234 | } 235 | document.removeEventListener('keydown', handleKeydown); 236 | }; 237 | 238 | const handleKeydown = (event: KeyboardEvent) => { 239 | // Only the last modal need to be escaped when pressing the esc key 240 | if (event.keyCode !== 27 || !modalManager.isTopModal(refModal)) { 241 | return; 242 | } 243 | 244 | onEscKeyDown?.(event); 245 | 246 | if (closeOnEsc) { 247 | onClose(); 248 | } 249 | }; 250 | 251 | useEffect(() => { 252 | return () => { 253 | if (showPortal) { 254 | // When the modal is closed or removed directly, cleanup the listeners 255 | handleClose(); 256 | } 257 | }; 258 | }, [showPortal]); 259 | 260 | useEffect(() => { 261 | // If the open prop is changing, we need to open the modal 262 | // This is also called on the first render if the open prop is true when the modal is created 263 | if (open && !showPortal) { 264 | setShowPortal(true); 265 | handleOpen(); 266 | } 267 | }, [open]); 268 | 269 | const handleClickOverlay = ( 270 | event: React.MouseEvent 271 | ) => { 272 | if (refShouldClose.current === null) { 273 | refShouldClose.current = true; 274 | } 275 | 276 | if (!refShouldClose.current) { 277 | refShouldClose.current = null; 278 | return; 279 | } 280 | 281 | onOverlayClick?.(event); 282 | 283 | if (closeOnOverlayClick) { 284 | onClose(); 285 | } 286 | 287 | refShouldClose.current = null; 288 | }; 289 | 290 | const handleModalEvent = () => { 291 | refShouldClose.current = false; 292 | }; 293 | 294 | const handleAnimationEnd = () => { 295 | if (!open) { 296 | setShowPortal(false); 297 | } 298 | 299 | onAnimationEnd?.(); 300 | }; 301 | 302 | const containerModal = container || refContainer.current; 303 | 304 | const overlayAnimation = open 305 | ? classNames?.overlayAnimationIn ?? classes.overlayAnimationIn 306 | : classNames?.overlayAnimationOut ?? classes.overlayAnimationOut; 307 | 308 | const modalAnimation = open 309 | ? classNames?.modalAnimationIn ?? classes.modalAnimationIn 310 | : classNames?.modalAnimationOut ?? classes.modalAnimationOut; 311 | 312 | return showPortal && containerModal 313 | ? ReactDom.createPortal( 314 |
319 |
328 |
340 |
359 | {focusTrapped && ( 360 | 364 | )} 365 | {children} 366 | {showCloseIcon && ( 367 | 375 | )} 376 |
377 |
378 |
, 379 | containerModal 380 | ) 381 | : null; 382 | } 383 | ); 384 | 385 | export default Modal; 386 | -------------------------------------------------------------------------------- /react-responsive-modal/src/lib/focusTrapJs.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/alexandrzavalii/focus-trap-js/blob/master/src/index.js v1.1.0 2 | 3 | export const candidateSelectors = [ 4 | 'input', 5 | 'select', 6 | 'textarea', 7 | 'a[href]', 8 | 'button', 9 | '[tabindex]', 10 | 'audio[controls]', 11 | 'video[controls]', 12 | '[contenteditable]:not([contenteditable="false"])', 13 | ]; 14 | 15 | function isHidden(node: any) { 16 | // offsetParent being null will allow detecting cases where an element is invisible or inside an invisible element, 17 | // as long as the element does not use position: fixed. For them, their visibility has to be checked directly as well. 18 | return ( 19 | node.offsetParent === null || getComputedStyle(node).visibility === 'hidden' 20 | ); 21 | } 22 | 23 | function getCheckedRadio(nodes: any, form: any) { 24 | for (var i = 0; i < nodes.length; i++) { 25 | if (nodes[i].checked && nodes[i].form === form) { 26 | return nodes[i]; 27 | } 28 | } 29 | } 30 | 31 | function isNotRadioOrTabbableRadio(node: any) { 32 | if (node.tagName !== 'INPUT' || node.type !== 'radio' || !node.name) { 33 | return true; 34 | } 35 | var radioScope = node.form || node.ownerDocument; 36 | var radioSet = radioScope.querySelectorAll( 37 | 'input[type="radio"][name="' + node.name + '"]' 38 | ); 39 | var checked = getCheckedRadio(radioSet, node.form); 40 | return checked === node || (checked === undefined && radioSet[0] === node); 41 | } 42 | 43 | export function getAllTabbingElements(parentElem: any) { 44 | var currentActiveElement = document.activeElement; 45 | var tabbableNodes = parentElem.querySelectorAll(candidateSelectors.join(',')); 46 | var onlyTabbable = []; 47 | for (var i = 0; i < tabbableNodes.length; i++) { 48 | var node = tabbableNodes[i]; 49 | if ( 50 | currentActiveElement === node || 51 | (!node.disabled && 52 | getTabindex(node) > -1 && 53 | !isHidden(node) && 54 | isNotRadioOrTabbableRadio(node)) 55 | ) { 56 | onlyTabbable.push(node); 57 | } 58 | } 59 | return onlyTabbable; 60 | } 61 | 62 | export function tabTrappingKey(event: any, parentElem: any) { 63 | // check if current event keyCode is tab 64 | if (!event || event.key !== 'Tab') return; 65 | 66 | if (!parentElem || !parentElem.contains) { 67 | if (process && process.env.NODE_ENV === 'development') { 68 | console.warn('focus-trap-js: parent element is not defined'); 69 | } 70 | return false; 71 | } 72 | 73 | if (!parentElem.contains(event.target)) { 74 | return false; 75 | } 76 | 77 | var allTabbingElements = getAllTabbingElements(parentElem); 78 | var firstFocusableElement = allTabbingElements[0]; 79 | var lastFocusableElement = allTabbingElements[allTabbingElements.length - 1]; 80 | 81 | if (event.shiftKey && event.target === firstFocusableElement) { 82 | lastFocusableElement.focus(); 83 | event.preventDefault(); 84 | return true; 85 | } else if (!event.shiftKey && event.target === lastFocusableElement) { 86 | firstFocusableElement.focus(); 87 | event.preventDefault(); 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | function getTabindex(node: any) { 94 | var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); 95 | 96 | if (!isNaN(tabindexAttr)) return tabindexAttr; 97 | // Browsers do not return tabIndex correctly for contentEditable nodes; 98 | // so if they don't have a tabindex attribute specifically set, assume it's 0. 99 | 100 | if (isContentEditable(node)) return 0; 101 | return node.tabIndex; 102 | } 103 | 104 | function isContentEditable(node: any) { 105 | return node.getAttribute('contentEditable'); 106 | } 107 | -------------------------------------------------------------------------------- /react-responsive-modal/src/modalManager.ts: -------------------------------------------------------------------------------- 1 | import { Ref, useEffect } from 'react'; 2 | 3 | let modals: Ref[] = []; 4 | 5 | /** 6 | * Handle the order of the modals. 7 | * Inspired by the material-ui implementation. 8 | */ 9 | export const modalManager = { 10 | /** 11 | * Register a new modal 12 | */ 13 | add: (newModal: Ref) => { 14 | modals.push(newModal); 15 | }, 16 | 17 | /** 18 | * Remove a modal 19 | */ 20 | remove: (oldModal: Ref) => { 21 | modals = modals.filter((modal) => modal !== oldModal); 22 | }, 23 | 24 | /** 25 | * When multiple modals are rendered will return true if current modal is the last one 26 | */ 27 | isTopModal: (modal: Ref) => 28 | !!modals.length && modals[modals.length - 1] === modal, 29 | }; 30 | 31 | export function useModalManager(ref: Ref, open: boolean) { 32 | useEffect(() => { 33 | if (open) { 34 | modalManager.add(ref); 35 | } 36 | return () => { 37 | modalManager.remove(ref); 38 | }; 39 | }, [open, ref]); 40 | } 41 | -------------------------------------------------------------------------------- /react-responsive-modal/src/useScrollLock.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'; 3 | 4 | export const useScrollLock = ( 5 | refModal: React.RefObject, 6 | open: boolean, 7 | showPortal: boolean, 8 | blockScroll: boolean, 9 | reserveScrollBarGap?: boolean 10 | ) => { 11 | const oldRef = useRef(null); 12 | 13 | useEffect(() => { 14 | if (open && refModal.current && blockScroll) { 15 | oldRef.current = refModal.current; 16 | disableBodyScroll(refModal.current, { reserveScrollBarGap }); 17 | } 18 | return () => { 19 | if (oldRef.current) { 20 | enableBodyScroll(oldRef.current); 21 | oldRef.current = null; 22 | } 23 | }; 24 | }, [open, showPortal, refModal, blockScroll, reserveScrollBarGap]); 25 | }; 26 | -------------------------------------------------------------------------------- /react-responsive-modal/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const isBrowser = typeof window !== 'undefined'; 2 | -------------------------------------------------------------------------------- /react-responsive-modal/styles.css: -------------------------------------------------------------------------------- 1 | .react-responsive-modal-root { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | z-index: 1000; 8 | } 9 | 10 | .react-responsive-modal-overlay { 11 | background: rgba(0, 0, 0, 0.5); 12 | position: fixed; 13 | top: 0; 14 | bottom: 0; 15 | left: 0; 16 | right: 0; 17 | z-index: -1; 18 | } 19 | 20 | .react-responsive-modal-container { 21 | height: 100%; 22 | outline: 0; 23 | overflow-x: hidden; 24 | overflow-y: auto; 25 | text-align: center; 26 | } 27 | 28 | /* Used to trick the browser to center the modal content properly */ 29 | .react-responsive-modal-containerCenter:after { 30 | width: 0; 31 | height: 100%; 32 | content: ''; 33 | display: inline-block; 34 | vertical-align: middle; 35 | } 36 | 37 | .react-responsive-modal-modal { 38 | max-width: 800px; 39 | display: inline-block; 40 | text-align: left; 41 | vertical-align: middle; 42 | background: #ffffff; 43 | box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.25); 44 | margin: 1.2rem; 45 | padding: 1.2rem; 46 | position: relative; 47 | overflow-y: auto; 48 | } 49 | 50 | .react-responsive-modal-closeButton { 51 | position: absolute; 52 | top: 14px; 53 | right: 14px; 54 | border: none; 55 | padding: 0; 56 | cursor: pointer; 57 | background-color: transparent; 58 | display: flex; 59 | } 60 | 61 | /* Used to fix a screen glitch issues with the animation see https://github.com/pradel/react-responsive-modal/issues/495 */ 62 | .react-responsive-modal-overlay, 63 | .react-responsive-modal-container, 64 | .react-responsive-modal-modal { 65 | animation-fill-mode: forwards !important; 66 | } 67 | 68 | @keyframes react-responsive-modal-overlay-in { 69 | 0% { 70 | opacity: 0; 71 | } 72 | 100% { 73 | opacity: 1; 74 | } 75 | } 76 | 77 | @keyframes react-responsive-modal-overlay-out { 78 | 0% { 79 | opacity: 1; 80 | } 81 | 100% { 82 | opacity: 0; 83 | } 84 | } 85 | 86 | @keyframes react-responsive-modal-modal-in { 87 | 0% { 88 | transform: scale(0.96); 89 | opacity: 0; 90 | } 91 | 100% { 92 | transform: scale(100%); 93 | opacity: 1; 94 | } 95 | } 96 | 97 | @keyframes react-responsive-modal-modal-out { 98 | 0% { 99 | transform: scale(100%); 100 | opacity: 1; 101 | } 102 | 100% { 103 | transform: scale(0.96); 104 | opacity: 0; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /react-responsive-modal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "moduleResolution": "node", 16 | "jsx": "react", 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true, 20 | "noEmit": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", "schedule:monthly"], 3 | "automerge": true, 4 | "major": { 5 | "automerge": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module 'babel-plugin-preval/macro'; 5 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | const remarkHighlight = require('remark-highlight.js'); 2 | const rehypeSlug = require('rehype-slug'); 3 | const rehypeHeadings = require('rehype-autolink-headings'); 4 | const remarkCodeImport = require('remark-code-import'); 5 | const withMDX = require('@next/mdx')({ 6 | options: { 7 | remarkPlugins: [remarkCodeImport, remarkHighlight], 8 | rehypePlugins: [ 9 | rehypeSlug, 10 | [ 11 | rehypeHeadings, 12 | { 13 | properties: { 14 | ariaHidden: true, 15 | tabIndex: -1, 16 | className: 'anchor', 17 | }, 18 | content: { 19 | type: 'element', 20 | tagName: 'svg', 21 | properties: { 22 | xmlns: 'http://www.w3.org/2000/svg', 23 | viewBox: '0 0 20 20', 24 | fill: 'currentColor', 25 | }, 26 | children: [ 27 | // Svg from https://heroicons.com/ 28 | { 29 | type: 'element', 30 | tagName: 'path', 31 | properties: { 32 | fillRule: 'evenodd', 33 | d: 34 | 'M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z', 35 | clipRule: 'evenodd', 36 | }, 37 | }, 38 | ], 39 | }, 40 | }, 41 | ], 42 | ], 43 | }, 44 | }); 45 | 46 | module.exports = withMDX(); 47 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next", 6 | "build": "next build", 7 | "start": "next start", 8 | "export": "next export", 9 | "type-check": "tsc" 10 | }, 11 | "dependencies": { 12 | "next": "10.0.1", 13 | "react": "17.0.1", 14 | "react-dom": "17.0.1", 15 | "react-responsive-modal": "workspace:react-responsive-modal" 16 | }, 17 | "devDependencies": { 18 | "@mdx-js/loader": "1.6.21", 19 | "@next/mdx": "10.0.1", 20 | "@tailwindcss/typography": "0.2.0", 21 | "@types/node": "14.14.7", 22 | "@types/react": "16.9.56", 23 | "@types/react-dom": "16.9.9", 24 | "fathom-client": "3.0.0", 25 | "highlight.js": "10.3.2", 26 | "rehype-autolink-headings": "5.0.1", 27 | "rehype-slug": "4.0.1", 28 | "remark-code-import": "0.2.0", 29 | "remark-highlight.js": "6.0.0", 30 | "tailwindcss": "1.9.6", 31 | "typeface-inter": "1.1.13", 32 | "typescript": "4.0.5" 33 | }, 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['tailwindcss', 'autoprefixer'], 3 | }; 4 | -------------------------------------------------------------------------------- /website/src/components/ExampleRendered.tsx: -------------------------------------------------------------------------------- 1 | import Simple from '../examples/Simple'; 2 | import ExampleMultiple from '../examples/Multiple'; 3 | import LongContent from '../examples/LongContent'; 4 | import FocusTrapped from '../examples/FocusTrapped'; 5 | import FocusTrappedInitialFocus from '../examples/FocusTrappedInitialFocus'; 6 | import CustomCssStyle from '../examples/CustomCssStyle'; 7 | import CustomAnimation from '../examples/CustomAnimation'; 8 | import CustomCloseIcon from '../examples/CustomCloseIcon'; 9 | import CustomContainer from '../examples/CustomContainer'; 10 | 11 | const examples: Record JSX.Element> = { 12 | simple: Simple, 13 | multiple: ExampleMultiple, 14 | longContent: LongContent, 15 | focusTrapped: FocusTrapped, 16 | focusTrappedInitialFocus: FocusTrappedInitialFocus, 17 | customCssStyle: CustomCssStyle, 18 | customAnimation: CustomAnimation, 19 | customCloseIcon: CustomCloseIcon, 20 | customContainer: CustomContainer, 21 | }; 22 | 23 | interface ExampleRenderedProps { 24 | name: string; 25 | } 26 | 27 | export const ExampleRendered = ({ name }: ExampleRenderedProps) => { 28 | const Example = examples[name]; 29 | if (!Example) { 30 | throw new Error('example not found'); 31 | } 32 | 33 | return ; 34 | }; 35 | -------------------------------------------------------------------------------- /website/src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { config } from '../config'; 2 | 3 | export const Footer = () => { 4 | return ( 5 | 76 | ); 77 | }; 78 | -------------------------------------------------------------------------------- /website/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import packageJson from 'react-responsive-modal/package.json'; 2 | 3 | export const Header = () => { 4 | return ( 5 |
6 | 38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /website/src/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | websiteUrl: 'https://www.leopradel.com', 3 | twitterUrl: 'https://twitter.com/leopradel', 4 | githubUrl: 'https://github.com/pradel', 5 | }; 6 | -------------------------------------------------------------------------------- /website/src/docs/index.mdx: -------------------------------------------------------------------------------- 1 | import { ExampleRendered } from '../components/ExampleRendered'; 2 | 3 | A simple responsive and accessible react modal. 4 | 5 | - Focus trap inside the modal. 6 | - Centered modals. 7 | - Scrolling modals. 8 | - Multiple modals. 9 | - Accessible modals. 10 | - Easily customizable via props. 11 | - Typescript support 12 | - [Small bundle size](https://bundlephobia.com/result?p=react-responsive-modal) 13 | 14 | ## Installation 15 | 16 | Inside your project directory, install react-responsive-modal by running the following: 17 | 18 | ```sh 19 | npm install react-responsive-modal --save 20 | # Or with yarn 21 | yarn add react-responsive-modal 22 | ``` 23 | 24 | ## Usage 25 | 26 | [![Edit react-responsive-modal](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/9jxp669j2o) 27 | 28 | 29 | 30 | ```javascript 31 | import React, { useState } from 'react'; 32 | import ReactDOM from 'react-dom'; 33 | import 'react-responsive-modal/styles.css'; 34 | import { Modal } from 'react-responsive-modal'; 35 | 36 | const App = () => { 37 | const [open, setOpen] = useState(false); 38 | 39 | const onOpenModal = () => setOpen(true); 40 | const onCloseModal = () => setOpen(false); 41 | 42 | return ( 43 |
44 | 45 | 46 |

Simple centered modal

47 |

48 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam 49 | pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet 50 | hendrerit risus, sed porttitor quam. 51 |

52 |
53 |
54 | ); 55 | }; 56 | 57 | ReactDOM.render(, document.getElementById('app')); 58 | ``` 59 | 60 | - If you are using Next.js, you need to import the styles in `pages/_app.js` or `pages/_app.tsx`. 61 | - If you are using Create React App, you need to import the styles in `index.js` or `index.tsx`. 62 | 63 | 💡 When you integrate react-responsive-modal in your app, make sure that your Modal content is wrapped in an element so the close icon is not shown on top. 64 | 65 | ### Multiple modals 66 | 67 | You can render multiple modals at the same time. Clicking on the overlay or pressing "esc" will only close the last modal. 68 | 69 | 70 | 71 | ```js file=../examples/Multiple.tsx 72 | 73 | ``` 74 | 75 | ### Modal with a lot of content 76 | 77 | When a modal with a content overflowing the window size, you can scroll the content of the modal but you will see that the body scroll is locked until you actually close the modal. 78 | 79 | 80 | 81 | ```js file=../examples/LongContent.tsx 82 | 83 | ``` 84 | 85 | ### Focus Trapped modal 86 | 87 | By default, when the modal open, the first focusable element will be focused. Press Tab to navigate between the focusable elements in the modal. You can notice that when the modal is open, you can't focus the elements outside of it. 88 | If you want to disable this behavior, set the `focusTrapped` prop to `false`. 89 | 90 | 91 | 92 | ```js file=../examples/FocusTrapped.tsx 93 | 94 | ``` 95 | 96 | ### Focus Trapped initial focus 97 | 98 | You can also set to trap focus within the modal, but decide where to put focus when opened. To do this use `initialFocusRef` prop and set it to a ref of an element you want to focus. In this example we focus on the modal root element. 99 | 100 | 101 | 102 | ```js file=../examples/FocusTrappedInitialFocus.tsx 103 | 104 | ``` 105 | 106 | ### Custom styling with css 107 | 108 | Customising the Modal style via css is really easy. For example if you add the following css to your app you will get the following result: 109 | 110 | ```css file=../examples/custom-styling.css 111 | 112 | ``` 113 | 114 | 115 | 116 | ```js file=../examples/CustomCssStyle.tsx 117 | 118 | ``` 119 | 120 | ### Custom animation 121 | 122 | If you want to change the default animation, you can do so by creating your own css animation. The modal and the overlay can be animated separately. For example if you add the following css to your app you will get the following result: 123 | 124 | ```css file=../examples/custom-animation.css 125 | 126 | ``` 127 | 128 | 129 | 130 | ```js file=../examples/CustomAnimation.tsx 131 | 132 | ``` 133 | 134 | If you want to apply a custom animation to the modal body you can do like this: 135 | 136 | ### Custom close icon 137 | 138 | You can customise the close icon used by the Modal by providing your own image or svg. 139 | 140 | 141 | 142 | ```js file=../examples/CustomCloseIcon.tsx 143 | 144 | ``` 145 | 146 | ### Custom container 147 | 148 | By default, the Modal will be rendered at the end of the html body tag. If you want to render the Modal in your own container, you can pass your own Element to the container prop. 149 | 150 | 151 | 152 | ```js file=../examples/CustomContainer.tsx 153 | 154 | ``` 155 | 156 | ## Accessibility 157 | 158 | - Use the `aria-labelledby` and `aria-describedby` props to follow the [ARIA best practices](https://www.w3.org/TR/wai-aria-practices/#dialog_modal). 159 | 160 | ```javascript 161 | 167 |

My Title

168 |

My Description

169 |
170 | ``` 171 | 172 | - `aria-modal` is set to true automatically. 173 | - When the modal is open the focus is trapped within it. 174 | - Clicking on the overlay closes the Modal. 175 | - Pressing the "Esc" key closes the Modal. 176 | - When the modal is open the page scroll is blocked for the elements behind the modal. 177 | - Closing the modal will unblock the scroll. 178 | - The modal is rendered in a portal at the end of the `body`. 179 | 180 | ## Props 181 | 182 | | Name | Type | Default | Description | 183 | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 184 | | **open\*** | `boolean` | | Control if the modal is open or not. | 185 | | **center** | `boolean` | false | Should the dialog be centered. | 186 | | **closeOnEsc** | `boolean` | true | Is the modal closable when user press esc key. | 187 | | **closeOnOverlayClick** | `boolean` | true | Is the modal closable when user click on overlay. | 188 | | **blockScroll** | `boolean` | true | Whether to block scrolling when dialog is open. | 189 | | **showCloseIcon** | `boolean` | true | Show the close icon. | 190 | | **closeIconId** | `string` | | id attribute for the close icon button. | 191 | | **closeIcon** | `React.ReactNode` | | Custom icon to render (svg, img, etc...). | 192 | | **focusTrapped** | `boolean` | true | When the modal is open, trap focus within it. | 193 | | **initialFocusRef** | `React.RefElement` | undefined | Sets focus on this specific element when modal opens if focus trap is used. | 194 | | **container** | `Element` | | You can specify a container prop which should be of type `Element`. The portal will be rendered inside that element. The default behavior will create a div node and render it at the at the end of document.body. | 195 | | **classNames** | `{`
`root?: string;`
`overlay?: string;`
`overlayAnimationIn?: string;`
`overlayAnimationOut?: string;`
`modal?: string;`
`modalAnimationIn?: string;`
`modalAnimationOut?: string;`
`closeButton?: string;`
`closeIcon?: string;`
`}` | | An object containing classNames to style the modal. | 196 | | **styles** | `{`
`root?: React.CSSProperties;`
`overlay?: React.CSSProperties;`
`overlay?: React.CSSProperties;`
`modalContainer?: React.CSSProperties;`
`modal?: React.CSSProperties;`
`closeButton?: React.CSSProperties;`
`closeIcon?: React.CSSProperties;`
`}` | | An object containing the styles objects to style the modal. | 197 | | **animationDuration** | `number` | 300 | Animation duration in milliseconds. | 198 | | **role** | `string` | "dialog" | ARIA role for modal | 199 | | **ref** | `React.RefElement` | undefined | Ref for modal dialog element | 200 | | **ariaLabelledby** | `string` | | ARIA label for modal | 201 | | **ariaDescribedby** | `string` | | ARIA description for modal | 202 | | **containerId** | `string` | | id attribute for modal container | 203 | | **modalId** | `string` | | id attribute for modal | | 204 | | **onClose\*** | `() => void` | | Callback fired when the Modal is requested to be closed by a click on the overlay or when user press esc key. | 205 | | **onEscKeyDown\*** | `(event: KeyboardEvent) => void` | | Callback fired when the escape key is pressed. | 206 | | **onOverlayClick\*** | `(event: React.MouseEvent) => void` | | Callback fired when the overlay is clicked. | 207 | | **onAnimationEnd\*** | `() => void` | | Callback fired when the Modal has exited and the animation is finished. | 208 | 209 | ## License 210 | 211 | react-responsive-modal is licensed under the [MIT license](https://github.com/pradel/react-responsive-modal/blob/master/LICENSE). 212 | -------------------------------------------------------------------------------- /website/src/examples/CustomAnimation.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | // import './examples/custom-animation.css'; 6 | const [open, setOpen] = React.useState(false); 7 | 8 | return ( 9 | <> 10 | 13 | 14 | setOpen(false)} 17 | center 18 | classNames={{ 19 | overlayAnimationIn: 'customEnterOverlayAnimation', 20 | overlayAnimationOut: 'customLeaveOverlayAnimation', 21 | modalAnimationIn: 'customEnterModalAnimation', 22 | modalAnimationOut: 'customLeaveModalAnimation', 23 | }} 24 | animationDuration={800} 25 | > 26 |

27 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam 28 | pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet 29 | hendrerit risus, sed porttitor quam. 30 |

31 |
32 | 33 | ); 34 | }; 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /website/src/examples/CustomCloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | 7 | const closeIcon = ( 8 | 9 | 14 | 15 | ); 16 | 17 | return ( 18 | <> 19 | 22 | 23 | setOpen(false)} 26 | center 27 | closeIcon={closeIcon} 28 | > 29 |

30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam 31 | pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet 32 | hendrerit risus, sed porttitor quam. 33 |

34 |
35 | 36 | ); 37 | }; 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /website/src/examples/CustomContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | 7 | const myRef = React.useRef(null); 8 | 9 | return ( 10 | <> 11 |
12 | 15 | setOpen(false)} 18 | center 19 | container={myRef.current} 20 | > 21 |

22 | Take a look with the devtools, you can see that the modal is inside 23 | the div we are targeting and not at the end of the body tag. 24 |

25 |
26 | 27 | ); 28 | }; 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /website/src/examples/CustomCssStyle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | // import './examples/custom-styling.css'; 6 | const [open, setOpen] = React.useState(false); 7 | 8 | return ( 9 | <> 10 | 13 | 14 | setOpen(false)} 17 | center 18 | classNames={{ 19 | overlay: 'customOverlay', 20 | modal: 'customModal', 21 | }} 22 | > 23 |

24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam 25 | pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet 26 | hendrerit risus, sed porttitor quam. 27 |

28 |
29 | 30 | ); 31 | }; 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /website/src/examples/FocusTrapped.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | 7 | return ( 8 | <> 9 | 12 | 13 | setOpen(false)}> 14 |

Try tabbing/shift-tabbing thru elements

15 |
16 |

17 | 21 |

22 |

23 | 27 |

28 | 29 | 30 |
31 |
32 | 33 | ); 34 | }; 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /website/src/examples/FocusTrappedInitialFocus.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | const modalRef = useRef(null); 7 | 8 | return ( 9 | <> 10 | 13 | 14 | setOpen(false)} 18 | initialFocusRef={modalRef} 19 | > 20 |

Try tabbing/shift-tabbing thru elements

21 |
22 |

23 | 27 |

28 |

29 | 33 |

34 | 35 | 36 |
37 |
38 | 39 | ); 40 | }; 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /website/src/examples/LongContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | 7 | const lorem = ( 8 |

9 | Mauris ac arcu sit amet dui interdum bibendum a sed diam. Praesent rhoncus 10 | congue ipsum elementum lobortis. Ut ligula purus, ultrices id condimentum 11 | quis, tincidunt quis purus. Proin quis enim metus. Nunc feugiat odio at 12 | eros porta, ut rhoncus lorem tristique. Nunc et ipsum eu ex vulputate 13 | consectetur vel eu nisi. Donec ultricies rutrum lectus, sit ame feugiat 14 | est semper vitae. Proin varius imperdiet consequat. Proin eu metus nisi. 15 | In hac habitasse platea dictumst. Vestibulum ac ultrices risus. 16 | Pellentesque arcu sapien, aliquet sed orci sit amet, pulvinar interdum 17 | velit. Nunc a rhoncus ipsum, maximus fermentum dolor. Praesent aliquet 18 | justo vitae rutrum volutpat. Ut quis pulvinar est. 19 |

20 | ); 21 | 22 | return ( 23 | <> 24 | 27 | 28 | setOpen(false)}> 29 |

Big modal

30 | {lorem} 31 | {lorem} 32 | {lorem} 33 | {lorem} 34 | {lorem} 35 | {lorem} 36 | {lorem} 37 | {lorem} 38 | {lorem} 39 |
40 | 41 | ); 42 | }; 43 | 44 | export default App; 45 | -------------------------------------------------------------------------------- /website/src/examples/Multiple.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [openFirst, setOpenFirst] = React.useState(false); 6 | const [openSecond, setOpenSecond] = React.useState(false); 7 | 8 | const littleLorem = ( 9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pulvinar 11 | risus non risus hendrerit venenatis. Pellentesque sit amet hendrerit 12 | risus, sed porttitor quam. 13 |

14 | ); 15 | 16 | return ( 17 | <> 18 | 21 | 22 | setOpenFirst(false)} center> 23 |

First modal

24 | {littleLorem} 25 | 28 |
29 | setOpenSecond(false)} center> 30 |

Second modal

31 | {littleLorem} 32 |
33 | 34 | ); 35 | }; 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /website/src/examples/Simple.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-responsive-modal'; 3 | 4 | const App = () => { 5 | const [open, setOpen] = React.useState(false); 6 | 7 | const onOpenModal = () => setOpen(true); 8 | const onCloseModal = () => setOpen(false); 9 | 10 | return ( 11 |
12 | 13 | 14 | 15 |

Simple centered modal

16 |

17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam 18 | pulvinar risus non risus hendrerit venenatis. Pellentesque sit amet 19 | hendrerit risus, sed porttitor quam. 20 |

21 |
22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /website/src/examples/custom-animation.css: -------------------------------------------------------------------------------- 1 | /* examples/custom-animation.css */ 2 | 3 | @keyframes customEnterOverlayAnimation { 4 | 0% { 5 | transform: scale(0); 6 | } 7 | 100% { 8 | transform: scale(1); 9 | } 10 | } 11 | @keyframes customLeaveOverlayAnimation { 12 | 0% { 13 | transform: scale(1); 14 | } 15 | 100% { 16 | transform: scale(0); 17 | } 18 | } 19 | 20 | @keyframes customEnterModalAnimation { 21 | 0% { 22 | transform: scale(0.2); 23 | } 24 | 100% { 25 | transform: scale(1); 26 | } 27 | } 28 | @keyframes customLeaveModalAnimation { 29 | 0% { 30 | transform: scale(1); 31 | } 32 | 100% { 33 | transform: scale(0.2); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/src/examples/custom-styling.css: -------------------------------------------------------------------------------- 1 | /* examples/custom-styling.css */ 2 | .customOverlay { 3 | background: rgba(36, 123, 160, 0.7); 4 | } 5 | .customModal { 6 | background: #b2dbbf; 7 | max-width: 500px; 8 | width: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /website/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { AppProps } from 'next/app'; 3 | import Router from 'next/router'; 4 | import * as Fathom from 'fathom-client'; 5 | import 'typeface-inter'; 6 | import 'react-responsive-modal/styles.css'; 7 | import '../examples/custom-styling.css'; 8 | import '../examples/custom-animation.css'; 9 | // highlight.js theme 10 | import '../styles/atom-one-light.css'; 11 | import '../styles/index.css'; 12 | 13 | // Record a pageview when route changes 14 | Router.events.on('routeChangeComplete', () => { 15 | Fathom.trackPageview(); 16 | }); 17 | 18 | function MyApp({ Component, pageProps }: AppProps) { 19 | // Initialize Fathom when the app loads 20 | useEffect(() => { 21 | Fathom.load('PIMHMGXF', { 22 | excludedDomains: ['localhost'], 23 | }); 24 | }, []); 25 | 26 | return ; 27 | } 28 | 29 | export default MyApp; 30 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import Content from '../docs/index.mdx'; 3 | import { Header } from '../components/Header'; 4 | import { Footer } from '../components/Footer'; 5 | 6 | const IndexPage = () => ( 7 | <> 8 |
9 | 10 |
11 |
12 | 101 |
102 | 103 |
104 |
105 |
106 | 107 |