-
Notifications
You must be signed in to change notification settings - Fork 183
/
Offcanvas.svelte
134 lines (125 loc) · 3.41 KB
/
Offcanvas.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<script>
import { createEventDispatcher, onMount } from 'svelte';
import InlineContainer from './InlineContainer.svelte';
import OffcanvasBackdrop from './OffcanvasBackdrop.svelte';
import OffcanvasBody from './OffcanvasBody.svelte';
import OffcanvasHeader from './OffcanvasHeader.svelte';
import Portal from './Portal.svelte';
import classnames, { browserEvent, getTransitionDuration } from './utils';
const dispatch = createEventDispatcher();
let className = '';
export { className as class };
export let backdrop = true;
export let body = true;
export let container = 'body';
export let fade = true;
export let header = undefined;
export let isOpen = false;
export let placement = 'start';
export let scroll = false;
export let sm = false;
export let md = false;
export let lg = false;
export let xl = false;
export let xxl = false;
export let style = '';
export let toggle = undefined;
// TODO support these like Modals:
// export let autoFocus = true;
// export let unmountOnClose = true;
// TODO focus trap
let bodyElement;
let isTransitioning = false;
let element;
let removeEscListener;
onMount(() => (bodyElement = document.body));
$: if (bodyElement) {
if (!scroll) {
bodyElement.classList.toggle(
'overflow-noscroll',
isOpen || isTransitioning
);
}
}
$: if (element) {
isOpen = isOpen; // Used to trigger reactive on isOpen changes.
isTransitioning = true;
dispatch(isOpen ? 'opening' : 'closing');
setTimeout(() => {
isTransitioning = false;
dispatch(isOpen ? 'open' : 'close');
}, getTransitionDuration(element));
}
$: if (isOpen && toggle && typeof window !== 'undefined') {
removeEscListener = browserEvent(document, 'keydown', (event) => {
if (event.key && event.key === 'Escape') toggle();
});
}
$: if (!isOpen && removeEscListener) {
removeEscListener();
}
$: handleMouseDown =
backdrop && toggle && bodyElement && isOpen
? (e) => {
if (e.target === bodyElement) {
toggle();
}
}
: undefined;
$: classes = classnames(
{
offcanvas: !sm && !md && !lg && !xl && !xxl,
'offcanvas-sm': sm,
'offcanvas-md': md,
'offcanvas-lg': lg,
'offcanvas-xl': xl,
'offcanvas-xxl': xxl,
show: isOpen
},
`offcanvas-${placement}`,
className
);
$: outer = container === 'inline' ? InlineContainer : Portal;
</script>
<svelte:body on:mousedown={handleMouseDown} />
<svelte:component this={outer}>
<div
{...$$restProps}
bind:this={element}
aria-hidden={!isOpen ? true : undefined}
aria-modal={isOpen ? true : undefined}
class={classes}
role={isOpen || isTransitioning ? 'dialog' : undefined}
style={`visibility: ${
isOpen || isTransitioning ? 'visible' : 'hidden'
};${style}`}
tabindex="-1"
>
{#if toggle || header || $$slots.header}
<OffcanvasHeader {toggle}>
{#if header}{header}{/if}
<slot name="header" />
</OffcanvasHeader>
{/if}
{#if body}
<OffcanvasBody>
<slot />
</OffcanvasBody>
{:else}
<slot />
{/if}
</div>
{#if backdrop}
<OffcanvasBackdrop
on:click={toggle ? () => toggle() : undefined}
{fade}
{isOpen}
/>
{/if}
</svelte:component>
<style>
:global(.overflow-noscroll) {
overflow: hidden;
padding-right: 0px;
}
</style>