@stacksjs/components 0.2.92 → 0.2.94

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.
Files changed (188) hide show
  1. package/dist/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
  2. package/dist/index.js +5 -5
  3. package/package.json +5 -6
  4. package/src/components/CodeBlock.stx +0 -54
  5. package/src/components/Footer.stx +0 -66
  6. package/src/components/Hero.stx +0 -25
  7. package/src/components/Installation.stx +0 -37
  8. package/src/components/index.ts +0 -4
  9. package/src/composables/index.ts +0 -4
  10. package/src/composables/useCopyCode.ts +0 -73
  11. package/src/composables/useDarkMode.ts +0 -100
  12. package/src/composables/useSEO.ts +0 -230
  13. package/src/composables/utilities/index.ts +0 -7
  14. package/src/composables/utilities/useClickOutside.ts +0 -72
  15. package/src/composables/utilities/useFocusTrap.ts +0 -159
  16. package/src/composables/utilities/useKeyboard.ts +0 -124
  17. package/src/composables/utilities/useLocalStorage.ts +0 -132
  18. package/src/composables/utilities/useMediaQuery.ts +0 -118
  19. package/src/index.ts +0 -99
  20. package/src/stx.d.ts +0 -4
  21. package/src/ui/accordion/Accordion.stx +0 -146
  22. package/src/ui/accordion/AccordionItem.stx +0 -37
  23. package/src/ui/accordion/index.ts +0 -47
  24. package/src/ui/audio/Audio.stx +0 -50
  25. package/src/ui/audio/README.md +0 -41
  26. package/src/ui/audio/index.ts +0 -13
  27. package/src/ui/auth/Login.stx +0 -161
  28. package/src/ui/auth/README.md +0 -142
  29. package/src/ui/auth/Signup.stx +0 -159
  30. package/src/ui/auth/TwoFactorChallenge.stx +0 -111
  31. package/src/ui/auth/index.ts +0 -32
  32. package/src/ui/avatar/Avatar.stx +0 -77
  33. package/src/ui/avatar/index.ts +0 -12
  34. package/src/ui/badge/Badge.stx +0 -62
  35. package/src/ui/badge/index.ts +0 -10
  36. package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
  37. package/src/ui/breadcrumb/index.ts +0 -15
  38. package/src/ui/button/Button.stx +0 -74
  39. package/src/ui/button/README.md +0 -80
  40. package/src/ui/button/index.ts +0 -85
  41. package/src/ui/calendar/Calendar.stx +0 -189
  42. package/src/ui/calendar/README.md +0 -254
  43. package/src/ui/calendar/index.ts +0 -14
  44. package/src/ui/card/Card.stx +0 -68
  45. package/src/ui/card/index.ts +0 -13
  46. package/src/ui/checkbox/Checkbox.stx +0 -86
  47. package/src/ui/checkbox/index.ts +0 -16
  48. package/src/ui/combobox/Combobox.stx +0 -96
  49. package/src/ui/combobox/ComboboxButton.stx +0 -25
  50. package/src/ui/combobox/ComboboxInput.stx +0 -32
  51. package/src/ui/combobox/ComboboxOption.stx +0 -49
  52. package/src/ui/combobox/ComboboxOptions.stx +0 -16
  53. package/src/ui/combobox/README.md +0 -150
  54. package/src/ui/combobox/index.ts +0 -40
  55. package/src/ui/command-palette/CommandPalette.stx +0 -81
  56. package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
  57. package/src/ui/command-palette/README.md +0 -42
  58. package/src/ui/command-palette/index.ts +0 -15
  59. package/src/ui/dialog/Dialog.stx +0 -41
  60. package/src/ui/dialog/DialogBackdrop.stx +0 -9
  61. package/src/ui/dialog/DialogDescription.stx +0 -11
  62. package/src/ui/dialog/DialogPanel.stx +0 -16
  63. package/src/ui/dialog/DialogTitle.stx +0 -11
  64. package/src/ui/dialog/README.md +0 -121
  65. package/src/ui/dialog/index.ts +0 -32
  66. package/src/ui/drawer/Drawer.stx +0 -120
  67. package/src/ui/drawer/README.md +0 -90
  68. package/src/ui/drawer/index.ts +0 -9
  69. package/src/ui/dropdown/Dropdown.stx +0 -82
  70. package/src/ui/dropdown/DropdownButton.stx +0 -29
  71. package/src/ui/dropdown/DropdownItem.stx +0 -35
  72. package/src/ui/dropdown/DropdownItems.stx +0 -18
  73. package/src/ui/dropdown/README.md +0 -114
  74. package/src/ui/dropdown/index.ts +0 -29
  75. package/src/ui/form/Form.stx +0 -95
  76. package/src/ui/form/index.ts +0 -67
  77. package/src/ui/heatmap/Heatmap.stx +0 -294
  78. package/src/ui/image/Image.stx +0 -165
  79. package/src/ui/image/README.md +0 -164
  80. package/src/ui/image/index.ts +0 -22
  81. package/src/ui/input/EmailInput.stx +0 -30
  82. package/src/ui/input/NumberInput.stx +0 -126
  83. package/src/ui/input/PasswordInput.stx +0 -143
  84. package/src/ui/input/SearchInput.stx +0 -44
  85. package/src/ui/input/TextInput.stx +0 -132
  86. package/src/ui/input/index.ts +0 -180
  87. package/src/ui/listbox/Listbox.stx +0 -101
  88. package/src/ui/listbox/ListboxButton.stx +0 -28
  89. package/src/ui/listbox/ListboxLabel.stx +0 -11
  90. package/src/ui/listbox/ListboxOption.stx +0 -49
  91. package/src/ui/listbox/ListboxOptions.stx +0 -16
  92. package/src/ui/listbox/README.md +0 -135
  93. package/src/ui/listbox/index.ts +0 -39
  94. package/src/ui/navigator/Navigator.stx +0 -113
  95. package/src/ui/navigator/README.md +0 -352
  96. package/src/ui/navigator/index.ts +0 -20
  97. package/src/ui/notification/Notification.stx +0 -117
  98. package/src/ui/notification/README.md +0 -114
  99. package/src/ui/notification/index.ts +0 -12
  100. package/src/ui/pagination/Pagination.stx +0 -150
  101. package/src/ui/pagination/index.ts +0 -11
  102. package/src/ui/payment/Checkout.stx +0 -31
  103. package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
  104. package/src/ui/payment/PaymentMethods.stx +0 -104
  105. package/src/ui/payment/README.md +0 -345
  106. package/src/ui/payment/SubscriptionCheckout.stx +0 -323
  107. package/src/ui/payment/index.ts +0 -59
  108. package/src/ui/popover/Popover.stx +0 -60
  109. package/src/ui/popover/PopoverButton.stx +0 -29
  110. package/src/ui/popover/PopoverPanel.stx +0 -23
  111. package/src/ui/popover/README.md +0 -91
  112. package/src/ui/popover/index.ts +0 -22
  113. package/src/ui/portal/Portal.stx +0 -57
  114. package/src/ui/portal/index.ts +0 -6
  115. package/src/ui/progress/Progress.stx +0 -131
  116. package/src/ui/progress/index.ts +0 -12
  117. package/src/ui/radio/Radio.stx +0 -77
  118. package/src/ui/radio/index.ts +0 -15
  119. package/src/ui/radio-group/README.md +0 -139
  120. package/src/ui/radio-group/RadioGroup.stx +0 -60
  121. package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
  122. package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
  123. package/src/ui/radio-group/RadioGroupOption.stx +0 -58
  124. package/src/ui/radio-group/index.ts +0 -30
  125. package/src/ui/select/README.md +0 -180
  126. package/src/ui/select/Select.stx +0 -82
  127. package/src/ui/select/index.ts +0 -21
  128. package/src/ui/sidebar/Sidebar.stx +0 -458
  129. package/src/ui/sidebar/SidebarFooter.stx +0 -71
  130. package/src/ui/sidebar/SidebarHeader.stx +0 -103
  131. package/src/ui/sidebar/SidebarItem.stx +0 -86
  132. package/src/ui/sidebar/SidebarSection.stx +0 -83
  133. package/src/ui/sidebar/index.ts +0 -153
  134. package/src/ui/sidebar/themes.ts +0 -240
  135. package/src/ui/skeleton/Skeleton.stx +0 -48
  136. package/src/ui/skeleton/index.ts +0 -11
  137. package/src/ui/spinner/Spinner.stx +0 -109
  138. package/src/ui/spinner/index.ts +0 -9
  139. package/src/ui/stepper/README.md +0 -36
  140. package/src/ui/stepper/Stepper.stx +0 -17
  141. package/src/ui/stepper/StepperStep.stx +0 -57
  142. package/src/ui/stepper/index.ts +0 -19
  143. package/src/ui/storage/README.md +0 -193
  144. package/src/ui/storage/index.ts +0 -347
  145. package/src/ui/switch/README.md +0 -114
  146. package/src/ui/switch/Switch.stx +0 -72
  147. package/src/ui/switch/index.ts +0 -10
  148. package/src/ui/table/README.md +0 -39
  149. package/src/ui/table/Table.stx +0 -14
  150. package/src/ui/table/TableBody.stx +0 -10
  151. package/src/ui/table/TableCell.stx +0 -10
  152. package/src/ui/table/TableHead.stx +0 -10
  153. package/src/ui/table/TableHeader.stx +0 -20
  154. package/src/ui/table/TableRow.stx +0 -11
  155. package/src/ui/table/index.ts +0 -35
  156. package/src/ui/tabs/TabPanel.stx +0 -25
  157. package/src/ui/tabs/Tabs.stx +0 -202
  158. package/src/ui/tabs/index.ts +0 -50
  159. package/src/ui/teleport/Teleport.stx +0 -65
  160. package/src/ui/teleport/index.ts +0 -7
  161. package/src/ui/textarea/Textarea.stx +0 -108
  162. package/src/ui/textarea/index.ts +0 -20
  163. package/src/ui/tooltip/Tooltip.stx +0 -71
  164. package/src/ui/tooltip/index.ts +0 -10
  165. package/src/ui/transition/README.md +0 -30
  166. package/src/ui/transition/Transition.stx +0 -102
  167. package/src/ui/transition/index.ts +0 -25
  168. package/src/ui/video/README.md +0 -47
  169. package/src/ui/video/Video.stx +0 -45
  170. package/src/ui/video/index.ts +0 -16
  171. package/src/ui/virtual-list/VirtualList.stx +0 -67
  172. package/src/ui/virtual-list/index.ts +0 -10
  173. package/src/ui/virtual-table/VirtualTable.stx +0 -103
  174. package/src/ui/virtual-table/index.ts +0 -21
  175. package/src/utils/PROP_VALIDATION.md +0 -458
  176. package/src/utils/THEME_SYSTEM.md +0 -702
  177. package/src/utils/accessibility.ts +0 -841
  178. package/src/utils/animation/easing.ts +0 -368
  179. package/src/utils/animation/helpers.ts +0 -223
  180. package/src/utils/animation/index.ts +0 -5
  181. package/src/utils/animation/keyframes.ts +0 -230
  182. package/src/utils/error-handling.ts +0 -475
  183. package/src/utils/highlighter.ts +0 -93
  184. package/src/utils/i18n.ts +0 -634
  185. package/src/utils/keep-alive.ts +0 -819
  186. package/src/utils/lazy-loading.ts +0 -530
  187. package/src/utils/prop-validation.ts +0 -478
  188. package/src/utils/theme.ts +0 -626
@@ -1,230 +0,0 @@
1
- /* eslint-disable style/max-statements-per-line */
2
- /**
3
- * SEO and meta tag utilities for STX components
4
- */
5
-
6
- export interface SEOMetaOptions {
7
- title?: string
8
- description?: string
9
- author?: string
10
- keywords?: string[]
11
- ogImage?: string
12
- ogImageWidth?: string
13
- ogImageHeight?: string
14
- twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player'
15
- twitterSite?: string
16
- twitterCreator?: string
17
- canonical?: string
18
- }
19
-
20
- /**
21
- * Set document title
22
- */
23
- export function setTitle(title: string, template?: string): void {
24
- if (typeof document === 'undefined')
25
- return
26
-
27
- document.title = template ? template.replace('%s', title) : title
28
- }
29
-
30
- /**
31
- * Set meta tag
32
- */
33
- export function setMeta(name: string, content: string, isProperty = false): void {
34
- if (typeof document === 'undefined')
35
- return
36
-
37
- const attribute = isProperty ? 'property' : 'name'
38
- let meta = document.querySelector(`meta[${attribute}="${name}"]`)
39
-
40
- if (!meta) {
41
- meta = document.createElement('meta')
42
- meta.setAttribute(attribute, name)
43
- document.head.appendChild(meta)
44
- }
45
-
46
- meta.setAttribute('content', content)
47
- }
48
-
49
- /**
50
- * Set link tag
51
- */
52
- export function setLink(rel: string, href: string): void {
53
- if (typeof document === 'undefined')
54
- return
55
-
56
- let link = document.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement
57
-
58
- if (!link) {
59
- link = document.createElement('link')
60
- link.setAttribute('rel', rel)
61
- document.head.appendChild(link)
62
- }
63
-
64
- link.setAttribute('href', href)
65
- }
66
-
67
- /**
68
- * Use SEO meta tags
69
- */
70
- export function useSEO(options: SEOMetaOptions): void {
71
- const {
72
- title,
73
- description,
74
- author,
75
- keywords,
76
- ogImage,
77
- ogImageWidth,
78
- ogImageHeight,
79
- twitterCard = 'summary_large_image',
80
- twitterSite,
81
- twitterCreator,
82
- canonical,
83
- } = options
84
-
85
- // Set title
86
- if (title) {
87
- setTitle(title)
88
- setMeta('og:title', title, true)
89
- setMeta('twitter:title', title)
90
- }
91
-
92
- // Set description
93
- if (description) {
94
- setMeta('description', description)
95
- setMeta('og:description', description, true)
96
- setMeta('twitter:description', description)
97
- }
98
-
99
- // Set author
100
- if (author) {
101
- setMeta('author', author)
102
- }
103
-
104
- // Set keywords
105
- if (keywords && keywords.length > 0) {
106
- setMeta('keywords', keywords.join(', '))
107
- }
108
-
109
- // Set Open Graph image
110
- if (ogImage) {
111
- setMeta('og:image', ogImage, true)
112
- setMeta('twitter:image', ogImage)
113
- }
114
-
115
- if (ogImageWidth) {
116
- setMeta('og:image:width', ogImageWidth, true)
117
- }
118
-
119
- if (ogImageHeight) {
120
- setMeta('og:image:height', ogImageHeight, true)
121
- }
122
-
123
- // Set Twitter card
124
- setMeta('twitter:card', twitterCard)
125
-
126
- if (twitterSite) {
127
- setMeta('twitter:site', twitterSite)
128
- }
129
-
130
- if (twitterCreator) {
131
- setMeta('twitter:creator', twitterCreator)
132
- }
133
-
134
- // Set canonical URL
135
- if (canonical) {
136
- setLink('canonical', canonical)
137
- }
138
-
139
- // Set og:type
140
- setMeta('og:type', 'website', true)
141
-
142
- // Set og:url from current URL
143
- if (typeof window !== 'undefined') {
144
- setMeta('og:url', window.location.href, true)
145
- }
146
- }
147
-
148
- /**
149
- * Generate SEO meta tags as HTML string for server-side rendering
150
- */
151
- export function generateSEOTags(options: SEOMetaOptions): string {
152
- const tags: string[] = []
153
-
154
- const {
155
- title,
156
- description,
157
- author,
158
- keywords,
159
- ogImage,
160
- ogImageWidth,
161
- ogImageHeight,
162
- twitterCard = 'summary_large_image',
163
- twitterSite,
164
- twitterCreator,
165
- canonical,
166
- } = options
167
-
168
- if (title) {
169
- tags.push(`<title>${escapeHtml(title)}</title>`)
170
- tags.push(`<meta property="og:title" content="${escapeHtml(title)}" />`)
171
- tags.push(`<meta name="twitter:title" content="${escapeHtml(title)}" />`)
172
- }
173
-
174
- if (description) {
175
- tags.push(`<meta name="description" content="${escapeHtml(description)}" />`)
176
- tags.push(`<meta property="og:description" content="${escapeHtml(description)}" />`)
177
- tags.push(`<meta name="twitter:description" content="${escapeHtml(description)}" />`)
178
- }
179
-
180
- if (author) {
181
- tags.push(`<meta name="author" content="${escapeHtml(author)}" />`)
182
- }
183
-
184
- if (keywords && keywords.length > 0) {
185
- tags.push(`<meta name="keywords" content="${escapeHtml(keywords.join(', '))}" />`)
186
- }
187
-
188
- if (ogImage) {
189
- tags.push(`<meta property="og:image" content="${escapeHtml(ogImage)}" />`)
190
- tags.push(`<meta name="twitter:image" content="${escapeHtml(ogImage)}" />`)
191
- }
192
-
193
- if (ogImageWidth) {
194
- tags.push(`<meta property="og:image:width" content="${escapeHtml(ogImageWidth)}" />`)
195
- }
196
-
197
- if (ogImageHeight) {
198
- tags.push(`<meta property="og:image:height" content="${escapeHtml(ogImageHeight)}" />`)
199
- }
200
-
201
- tags.push(`<meta name="twitter:card" content="${escapeHtml(twitterCard)}" />`)
202
-
203
- if (twitterSite) {
204
- tags.push(`<meta name="twitter:site" content="${escapeHtml(twitterSite)}" />`)
205
- }
206
-
207
- if (twitterCreator) {
208
- tags.push(`<meta name="twitter:creator" content="${escapeHtml(twitterCreator)}" />`)
209
- }
210
-
211
- if (canonical) {
212
- tags.push(`<link rel="canonical" href="${escapeHtml(canonical)}" />`)
213
- }
214
-
215
- tags.push('<meta property="og:type" content="website" />')
216
-
217
- return tags.join('\n')
218
- }
219
-
220
- /**
221
- * Escape HTML special characters
222
- */
223
- function escapeHtml(str: string): string {
224
- return str
225
- .replace(/&/g, '&amp;')
226
- .replace(/</g, '&lt;')
227
- .replace(/>/g, '&gt;')
228
- .replace(/"/g, '&quot;')
229
- .replace(/'/g, '&#039;')
230
- }
@@ -1,7 +0,0 @@
1
- // Composable utilities exports
2
-
3
- export * from './useClickOutside'
4
- export * from './useFocusTrap'
5
- export * from './useKeyboard'
6
- export * from './useLocalStorage'
7
- export * from './useMediaQuery'
@@ -1,72 +0,0 @@
1
- /**
2
- * Detects clicks outside a specified element
3
- *
4
- * @example
5
- * ```ts
6
- * const cleanup = useClickOutside(
7
- * modalRef,
8
- * () => closeModal(),
9
- * { enabled: isOpen, ignore: ['.tooltip'] }
10
- * )
11
- * ```
12
- */
13
-
14
- /**
15
- * Options for useClickOutside hook
16
- */
17
- export interface UseClickOutsideOptions {
18
- /** Whether the hook is enabled */
19
- enabled?: boolean
20
- /** CSS selectors to ignore (clicks on these elements won't trigger callback) */
21
- ignore?: string[]
22
- }
23
-
24
- /**
25
- * Hook to detect clicks outside an element
26
- *
27
- * @param elementRef - The element to detect clicks outside of
28
- * @param callback - Function to call when click occurs outside element
29
- * @param options - Configuration options
30
- * @returns Cleanup function to remove event listeners
31
- */
32
- export function useClickOutside(
33
- elementRef: HTMLElement | null,
34
- callback: (event: MouseEvent | TouchEvent) => void,
35
- options: UseClickOutsideOptions = {},
36
- ): () => void {
37
- const { enabled = true, ignore = [] } = options
38
-
39
- const handleClick = (event: MouseEvent | TouchEvent) => {
40
- if (!enabled || !elementRef)
41
- return
42
-
43
- const target = event.target as Node
44
-
45
- // Check if click is outside element
46
- if (!elementRef.contains(target)) {
47
- // Check if target matches any ignore selectors
48
- const shouldIgnore = ignore.some((selector) => {
49
- const elements = document.querySelectorAll(selector)
50
- return Array.from(elements).some(el => el.contains(target))
51
- })
52
-
53
- if (!shouldIgnore) {
54
- callback(event)
55
- }
56
- }
57
- }
58
-
59
- // Add event listeners
60
- if (typeof document !== 'undefined') {
61
- document.addEventListener('mousedown', handleClick)
62
- document.addEventListener('touchstart', handleClick)
63
- }
64
-
65
- // Return cleanup function
66
- return () => {
67
- if (typeof document !== 'undefined') {
68
- document.removeEventListener('mousedown', handleClick)
69
- document.removeEventListener('touchstart', handleClick)
70
- }
71
- }
72
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Traps focus within a container element (useful for modals, dialogs, dropdowns)
3
- *
4
- * @example
5
- * ```ts
6
- * const { activate, deactivate } = useFocusTrap(modalRef, {
7
- * initialFocus: firstInputRef,
8
- * returnFocus: true
9
- * })
10
- *
11
- * // When modal opens
12
- * activate()
13
- *
14
- * // When modal closes
15
- * deactivate()
16
- * ```
17
- */
18
-
19
- /**
20
- * Options for useFocusTrap hook
21
- */
22
- export interface UseFocusTrapOptions {
23
- /** Whether the focus trap is enabled */
24
- enabled?: boolean
25
- /** Element to focus initially (defaults to first focusable element) */
26
- initialFocus?: HTMLElement | null
27
- /** Whether to return focus to previously focused element on deactivate */
28
- returnFocus?: boolean
29
- /** Fallback element to focus if no focusable elements found */
30
- fallbackFocus?: HTMLElement | null
31
- }
32
-
33
- /**
34
- * Hook to trap focus within an element
35
- *
36
- * @param containerRef - The container element to trap focus within
37
- * @param options - Configuration options
38
- * @returns Object with activate and deactivate functions
39
- */
40
- export function useFocusTrap(
41
- containerRef: HTMLElement | null,
42
- options: UseFocusTrapOptions = {},
43
- ): { activate: () => void, deactivate: () => void } {
44
- const {
45
- enabled = true,
46
- initialFocus = null,
47
- returnFocus = true,
48
- fallbackFocus = null,
49
- } = options
50
-
51
- let previouslyFocusedElement: HTMLElement | null = null
52
- let isActive = false
53
-
54
- // Get all focusable elements within container
55
- function getFocusableElements(): HTMLElement[] {
56
- if (!containerRef)
57
- return []
58
-
59
- const focusableSelectors = [
60
- 'a[href]',
61
- 'button:not([disabled])',
62
- 'textarea:not([disabled])',
63
- 'input:not([disabled])',
64
- 'select:not([disabled])',
65
- '[tabindex]:not([tabindex="-1"])',
66
- ].join(',')
67
-
68
- return Array.from(containerRef.querySelectorAll(focusableSelectors))
69
- }
70
-
71
- // Handle tab key
72
- function handleTab(event: KeyboardEvent) {
73
- if (!enabled || !isActive || !containerRef)
74
- return
75
-
76
- const focusableElements = getFocusableElements()
77
-
78
- if (focusableElements.length === 0) {
79
- event.preventDefault()
80
- return
81
- }
82
-
83
- const firstElement = focusableElements[0]
84
- const lastElement = focusableElements[focusableElements.length - 1]
85
-
86
- if (event.shiftKey) {
87
- // Shift + Tab
88
- if (document.activeElement === firstElement || document.activeElement === containerRef) {
89
- event.preventDefault()
90
- lastElement.focus()
91
- }
92
- }
93
- else {
94
- // Tab
95
- if (document.activeElement === lastElement) {
96
- event.preventDefault()
97
- firstElement.focus()
98
- }
99
- }
100
- }
101
-
102
- // Handle escape key
103
- function handleEscape(event: KeyboardEvent) {
104
- if (event.key === 'Escape' && enabled && isActive) {
105
- deactivate()
106
- }
107
- }
108
-
109
- // Activate focus trap
110
- function activate() {
111
- if (!enabled || !containerRef || isActive)
112
- return
113
-
114
- // Store previously focused element
115
- previouslyFocusedElement = document.activeElement as HTMLElement
116
-
117
- // Focus initial element
118
- if (initialFocus) {
119
- initialFocus.focus()
120
- }
121
- else {
122
- const focusableElements = getFocusableElements()
123
- if (focusableElements.length > 0) {
124
- focusableElements[0].focus()
125
- }
126
- else if (fallbackFocus) {
127
- fallbackFocus.focus()
128
- }
129
- else {
130
- containerRef.focus()
131
- }
132
- }
133
-
134
- // Add event listeners
135
- document.addEventListener('keydown', handleTab)
136
- document.addEventListener('keydown', handleEscape)
137
-
138
- isActive = true
139
- }
140
-
141
- // Deactivate focus trap
142
- function deactivate() {
143
- if (!isActive)
144
- return
145
-
146
- // Remove event listeners
147
- document.removeEventListener('keydown', handleTab)
148
- document.removeEventListener('keydown', handleEscape)
149
-
150
- // Return focus
151
- if (returnFocus && previouslyFocusedElement) {
152
- previouslyFocusedElement.focus()
153
- }
154
-
155
- isActive = false
156
- }
157
-
158
- return { activate, deactivate }
159
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * Hook for keyboard event handling with support for shortcuts and modifiers
3
- *
4
- * @example
5
- * ```ts
6
- * useKeyboard([
7
- * { key: 'Escape', handler: () => closeModal() },
8
- * { key: 's', ctrl: true, handler: () => save() },
9
- * { key: ['ArrowUp', 'ArrowDown'], handler: (e) => navigate(e.key) }
10
- * ])
11
- * ```
12
- */
13
-
14
- /**
15
- * Options for useKeyboard hook
16
- */
17
- export interface UseKeyboardOptions {
18
- /** Whether the hook is enabled */
19
- enabled?: boolean
20
- /** Whether to prevent default browser behavior */
21
- preventDefault?: boolean
22
- /** Whether to stop event propagation */
23
- stopPropagation?: boolean
24
- /** Target element for event listener (defaults to document) */
25
- target?: HTMLElement | Document | Window | null
26
- }
27
-
28
- /**
29
- * Keyboard event handler configuration
30
- */
31
- export interface KeyboardHandler {
32
- /** Key or keys to listen for */
33
- key: string | string[]
34
- /** Handler function to execute */
35
- handler: (event: KeyboardEvent) => void
36
- /** Whether Ctrl key must be pressed */
37
- ctrl?: boolean
38
- /** Whether Shift key must be pressed */
39
- shift?: boolean
40
- /** Whether Alt key must be pressed */
41
- alt?: boolean
42
- /** Whether Meta (Command on Mac) key must be pressed */
43
- meta?: boolean
44
- }
45
-
46
- /**
47
- * Hook for keyboard event handling
48
- *
49
- * @param handlers - Single handler or array of keyboard handlers
50
- * @param options - Configuration options
51
- * @returns Cleanup function to remove event listeners
52
- */
53
- export function useKeyboard(
54
- handlers: KeyboardHandler | KeyboardHandler[],
55
- options: UseKeyboardOptions = {},
56
- ): () => void {
57
- const {
58
- enabled = true,
59
- preventDefault = true,
60
- stopPropagation = false,
61
- target = typeof document !== 'undefined' ? document : null,
62
- } = options
63
-
64
- const handlerArray = Array.isArray(handlers) ? handlers : [handlers]
65
-
66
- const handleKeydown = (event: KeyboardEvent) => {
67
- if (!enabled)
68
- return
69
-
70
- handlerArray.forEach((handler) => {
71
- const keys = Array.isArray(handler.key) ? handler.key : [handler.key]
72
- const keyMatches = keys.some(k => k.toLowerCase() === event.key.toLowerCase())
73
-
74
- if (!keyMatches)
75
- return
76
-
77
- // Check modifier keys
78
- if (handler.ctrl !== undefined && handler.ctrl !== event.ctrlKey)
79
- return
80
- if (handler.shift !== undefined && handler.shift !== event.shiftKey)
81
- return
82
- if (handler.alt !== undefined && handler.alt !== event.altKey)
83
- return
84
- if (handler.meta !== undefined && handler.meta !== event.metaKey)
85
- return
86
-
87
- // Execute handler
88
- if (preventDefault)
89
- event.preventDefault()
90
- if (stopPropagation)
91
- event.stopPropagation()
92
-
93
- handler.handler(event)
94
- })
95
- }
96
-
97
- // Add event listener
98
- if (target) {
99
- target.addEventListener('keydown', handleKeydown as EventListener)
100
- }
101
-
102
- // Return cleanup function
103
- return () => {
104
- if (target) {
105
- target.removeEventListener('keydown', handleKeydown as EventListener)
106
- }
107
- }
108
- }
109
-
110
- // Common keyboard shortcuts
111
- export const KeyboardShortcuts = {
112
- ESCAPE: 'Escape',
113
- ENTER: 'Enter',
114
- SPACE: ' ',
115
- TAB: 'Tab',
116
- ARROW_UP: 'ArrowUp',
117
- ARROW_DOWN: 'ArrowDown',
118
- ARROW_LEFT: 'ArrowLeft',
119
- ARROW_RIGHT: 'ArrowRight',
120
- HOME: 'Home',
121
- END: 'End',
122
- PAGE_UP: 'PageUp',
123
- PAGE_DOWN: 'PageDown',
124
- } as const