├── .editorconfig ├── .eslintrc.json ├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── .huskyrc ├── .lintstagedrc ├── .nvm ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── Modal.svelte └── index.js └── types ├── Modal.svelte.d.ts └── index.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2021, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "es6": true, 8 | "browser": true 9 | }, 10 | "overrides": [ 11 | { 12 | "files": ["*.svelte"], 13 | "parser": "svelte-eslint-parser" 14 | } 15 | ], 16 | "extends": ["eslint:recommended", "plugin:svelte/recommended"], 17 | "settings": {}, 18 | "rules": { 19 | "arrow-spacing": 2, 20 | "keyword-spacing": [2, { "before": true, "after": true }], 21 | "no-console": ["error", { "allow": ["warn", "error"] }], 22 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 23 | "no-var": 2, 24 | "object-shorthand": [2, "always"], 25 | "one-var": [2, "never"], 26 | "prefer-arrow-callback": 2, 27 | "prefer-const": 2, 28 | "quote-props": [2, "as-needed"], 29 | "semi": [2, "always"], 30 | "space-before-blocks": [2, "always"], 31 | "space-before-function-paren": [ 32 | 2, 33 | { "anonymous": "always", "named": "never", "asyncArrow": "always" } 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'monthly' 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Hi there 👋 2 | 3 | Thank you so much for creating a PR. To streamline the process and make sure that your changes can get merged easily, please stick to the following structure. See [#47](https://github.com/flekschas/svelte-simple-modal/pull/47) for an example of how to create an excellent PR description. 4 | 5 | ## Background 6 | 7 | Describe the context on how you use svelte-simple-modal. E.g., plain Svelte app, SvelteKit, Sapper, SSR, etc. 8 | 9 | ## Currently Observed Behavior 10 | 11 | Describe the current behavior and explain why you want to change it. If this is a bugfix PR, please reference the issue or describe it here in details. 12 | 13 | **Demo:** If this is a bugfix PR, please provide a live demo that reproduces the issue. E.g., you can fork the [current demo](https://svelte.dev/repl/033e824fad0a4e34907666e7196caec4). 14 | 15 | ## New Behavior 16 | 17 | Describe the changes that you introduce with this PR. 18 | 19 | **Demo:** Provide a live demo that implements the new behavior. E.g., you can fork the [current demo](https://svelte.dev/repl/033e824fad0a4e34907666e7196caec4). 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [18.x, 20.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | - run: npm run build --if-present 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | - run: sudo apt-get install xvfb 17 | - run: npm ci 18 | - run: npm run build --if-present 19 | env: 20 | CI: true 21 | 22 | publish-npm: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-node@v4 28 | with: 29 | node-version: 20 30 | registry-url: https://registry.npmjs.org/ 31 | - run: npm ci 32 | - run: npm run prerelease 33 | - run: npm publish 34 | env: 35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | - name: Create Release 16 | id: create_release 17 | uses: actions/create-release@v1 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: ${{ github.ref }} 23 | draft: false 24 | prerelease: false 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | index.js 4 | !src/index.js 5 | module.js 6 | dist.zip 7 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "npm run precommit" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.svelte": [ 3 | "pretty-quick --staged", 4 | "eslint --fix", 5 | "git add" 6 | ], 7 | } 8 | -------------------------------------------------------------------------------- /.nvm: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 2 | 3 | **BREAKING CHANGE:** Upgraded to Svelte v5. This change is unfortunately not backwards compatible with Svelte 3 or 4. If you want to use Svelte Simple Modal with Svelte v3 or v4, check out version 1 (https://github.com/flekschas/svelte-simple-modal/tree/v1) ([#119]https://github.com/flekschas/svelte-simple-modal/pull/119) 4 | 5 | # 1.6.2 6 | 7 | - Fix Svelte type resolve issue ([#107](https://github.com/flekschas/svelte-simple-modal/issues/107)) 8 | 9 | # 1.6.1 10 | 11 | - Fix Svelte resolve warning ([#103](https://github.com/flekschas/svelte-simple-modal/issues/103)) 12 | 13 | # 1.6.0 14 | 15 | - Enable usage with Svelte v4 (the library remains compatible with v3) 16 | - Update third-party libs 17 | 18 | # 1.5.2 19 | 20 | - Fix and expose context types. This is useful as an interim solution for ([#88](https://github.com/flekschas/svelte-simple-modal/pull/88)). E.g.: 21 | 22 | ```ts 23 | import { getContext } from 'svelte'; 24 | import type { Context } from 'svelte-simple-modal'; 25 | const { open, close } = getContext('simple-modal') as Context; 26 | ``` 27 | 28 | # 1.5.1 29 | 30 | - Fix the scroll reset behavior to `instant` to avoid seeing a smooth scrolling when closing the modal in cases where the default scroll behavior was changed ([#97(https://github.com/flekschas/svelte-simple-modal/pull/97)) 31 | 32 | # 1.5.0 33 | 34 | - Add property to assign an ID to the modal's root element ([#96(https://github.com/flekschas/svelte-simple-modal/pull/96)) 35 | 36 | # 1.4.6 37 | 38 | - Improve type definition ([#94(https://github.com/flekschas/svelte-simple-modal/pull/94)) 39 | 40 | # 1.4.5 41 | 42 | - Fix min-height issue with Safari iOS 15 ([#64](https://github.com/flekschas/svelte-simple-modal/issues/64)) 43 | 44 | # 1.4.4 45 | 46 | - One more type definition improvement 47 | 48 | # 1.4.3 49 | 50 | - Further improve type definitions 51 | 52 | # 1.4.2 53 | 54 | - Improve type definitions 55 | 56 | # 1.4.1 57 | 58 | - Avoid submitting a `
` if it wraps `` by setting the close button type to `button`. ([#84](https://github.com/flekschas/svelte-simple-modal/pull/84)) Shoutout to [@jnysteen](https://github.com/jnysteen)! 59 | 60 | # 1.4.0 61 | 62 | - Skip untabbable element ([#82](https://github.com/flekschas/svelte-simple-modal/issues/82)) Massive thanks to [@jassenjj](https://github.com/jassenjj) 🙏 63 | - Added `isTabbable` property to allow users to override the tab-check. E.g., one might want to use a more the [tabbable library](https://github.com/focus-trap/tabbable#istabbable). 64 | 65 | # 1.3.4 66 | 67 | - Respect the tab order defined by `tabindex` attributes of elements within a modal ([#80](https://github.com/flekschas/svelte-simple-modal/pull/80)) 68 | 69 | # 1.3.3 70 | 71 | - Fix accidentally removed global export of `bind()` 72 | 73 | # 1.3.2 74 | 75 | - Fix type issue of `bind()` via a new version of `sveld` (#73). See https://github.com/carbon-design-system/sveld/issues/83 for context. Also huge shoutout to [Dustin Coffey](https://github.com/dustinc) for [his PR (#75) on this matter](https://github.com/flekschas/svelte-simple-modal/pull/75). 🙏 76 | 77 | # 1.3.1 78 | 79 | - Updated `sveld` to fix the type definitions (#58). See https://github.com/carbon-design-system/sveld/issues/56 for context 80 | 81 | # 1.3.0 82 | 83 | - Add `classBg`, `classWindow`, `classWindowWrap`, `classContent`, and `classCloseButton` properties to applying custom CSS classes to the modal elements (#62) 84 | - Add `unstyled` to prevent applying default styles and providing full control over the modal styling 85 | 86 | # 1.2.0 87 | 88 | - Add `ariaLabel` and `ariaLabelledBy` to props/options to support improved accessibility (#37) 89 | - Add `aria-label="Close modal"` to the default close button component (#37) 90 | 91 | # 1.1.3 92 | 93 | - Add `"type": "module"` in `package.json` (#57) 94 | 95 | # 1.1.2 96 | 97 | - Properly export `bind()` (#58) 98 | 99 | # 1.1.1 100 | 101 | - Add type annotations via JSDocs, Typescript, and Sveld (#52) 102 | 103 | # 1.1.0 104 | 105 | - Add `disableFocusTrap` property for disabling the focus trap behavior (#49) 106 | 107 | # 1.0.4 108 | 109 | - Make sure that the modal only closes when it's open to fix (#53) 110 | 111 | # 1.0.3 112 | 113 | - Remove accidentally added `console.log` (#50) and forbid `console.log` via linting 114 | 115 | # 1.0.2 116 | 117 | - Revert changes from v1.0.1 as it turns out that in certain cases `overflow: hidden` on body is not enough to prevent background scrolling in iOS. For details see https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios/ 118 | 119 | # 1.0.1 120 | 121 | - Remove `position: fixed` from body on opening a modal as it appears that the fixed positioning is not needed to avoid scrolling (#38) 122 | 123 | # 1.0.0 124 | 125 | Woohoo 🎉 Thanks everyone who uses and supports this library. This release 126 | really just introduces a bug fix but given that most (hopefully all 🤞) of the 127 | features are stable, I decided to finally release v1. 128 | 129 | - Fixes a scrolling issue introduced with #45 while still keeping the library compatible with SvelteKit (#47) 130 | 131 | # 0.10.4 132 | 133 | - Default bg width/height to innerWidth/innerHeight (#40) 134 | - Make compatible with svelte kit (#45) 135 | 136 | # 0.10.3 137 | 138 | - Avoid exception when converting falsy value to CSS (#42) 139 | 140 | # 0.10.2 141 | 142 | - Improve outer click handling and avoid accidental closing of the modal (#39) 143 | 144 | # 0.10.1 145 | 146 | - Harmonize `on:open`/`on:opening` and `on:close`/`on:closing` event (#33 and #34). Note, `on:opening` and `on:closing` are still being dispatched for backward compatibility but they will be remove in future versions so please switch over to `on:open` and `on:close`. 147 | 148 | # 0.10.0 149 | 150 | - Disable body scrolling while modal is open (#31 -> #28) 151 | - Fixed a visual glitch where previously customized styles were not reset 152 | 153 | # 0.9.0 154 | 155 | - Added `` as an alternative to other the context API (#22) 156 | 157 | # 0.8.1 158 | 159 | - Replace too strict `isSvelteComponent` with `isFunction` (#27) 160 | 161 | # 0.8.0 162 | 163 | - Dispatch open, opened, close, and closed events (#25) 164 | 165 | # 0.7.0 166 | 167 | - Allow customizing the window wrapper via `styleWindowWrap` (#24) 168 | 169 | # 0.6.1 170 | 171 | - Fall back gracefully for older versions of Svelte (`<3.19`) when `SvelteComponent` for the custom close button is not available (#21) 172 | 173 | # 0.6.0 174 | 175 | - Allow customizing the close button (#20) 176 | 177 | # 0.5.0 178 | 179 | - Trap focus in modal (#17) 180 | - Add `aria` information 181 | 182 | # 0.4.2 183 | 184 | - Explicitly expose the `event` object (#13) 185 | 186 | # 0.4.1 187 | 188 | - Remove wrapping `div` element as it's not necessary (#12) 189 | 190 | # 0.4.0 191 | 192 | - Add support for transition event callbacks via [`open(..., ..., ..., { onOpen, onOpened, onClose, onClosed })`](README.md#open)) 193 | 194 | # 0.3.1 195 | 196 | - Fix non-reactive component (#11) 197 | 198 | # 0.3.0 199 | 200 | - Allow to specify instance-specific options via [`open(Component, componentProps, options)`](README.md#open)) 201 | 202 | # 0.2.2 203 | 204 | - Update `package-lock.json` file 205 | 206 | # 0.2.1 207 | 208 | - Set backgroupd position to `top: 0` and `left: 0` by default thanks to https://github.com/flekschas/svelte-simple-modal/pull/5 209 | 210 | # 0.2.0 211 | 212 | - Allow customizing the modal style on `open(Component, props, styling)`. This can be used for content-aware theming. 213 | 214 | # 0.1.1 215 | 216 | - Robustify overflow 217 | 218 | # 0.1.0 219 | 220 | - First release 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fritz Lekschas 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 |

2 | Svelte Simple Modal 3 |

4 | 5 |
6 | 7 | **A simple, small, and content-agnostic modal for [Svelte](https://svelte.dev).** 8 | 9 |
10 | 11 |
12 | 13 |
14 | 15 | [![NPM Version](https://img.shields.io/npm/v/svelte-simple-modal.svg?style=flat-square&color=7f99ff)](https://npmjs.org/package/svelte-simple-modal) 16 | [![Build Status](https://img.shields.io/github/actions/workflow/status/flekschas/svelte-simple-modal/build.yml?branch=master&color=a17fff&style=flat-square)](https://github.com/flekschas/svelte-simple-modal/actions?query=workflow%3Abuild) 17 | [![File Size](http://img.badgesize.io/https://unpkg.com/svelte-simple-modal/src/Modal.svelte?compression=gzip&style=flat-square&color=e17fff)](https://bundlephobia.com/result?p=svelte-simple-modal) 18 | [![Code Style Prettier](https://img.shields.io/badge/code%20style-prettier-ff7fe1.svg?style=flat-square)](https://github.com/prettier/prettier#readme) 19 | [![Demo](https://img.shields.io/badge/demo-👍-ff7fa5.svg?style=flat-square)](https://svelte.dev/repl/033e824fad0a4e34907666e7196caec4?version=3.18.2) 20 | 21 |
22 | 23 |
24 | 25 | ![simple-modal](https://user-images.githubusercontent.com/932103/57642565-9d335d00-7585-11e9-80c6-e4b835f02428.gif) 26 | 27 |
28 | 29 | **Live demo:** https://svelte.dev/playground/b95ce66b0ef34064a34afc5c0249f313?version=5.17.3 30 | 31 | **Works with:** Svelte `>=v5`! 32 | 33 | > [!NOTE] 34 | > If you want to use Svelte Simple Modal with Svelte v3 or v4, check out [version 1](https://github.com/flekschas/svelte-simple-modal/tree/v1). 35 | 36 | ## Table of Contents 37 | 38 | - [Install](#install) 39 | - [Rollup Setup](#rollup-setup) 40 | - [Usage](#usage) 41 | - [TypeScript Example](#usage-with-typescript) 42 | - [Svelte Store Example](#usage-with-a-svelte-store) 43 | - [Styling](#styling) 44 | - [SSR](#server-side-rendering) 45 | - [Accessibility](#accessibility) 46 | - [API](#api) 47 | - [Component](#component-api) 48 | - [Events](#component-events) 49 | - [Context API](#context-api) 50 | - [Store API](#store-api) 51 | - [FAQ](#faq) 52 | 53 | ## Install 54 | 55 | ```bash 56 | npm install --save svelte-simple-modal 57 | ``` 58 | 59 | #### Rollup Setup 60 | 61 | Make sure that the main application's version of `svelte` is used for bundling by setting `@rollup/plugin-node-resolve`'s `dedupe` option as follows: 62 | 63 | ```js 64 | import resolve from '@rollup/plugin-node-resolve'; 65 | 66 | export default { 67 | plugins: [ 68 | resolve({ 69 | dedupe: ['svelte', 'svelte/transition', 'svelte/internal'], // important! 70 | }), 71 | ], 72 | }; 73 | ``` 74 | 75 | ## Usage 76 | 77 | Import the `Modal` component into your main app component (e.g., `App.svelte`). 78 | 79 | The modal is exposing [two context functions](#context-api): 80 | 81 | - [`open()`](#open) opens a component as a modal. 82 | - [`close()`](#close) simply closes the modal. 83 | 84 | ```svelte 85 | 86 | 90 | 91 | 92 | 93 | 94 | 95 | 101 | 102 |

103 | 104 | 105 | 106 | 109 | 110 |

🎉 {message} 🍾

111 | ``` 112 | 113 | **Demo:** https://svelte.dev/repl/52e0ade6d42546d8892faf8528c70d30 114 | 115 | ### Usage with TypeScript 116 | 117 | You can use the `Context` type exported by the package to validate the `getContext` function: 118 | 119 | ```ts 120 | import type { Context } from 'svelte-simple-modal'; 121 | const { open } = getContext('simple-modal'); 122 | // ... 123 | ``` 124 | 125 | ### Usage With a Svelte Store 126 | 127 | Alternatively, you can use a [Svelte store](#store-api) to show/hide a component as a modal. 128 | 129 | ```svelte 130 | 131 | 138 | 139 | 140 | 141 | 142 | ``` 143 | 144 | **Demo:** https://svelte.dev/repl/aec0c7d9f5084e7daa64f6d0c8ef0209 145 | 146 | The `` component is the same as in the example above. 147 | 148 | To hide the modal programmatically, simply unset the store. E.g., `modal.set(null)`. 149 | 150 | ### Styling 151 | 152 | The modal comes pre-styled for convenience but you can easily extent or replace the styling using either custom CSS classes or explicit CSS styles. 153 | 154 | Custom CSS classes can be applied via the `classBg`, `classWindow`, `classWindowWrap`, `classContent`, and `classCloseButton` properties. For instance, you could customize the styling with [TailwindCSS](https://tailwindcss.com/) as follows: 155 | 156 | ```svelte 157 | 166 | 167 | 168 | ``` 169 | 170 | **Demo:** https://svelte.dev/repl/f2a988ddbd5644f18d7cd4a4a8277993 171 | 172 | > Note: to take full control over the modal styles with CSS classes you have to reset existing styles via `unstyled={true}` as internal CSS classes are always applied last due to Svelte's class scoping. 173 | 174 | Alternatively, you can also apply CSS styles directly via the `styleBg`, `styleWindow`, `styleWindowWrap`, `styleContent`, and `styleCloseButton` properties. For instance: 175 | 176 | ```svelte 177 | 182 | 183 | 184 | ``` 185 | 186 | **Demo:** https://svelte.dev/repl/50df1c694b3243c1bd524b27f86eec51 187 | 188 | ### Server-Side Rendering 189 | 190 | With [SvelteKit](https://kit.svelte.dev/) you can enable [SSR](https://www.google.com/search?q=server+side+rendering) using the `browser` environmental variable as follows: 191 | 192 | ```svelte 193 | 197 | 198 | {#if browser} 199 | 200 | 201 | 202 | {/if} 203 | ``` 204 | 205 | Alternatively, you can enable SSR by dynamically importing svelte-simple-modal in `onMount()` as follows: 206 | 207 | ```js 208 | import { onMount } from 'svelte'; 209 | 210 | onMount(async () => { 211 | const svelteSimpleModal = await import('svelte-simple-modal'); 212 | Modal = svelteSimpleModal.default; 213 | }); 214 | ``` 215 | 216 | ### Accessibility 217 | 218 | The library applies the following WAI-ARIA guidelines for modal dialogs 219 | automtically: 220 | 221 | - `aria-modal="true"` and `role="dialog"` are applied automatically 222 | - The tab focus is trapped in the modal 223 | - The modal is closed on pressing the `esc` key 224 | 225 | To further improve the accessibility you'll have to either provide a label via 226 | [`ariaLabel`](https://www.w3.org/TR/wai-aria-1.1/#aria-label) or reference a 227 | title element via [`ariaLabelledBy`](https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby). 228 | The `ariaLabel` is automatically omitted when `ariaLabelledBy` is specified. 229 | 230 | ## API 231 | 232 | ### Component API 233 | 234 | The `` component accepts the following optional properties: 235 | 236 | | Property | Type | Default | Description | 237 | | ------------------------- | ---------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 238 | | **show** | Component \| null | `null` | A Svelte component to show as the modal. See [Store API](#store-api) for details. | 239 | | **id** | string \| null | `null` | Element ID to be assigned to the modal's root DOM element. | 240 | | **ariaLabel** | string \| null | `null` | Accessibility label of the modal. See [W3C WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/#aria-label) for details. | 241 | | **ariaLabelledBy** | string \| null | `null` | Element ID holding the accessibility label of the modal. See [W3C WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby) for details. | 242 | | **closeButton** | Component \| boolean | `true` | If `true` a button for closing the modal is rendered. You can also pass in a [custom Svelte component](#custom-close-button) to have full control over the styling. | 243 | | **closeOnEsc** | boolean | `true` | If `true` the modal will close when pressing the escape key. | 244 | | **closeOnOuterClick** | boolean | `true` | If `true` the modal will close when clicking outside the modal window. | 245 | | **transitionBg** | function | `svelte.fade` | Transition function for the background. | 246 | | **transitionBgProps** | BlurParams \| FadeParams \| FlyParams \| SlideParams | `{}` | Properties of the transition function for the background. | 247 | | **transitionWindow** | function | `svelte.fade` | Transition function for the window. | 248 | | **transitionWindowProps** | BlurParams \| FadeParams \| FlyParams \| SlideParams | `{}` | Properties of the transition function for the window. | 249 | | **classBg** | string \| null | `null` | Class name for the background element. | 250 | | **classWindowWrap** | string \| null | `null` | Class name for the modal window wrapper element. | 251 | | **classWindow** | string \| null | `null` | Class name for the modal window element. | 252 | | **classContent** | string \| null | `null` | Class name for the modal content element. | 253 | | **classCloseButton** | string \| null | `null` | Class name for the built-in close button. | 254 | | **styleBg** | Record | `{}` | Style properties of the background. | 255 | | **styleWindowWrap** | Record | `{}` | Style properties of the modal window wrapper element. | 256 | | **styleWindow** | Record | `{}` | Style properties of the modal window. | 257 | | **styleContent** | Record | `{}` | Style properties of the modal content. | 258 | | **styleCloseButton** | Record | `{}` | Style properties of the built-in close button. | 259 | | **unstyled** | boolean | `false` | When `true`, the default styles are not applied to the modal elements. | 260 | | **disableFocusTrap** | boolean | `false` | If `true` elements outside the modal can be in focus. This can be useful in certain edge cases. | 261 | | **isTabbable** | (node: Element): boolean | internal function | A function to determine if an HTML element is tabbable. | 262 | | **key** | string | `"simple-modal"` | The context key that is used to expose `open()` and `close()`. Adjust to avoid clashes with other contexts. | 263 | | **setContext** | function | `svelte.setContent` | You can normally ingore this property when you have [configured Rollup properly](#rollup-setup). If you want to bundle simple-modal with its own version of Svelte you have to pass `setContext()` from your main app to simple-modal using this parameter. | 264 | 265 | ### Component Events 266 | 267 | The `` component dispatches the following events: 268 | 269 | - `open`: dispatched when the modal window starts to open. 270 | - `opened`: dispatched when the modal window opened. 271 | - `close`: dispatched when the modal window starts to close. 272 | - `closed`: dispatched when the modal window closed. 273 | 274 | Alternatively, you can listen to those events via callbacks passed to [`open()`](#open) and [`close()`](#close). 275 | 276 | ### Context API 277 | 278 | Svelte Simple Modal uses [Svelte's context API](https://svelte.dev/tutorial/context-api) to expose the `open()` and `close()` methods. You can get these methods as follows: 279 | 280 | ```js 281 | const { open, close } = getContext('simple-modal'); 282 | ``` 283 | 284 | # open(Component, props = {}, options = {}, callbacks = {}) 285 | 286 | Opens the modal with `` rendered as the content. `options` can be used to adjust the modal behavior once for the modal that is about to be opened. The `options` allows to customize all [parameters](#parameters) except `key` and `setContext`: 287 | 288 | ```javascript 289 | { 290 | closeButton: false, 291 | closeOnEsc: false, 292 | closeOnOuterClick: false, 293 | transitionBg: fade, 294 | transitionBgProps: { 295 | duration: 5000 296 | }, 297 | transitionWindow: fly, 298 | transitionWindowProps: { 299 | y: 100, 300 | duration: 250 301 | }, 302 | styleBg: { backgroundImage: 'http://example.com/my-background.jpg' }, 303 | styleWindow: { fontSize: '20em' }, 304 | styleContent: { color: 'yellow' }, 305 | styleCloseButton: { width: '3rem', height: '3rem' }, 306 | disableFocusTrap: true 307 | } 308 | ``` 309 | 310 | # close(callbacks = {}) 311 | 312 | Closes the modal. Similar to `open()`, this method supports adding callbacks for the closing transition: 313 | 314 | ```javascript 315 | { 316 | onClose: () => { /* modal window starts to close */ }, 317 | onClosed: () => { /* modal window closed */ }, 318 | } 319 | ``` 320 | 321 | Callbacks are triggered at the beginning and end of the opening and closing transition. The following callbacks are supported: 322 | 323 | ```javascript 324 | { 325 | onOpen: () => { /* modal window starts to open */ }, 326 | onOpened: () => { /* modal window opened */ }, 327 | onClose: () => { /* modal window starts to close */ }, 328 | onClosed: () => { /* modal window closed */ }, 329 | } 330 | ``` 331 | 332 | ### Store API 333 | 334 | You can also use [Svelte stores](https://svelte.dev/tutorial/writable-stores) to open a modal using the ``'s [`show` property](#properties) as follows: 335 | 336 | ```svelte 337 | 338 | 345 | 346 | 347 | 348 | 349 | 350 | 351 |

🎉 Hi 🍾

352 | ``` 353 | 354 | **Demo:** https://svelte.dev/repl/6f55b643195646408e780ceeb779ac2a 355 | 356 | An added benefit of using stores is that the component opening the modal does not have to be a parent of ``. For instance, in the example above, `App.svelte` is toggling `Popup.svelte` as a modal even though `App.svelte` is not a child of ``. 357 | 358 | #### Bind Props to a Component Shown as a Modal 359 | 360 | Sometimes you want to pass properties to the component shown as a modal. To accomplish this, you can use our `bind()` helper function as follows: 361 | 362 | ```svelte 363 | 370 | ``` 371 | 372 | If you've worked with React/JSX then think of `const c = bind(Component, props)` as the equivalent of `const c = `. 373 | 374 | ## FAQ 375 | 376 | #### Custom Close Button 377 | 378 | **This feature requires Svelte >=v3.19!** 379 | 380 | Unfortunately, it's not possible to adjust all styles of the built-in close button via the `styleCloseButton` option. If you need full control you can implement your own Svelte component and use that as the close button. To do so pass your component via the `closeButton` option to `` or `open()`. 381 | 382 | For example, your close button might look as follows: 383 | 384 | ```svelte 385 | 386 | 390 | 391 | 392 | 393 | 401 | ``` 402 | 403 | Now you can set it as the default close button by passing it to `` as follows: 404 | 405 | ```svelte 406 | 407 | 412 | 413 | 414 | 415 | 416 | ``` 417 | 418 | Or you can pass `CloseButton` to `open()` as shown below: 419 | 420 | ```svelte 421 | 422 | 433 | 434 |

435 | ``` 436 | 437 | ## License 438 | 439 | [MIT](LICENSE) 440 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-simple-modal", 3 | "version": "2.0.0", 4 | "description": "A small and simple modal for Svelte", 5 | "type": "module", 6 | "svelte": "src/index.js", 7 | "main": "lib/index.js", 8 | "module": "lib/index.js", 9 | "types": "types/index.d.ts", 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 0", 12 | "build": "rm -rf lib && rm -rf types && rollup -c --bundleConfigAsCjs && sed -i '/from \"\"/d' types/index.d.ts && echo 'export { Context, Open, Close } from \"./Modal.svelte\";' >> types/index.d.ts && sed -i 's/SvelteComponentTyped/SvelteComponent/g' types/Modal.svelte.d.ts", 13 | "precommit": "NODE_ENV=production lint-staged; npm run lint", 14 | "prepare": "npm run lint && npm run build", 15 | "prerelease": "npm run lint; rm -f dist.zip; npm run build; zip dist.zip lib/* src/* types/*", 16 | "lint": "eslint src/* rollup.config.js" 17 | }, 18 | "files": [ 19 | "lib", 20 | "src", 21 | "types" 22 | ], 23 | "exports": { 24 | ".": { 25 | "svelte": "./src/index.js", 26 | "types": "./types/index.d.ts" 27 | } 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/flekschas/svelte-simple-modal.git" 32 | }, 33 | "keywords": [ 34 | "svelte", 35 | "modal", 36 | "popup", 37 | "dialog" 38 | ], 39 | "author": "Fritz Lekschas", 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/flekschas/svelte-simple-modal/issues" 43 | }, 44 | "homepage": "https://github.com/flekschas/svelte-simple-modal#readme", 45 | "peerDependencies": { 46 | "svelte": "^5.0.0" 47 | }, 48 | "devDependencies": { 49 | "@rollup/plugin-node-resolve": "^15.1.0", 50 | "@tsconfig/svelte": "^4.0.1", 51 | "eslint": "^8.43.0", 52 | "eslint-plugin-import": "^2.27.5", 53 | "eslint-plugin-svelte": "^2.31.1", 54 | "husky": "^4.3.8", 55 | "lint-staged": "^10.5.4", 56 | "prettier": "^2.8.8", 57 | "prettier-plugin-svelte": "^2.10.1", 58 | "pretty-quick": "^3.1.3", 59 | "rollup": "^3.25.2", 60 | "rollup-plugin-svelte": "^7.1.5", 61 | "sveld": "^0.20.0", 62 | "svelte": "^4.0.0", 63 | "svelte-eslint-parser": "^0.31.0", 64 | "typescript": "^5.1.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | import node from '@rollup/plugin-node-resolve'; 3 | import svelte from 'rollup-plugin-svelte'; 4 | import { sveld } from 'sveld'; 5 | 6 | import { readFileSync } from 'fs'; 7 | const pkg = JSON.parse(readFileSync('./package.json')); 8 | 9 | const production = !process.env.ROLLUP_WATCH; 10 | 11 | export default { 12 | input: pkg.svelte, 13 | output: { format: 'es', file: pkg.module }, 14 | plugins: [ 15 | svelte({ emitCss: false }), 16 | node({ 17 | browser: true, 18 | exportConditions: ['svelte'], 19 | extensions: ['.svelte'], 20 | }), 21 | production && sveld(), 22 | ], 23 | external: ['svelte', 'svelte/internal', 'svelte/transition'], 24 | }; 25 | -------------------------------------------------------------------------------- /src/Modal.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 469 | 470 | 471 | 472 | {#if Component} 473 | 529 | {/if} 530 | 531 | 532 | 656 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Modal, { bind } from './Modal.svelte'; 2 | export default Modal; 3 | export { Modal, bind }; 4 | -------------------------------------------------------------------------------- /types/Modal.svelte.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import type { SvelteComponent } from "svelte"; 3 | 4 | /** 5 | * Create a Svelte component with props bound to it. 6 | */ 7 | export declare function bind( 8 | component: Component, 9 | props: Record 10 | ): Component; 11 | export type Component = 12 | | typeof import("svelte").SvelteComponent 13 | | typeof import("svelte").SvelteComponent; 14 | 15 | export type BlurParams = import("svelte/types/runtime/transition").BlurParams; 16 | 17 | export type FadeParams = import("svelte/types/runtime/transition").FadeParams; 18 | 19 | export type FlyParams = import("svelte/types/runtime/transition").FlyParams; 20 | 21 | export type SlideParams = import("svelte/types/runtime/transition").SlideParams; 22 | 23 | export type TransitionConfig = 24 | import("svelte/types/runtime/transition").TransitionConfig; 25 | 26 | export type Styles = Record; 27 | 28 | export type TransitionFn = ( 29 | node: Element, 30 | parameters: BlurParams | FadeParams | FlyParams | SlideParams 31 | ) => TransitionConfig; 32 | 33 | export interface Options { 34 | id: string | null; 35 | ariaLabel: string | null; 36 | ariaLabelledBy: string | null; 37 | closeButton: Component | boolean; 38 | closeOnEsc: boolean; 39 | closeOnOuterClick: boolean; 40 | styleBg: Styles; 41 | styleWindowWrap: Styles; 42 | styleWindow: Styles; 43 | styleContent: Styles; 44 | styleCloseButton: Styles; 45 | classBg: string | null; 46 | classWindowWrap: string | null; 47 | classWindow: string | null; 48 | classContent: string | null; 49 | classCloseButton: string | null; 50 | transitionBg: TransitionFn; 51 | transitionBgProps: BlurParams; 52 | transitionWindow: TransitionFn; 53 | transitionWindowProps: BlurParams; 54 | disableFocusTrap: boolean; 55 | isTabbable: boolean; 56 | unstyled: boolean; 57 | } 58 | 59 | export type Callback = () => void; 60 | 61 | export interface Callbacks { 62 | onOpen: Callback; 63 | onOpened: Callback; 64 | onClose: Callback; 65 | onClosed: Callback; 66 | } 67 | 68 | export type Open = ( 69 | NewComponent: Component, 70 | newProps?: Record, 71 | options?: Partial, 72 | callbacks?: Partial 73 | ) => void; 74 | 75 | export type Close = (callback?: Partial) => void; 76 | 77 | export interface Context { 78 | open: Open; 79 | close: Close; 80 | } 81 | 82 | export interface ModalProps { 83 | /** 84 | * A function to determine if an HTML element is tabbable 85 | * @default undefined 86 | */ 87 | isTabbable?: (node: Element) => boolean; 88 | 89 | /** 90 | * Svelte component to be shown as the modal 91 | * @default null 92 | */ 93 | show?: Component | null; 94 | 95 | /** 96 | * Element ID assigned to the modal's root DOM element 97 | * @default null 98 | */ 99 | id?: string | null; 100 | 101 | /** 102 | * Svelte context key to reference the simple modal context 103 | * @default 'simple-modal' 104 | */ 105 | key?: string; 106 | 107 | /** 108 | * Accessibility label of the modal 109 | * @see https://www.w3.org/TR/wai-aria-1.1/#aria-label 110 | * @default null 111 | */ 112 | ariaLabel?: string | null; 113 | 114 | /** 115 | * Element ID holding the accessibility label of the modal 116 | * @see https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby 117 | * @default null 118 | */ 119 | ariaLabelledBy?: string | null; 120 | 121 | /** 122 | * Whether to show a close button or not 123 | * @default true 124 | */ 125 | closeButton?: Component | boolean; 126 | 127 | /** 128 | * Whether to close the modal on hitting the escape key or not 129 | * @default true 130 | */ 131 | closeOnEsc?: boolean; 132 | 133 | /** 134 | * Whether to close the modal upon an outside mouse click or not 135 | * @default true 136 | */ 137 | closeOnOuterClick?: boolean; 138 | 139 | /** 140 | * CSS for styling the background element 141 | * @default {} 142 | */ 143 | styleBg?: Styles; 144 | 145 | /** 146 | * CSS for styling the window wrapper element 147 | * @default {} 148 | */ 149 | styleWindowWrap?: Styles; 150 | 151 | /** 152 | * CSS for styling the window element 153 | * @default {} 154 | */ 155 | styleWindow?: Styles; 156 | 157 | /** 158 | * CSS for styling the content element 159 | * @default {} 160 | */ 161 | styleContent?: Styles; 162 | 163 | /** 164 | * CSS for styling the close element 165 | * @default {} 166 | */ 167 | styleCloseButton?: Styles; 168 | 169 | /** 170 | * Class name for the background element 171 | * @default null 172 | */ 173 | classBg?: string | null; 174 | 175 | /** 176 | * Class name for window wrapper element 177 | * @default null 178 | */ 179 | classWindowWrap?: string | null; 180 | 181 | /** 182 | * Class name for window element 183 | * @default null 184 | */ 185 | classWindow?: string | null; 186 | 187 | /** 188 | * Class name for content element 189 | * @default null 190 | */ 191 | classContent?: string | null; 192 | 193 | /** 194 | * Class name for close element 195 | * @default null 196 | */ 197 | classCloseButton?: string | null; 198 | 199 | /** 200 | * Do not apply default styles to the modal 201 | * @default false 202 | */ 203 | unstyled?: boolean; 204 | 205 | /** 206 | * The setContext() function associated with this library 207 | * @description If you want to bundle simple-modal with its own version of 208 | * Svelte you have to pass `setContext()` from your main app to simple-modal 209 | * using this parameter 210 | * @see https://svelte.dev/docs#run-time-svelte-setcontext 211 | * @default undefined 212 | */ 213 | setContext?: (key: any, context: T) => T; 214 | 215 | /** 216 | * Transition function for the background element 217 | * @see https://svelte.dev/docs#transition_fn 218 | * @default undefined 219 | */ 220 | transitionBg?: TransitionFn; 221 | 222 | /** 223 | * Parameters for the background element transition 224 | * @default { duration: 250 } 225 | */ 226 | transitionBgProps?: BlurParams | FadeParams | FlyParams | SlideParams; 227 | 228 | /** 229 | * Transition function for the window element 230 | * @see https://svelte.dev/docs#transition_fn 231 | * @default undefined 232 | */ 233 | transitionWindow?: TransitionFn; 234 | 235 | /** 236 | * Parameters for the window element transition 237 | * @default undefined 238 | */ 239 | transitionWindowProps?: BlurParams | FadeParams | FlyParams | SlideParams; 240 | 241 | /** 242 | * If `true` elements outside the modal can be focused 243 | * @default false 244 | */ 245 | disableFocusTrap?: boolean; 246 | } 247 | 248 | export default class Modal extends SvelteComponent< 249 | ModalProps, 250 | { 251 | open: CustomEvent; 252 | opening: CustomEvent; 253 | close: CustomEvent; 254 | closing: CustomEvent; 255 | opened: CustomEvent; 256 | closed: CustomEvent; 257 | }, 258 | { default: {} } 259 | > {} 260 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as Modal, bind } from "./Modal.svelte"; 2 | export { default } from "./Modal.svelte"; 3 | export { Context, Open, Close } from "./Modal.svelte"; 4 | --------------------------------------------------------------------------------