@txstate-mws/svelte-components 1.6.12 → 1.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,115 +12,109 @@ When they do, the escape event will be fired, allowing you to remove the dialog.
12
12
  `FocusLock`s can be nested. The user will be trapped inside the deepest `FocusLock` present in the DOM.
13
13
  When it goes away, they'll be trapped inside the previous `FocusLock`, and so on.
14
14
  -->
15
- <script context="module" lang="ts">export const FocusLockStack = [];
16
- const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve) => requestAnimationFrame(resolve) : (resolve) => resolve(0);
15
+ <script context="module" lang="ts">
16
+ export const FocusLockStack: { focusId: string, pause: () => void, unpause: () => void, update: () => void }[] = []
17
+ const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve: (value: unknown) => void) => requestAnimationFrame(resolve) : (resolve: (value: unknown) => void) => resolve(0)
17
18
  </script>
18
19
 
19
- <script lang="ts">import { onMount, onDestroy, createEventDispatcher } from 'svelte';
20
- import { tabbable } from 'tabbable';
21
- import { randomid, sleep } from 'txstate-utils';
22
- import { buttonify } from '../actions';
23
- import ScreenReaderOnly from './ScreenReaderOnly.svelte';
24
- export let escapable = true;
25
- export let hidefocus = true;
26
- export let hidefocuslabel = 'focus is above modal dialog, start tabbing';
27
- export let initialfocus = undefined;
28
- export let returnfocusto = undefined;
29
- /** If you expect any popup menus to be added to the body, we need to know that they
30
- are considered to be part of the focus lock, or else the modal will be dismissed
31
- when the user clicks inside. Use commas to include multiple selectors. */
32
- export let includeselector = undefined;
33
- let className = '';
34
- export { className as class };
35
- export let focusId = randomid();
36
- const dispatch = createEventDispatcher();
37
- let lockelement;
38
- let abovelockelement;
39
- let state = 'init';
40
- if (initialfocus)
41
- hidefocus = false;
42
- onMount(async () => {
43
- const prevFocusLock = FocusLockStack.slice(-1)[0];
44
- if (prevFocusLock)
45
- prevFocusLock.pause();
20
+ <script lang="ts">
21
+ import { onMount, onDestroy, createEventDispatcher } from 'svelte'
22
+ import { tabbable } from 'tabbable'
23
+ import { randomid, sleep } from 'txstate-utils'
24
+ import { buttonify } from '../actions'
25
+ import ScreenReaderOnly from './ScreenReaderOnly.svelte'
26
+
27
+ export let escapable = true
28
+ export let hidefocus = true
29
+ export let hidefocuslabel = 'focus is above modal dialog, start tabbing'
30
+ export let initialfocus: string | undefined = undefined
31
+ export let returnfocusto: HTMLElement | undefined = undefined
32
+ /** If you expect any popup menus to be added to the body, we need to know that they
33
+ are considered to be part of the focus lock, or else the modal will be dismissed
34
+ when the user clicks inside. Use commas to include multiple selectors. */
35
+ export let includeselector: string | undefined = undefined
36
+ let className = ''
37
+ export { className as class }
38
+ export let focusId = randomid()
39
+
40
+ const dispatch = createEventDispatcher()
41
+ let lockelement: HTMLElement
42
+ let abovelockelement: HTMLElement
43
+ let state: 'init' | 'active' | 'paused' | 'destroyed' = 'init'
44
+ if (initialfocus) hidefocus = false
45
+ onMount(async () => {
46
+ const prevFocusLock = FocusLockStack.slice(-1)[0]
47
+ if (prevFocusLock) prevFocusLock.pause()
46
48
  FocusLockStack.push({
47
- focusId,
48
- pause: () => { state = 'paused'; },
49
- unpause: () => { if (state === 'paused')
50
- state = 'active'; },
51
- update: () => { dispatch('focuslockupdate'); }
52
- });
53
- for (const entry of FocusLockStack)
54
- entry.update();
49
+ focusId,
50
+ pause: () => { state = 'paused' },
51
+ unpause: () => { if (state === 'paused') state = 'active' },
52
+ update: () => { dispatch('focuslockupdate') }
53
+ })
54
+ for (const entry of FocusLockStack) entry.update()
55
55
  if (typeof returnfocusto === 'undefined') {
56
- returnfocusto = document.activeElement;
56
+ returnfocusto = document.activeElement as HTMLElement | undefined
57
57
  }
58
- await sleep(1); // sapper resets focus on page load, so we'll wait a millisecond in case our modal is on screen at load
59
- if (!lockelement)
60
- return; // in case we already unmounted
58
+ await sleep(1) // sapper resets focus on page load, so we'll wait a millisecond in case our modal is on screen at load
59
+ if (!lockelement) return // in case we already unmounted
61
60
  if (initialfocus) {
62
- const focusEl = lockelement.querySelector(initialfocus);
63
- if (focusEl instanceof HTMLElement)
64
- focusEl.focus();
65
- }
66
- else {
67
- setInitialFocus();
61
+ const focusEl = lockelement.querySelector(initialfocus)
62
+ if (focusEl instanceof HTMLElement) focusEl.focus()
63
+ } else {
64
+ setInitialFocus()
68
65
  }
69
- setTimeout(() => { state = 'active'; }, 0);
70
- });
71
- onDestroy(async () => {
72
- const wasactive = state !== 'paused';
73
- state = 'destroyed';
66
+ setTimeout(() => { state = 'active' }, 0)
67
+ })
68
+ onDestroy(async () => {
69
+ const wasactive = state !== 'paused'
70
+ state = 'destroyed'
71
+
74
72
  // we need to allow the screen to redraw so that we can
75
73
  // change focus back to where it was, and we need
76
74
  // to allow the click event that destroyed us to finish bubbling
77
75
  // before we unpause the next focuslock in the stack or else it
78
76
  // will think it was an "outside" click and kill itself
79
- await new Promise(waitAtick);
77
+ await new Promise(waitAtick)
78
+
80
79
  if (returnfocusto && wasactive) {
81
- returnfocusto.focus();
80
+ returnfocusto.focus()
82
81
  }
83
- const idx = FocusLockStack.findIndex(f => f.focusId === focusId);
84
- if (idx > -1)
85
- FocusLockStack.splice(idx, 1);
86
- const prevFocusLock = FocusLockStack.slice(-1)[0];
87
- if (prevFocusLock)
88
- prevFocusLock.unpause();
89
- for (const entry of FocusLockStack)
90
- entry.update();
91
- });
92
- const setInitialFocus = () => {
93
- const firstfocus = lockelement ? tabbable(lockelement)[0] : undefined;
82
+ const idx = FocusLockStack.findIndex(f => f.focusId === focusId)
83
+ if (idx > -1) FocusLockStack.splice(idx, 1)
84
+ const prevFocusLock = FocusLockStack.slice(-1)[0]
85
+ if (prevFocusLock) prevFocusLock.unpause()
86
+ for (const entry of FocusLockStack) entry.update()
87
+ })
88
+ const setInitialFocus = () => {
89
+ const firstfocus = lockelement ? tabbable(lockelement)[0] : undefined
94
90
  if (firstfocus?.focus) {
95
- firstfocus.focus();
91
+ firstfocus.focus()
96
92
  }
97
- };
98
- const setLastFocus = () => {
99
- const lastfocus = lockelement ? tabbable(lockelement).slice(-1)[0] : undefined;
93
+ }
94
+ const setLastFocus = () => {
95
+ const lastfocus = lockelement ? tabbable(lockelement).slice(-1)[0] : undefined
100
96
  if (lastfocus?.focus) {
101
- lastfocus.focus();
97
+ lastfocus.focus()
102
98
  }
103
- };
104
- const keydown = (e) => {
99
+ }
100
+ const keydown = (e: KeyboardEvent) => {
105
101
  if (state === 'active' && e.key === 'Escape' && escapable) {
106
- e.preventDefault();
107
- dispatch('escape');
102
+ e.preventDefault()
103
+ dispatch('escape')
108
104
  }
109
- };
110
- const focusin = (e) => {
105
+ }
106
+ const focusin = (e: FocusEvent) => {
111
107
  if (state === 'active' && e.target instanceof HTMLElement && !lockelement?.contains(e.target)) {
112
- if (e.target === abovelockelement)
113
- setLastFocus();
114
- else
115
- setInitialFocus();
108
+ if (e.target === abovelockelement) setLastFocus()
109
+ else setInitialFocus()
116
110
  }
117
- };
118
- const windowclick = (e) => {
111
+ }
112
+ const windowclick = (e: MouseEvent) => {
119
113
  if (state === 'active' && e.target instanceof HTMLElement && e.target.isConnected && (!includeselector || !e.target.closest(includeselector))) {
120
- returnfocusto = undefined;
121
- dispatch('escape');
114
+ returnfocusto = undefined
115
+ dispatch('escape')
122
116
  }
123
- };
117
+ }
124
118
  </script>
125
119
  <svelte:window on:mousedown={windowclick} />
126
120
  <!-- svelte-ignore a11y_interactive_supports_focus -->
@@ -3,21 +3,22 @@
3
3
  until the slotted content is signals it is done loading via the `loading` boolean bind at which point the slotted content
4
4
  will be rendered.
5
5
  -->
6
- <script lang="ts">import { afterUpdate } from 'svelte';
7
- import ScreenReaderOnly from './ScreenReaderOnly.svelte';
8
- import { resize, ResizeStore } from '../actions';
9
- export let loading;
10
- export let height = undefined;
11
- const store = new ResizeStore();
12
- let containerelement;
13
- let parentScrolls;
14
- afterUpdate(() => {
15
- if (height || !containerelement)
16
- return;
17
- const parent = containerelement.offsetParent;
18
- const overflowY = window.getComputedStyle(parent).overflowY;
19
- parentScrolls = overflowY === 'scroll' || overflowY === 'auto';
20
- });
6
+ <script lang="ts">
7
+ import { afterUpdate } from 'svelte'
8
+ import ScreenReaderOnly from './ScreenReaderOnly.svelte'
9
+ import { resize, ResizeStore } from '../actions'
10
+ export let loading: boolean | undefined
11
+ export let height: string | undefined = undefined
12
+ const store = new ResizeStore()
13
+
14
+ let containerelement: HTMLElement
15
+ let parentScrolls: boolean
16
+ afterUpdate(() => {
17
+ if (height || !containerelement) return
18
+ const parent = containerelement.offsetParent!
19
+ const overflowY = window.getComputedStyle(parent).overflowY
20
+ parentScrolls = overflowY === 'scroll' || overflowY === 'auto'
21
+ })
21
22
  </script>
22
23
 
23
24
  {#if loading}
@@ -2,39 +2,45 @@
2
2
  The purpose of `Lottie` is to provide a [Lottie](https://www.npmjs.com/package/lottie-web) player element
3
3
  with Screen Reader compatible attributes and lables readily available through the `alt` prop.
4
4
  -->
5
- <script lang="ts">import { onMount } from 'svelte';
6
- import { isBlank } from 'txstate-utils';
7
- import ScreenReaderOnly from './ScreenReaderOnly.svelte';
8
- export let animationData;
9
- export let loop = true;
10
- export let name = undefined;
11
- export let speed = 1;
12
- export let direction = 1;
13
- export let paused = false;
14
- export let width = undefined;
15
- export let height = undefined;
16
- /** `<ScreenReaderOnly>` lable to be applied. */
17
- export let alt = '';
18
- let container;
19
- let animation;
20
- $: animation?.setSpeed(speed);
21
- $: animation?.setDirection(direction);
22
- $: paused ? animation?.pause() : animation?.play();
23
- onMount(async () => {
24
- const lottie = (await import('lottie-web/build/player/lottie_light')).default;
5
+ <script lang="ts">
6
+ import type { AnimationItem } from 'lottie-web/build/player/lottie_light'
7
+ import { onMount } from 'svelte'
8
+ import { isBlank } from 'txstate-utils'
9
+ import ScreenReaderOnly from './ScreenReaderOnly.svelte'
10
+
11
+ export let animationData: any
12
+ export let loop = true
13
+ export let name: string | undefined = undefined
14
+ export let speed = 1
15
+ export let direction: 1 | -1 = 1
16
+ export let paused = false
17
+ export let width: string | undefined = undefined
18
+ export let height: string | undefined = undefined
19
+ /** `<ScreenReaderOnly>` lable to be applied. */
20
+ export let alt = ''
21
+
22
+ let container: HTMLElement
23
+ let animation: AnimationItem | undefined
24
+
25
+ $: animation?.setSpeed(speed)
26
+ $: animation?.setDirection(direction)
27
+ $: paused ? animation?.pause() : animation?.play()
28
+
29
+ onMount(async () => {
30
+ const lottie = (await import('lottie-web/build/player/lottie_light')).default
25
31
  animation = lottie.loadAnimation({
26
- container,
27
- animationData,
28
- renderer: 'svg',
29
- loop,
30
- autoplay: !paused,
31
- name
32
- });
32
+ container,
33
+ animationData,
34
+ renderer: 'svg',
35
+ loop,
36
+ autoplay: !paused,
37
+ name
38
+ })
33
39
  animation.addEventListener('DOMLoaded', () => {
34
- animation.setSpeed(speed);
35
- animation.setDirection(direction);
36
- });
37
- });
40
+ animation!.setSpeed(speed)
41
+ animation!.setDirection(direction)
42
+ })
43
+ })
38
44
  </script>
39
45
 
40
46
  <div bind:this={container} style:width style:height aria-hidden={isBlank(alt)}><ScreenReaderOnly>{alt}</ScreenReaderOnly></div>
@@ -5,40 +5,43 @@ It provides only the backdrop and a scrollable container for your content. If yo
5
5
 
6
6
  Any time the Modal is in the DOM, it will take over the screen. You make it go away by removing it from the DOM.
7
7
  -->
8
- <script lang="ts">import { createEventDispatcher, onMount } from 'svelte';
9
- import { randomid } from 'txstate-utils';
10
- import FocusLock, { FocusLockStack } from './FocusLock.svelte';
11
- import { portal } from '../actions';
12
- export let opaque = false;
13
- export let containerClass = '';
14
- export let escapable = true;
15
- export let hidefocus = true;
16
- export let hidefocuslabel = undefined;
17
- export let initialfocus = undefined;
18
- export let returnfocusto = undefined;
19
- export let usePortal = undefined;
20
- /** If you expect any popup menus to be added to the body, we need to know that they
21
- are considered to be part of the focus lock, or else the modal will be dismissed
22
- when the user clicks inside. Use commas to include multiple selectors. */
23
- export let includeselector = undefined;
24
- export let focusId = randomid();
25
- const dispatch = createEventDispatcher();
26
- const endmodal = () => {
27
- dispatch('escape');
28
- };
29
- let stackPosition;
30
- function onFocusLockUpdate() {
31
- stackPosition = FocusLockStack.findIndex(f => f.focusId === focusId);
32
- }
33
- onMount(() => {
34
- onFocusLockUpdate();
35
- document.body.style.marginRight = (window.innerWidth - document.body.clientWidth) + 'px';
36
- document.body.style.overflow = 'hidden';
8
+ <script lang="ts">
9
+ import { createEventDispatcher, onMount } from 'svelte'
10
+ import { randomid } from 'txstate-utils'
11
+ import FocusLock, { FocusLockStack } from './FocusLock.svelte'
12
+ import { portal } from '../actions'
13
+
14
+ export let opaque = false
15
+ export let containerClass = ''
16
+ export let escapable = true
17
+ export let hidefocus = true
18
+ export let hidefocuslabel: string | undefined = undefined
19
+ export let initialfocus: string | undefined = undefined
20
+ export let returnfocusto: HTMLElement | undefined = undefined
21
+ export let usePortal: HTMLElement | undefined = undefined
22
+ /** If you expect any popup menus to be added to the body, we need to know that they
23
+ are considered to be part of the focus lock, or else the modal will be dismissed
24
+ when the user clicks inside. Use commas to include multiple selectors. */
25
+ export let includeselector: string | undefined = undefined
26
+ export let focusId = randomid()
27
+
28
+ const dispatch = createEventDispatcher()
29
+ const endmodal = () => {
30
+ dispatch('escape')
31
+ }
32
+ let stackPosition: number
33
+ function onFocusLockUpdate () {
34
+ stackPosition = FocusLockStack.findIndex(f => f.focusId === focusId)
35
+ }
36
+ onMount(() => {
37
+ onFocusLockUpdate()
38
+ document.body.style.marginRight = (window.innerWidth - document.body.clientWidth) + 'px'
39
+ document.body.style.overflow = 'hidden'
37
40
  return () => {
38
- document.body.style.overflow = '';
39
- document.body.style.marginRight = '';
40
- };
41
- });
41
+ document.body.style.overflow = ''
42
+ document.body.style.marginRight = ''
43
+ }
44
+ })
42
45
  </script>
43
46
 
44
47
  <!-- svelte-ignore a11y-no-static-element-interactions -->