zabi-components 5.0.18 → 5.0.19
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
|
-
import { trapFocus, saveFocus, returnFocus, focusFirstElement } from "
|
|
3
|
+
import { trapFocus, saveFocus, returnFocus, focusFirstElement } from "../routes/lib/focus-utils.js";
|
|
4
4
|
|
|
5
5
|
type Size = "sm" | "md" | "lg";
|
|
6
6
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus management utilities for accessibility
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get all focusable elements within a container
|
|
6
|
+
*/
|
|
7
|
+
export declare function getFocusableElements(container: HTMLElement): HTMLElement[];
|
|
8
|
+
/**
|
|
9
|
+
* Trap focus within a container element
|
|
10
|
+
*/
|
|
11
|
+
export declare function trapFocus(container: HTMLElement): () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Save the currently focused element
|
|
14
|
+
*/
|
|
15
|
+
export declare function saveFocus(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Return focus to the previously focused element
|
|
18
|
+
*/
|
|
19
|
+
export declare function returnFocus(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Focus the first focusable element in a container
|
|
22
|
+
*/
|
|
23
|
+
export declare function focusFirstElement(container: HTMLElement): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus management utilities for accessibility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get all focusable elements within a container
|
|
7
|
+
*/
|
|
8
|
+
export function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
|
9
|
+
const selector = [
|
|
10
|
+
'button:not([disabled])',
|
|
11
|
+
'[href]',
|
|
12
|
+
'input:not([disabled])',
|
|
13
|
+
'select:not([disabled])',
|
|
14
|
+
'textarea:not([disabled])',
|
|
15
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
16
|
+
].join(', ');
|
|
17
|
+
|
|
18
|
+
return Array.from(container.querySelectorAll<HTMLElement>(selector)).filter(
|
|
19
|
+
(el) => {
|
|
20
|
+
// Filter out elements that are not visible
|
|
21
|
+
const style = window.getComputedStyle(el);
|
|
22
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Trap focus within a container element
|
|
29
|
+
*/
|
|
30
|
+
export function trapFocus(container: HTMLElement): () => void {
|
|
31
|
+
const focusableElements = getFocusableElements(container);
|
|
32
|
+
|
|
33
|
+
if (focusableElements.length === 0) {
|
|
34
|
+
return () => {}; // No-op cleanup
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const firstElement = focusableElements[0];
|
|
38
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
39
|
+
|
|
40
|
+
// Focus first element
|
|
41
|
+
firstElement.focus();
|
|
42
|
+
|
|
43
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
44
|
+
if (event.key !== 'Tab') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentFocus = document.activeElement as HTMLElement;
|
|
49
|
+
|
|
50
|
+
if (!focusableElements.includes(currentFocus)) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
firstElement.focus();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (event.shiftKey) {
|
|
57
|
+
// Shift + Tab
|
|
58
|
+
if (currentFocus === firstElement) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
lastElement.focus();
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
// Tab
|
|
64
|
+
if (currentFocus === lastElement) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
firstElement.focus();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
container.addEventListener('keydown', handleKeydown);
|
|
72
|
+
|
|
73
|
+
// Return cleanup function
|
|
74
|
+
return () => {
|
|
75
|
+
container.removeEventListener('keydown', handleKeydown);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Store the element that had focus before opening a modal/dialog
|
|
81
|
+
*/
|
|
82
|
+
let previousFocus: HTMLElement | null = null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Save the currently focused element
|
|
86
|
+
*/
|
|
87
|
+
export function saveFocus(): void {
|
|
88
|
+
if (typeof document !== 'undefined') {
|
|
89
|
+
previousFocus = document.activeElement as HTMLElement;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Return focus to the previously focused element
|
|
95
|
+
*/
|
|
96
|
+
export function returnFocus(): void {
|
|
97
|
+
if (previousFocus && typeof document !== 'undefined') {
|
|
98
|
+
previousFocus.focus();
|
|
99
|
+
previousFocus = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Focus the first focusable element in a container
|
|
105
|
+
*/
|
|
106
|
+
export function focusFirstElement(container: HTMLElement): void {
|
|
107
|
+
const focusableElements = getFocusableElements(container);
|
|
108
|
+
if (focusableElements.length > 0) {
|
|
109
|
+
focusableElements[0].focus();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zabi-components",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.19",
|
|
4
4
|
"description": "A modern Svelte 5 component library with TypeScript, Tailwind CSS v4, and 100% SSR-safe components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -80,8 +80,9 @@
|
|
|
80
80
|
"dev": "vite dev",
|
|
81
81
|
"build": "vite build && npm run build:types",
|
|
82
82
|
"build:components": "svelte-package -o dist --input src/components",
|
|
83
|
-
"build:lib-files": "mkdir -p dist/routes/lib && cp src/routes/lib/ssr-safe.ts dist/routes/lib/ && cp src/routes/lib/variant-utils.ts dist/routes/lib/",
|
|
84
|
-
"build:
|
|
83
|
+
"build:lib-files": "mkdir -p dist/routes/lib && cp src/routes/lib/ssr-safe.ts dist/routes/lib/ && cp src/routes/lib/variant-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.ts dist/routes/lib/ && cp src/routes/lib/focus-utils.d.ts dist/routes/lib/ 2>/dev/null || true",
|
|
84
|
+
"build:fix-imports": "node scripts/fix-modal-import.js",
|
|
85
|
+
"build:lib": "npm run build:css && npm run build:components && npm run build:lib-files && npm run build:fix-imports && npm run build:types && npm run build:css && node scripts/verify-build.js",
|
|
85
86
|
"build:css": "node scripts/build-css.js && node scripts/validate-theme.js && node scripts/cleanup-css.js",
|
|
86
87
|
"build:types": "tsc --project tsconfig.lib.json",
|
|
87
88
|
"preview": "vite preview",
|