@absolutejs/absolute 0.19.0-beta.232 → 0.19.0-beta.234

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 (44) hide show
  1. package/.absolutejs/vue-tsc.tsbuildinfo +1 -1
  2. package/dist/ai/providers/openai.js +30 -21
  3. package/dist/ai/providers/openai.js.map +3 -3
  4. package/dist/ai-client/angular/ai/index.js +2 -2
  5. package/dist/ai-client/react/ai/index.js +2 -2
  6. package/dist/ai-client/vue/ai/index.js +2 -2
  7. package/dist/angular/ai/index.js +3 -3
  8. package/dist/angular/ai/index.js.map +3 -3
  9. package/dist/angular/components/image.component.js +202 -0
  10. package/dist/angular/components/index.js +1 -0
  11. package/dist/dev/client/constants.ts +25 -0
  12. package/dist/dev/client/cssUtils.ts +305 -0
  13. package/dist/dev/client/domDiff.ts +225 -0
  14. package/dist/dev/client/domState.ts +420 -0
  15. package/dist/dev/client/domTracker.ts +60 -0
  16. package/dist/dev/client/errorOverlay.ts +183 -0
  17. package/dist/dev/client/frameworkDetect.ts +62 -0
  18. package/dist/dev/client/handlers/angular.ts +551 -0
  19. package/dist/dev/client/handlers/angularRuntime.ts +205 -0
  20. package/dist/dev/client/handlers/html.ts +361 -0
  21. package/dist/dev/client/handlers/htmx.ts +274 -0
  22. package/dist/dev/client/handlers/react.ts +107 -0
  23. package/dist/dev/client/handlers/rebuild.ts +152 -0
  24. package/dist/dev/client/handlers/svelte.ts +331 -0
  25. package/dist/dev/client/handlers/vue.ts +291 -0
  26. package/dist/dev/client/headPatch.ts +232 -0
  27. package/dist/dev/client/hmrClient.ts +250 -0
  28. package/dist/dev/client/moduleVersions.ts +61 -0
  29. package/dist/dev/client/reactRefreshSetup.ts +32 -0
  30. package/dist/react/ai/index.js +3 -3
  31. package/dist/react/ai/index.js.map +3 -3
  32. package/dist/svelte/ai/index.js +3 -3
  33. package/dist/svelte/ai/index.js.map +3 -3
  34. package/dist/svelte/components/Head.svelte +147 -0
  35. package/dist/svelte/components/Head.svelte.d.ts +5 -0
  36. package/dist/svelte/components/Image.svelte +161 -0
  37. package/dist/svelte/components/Image.svelte.d.ts +5 -0
  38. package/dist/svelte/components/JsonLd.svelte +20 -0
  39. package/dist/svelte/components/JsonLd.svelte.d.ts +2 -0
  40. package/dist/vue/ai/index.js +3 -3
  41. package/dist/vue/ai/index.js.map +3 -3
  42. package/dist/vue/components/Image.vue +186 -0
  43. package/dist/vue/components/Image.vue.d.ts +4 -0
  44. package/package.json +1 -1
@@ -0,0 +1,62 @@
1
+ /* Framework detection and manifest lookup utilities */
2
+
3
+ export const detectCurrentFramework = () => {
4
+ if (window.__HMR_FRAMEWORK__) return window.__HMR_FRAMEWORK__;
5
+ if (window.__REACT_ROOT__) return 'react';
6
+ const path = window.location.pathname;
7
+ if (path === '/vue' || path.startsWith('/vue/')) return 'vue';
8
+ if (path === '/svelte' || path.startsWith('/svelte/')) return 'svelte';
9
+ if (path === '/angular' || path.startsWith('/angular/')) return 'angular';
10
+ if (path === '/htmx' || path.startsWith('/htmx/')) return 'htmx';
11
+ if (path === '/html' || path.startsWith('/html/')) return 'html';
12
+ if (path === '/react' || path.startsWith('/react/')) return 'react';
13
+
14
+ return null;
15
+ };
16
+ export const findIndexPath = (
17
+ manifest: Record<string, string> | undefined,
18
+ sourceFile: string | undefined,
19
+ framework: string
20
+ ) => {
21
+ if (!manifest) return null;
22
+
23
+ if (sourceFile) {
24
+ const componentName = getComponentNameFromPath(sourceFile);
25
+ const indexKey = componentName ? `${componentName}Index` : null;
26
+ if (indexKey && manifest[indexKey]) {
27
+ return manifest[indexKey];
28
+ }
29
+ }
30
+
31
+ const frameworkPatterns: Record<string, RegExp> = {
32
+ angular: /angular/i,
33
+ react: /react/i,
34
+ svelte: /svelte/i,
35
+ vue: /vue/i
36
+ };
37
+ const pattern = frameworkPatterns[framework];
38
+
39
+ for (const key in manifest) {
40
+ const value = manifest[key];
41
+ if (
42
+ key.endsWith('Index') &&
43
+ value &&
44
+ (!pattern || pattern.test(key) || value.includes(`/${framework}/`))
45
+ ) {
46
+ return value;
47
+ }
48
+ }
49
+
50
+ return null;
51
+ };
52
+ export const getComponentNameFromPath = (filePath: string) => {
53
+ if (!filePath) return null;
54
+ const parts = filePath.replace(/\\/g, '/').split('/');
55
+ const fileName = parts[parts.length - 1] || '';
56
+ const baseName = fileName.replace(/\.(tsx?|jsx?|vue|svelte|html)$/, '');
57
+
58
+ return baseName
59
+ .split(/[-_]/)
60
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
61
+ .join('');
62
+ };
@@ -0,0 +1,551 @@
1
+ /* Angular HMR — Re-Bootstrap with View Transitions API (Zero Flicker)
2
+ DEV MODE ONLY — never active in production.
3
+
4
+ Strategy:
5
+ 1. Capture component state (ng.getComponent) + DOM state
6
+ 2. Use document.startViewTransition() — browser captures a screenshot
7
+ 3. Destroy old app, recreate root element, import new module
8
+ 4. bootstrapApplication() renders new content (behind the screenshot)
9
+ 5. After bootstrap: restore state via ng.getComponent + ng.applyChanges
10
+ 6. View transition resolves — browser smoothly crossfades to new content
11
+
12
+ document.startViewTransition() is the native browser API for page
13
+ transitions. It captures a screenshot before the callback, runs the
14
+ callback (which can be async), and crossfades when the callback finishes.
15
+ The user never sees empty/default state — only the before and after. */
16
+
17
+ import { ANGULAR_INIT_TIMEOUT_MS } from '../constants';
18
+ import {
19
+ saveFormState,
20
+ restoreFormState,
21
+ saveScrollState,
22
+ restoreScrollState
23
+ } from '../domState';
24
+ import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
25
+
26
+ type HMRMessage = {
27
+ data: {
28
+ cssBaseName?: string;
29
+ cssUrl?: string;
30
+ html?: string;
31
+ manifest?: Record<string, string>;
32
+ pageModuleUrl?: string;
33
+ serverDuration?: number;
34
+ sourceFile?: string;
35
+ updateType?: string;
36
+ };
37
+ };
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ type NgApi = any;
41
+
42
+ const swapStylesheet = (
43
+ cssUrl: string,
44
+ cssBaseName: string,
45
+ framework: string
46
+ ) => {
47
+ let existingLink: HTMLLinkElement | null = null;
48
+ document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
49
+ const linkEl = link instanceof HTMLLinkElement ? link : null;
50
+ const href = linkEl?.getAttribute('href') ?? '';
51
+ if (href.includes(cssBaseName) || href.includes(framework)) {
52
+ existingLink = linkEl;
53
+ }
54
+ });
55
+ if (!existingLink) return;
56
+
57
+ const capturedExisting: HTMLLinkElement = existingLink;
58
+ const newLink = document.createElement('link');
59
+ newLink.rel = 'stylesheet';
60
+ newLink.href = `${cssUrl}?t=${Date.now()}`;
61
+ newLink.onload = function () {
62
+ if (capturedExisting && capturedExisting.parentNode)
63
+ capturedExisting.remove();
64
+ };
65
+ document.head.appendChild(newLink);
66
+ };
67
+
68
+ // ─── State Capture/Restore via ng.getComponent ──────────────
69
+
70
+ type StateSnapshot = {
71
+ selector: string;
72
+ index: number;
73
+ properties: Record<string, unknown>;
74
+ };
75
+
76
+ const readDomCounter = (
77
+ element: Element,
78
+ properties: Record<string, unknown>
79
+ ) => {
80
+ element
81
+ .querySelectorAll('[class*="value"], [class*="count"]')
82
+ .forEach((stateEl) => {
83
+ const text = stateEl.textContent;
84
+ if (text === null || text.trim() === '') return;
85
+ const num = parseInt(text.trim(), 10);
86
+ if (!isNaN(num)) properties['__dom_counter'] = num;
87
+ });
88
+ };
89
+
90
+ const copyInstanceProperty = (
91
+ instance: Record<string, unknown>,
92
+ key: string,
93
+ properties: Record<string, unknown>
94
+ ) => {
95
+ if (key.startsWith('ɵ') || key.startsWith('__')) return;
96
+ const val = instance[key];
97
+ if (typeof val === 'function') return;
98
+ properties[key] = val;
99
+ };
100
+
101
+ const captureInstanceProperties = (
102
+ ngApi: NgApi,
103
+ element: Element,
104
+ properties: Record<string, unknown>
105
+ ) => {
106
+ if (!ngApi || typeof ngApi.getComponent !== 'function') return;
107
+
108
+ try {
109
+ const instance = ngApi.getComponent(element);
110
+ if (!instance) return;
111
+
112
+ const record: Record<string, unknown> = instance;
113
+ Object.keys(record).forEach((key) => {
114
+ copyInstanceProperty(record, key, properties);
115
+ });
116
+ } catch {
117
+ /* ignored */
118
+ }
119
+ };
120
+
121
+ const captureComponentState = () => {
122
+ const snapshots: StateSnapshot[] = [];
123
+ const selectorCounts = new Map<string, number>();
124
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
125
+ const ngApi: NgApi = (window as any).ng;
126
+
127
+ document.querySelectorAll('*').forEach((elem) => {
128
+ const tagName = elem.tagName.toLowerCase();
129
+ if (!tagName.includes('-')) return;
130
+
131
+ const count = selectorCounts.get(tagName) || 0;
132
+ selectorCounts.set(tagName, count + 1);
133
+
134
+ const properties: Record<string, unknown> = {};
135
+ readDomCounter(elem, properties);
136
+ captureInstanceProperties(ngApi, elem, properties);
137
+
138
+ if (Object.keys(properties).length > 0) {
139
+ snapshots.push({ index: count, properties, selector: tagName });
140
+ }
141
+ });
142
+
143
+ return snapshots;
144
+ };
145
+
146
+ const safeSetProperty = (
147
+ instance: Record<string, unknown>,
148
+ key: string,
149
+ value: unknown
150
+ ) => {
151
+ try {
152
+ instance[key] = value;
153
+ } catch {
154
+ /* ignored */
155
+ }
156
+ };
157
+
158
+ const restoreInstanceProperties = (
159
+ instance: Record<string, unknown>,
160
+ snap: StateSnapshot
161
+ ) => {
162
+ const domCounter = snap.properties['__dom_counter'];
163
+ Object.entries(snap.properties).forEach(([key, value]) => {
164
+ if (key === '__dom_counter') return;
165
+ safeSetProperty(instance, key, value);
166
+ });
167
+ if (
168
+ domCounter !== undefined &&
169
+ typeof domCounter === 'number' &&
170
+ 'count' in instance
171
+ ) {
172
+ instance['count'] = domCounter;
173
+ }
174
+ };
175
+
176
+ const restoreViaInstance = (
177
+ ngApi: NgApi,
178
+ element: Element,
179
+ snap: StateSnapshot
180
+ ) => {
181
+ if (!ngApi || typeof ngApi.getComponent !== 'function') return false;
182
+
183
+ try {
184
+ const instance = ngApi.getComponent(element);
185
+ if (!instance) return false;
186
+
187
+ const record: Record<string, unknown> = instance;
188
+ restoreInstanceProperties(record, snap);
189
+ if (typeof ngApi.applyChanges === 'function')
190
+ ngApi.applyChanges(element);
191
+
192
+ return true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ };
197
+
198
+ const restoreDomFallback = (element: Element, snap: StateSnapshot) => {
199
+ const domCounter = snap.properties['__dom_counter'];
200
+ if (domCounter === undefined) return;
201
+
202
+ element
203
+ .querySelectorAll('[class*="value"], [class*="count"]')
204
+ .forEach((counterEl) => {
205
+ counterEl.textContent = String(domCounter);
206
+ });
207
+ };
208
+
209
+ const restoreComponentState = (snapshots: StateSnapshot[]) => {
210
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
211
+ const ngApi: NgApi = (window as any).ng;
212
+ if (snapshots.length === 0) return;
213
+
214
+ const bySelector = new Map<string, StateSnapshot[]>();
215
+ for (const snap of snapshots) {
216
+ const list = bySelector.get(snap.selector) || [];
217
+ list.push(snap);
218
+ bySelector.set(snap.selector, list);
219
+ }
220
+
221
+ bySelector.forEach((snaps, selector) => {
222
+ const elements = document.querySelectorAll(selector);
223
+ snaps.forEach((snap) => {
224
+ const element = elements[snap.index];
225
+ if (!element) return;
226
+
227
+ const restored = restoreViaInstance(ngApi, element, snap);
228
+ if (!restored) restoreDomFallback(element, snap);
229
+ });
230
+ });
231
+ };
232
+
233
+ // ─── Wait for Angular bootstrap (event-based, no polling) ───
234
+ // Installs a property setter trap on window.__ANGULAR_APP__ that
235
+ // resolves the promise the instant the bootstrap code writes to it.
236
+ // Falls back to a short timeout in case the setter is bypassed.
237
+
238
+ const waitForAngularApp = () => {
239
+ if (window.__ANGULAR_APP__) return Promise.resolve();
240
+
241
+ // eslint-disable-next-line promise/avoid-new
242
+ return new Promise<void>((resolve) => {
243
+ const timeout = setTimeout(resolve, ANGULAR_INIT_TIMEOUT_MS);
244
+
245
+ let stored = window.__ANGULAR_APP__;
246
+
247
+ Object.defineProperty(window, '__ANGULAR_APP__', {
248
+ configurable: true,
249
+ enumerable: true,
250
+ get() {
251
+ return stored;
252
+ },
253
+ set(val) {
254
+ stored = val;
255
+ Object.defineProperty(window, '__ANGULAR_APP__', {
256
+ configurable: true,
257
+ enumerable: true,
258
+ value: val,
259
+ writable: true
260
+ });
261
+ clearTimeout(timeout);
262
+ resolve();
263
+ }
264
+ });
265
+ });
266
+ };
267
+
268
+ // ============================================================
269
+ // FAST UPDATE — Runtime patching without destroy/re-bootstrap
270
+ // ============================================================
271
+
272
+ const suppressNg0912 = () => {
273
+ const origWarn = console.warn;
274
+ console.warn = function (...args: unknown[]) {
275
+ if (typeof args[0] === 'string' && args[0].includes('NG0912')) return;
276
+ origWarn.apply(console, args);
277
+ };
278
+
279
+ return origWarn;
280
+ };
281
+
282
+ const tryPatchExport = (
283
+ exportName: string,
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ newModule: Record<string, any>,
286
+ registry: Map<string, unknown>,
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ hmr: any,
289
+ sourceFile: string
290
+ ) => {
291
+ const exported = newModule[exportName];
292
+ if (typeof exported !== 'function' || !exported.ɵcmp) return 'skip';
293
+
294
+ const registryId = `${sourceFile}#${exportName}`;
295
+ if (!registry.has(registryId)) return 'skip';
296
+
297
+ const success = hmr.applyUpdate(registryId, exported);
298
+ if (!success) return 'fail';
299
+
300
+ return 'patched';
301
+ };
302
+
303
+ const patchRegisteredComponents = (
304
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
305
+ newModule: Record<string, any>,
306
+ registry: Map<string, unknown>,
307
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
308
+ hmr: any,
309
+ sourceFile: string
310
+ ) => {
311
+ let patchedAny = false;
312
+ const allPatched = Object.keys(newModule).every((exportName) => {
313
+ const result = tryPatchExport(
314
+ exportName,
315
+ newModule,
316
+ registry,
317
+ hmr,
318
+ sourceFile
319
+ );
320
+ if (result === 'skip') {
321
+ return true;
322
+ }
323
+ if (result === 'fail') {
324
+ return false;
325
+ }
326
+ patchedAny = true;
327
+
328
+ return true;
329
+ });
330
+
331
+ return { allPatched, patchedAny };
332
+ };
333
+
334
+ const attemptFastPatch = async (
335
+ indexPath: string,
336
+ registry: Map<string, unknown>,
337
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
+ hmr: any,
339
+ sourceFile: string,
340
+ origWarn: typeof console.warn
341
+ ) => {
342
+ try {
343
+ const newModule = await import(`${indexPath}?t=${Date.now()}`);
344
+
345
+ console.warn = origWarn;
346
+
347
+ const { allPatched, patchedAny } = patchRegisteredComponents(
348
+ newModule,
349
+ registry,
350
+ hmr,
351
+ sourceFile
352
+ );
353
+
354
+ if (!patchedAny) return false;
355
+ if (!allPatched) return false;
356
+
357
+ hmr.refresh();
358
+
359
+ return true;
360
+ } catch (err) {
361
+ console.warn = origWarn;
362
+ console.warn('[HMR] Angular fast update failed, falling back:', err);
363
+
364
+ return false;
365
+ }
366
+ };
367
+
368
+ // handleFastUpdate is kept for future use when the fast path is re-enabled.
369
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
370
+ const _handleFastUpdate = async (message: HMRMessage) => {
371
+ const hmr = window.__ANGULAR_HMR__;
372
+ if (!hmr || !hmr.getRegistry) return false;
373
+
374
+ const registry = hmr.getRegistry();
375
+ if (registry.size === 0) return false;
376
+
377
+ const indexPath = findIndexPath(
378
+ message.data.manifest,
379
+ message.data.sourceFile,
380
+ 'angular'
381
+ );
382
+ if (!indexPath) return false;
383
+
384
+ const origWarn = suppressNg0912();
385
+
386
+ const patched = await attemptFastPatch(
387
+ indexPath,
388
+ registry,
389
+ hmr,
390
+ message.data.sourceFile || '',
391
+ origWarn
392
+ );
393
+
394
+ if (patched && message.data.cssUrl) {
395
+ swapStylesheet(
396
+ message.data.cssUrl,
397
+ message.data.cssBaseName || '',
398
+ 'angular'
399
+ );
400
+ }
401
+
402
+ return patched;
403
+ };
404
+
405
+ // ============================================================
406
+ // MAIN ENTRY POINT
407
+ // ============================================================
408
+
409
+ export const handleAngularUpdate = (message: HMRMessage) => {
410
+ if (detectCurrentFramework() !== 'angular') return;
411
+
412
+ const updateType = message.data.updateType || 'logic';
413
+
414
+ if (
415
+ (updateType === 'style' || updateType === 'css-only') &&
416
+ message.data.cssUrl
417
+ ) {
418
+ swapStylesheet(
419
+ message.data.cssUrl,
420
+ message.data.cssBaseName || '',
421
+ 'angular'
422
+ );
423
+
424
+ return;
425
+ }
426
+
427
+ handleFullUpdate(message);
428
+ };
429
+
430
+ // ============================================================
431
+ // RE-BOOTSTRAP WITH VIEW TRANSITIONS API
432
+ // ============================================================
433
+
434
+ const findRootSelector = (container: Element) => {
435
+ const candidates = container.querySelectorAll('*');
436
+ for (let idx = 0; idx < candidates.length; idx++) {
437
+ const candidate = candidates[idx];
438
+ if (!candidate) continue;
439
+ const tag = candidate.tagName.toLowerCase();
440
+ if (tag.includes('-')) return tag;
441
+ }
442
+
443
+ return null;
444
+ };
445
+
446
+ const destroyAngularApp = () => {
447
+ if (!window.__ANGULAR_APP__) return;
448
+
449
+ try {
450
+ window.__ANGULAR_APP__.destroy();
451
+ } catch {
452
+ /* ignored */
453
+ }
454
+ window.__ANGULAR_APP__ = null;
455
+ };
456
+
457
+ const bootstrapAngularModule = async (
458
+ indexPath: string,
459
+ rootSelector: string | null,
460
+ rootContainer: Element
461
+ ) => {
462
+ if (rootSelector && !rootContainer.querySelector(rootSelector)) {
463
+ rootContainer.appendChild(document.createElement(rootSelector));
464
+ }
465
+
466
+ window.__HMR_SKIP_HYDRATION__ = true;
467
+
468
+ const origWarn = suppressNg0912();
469
+
470
+ await import(`${indexPath}?t=${Date.now()}`);
471
+ await waitForAngularApp();
472
+
473
+ console.warn = origWarn;
474
+ };
475
+
476
+ const tickAngularApp = () => {
477
+ if (!window.__ANGULAR_APP__) return;
478
+
479
+ try {
480
+ window.__ANGULAR_APP__.tick();
481
+ } catch {
482
+ /* ignored */
483
+ }
484
+ };
485
+
486
+ const runWithViewTransition = (updateFn: () => Promise<void>) => {
487
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
488
+ const doc = document as any;
489
+ if (typeof doc.startViewTransition !== 'function') {
490
+ updateFn().catch((err: unknown) => {
491
+ console.warn('[HMR] Angular update failed (non-fatal):', err);
492
+ });
493
+
494
+ return;
495
+ }
496
+
497
+ let styleEl: HTMLStyleElement | null = null;
498
+ try {
499
+ styleEl = document.createElement('style');
500
+ styleEl.textContent =
501
+ '::view-transition-old(root),::view-transition-new(root){animation:none!important}';
502
+ document.head.appendChild(styleEl);
503
+ } catch {
504
+ /* ignored */
505
+ }
506
+
507
+ const removeStyle = () => {
508
+ if (styleEl && styleEl.parentNode) styleEl.remove();
509
+ };
510
+
511
+ doc.startViewTransition(async () => {
512
+ await updateFn();
513
+ })
514
+ .finished.then(removeStyle)
515
+ .catch(removeStyle);
516
+ };
517
+
518
+ const handleFullUpdate = (message: HMRMessage) => {
519
+ const componentState = captureComponentState();
520
+ const scrollState = saveScrollState();
521
+ const formState = saveFormState();
522
+
523
+ if (message.data.cssUrl) {
524
+ swapStylesheet(
525
+ message.data.cssUrl,
526
+ message.data.cssBaseName || '',
527
+ 'angular'
528
+ );
529
+ }
530
+
531
+ const rootContainer = document.getElementById('root') || document.body;
532
+ const rootSelector = findRootSelector(rootContainer);
533
+
534
+ const indexPath = findIndexPath(
535
+ message.data.manifest,
536
+ message.data.sourceFile,
537
+ 'angular'
538
+ );
539
+ if (!indexPath) return;
540
+
541
+ const doUpdate = async () => {
542
+ destroyAngularApp();
543
+ await bootstrapAngularModule(indexPath, rootSelector, rootContainer);
544
+ restoreComponentState(componentState);
545
+ tickAngularApp();
546
+ restoreFormState(formState);
547
+ restoreScrollState(scrollState);
548
+ };
549
+
550
+ runWithViewTransition(doUpdate);
551
+ };