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 "../../routes/lib/focus-utils.js";
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.18",
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:lib": "npm run build:css && npm run build:components && npm run build:lib-files && npm run build:types && npm run build:css && node scripts/verify-build.js",
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",