meno-core 1.0.0

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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Route Loader
3
+ * Handles loading of pages, components, and manages race conditions
4
+ */
5
+
6
+ import { API_ROUTES, NOT_FOUND_TIMEOUT_MS } from '../../shared/constants';
7
+ import type { ComponentRegistry } from '../componentRegistry';
8
+ import type { ComponentNode, PageData, I18nConfig } from '../../shared/types';
9
+ import { validatePageData } from '../../shared/validation/validators';
10
+ import { parseLocaleFromPath, DEFAULT_I18N_CONFIG } from '../../shared/i18n';
11
+
12
+ export interface RouteLoaderConfig {
13
+ componentRegistry: ComponentRegistry;
14
+ onLoadStart?: () => void;
15
+ onLoadComplete?: (tree: ComponentNode | ComponentNode[] | null) => void;
16
+ onLoadError?: (error: Error, errorTree?: ComponentNode) => void;
17
+ onNotFound?: () => void;
18
+ onPagesLoaded?: (pages: string[]) => void;
19
+ onLocaleDetected?: (locale: string) => void;
20
+ }
21
+
22
+ export class RouteLoader {
23
+ private abortController: AbortController | null = null;
24
+ private notFoundTimeout: NodeJS.Timeout | null = null;
25
+ private config: RouteLoaderConfig;
26
+ private isCancelled: boolean = false;
27
+ private i18nConfig: I18nConfig = DEFAULT_I18N_CONFIG;
28
+
29
+ constructor(config: RouteLoaderConfig) {
30
+ this.config = config;
31
+ }
32
+
33
+ /**
34
+ * Update i18n configuration
35
+ */
36
+ setI18nConfig(config: I18nConfig): void {
37
+ this.i18nConfig = config;
38
+ }
39
+
40
+ /**
41
+ * Load global components (shared across all pages)
42
+ * @param signal Optional abort signal to cancel the request
43
+ */
44
+ async loadGlobalComponents(signal?: AbortSignal): Promise<void> {
45
+ try {
46
+ const fetchOptions: RequestInit = { cache: 'no-store' };
47
+ if (signal) {
48
+ fetchOptions.signal = signal;
49
+ }
50
+
51
+ const response = await fetch(API_ROUTES.COMPONENTS, fetchOptions);
52
+
53
+ // Check if aborted after fetch
54
+ if (signal?.aborted) {
55
+ return;
56
+ }
57
+
58
+ const globalComps = await response.json();
59
+
60
+ // Check again if aborted after JSON parse
61
+ if (signal?.aborted) {
62
+ return;
63
+ }
64
+
65
+ if (Object.keys(globalComps).length > 0) {
66
+ // Merge global components into registry (page-specific components will override)
67
+ this.config.componentRegistry.merge(globalComps);
68
+ // JavaScript will be executed via useEffect after componentTree renders
69
+ }
70
+ } catch (error) {
71
+ // Ignore abort errors
72
+ if (error instanceof Error && error.name === 'AbortError') {
73
+ return;
74
+ }
75
+ // Continue anyway - components might still work with cached/previous registry state
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Load available pages list
81
+ */
82
+ async loadPages(): Promise<string[]> {
83
+ try {
84
+ const response = await fetch(API_ROUTES.PAGES);
85
+ const data = await response.json();
86
+ const pages = data.pages || [];
87
+
88
+ if (this.config.onPagesLoaded) {
89
+ this.config.onPagesLoaded(pages);
90
+ }
91
+
92
+ return pages;
93
+ } catch (error) {
94
+ return [];
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Load JSON components for current page
100
+ */
101
+ async loadComponents(path: string): Promise<ComponentNode | ComponentNode[] | null> {
102
+ // Cancel any previous loading request
103
+ if (this.abortController) {
104
+ this.abortController.abort();
105
+ }
106
+
107
+ // Clear any pending "not found" timeout
108
+ this.clearNotFoundTimeout();
109
+
110
+ // Reset cancelled flag
111
+ this.isCancelled = false;
112
+
113
+ // Create new abort controller for this request
114
+ const abortController = new AbortController();
115
+ this.abortController = abortController;
116
+
117
+ try {
118
+ // Set loading state and reset showNotFound
119
+ if (this.config.onLoadStart && !this.isCancelled) {
120
+ this.config.onLoadStart();
121
+ }
122
+
123
+ // Reload global components first (with abort signal)
124
+ await this.loadGlobalComponents(abortController.signal);
125
+
126
+ // Check if request was aborted
127
+ if (abortController.signal.aborted || this.isCancelled) {
128
+ return null;
129
+ }
130
+
131
+ // Parse locale from path (e.g., /pl/about -> locale: 'pl', path: '/about')
132
+ const { locale, pathWithoutLocale } = parseLocaleFromPath(path, this.i18nConfig);
133
+
134
+ // Notify about detected locale
135
+ if (this.config.onLocaleDetected && !this.isCancelled) {
136
+ this.config.onLocaleDetected(locale);
137
+ }
138
+
139
+ const response = await fetch(`${API_ROUTES.YAML}?page=${encodeURIComponent(pathWithoutLocale)}`, {
140
+ cache: 'no-store',
141
+ signal: abortController.signal,
142
+ });
143
+
144
+ // Check again if aborted (after fetch completes)
145
+ if (abortController.signal.aborted || this.isCancelled) {
146
+ return null;
147
+ }
148
+
149
+ if (!response.ok) {
150
+
151
+ if (this.config.onLoadComplete && !this.isCancelled) {
152
+ this.config.onLoadComplete(null);
153
+ }
154
+
155
+ // Delay showing "not found" to prevent flash
156
+ // Only set timeout if not cancelled
157
+ if (!this.isCancelled) {
158
+ this.notFoundTimeout = setTimeout(() => {
159
+ // Check again before calling callback (component might have unmounted)
160
+ if (!this.isCancelled && this.config.onNotFound) {
161
+ this.config.onNotFound();
162
+ }
163
+ this.notFoundTimeout = null;
164
+ }, NOT_FOUND_TIMEOUT_MS);
165
+ }
166
+
167
+ return null;
168
+ }
169
+
170
+ const jsonText = await response.text();
171
+
172
+ // Check if aborted after text extraction
173
+ if (abortController.signal.aborted || this.isCancelled) {
174
+ return null;
175
+ }
176
+
177
+ let parsed;
178
+ try {
179
+ parsed = JSON.parse(jsonText);
180
+
181
+ // Validate page data (logs warnings but doesn't fail - graceful degradation)
182
+ const validationResult = validatePageData(parsed);
183
+ if (!validationResult.valid) {
184
+ console.warn(`[RouteLoader] Page validation failed for ${path}:`,
185
+ validationResult.errors.map(e => `${e.path}: ${e.message}`).join('; '));
186
+ }
187
+ // Use validated data if available, otherwise use parsed (graceful degradation)
188
+ parsed = validationResult.valid ? validationResult.data : parsed;
189
+ } catch (parseError) {
190
+ // Set an error tree to trigger error boundary
191
+ const errorTree = this.createErrorTree(
192
+ 'Invalid JSON format',
193
+ `Failed to parse JSON for page: ${path}`,
194
+ String(parseError)
195
+ );
196
+
197
+ if (this.config.onLoadComplete && !this.isCancelled) {
198
+ this.config.onLoadComplete(errorTree);
199
+ }
200
+
201
+ if (this.config.onLoadError && !this.isCancelled) {
202
+ this.config.onLoadError(
203
+ parseError instanceof Error ? parseError : new Error(String(parseError)),
204
+ errorTree
205
+ );
206
+ }
207
+
208
+ return errorTree;
209
+ }
210
+
211
+ // Check if JSON has components section (page-specific components)
212
+ let tree: ComponentNode | ComponentNode[] | null = null;
213
+
214
+ if (parsed.components) {
215
+ // Merge page-specific components with global (page-specific override global)
216
+ try {
217
+ this.config.componentRegistry.merge(parsed.components);
218
+ // JavaScript will be executed via useEffect after componentTree renders
219
+ } catch (componentError) {
220
+ }
221
+ tree = parsed.root || parsed;
222
+ } else if (parsed.root) {
223
+ // Has root property but no components section - use only global components
224
+ tree = parsed.root;
225
+ // JavaScript will be executed via useEffect after componentTree renders
226
+ } else {
227
+ // Legacy format - direct component tree
228
+ tree = parsed;
229
+ // JavaScript will be executed via useEffect after componentTree renders
230
+ }
231
+
232
+ // Check one more time if aborted
233
+ if (abortController.signal.aborted || this.isCancelled) {
234
+ return null;
235
+ }
236
+
237
+ if (this.config.onLoadComplete && !this.isCancelled) {
238
+ this.config.onLoadComplete(tree);
239
+ }
240
+
241
+ this.clearNotFoundTimeout();
242
+ this.abortController = null;
243
+
244
+ return tree;
245
+ } catch (error) {
246
+ // Ignore abort errors
247
+ if (error instanceof Error && error.name === 'AbortError') {
248
+ return null;
249
+ }
250
+
251
+ // Check if aborted before handling error
252
+ if (abortController.signal.aborted || this.isCancelled) {
253
+ return null;
254
+ }
255
+
256
+ // Set a generic error tree
257
+ const errorTree = this.createErrorTree('Error loading page', String(error));
258
+
259
+ if (this.config.onLoadComplete && !this.isCancelled) {
260
+ this.config.onLoadComplete(errorTree);
261
+ }
262
+
263
+ if (this.config.onLoadError && !this.isCancelled) {
264
+ this.config.onLoadError(
265
+ error instanceof Error ? error : new Error(String(error)),
266
+ errorTree
267
+ );
268
+ }
269
+
270
+ this.clearNotFoundTimeout();
271
+ this.abortController = null;
272
+
273
+ return errorTree;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Cancel current loading request
279
+ */
280
+ cancel(): void {
281
+ this.isCancelled = true;
282
+ if (this.abortController) {
283
+ this.abortController.abort();
284
+ this.abortController = null;
285
+ }
286
+ this.clearNotFoundTimeout();
287
+ }
288
+
289
+ /**
290
+ * Create error tree ComponentNode structure
291
+ */
292
+ private createErrorTree(
293
+ title: string,
294
+ message: string,
295
+ details?: string
296
+ ): ComponentNode {
297
+ const children: ComponentNode[] = [
298
+ {
299
+ type: 'node',
300
+ tag: 'h2',
301
+ style: { color: '#c00' },
302
+ children: [title],
303
+ },
304
+ {
305
+ type: 'node',
306
+ tag: 'p',
307
+ style: { color: '#666', marginTop: '16px' },
308
+ children: [message],
309
+ },
310
+ ];
311
+
312
+ if (details) {
313
+ children.push({
314
+ type: 'node',
315
+ tag: 'pre',
316
+ style: {
317
+ background: '#f5f5f5',
318
+ padding: '16px',
319
+ borderRadius: '4px',
320
+ textAlign: 'left',
321
+ maxWidth: '600px',
322
+ margin: '16px auto',
323
+ overflow: 'auto',
324
+ },
325
+ children: [details],
326
+ });
327
+ }
328
+
329
+ return {
330
+ type: 'node',
331
+ tag: 'div',
332
+ style: { padding: '40px', textAlign: 'center' },
333
+ children,
334
+ };
335
+ }
336
+
337
+ /**
338
+ * Clear not found timeout
339
+ */
340
+ private clearNotFoundTimeout(): void {
341
+ if (this.notFoundTimeout) {
342
+ clearTimeout(this.notFoundTimeout);
343
+ this.notFoundTimeout = null;
344
+ }
345
+ }
346
+ }
347
+