@absolutejs/absolute 0.19.0-beta.706 → 0.19.0-beta.707

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