UNPKG

svelte-simple-modal

Version:
440 lines (326 loc) 22.6 kB
<h1 align="center"> Svelte Simple Modal </h1> <div align="center"> **A simple, small, and content-agnostic modal for [Svelte](https://svelte.dev).** </div> <br/> <div align="center"> [![NPM Version](https://img.shields.io/npm/v/svelte-simple-modal.svg?style=flat-square&color=7f99ff)](https://npmjs.org/package/svelte-simple-modal) [![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) [![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) [![Code Style Prettier](https://img.shields.io/badge/code%20style-prettier-ff7fe1.svg?style=flat-square)](https://github.com/prettier/prettier#readme) [![Demo](https://img.shields.io/badge/demo-👍-ff7fa5.svg?style=flat-square)](https://svelte.dev/repl/033e824fad0a4e34907666e7196caec4?version=3.18.2) </div> <div id="teaser-matrices" align="center"> ![simple-modal](https://user-images.githubusercontent.com/932103/57642565-9d335d00-7585-11e9-80c6-e4b835f02428.gif) </div> **Live demo:** https://svelte.dev/playground/b95ce66b0ef34064a34afc5c0249f313?version=5.17.3 **Works with:** Svelte `>=v5`! > [!NOTE] > 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). ## Table of Contents - [Install](#install) - [Rollup Setup](#rollup-setup) - [Usage](#usage) - [TypeScript Example](#usage-with-typescript) - [Svelte Store Example](#usage-with-a-svelte-store) - [Styling](#styling) - [SSR](#server-side-rendering) - [Accessibility](#accessibility) - [API](#api) - [Component](#component-api) - [Events](#component-events) - [Context API](#context-api) - [Store API](#store-api) - [FAQ](#faq) ## Install ```bash npm install --save svelte-simple-modal ``` #### Rollup Setup 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: ```js import resolve from 'rollup-plugin-node-resolve'; export default { plugins: [ resolve({ dedupe: ['svelte', 'svelte/transition', 'svelte/internal'], // important! }), ], }; ``` ## Usage Import the `Modal` component into your main app component (e.g., `App.svelte`). The modal is exposing [two context functions](#context-api): - [`open()`](#open) opens a component as a modal. - [`close()`](#close) simply closes the modal. ```svelte <!-- App.svelte --> <script> import Content from './Content.svelte'; import Modal from 'svelte-simple-modal'; </script> <Modal><Content /></Modal> <!-- Content.svelte --> <script> import { getContext } from 'svelte'; import Popup from './Popup.svelte'; const { open } = getContext('simple-modal'); const showSurprise = () => open(Popup, { message: "It's a modal!" }); </script> <p><button on:click={showSurprise}>Show me a surprise!</button></p> <!-- Popup.svelte --> <script> export let message = 'Hi'; </script> <p>🎉 {message} 🍾</p> ``` **Demo:** https://svelte.dev/repl/52e0ade6d42546d8892faf8528c70d30 ### Usage with TypeScript You can use the `Context` type exported by the package to validate the `getContext` function: ```ts import type { Context } from 'svelte-simple-modal'; const { open } = getContext<Context>('simple-modal'); // ... ``` ### Usage With a Svelte Store Alternatively, you can use a [Svelte store](#store-api) to show/hide a component as a modal. ```svelte <!-- App.svelte --> <script> import { writable } from 'svelte/store'; import Modal, { bind } from 'svelte-simple-modal'; import Popup from './Popup.svelte'; const modal = writable(null); const showModal = () => modal.set(bind(Popup, { message: "It's a modal!" })); </script> <Modal show={$modal}> <button on:click={showModal}>Show modal</button> </Modal> ``` **Demo:** https://svelte.dev/repl/aec0c7d9f5084e7daa64f6d0c8ef0209 The `<Popup />` component is the same as in the example above. To hide the modal programmatically, simply unset the store. E.g., `modal.set(null)`. ### Styling 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. 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: ```svelte <Modal show={$modal} unstyled={true} classBg="fixed top-0 left-0 w-screen h-screen flex flex-col justify-center bg-orange-100/[.9]" classWindowWrap="relative m-2 max-h-full" classWindow="relative w-40 max-w-full max-h-full my-2 mx-auto text-orange-200 rounded shadow-md bg-indigo-900" classContent="relative p-2 overflow-auto" closeButton={false} > <button on:click={showModal}>Show modal</button> </Modal> ``` **Demo:** https://svelte.dev/repl/f2a988ddbd5644f18d7cd4a4a8277993 > 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. Alternatively, you can also apply CSS styles directly via the `styleBg`, `styleWindow`, `styleWindowWrap`, `styleContent`, and `styleCloseButton` properties. For instance: ```svelte <Modal show={$modal} styleBg={{ backgroundColor: 'rgba(255, 255, 255, 0.85)' }} styleWindow={{ boxShadow: '0 2px 5px 0 rgba(0, 0, 0, 0.15)' }} > <button on:click={showModal}>Show modal</button> </Modal> ``` **Demo:** https://svelte.dev/repl/50df1c694b3243c1bd524b27f86eec51 ### Server-Side Rendering 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: ```svelte <script> import Modal from 'svelte-simple-modal'; import { browser } from '$app/env'; </script> {#if browser} <Modal> <Content /> </Modal> {/if} ``` Alternatively, you can enable SSR by dynamically importing svelte-simple-modal in `onMount()` as follows: ```js import { onMount } from 'svelte'; onMount(async () => { const svelteSimpleModal = await import('svelte-simple-modal'); Modal = svelteSimpleModal.default; }); ``` ### Accessibility The library applies the following WAI-ARIA guidelines for modal dialogs automtically: - `aria-modal="true"` and `role="dialog"` are applied automatically - The tab focus is trapped in the modal - The modal is closed on pressing the `esc` key To further improve the accessibility you'll have to either provide a label via [`ariaLabel`](https://www.w3.org/TR/wai-aria-1.1/#aria-label) or reference a title element via [`ariaLabelledBy`](https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby). The `ariaLabel` is automatically omitted when `ariaLabelledBy` is specified. ## API ### Component API The `<Modal />` component accepts the following optional properties: | Property | Type | Default | Description | | ------------------------- | ---------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **show** | Component \| null | `null` | A Svelte component to show as the modal. See [Store API](#store-api) for details. | | **id** | string \| null | `null` | Element ID to be assigned to the modal's root DOM element. | | **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. | | **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. | | **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. | | **closeOnEsc** | boolean | `true` | If `true` the modal will close when pressing the escape key. | | **closeOnOuterClick** | boolean | `true` | If `true` the modal will close when clicking outside the modal window. | | **transitionBg** | function | `svelte.fade` | Transition function for the background. | | **transitionBgProps** | BlurParams \| FadeParams \| FlyParams \| SlideParams | `{}` | Properties of the transition function for the background. | | **transitionWindow** | function | `svelte.fade` | Transition function for the window. | | **transitionWindowProps** | BlurParams \| FadeParams \| FlyParams \| SlideParams | `{}` | Properties of the transition function for the window. | | **classBg** | string \| null | `null` | Class name for the background element. | | **classWindowWrap** | string \| null | `null` | Class name for the modal window wrapper element. | | **classWindow** | string \| null | `null` | Class name for the modal window element. | | **classContent** | string \| null | `null` | Class name for the modal content element. | | **classCloseButton** | string \| null | `null` | Class name for the built-in close button. | | **styleBg** | Record<string, string \| number> | `{}` | Style properties of the background. | | **styleWindowWrap** | Record<string, string \| number> | `{}` | Style properties of the modal window wrapper element. | | **styleWindow** | Record<string, string \| number> | `{}` | Style properties of the modal window. | | **styleContent** | Record<string, string \| number> | `{}` | Style properties of the modal content. | | **styleCloseButton** | Record<string, string \| number> | `{}` | Style properties of the built-in close button. | | **unstyled** | boolean | `false` | When `true`, the default styles are not applied to the modal elements. | | **disableFocusTrap** | boolean | `false` | If `true` elements outside the modal can be in focus. This can be useful in certain edge cases. | | **isTabbable** | (node: Element): boolean | internal function | A function to determine if an HTML element is tabbable. | | **key** | string | `"simple-modal"` | The context key that is used to expose `open()` and `close()`. Adjust to avoid clashes with other contexts. | | **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. | ### Component Events The `<Modal />` component dispatches the following events: - `open`: dispatched when the modal window starts to open. - `opened`: dispatched when the modal window opened. - `close`: dispatched when the modal window starts to close. - `closed`: dispatched when the modal window closed. Alternatively, you can listen to those events via callbacks passed to [`open()`](#open) and [`close()`](#close). ### Context API 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: ```js const { open, close } = getContext('simple-modal'); ``` <a name="open" href="#open">#</a> <b>open</b>(<i>Component</i>, <i>props = {}</i>, <i>options = {}</i>, <i>callbacks = {}</i>) Opens the modal with `<Component {props}>` 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`: ```javascript { closeButton: false, closeOnEsc: false, closeOnOuterClick: false, transitionBg: fade, transitionBgProps: { duration: 5000 }, transitionWindow: fly, transitionWindowProps: { y: 100, duration: 250 }, styleBg: { backgroundImage: 'http://example.com/my-background.jpg' }, styleWindow: { fontSize: '20em' }, styleContent: { color: 'yellow' }, styleCloseButton: { width: '3rem', height: '3rem' }, disableFocusTrap: true } ``` <a name="close" href="#close">#</a> <b>close</b>(<i>callbacks = {}</i>) Closes the modal. Similar to `open()`, this method supports adding callbacks for the closing transition: ```javascript { onClose: () => { /* modal window starts to close */ }, onClosed: () => { /* modal window closed */ }, } ``` Callbacks are triggered at the beginning and end of the opening and closing transition. The following callbacks are supported: ```javascript { onOpen: () => { /* modal window starts to open */ }, onOpened: () => { /* modal window opened */ }, onClose: () => { /* modal window starts to close */ }, onClosed: () => { /* modal window closed */ }, } ``` ### Store API You can also use [Svelte stores](https://svelte.dev/tutorial/writable-stores) to open a modal using the `<Modal />`'s [`show` property](#properties) as follows: ```svelte <!-- App.svelte --> <script> import { writable } from 'svelte/store'; import Modal from 'svelte-simple-modal'; import Popup from './Popup.svelte'; const modal = writable(null); const showModal = () => modal.set(Popup); </script> <Modal show={$modal}> <button on:click={showModal}>Show modal</button> </Modal> <!-- Popup.svelte --> <p>🎉 Hi 🍾</p> ``` **Demo:** https://svelte.dev/repl/6f55b643195646408e780ceeb779ac2a An added benefit of using stores is that the component opening the modal does not have to be a parent of `<Modal />`. For instance, in the example above, `App.svelte` is toggling `Popup.svelte` as a modal even though `App.svelte` is not a child of `<Modal />`. #### Bind Props to a Component Shown as a Modal 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: ```svelte <script> import { bind } from 'svelte-simple-modal'; import Popup from './Popup.svelte'; import { modal } from './App.svelte'; modal.set(bind(Popup, { name: 'custom name' })); </script> ``` If you've worked with React/JSX then think of `const c = bind(Component, props)` as the equivalent of `const c = <Component ...props />`. ## FAQ #### Custom Close Button **This feature requires Svelte >=v3.19!** 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 `<Modal />` or `open()`. For example, your close button might look as follows: ```svelte <!-- CloseButton.svelte --> <script> // This property is used by Modal.svelte to pass down the close function export let onClose; </script> <button on:click={onClose}>Custom Close Button</button> <style> /* Customize to your liking */ button { position: absolute; top: -3rem; right: 0; } </style> ``` Now you can set it as the default close button by passing it to `<Modal />` as follows: ```svelte <!-- App.svelte --> <script> import Content from './Content.svelte'; import CloseButton from './CloseButton.svelte'; import Modal from 'svelte-simple-modal'; </script> <Modal closeButton={CloseButton}> <Content /> </Modal> ``` Or you can pass `CloseButton` to `open()` as shown below: ```svelte <!-- Content.svelte --> <script> import { getContext } from 'svelte'; import Surprise from './Surprise.svelte'; import CloseButton from './CloseButton.svelte'; const { open } = getContext('simple-modal'); const showSurprise = () => { open(Surprise, { message: "It's a modal!" }, { closeButton: CloseButton }); }; </script> <p><button on:click={showSurprise}>Show me a surprise!</button></p> ``` ## License [MIT](LICENSE)