@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,530 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lazy loading utilities for @stacksjs/components
|
|
3
|
-
*
|
|
4
|
-
* Provides component-level code splitting and lazy loading with
|
|
5
|
-
* intersection observer support for on-demand loading
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { lazyLoad, lazyLoadComponent } from '@stacksjs/components'
|
|
10
|
-
*
|
|
11
|
-
* // Lazy load a component
|
|
12
|
-
* const LazyButton = lazyLoadComponent(() => import('./Button'))
|
|
13
|
-
*
|
|
14
|
-
* // Lazy load on intersection
|
|
15
|
-
* lazyLoad(element, () => import('./heavy-component'))
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Lazy loading options
|
|
21
|
-
*/
|
|
22
|
-
export interface LazyLoadOptions {
|
|
23
|
-
/** IntersectionObserver root element */
|
|
24
|
-
root?: Element | null
|
|
25
|
-
/** Root margin for intersection observer */
|
|
26
|
-
rootMargin?: string
|
|
27
|
-
/** Threshold for intersection observer */
|
|
28
|
-
threshold?: number | number[]
|
|
29
|
-
/** Placeholder content while loading */
|
|
30
|
-
placeholder?: string | HTMLElement
|
|
31
|
-
/** Error content if loading fails */
|
|
32
|
-
errorContent?: string | HTMLElement
|
|
33
|
-
/** Callback when component starts loading */
|
|
34
|
-
onLoading?: () => void
|
|
35
|
-
/** Callback when component loads successfully */
|
|
36
|
-
onLoaded?: (module: any) => void
|
|
37
|
-
/** Callback when loading fails */
|
|
38
|
-
onError?: (error: Error) => void
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Component lazy load options
|
|
43
|
-
*/
|
|
44
|
-
export interface ComponentLazyLoadOptions extends LazyLoadOptions {
|
|
45
|
-
/** Whether to preload the component on hover */
|
|
46
|
-
preloadOnHover?: boolean
|
|
47
|
-
/** Retry attempts on failure */
|
|
48
|
-
retryAttempts?: number
|
|
49
|
-
/** Retry delay in milliseconds */
|
|
50
|
-
retryDelay?: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Lazy load state
|
|
55
|
-
*/
|
|
56
|
-
export type LazyLoadState = 'idle' | 'loading' | 'loaded' | 'error'
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Lazy loaded component wrapper
|
|
60
|
-
*/
|
|
61
|
-
export interface LazyComponent<T = any> {
|
|
62
|
-
/** Current load state */
|
|
63
|
-
state: LazyLoadState
|
|
64
|
-
/** Loaded module (if loaded) */
|
|
65
|
-
module?: T
|
|
66
|
-
/** Loading error (if failed) */
|
|
67
|
-
error?: Error
|
|
68
|
-
/** Load the component */
|
|
69
|
-
load: () => Promise<T>
|
|
70
|
-
/** Preload the component without rendering */
|
|
71
|
-
preload: () => Promise<T>
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Cache for lazy loaded modules
|
|
76
|
-
*/
|
|
77
|
-
const moduleCache = new Map<string, Promise<any>>()
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Lazy load a module with caching
|
|
81
|
-
*
|
|
82
|
-
* @param importFn - Dynamic import function
|
|
83
|
-
* @param cacheKey - Optional cache key (defaults to function string)
|
|
84
|
-
* @returns Promise of module
|
|
85
|
-
*/
|
|
86
|
-
export async function lazyLoadModule<T = any>(
|
|
87
|
-
importFn: () => Promise<T>,
|
|
88
|
-
cacheKey?: string,
|
|
89
|
-
): Promise<T> {
|
|
90
|
-
const key = cacheKey || importFn.toString()
|
|
91
|
-
|
|
92
|
-
if (moduleCache.has(key)) {
|
|
93
|
-
return moduleCache.get(key)!
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const promise = importFn()
|
|
97
|
-
moduleCache.set(key, promise)
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
return await promise
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
// Remove from cache on error to allow retry
|
|
104
|
-
moduleCache.delete(key)
|
|
105
|
-
throw error
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Create a lazy loaded component
|
|
111
|
-
*
|
|
112
|
-
* @param importFn - Dynamic import function
|
|
113
|
-
* @param options - Lazy load options
|
|
114
|
-
* @returns Lazy component wrapper
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* const LazyButton = lazyLoadComponent(
|
|
119
|
-
* () => import('./components/Button'),
|
|
120
|
-
* { preloadOnHover: true }
|
|
121
|
-
* )
|
|
122
|
-
*
|
|
123
|
-
* // Later, load and use
|
|
124
|
-
* const button = await LazyButton.load()
|
|
125
|
-
* ```
|
|
126
|
-
*/
|
|
127
|
-
export function lazyLoadComponent<T = any>(
|
|
128
|
-
importFn: () => Promise<T>,
|
|
129
|
-
options: ComponentLazyLoadOptions = {},
|
|
130
|
-
): LazyComponent<T> {
|
|
131
|
-
const {
|
|
132
|
-
retryAttempts = 3,
|
|
133
|
-
retryDelay = 1000,
|
|
134
|
-
onLoading,
|
|
135
|
-
onLoaded,
|
|
136
|
-
onError,
|
|
137
|
-
} = options
|
|
138
|
-
|
|
139
|
-
let state: LazyLoadState = 'idle'
|
|
140
|
-
let module: T | undefined
|
|
141
|
-
let error: Error | undefined
|
|
142
|
-
|
|
143
|
-
const loadWithRetry = async (attempt = 0): Promise<T> => {
|
|
144
|
-
try {
|
|
145
|
-
if (state === 'loading' || state === 'loaded') {
|
|
146
|
-
if (module) {
|
|
147
|
-
return module
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
state = 'loading'
|
|
152
|
-
onLoading?.()
|
|
153
|
-
|
|
154
|
-
const loaded = await lazyLoadModule(importFn)
|
|
155
|
-
module = loaded
|
|
156
|
-
state = 'loaded'
|
|
157
|
-
onLoaded?.(loaded)
|
|
158
|
-
|
|
159
|
-
return loaded
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
const loadError = err as Error
|
|
163
|
-
|
|
164
|
-
if (attempt < retryAttempts) {
|
|
165
|
-
// Retry after delay
|
|
166
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1)))
|
|
167
|
-
return loadWithRetry(attempt + 1)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
state = 'error'
|
|
171
|
-
error = loadError
|
|
172
|
-
onError?.(loadError)
|
|
173
|
-
throw loadError
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
get state() {
|
|
179
|
-
return state
|
|
180
|
-
},
|
|
181
|
-
get module() {
|
|
182
|
-
return module
|
|
183
|
-
},
|
|
184
|
-
get error() {
|
|
185
|
-
return error
|
|
186
|
-
},
|
|
187
|
-
load: () => loadWithRetry(),
|
|
188
|
-
preload: () => loadWithRetry(),
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Lazy load content when element enters viewport
|
|
194
|
-
*
|
|
195
|
-
* @param element - Target element to observe
|
|
196
|
-
* @param importFn - Dynamic import function
|
|
197
|
-
* @param options - Lazy load options
|
|
198
|
-
* @returns Cleanup function
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* ```ts
|
|
202
|
-
* const cleanup = lazyLoad(
|
|
203
|
-
* document.getElementById('heavy-component'),
|
|
204
|
-
* () => import('./HeavyComponent'),
|
|
205
|
-
* {
|
|
206
|
-
* rootMargin: '50px',
|
|
207
|
-
* placeholder: '<div>Loading...</div>'
|
|
208
|
-
* }
|
|
209
|
-
* )
|
|
210
|
-
* ```
|
|
211
|
-
*/
|
|
212
|
-
export function lazyLoad<T = any>(
|
|
213
|
-
element: Element | null,
|
|
214
|
-
importFn: () => Promise<T>,
|
|
215
|
-
options: LazyLoadOptions = {},
|
|
216
|
-
): () => void {
|
|
217
|
-
if (!element) {
|
|
218
|
-
console.warn('lazyLoad: element is null')
|
|
219
|
-
return () => {}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const {
|
|
223
|
-
root = null,
|
|
224
|
-
rootMargin = '0px',
|
|
225
|
-
threshold = 0.01,
|
|
226
|
-
placeholder,
|
|
227
|
-
errorContent,
|
|
228
|
-
onLoading,
|
|
229
|
-
onLoaded,
|
|
230
|
-
onError,
|
|
231
|
-
} = options
|
|
232
|
-
|
|
233
|
-
let hasLoaded = false
|
|
234
|
-
let observer: IntersectionObserver | null = null
|
|
235
|
-
|
|
236
|
-
// Set placeholder
|
|
237
|
-
if (placeholder) {
|
|
238
|
-
if (typeof placeholder === 'string') {
|
|
239
|
-
element.innerHTML = placeholder
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
element.innerHTML = ''
|
|
243
|
-
element.appendChild(placeholder)
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const handleIntersection: IntersectionObserverCallback = async (entries) => {
|
|
248
|
-
for (const entry of entries) {
|
|
249
|
-
if (entry.isIntersecting && !hasLoaded) {
|
|
250
|
-
hasLoaded = true
|
|
251
|
-
observer?.disconnect()
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
onLoading?.()
|
|
255
|
-
|
|
256
|
-
const module = await lazyLoadModule(importFn)
|
|
257
|
-
|
|
258
|
-
onLoaded?.(module)
|
|
259
|
-
|
|
260
|
-
// If module has a default export with render method
|
|
261
|
-
if (module && typeof (module as any).default?.render === 'function') {
|
|
262
|
-
const content = await (module as any).default.render()
|
|
263
|
-
element.innerHTML = content
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
const loadError = error as Error
|
|
268
|
-
onError?.(loadError)
|
|
269
|
-
|
|
270
|
-
// Set error content
|
|
271
|
-
if (errorContent) {
|
|
272
|
-
if (typeof errorContent === 'string') {
|
|
273
|
-
element.innerHTML = errorContent
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
element.innerHTML = ''
|
|
277
|
-
element.appendChild(errorContent)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
element.innerHTML = `<div style="color: red;">Failed to load component: ${loadError.message}</div>`
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Create intersection observer
|
|
289
|
-
observer = new IntersectionObserver(handleIntersection, {
|
|
290
|
-
root,
|
|
291
|
-
rootMargin,
|
|
292
|
-
threshold,
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
observer.observe(element)
|
|
296
|
-
|
|
297
|
-
// Return cleanup function
|
|
298
|
-
return () => {
|
|
299
|
-
observer?.disconnect()
|
|
300
|
-
observer = null
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Lazy load multiple elements
|
|
306
|
-
*
|
|
307
|
-
* @param elements - Array of elements or NodeList
|
|
308
|
-
* @param importFn - Dynamic import function or function that takes element
|
|
309
|
-
* @param options - Lazy load options
|
|
310
|
-
* @returns Cleanup function
|
|
311
|
-
*/
|
|
312
|
-
export function lazyLoadAll<T = any>(
|
|
313
|
-
elements: Element[] | NodeListOf<Element>,
|
|
314
|
-
importFn: ((_element: Element) => Promise<T>) | (() => Promise<T>),
|
|
315
|
-
options: LazyLoadOptions = {},
|
|
316
|
-
): () => void {
|
|
317
|
-
const cleanupFunctions: Array<() => void> = []
|
|
318
|
-
|
|
319
|
-
const elementsArray = Array.from(elements)
|
|
320
|
-
|
|
321
|
-
for (const element of elementsArray) {
|
|
322
|
-
const importFunction = typeof importFn === 'function' && importFn.length > 0
|
|
323
|
-
? () => (importFn as (element: Element) => Promise<T>)(element)
|
|
324
|
-
: (importFn as () => Promise<T>)
|
|
325
|
-
|
|
326
|
-
const cleanup = lazyLoad(element, importFunction, options)
|
|
327
|
-
cleanupFunctions.push(cleanup)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Return combined cleanup function
|
|
331
|
-
return () => {
|
|
332
|
-
cleanupFunctions.forEach(cleanup => cleanup())
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Preload a module without executing it
|
|
338
|
-
*
|
|
339
|
-
* Useful for prefetching components that will likely be needed soon
|
|
340
|
-
*
|
|
341
|
-
* @param importFn - Dynamic import function
|
|
342
|
-
* @returns Promise of module
|
|
343
|
-
*/
|
|
344
|
-
export function preloadModule<T = any>(importFn: () => Promise<T>): Promise<T> {
|
|
345
|
-
return lazyLoadModule(importFn)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Preload multiple modules
|
|
350
|
-
*
|
|
351
|
-
* @param importFns - Array of dynamic import functions
|
|
352
|
-
* @returns Promise of all modules
|
|
353
|
-
*/
|
|
354
|
-
export function preloadModules(importFns: Array<() => Promise<any>>): Promise<any[]> {
|
|
355
|
-
return Promise.all(importFns.map(fn => preloadModule(fn)))
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Clear module cache
|
|
360
|
-
*
|
|
361
|
-
* Useful for testing or forcing re-imports
|
|
362
|
-
*
|
|
363
|
-
* @param cacheKey - Optional specific key to clear, or clear all if undefined
|
|
364
|
-
*/
|
|
365
|
-
export function clearModuleCache(cacheKey?: string): void {
|
|
366
|
-
if (cacheKey) {
|
|
367
|
-
moduleCache.delete(cacheKey)
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
moduleCache.clear()
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Get cache statistics
|
|
376
|
-
*
|
|
377
|
-
* @returns Cache size and keys
|
|
378
|
-
*/
|
|
379
|
-
export function getCacheStats(): { size: number, keys: string[] } {
|
|
380
|
-
return {
|
|
381
|
-
size: moduleCache.size,
|
|
382
|
-
keys: Array.from(moduleCache.keys()),
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Route-based code splitting helper
|
|
388
|
-
*
|
|
389
|
-
* Lazy loads components based on route patterns
|
|
390
|
-
*
|
|
391
|
-
* @example
|
|
392
|
-
* ```ts
|
|
393
|
-
* const routes = {
|
|
394
|
-
* '/': () => import('./pages/Home'),
|
|
395
|
-
* '/about': () => import('./pages/About'),
|
|
396
|
-
* '/blog/:id': () => import('./pages/BlogPost')
|
|
397
|
-
* }
|
|
398
|
-
*
|
|
399
|
-
* const loader = createRouteLoader(routes)
|
|
400
|
-
* const component = await loader.load('/about')
|
|
401
|
-
* ```
|
|
402
|
-
*/
|
|
403
|
-
export function createRouteLoader(routes: Record<string, () => Promise<any>>) {
|
|
404
|
-
return {
|
|
405
|
-
/**
|
|
406
|
-
* Load component for route
|
|
407
|
-
*/
|
|
408
|
-
async load(path: string): Promise<any> {
|
|
409
|
-
const importFn = routes[path]
|
|
410
|
-
if (!importFn) {
|
|
411
|
-
throw new Error(`No route found for path: ${path}`)
|
|
412
|
-
}
|
|
413
|
-
return lazyLoadModule(importFn, `route:${path}`)
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Preload component for route
|
|
418
|
-
*/
|
|
419
|
-
async preload(path: string): Promise<any> {
|
|
420
|
-
return this.load(path)
|
|
421
|
-
},
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Preload multiple routes
|
|
425
|
-
*/
|
|
426
|
-
async preloadRoutes(paths: string[]): Promise<any[]> {
|
|
427
|
-
return Promise.all(paths.map(path => this.preload(path)))
|
|
428
|
-
},
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Image lazy loading helper
|
|
434
|
-
*
|
|
435
|
-
* @param img - Image element
|
|
436
|
-
* @param src - Image source URL
|
|
437
|
-
* @param options - Lazy load options
|
|
438
|
-
* @returns Cleanup function
|
|
439
|
-
*/
|
|
440
|
-
export function lazyLoadImage(
|
|
441
|
-
img: HTMLImageElement | null,
|
|
442
|
-
src: string,
|
|
443
|
-
options: LazyLoadOptions = {},
|
|
444
|
-
): () => void {
|
|
445
|
-
if (!img) {
|
|
446
|
-
return () => {}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const {
|
|
450
|
-
root = null,
|
|
451
|
-
rootMargin = '50px',
|
|
452
|
-
threshold = 0.01,
|
|
453
|
-
placeholder,
|
|
454
|
-
onLoading,
|
|
455
|
-
onLoaded,
|
|
456
|
-
onError,
|
|
457
|
-
} = options
|
|
458
|
-
|
|
459
|
-
// Set placeholder
|
|
460
|
-
if (placeholder && typeof placeholder === 'string') {
|
|
461
|
-
img.src = placeholder
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
let hasLoaded = false
|
|
465
|
-
let observer: IntersectionObserver | null = null
|
|
466
|
-
|
|
467
|
-
const handleIntersection: IntersectionObserverCallback = (entries) => {
|
|
468
|
-
for (const entry of entries) {
|
|
469
|
-
if (entry.isIntersecting && !hasLoaded) {
|
|
470
|
-
hasLoaded = true
|
|
471
|
-
observer?.disconnect()
|
|
472
|
-
|
|
473
|
-
onLoading?.()
|
|
474
|
-
|
|
475
|
-
img.src = src
|
|
476
|
-
|
|
477
|
-
img.addEventListener('load', () => {
|
|
478
|
-
onLoaded?.(img)
|
|
479
|
-
img.classList.add('loaded')
|
|
480
|
-
}, { once: true })
|
|
481
|
-
|
|
482
|
-
img.addEventListener('error', (_event) => {
|
|
483
|
-
const error = new Error(`Failed to load image: ${src}`)
|
|
484
|
-
onError?.(error)
|
|
485
|
-
img.classList.add('error')
|
|
486
|
-
}, { once: true })
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
observer = new IntersectionObserver(handleIntersection, {
|
|
492
|
-
root,
|
|
493
|
-
rootMargin,
|
|
494
|
-
threshold,
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
observer.observe(img)
|
|
498
|
-
|
|
499
|
-
return () => {
|
|
500
|
-
observer?.disconnect()
|
|
501
|
-
observer = null
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Lazy load all images with data-lazy-src attribute
|
|
507
|
-
*
|
|
508
|
-
* @param container - Container element to search within
|
|
509
|
-
* @param options - Lazy load options
|
|
510
|
-
* @returns Cleanup function
|
|
511
|
-
*/
|
|
512
|
-
export function lazyLoadImages(
|
|
513
|
-
container: Element | Document = document,
|
|
514
|
-
options: LazyLoadOptions = {},
|
|
515
|
-
): () => void {
|
|
516
|
-
const images = container.querySelectorAll<HTMLImageElement>('img[data-lazy-src]')
|
|
517
|
-
const cleanupFunctions: Array<() => void> = []
|
|
518
|
-
|
|
519
|
-
images.forEach((img) => {
|
|
520
|
-
const src = img.getAttribute('data-lazy-src')
|
|
521
|
-
if (src) {
|
|
522
|
-
const cleanup = lazyLoadImage(img, src, options)
|
|
523
|
-
cleanupFunctions.push(cleanup)
|
|
524
|
-
}
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
return () => {
|
|
528
|
-
cleanupFunctions.forEach(cleanup => cleanup())
|
|
529
|
-
}
|
|
530
|
-
}
|