npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

svelte-promise-modals

v0.1.4

Published

The better way to handle modals in your Svelte apps. Promised! 🤞

Downloads

321

Readme

Svelte Promise Modals

The better way to handle modals in your Svelte apps. Promised! 🤞

[!NOTE] svelte-promise-modals was written and is maintained by Mainmatter and contributors. We offer consulting, training, and team augmentation for Svelte & SvelteKit – check out our website to learn more!

Compatibility

  • Svelte v3 or above
  • Node v16 or above

Installation

npm install --save-dev svelte-promise-modals

Usage

To use SPM in your project, add the target for the modals to your root template:

<!-- Eg. in SvelteKit: src/routes/+layout.svelte -->
<script>
  import 'svelte-promise-modals/style.css';
  import { ModalContainer } from 'svelte-promise-modals';
</script>

<slot />

<ModalContainer />

Then you can import the openModal function wherever you need it and call with a component reference to render it as a modal.

<script>
  import { openModal } from 'svelte-promise-modals';
  import SomeComponent from './SomeComponent.svelte';

  async function handleOpenModal() {
    let modal = openModal(SomeComponent);

    // the instance acts as a promise that resolves with anything passed to the close function
    modal.then((result) => {
      // do something with the data
    });

    // so does `await`ing it!
    let result = await modal;

    // you can also close the modal from outside
    modal.close();
  }
</script>

<button type="button" on:click={handleOpenModal}>Open "SomeComponent" as a modal</button>

Passing data to the rendered component

Passing data to the component rendered as a modal is done via props like so:

openModal(FilePreview, {
  fileUrl: 'http://example.com/some-file.pdf',
});

Each key of the object is just a regular prop:

<!-- FilePreview.svelte -->
<script>
  export let fileUrl;
</script>

<img src={fileUrl} />

NOTE: By default, a closeModal function is passed in your rendered component, in order to trigger the "close modal" action. It can be called like so:

<!-- FilePreview.svelte -->
<script>
  export let closeModal;
</script>

<button type="button" on:click={() => closeModal('some result')}>Close</button>

TypeScript

In order to make sure you don't pass something other to closeModal than expected, you can specify its value param type in your modal component using the CloseModalFn<T> type, such as:

<!-- MyModal.svelte -->
<script lang="ts">
  import type { CloseModalFn } from './svelte-promise-modals';
  export let closeModal: CloseModalFn<string>;

  function handleClose() {
    closeModal('foo');
  }
</script>

Then when you open the modal, it'll correctly infer the type of the result:

<script lang="ts">
  import { openModal } from 'svelte-promise-modals';
  import MyModal from './MyModal.svelte';

  async function handleOpenModal() {
    // You can specify `string` here, but it's also automatically inferred
    let result: string = openModal(MyModal);
  }
</script>

If you don't pass a type parameter to CloseModalFn, it means you won't be passing anything to closeModal, such as:

// This means you can only call `closeModal();` without params
export let closeModal: CloseModalFn;

And the result will be undefined:

let result: undefined = await openModal(MyModal);

Last, but not least, you can omit closeModal entirely, but then you'll have to close the modal from when you opened it.

Destroying the component

It's worth noting that since modals are opened as a descendant of ModalContainer, and therefore likely placed at the root layout, when the component the modal was opened from gets destroyed, such as when navigating away from a route, the modal will continue to live on. To automatically destroy the modal in such cases, create a modal context first, then use its openModal function instead of the one exported from the package. Modal context's openModal function hooks into onDestroy, ensuring all modals opened from that specific component gets destroyed when the component is unrendered.

<script>
  import { useModalContext } from 'svelte-promise-modals';

  let { openModal } = useModalContext();

  async function handleOpenModal() {
    let result = await openModal(FooModal);
    // The modal will get destroyed if the component is destroyed
  }
</script>

Animation

This addon uses CSS animations. You can either replace the styles of this package with your own or adjust the defaults using CSS custom properties in your :root{} declaration or in the CSS of any parent container of <ModalContainer />.

Available properties and their defaults can be found in the :root {} block inside the package CSS.

By default, the animations are dropped when prefers-reduced-motion is detected.

Custom animations

To override the animation for a specific modal, an options object containing a custom className can be handed to the openModal() method.

openModal(
  FilePreview,
  {
    fileUrl: 'http://example.com/some-file.pdf',
  },
  {
    // custom class, see below for example
    className: 'custom-modal',
    // optional: a hook that is called when the closing animation of
    //           the modal (so not the backdrop) has finished.
    onAnimationModalOutEnd: () => {},
  }
);
.custom-modal {
  animation: custom-animation-in 0.5s;
  opacity: 1;
  transform: translate(0, 0);
}

/* 
  The `.spm-out` class is added to the parent of the modal when the modal 
  should be closed, which triggers the animation
*/
.custom-modal.spm-out {
  animation: custom-animation-name-out 0.2s; /* default out animation is 2s */
  opacity: 0;
  transform: translate(0, 100%);
}

/* 
  animation name has to end in "-out" to be detected by the custom animationend 
  event handler 
*/
@keyframes custom-animation-name-out {
  0% {
    opacity: 1;
    transform: translate(0, 0);
  }
  100% {
    opacity: 0;
    transform: translate(0, 100%);
  }
}

The CSS animations which are applied by the custom CSS class must end in -out to make the animations trigger the modal removal.

Examples

Examples for custom animations and how to apply them can be found in the addons dummy application.

See index route for how the modals are openend in and look at app.css for the style definition of these custom animations.

CSS Variables

Use the below CSS variables to override the defaults:

--spm-animation-backdrop-in-duration;
--spm-animation-backdrop-out-duration;
--spm-animation-modal-in-duration;
--spm-animation-modal-out-duration;
--spm-animation-backdrop-in-delay;
--spm-animation-backdrop-out-delay;
--spm-animation-modal-in-delay;
--spm-animation-modal-out-delay;
--spm-animation-backdrop-in;
--spm-animation-backdrop-out;
--spm-animation-modal-in;
--spm-animation-modal-out;
--spm-backdrop-background;

Accessibility

User can press the Esc key to close the modal.

SPM uses focus-trap internally to handle user focus.

SPM will ensure to focus the first "tabbable element" by default. If no focusable element is present, focus will be applied on the currently visible auto-generated container for the current modal.

Focus Trap can be configured both on the <ModalContainer /> component, and the individual modal level when calling openModal(). Global and local options are used in that order, which means that local config take precedence.

To set global Focus Trap config that all modals inherit, provide the focusTrapOptions property to the <ModalContainer /> component:

<ModalContainer
  options={{
    focusTrapOptions: {
      clickOutsideDeactivates: false,
    },
  }}
/>

Example for local Focus Trap option, when opening a specific modal:

openModal(
  FilePreview,
  { fileUrl: 'http://example.com/some-file.pdf' },
  {
    focusTrapOptions: {
      clickOutsideDeactivates: false,
    },
  }
);

To disable Focus Trap completely, set focusTrapOptions to null on the <ModalContainer />:

<ModalContainer
  options={{
    focusTrapOptions: null,
  }}
/>

Or when opening a modal:

openModal(
  FilePreview,
  { fileUrl: 'http://example.com/some-file.pdf' },
  {
    focusTrapOptions: null,
  }
);

⚠️ We strongly advise against doing this. This will in most cases worsen the accessibility of modals for your users. Be very careful.

Testing

In order to speed up modal in/out animations during testing, either:

  • Switch to reduced motion, for ex. in Playwright:
    await page.emulateMedia({ reducedMotion: 'reduce' });
  • Include the testing.css into your app that will do the same thing

Contributing

Once you've cloned the project and installed dependencies with pnpm install, start a development server:

npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open

License

svelte-promise-modals is developed by and © Mainmatter GmbH and contributors. It is released under the MIT License.