@marianmeres/stuic 1.11.0 → 1.13.0
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/dist/actions/focus-trap.d.ts +7 -2
- package/dist/actions/focus-trap.js +57 -29
- package/dist/components/Backdrop/Backdrop.svelte +1 -1
- package/dist/components/Button/Button.svelte +13 -11
- package/dist/components/X/X.svelte +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/package.json +2 -2
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
interface FocusTrapOptions {
|
|
2
|
+
enabled?: boolean;
|
|
3
|
+
autoFocusFirst?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function focusTrap(node: HTMLElement, options?: FocusTrapOptions): {
|
|
6
|
+
update(options?: FocusTrapOptions): void;
|
|
3
7
|
destroy(): void;
|
|
4
8
|
};
|
|
9
|
+
export {};
|
|
@@ -1,52 +1,81 @@
|
|
|
1
1
|
// copied from skeleton
|
|
2
|
+
const defaults = { enabled: true, autoFocusFirst: true };
|
|
2
3
|
// Action: Focus Trap
|
|
3
|
-
export function focusTrap(node,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export function focusTrap(node, options = {}) {
|
|
5
|
+
let { enabled, autoFocusFirst } = { ...defaults, ...(options || {}) };
|
|
6
|
+
const focusableSelectors = [
|
|
7
|
+
'[contentEditable=true]:not([tabindex^="-"])',
|
|
8
|
+
//
|
|
9
|
+
'button:not([disabled]):not([tabindex^="-"])',
|
|
10
|
+
'input:not([disabled]):not([tabindex^="-"])',
|
|
11
|
+
'select:not([disabled]):not([tabindex^="-"])',
|
|
12
|
+
'textarea:not([disabled]):not([tabindex^="-"])',
|
|
13
|
+
//
|
|
14
|
+
'a[href]:not([tabindex^="-"])',
|
|
15
|
+
'area[href]:not([tabindex^="-"])',
|
|
16
|
+
'details:not([tabindex^="-"])',
|
|
17
|
+
'iframe:not([tabindex^="-"])',
|
|
18
|
+
//
|
|
19
|
+
'[tabindex]:not([tabindex^="-"])',
|
|
20
|
+
].join(',');
|
|
21
|
+
let first;
|
|
22
|
+
let last;
|
|
7
23
|
// When the first element is selected, shift+tab pressed, jump to the last selectable item.
|
|
8
24
|
function onFirstElemKeydown(e) {
|
|
9
25
|
if (e.shiftKey && e.code === 'Tab') {
|
|
10
26
|
e.preventDefault();
|
|
11
|
-
|
|
27
|
+
last.focus();
|
|
12
28
|
}
|
|
13
29
|
}
|
|
14
30
|
// When the last item selected, tab pressed, jump to the first selectable item.
|
|
15
31
|
function onLastElemKeydown(e) {
|
|
16
32
|
if (!e.shiftKey && e.code === 'Tab') {
|
|
17
33
|
e.preventDefault();
|
|
18
|
-
|
|
34
|
+
first.focus();
|
|
19
35
|
}
|
|
20
36
|
}
|
|
21
|
-
const
|
|
37
|
+
const queryElements = (fromObserver) => {
|
|
22
38
|
if (enabled === false)
|
|
23
39
|
return;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
const focusable = [...node.querySelectorAll(focusableSelectors)]
|
|
41
|
+
// in case I didn't get the selectors right, make sure to manually check as well...
|
|
42
|
+
// (negligible overhead, if any...)
|
|
43
|
+
.filter((e) => {
|
|
44
|
+
if (e.getAttribute('disabled') === '')
|
|
45
|
+
return false;
|
|
46
|
+
if ((e.getAttribute('tabindex') || '').startsWith('-'))
|
|
47
|
+
return false;
|
|
48
|
+
return true;
|
|
49
|
+
})
|
|
50
|
+
// important to sort by tabindex, so the first/last will work as expected
|
|
51
|
+
.sort((e1, e2) => {
|
|
52
|
+
const a = parseInt(e1.getAttribute('tabindex') || '0');
|
|
53
|
+
const b = parseInt(e2.getAttribute('tabindex') || '0');
|
|
54
|
+
return a - b;
|
|
55
|
+
});
|
|
56
|
+
if (focusable.length) {
|
|
57
|
+
first = focusable[0];
|
|
58
|
+
last = focusable[focusable.length - 1];
|
|
30
59
|
// Auto-focus first focusable element only when not called from observer
|
|
31
|
-
if (!fromObserver)
|
|
32
|
-
|
|
60
|
+
if (!fromObserver && autoFocusFirst)
|
|
61
|
+
first.focus();
|
|
33
62
|
// Listen for keydown on first & last element
|
|
34
|
-
|
|
35
|
-
|
|
63
|
+
first.addEventListener('keydown', onFirstElemKeydown);
|
|
64
|
+
last.addEventListener('keydown', onLastElemKeydown);
|
|
36
65
|
}
|
|
37
66
|
};
|
|
38
|
-
|
|
39
|
-
function
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
67
|
+
queryElements(false);
|
|
68
|
+
function cleanup() {
|
|
69
|
+
if (first)
|
|
70
|
+
first.removeEventListener('keydown', onFirstElemKeydown);
|
|
71
|
+
if (last)
|
|
72
|
+
last.removeEventListener('keydown', onLastElemKeydown);
|
|
44
73
|
}
|
|
45
74
|
// When children of node are changed (added or removed)
|
|
46
75
|
const onObservationChange = (mutationRecords, observer) => {
|
|
47
76
|
if (mutationRecords.length) {
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
cleanup();
|
|
78
|
+
queryElements(true);
|
|
50
79
|
}
|
|
51
80
|
return observer;
|
|
52
81
|
};
|
|
@@ -54,12 +83,11 @@ export function focusTrap(node, enabled) {
|
|
|
54
83
|
observer.observe(node, { childList: true, subtree: true });
|
|
55
84
|
// Lifecycle
|
|
56
85
|
return {
|
|
57
|
-
update(
|
|
58
|
-
enabled
|
|
59
|
-
newArgs ? onScanElements(false) : onCleanUp();
|
|
86
|
+
update(options = {}) {
|
|
87
|
+
options?.enabled ? queryElements(false) : cleanup();
|
|
60
88
|
},
|
|
61
89
|
destroy() {
|
|
62
|
-
|
|
90
|
+
cleanup();
|
|
63
91
|
observer.disconnect();
|
|
64
92
|
},
|
|
65
93
|
};
|
|
@@ -43,7 +43,7 @@ $:
|
|
|
43
43
|
on:keydown={(e) => e.code === 'Escape' && dispatch('escape')}
|
|
44
44
|
in:fade={{ duration: fadeInDuration }}
|
|
45
45
|
out:fade={{ duration: fadeOutDuration }}
|
|
46
|
-
use:focusTrap={useFocusTrap}
|
|
46
|
+
use:focusTrap={{ enabled: useFocusTrap }}
|
|
47
47
|
role="presentation"
|
|
48
48
|
tabindex="-1"
|
|
49
49
|
>
|
|
@@ -4,18 +4,20 @@ export class ButtonConfig {
|
|
|
4
4
|
static defaultShadow = false;
|
|
5
5
|
static defaultRounded = true;
|
|
6
6
|
static defaultVariant = void 0;
|
|
7
|
-
// hover:brightness-[1.15]
|
|
8
7
|
static presetBase = `
|
|
9
|
-
text-base
|
|
10
8
|
text-center whitespace-nowrap
|
|
11
|
-
inline-flex
|
|
12
|
-
|
|
13
|
-
hover:brightness-[1.1]
|
|
9
|
+
inline-flex items-center gap-x-2
|
|
10
|
+
hover:brightness-[1.05]
|
|
14
11
|
active:brightness-[0.95]
|
|
15
12
|
disabled:!cursor-not-allowed disabled:!opacity-50 disabled:hover:brightness-100
|
|
16
13
|
no-underline
|
|
14
|
+
border
|
|
15
|
+
|
|
16
|
+
bg-zinc-200 text-black
|
|
17
|
+
dark:bg-zinc-600 dark:text-white
|
|
18
|
+
border-zinc-400 dark:border-zinc-500
|
|
17
19
|
`.trim();
|
|
18
|
-
static presetSquare = "p-0 aspect-square";
|
|
20
|
+
static presetSquare = "p-0 aspect-square justify-center";
|
|
19
21
|
static presetsRounded = {
|
|
20
22
|
xs: "rounded",
|
|
21
23
|
sm: "rounded",
|
|
@@ -31,11 +33,11 @@ export class ButtonConfig {
|
|
|
31
33
|
xl: "shadow-md dark:shadow-black"
|
|
32
34
|
};
|
|
33
35
|
static presetsSize = {
|
|
34
|
-
xs: "px-2 py-0.5 text-xs",
|
|
35
|
-
sm: "px-
|
|
36
|
-
md: "px-3
|
|
37
|
-
lg: "px-4 py-1.5 text-lg",
|
|
38
|
-
xl: "px-4 py-2 text-xl"
|
|
36
|
+
xs: "px-2 py-0.5 leading-tight text-xs",
|
|
37
|
+
sm: "px-2.5 py-0.5 leading-snug text-sm",
|
|
38
|
+
md: "px-3 py-1 leading-normal text-base",
|
|
39
|
+
lg: "px-4 py-1.5 leading-relaxed text-lg",
|
|
40
|
+
xl: "px-4 py-2 leading text-xl"
|
|
39
41
|
};
|
|
40
42
|
static classBySize = {
|
|
41
43
|
xs: "",
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,7 @@ export { ColorScheme } from './components/ColorScheme/color-scheme.js';
|
|
|
7
7
|
export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer.svelte';
|
|
8
8
|
export { default as HoverExpandableWidth } from './components/HoverExpandableWidth/HoverExpandableWidth.svelte';
|
|
9
9
|
export { default as X } from './components/X/X.svelte';
|
|
10
|
+
export { clickOutside } from './actions/click-outside.js';
|
|
11
|
+
export { focusTrap } from './actions/focus-trap.js';
|
|
10
12
|
export { windowSize, breakpoint } from './utils/window-size.js';
|
|
11
13
|
export { DevicePointer } from './utils/device-pointer.js';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,9 @@ export { default as Drawer, createDrawerStore } from './components/Drawer/Drawer
|
|
|
15
15
|
export { default as HoverExpandableWidth } from './components/HoverExpandableWidth/HoverExpandableWidth.svelte';
|
|
16
16
|
//
|
|
17
17
|
export { default as X } from './components/X/X.svelte';
|
|
18
|
+
// actions
|
|
19
|
+
export { clickOutside } from './actions/click-outside.js';
|
|
20
|
+
export { focusTrap } from './actions/focus-trap.js';
|
|
18
21
|
// utils
|
|
19
22
|
export { windowSize, breakpoint } from './utils/window-size.js';
|
|
20
23
|
export { DevicePointer } from './utils/device-pointer.js';
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/stuic",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
|
-
"build": "vite build && npm run package",
|
|
6
|
+
"build": "vite build && npm run package && node ./scripts/date.js",
|
|
7
7
|
"build:watch": "fswatch -o src | xargs -n1 -I{} npm run build",
|
|
8
8
|
"preview": "vite preview",
|
|
9
9
|
"package": "svelte-kit sync && svelte-package && publint",
|