@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.
- package/dist/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
- package/dist/index.js +5 -5
- package/package.json +5 -6
- package/src/components/CodeBlock.stx +0 -54
- package/src/components/Footer.stx +0 -66
- package/src/components/Hero.stx +0 -25
- package/src/components/Installation.stx +0 -37
- package/src/components/index.ts +0 -4
- package/src/composables/index.ts +0 -4
- package/src/composables/useCopyCode.ts +0 -73
- package/src/composables/useDarkMode.ts +0 -100
- package/src/composables/useSEO.ts +0 -230
- package/src/composables/utilities/index.ts +0 -7
- package/src/composables/utilities/useClickOutside.ts +0 -72
- package/src/composables/utilities/useFocusTrap.ts +0 -159
- package/src/composables/utilities/useKeyboard.ts +0 -124
- package/src/composables/utilities/useLocalStorage.ts +0 -132
- package/src/composables/utilities/useMediaQuery.ts +0 -118
- package/src/index.ts +0 -99
- package/src/stx.d.ts +0 -4
- package/src/ui/accordion/Accordion.stx +0 -146
- package/src/ui/accordion/AccordionItem.stx +0 -37
- package/src/ui/accordion/index.ts +0 -47
- package/src/ui/audio/Audio.stx +0 -50
- package/src/ui/audio/README.md +0 -41
- package/src/ui/audio/index.ts +0 -13
- package/src/ui/auth/Login.stx +0 -161
- package/src/ui/auth/README.md +0 -142
- package/src/ui/auth/Signup.stx +0 -159
- package/src/ui/auth/TwoFactorChallenge.stx +0 -111
- package/src/ui/auth/index.ts +0 -32
- package/src/ui/avatar/Avatar.stx +0 -77
- package/src/ui/avatar/index.ts +0 -12
- package/src/ui/badge/Badge.stx +0 -62
- package/src/ui/badge/index.ts +0 -10
- package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
- package/src/ui/breadcrumb/index.ts +0 -15
- package/src/ui/button/Button.stx +0 -74
- package/src/ui/button/README.md +0 -80
- package/src/ui/button/index.ts +0 -85
- package/src/ui/calendar/Calendar.stx +0 -189
- package/src/ui/calendar/README.md +0 -254
- package/src/ui/calendar/index.ts +0 -14
- package/src/ui/card/Card.stx +0 -68
- package/src/ui/card/index.ts +0 -13
- package/src/ui/checkbox/Checkbox.stx +0 -86
- package/src/ui/checkbox/index.ts +0 -16
- package/src/ui/combobox/Combobox.stx +0 -96
- package/src/ui/combobox/ComboboxButton.stx +0 -25
- package/src/ui/combobox/ComboboxInput.stx +0 -32
- package/src/ui/combobox/ComboboxOption.stx +0 -49
- package/src/ui/combobox/ComboboxOptions.stx +0 -16
- package/src/ui/combobox/README.md +0 -150
- package/src/ui/combobox/index.ts +0 -40
- package/src/ui/command-palette/CommandPalette.stx +0 -81
- package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
- package/src/ui/command-palette/README.md +0 -42
- package/src/ui/command-palette/index.ts +0 -15
- package/src/ui/dialog/Dialog.stx +0 -41
- package/src/ui/dialog/DialogBackdrop.stx +0 -9
- package/src/ui/dialog/DialogDescription.stx +0 -11
- package/src/ui/dialog/DialogPanel.stx +0 -16
- package/src/ui/dialog/DialogTitle.stx +0 -11
- package/src/ui/dialog/README.md +0 -121
- package/src/ui/dialog/index.ts +0 -32
- package/src/ui/drawer/Drawer.stx +0 -120
- package/src/ui/drawer/README.md +0 -90
- package/src/ui/drawer/index.ts +0 -9
- package/src/ui/dropdown/Dropdown.stx +0 -82
- package/src/ui/dropdown/DropdownButton.stx +0 -29
- package/src/ui/dropdown/DropdownItem.stx +0 -35
- package/src/ui/dropdown/DropdownItems.stx +0 -18
- package/src/ui/dropdown/README.md +0 -114
- package/src/ui/dropdown/index.ts +0 -29
- package/src/ui/form/Form.stx +0 -95
- package/src/ui/form/index.ts +0 -67
- package/src/ui/heatmap/Heatmap.stx +0 -294
- package/src/ui/image/Image.stx +0 -165
- package/src/ui/image/README.md +0 -164
- package/src/ui/image/index.ts +0 -22
- package/src/ui/input/EmailInput.stx +0 -30
- package/src/ui/input/NumberInput.stx +0 -126
- package/src/ui/input/PasswordInput.stx +0 -143
- package/src/ui/input/SearchInput.stx +0 -44
- package/src/ui/input/TextInput.stx +0 -132
- package/src/ui/input/index.ts +0 -180
- package/src/ui/listbox/Listbox.stx +0 -101
- package/src/ui/listbox/ListboxButton.stx +0 -28
- package/src/ui/listbox/ListboxLabel.stx +0 -11
- package/src/ui/listbox/ListboxOption.stx +0 -49
- package/src/ui/listbox/ListboxOptions.stx +0 -16
- package/src/ui/listbox/README.md +0 -135
- package/src/ui/listbox/index.ts +0 -39
- package/src/ui/navigator/Navigator.stx +0 -113
- package/src/ui/navigator/README.md +0 -352
- package/src/ui/navigator/index.ts +0 -20
- package/src/ui/notification/Notification.stx +0 -117
- package/src/ui/notification/README.md +0 -114
- package/src/ui/notification/index.ts +0 -12
- package/src/ui/pagination/Pagination.stx +0 -150
- package/src/ui/pagination/index.ts +0 -11
- package/src/ui/payment/Checkout.stx +0 -31
- package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
- package/src/ui/payment/PaymentMethods.stx +0 -104
- package/src/ui/payment/README.md +0 -345
- package/src/ui/payment/SubscriptionCheckout.stx +0 -323
- package/src/ui/payment/index.ts +0 -59
- package/src/ui/popover/Popover.stx +0 -60
- package/src/ui/popover/PopoverButton.stx +0 -29
- package/src/ui/popover/PopoverPanel.stx +0 -23
- package/src/ui/popover/README.md +0 -91
- package/src/ui/popover/index.ts +0 -22
- package/src/ui/portal/Portal.stx +0 -57
- package/src/ui/portal/index.ts +0 -6
- package/src/ui/progress/Progress.stx +0 -131
- package/src/ui/progress/index.ts +0 -12
- package/src/ui/radio/Radio.stx +0 -77
- package/src/ui/radio/index.ts +0 -15
- package/src/ui/radio-group/README.md +0 -139
- package/src/ui/radio-group/RadioGroup.stx +0 -60
- package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
- package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
- package/src/ui/radio-group/RadioGroupOption.stx +0 -58
- package/src/ui/radio-group/index.ts +0 -30
- package/src/ui/select/README.md +0 -180
- package/src/ui/select/Select.stx +0 -82
- package/src/ui/select/index.ts +0 -21
- package/src/ui/sidebar/Sidebar.stx +0 -458
- package/src/ui/sidebar/SidebarFooter.stx +0 -71
- package/src/ui/sidebar/SidebarHeader.stx +0 -103
- package/src/ui/sidebar/SidebarItem.stx +0 -86
- package/src/ui/sidebar/SidebarSection.stx +0 -83
- package/src/ui/sidebar/index.ts +0 -153
- package/src/ui/sidebar/themes.ts +0 -240
- package/src/ui/skeleton/Skeleton.stx +0 -48
- package/src/ui/skeleton/index.ts +0 -11
- package/src/ui/spinner/Spinner.stx +0 -109
- package/src/ui/spinner/index.ts +0 -9
- package/src/ui/stepper/README.md +0 -36
- package/src/ui/stepper/Stepper.stx +0 -17
- package/src/ui/stepper/StepperStep.stx +0 -57
- package/src/ui/stepper/index.ts +0 -19
- package/src/ui/storage/README.md +0 -193
- package/src/ui/storage/index.ts +0 -347
- package/src/ui/switch/README.md +0 -114
- package/src/ui/switch/Switch.stx +0 -72
- package/src/ui/switch/index.ts +0 -10
- package/src/ui/table/README.md +0 -39
- package/src/ui/table/Table.stx +0 -14
- package/src/ui/table/TableBody.stx +0 -10
- package/src/ui/table/TableCell.stx +0 -10
- package/src/ui/table/TableHead.stx +0 -10
- package/src/ui/table/TableHeader.stx +0 -20
- package/src/ui/table/TableRow.stx +0 -11
- package/src/ui/table/index.ts +0 -35
- package/src/ui/tabs/TabPanel.stx +0 -25
- package/src/ui/tabs/Tabs.stx +0 -202
- package/src/ui/tabs/index.ts +0 -50
- package/src/ui/teleport/Teleport.stx +0 -65
- package/src/ui/teleport/index.ts +0 -7
- package/src/ui/textarea/Textarea.stx +0 -108
- package/src/ui/textarea/index.ts +0 -20
- package/src/ui/tooltip/Tooltip.stx +0 -71
- package/src/ui/tooltip/index.ts +0 -10
- package/src/ui/transition/README.md +0 -30
- package/src/ui/transition/Transition.stx +0 -102
- package/src/ui/transition/index.ts +0 -25
- package/src/ui/video/README.md +0 -47
- package/src/ui/video/Video.stx +0 -45
- package/src/ui/video/index.ts +0 -16
- package/src/ui/virtual-list/VirtualList.stx +0 -67
- package/src/ui/virtual-list/index.ts +0 -10
- package/src/ui/virtual-table/VirtualTable.stx +0 -103
- package/src/ui/virtual-table/index.ts +0 -21
- package/src/utils/PROP_VALIDATION.md +0 -458
- package/src/utils/THEME_SYSTEM.md +0 -702
- package/src/utils/accessibility.ts +0 -841
- package/src/utils/animation/easing.ts +0 -368
- package/src/utils/animation/helpers.ts +0 -223
- package/src/utils/animation/index.ts +0 -5
- package/src/utils/animation/keyframes.ts +0 -230
- package/src/utils/error-handling.ts +0 -475
- package/src/utils/highlighter.ts +0 -93
- package/src/utils/i18n.ts +0 -634
- package/src/utils/keep-alive.ts +0 -819
- package/src/utils/lazy-loading.ts +0 -530
- package/src/utils/prop-validation.ts +0 -478
- package/src/utils/theme.ts +0 -626
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keyframe animation utilities using Web Animations API
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```ts
|
|
6
|
-
* import { animate, KeyframeAnimations } from '@stacksjs/components'
|
|
7
|
-
*
|
|
8
|
-
* // Use predefined animation
|
|
9
|
-
* animate(element, 'fadeIn', { duration: 500 })
|
|
10
|
-
*
|
|
11
|
-
* // Custom keyframes
|
|
12
|
-
* createKeyframeAnimation(element, [
|
|
13
|
-
* { offset: 0, opacity: 0, transform: 'scale(0.5)' },
|
|
14
|
-
* { offset: 1, opacity: 1, transform: 'scale(1)' }
|
|
15
|
-
* ], { duration: 300 })
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Single keyframe definition
|
|
21
|
-
*/
|
|
22
|
-
export interface Keyframe {
|
|
23
|
-
/** Keyframe position (0 to 1) */
|
|
24
|
-
offset: number
|
|
25
|
-
/** CSS properties to animate */
|
|
26
|
-
[property: string]: any
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Animation configuration options
|
|
31
|
-
*/
|
|
32
|
-
export interface AnimationOptions {
|
|
33
|
-
/** Animation duration in milliseconds */
|
|
34
|
-
duration?: number
|
|
35
|
-
/** Easing function, CSS easing string */
|
|
36
|
-
easing?: string
|
|
37
|
-
/** Delay before animation starts in milliseconds */
|
|
38
|
-
delay?: number
|
|
39
|
-
/** Number of iterations (Infinity for infinite) */
|
|
40
|
-
iterations?: number
|
|
41
|
-
/** Animation direction */
|
|
42
|
-
direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse'
|
|
43
|
-
/** Fill mode */
|
|
44
|
-
fill?: 'none' | 'forwards' | 'backwards' | 'both'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Create a keyframe animation using Web Animations API
|
|
49
|
-
*
|
|
50
|
-
* @param element - Target HTML element
|
|
51
|
-
* @param keyframes - Array of keyframe definitions
|
|
52
|
-
* @param options - Animation options
|
|
53
|
-
* @returns Animation instance or null if not supported
|
|
54
|
-
*/
|
|
55
|
-
export function createKeyframeAnimation(
|
|
56
|
-
element: HTMLElement,
|
|
57
|
-
keyframes: readonly Keyframe[] | Keyframe[],
|
|
58
|
-
options: AnimationOptions = {},
|
|
59
|
-
): Animation | null {
|
|
60
|
-
if (typeof element.animate !== 'function') {
|
|
61
|
-
console.warn('Web Animations API not supported')
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const {
|
|
66
|
-
duration = 1000,
|
|
67
|
-
easing = 'ease',
|
|
68
|
-
delay = 0,
|
|
69
|
-
iterations = 1,
|
|
70
|
-
direction = 'normal',
|
|
71
|
-
fill = 'both',
|
|
72
|
-
} = options
|
|
73
|
-
|
|
74
|
-
return element.animate([...keyframes], {
|
|
75
|
-
duration,
|
|
76
|
-
easing,
|
|
77
|
-
delay,
|
|
78
|
-
iterations,
|
|
79
|
-
direction,
|
|
80
|
-
fill,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Predefined keyframe animation presets
|
|
86
|
-
*
|
|
87
|
-
* Available animations:
|
|
88
|
-
* - fadeIn/fadeOut
|
|
89
|
-
* - slideInUp/Down/Left/Right, slideOutUp/Down/Left/Right
|
|
90
|
-
* - zoomIn/Out
|
|
91
|
-
* - rotate, bounce, pulse, shake, swing
|
|
92
|
-
* - flip, rubberBand, heartbeat
|
|
93
|
-
*/
|
|
94
|
-
export const KeyframeAnimations = {
|
|
95
|
-
fadeIn: [
|
|
96
|
-
{ offset: 0, opacity: 0 },
|
|
97
|
-
{ offset: 1, opacity: 1 },
|
|
98
|
-
],
|
|
99
|
-
fadeOut: [
|
|
100
|
-
{ offset: 0, opacity: 1 },
|
|
101
|
-
{ offset: 1, opacity: 0 },
|
|
102
|
-
],
|
|
103
|
-
slideInUp: [
|
|
104
|
-
{ offset: 0, transform: 'translateY(100%)', opacity: 0 },
|
|
105
|
-
{ offset: 1, transform: 'translateY(0)', opacity: 1 },
|
|
106
|
-
],
|
|
107
|
-
slideInDown: [
|
|
108
|
-
{ offset: 0, transform: 'translateY(-100%)', opacity: 0 },
|
|
109
|
-
{ offset: 1, transform: 'translateY(0)', opacity: 1 },
|
|
110
|
-
],
|
|
111
|
-
slideInLeft: [
|
|
112
|
-
{ offset: 0, transform: 'translateX(-100%)', opacity: 0 },
|
|
113
|
-
{ offset: 1, transform: 'translateX(0)', opacity: 1 },
|
|
114
|
-
],
|
|
115
|
-
slideInRight: [
|
|
116
|
-
{ offset: 0, transform: 'translateX(100%)', opacity: 0 },
|
|
117
|
-
{ offset: 1, transform: 'translateX(0)', opacity: 1 },
|
|
118
|
-
],
|
|
119
|
-
slideOutUp: [
|
|
120
|
-
{ offset: 0, transform: 'translateY(0)', opacity: 1 },
|
|
121
|
-
{ offset: 1, transform: 'translateY(-100%)', opacity: 0 },
|
|
122
|
-
],
|
|
123
|
-
slideOutDown: [
|
|
124
|
-
{ offset: 0, transform: 'translateY(0)', opacity: 1 },
|
|
125
|
-
{ offset: 1, transform: 'translateY(100%)', opacity: 0 },
|
|
126
|
-
],
|
|
127
|
-
slideOutLeft: [
|
|
128
|
-
{ offset: 0, transform: 'translateX(0)', opacity: 1 },
|
|
129
|
-
{ offset: 1, transform: 'translateX(-100%)', opacity: 0 },
|
|
130
|
-
],
|
|
131
|
-
slideOutRight: [
|
|
132
|
-
{ offset: 0, transform: 'translateX(0)', opacity: 1 },
|
|
133
|
-
{ offset: 1, transform: 'translateX(100%)', opacity: 0 },
|
|
134
|
-
],
|
|
135
|
-
zoomIn: [
|
|
136
|
-
{ offset: 0, transform: 'scale(0)', opacity: 0 },
|
|
137
|
-
{ offset: 0.5, opacity: 1 },
|
|
138
|
-
{ offset: 1, transform: 'scale(1)' },
|
|
139
|
-
],
|
|
140
|
-
zoomOut: [
|
|
141
|
-
{ offset: 0, transform: 'scale(1)', opacity: 1 },
|
|
142
|
-
{ offset: 0.5, opacity: 1 },
|
|
143
|
-
{ offset: 1, transform: 'scale(0)', opacity: 0 },
|
|
144
|
-
],
|
|
145
|
-
rotate: [
|
|
146
|
-
{ offset: 0, transform: 'rotate(0deg)' },
|
|
147
|
-
{ offset: 1, transform: 'rotate(360deg)' },
|
|
148
|
-
],
|
|
149
|
-
bounce: [
|
|
150
|
-
{ offset: 0, transform: 'translateY(0)' },
|
|
151
|
-
{ offset: 0.2, transform: 'translateY(0)' },
|
|
152
|
-
{ offset: 0.4, transform: 'translateY(-30px)' },
|
|
153
|
-
{ offset: 0.43, transform: 'translateY(-30px)' },
|
|
154
|
-
{ offset: 0.53, transform: 'translateY(0)' },
|
|
155
|
-
{ offset: 0.7, transform: 'translateY(-15px)' },
|
|
156
|
-
{ offset: 0.8, transform: 'translateY(0)' },
|
|
157
|
-
{ offset: 0.9, transform: 'translateY(-4px)' },
|
|
158
|
-
{ offset: 1, transform: 'translateY(0)' },
|
|
159
|
-
],
|
|
160
|
-
pulse: [
|
|
161
|
-
{ offset: 0, transform: 'scale(1)' },
|
|
162
|
-
{ offset: 0.5, transform: 'scale(1.05)' },
|
|
163
|
-
{ offset: 1, transform: 'scale(1)' },
|
|
164
|
-
],
|
|
165
|
-
shake: [
|
|
166
|
-
{ offset: 0, transform: 'translateX(0)' },
|
|
167
|
-
{ offset: 0.1, transform: 'translateX(-10px)' },
|
|
168
|
-
{ offset: 0.2, transform: 'translateX(10px)' },
|
|
169
|
-
{ offset: 0.3, transform: 'translateX(-10px)' },
|
|
170
|
-
{ offset: 0.4, transform: 'translateX(10px)' },
|
|
171
|
-
{ offset: 0.5, transform: 'translateX(-10px)' },
|
|
172
|
-
{ offset: 0.6, transform: 'translateX(10px)' },
|
|
173
|
-
{ offset: 0.7, transform: 'translateX(-10px)' },
|
|
174
|
-
{ offset: 0.8, transform: 'translateX(10px)' },
|
|
175
|
-
{ offset: 0.9, transform: 'translateX(-10px)' },
|
|
176
|
-
{ offset: 1, transform: 'translateX(0)' },
|
|
177
|
-
],
|
|
178
|
-
swing: [
|
|
179
|
-
{ offset: 0, transform: 'rotate(0deg)' },
|
|
180
|
-
{ offset: 0.2, transform: 'rotate(15deg)' },
|
|
181
|
-
{ offset: 0.4, transform: 'rotate(-10deg)' },
|
|
182
|
-
{ offset: 0.6, transform: 'rotate(5deg)' },
|
|
183
|
-
{ offset: 0.8, transform: 'rotate(-5deg)' },
|
|
184
|
-
{ offset: 1, transform: 'rotate(0deg)' },
|
|
185
|
-
],
|
|
186
|
-
flip: [
|
|
187
|
-
{ offset: 0, transform: 'perspective(400px) rotateY(0)', animationTimingFunction: 'ease-out' },
|
|
188
|
-
{ offset: 0.4, transform: 'perspective(400px) translateZ(150px) rotateY(170deg)', animationTimingFunction: 'ease-out' },
|
|
189
|
-
{ offset: 0.5, transform: 'perspective(400px) translateZ(150px) rotateY(190deg)', animationTimingFunction: 'ease-in' },
|
|
190
|
-
{ offset: 0.8, transform: 'perspective(400px) rotateY(360deg)', animationTimingFunction: 'ease-in' },
|
|
191
|
-
{ offset: 1, transform: 'perspective(400px)', animationTimingFunction: 'ease-in' },
|
|
192
|
-
],
|
|
193
|
-
rubberBand: [
|
|
194
|
-
{ offset: 0, transform: 'scale(1)' },
|
|
195
|
-
{ offset: 0.3, transform: 'scaleX(1.25) scaleY(0.75)' },
|
|
196
|
-
{ offset: 0.4, transform: 'scaleX(0.75) scaleY(1.25)' },
|
|
197
|
-
{ offset: 0.5, transform: 'scaleX(1.15) scaleY(0.85)' },
|
|
198
|
-
{ offset: 0.65, transform: 'scaleX(0.95) scaleY(1.05)' },
|
|
199
|
-
{ offset: 0.75, transform: 'scaleX(1.05) scaleY(0.95)' },
|
|
200
|
-
{ offset: 1, transform: 'scale(1)' },
|
|
201
|
-
],
|
|
202
|
-
heartbeat: [
|
|
203
|
-
{ offset: 0, transform: 'scale(1)' },
|
|
204
|
-
{ offset: 0.14, transform: 'scale(1.3)' },
|
|
205
|
-
{ offset: 0.28, transform: 'scale(1)' },
|
|
206
|
-
{ offset: 0.42, transform: 'scale(1.3)' },
|
|
207
|
-
{ offset: 0.7, transform: 'scale(1)' },
|
|
208
|
-
],
|
|
209
|
-
} as const
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Type for predefined animation names
|
|
213
|
-
*/
|
|
214
|
-
export type KeyframeAnimationName = keyof typeof KeyframeAnimations
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Animate an element with a predefined animation
|
|
218
|
-
*
|
|
219
|
-
* @param element - Target HTML element
|
|
220
|
-
* @param animationName - Name of predefined animation
|
|
221
|
-
* @param options - Animation options
|
|
222
|
-
* @returns Animation instance or null if not supported
|
|
223
|
-
*/
|
|
224
|
-
export function animate(
|
|
225
|
-
element: HTMLElement,
|
|
226
|
-
animationName: KeyframeAnimationName,
|
|
227
|
-
options: AnimationOptions = {},
|
|
228
|
-
): Animation | null {
|
|
229
|
-
return createKeyframeAnimation(element, KeyframeAnimations[animationName], options)
|
|
230
|
-
}
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error handling utilities for @stacksjs/components
|
|
3
|
-
*
|
|
4
|
-
* Provides clear, actionable error messages with links to documentation
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* import { ComponentError, handleError } from '@stacksjs/components'
|
|
9
|
-
*
|
|
10
|
-
* throw new ComponentError('Button', 'Invalid variant provided', {
|
|
11
|
-
* received: 'invalid',
|
|
12
|
-
* expected: ['primary', 'secondary', 'outline']
|
|
13
|
-
* })
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
import process from 'node:process'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Base URL for documentation
|
|
20
|
-
*/
|
|
21
|
-
const DOCS_BASE_URL = 'https://stacksjs.com/components'
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Error context information
|
|
25
|
-
*/
|
|
26
|
-
export interface ErrorContext {
|
|
27
|
-
/** The component name where the error occurred */
|
|
28
|
-
component?: string
|
|
29
|
-
/** The prop name that caused the error */
|
|
30
|
-
prop?: string
|
|
31
|
-
/** The value that was received */
|
|
32
|
-
received?: any
|
|
33
|
-
/** The expected value(s) */
|
|
34
|
-
expected?: any
|
|
35
|
-
/** Additional details about the error */
|
|
36
|
-
details?: string
|
|
37
|
-
/** Link to relevant documentation */
|
|
38
|
-
docsUrl?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Custom error class for component errors
|
|
43
|
-
*/
|
|
44
|
-
export class ComponentError extends Error {
|
|
45
|
-
component: string
|
|
46
|
-
context: ErrorContext
|
|
47
|
-
|
|
48
|
-
constructor(component: string, message: string, context: ErrorContext = {}) {
|
|
49
|
-
const fullMessage = ComponentError.formatMessage(component, message, context)
|
|
50
|
-
super(fullMessage)
|
|
51
|
-
|
|
52
|
-
this.name = 'ComponentError'
|
|
53
|
-
this.component = component
|
|
54
|
-
this.context = { component, ...context }
|
|
55
|
-
|
|
56
|
-
// Maintains proper stack trace for where error was thrown (only available on V8)
|
|
57
|
-
if (Error.captureStackTrace) {
|
|
58
|
-
Error.captureStackTrace(this, ComponentError)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Format error message with context
|
|
64
|
-
*/
|
|
65
|
-
static formatMessage(component: string, message: string, context: ErrorContext): string {
|
|
66
|
-
let formatted = `[${component}] ${message}`
|
|
67
|
-
|
|
68
|
-
if (context.prop) {
|
|
69
|
-
formatted += `\n Prop: "${context.prop}"`
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (context.received !== undefined) {
|
|
73
|
-
formatted += `\n Received: ${JSON.stringify(context.received)}`
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (context.expected !== undefined) {
|
|
77
|
-
const expected = Array.isArray(context.expected)
|
|
78
|
-
? context.expected.map(v => JSON.stringify(v)).join(', ')
|
|
79
|
-
: JSON.stringify(context.expected)
|
|
80
|
-
formatted += `\n Expected: ${expected}`
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (context.details) {
|
|
84
|
-
formatted += `\n Details: ${context.details}`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (context.docsUrl) {
|
|
88
|
-
formatted += `\n Documentation: ${context.docsUrl}`
|
|
89
|
-
}
|
|
90
|
-
else if (component) {
|
|
91
|
-
formatted += `\n Documentation: ${DOCS_BASE_URL}/${component.toLowerCase()}`
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return formatted
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get error as plain object
|
|
99
|
-
*/
|
|
100
|
-
toJSON(): { name: string, message: string, component: string, context: ErrorContext | undefined, stack: string | undefined } {
|
|
101
|
-
return {
|
|
102
|
-
name: this.name,
|
|
103
|
-
message: this.message,
|
|
104
|
-
component: this.component,
|
|
105
|
-
context: this.context,
|
|
106
|
-
stack: this.stack,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Error types for common scenarios
|
|
113
|
-
*/
|
|
114
|
-
export const ErrorTypes = {
|
|
115
|
-
/**
|
|
116
|
-
* Invalid prop value error
|
|
117
|
-
*/
|
|
118
|
-
invalidProp(component: string, prop: string, received: any, expected: any): ComponentError {
|
|
119
|
-
return new ComponentError(
|
|
120
|
-
component,
|
|
121
|
-
`Invalid prop value for "${prop}"`,
|
|
122
|
-
{
|
|
123
|
-
prop,
|
|
124
|
-
received,
|
|
125
|
-
expected,
|
|
126
|
-
details: Array.isArray(expected)
|
|
127
|
-
? `The "${prop}" prop must be one of the allowed values`
|
|
128
|
-
: `The "${prop}" prop does not match the expected type`,
|
|
129
|
-
},
|
|
130
|
-
)
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Required prop missing error
|
|
135
|
-
*/
|
|
136
|
-
requiredProp(component: string, prop: string): ComponentError {
|
|
137
|
-
return new ComponentError(
|
|
138
|
-
component,
|
|
139
|
-
`Required prop "${prop}" is missing`,
|
|
140
|
-
{
|
|
141
|
-
prop,
|
|
142
|
-
details: `The "${prop}" prop is required for ${component} to function correctly`,
|
|
143
|
-
},
|
|
144
|
-
)
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Invalid prop type error
|
|
149
|
-
*/
|
|
150
|
-
invalidType(component: string, prop: string, received: any, expected: string): ComponentError {
|
|
151
|
-
return new ComponentError(
|
|
152
|
-
component,
|
|
153
|
-
`Invalid type for prop "${prop}"`,
|
|
154
|
-
{
|
|
155
|
-
prop,
|
|
156
|
-
received: typeof received,
|
|
157
|
-
expected,
|
|
158
|
-
details: `Expected "${prop}" to be of type ${expected}, but received ${typeof received}`,
|
|
159
|
-
},
|
|
160
|
-
)
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Component not found error
|
|
165
|
-
*/
|
|
166
|
-
componentNotFound(componentName: string): ComponentError {
|
|
167
|
-
return new ComponentError(
|
|
168
|
-
'ComponentLoader',
|
|
169
|
-
`Component "${componentName}" not found`,
|
|
170
|
-
{
|
|
171
|
-
component: componentName,
|
|
172
|
-
details: 'Make sure the component is properly imported and registered',
|
|
173
|
-
},
|
|
174
|
-
)
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Invalid children error
|
|
179
|
-
*/
|
|
180
|
-
invalidChildren(component: string, received: any, expected: string): ComponentError {
|
|
181
|
-
return new ComponentError(
|
|
182
|
-
component,
|
|
183
|
-
'Invalid children',
|
|
184
|
-
{
|
|
185
|
-
received: typeof received,
|
|
186
|
-
expected,
|
|
187
|
-
details: `${component} expects ${expected} as children`,
|
|
188
|
-
},
|
|
189
|
-
)
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* State update error
|
|
194
|
-
*/
|
|
195
|
-
stateUpdate(component: string, stateName: string, error: Error): ComponentError {
|
|
196
|
-
return new ComponentError(
|
|
197
|
-
component,
|
|
198
|
-
`Failed to update state "${stateName}"`,
|
|
199
|
-
{
|
|
200
|
-
details: error.message,
|
|
201
|
-
},
|
|
202
|
-
)
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Event handler error
|
|
207
|
-
*/
|
|
208
|
-
eventHandler(component: string, eventName: string, error: Error): ComponentError {
|
|
209
|
-
return new ComponentError(
|
|
210
|
-
component,
|
|
211
|
-
`Error in "${eventName}" event handler`,
|
|
212
|
-
{
|
|
213
|
-
details: error.message,
|
|
214
|
-
},
|
|
215
|
-
)
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Render error
|
|
220
|
-
*/
|
|
221
|
-
render(component: string, error: Error): ComponentError {
|
|
222
|
-
return new ComponentError(
|
|
223
|
-
component,
|
|
224
|
-
'Failed to render component',
|
|
225
|
-
{
|
|
226
|
-
details: error.message,
|
|
227
|
-
},
|
|
228
|
-
)
|
|
229
|
-
},
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Global error handler
|
|
234
|
-
*/
|
|
235
|
-
let errorHandler: ((_error: Error) => void) | null = null
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Set global error handler
|
|
239
|
-
*
|
|
240
|
-
* @param handler - Error handler function
|
|
241
|
-
*
|
|
242
|
-
* @example
|
|
243
|
-
* ```ts
|
|
244
|
-
* setErrorHandler((error) => {
|
|
245
|
-
* console.error('Component error:', error)
|
|
246
|
-
* // Send to error tracking service
|
|
247
|
-
* })
|
|
248
|
-
* ```
|
|
249
|
-
*/
|
|
250
|
-
export function setErrorHandler(handler: (error: Error) => void): void {
|
|
251
|
-
errorHandler = handler
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Clear the global error handler (useful for testing)
|
|
256
|
-
*/
|
|
257
|
-
export function clearErrorHandler(): void {
|
|
258
|
-
errorHandler = null
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Handle an error
|
|
263
|
-
*
|
|
264
|
-
* @param error - Error to handle
|
|
265
|
-
* @param options - Handling options
|
|
266
|
-
* @param options.throwError - Whether to re-throw the error after handling
|
|
267
|
-
* @param options.logError - Whether to log the error to console
|
|
268
|
-
*
|
|
269
|
-
* @example
|
|
270
|
-
* ```ts
|
|
271
|
-
* try {
|
|
272
|
-
* // Component code
|
|
273
|
-
* }
|
|
274
|
-
catch (error) {
|
|
275
|
-
* handleError(error, { throwError: false })
|
|
276
|
-
* }
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
export function handleError(
|
|
280
|
-
error: Error,
|
|
281
|
-
options: {
|
|
282
|
-
/** Whether to re-throw the error after handling */
|
|
283
|
-
throwError?: boolean
|
|
284
|
-
/** Whether to log the error to console */
|
|
285
|
-
logError?: boolean
|
|
286
|
-
} = {},
|
|
287
|
-
): void {
|
|
288
|
-
const { throwError = true, logError = true } = options
|
|
289
|
-
|
|
290
|
-
// Log to console in development
|
|
291
|
-
if (logError && process.env.NODE_ENV !== 'production') {
|
|
292
|
-
console.error('Component Error:', error)
|
|
293
|
-
|
|
294
|
-
if (error instanceof ComponentError) {
|
|
295
|
-
console.group('Error Details')
|
|
296
|
-
// eslint-disable-next-line no-console
|
|
297
|
-
console.log('Component:', error.component)
|
|
298
|
-
// eslint-disable-next-line no-console
|
|
299
|
-
console.log('Context:', error.context)
|
|
300
|
-
console.groupEnd()
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Call global error handler if set
|
|
305
|
-
if (errorHandler) {
|
|
306
|
-
try {
|
|
307
|
-
errorHandler(error)
|
|
308
|
-
}
|
|
309
|
-
catch (handlerError) {
|
|
310
|
-
console.error('Error in error handler:', handlerError)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Re-throw if requested
|
|
315
|
-
if (throwError) {
|
|
316
|
-
throw error
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Create a wrapped function that catches and handles errors
|
|
322
|
-
*
|
|
323
|
-
* @param fn - Function to wrap
|
|
324
|
-
* @param component - Component name for error context
|
|
325
|
-
* @param eventName - Event name for error context
|
|
326
|
-
* @returns Wrapped function
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* ```ts
|
|
330
|
-
* const safeHandler = wrapErrorHandler(
|
|
331
|
-
* () => { /* ... * / },
|
|
332
|
-
* 'Button',
|
|
333
|
-
* 'onClick'
|
|
334
|
-
* )
|
|
335
|
-
* ```
|
|
336
|
-
*/
|
|
337
|
-
export function wrapErrorHandler<T extends (...args: any[]) => any>(
|
|
338
|
-
fn: T,
|
|
339
|
-
component: string,
|
|
340
|
-
eventName: string,
|
|
341
|
-
): T {
|
|
342
|
-
return ((...args: any[]) => {
|
|
343
|
-
try {
|
|
344
|
-
const result = fn(...args)
|
|
345
|
-
|
|
346
|
-
// Handle async functions
|
|
347
|
-
if (result instanceof Promise) {
|
|
348
|
-
return result.catch((error) => {
|
|
349
|
-
const wrappedError = ErrorTypes.eventHandler(component, eventName, error)
|
|
350
|
-
handleError(wrappedError, { throwError: false })
|
|
351
|
-
throw wrappedError
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return result
|
|
356
|
-
}
|
|
357
|
-
catch (error) {
|
|
358
|
-
const wrappedError = ErrorTypes.eventHandler(component, eventName, error as Error)
|
|
359
|
-
handleError(wrappedError, { throwError: false })
|
|
360
|
-
throw wrappedError
|
|
361
|
-
}
|
|
362
|
-
}) as T
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Assert a condition with a custom error
|
|
367
|
-
*
|
|
368
|
-
* @param condition - Condition to check
|
|
369
|
-
* @param error - Error to throw if condition is false
|
|
370
|
-
*
|
|
371
|
-
* @example
|
|
372
|
-
* ```ts
|
|
373
|
-
* assert(
|
|
374
|
-
* props.variant !== undefined,
|
|
375
|
-
* ErrorTypes.requiredProp('Button', 'variant')
|
|
376
|
-
* )
|
|
377
|
-
* ```
|
|
378
|
-
*/
|
|
379
|
-
export function assert(condition: any, error: Error): asserts condition {
|
|
380
|
-
if (!condition) {
|
|
381
|
-
throw error
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Warning message handler
|
|
387
|
-
*/
|
|
388
|
-
export function warn(component: string, message: string, context: Partial<ErrorContext> = {}): void {
|
|
389
|
-
if (process.env.NODE_ENV === 'production') {
|
|
390
|
-
return
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const fullMessage = ComponentError.formatMessage(component, message, context as ErrorContext)
|
|
394
|
-
|
|
395
|
-
console.warn(`⚠️ ${fullMessage}`)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Development-only assertion
|
|
400
|
-
*/
|
|
401
|
-
export function devAssert(condition: any, error: Error): asserts condition {
|
|
402
|
-
if (process.env.NODE_ENV === 'production') {
|
|
403
|
-
return
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
assert(condition, error)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Create a debug logger for a component
|
|
411
|
-
*
|
|
412
|
-
* @param component - Component name
|
|
413
|
-
* @returns Debug logger functions
|
|
414
|
-
*
|
|
415
|
-
* @example
|
|
416
|
-
* ```ts
|
|
417
|
-
* const debug = createDebugger('Button')
|
|
418
|
-
* debug.log('Button rendered')
|
|
419
|
-
* debug.error('Invalid prop')
|
|
420
|
-
* ```
|
|
421
|
-
*/
|
|
422
|
-
export function createDebugger(component: string): {
|
|
423
|
-
log: (...args: any[]) => void
|
|
424
|
-
warn: (message: string, context?: Partial<ErrorContext>) => void
|
|
425
|
-
error: (message: string, context?: ErrorContext) => void
|
|
426
|
-
} {
|
|
427
|
-
const isDebug = typeof process !== 'undefined' && process.env.DEBUG === 'true'
|
|
428
|
-
|
|
429
|
-
return {
|
|
430
|
-
log(...args: any[]): void {
|
|
431
|
-
if (isDebug) {
|
|
432
|
-
console.log(`[${component}]`, ...args)
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
warn(message: string, context?: Partial<ErrorContext>): void {
|
|
436
|
-
warn(component, message, context)
|
|
437
|
-
},
|
|
438
|
-
error(message: string, context?: ErrorContext): void {
|
|
439
|
-
const error = new ComponentError(component, message, context)
|
|
440
|
-
handleError(error, { throwError: false })
|
|
441
|
-
},
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Error boundary helper for catching render errors
|
|
447
|
-
*
|
|
448
|
-
* @param component - Component name
|
|
449
|
-
* @param renderFn - Render function
|
|
450
|
-
* @param fallback - Fallback content on error
|
|
451
|
-
* @returns Rendered content or fallback
|
|
452
|
-
*
|
|
453
|
-
* @example
|
|
454
|
-
* ```ts
|
|
455
|
-
* const content = errorBoundary(
|
|
456
|
-
* 'MyComponent',
|
|
457
|
-
* () => renderComponent(),
|
|
458
|
-
* '<div>Error loading component</div>'
|
|
459
|
-
* )
|
|
460
|
-
* ```
|
|
461
|
-
*/
|
|
462
|
-
export function errorBoundary<T>(
|
|
463
|
-
component: string,
|
|
464
|
-
renderFn: () => T,
|
|
465
|
-
fallback: T,
|
|
466
|
-
): T {
|
|
467
|
-
try {
|
|
468
|
-
return renderFn()
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
const wrappedError = ErrorTypes.render(component, error as Error)
|
|
472
|
-
handleError(wrappedError, { throwError: false })
|
|
473
|
-
return fallback
|
|
474
|
-
}
|
|
475
|
-
}
|