1
1
<script >
2
- import { createEventDispatcher } from ' svelte' ;
2
+ import { createEventDispatcher , onDestroy } from ' svelte' ;
3
3
4
4
const dispatch = createEventDispatcher ();
5
-
6
- const handleClose = () => dispatch ( ' close ' )
7
-
8
- function handleKeydown ( event ) {
9
- if ( event . key == ' Escape ' ) {
10
- handleClose ()
11
- } else if ( event . key == ' Tab ' ) {
12
- event . preventDefault ()
5
+ const close = () => dispatch ( ' close ' );
6
+
7
+ let modal;
8
+
9
+ const handle_keydown = e => {
10
+ if ( e . key === ' Escape ' ) {
11
+ close ();
12
+ return ;
13
13
}
14
- }
15
14
16
- let closeButton
17
- onMount (() => {
18
- closeButton .focus ()
19
- })
15
+ if (e .key === ' Tab' ) {
16
+ // trap focus
17
+ const nodes = modal .querySelectorAll (' *' );
18
+ const active = document .activeElement ;
19
+
20
+ const tabbable = Array .from (nodes).filter (n => n .tabIndex >= 0 );
21
+ const first = tabbable[0 ];
22
+ const last = tabbable[tabbable .length - 1 ];
20
23
24
+ let index = tabbable .indexOf (document .activeElement );
25
+ if (index === - 1 && e .shiftKey ) index = 0 ;
26
+
27
+ index += tabbable .length + (e .shiftKey ? - 1 : 1 );
28
+ index %= tabbable .length ;
29
+
30
+ tabbable[index].focus ();
31
+ e .preventDefault ();
32
+ }
33
+ };
34
+
35
+ const previously_focused = typeof document !== ' undefined' && document .activeElement ;
36
+
37
+ if (previously_focused) {
38
+ onDestroy (() => {
39
+ previously_focused .focus ();
40
+ });
41
+ }
21
42
</script >
22
43
44
+ <svelte:window on:keydown ={handle_keydown }/>
45
+
46
+ <div class ="modal-background" on:click ={close }></div >
47
+
48
+ <div class ="modal" role ="dialog" aria-modal ="true" bind:this ={modal }>
49
+ <slot name =" header" ></slot >
50
+ <hr >
51
+ <slot ></slot >
52
+ <hr >
53
+
54
+ <!-- svelte-ignore a11y-autofocus -->
55
+ <button autofocus on:click ={close }>close modal</button >
56
+
57
+ <a href =" argh" >argh</a >
58
+ </div >
59
+
23
60
<style >
24
61
.modal-background {
25
62
position : fixed ;
32
69
33
70
.modal {
34
71
position : absolute ;
35
- left : 50% ; top : 50% ;
72
+ left : 50% ;
73
+ top : 50% ;
36
74
width : calc (100vw - 4em );
37
75
max-width : 32em ;
38
76
max-height : calc (100vh - 4em );
46
84
button {
47
85
display : block ;
48
86
}
49
- </style >
50
-
51
- <div class ='modal-background' on :click =' {handleClose }' ></div >
52
-
53
- <div class =' modal' >
54
- <slot name =' header' ></slot >
55
- <hr >
56
- <slot ></slot >
57
- <hr >
58
-
59
- <button on :click =' {() => dispatch (" close" )}' bind:this ={closeButton }>close modal</button >
60
- </div >
87
+ </style >
0 commit comments