flowbite-svelte 0.26.31 → 0.27.2
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.
- package/CHANGELOG.md +33 -0
- package/modals/Modal.svelte +81 -94
- package/modals/Modal.svelte.d.ts +5 -3
- package/package.json +1 -1
- package/types.d.ts +6 -0
- package/utils/focusTrap.d.ts +1 -2
- package/utils/focusTrap.js +37 -50
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.27.2](https://github.com/themesberg/flowbite-svelte/compare/v0.27.1...v0.27.2) (2022-09-26)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* permanent option ([cceeb1b](https://github.com/themesberg/flowbite-svelte/commit/cceeb1b21995e4b386e61fa90256c7b3a304326e))
|
|
11
|
+
|
|
12
|
+
### [0.27.1](https://github.com/themesberg/flowbite-svelte/compare/v0.27.0...v0.27.1) (2022-09-24)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* modal block background scroll ([f3eb627](https://github.com/themesberg/flowbite-svelte/commit/f3eb62732dc899d6f1483d9f86a5c644c8d2edd2))
|
|
18
|
+
* modal internal scrolling ([7ca08ea](https://github.com/themesberg/flowbite-svelte/commit/7ca08ea939e0116241c564738ec237bd501ca32c))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* placement and padding ([169c749](https://github.com/themesberg/flowbite-svelte/commit/169c749004d16d9b7c1aef46448608beefd3caf7))
|
|
24
|
+
|
|
25
|
+
## [0.27.0](https://github.com/themesberg/flowbite-svelte/compare/v0.26.31...v0.27.0) (2022-09-24)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
* add server hooks to redirect component pages and add redirect.spec test ([9e2cb72](https://github.com/themesberg/flowbite-svelte/commit/9e2cb72653cb2de16f77c30d73bd69a69bd73bd5))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* add other dir ([dc5e73f](https://github.com/themesberg/flowbite-svelte/commit/dc5e73feaa44c3fc7f020967f2e342b08bafccdf))
|
|
36
|
+
* add redirect to all other directories ([6ca9ce0](https://github.com/themesberg/flowbite-svelte/commit/6ca9ce0c38c2a417fc1bcd88520e609a0e1b2b0e))
|
|
37
|
+
|
|
5
38
|
### [0.26.31](https://github.com/themesberg/flowbite-svelte/compare/v0.26.30...v0.26.31) (2022-09-23)
|
|
6
39
|
|
|
7
40
|
|
package/modals/Modal.svelte
CHANGED
|
@@ -1,61 +1,20 @@
|
|
|
1
|
-
<script>import
|
|
1
|
+
<script>import classNames from 'classnames';
|
|
2
|
+
import Frame from '../utils/Frame.svelte';
|
|
2
3
|
import { createEventDispatcher } from 'svelte';
|
|
3
4
|
import CloseButton from '../utils/CloseButton.svelte';
|
|
4
5
|
import focusTrap from '../utils/focusTrap';
|
|
5
6
|
export let open = false;
|
|
6
|
-
export let title =
|
|
7
|
+
export let title = '';
|
|
7
8
|
export let size = 'md';
|
|
8
9
|
export let placement = 'center';
|
|
9
10
|
export let autoclose = true;
|
|
10
|
-
export let
|
|
11
|
+
export let permanent = false;
|
|
12
|
+
export let backdropClasses = 'bg-gray-900 bg-opacity-50 dark:bg-opacity-80';
|
|
11
13
|
const dispatch = createEventDispatcher();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'items-start',
|
|
17
|
-
'items-center',
|
|
18
|
-
'items-end'
|
|
19
|
-
];
|
|
20
|
-
let backdropEl;
|
|
21
|
-
const init = (node, _open) => {
|
|
22
|
-
getPlacementClasses().map((c) => node.classList.add(c));
|
|
23
|
-
_open && createBackdrop(node);
|
|
24
|
-
return {
|
|
25
|
-
update(_open) {
|
|
26
|
-
allPlacementClasses.map((c) => node.classList.remove(c));
|
|
27
|
-
getPlacementClasses().map((c) => node.classList.add(c));
|
|
28
|
-
_open ? createBackdrop(node) : destroyBackdrop(node);
|
|
29
|
-
},
|
|
30
|
-
destroy() {
|
|
31
|
-
destroyBackdrop(node);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
const handleEscape = (e) => {
|
|
36
|
-
if (open && e.key === 'Escape')
|
|
37
|
-
open = false;
|
|
38
|
-
};
|
|
39
|
-
const createBackdrop = (node) => {
|
|
40
|
-
if (!backdropEl) {
|
|
41
|
-
backdropEl = document.createElement('div');
|
|
42
|
-
backdropEl.classList.add(...backdropClasses.split(' '));
|
|
43
|
-
const body = document.querySelector('body');
|
|
44
|
-
body.append(backdropEl);
|
|
45
|
-
body.style.overflow = 'hidden';
|
|
46
|
-
document.addEventListener('keydown', handleEscape, true);
|
|
47
|
-
}
|
|
48
|
-
dispatch('show', node);
|
|
49
|
-
};
|
|
50
|
-
const destroyBackdrop = (node) => {
|
|
51
|
-
const body = document.querySelector('body');
|
|
52
|
-
body.style.overflow = 'auto';
|
|
53
|
-
if (backdropEl)
|
|
54
|
-
backdropEl.remove();
|
|
55
|
-
backdropEl = undefined;
|
|
56
|
-
document.removeEventListener('keydown', handleEscape, true);
|
|
57
|
-
dispatch('hide', node);
|
|
58
|
-
};
|
|
14
|
+
$: dispatch(open ? 'open' : 'hide');
|
|
15
|
+
function grabFocus(node) {
|
|
16
|
+
node.focus();
|
|
17
|
+
}
|
|
59
18
|
const getPlacementClasses = () => {
|
|
60
19
|
switch (placement) {
|
|
61
20
|
// top
|
|
@@ -90,53 +49,81 @@ const sizes = {
|
|
|
90
49
|
lg: 'max-w-4xl',
|
|
91
50
|
xl: 'max-w-7xl'
|
|
92
51
|
};
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
52
|
+
const onAutoClose = (e) => {
|
|
53
|
+
const target = e.target;
|
|
54
|
+
if (autoclose && target?.tagName === 'BUTTON')
|
|
55
|
+
open = false;
|
|
96
56
|
};
|
|
97
57
|
const hide = () => {
|
|
98
58
|
open = false;
|
|
99
59
|
};
|
|
60
|
+
let mainClass;
|
|
61
|
+
$: mainClass = classNames('flex overflow-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 h-modal md:h-full', backdropClasses, ...getPlacementClasses(), $$props.class);
|
|
62
|
+
const isScrollable = (e) => [
|
|
63
|
+
e.scrollWidth > e.clientWidth && ['scroll', 'auto'].indexOf(getComputedStyle(e).overflowX) >= 0,
|
|
64
|
+
e.scrollHeight > e.clientHeight && ['scroll', 'auto'].indexOf(getComputedStyle(e).overflowY) >= 0
|
|
65
|
+
];
|
|
66
|
+
const isArrowKey = (e, x, y) => (x && ['ArrowLeft', 'ArrowRight'].includes(e.key)) || (y && ['ArrowDown', 'ArrowUp'].includes(e.key));
|
|
67
|
+
function preventWheelDefault(e) {
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
const [x, y] = isScrollable(this);
|
|
70
|
+
return x || y || e.preventDefault();
|
|
71
|
+
}
|
|
72
|
+
function stopArrowsPropagation(e) {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
const [x, y] = isScrollable(this);
|
|
75
|
+
if (isArrowKey(e, x, y))
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
}
|
|
78
|
+
function handleKeys(e) {
|
|
79
|
+
if (e.key === 'Escape' && !permanent)
|
|
80
|
+
return hide();
|
|
81
|
+
return isArrowKey(e, true, true) ? e.preventDefault() : true;
|
|
82
|
+
}
|
|
100
83
|
</script>
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
<div
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
85
|
+
{#if open}
|
|
86
|
+
<div
|
|
87
|
+
tabindex="-1"
|
|
88
|
+
class={mainClass}
|
|
89
|
+
aria-modal="true"
|
|
90
|
+
role="dialog"
|
|
91
|
+
on:keydown={handleKeys}
|
|
92
|
+
on:wheel|preventDefault
|
|
93
|
+
use:grabFocus
|
|
94
|
+
use:focusTrap
|
|
95
|
+
on:click={autoclose ? onAutoClose : null}>
|
|
96
|
+
<div class="flex p-4 w-full {sizes[size]} h-full md:h-auto max-h-screen">
|
|
97
|
+
<!-- Modal content -->
|
|
98
|
+
<Frame rounded shadow class="relative flex flex-col w-full h-full md:h-auto">
|
|
99
|
+
<!-- Modal header -->
|
|
100
|
+
{#if $$slots.header || title}
|
|
101
|
+
<div class="flex justify-between items-center p-4 rounded-t border-b dark:border-gray-600">
|
|
102
|
+
<slot name="header">
|
|
103
|
+
<h3 class="text-xl font-semibold text-gray-900 dark:text-white p-0">
|
|
104
|
+
{title}
|
|
105
|
+
</h3>
|
|
106
|
+
</slot>
|
|
107
|
+
{#if !permanent}<CloseButton name="Close modal" on:click={hide} />{/if}
|
|
108
|
+
</div>
|
|
109
|
+
{:else if !permanent}
|
|
110
|
+
<CloseButton name="Close modal" class="absolute top-3 right-2.5" on:click={hide} />
|
|
111
|
+
{/if}
|
|
112
|
+
<!-- Modal body -->
|
|
113
|
+
<div
|
|
114
|
+
class="p-6 space-y-6 flex-1 overflow-y-auto overscroll-contain"
|
|
115
|
+
on:keydown={stopArrowsPropagation}
|
|
116
|
+
on:wheel|stopPropagation={preventWheelDefault}>
|
|
117
|
+
<slot />
|
|
118
|
+
</div>
|
|
119
|
+
<!-- Modal footer -->
|
|
120
|
+
{#if $$slots.footer}
|
|
121
|
+
<div
|
|
122
|
+
class="flex items-center p-6 space-x-2 rounded-b border-t border-gray-200 dark:border-gray-600">
|
|
123
|
+
<slot name="footer" />
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
126
|
+
</Frame>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
{/if}
|
package/modals/Modal.svelte.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import type { Size } from '../types';
|
|
2
3
|
declare const __propDef: {
|
|
3
4
|
props: {
|
|
5
|
+
[x: string]: any;
|
|
4
6
|
open?: boolean | undefined;
|
|
5
7
|
title?: string | undefined;
|
|
6
|
-
size?:
|
|
8
|
+
size?: Size | undefined;
|
|
7
9
|
placement?: "top-left" | "top-center" | "top-right" | "center-left" | "center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right" | undefined;
|
|
8
10
|
autoclose?: boolean | undefined;
|
|
11
|
+
permanent?: boolean | undefined;
|
|
9
12
|
backdropClasses?: string | undefined;
|
|
10
13
|
};
|
|
11
14
|
events: {
|
|
12
|
-
|
|
13
|
-
hide: CustomEvent<any>;
|
|
15
|
+
wheel: WheelEvent;
|
|
14
16
|
} & {
|
|
15
17
|
[evt: string]: CustomEvent<any>;
|
|
16
18
|
};
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -147,6 +147,12 @@ export interface PageType {
|
|
|
147
147
|
pageNum: number;
|
|
148
148
|
href: string;
|
|
149
149
|
}
|
|
150
|
+
export declare const xs = "xs";
|
|
151
|
+
export declare const sm = "sm";
|
|
152
|
+
export declare const md = "md";
|
|
153
|
+
export declare const lg = "lg";
|
|
154
|
+
export declare const xl = "xl";
|
|
155
|
+
export declare type Size = typeof xs | typeof sm | typeof md | typeof lg | typeof xl;
|
|
150
156
|
export interface PillTabType {
|
|
151
157
|
name: string;
|
|
152
158
|
selected: boolean;
|
package/utils/focusTrap.d.ts
CHANGED
package/utils/focusTrap.js
CHANGED
|
@@ -4,58 +4,45 @@
|
|
|
4
4
|
|
|
5
5
|
// add all the elements inside modal which you want to make focusable
|
|
6
6
|
const focusableElements =
|
|
7
|
-
|
|
7
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
8
8
|
|
|
9
9
|
function getFocusable(node) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const focusableContent = node.querySelectorAll(focusableElements);
|
|
11
|
+
return [focusableContent[0], focusableContent[focusableContent.length - 1]];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export default function focusTrap(node
|
|
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
|
-
if (_open) {
|
|
49
|
-
[firstFocusableElement, lastFocusableElement] = getFocusable(node);
|
|
50
|
-
document.addEventListener('keydown', handleFocusTrap, true);
|
|
51
|
-
firstFocusableElement.focus();
|
|
52
|
-
} else {
|
|
53
|
-
document.removeEventListener('keydown', handleFocusTrap, true);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
destroy() {
|
|
58
|
-
document.removeEventListener('keydown', handleFocusTrap, true);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
14
|
+
export default function focusTrap(node) {
|
|
15
|
+
let firstFocusableElement, lastFocusableElement; // first and last element to be focused inside modal
|
|
16
|
+
|
|
17
|
+
function handleFocusTrap(e) {
|
|
18
|
+
let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
|
|
19
|
+
|
|
20
|
+
if (!isTabPressed) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (e.shiftKey) {
|
|
25
|
+
// if shift key pressed for shift + tab combination
|
|
26
|
+
if (document.activeElement === firstFocusableElement) {
|
|
27
|
+
lastFocusableElement.focus(); // add focus for the last focusable element
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
// if tab key is pressed
|
|
32
|
+
if (document.activeElement === lastFocusableElement) {
|
|
33
|
+
// if focused has reached to last focusable element then focus first focusable element after pressing tab
|
|
34
|
+
firstFocusableElement.focus(); // add focus for the first focusable element
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[firstFocusableElement, lastFocusableElement] = getFocusable(node);
|
|
41
|
+
document.addEventListener('keydown', handleFocusTrap, true);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
destroy() {
|
|
45
|
+
document.removeEventListener('keydown', handleFocusTrap, true);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
61
48
|
}
|