Skip to content

Commit 2de1862

Browse files
committedJun 12, 2021
remove context api, use shared store instead
1 parent 9cdef78 commit 2de1862

15 files changed

+209
-298
lines changed
 

‎src/lib/ModalStack.svelte

+5-84
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,5 @@
1-
<script context="module">
2-
export function useModals() {
3-
const ctx = getContext('svelte-modal-stack')
4-
5-
if (!ctx) {
6-
throw Error('useModals() requires a parent <ModalStack /> component')
7-
}
8-
9-
return ctx
10-
}
11-
12-
</script>
13-
141
<script>
15-
import { getContext, setContext } from 'svelte'
16-
import { writable } from 'svelte/store'
17-
18-
let stack = writable([])
19-
let action = writable(null)
20-
21-
let exitBeforeEnter = false
22-
let transitioning = null
23-
24-
function pop(amount = 1) {
25-
$stack = [...$stack].slice(0, $stack.length - amount)
26-
}
27-
28-
function closeAllModals() {
29-
$stack = []
30-
}
31-
32-
function closeModals(amount = 1) {
33-
if (transitioning) {
34-
return
35-
}
36-
37-
if (exitBeforeEnter && $stack.length > 0) {
38-
transitioning = true
39-
}
40-
exitBeforeEnter = false
41-
42-
$action = 'pop'
43-
44-
if ($stack.length === 1) {
45-
closeAllModals()
46-
} else {
47-
pop(amount)
48-
}
49-
}
50-
51-
function openModal(component, props, options) {
52-
let newStack = [...$stack]
53-
54-
if (transitioning) {
55-
return
56-
}
57-
58-
$action = 'push'
59-
60-
if (options?.replace) {
61-
newStack = $stack.slice(0, $stack.length - 1)
62-
}
63-
64-
if (exitBeforeEnter && $stack.length > 0) {
65-
transitioning = true
66-
}
67-
exitBeforeEnter = false
68-
69-
$stack = [...newStack, { component, props }]
70-
}
71-
72-
const context = {
73-
openModal,
74-
closeModal: () => closeModals(1),
75-
closeModals,
76-
closeAllModals,
77-
stack,
78-
action
79-
}
80-
81-
setContext('svelte-modal-stack', context)
2+
import { stack, exitBeforeEnter, transitioning } from './store'
823
834
</script>
845

@@ -90,16 +11,16 @@
9011
{#each $stack as modal, i (i)}
9112
<svelte:component
9213
this={modal.component}
93-
isOpen={i === $stack.length - 1 && !transitioning}
14+
isOpen={i === $stack.length - 1 && !$transitioning}
9415
on:introstart={() => {
95-
exitBeforeEnter = true
16+
$exitBeforeEnter = true
9617
}}
9718
on:outroend={() => {
98-
transitioning = false
19+
$transitioning = false
9920
}}
10021
{...modal.props || {}}
10122
/>
10223
{/each}
10324
</slot>
10425

105-
<slot {...context} stack={$stack} />
26+
<slot stack={$stack} />

‎src/lib/ModalStack.svelte.d.ts

+4-52
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,13 @@
1-
import { SvelteComponent, SvelteComponentTyped, SvelteComponentDev } from 'svelte'
2-
import { Writable } from 'svelte/store'
3-
4-
export interface ModalStackContext {
5-
/**
6-
* Adds a Modal component to the stack
7-
*/
8-
openModal: <T>(
9-
component: SvelteComponent | SvelteComponentTyped<T> | SvelteComponentDev,
10-
props?: T,
11-
options?: { replace?: boolean }
12-
) => void
13-
14-
/**
15-
* Closes the current modal component
16-
*/
17-
closeModal: () => void
18-
19-
/**
20-
* Closes the last `amount` of modals in the stack
21-
*/
22-
closeModals: (amount: number) => void
23-
24-
/**
25-
* Closes all modals in the stack
26-
*/
27-
closeAllModals: () => void
28-
29-
/**
30-
* A Svelte store containing the current modal stack
31-
*/
32-
stack: Writable<Array<{ component: SvelteComponent; props?: unknown }>>
33-
34-
/**
35-
* If `exitBeforeEnter` is enabled on the ModalStack component, this store will contain
36-
* the current transition state (either null, 'in' or 'out')
37-
*/
38-
transitioning: Writable<null | 'in' | 'out'>
39-
40-
/**
41-
* A store describing how the current modal came to be active. "push" means it was
42-
* newly added (from openModal), "pop" means the modal ahead of it was closed (closeModal).
43-
*
44-
* This can be useful for animations
45-
*/
46-
action: Writable<null | 'push' | 'pop'>
47-
}
48-
49-
export function useModals(): ModalStackContext
1+
import { SvelteComponentTyped } from 'svelte'
502

513
export default class ModalStack extends SvelteComponentTyped<
524
{
535
exitBeforeEnter?: boolean
546
},
557
Record<string, never>,
568
{
57-
backdrop: ModalStackContext
58-
modals: ModalStackContext
59-
default: ModalStackContext
9+
backdrop: Record<string, never>
10+
modals: Record<string, never>
11+
default: Record<string, never>
6012
}
6113
> {}

‎src/lib/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { default as ModalStack, useModals } from './ModalStack.svelte';
1+
export { default as ModalStack } from './ModalStack.svelte'
2+
export { action, stack, openModal, closeModal, closeModals, closeAllModals } from './store'

‎src/lib/store.d.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { SvelteComponent, SvelteComponentTyped } from 'svelte'
2+
import { SvelteComponentDev } from 'svelte/internal'
3+
import { Writable } from 'svelte/store'
4+
5+
/**
6+
* Adds a Modal component to the stack
7+
*/
8+
export const openModal: <T>(
9+
component: SvelteComponent | SvelteComponentTyped<T> | SvelteComponentDev,
10+
props?: T,
11+
options?: { replace?: boolean }
12+
) => void
13+
14+
/**
15+
* Closes the current modal component
16+
*/
17+
export const closeModal: () => void
18+
19+
/**
20+
* Closes the last `amount` of modals in the stack
21+
*/
22+
export const closeModals: (amount: number) => void
23+
24+
/**
25+
* Closes all modals in the stack
26+
*/
27+
export const closeAllModals: () => void
28+
29+
/**
30+
* A Svelte store containing the current modal stack
31+
*/
32+
export const stack: Writable<Array<{ component: SvelteComponent; props?: unknown }>>
33+
34+
/**
35+
* The transition state of the modals
36+
*/
37+
export const transitioning: Writable<null | 'in' | 'out'>
38+
39+
/**
40+
* A store describing how the current modal came to be active. "push" means it was
41+
* newly added (from openModal), "pop" means the modal ahead of it was closed (closeModal).
42+
*
43+
* This can be useful for animations
44+
*/
45+
export const action: Writable<null | 'push' | 'pop'>

‎src/lib/store.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { SvelteComponent, SvelteComponentTyped } from 'svelte'
2+
import type { SvelteComponentDev } from 'svelte/internal'
3+
4+
import { get, writable } from 'svelte/store'
5+
6+
export const exitBeforeEnter = writable(false)
7+
export const transitioning = writable(null)
8+
9+
export const stack = writable<Array<{ component: SvelteComponent; props?: unknown }>>([])
10+
11+
export const action = writable<null | 'push' | 'pop'>(null)
12+
13+
function pop(amount = 1) {
14+
stack.update((prev) => prev.slice(0, Math.max(0, prev.length - amount)))
15+
}
16+
17+
export function closeAllModals(): void {
18+
stack.set([])
19+
}
20+
21+
export function closeModals(amount = 1): void {
22+
if (get(transitioning)) {
23+
return
24+
}
25+
26+
const stackLength = get(stack).length
27+
if (get(exitBeforeEnter) && stackLength > 0) {
28+
transitioning.set(true)
29+
}
30+
exitBeforeEnter.set(false)
31+
32+
action.set('pop')
33+
34+
pop(amount)
35+
}
36+
37+
export function closeModal(): void {
38+
return closeModals(1)
39+
}
40+
41+
export function openModal<T>(
42+
component: SvelteComponent | SvelteComponentTyped<T> | SvelteComponentDev,
43+
props?: T,
44+
options?: { replace?: boolean }
45+
): void {
46+
if (get(transitioning)) {
47+
return
48+
}
49+
50+
action.set('push')
51+
52+
if (options?.replace) {
53+
stack.update((prev) => prev.slice(0, prev.length - 1))
54+
}
55+
56+
if (get(exitBeforeEnter) && get(stack).length) {
57+
transitioning.set(true)
58+
}
59+
exitBeforeEnter.set(false)
60+
61+
stack.update((prev) => [...prev, { component, props }])
62+
}

‎src/routes/_AlertModal.svelte

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<script>
2-
import { useModals } from 'svelte-modal-stack'
3-
4-
const { closeModal } = useModals()
2+
import { closeModal } from 'svelte-modal-stack'
53
64
export let isOpen
75
export let title

‎src/routes/__layout.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script>
2-
import { ModalStack } from 'svelte-modal-stack'
2+
import { ModalStack, closeModal } from 'svelte-modal-stack'
33
import '../app.css'
44
import '../prism.css'
55
import Sidebar from './_components/Sidebar.svelte'
66
77
</script>
88

99
<ModalStack>
10-
<div slot="backdrop" let:closeModal class="backdrop" on:click={closeModal} />
10+
<div slot="backdrop" class="backdrop" on:click={closeModal} />
1111
<div class="h-screen bg-white overflow-hidden flex sm:max-w-6xl mx-auto">
1212
<Sidebar />
1313
<div class="flex-1 w-0 flex flex-col overflow-y-auto md:px-8 xl:px-0 ">
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,65 @@
11
<script>
2-
import { useModals } from 'svelte-modal-stack'
3-
import { fade } from 'svelte/transition'
2+
import { closeModal } from 'svelte-modal-stack'
3+
import { fade } from 'svelte/transition'
44
5-
const { closeModal } = useModals()
6-
7-
export let isOpen
8-
export let title
9-
export let message
5+
export let isOpen
6+
export let title
7+
export let message
108
119
</script>
1210

1311
{#if isOpen}
14-
<div role="dialog" class="modal" transition:fade on:introstart on:outroend>
15-
<div class="contents">
16-
<h2>{title}</h2>
17-
<p>{message}</p>
18-
<div class="actions">
19-
<button on:click={closeModal}>OK</button>
20-
</div>
21-
</div>
22-
</div>
12+
<div role="dialog" class="modal" transition:fade on:introstart on:outroend>
13+
<div class="contents">
14+
<h2>{title}</h2>
15+
<p>{message}</p>
16+
<div class="actions">
17+
<button on:click={closeModal}>OK</button>
18+
</div>
19+
</div>
20+
</div>
2321
{/if}
2422

2523
<style>
26-
.modal {
27-
position: fixed;
28-
top: 0;
29-
bottom: 0;
30-
right: 0;
31-
left: 0;
32-
display: flex;
33-
justify-content: center;
34-
align-items: center;
24+
.modal {
25+
position: fixed;
26+
top: 0;
27+
bottom: 0;
28+
right: 0;
29+
left: 0;
30+
display: flex;
31+
justify-content: center;
32+
align-items: center;
3533
36-
/* allow click-through to backdrop */
37-
pointer-events: none;
38-
}
34+
/* allow click-through to backdrop */
35+
pointer-events: none;
36+
}
3937
40-
.contents {
41-
min-width: 240px;
42-
border-radius: 6px;
43-
padding: 16px;
44-
background: white;
45-
display: flex;
46-
flex-direction: column;
47-
justify-content: space-between;
48-
pointer-events: auto;
49-
}
38+
.contents {
39+
min-width: 240px;
40+
border-radius: 6px;
41+
padding: 16px;
42+
background: white;
43+
display: flex;
44+
flex-direction: column;
45+
justify-content: space-between;
46+
pointer-events: auto;
47+
}
5048
51-
h2 {
52-
text-align: center;
53-
font-size: 24px;
54-
}
49+
h2 {
50+
text-align: center;
51+
font-size: 24px;
52+
}
5553
56-
p {
57-
text-align: center;
58-
margin-top: 16px;
59-
}
54+
p {
55+
text-align: center;
56+
margin-top: 16px;
57+
}
6058
61-
.actions {
62-
margin-top: 32px;
63-
display: flex;
64-
justify-content: flex-end;
65-
}
59+
.actions {
60+
margin-top: 32px;
61+
display: flex;
62+
justify-content: flex-end;
63+
}
6664
6765
</style>

‎src/routes/animation/_AnimatedInfiniteModal.svelte

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
<script>
22
import { createEventDispatcher } from 'svelte'
33
4-
import { useModals } from 'svelte-modal-stack'
4+
import { closeModal, stack } from 'svelte-modal-stack'
55
import { fade, fly } from 'svelte/transition'
66
7-
const { closeModal, stack } = useModals()
8-
97
const dispatch = createEventDispatcher()
108
119
export let isOpen

‎src/routes/animation/index.md

+19-58
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { ModalStack, useModals } from 'svelte-modal-stack'
2+
import { ModalStack, openModal } from 'svelte-modal-stack'
33
import AnimatedAlertModal from './_AnimatedAlertModal.svelte'
44
import AnimatedInfiniteModal from './_AnimatedInfiniteModal.svelte'
55
import { fade } from 'svelte/transition'
@@ -20,14 +20,13 @@ Let's add a fade to our backdrop by adding `transition:fade`
2020

2121
```svelte
2222
<script>
23-
import { ModalStack } from 'svelte-modal-stack'
23+
import { ModalStack, closeModal } from 'svelte-modal-stack'
2424
import { fade } from 'svelte/transition'
2525
</script>
2626
2727
<ModalStack>
2828
<div
2929
slot="backdrop"
30-
let:closeModal
3130
class="backdrop"
3231
transition:fade
3332
on:click={closeModal}
@@ -40,11 +39,9 @@ and let's do the same for the modal
4039

4140
```svelte
4241
<script>
43-
import { useModals } from 'svelte-modal-stack'
42+
import { closeModal } from 'svelte-modal-stack'
4443
import { fade } from 'svelte/transition'
4544
46-
const { closeModal } = useModals()
47-
4845
export let isOpen
4946
export let title
5047
export let message
@@ -64,29 +61,17 @@ and let's do the same for the modal
6461
{/if}
6562
```
6663

67-
<ModalStack let:openModal>
68-
<div
69-
slot="backdrop"
70-
let:closeModal
71-
class="backdrop"
72-
transition:fade
73-
on:click={closeModal}
74-
/>
75-
<button
76-
class="mt-6"
77-
on:click={() => {
78-
openModal(AnimatedAlertModal, { title: 'Alert', message: 'This is an alert' })
79-
}}
80-
>
81-
Try it out
82-
</button>
83-
</ModalStack>
64+
<button
65+
class="mt-6"
66+
on:click={() => {
67+
openModal(AnimatedAlertModal, { title: 'Alert', message: 'This is an alert' })
68+
}}> Try it out</button>
8469

8570
## Transitions between Modals
8671

87-
If you are opening one modal after another, the transitions will overlap. Sometimes this is desired, but most times probably not.
72+
If you are opening one modal after another, the intro and outro transitions of both modals will overlap. Depending on your animation, this might be ok, but often it's cleaner to transition one at a time.
8873

89-
To change this, modals can transition one at a time as long as they forward the `on:introstart` and `on:outroend` events.
74+
You can do this by forwarding the `on:introstart` and `on:outroend` events in your modal components.
9075

9176
```svelte
9277
<script>
@@ -102,38 +87,14 @@ To change this, modals can transition one at a time as long as they forward the
10287
{/if}
10388
```
10489

105-
Let's see how they compare:
90+
Let's see how the transitions compare:
10691

107-
<ModalStack let:openModal>
108-
<div
109-
slot="backdrop"
110-
let:closeModal
111-
class="backdrop"
112-
transition:fade
113-
on:click={closeModal}
114-
/>
115-
<button
116-
on:click={() => {
117-
openAnimatedInfiniteModal(openModal)
118-
}}
119-
>
120-
Before
121-
</button>
122-
</ModalStack>
92+
<button
93+
on:click={() => {
94+
openAnimatedInfiniteModal(openModal)
95+
}}>Overlapped</button>
12396

124-
<ModalStack let:openModal>
125-
<div
126-
slot="backdrop"
127-
let:closeModal
128-
class="backdrop"
129-
transition:fade
130-
on:click={closeModal}
131-
/>
132-
<button
133-
on:click={() => {
134-
openAnimatedInfiniteModal(openModal, { exitBeforeEnter: true })
135-
}}
136-
>
137-
After
138-
</button>
139-
</ModalStack>
97+
<button
98+
on:click={() => {
99+
openAnimatedInfiniteModal(openModal, { exitBeforeEnter: true })
100+
}}>Deferred</button>

‎src/routes/index.md

+9-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { ModalStack, useModals } from 'svelte-modal-stack'
2+
import { ModalStack, openModal, closeModal} from 'svelte-modal-stack'
33
import AlertModal from './_AlertModal.svelte'
44
import { fade } from 'svelte/transition'
55
</script>
@@ -16,13 +16,12 @@ Add `ModalStack` at the root of your app (or in your \_\_layout if using SvelteK
1616

1717
```svelte
1818
<script>
19-
import { ModalStack } from 'svelte-modal-stack'
19+
import { ModalStack, closeModal } from 'svelte-modal-stack'
2020
</script>
2121
2222
<ModalStack>
2323
<div
2424
slot="backdrop"
25-
let:closeModal
2625
class="backdrop"
2726
on:click={closeModal}
2827
/>
@@ -47,9 +46,7 @@ Create your Modal component
4746
```svelte
4847
<!-- Modal.svelte -->
4948
<script>
50-
import { useModals } from 'svelte-modal-stack'
51-
52-
const { closeModal } = useModals()
49+
import { closeModal } from 'svelte-modal-stack'
5350
5451
// provided by ModalStack
5552
export let isOpen
@@ -120,11 +117,9 @@ Open it
120117

121118
```svelte
122119
<script>
123-
import { useModals } from 'svelte-modal-stack'
120+
import { openModal } from 'svelte-modal-stack'
124121
import Modal from './Modal.svelte'
125122
126-
const { openModal } = useModals()
127-
128123
function handleClick() {
129124
openModal(Modal, { title: "Alert", message: "This is an alert" })
130125
}
@@ -133,19 +128,8 @@ Open it
133128
<button on:click={handleClick}>Open Modal</button>
134129
```
135130

136-
<ModalStack let:openModal>
137-
<div
138-
slot="backdrop"
139-
let:closeModal
140-
class="backdrop"
141-
on:click={closeModal}
142-
/>
143-
<button
144-
class="mt-6"
145-
on:click={() => {
146-
openModal(AlertModal, { title: 'Alert', message: 'This is an alert' })
147-
}}
148-
>
149-
Try it out!
150-
</button>
151-
</ModalStack>
131+
<button
132+
class="mt-6"
133+
on:click={() => {
134+
openModal(AlertModal, { title: 'Alert', message: 'This is an alert' })
135+
}}>Try it out!</button>

‎src/routes/stacking-modals/_InfiniteModal.svelte

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<script>
2-
import { useModals } from 'svelte-modal-stack'
3-
4-
const { closeModal, stack } = useModals()
2+
import { closeModal, stack } from 'svelte-modal-stack'
53
64
export let isOpen
75
export let title

‎src/routes/stacking-modals/_Step1.svelte

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
<script>
2+
import { openModal } from 'svelte-modal-stack'
23
import BaseModal from './_BaseModal.svelte'
3-
import { useModals } from 'svelte-modal-stack'
44
import Step2 from './_Step2.svelte'
55
6-
const { closeModal, openModal } = useModals()
7-
86
export let isOpen
97
108
</script>

‎src/routes/stacking-modals/_Step2.svelte

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<script>
22
import BaseModal from './_BaseModal.svelte'
3-
import { useModals } from 'svelte-modal-stack'
4-
5-
const { closeAllModals, closeModal } = useModals()
3+
import { closeAllModals, closeModal } from 'svelte-modal-stack'
64
75
export let isOpen
86

‎src/routes/stacking-modals/index.md

+7-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
<script>
2-
import { ModalStack, useModals } from 'svelte-modal-stack'
2+
import { ModalStack, openModal } from 'svelte-modal-stack'
33
import Step1 from './_Step1.svelte'
4-
5-
const { openModal } = useModals()
64
</script>
75

86
# Stacking Modals
@@ -11,11 +9,12 @@ Modals are managed using a LIFO (last in first out) stack. `openModal()` will ad
119

1210
```svelte
1311
<script>
14-
import { useModals } from 'svelte-modal-stack'
15-
16-
const { openModal } = useModals()
12+
import { onMount } from 'svelte'
13+
import { openModal } from 'svelte-modal-stack'
1714
18-
openModal(YourModalComponent, props)
15+
onMount(() => {
16+
openModal(YourModalComponent, props)
17+
})
1918
</script>
2019
```
2120

@@ -38,11 +37,9 @@ Your modal components receive a boolean `isOpen` prop. It's up to you to impleme
3837

3938
```svelte
4039
<script>
41-
import { useModals } from 'svelte-modal-stack'
40+
import { openModal } from 'svelte-modal-stack'
4241
import Step2Modal from './Step2Modal.svelte'
4342
44-
const { openModal } = useModals()
45-
4643
export let isOpen
4744
4845
</script>

0 commit comments

Comments
 (0)
Please sign in to comment.