@nativescript/angular 21.0.1-alpha.1 → 21.0.1-alpha.3

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.
@@ -6,8 +6,8 @@ import { __decorate, __param, __metadata } from 'tslib';
6
6
  import * as i1$2 from '@angular/common';
7
7
  import { LocationStrategy, XhrFactory, CommonModule, ɵNullViewportScroller as _NullViewportScroller, ViewportScroller, PlatformLocation, DOCUMENT } from '@angular/common';
8
8
  import * as i1$3 from '@angular/router';
9
- import { DefaultUrlSerializer, Router, PRIMARY_OUTLET, NavigationEnd, ChildrenOutletContexts, ActivatedRoute, RouteReuseStrategy, RouterModule, provideRouter } from '@angular/router';
10
- import { BehaviorSubject, Subject, fromEvent, defer, Observable as Observable$1 } from 'rxjs';
9
+ import { DefaultUrlSerializer, Router, PRIMARY_OUTLET, NavigationEnd, ChildrenOutletContexts, ActivatedRoute, NavigationCancel, NavigationError, NavigationStart, RouteReuseStrategy, RouterModule, provideRouter } from '@angular/router';
10
+ import { BehaviorSubject, Subject, ReplaySubject, fromEvent, defer, Observable as Observable$1 } from 'rxjs';
11
11
  import { filter, map, take, distinctUntilChanged, startWith } from 'rxjs/operators';
12
12
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
13
13
  import { ɵBrowserAnimationBuilder as _BrowserAnimationBuilder, AnimationBuilder } from '@angular/animations';
@@ -256,6 +256,560 @@ function resetAngularHmrCompiledComponents(core) {
256
256
  }
257
257
  }
258
258
 
259
+ /**
260
+ * Centralised dev-mode + HMR detection for `@nativescript/angular` helpers.
261
+ *
262
+ * The package ships HMR scaffolding (route tracker, route replay, modal
263
+ * preservation, compiled-component reset) that subscribes to long-lived
264
+ * router and bootstrap streams. None of that work belongs in a production
265
+ * binary — it would attach observers that never fire and keep references
266
+ * that confuse Angular's destroy logic.
267
+ *
268
+ * Every HMR helper consults {@link isAngularHmrEnabled} from its
269
+ * constructor. The check is intentionally cheap (no network, no I/O) so it
270
+ * is safe to call in dependency-injection factories and in fast paths.
271
+ *
272
+ * Detection cascade (returns the first match):
273
+ * 1. **Production build short-circuit** — `ngDevMode === false` means
274
+ * Angular built the app in production mode. We bail immediately.
275
+ * 2. **NativeScript Vite dev signal** — see
276
+ * {@link isNativeScriptViteHmrActive}. We accept either of the two
277
+ * persistent globals the NS Vite root-placeholder installer manages
278
+ * (`__NS_DEV_PLACEHOLDER_ROOT_EARLY__` during early boot,
279
+ * `__NS_HMR_BOOT_COMPLETE__` after the real app root commits) so
280
+ * services that are constructed *after* the placeholder has handed
281
+ * off — e.g. `NativeDialog` instantiated lazily when the user opens
282
+ * their first modal — still detect HMR correctly.
283
+ * 3. **Webpack HMR signal** — `globalThis.__webpack_require__` is set
284
+ * when the webpack runtime is loaded. Combined with the `ngDevMode`
285
+ * short-circuit above, its presence means "webpack dev". The
286
+ * production webpack runtime also sets the global, but `ngDevMode`
287
+ * would already be `false`, so the production case never reaches
288
+ * here.
289
+ *
290
+ * If none of these match, the caller should treat HMR as disabled and
291
+ * skip subscribing to disposal/bootstrap streams.
292
+ *
293
+ * The webpack signal lives on `globalThis` rather than `import.meta` so
294
+ * this file compiles cleanly under `--module commonjs` (the jest spec
295
+ * compiler) and under `--module esnext` (the library build).
296
+ */
297
+ function isAngularHmrEnabled() {
298
+ if (typeof ngDevMode !== 'undefined' && ngDevMode === false) {
299
+ return false;
300
+ }
301
+ return isNativeScriptViteHmrActive() || isWebpackHmrActive();
302
+ }
303
+ /**
304
+ * True when the NativeScript Vite dev HMR runtime is active. This is the
305
+ * most reliable signal that the project's `nativescript.config.ts` set
306
+ * `bundler: 'vite'` AND we are running the dev server.
307
+ *
308
+ * The NS Vite root-placeholder installer manages two persistent globals:
309
+ * - `__NS_DEV_PLACEHOLDER_ROOT_EARLY__` is set the moment the placeholder
310
+ * runs (very early, before the real app boots), then **deleted** by
311
+ * `clearPlaceholderGlobals` once `tryFinalizeBootPlaceholder` succeeds.
312
+ * - `__NS_HMR_BOOT_COMPLETE__` is set in the same finalize step and is
313
+ * **never deleted** for the lifetime of the dev session.
314
+ *
315
+ * Callers run the gamut of timing — e.g. the route tracker is constructed
316
+ * during bootstrap (early flag still set) but `NativeDialog` is typically
317
+ * instantiated lazily when the user opens their first modal (early flag
318
+ * already cleared, complete flag set). Checking either global covers both
319
+ * windows. If we only checked the early flag, every late-instantiated
320
+ * service would silently no-op and HMR features (modal preservation,
321
+ * route replay) would appear broken in development.
322
+ */
323
+ function isNativeScriptViteHmrActive() {
324
+ const g = globalThis;
325
+ return !!(g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__ || g.__NS_HMR_BOOT_COMPLETE__);
326
+ }
327
+ /**
328
+ * True when the webpack runtime is loaded. The webpack runtime sets
329
+ * `__webpack_require__` on `globalThis` whenever a webpack bundle is
330
+ * executing — both in dev and prod. Callers gate on
331
+ * {@link isAngularHmrEnabled} (not this directly) so the production
332
+ * short-circuit fires first.
333
+ */
334
+ function isWebpackHmrActive() {
335
+ return typeof globalThis.__webpack_require__ === 'function';
336
+ }
337
+ /**
338
+ * True when Angular reports we are running with dev-mode flags. Useful
339
+ * for code paths that want to opt out of cost in production but don't
340
+ * care which bundler is running.
341
+ */
342
+ function isAngularDevMode() {
343
+ if (typeof ngDevMode === 'undefined') {
344
+ return true;
345
+ }
346
+ return ngDevMode !== false;
347
+ }
348
+
349
+ class NativeScriptDebug {
350
+ static { this.animationsTraceCategory = 'ns-animations'; }
351
+ static { this.rendererTraceCategory = 'ns-renderer'; }
352
+ static { this.viewUtilCategory = 'ns-view-util'; }
353
+ static { this.routerTraceCategory = 'ns-router'; }
354
+ static { this.routeReuseStrategyTraceCategory = 'ns-route-reuse-strategy'; }
355
+ static { this.listViewTraceCategory = 'ns-list-view'; }
356
+ static { this.bootstrapCategory = 'bootstrap'; }
357
+ static { this.hmrTraceCategory = 'ns-ng-hmr'; }
358
+ // TODO: migrate all usage to this - avoids extraneous method executions
359
+ static { this.enabled = Trace.isEnabled(); }
360
+ static isLogEnabled() {
361
+ return Trace.isEnabled();
362
+ }
363
+ static animationsLog(message) {
364
+ Trace.write(message, NativeScriptDebug.animationsTraceCategory);
365
+ }
366
+ static rendererLog(msg) {
367
+ Trace.write(msg, NativeScriptDebug.rendererTraceCategory);
368
+ }
369
+ static rendererError(message) {
370
+ Trace.write(message, NativeScriptDebug.rendererTraceCategory, Trace.messageType.error);
371
+ }
372
+ static viewUtilLog(msg) {
373
+ Trace.write(msg, NativeScriptDebug.viewUtilCategory);
374
+ }
375
+ static routerLog(message) {
376
+ Trace.write(message, NativeScriptDebug.routerTraceCategory);
377
+ }
378
+ static routerError(message) {
379
+ Trace.write(message, NativeScriptDebug.routerTraceCategory, Trace.messageType.error);
380
+ }
381
+ static routeReuseStrategyLog(message) {
382
+ Trace.write(message, NativeScriptDebug.routeReuseStrategyTraceCategory);
383
+ }
384
+ static styleError(message) {
385
+ Trace.write(message, Trace.categories.Style, Trace.messageType.error);
386
+ }
387
+ static listViewLog(message) {
388
+ Trace.write(message, NativeScriptDebug.listViewTraceCategory);
389
+ }
390
+ static listViewError(message) {
391
+ Trace.write(message, NativeScriptDebug.listViewTraceCategory, Trace.messageType.error);
392
+ }
393
+ static bootstrapLog(message) {
394
+ Trace.write(message, NativeScriptDebug.bootstrapCategory);
395
+ }
396
+ static bootstrapLogError(message) {
397
+ Trace.write(message, NativeScriptDebug.bootstrapCategory, Trace.messageType.error);
398
+ }
399
+ static hmrLog(message) {
400
+ Trace.write(message, NativeScriptDebug.hmrTraceCategory);
401
+ }
402
+ static hmrLogError(message) {
403
+ Trace.write(message, NativeScriptDebug.hmrTraceCategory, Trace.messageType.error);
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Fresh-class registry for HMR.
409
+ *
410
+ * After an HMR reboot, every previously imported component module is
411
+ * re-evaluated. Each `@Component()`-decorated class becomes a *new* class
412
+ * object — it shares the source name (e.g. `ResourceModalComponent`) but
413
+ * has a different identity from the class the host code captured before
414
+ * the reboot.
415
+ *
416
+ * Helpers like `NativeDialog._restoreSingleDialog` need to re-open a
417
+ * captured modal *with the new class so the visual update applies*.
418
+ * Holding onto the pre-reboot class reference reopens the modal with
419
+ * the old metadata, which manifests as "the change appears the next
420
+ * time I close and re-open the modal myself, but not when HMR auto-
421
+ * reopens it."
422
+ *
423
+ * The mechanism is:
424
+ *
425
+ * 1. The Vite plugin `ns-component-hmr-register` (in
426
+ * `@nativescript/vite/configuration/angular`) injects a
427
+ * registration call at the end of every user `.ts` file that
428
+ * defines an `@Component`-decorated class:
429
+ *
430
+ * if (typeof globalThis.__NS_HMR_REGISTER_COMPONENT__ === 'function') {
431
+ * try { globalThis.__NS_HMR_REGISTER_COMPONENT__(
432
+ * 'ResourceModalComponent', ResourceModalComponent, import.meta.url
433
+ * ); } catch {}
434
+ * }
435
+ *
436
+ * 2. This module installs `__NS_HMR_REGISTER_COMPONENT__` on
437
+ * `globalThis` so module re-evaluations after an HMR reboot
438
+ * replace the previously-registered class with the fresh one.
439
+ *
440
+ * 3. HMR helpers (modal restore, route replay) read the registry
441
+ * via {@link getFreshComponentClass} to swap in the fresh class.
442
+ *
443
+ * This avoids patching `ɵɵdefineComponent` directly, which is exported
444
+ * as an immutable ESM namespace binding from `@angular/core` — patch
445
+ * attempts silently fail (the assignment is a no-op under strict
446
+ * mode) so the registry never gets populated. With the self-
447
+ * registration approach the binding stays untouched and we don't
448
+ * depend on Angular's internal export shape.
449
+ *
450
+ * Production short-circuit: the registrar is only installed when
451
+ * {@link isAngularHmrEnabled} reports dev + (vite | webpack). In a
452
+ * production build the global hook is never assigned and the Vite
453
+ * plugin only runs in `apply: 'serve'`, so the registration calls
454
+ * never reach the runtime.
455
+ */
456
+ const REGISTRY_KEY$1 = '__NS_ANGULAR_HMR_CLASS_REGISTRY__';
457
+ const REGISTRY_META_KEY = '__NS_ANGULAR_HMR_CLASS_META__';
458
+ const REGISTRAR_HOOK = '__NS_HMR_REGISTER_COMPONENT__';
459
+ const REGISTRAR_INSTALLED_FLAG = '__NS_ANGULAR_HMR_REGISTRAR_INSTALLED__';
460
+ /**
461
+ * Diagnostic: counters that survive across HMR cycles via globalThis.
462
+ * Used to spot patterns like "the same class registered N times in a
463
+ * single cycle" or "a brand-new class object every cycle".
464
+ */
465
+ const DIAG_KEY = '__NS_HMR_DIAG__';
466
+ function getDiag() {
467
+ const slot = globalThis;
468
+ if (!slot[DIAG_KEY]) {
469
+ slot[DIAG_KEY] = {
470
+ cycle: 0,
471
+ registerCalls: 0,
472
+ classIdentities: new Map(),
473
+ classRegisterCounts: new Map(),
474
+ classIds: new WeakMap(),
475
+ classIdNext: 1,
476
+ };
477
+ }
478
+ return slot[DIAG_KEY];
479
+ }
480
+ /** Get/assign a short stable id for a class object. */
481
+ function getClassId(diag, cls) {
482
+ let id = diag.classIds.get(cls);
483
+ if (!id) {
484
+ id = `c${diag.classIdNext++}`;
485
+ diag.classIds.set(cls, id);
486
+ }
487
+ return id;
488
+ }
489
+ /**
490
+ * Class-registry HMR diagnostic.
491
+ */
492
+ function diagLog(message) {
493
+ if (!isAngularHmrEnabled())
494
+ return;
495
+ if (!NativeScriptDebug.isLogEnabled())
496
+ return;
497
+ NativeScriptDebug.hmrLog(`[class-registry] ${message}`);
498
+ }
499
+ /**
500
+ * Log helper for "must surface at module-load time" messages — fires
501
+ * for any dev-mode build (not gated on the HMR-globals check) so the
502
+ * one-shot "registrar installed" line doesn't get suppressed by a
503
+ * module-load ordering race.
504
+ */
505
+ function bootLog(message) {
506
+ if (!isAngularDevMode())
507
+ return;
508
+ if (!NativeScriptDebug.isLogEnabled())
509
+ return;
510
+ NativeScriptDebug.hmrLog(`[class-registry] ${message}`);
511
+ }
512
+ /**
513
+ * Public so callers from application.ts can bump the cycle counter when
514
+ * a new HMR reboot starts. Kept as a free function (not a class method)
515
+ * to avoid forcing more imports on the application module.
516
+ *
517
+ * Self-heal: by the time we hit cycle bump, dev/HMR globals are
518
+ * definitely set (`__NS_HMR_BOOT_COMPLETE__` was set before the first
519
+ * HMR cycle ever runs). Some module-load orderings end up with
520
+ * `installAngularHmrComponentRegistrar()` called before the early
521
+ * placeholder global was set, so the registrar would have returned
522
+ * early and never installed the hook. Re-attempting here closes that
523
+ * window — `installAngularHmrComponentRegistrar()` is idempotent.
524
+ */
525
+ function _hmrDiagBumpCycle() {
526
+ const diag = getDiag();
527
+ diag.cycle += 1;
528
+ diagLog(`---- cycle ${diag.cycle} start ----`);
529
+ installAngularHmrComponentRegistrar();
530
+ return diag.cycle;
531
+ }
532
+ /** Public for tests. */
533
+ function _hmrDiagSnapshot() {
534
+ const diag = getDiag();
535
+ return {
536
+ cycle: diag.cycle,
537
+ registerCalls: diag.registerCalls,
538
+ namesSeen: diag.classIdentities.size,
539
+ };
540
+ }
541
+ function getRegistry() {
542
+ const slot = globalThis;
543
+ let registry = slot[REGISTRY_KEY$1];
544
+ if (!registry) {
545
+ registry = new Map();
546
+ slot[REGISTRY_KEY$1] = registry;
547
+ }
548
+ return registry;
549
+ }
550
+ function getMetaRegistry() {
551
+ const slot = globalThis;
552
+ let registry = slot[REGISTRY_META_KEY];
553
+ if (!registry) {
554
+ registry = new Map();
555
+ slot[REGISTRY_META_KEY] = registry;
556
+ }
557
+ return registry;
558
+ }
559
+ /**
560
+ * Internal: write a class into the registry. Exposed for unit tests
561
+ * (which can call this directly to simulate a Vite-injected
562
+ * registration without spinning up the Vite plugin pipeline).
563
+ *
564
+ * Production callers should never need this — user code just calls
565
+ * the global `__NS_HMR_REGISTER_COMPONENT__` hook installed by
566
+ * {@link installAngularHmrComponentRegistrar}.
567
+ */
568
+ function _registerComponentForHmr(name, cls, url = '') {
569
+ if (!name || typeof name !== 'string')
570
+ return;
571
+ if (cls === undefined || cls === null)
572
+ return;
573
+ const registry = getRegistry();
574
+ const meta = getMetaRegistry();
575
+ const previous = registry.get(name);
576
+ registry.set(name, cls);
577
+ meta.set(name, { url: url || '', cycle: getDiag().cycle });
578
+ const d = getDiag();
579
+ d.registerCalls += 1;
580
+ if (typeof cls === 'object' || typeof cls === 'function') {
581
+ const classId = getClassId(d, cls);
582
+ let identitySet = d.classIdentities.get(name);
583
+ if (!identitySet) {
584
+ identitySet = new Set();
585
+ d.classIdentities.set(name, identitySet);
586
+ }
587
+ identitySet.add(cls);
588
+ d.classRegisterCounts.set(name, (d.classRegisterCounts.get(name) ?? 0) + 1);
589
+ // Only log a small set of "interesting" components to keep noise
590
+ // manageable. Verbose mode is enabled by setting
591
+ // globalThis.__NS_HMR_DIAG_VERBOSE = true (e.g. from the user
592
+ // app's main.ts) when we want all names.
593
+ const verbose = !!globalThis.__NS_HMR_DIAG_VERBOSE;
594
+ const watchPattern = globalThis.__NS_HMR_DIAG_WATCH;
595
+ const matches = watchPattern instanceof RegExp ? watchPattern.test(name) : typeof watchPattern === 'string' ? name.includes(watchPattern) : /Modal|Dialog/.test(name);
596
+ if (verbose || matches) {
597
+ diagLog(`register name=${name} classId=${classId} sameAsPrev=${previous === cls} cycle=${d.cycle} totalIdentitiesForName=${identitySet.size} registerCountForName=${d.classRegisterCounts.get(name)} url=${url || '(none)'}`);
598
+ }
599
+ }
600
+ }
601
+ /**
602
+ * Install the cross-module `__NS_HMR_REGISTER_COMPONENT__` hook on
603
+ * `globalThis`. The Vite plugin `ns-component-hmr-register` injects
604
+ * a call to this hook at the end of every user `.ts` file that
605
+ * defines an `@Component`-decorated class, so re-evaluations after
606
+ * an HMR reboot keep the registry pointed at the live class.
607
+ *
608
+ * Idempotent: calling twice is a no-op (the second call sees the
609
+ * installed flag and returns).
610
+ *
611
+ * The hook is installed unconditionally — it's a single function
612
+ * reference on globalThis with negligible cost. Production builds
613
+ * never reach this code path because the Vite plugin
614
+ * `ns-component-hmr-register` runs only with `apply: 'serve'`, so
615
+ * the hook is never *called* in production. We previously gated
616
+ * this on `isAngularHmrEnabled()` but that check depends on
617
+ * NativeScript Vite globals (`__NS_DEV_PLACEHOLDER_ROOT_EARLY__` /
618
+ * `__NS_HMR_BOOT_COMPLETE__`) that are set imperatively from
619
+ * `main-entry.ts`. Module-load ordering can put `application.ts`
620
+ * evaluation *before* those globals are set in some startup paths,
621
+ * causing the install to silently no-op while the Vite plugin
622
+ * happily emits registration calls into user `.ts` files. The end
623
+ * result was an empty registry and `getFreshComponentClass` always
624
+ * reporting `reason=no-registry`. Removing the gate eliminates
625
+ * that race entirely.
626
+ */
627
+ function installAngularHmrComponentRegistrar() {
628
+ const slot = globalThis;
629
+ if (slot[REGISTRAR_INSTALLED_FLAG]) {
630
+ return;
631
+ }
632
+ // Define the hook BEFORE marking installed so concurrent module
633
+ // initializers see the function as soon as the flag is observable.
634
+ slot[REGISTRAR_HOOK] = (name, cls, url) => {
635
+ try {
636
+ _registerComponentForHmr(name, cls, typeof url === 'string' ? url : '');
637
+ }
638
+ catch (err) {
639
+ // Registration is best-effort — never break a user module load
640
+ // because of a registration failure.
641
+ diagLog(`registrar threw for ${name}: ${err?.message ?? err}`);
642
+ }
643
+ };
644
+ slot[REGISTRAR_INSTALLED_FLAG] = true;
645
+ bootLog('installAngularHmrComponentRegistrar installed global hook __NS_HMR_REGISTER_COMPONENT__');
646
+ }
647
+ /**
648
+ * Look up the freshest registered component class for a given name, or
649
+ * `undefined` if no match. HMR helpers (e.g. dialog restore) call this
650
+ * with the captured class's `.name` to find the live class after a
651
+ * reboot. Returns `undefined` in production builds because the registrar
652
+ * is never installed there.
653
+ */
654
+ function getFreshComponentClass(name) {
655
+ if (!name)
656
+ return undefined;
657
+ const slot = globalThis;
658
+ const registry = slot[REGISTRY_KEY$1];
659
+ // Diagnostics: log every lookup with the resolved class id so the
660
+ // restore path can be cross-referenced against register emissions.
661
+ if (registry) {
662
+ const value = registry.get(name);
663
+ const diag = getDiag();
664
+ const classId = value && (typeof value === 'object' || typeof value === 'function') ? getClassId(diag, value) : '(none)';
665
+ const knownNames = registry.size;
666
+ diagLog(`getFreshComponentClass name=${name} found=${!!value} classId=${classId} registrySize=${knownNames}`);
667
+ return value;
668
+ }
669
+ diagLog(`getFreshComponentClass name=${name} found=false reason=no-registry`);
670
+ return undefined;
671
+ }
672
+ /**
673
+ * Look up the source URL (`import.meta.url`) recorded for a registered
674
+ * component. Used by HMR helpers that need to force a fresh import of
675
+ * a lazily-loaded module (e.g. modals whose static import chain doesn't
676
+ * walk the bootstrap path).
677
+ *
678
+ * Returns `undefined` if the name was never registered or if no URL was
679
+ * provided at registration time.
680
+ */
681
+ function getRegisteredComponentUrl(name) {
682
+ if (!name)
683
+ return undefined;
684
+ const slot = globalThis;
685
+ const meta = slot[REGISTRY_META_KEY];
686
+ const entry = meta?.get(name);
687
+ return entry?.url || undefined;
688
+ }
689
+ /**
690
+ * Test/debug helper: clear all registered classes. Production callers
691
+ * never need this; the registry stays empty without the registrar.
692
+ */
693
+ function clearAngularHmrClassRegistry() {
694
+ const slot = globalThis;
695
+ slot[REGISTRY_KEY$1] = undefined;
696
+ slot[REGISTRY_META_KEY] = undefined;
697
+ slot[REGISTRAR_INSTALLED_FLAG] = undefined;
698
+ slot[REGISTRAR_HOOK] = undefined;
699
+ // Also reset diag so test isolation isn't broken by counts leaking.
700
+ const diagSlot = globalThis;
701
+ diagSlot[DIAG_KEY] = undefined;
702
+ }
703
+
704
+ const REGISTRY_KEY = '__NS_HMR_EAGER_SERVICES__';
705
+ const REGISTER_KEY = '__NS_REGISTER_HMR_EAGER_SERVICE__';
706
+ /**
707
+ * Diagnostic helper.
708
+ */
709
+ function eagerDiag(message) {
710
+ const g = globalThis;
711
+ const ngDev = (typeof g.ngDevMode === 'boolean') ? g.ngDevMode : true;
712
+ const viteHmr = !!g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__ || !!g.__NS_HMR_BOOT_COMPLETE__;
713
+ if (!(ngDev && viteHmr))
714
+ return;
715
+ if (!NativeScriptDebug.isLogEnabled())
716
+ return;
717
+ NativeScriptDebug.hmrLog(`[eager] ${message}`);
718
+ }
719
+ function getStore() {
720
+ return globalThis;
721
+ }
722
+ /**
723
+ * Returns the live (mutable) array of registered eager instantiators.
724
+ * Callers must not mutate it directly outside of the helpers in this
725
+ * module — use {@link registerHmrEagerInstantiator} or
726
+ * {@link clearHmrEagerInstantiators} instead.
727
+ */
728
+ function getRegisteredHmrEagerInstantiators() {
729
+ const store = getStore();
730
+ const list = store[REGISTRY_KEY];
731
+ if (!Array.isArray(list)) {
732
+ const fresh = [];
733
+ store[REGISTRY_KEY] = fresh;
734
+ return fresh;
735
+ }
736
+ return list;
737
+ }
738
+ /**
739
+ * Idempotently register an instantiator callback. Returns `true` when the
740
+ * callback was added, `false` when it was already present.
741
+ */
742
+ function registerHmrEagerInstantiator(fn) {
743
+ if (typeof fn !== 'function') {
744
+ return false;
745
+ }
746
+ const list = getRegisteredHmrEagerInstantiators();
747
+ if (list.includes(fn)) {
748
+ eagerDiag(`registerHmrEagerInstantiator dedup (already present) listSize=${list.length}`);
749
+ return false;
750
+ }
751
+ list.push(fn);
752
+ eagerDiag(`registerHmrEagerInstantiator added newSize=${list.length} fnName=${fn.name || '(anon)'}`);
753
+ return true;
754
+ }
755
+ /**
756
+ * Clear all registered instantiators. Tests use this to reset state
757
+ * between specs; production code should not call it.
758
+ */
759
+ function clearHmrEagerInstantiators() {
760
+ const list = getRegisteredHmrEagerInstantiators();
761
+ list.length = 0;
762
+ }
763
+ /**
764
+ * Install the cross-module registration entry point on `globalThis` so
765
+ * consumer modules (e.g. `dialog-services.ts`) can register without
766
+ * statically importing this file. Idempotent across multiple calls so
767
+ * application.ts can call it on every reboot without leaking state.
768
+ */
769
+ function installHmrEagerRegistrar() {
770
+ const store = getStore();
771
+ if (typeof store[REGISTER_KEY] === 'function') {
772
+ return;
773
+ }
774
+ store[REGISTER_KEY] = (fn) => {
775
+ registerHmrEagerInstantiator(fn);
776
+ };
777
+ }
778
+ /**
779
+ * Invoke every registered instantiator with the bootstrapped injector.
780
+ * Per-callback exceptions are swallowed; pass `onError` to receive them
781
+ * for logging.
782
+ */
783
+ function runHmrEagerInstantiators(injector, onError) {
784
+ if (!injector) {
785
+ eagerDiag(`runHmrEagerInstantiators called without injector — no-op`);
786
+ return;
787
+ }
788
+ const list = getRegisteredHmrEagerInstantiators();
789
+ eagerDiag(`runHmrEagerInstantiators list.length=${list.length}`);
790
+ if (list.length === 0) {
791
+ return;
792
+ }
793
+ for (let i = 0; i < list.length; i++) {
794
+ const fn = list[i];
795
+ try {
796
+ eagerDiag(`runHmrEagerInstantiators calling [${i}] ${fn.name || '(anon)'}`);
797
+ fn(injector);
798
+ }
799
+ catch (err) {
800
+ eagerDiag(`runHmrEagerInstantiators [${i}] threw: ${err?.message ?? err}`);
801
+ if (onError) {
802
+ try {
803
+ onError(err);
804
+ }
805
+ catch {
806
+ // The error reporter must not itself break the loop.
807
+ }
808
+ }
809
+ }
810
+ }
811
+ }
812
+
259
813
  class NativeScriptLoadingService {
260
814
  constructor() {
261
815
  this.mainModuleReady$ = new BehaviorSubject(false);
@@ -356,57 +910,6 @@ function clearAngularHmrRouteConfigCaches(routes) {
356
910
  return cleared;
357
911
  }
358
912
 
359
- class NativeScriptDebug {
360
- static { this.animationsTraceCategory = 'ns-animations'; }
361
- static { this.rendererTraceCategory = 'ns-renderer'; }
362
- static { this.viewUtilCategory = 'ns-view-util'; }
363
- static { this.routerTraceCategory = 'ns-router'; }
364
- static { this.routeReuseStrategyTraceCategory = 'ns-route-reuse-strategy'; }
365
- static { this.listViewTraceCategory = 'ns-list-view'; }
366
- static { this.bootstrapCategory = 'bootstrap'; }
367
- // TODO: migrate all usage to this - avoids extraneous method executions
368
- static { this.enabled = Trace.isEnabled(); }
369
- static isLogEnabled() {
370
- return Trace.isEnabled();
371
- }
372
- static animationsLog(message) {
373
- Trace.write(message, NativeScriptDebug.animationsTraceCategory);
374
- }
375
- static rendererLog(msg) {
376
- Trace.write(msg, NativeScriptDebug.rendererTraceCategory);
377
- }
378
- static rendererError(message) {
379
- Trace.write(message, NativeScriptDebug.rendererTraceCategory, Trace.messageType.error);
380
- }
381
- static viewUtilLog(msg) {
382
- Trace.write(msg, NativeScriptDebug.viewUtilCategory);
383
- }
384
- static routerLog(message) {
385
- Trace.write(message, NativeScriptDebug.routerTraceCategory);
386
- }
387
- static routerError(message) {
388
- Trace.write(message, NativeScriptDebug.routerTraceCategory, Trace.messageType.error);
389
- }
390
- static routeReuseStrategyLog(message) {
391
- Trace.write(message, NativeScriptDebug.routeReuseStrategyTraceCategory);
392
- }
393
- static styleError(message) {
394
- Trace.write(message, Trace.categories.Style, Trace.messageType.error);
395
- }
396
- static listViewLog(message) {
397
- Trace.write(message, NativeScriptDebug.listViewTraceCategory);
398
- }
399
- static listViewError(message) {
400
- Trace.write(message, NativeScriptDebug.listViewTraceCategory, Trace.messageType.error);
401
- }
402
- static bootstrapLog(message) {
403
- Trace.write(message, NativeScriptDebug.bootstrapCategory);
404
- }
405
- static bootstrapLogError(message) {
406
- Trace.write(message, NativeScriptDebug.bootstrapCategory, Trace.messageType.error);
407
- }
408
- }
409
-
410
913
  class FrameService {
411
914
  // TODO: Add any methods that are needed to handle frame/page navigation
412
915
  getFrame() {
@@ -1450,13 +1953,43 @@ function createAngularRootTransitionGuard(globalObj = globalThis, timers = { set
1450
1953
  // This is crucial because HMR imports a fresh @angular/core with empty LView tracking
1451
1954
  // We need to use the original one that has the registered LViews
1452
1955
  rememberAngularCoreForHmr(i0, globalThis);
1956
+ // Install the cross-module HMR component registrar. The Vite plugin
1957
+ // `ns-component-hmr-register` injects a call to the global hook
1958
+ // `__NS_HMR_REGISTER_COMPONENT__` at the end of every user `.ts` file
1959
+ // that declares an `@Component`-decorated class. After an HMR reboot,
1960
+ // each re-evaluated module pushes its fresh class into the registry,
1961
+ // and HMR helpers (modal restore, route replay) read the registry via
1962
+ // `getFreshComponentClass` to re-attach to the *live* class instead of
1963
+ // a captured stale reference. Production short-circuits inside the
1964
+ // helper (the hook is never assigned).
1965
+ //
1966
+ // We install the registrar before any other module initialization can
1967
+ // reference the hook so a user module loaded synchronously alongside
1968
+ // `@nativescript/angular` always finds the function present.
1969
+ installAngularHmrComponentRegistrar();
1970
+ // Install the cross-module registration entry point used by HMR-aware
1971
+ // services (e.g. `NativeDialog`) to ask for eager construction after
1972
+ // every bootstrap. Idempotent: re-evaluations after HMR are no-ops.
1973
+ installHmrEagerRegistrar();
1453
1974
  const angularHmrGlobal = globalThis;
1454
- angularHmrGlobal.__NS_REMEMBER_ANGULAR_CORE__ = (core) => setAngularCoreForHmr(core, angularHmrGlobal);
1975
+ angularHmrGlobal.__NS_REMEMBER_ANGULAR_CORE__ = (core) => {
1976
+ setAngularCoreForHmr(core, angularHmrGlobal);
1977
+ };
1455
1978
  function disableRootViewHanding(view) {
1456
1979
  view.__disable_root_view_handling = true;
1457
1980
  }
1458
1981
  const preAngularDisposal$ = new Subject();
1459
- const postAngularBootstrap$ = new Subject();
1982
+ /**
1983
+ * Stream that emits when an Angular module finishes bootstrapping. Modeled
1984
+ * as a `ReplaySubject(1)` so consumers (e.g. `NativeDialog`) instantiated
1985
+ * lazily — *after* the bootstrap event has already fired — still receive
1986
+ * the latest event and can react. Without buffering, a service that the
1987
+ * user app injects on first need (after bootstrap) would silently miss the
1988
+ * `hotreload` notification and skip HMR-only work like restoring captured
1989
+ * modal state. The buffer size of 1 means each new HMR cycle replaces the
1990
+ * cached event so cycle N's late subscribers don't see cycle N-1's event.
1991
+ */
1992
+ const postAngularBootstrap$ = new ReplaySubject(1);
1460
1993
  /**
1461
1994
  * @deprecated
1462
1995
  */
@@ -1473,14 +2006,34 @@ if (import.meta['webpackHot']) {
1473
2006
  };
1474
2007
  }
1475
2008
  function emitModuleBootstrapEvent(ref, name, reason) {
2009
+ if (NativeScriptDebug.isLogEnabled()) {
2010
+ NativeScriptDebug.hmrLog(`emitModuleBootstrapEvent name=${name} reason=${reason}`);
2011
+ }
2012
+ // Instantiate registered HMR-aware services *before* emitting so they
2013
+ // attach their subscriptions in the same JS task and are guaranteed to
2014
+ // observe the event being emitted. `postAngularBootstrap$` is also a
2015
+ // `ReplaySubject(1)`, so a service injected later still receives the
2016
+ // buffered event — the eager pass is the fast path that lets the
2017
+ // restore work begin in the same task as bootstrap completion.
2018
+ if (name === 'main') {
2019
+ runHmrEagerInstantiators(ref.injector, (err) => NativeScriptDebug.bootstrapLogError(`HMR eager instantiator threw: ${err?.message ?? err}`));
2020
+ }
1476
2021
  postAngularBootstrap$.next({
1477
2022
  moduleType: name,
1478
2023
  reference: ref,
1479
2024
  reason,
1480
2025
  });
2026
+ if (NativeScriptDebug.isLogEnabled()) {
2027
+ NativeScriptDebug.hmrLog(`postAngularBootstrap$.next() emitted name=${name} reason=${reason}`);
2028
+ }
1481
2029
  }
1482
2030
  function destroyRef(ref, name, reason) {
1483
2031
  if (ref) {
2032
+ const refKind = ref instanceof PlatformRef ? 'PlatformRef' : ref instanceof NgModuleRef ? 'NgModuleRef' : ref instanceof ApplicationRef ? 'ApplicationRef' : '(unknown)';
2033
+ const traceEnabled = NativeScriptDebug.isLogEnabled();
2034
+ if (traceEnabled) {
2035
+ NativeScriptDebug.hmrLog(`destroyRef kind=${refKind} name=${name ?? '(none)'} reason=${reason ?? '(none)'}`);
2036
+ }
1484
2037
  if (ref instanceof PlatformRef) {
1485
2038
  preAngularDisposal$.next({
1486
2039
  moduleType: 'platform',
@@ -1496,6 +2049,9 @@ function destroyRef(ref, name, reason) {
1496
2049
  });
1497
2050
  }
1498
2051
  ref.destroy();
2052
+ if (traceEnabled) {
2053
+ NativeScriptDebug.hmrLog(`destroyRef DONE kind=${refKind} name=${name ?? '(none)'}`);
2054
+ }
1499
2055
  }
1500
2056
  }
1501
2057
  function runZoneSyncTask(fn) {
@@ -1591,11 +2147,9 @@ function runNativeScriptAngularApp(options) {
1591
2147
  if (clearedDetached > 0 ||
1592
2148
  cleared > 0 ||
1593
2149
  (clearedLocation && (clearedLocation.outlets > 0 || clearedLocation.states > 0 || clearedLocation.callbacks > 0 || clearedLocation.hadUrlTree))) {
1594
- console.log('[ng-hmr] cleared Angular route caches before reboot:', {
1595
- detachedViews: clearedDetached,
1596
- locationState: clearedLocation,
1597
- routeFields: cleared,
1598
- });
2150
+ if (NativeScriptDebug.isLogEnabled()) {
2151
+ NativeScriptDebug.hmrLog(`cleared Angular route caches before reboot: detachedViews=${clearedDetached} routeFields=${cleared} locationState=${JSON.stringify(clearedLocation)}`);
2152
+ }
1599
2153
  }
1600
2154
  }
1601
2155
  catch { }
@@ -1625,21 +2179,30 @@ function runNativeScriptAngularApp(options) {
1625
2179
  }, 0);
1626
2180
  };
1627
2181
  const setRootView = (ref) => {
1628
- console.log('[ng-hmr] setRootView called, bootstrapId:', bootstrapId, 'ref type:', ref?.constructor?.name);
2182
+ const traceEnabled = NativeScriptDebug.isLogEnabled();
2183
+ if (traceEnabled) {
2184
+ NativeScriptDebug.hmrLog(`setRootView called bootstrapId=${bootstrapId} refType=${ref?.constructor?.name}`);
2185
+ }
1629
2186
  if (bootstrapId === -1) {
1630
- // treat edge cases
1631
- console.log('[ng-hmr] setRootView: bootstrapId is -1, returning early');
2187
+ // edge case: a stale ref racing with a teardown
2188
+ if (traceEnabled) {
2189
+ NativeScriptDebug.hmrLog('setRootView: bootstrapId is -1, returning early');
2190
+ }
1632
2191
  return;
1633
2192
  }
1634
2193
  if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
1635
2194
  if (ref.injector.get(DISABLE_ROOT_VIEW_HANDLING, false)) {
1636
- console.log('[ng-hmr] setRootView: DISABLE_ROOT_VIEW_HANDLING is true, returning');
2195
+ if (traceEnabled) {
2196
+ NativeScriptDebug.hmrLog('setRootView: DISABLE_ROOT_VIEW_HANDLING is true, returning');
2197
+ }
1637
2198
  return;
1638
2199
  }
1639
2200
  }
1640
2201
  else {
1641
2202
  if (ref['__disable_root_view_handling']) {
1642
- console.log('[ng-hmr] setRootView: __disable_root_view_handling is true, returning');
2203
+ if (traceEnabled) {
2204
+ NativeScriptDebug.hmrLog('setRootView: __disable_root_view_handling is true, returning');
2205
+ }
1643
2206
  return;
1644
2207
  }
1645
2208
  }
@@ -1647,8 +2210,8 @@ function runNativeScriptAngularApp(options) {
1647
2210
  NativeScriptDebug.bootstrapLog(`Setting RootView ${launchEventDone ? 'outside of' : 'during'} launch event`);
1648
2211
  // TODO: check for leaks when root view isn't properly destroyed
1649
2212
  if (ref instanceof View) {
1650
- console.log('[ng-hmr] setRootView: ref is View, launchEventDone:', launchEventDone);
1651
- if (NativeScriptDebug.isLogEnabled()) {
2213
+ if (traceEnabled) {
2214
+ NativeScriptDebug.hmrLog(`setRootView: ref is View, launchEventDone=${launchEventDone}`);
1652
2215
  NativeScriptDebug.bootstrapLog(`Setting RootView to ${ref}`);
1653
2216
  }
1654
2217
  if (currentOptions.embedded) {
@@ -1665,38 +2228,34 @@ function runNativeScriptAngularApp(options) {
1665
2228
  }
1666
2229
  const view = ref.injector.get(APP_ROOT_VIEW);
1667
2230
  const newRoot = view instanceof AppHostView ? view.content : view;
1668
- console.log('[ng-hmr] setRootView: view from injector:', view?.constructor?.name, 'newRoot:', newRoot?.constructor?.name);
1669
- console.log('[ng-hmr] setRootView: launchEventDone:', launchEventDone, 'embedded:', currentOptions.embedded);
1670
- if (NativeScriptDebug.isLogEnabled()) {
2231
+ if (traceEnabled) {
2232
+ NativeScriptDebug.hmrLog(`setRootView: view=${view?.constructor?.name} newRoot=${newRoot?.constructor?.name} launchEventDone=${launchEventDone} embedded=${!!currentOptions.embedded}`);
1671
2233
  NativeScriptDebug.bootstrapLog(`Setting RootView to ${newRoot}`);
1672
2234
  }
1673
2235
  if (currentOptions.embedded) {
1674
- console.log('[ng-hmr] setRootView: calling Application.run (embedded)');
2236
+ if (traceEnabled) {
2237
+ NativeScriptDebug.hmrLog('setRootView: calling Application.run (embedded)');
2238
+ }
1675
2239
  Application.run({ create: () => newRoot });
1676
2240
  }
1677
2241
  else if (launchEventDone) {
1678
- console.log('[ng-hmr] setRootView: calling Application.resetRootView');
1679
- console.log('[ng-hmr] setRootView: newRoot details:', {
1680
- type: newRoot?.constructor?.name,
1681
- nativeView: !!newRoot?.nativeView,
1682
- parent: newRoot?.parent?.constructor?.name,
1683
- childCount: newRoot?.getChildrenCount?.() ?? 'N/A',
1684
- });
2242
+ if (traceEnabled) {
2243
+ NativeScriptDebug.hmrLog(`setRootView: calling Application.resetRootView newRoot type=${newRoot?.constructor?.name} hasNativeView=${!!newRoot?.nativeView} parent=${newRoot?.parent?.constructor?.name} childCount=${newRoot?.getChildrenCount?.() ?? 'N/A'}`);
2244
+ }
1685
2245
  rootTransitionGuard.runApplicationResetRootView(Application, () => newRoot, newRoot?.constructor?.name || 'View');
1686
2246
  refreshRootViewCss(newRoot);
1687
- console.log('[ng-hmr] setRootView: Application.resetRootView returned');
1688
- // Check root view after reset
1689
- setTimeout(() => {
1690
- const currentRoot = Application.getRootView();
1691
- console.log('[ng-hmr] setRootView: after reset, getRootView:', {
1692
- type: currentRoot?.constructor?.name,
1693
- nativeView: !!currentRoot?.nativeView,
1694
- childCount: currentRoot?.getChildrenCount?.() ?? 'N/A',
1695
- });
1696
- }, 100);
2247
+ if (traceEnabled) {
2248
+ NativeScriptDebug.hmrLog('setRootView: Application.resetRootView returned');
2249
+ setTimeout(() => {
2250
+ const currentRoot = Application.getRootView();
2251
+ NativeScriptDebug.hmrLog(`setRootView: post-reset getRootView type=${currentRoot?.constructor?.name} hasNativeView=${!!currentRoot?.nativeView} childCount=${currentRoot?.getChildrenCount?.() ?? 'N/A'}`);
2252
+ }, 100);
2253
+ }
1697
2254
  }
1698
2255
  else {
1699
- console.log('[ng-hmr] setRootView: setting targetRootView (launch in progress)');
2256
+ if (traceEnabled) {
2257
+ NativeScriptDebug.hmrLog('setRootView: setting targetRootView (launch in progress)');
2258
+ }
1700
2259
  targetRootView = newRoot;
1701
2260
  }
1702
2261
  };
@@ -1708,25 +2267,34 @@ function runNativeScriptAngularApp(options) {
1708
2267
  setRootView(errorTextBox);
1709
2268
  };
1710
2269
  const bootstrapRoot = (reason) => {
1711
- console.log('[ng-hmr] bootstrapRoot called, reason:', reason);
2270
+ if (NativeScriptDebug.isLogEnabled()) {
2271
+ NativeScriptDebug.hmrLog(`bootstrapRoot called reason=${reason}`);
2272
+ }
1712
2273
  try {
1713
2274
  if (reason === 'hotreload') {
1714
2275
  resetAngularHmrCompiledComponents(getAngularCoreForHmrReset(i0, globalThis));
1715
2276
  }
1716
2277
  bootstrapId = Date.now();
1717
- console.log('[ng-hmr] bootstrapRoot: new bootstrapId:', bootstrapId);
2278
+ if (NativeScriptDebug.isLogEnabled()) {
2279
+ NativeScriptDebug.hmrLog(`bootstrapRoot: new bootstrapId=${bootstrapId}`);
2280
+ }
1718
2281
  const currentBootstrapId = bootstrapId;
1719
2282
  let bootstrapped = false;
1720
2283
  let onMainBootstrap = () => {
1721
2284
  setRootView(mainModuleRef);
1722
2285
  };
1723
2286
  runSynchronously(() => currentOptions.appModuleBootstrap(reason).then((ref) => {
1724
- console.log('[ng-hmr] appModuleBootstrap resolved, ref:', ref?.constructor?.name);
1725
- console.log('[ng-hmr] currentBootstrapId:', currentBootstrapId, 'bootstrapId:', bootstrapId);
2287
+ if (NativeScriptDebug.isLogEnabled()) {
2288
+ NativeScriptDebug.hmrLog(`appModuleBootstrap resolved ref=${ref?.constructor?.name} currentBootstrapId=${currentBootstrapId} bootstrapId=${bootstrapId}`);
2289
+ }
1726
2290
  if (currentBootstrapId !== bootstrapId) {
1727
- // this module is old and not needed anymore
1728
- // this may happen when developer uses async app initializer and the user exits the app before this bootstraps
1729
- console.log('[ng-hmr] bootstrap ID mismatch, destroying ref');
2291
+ // The pending bootstrap resolved AFTER another reboot bumped
2292
+ // bootstrapId. This typically happens when a developer ships
2293
+ // an async APP_INITIALIZER and the user exits/re-enters the
2294
+ // app while it's still resolving. Drop this ref.
2295
+ if (NativeScriptDebug.isLogEnabled()) {
2296
+ NativeScriptDebug.hmrLog('bootstrap ID mismatch, destroying ref');
2297
+ }
1730
2298
  ref.destroy();
1731
2299
  return;
1732
2300
  }
@@ -1748,41 +2316,49 @@ function runNativeScriptAngularApp(options) {
1748
2316
  };
1749
2317
  runInZone(() => {
1750
2318
  mainModuleRef = ref;
1751
- // Expose ApplicationRef for HMR to trigger change detection
1752
- // Check for ApplicationRef by duck-typing since instanceof can fail across module realms
2319
+ // Expose ApplicationRef for HMR to trigger change detection.
2320
+ // Check by duck-typing because `instanceof` can fail across
2321
+ // module realms during HMR — we may be holding a fresh
2322
+ // ApplicationRef class while `ref` was constructed by an
2323
+ // earlier (now-evicted) realm copy.
1753
2324
  const refAny = ref;
1754
2325
  const isAppRef = refAny && typeof refAny.tick === 'function' && Array.isArray(refAny.components);
1755
- console.log('[ng-hmr] ref type check: isAppRef=', isAppRef, 'has tick=', typeof refAny?.tick === 'function', 'has components=', Array.isArray(refAny?.components));
2326
+ if (NativeScriptDebug.isLogEnabled()) {
2327
+ NativeScriptDebug.hmrLog(`ref type check isAppRef=${isAppRef} hasTick=${typeof refAny?.tick === 'function'} hasComponents=${Array.isArray(refAny?.components)}`);
2328
+ }
1756
2329
  if (isAppRef) {
1757
2330
  global['__NS_ANGULAR_APP_REF__'] = ref;
1758
- // Mark boot complete for the HMR system
1759
2331
  global['__NS_HMR_BOOT_COMPLETE__'] = true;
1760
- // Register bootstrapped components for HMR lookup
1761
2332
  if (!global['__NS_ANGULAR_COMPONENTS__']) {
1762
2333
  global['__NS_ANGULAR_COMPONENTS__'] = {};
1763
2334
  }
1764
- // Get the component class from the first bootstrapped component
1765
- console.log('[ng-hmr] ApplicationRef components count:', refAny.components?.length);
2335
+ if (NativeScriptDebug.isLogEnabled()) {
2336
+ NativeScriptDebug.hmrLog(`ApplicationRef components count=${refAny.components?.length ?? 0}`);
2337
+ }
1766
2338
  if (refAny.components && refAny.components.length > 0) {
1767
2339
  const componentRef = refAny.components[0];
1768
- console.log('[ng-hmr] componentRef:', componentRef?.constructor?.name);
1769
- console.log('[ng-hmr] componentRef.componentType:', componentRef?.componentType?.name);
1770
- // For Angular 17+ standalone components, the component type is on componentRef.componentType
1771
- // For older Angular, try componentRef.instance.constructor
2340
+ if (NativeScriptDebug.isLogEnabled()) {
2341
+ NativeScriptDebug.hmrLog(`componentRef=${componentRef?.constructor?.name} componentType=${componentRef?.componentType?.name}`);
2342
+ }
2343
+ // Angular 17+ standalone: the component class is on
2344
+ // `componentRef.componentType`. Older Angular keeps it on
2345
+ // `componentRef.instance.constructor`.
1772
2346
  let componentType = componentRef?.componentType;
1773
2347
  if (!componentType && componentRef?.instance) {
1774
2348
  componentType = componentRef.instance.constructor;
1775
2349
  }
1776
2350
  if (componentType && componentType.name) {
1777
2351
  global['__NS_ANGULAR_COMPONENTS__'][componentType.name] = componentType;
1778
- console.log('[ng-hmr] Registered component for HMR:', componentType.name);
2352
+ if (NativeScriptDebug.isLogEnabled()) {
2353
+ NativeScriptDebug.hmrLog(`registered component for HMR: ${componentType.name}`);
2354
+ }
1779
2355
  }
1780
- else {
1781
- console.log('[ng-hmr] Could not get componentType name');
2356
+ else if (NativeScriptDebug.isLogEnabled()) {
2357
+ NativeScriptDebug.hmrLog('could not resolve componentType name');
1782
2358
  }
1783
2359
  }
1784
- else {
1785
- console.log('[ng-hmr] No components in ApplicationRef');
2360
+ else if (NativeScriptDebug.isLogEnabled()) {
2361
+ NativeScriptDebug.hmrLog('no components in ApplicationRef');
1786
2362
  }
1787
2363
  }
1788
2364
  else {
@@ -1958,20 +2534,33 @@ function runNativeScriptAngularApp(options) {
1958
2534
  disposePlatform('hotreload');
1959
2535
  };
1960
2536
  global['__reboot_ng_modules__'] = (shouldDisposePlatform = false) => {
1961
- console.log('[ng-hmr] __reboot_ng_modules__ called, shouldDisposePlatform:', shouldDisposePlatform);
1962
- console.log('[ng-hmr] current bootstrapId:', bootstrapId, 'mainModuleRef:', !!mainModuleRef);
2537
+ // Bump the global HMR cycle counter so subsequent diagnostic log
2538
+ // lines (class registry, dialog services) can be cross-referenced
2539
+ // to a specific reboot. Counter is always incremented; the trace
2540
+ // category gates whether we surface it in the console.
2541
+ const cycleNum = _hmrDiagBumpCycle();
2542
+ const traceEnabled = NativeScriptDebug.isLogEnabled();
2543
+ if (traceEnabled) {
2544
+ NativeScriptDebug.hmrLog(`__reboot_ng_modules__ called cycle=${cycleNum} shouldDisposePlatform=${shouldDisposePlatform} bootstrapId=${bootstrapId} hasMainModuleRef=${!!mainModuleRef}`);
2545
+ }
1963
2546
  try {
1964
2547
  global['__NS_CAPTURE_ANGULAR_HMR_ROUTE__']?.();
1965
2548
  }
1966
2549
  catch { }
1967
2550
  disposeLastModules('hotreload');
1968
- console.log('[ng-hmr] after disposeLastModules, bootstrapId:', bootstrapId);
2551
+ if (traceEnabled) {
2552
+ NativeScriptDebug.hmrLog(`after disposeLastModules cycle=${cycleNum} bootstrapId=${bootstrapId}`);
2553
+ }
1969
2554
  if (shouldDisposePlatform) {
1970
2555
  disposePlatform('hotreload');
1971
2556
  }
1972
- console.log('[ng-hmr] calling bootstrapRoot...');
2557
+ if (traceEnabled) {
2558
+ NativeScriptDebug.hmrLog(`calling bootstrapRoot cycle=${cycleNum}`);
2559
+ }
1973
2560
  bootstrapRoot('hotreload');
1974
- console.log('[ng-hmr] bootstrapRoot returned, new bootstrapId:', bootstrapId);
2561
+ if (traceEnabled) {
2562
+ NativeScriptDebug.hmrLog(`bootstrapRoot returned cycle=${cycleNum} bootstrapId=${bootstrapId}`);
2563
+ }
1975
2564
  };
1976
2565
  if (isWebpackHot) {
1977
2566
  // Webpack-specific HMR handling
@@ -2224,13 +2813,15 @@ function printNgTree(view) {
2224
2813
  }
2225
2814
  function printChildrenRecurse(parent) {
2226
2815
  const children = parent.firstChild ? [parent.firstChild, ...getChildrenSiblings(parent.firstChild).nextSiblings] : [];
2227
- console.log(`parent: ${parent}, firstChild: ${parent.firstChild}, lastChild: ${parent.lastChild} children: ${children}`);
2228
- if (parent.firstChild) {
2229
- console.log(`----- start ${parent}`);
2816
+ if (NativeScriptDebug.isLogEnabled()) {
2817
+ NativeScriptDebug.viewUtilLog(`parent: ${parent}, firstChild: ${parent.firstChild}, lastChild: ${parent.lastChild} children: ${children}`);
2818
+ if (parent.firstChild) {
2819
+ NativeScriptDebug.viewUtilLog(`----- start ${parent}`);
2820
+ }
2230
2821
  }
2231
2822
  children.forEach((c) => printChildrenRecurse(c));
2232
- if (parent.firstChild) {
2233
- console.log(`----- end ${parent}`);
2823
+ if (parent.firstChild && NativeScriptDebug.isLogEnabled()) {
2824
+ NativeScriptDebug.viewUtilLog(`----- end ${parent}`);
2234
2825
  }
2235
2826
  }
2236
2827
  function getChildrenSiblings(view) {
@@ -2252,8 +2843,10 @@ function getChildrenSiblings(view) {
2252
2843
  };
2253
2844
  }
2254
2845
  function printSiblingsTree(view) {
2846
+ if (!NativeScriptDebug.isLogEnabled())
2847
+ return;
2255
2848
  const { previousSiblings, nextSiblings } = getChildrenSiblings(view);
2256
- console.log(`${view} previousSiblings: ${previousSiblings} nextSiblings: ${nextSiblings}`);
2849
+ NativeScriptDebug.viewUtilLog(`${view} previousSiblings: ${previousSiblings} nextSiblings: ${nextSiblings}`);
2257
2850
  }
2258
2851
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
2259
2852
  const propertyMaps = new Map();
@@ -5402,7 +5995,7 @@ function createApplication(options) {
5402
5995
  * @deprecated use runNativeScriptAngularApp instead
5403
5996
  */
5404
5997
  const platformNativeScriptDynamic = function (options, extraProviders) {
5405
- console.log('platformNativeScriptDynamic is deprecated, use runNativeScriptAngularApp instead');
5998
+ console.warn('platformNativeScriptDynamic is deprecated, use runNativeScriptAngularApp instead');
5406
5999
  options = options || {};
5407
6000
  extraProviders = extraProviders || [];
5408
6001
  const ngRootView = new AppHostView(new Color(options.backgroundColor || 'white'));
@@ -5491,6 +6084,22 @@ class NativeDialogConfig {
5491
6084
  */
5492
6085
  this.closeOnNavigation = true;
5493
6086
  this.nativeOptions = {};
6087
+ /**
6088
+ * When true, this dialog will be re-opened automatically on Angular HMR
6089
+ * reboots so the user does not lose context every time a related file
6090
+ * changes. The new dialog reuses the same component class and `data` payload
6091
+ * (provided via `data`); other config such as `nativeOptions` is preserved
6092
+ * verbatim.
6093
+ *
6094
+ * The original `dialogRef.afterClosed()` subject is wired to the restored
6095
+ * dialog so consumers `await openModal(...)` resolve normally when the user
6096
+ * eventually closes the restored modal.
6097
+ *
6098
+ * Only opens via component class are restorable — `TemplateRef` openings
6099
+ * carry references that don't survive an HMR reboot and are silently
6100
+ * skipped. Has no effect outside of HMR.
6101
+ */
6102
+ this.preserveOnHmr = false;
5494
6103
  // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling.
5495
6104
  }
5496
6105
  }
@@ -5613,6 +6222,183 @@ class NativeDialogRef {
5613
6222
  }
5614
6223
  }
5615
6224
 
6225
+ const STASH_KEY = '__NS_ANGULAR_HMR_PENDING_MODALS__';
6226
+ function getStashSlot() {
6227
+ return globalThis;
6228
+ }
6229
+ /**
6230
+ * Pure helper: filter the open dialog list down to entries that opted in via
6231
+ * `preserveOnHmr` and that we actually know how to restore (component-class
6232
+ * openings, not template openings).
6233
+ */
6234
+ function selectPreservableDialogs(openDialogs) {
6235
+ return openDialogs.filter((dialog) => isPreservable(dialog));
6236
+ }
6237
+ function isPreservable(dialog) {
6238
+ if (!dialog.config?.preserveOnHmr) {
6239
+ return false;
6240
+ }
6241
+ return typeof dialog.componentClass === 'function';
6242
+ }
6243
+ /**
6244
+ * Capture the open dialogs that opted into HMR preservation. Returns the
6245
+ * captured entries so callers can correlate counts in their logs.
6246
+ */
6247
+ function captureDialogsForHmr(openDialogs) {
6248
+ const preservable = selectPreservableDialogs(openDialogs);
6249
+ if (preservable.length === 0) {
6250
+ clearPendingHmrDialogs();
6251
+ return [];
6252
+ }
6253
+ const captures = preservable.map(({ ref, componentClass, config }) => {
6254
+ const subject = readAfterClosedSubject(ref);
6255
+ // Capture the source name now — the class reference itself becomes
6256
+ // stale after the reboot, but the name is stable across realms and
6257
+ // is what the post-reboot registry is keyed on.
6258
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6259
+ const componentName = componentClass?.name ?? '';
6260
+ return {
6261
+ // Asserted non-null in `isPreservable`.
6262
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6263
+ componentClass: componentClass,
6264
+ componentName,
6265
+ config,
6266
+ graftAfterClosed: (value) => {
6267
+ if (!subject) {
6268
+ return;
6269
+ }
6270
+ try {
6271
+ if (!subject.closed) {
6272
+ subject.next(value);
6273
+ subject.complete();
6274
+ }
6275
+ }
6276
+ catch {
6277
+ // Swallow: the subject may have completed during dispose; nothing for us to do.
6278
+ }
6279
+ },
6280
+ };
6281
+ });
6282
+ globalThis[STASH_KEY] = captures;
6283
+ return captures;
6284
+ }
6285
+ /**
6286
+ * Drain the pending captures. The caller (the new `NativeDialog`) is expected
6287
+ * to re-open each entry and graft `afterClosed` back into the original
6288
+ * subject.
6289
+ */
6290
+ function consumePendingHmrDialogs() {
6291
+ const slot = globalThis[STASH_KEY];
6292
+ if (!Array.isArray(slot)) {
6293
+ return [];
6294
+ }
6295
+ delete globalThis[STASH_KEY];
6296
+ return slot.filter((entry) => !!entry && typeof entry.componentClass === 'function');
6297
+ }
6298
+ /**
6299
+ * Remove the pending captures without restoring. Useful when a reboot happens
6300
+ * for reasons other than module replacement (e.g. platform dispose) and we
6301
+ * don't want stale modal state to leak into the next bootstrap.
6302
+ */
6303
+ function clearPendingHmrDialogs() {
6304
+ delete globalThis[STASH_KEY];
6305
+ }
6306
+ /**
6307
+ * Test/debug helper: read the current stash without consuming it.
6308
+ */
6309
+ function peekPendingHmrDialogs() {
6310
+ const slot = globalThis[STASH_KEY];
6311
+ return Array.isArray(slot) ? slot.slice() : [];
6312
+ }
6313
+ /**
6314
+ * Reach into the dialog ref's private `_afterClosed` subject. We touch the
6315
+ * private field intentionally — the ref class lives inside this package and
6316
+ * we want HMR restore to be a feature of the dialog system rather than a
6317
+ * reason to widen its public surface for everyone.
6318
+ */
6319
+ function readAfterClosedSubject(ref) {
6320
+ const candidate = ref._afterClosed;
6321
+ if (!candidate || typeof candidate !== 'object') {
6322
+ return undefined;
6323
+ }
6324
+ if (typeof candidate.next !== 'function') {
6325
+ return undefined;
6326
+ }
6327
+ return candidate;
6328
+ }
6329
+ /**
6330
+ * Used by the resume-side: if the stash references something that can no
6331
+ * longer be opened (e.g. the component class is dead post-reload), we still
6332
+ * need to release its consumers so awaited promises don't dangle forever.
6333
+ */
6334
+ function abortCapturedDialog(captured) {
6335
+ try {
6336
+ captured.graftAfterClosed(undefined);
6337
+ }
6338
+ catch {
6339
+ // Best-effort.
6340
+ }
6341
+ }
6342
+
6343
+ /**
6344
+ * Best-effort animation helpers used by the dialog HMR layer to make
6345
+ * the close + reopen round-trip feel like an in-place content refresh.
6346
+ *
6347
+ * They live in a tiny standalone module on purpose:
6348
+ *
6349
+ * - `dialog-services.ts` pulls in `@angular/core`, which Jest cannot
6350
+ * load in our spec runner without an extra ESM transform. By
6351
+ * keeping these helpers free of `@angular/core` we can unit-test
6352
+ * them in isolation (`dialog-hmr-animation.spec.ts`) while
6353
+ * `dialog-services.ts` re-exports them at the public API layer.
6354
+ * - The helpers are inherently best-effort: a missing
6355
+ * `_nativeModalRef`, a frozen `_modalAnimatedOptions` stack, or a
6356
+ * future `NativeDialogConfig` shape change must never break HMR
6357
+ * restore — we just fall back to the original animated behavior.
6358
+ */
6359
+ /**
6360
+ * Mutate the top of `parentView._modalAnimatedOptions` to `false` for
6361
+ * the given candidate so the imminent native close runs un-animated.
6362
+ *
6363
+ * iOS reads `_modalAnimatedOptions.slice(-1)[0]` when dismissing a
6364
+ * modal (see core `view-common.ts` / `view/index.ios.ts`). The
6365
+ * Angular dialog service only pushes one entry per open call, so the
6366
+ * top entry is the exact flag that controls the dismiss we're about
6367
+ * to trigger as part of the HMR root-view replacement.
6368
+ */
6369
+ function suppressNativeCloseAnimation(candidate) {
6370
+ if (!candidate.config?.preserveOnHmr) {
6371
+ return;
6372
+ }
6373
+ try {
6374
+ const modalRef = candidate.ref?._nativeModalRef;
6375
+ const parentView = modalRef?.parentView;
6376
+ const stack = parentView?._modalAnimatedOptions;
6377
+ if (Array.isArray(stack) && stack.length > 0) {
6378
+ stack[stack.length - 1] = false;
6379
+ }
6380
+ }
6381
+ catch {
6382
+ // Swallow: a missing `_nativeModalRef` / `_modalAnimatedOptions`
6383
+ // is acceptable — we just lose the no-animation optimisation.
6384
+ }
6385
+ }
6386
+ /**
6387
+ * Build a `NativeDialogConfig` clone of `original` whose
6388
+ * `nativeOptions.animated` is forced to `false`. Used when re-opening
6389
+ * a captured modal so the open animation matches the suppressed
6390
+ * close — together they make the HMR round-trip feel like a content
6391
+ * refresh instead of a close/reopen.
6392
+ */
6393
+ function buildNonAnimatedRestoreConfig(original) {
6394
+ // Clone via `Object.assign` so consumers holding the original
6395
+ // config (e.g. caching it for re-open) don't see mutations from
6396
+ // the HMR pathway.
6397
+ const cloned = Object.assign(new NativeDialogConfig(), original);
6398
+ cloned.nativeOptions = { ...(original?.nativeOptions || {}), animated: false };
6399
+ return cloned;
6400
+ }
6401
+
5616
6402
  let NativeModalRef = class NativeModalRef {
5617
6403
  constructor(_config, _injector, location) {
5618
6404
  this._config = _config;
@@ -5747,6 +6533,54 @@ NativeModalRef = __decorate([
5747
6533
  * Use of this source code is governed by an MIT-style license that can be
5748
6534
  * found in the LICENSE file at https://angular.io/license
5749
6535
  */
6536
+ /**
6537
+ * Dialog HMR lifecycle log.
6538
+ */
6539
+ function hmrDialogLog(message) {
6540
+ if (!isAngularHmrEnabled()) {
6541
+ return;
6542
+ }
6543
+ if (!NativeScriptDebug.isLogEnabled()) {
6544
+ return;
6545
+ }
6546
+ NativeScriptDebug.hmrLog(`[dialog] ${message}`);
6547
+ }
6548
+ /**
6549
+ * Lower-level dialog HMR wiring trace (module-realm count, NativeDialog
6550
+ * instance count, registry hits/misses). Distinct from `hmrDialogLog`
6551
+ * for greppability — both fan into the same Trace category so a single
6552
+ * `Trace.setCategories(NativeScriptDebug.hmrTraceCategory)` toggle
6553
+ * surfaces them all.
6554
+ */
6555
+ function hmrDialogDiag(message) {
6556
+ if (!isAngularHmrEnabled()) {
6557
+ return;
6558
+ }
6559
+ if (!NativeScriptDebug.isLogEnabled()) {
6560
+ return;
6561
+ }
6562
+ NativeScriptDebug.hmrLog(`[dialog-diag] ${message}`);
6563
+ }
6564
+ /**
6565
+ * Module-evaluation marker. Increments on every fresh evaluation of
6566
+ * `dialog-services.ts`. If we see this number rise on every HMR cycle,
6567
+ * the file is being re-evaluated (good). If it stays flat, the module
6568
+ * is being served from cache (bad — class identities won't change).
6569
+ */
6570
+ const DIALOG_MODULE_DIAG_KEY = '__NS_HMR_DIAG_DIALOG_MODULE__';
6571
+ function getDialogModuleDiag() {
6572
+ const slot = globalThis;
6573
+ if (!slot[DIALOG_MODULE_DIAG_KEY]) {
6574
+ slot[DIALOG_MODULE_DIAG_KEY] = { evals: 0, instances: 0, lastEvalAt: 0 };
6575
+ }
6576
+ return slot[DIALOG_MODULE_DIAG_KEY];
6577
+ }
6578
+ {
6579
+ const md = getDialogModuleDiag();
6580
+ md.evals += 1;
6581
+ md.lastEvalAt = Date.now();
6582
+ hmrDialogDiag(`module-eval count=${md.evals} (file=dialog-services.ts) timestamp=${md.lastEvalAt}`);
6583
+ }
5750
6584
  /** Injection token that can be used to access the data that was passed in to a dialog. */
5751
6585
  const NATIVE_DIALOG_DATA = new InjectionToken('NativeDialogData');
5752
6586
  /** Injection token that can be used to specify default dialog options. */
@@ -5760,6 +6594,14 @@ class NativeDialog {
5760
6594
  this._openDialogsAtThisLevel = [];
5761
6595
  this._afterAllClosedAtThisLevel = new Subject();
5762
6596
  this._afterOpenedAtThisLevel = new Subject();
6597
+ /**
6598
+ * Maps each open dialog ref back to the `(componentClass, config)` pair it
6599
+ * was opened with so the HMR snapshot can replay the call later. Dialogs
6600
+ * opened with a `TemplateRef` are tracked with `componentClass: undefined`
6601
+ * — the HMR layer skips them automatically.
6602
+ */
6603
+ this._openDialogMetadata = new WeakMap();
6604
+ this._hmrSubscriptions = [];
5763
6605
  // TODO (jelbourn): tighten the typing right-hand side of this expression.
5764
6606
  /**
5765
6607
  * Stream that emits when all open dialog have finished closing.
@@ -5777,6 +6619,50 @@ class NativeDialog {
5777
6619
  this._nativeModalType = NativeModalRef;
5778
6620
  this._dialogDataToken = NATIVE_DIALOG_DATA;
5779
6621
  this.locationStrategy = inject(NSLocationStrategy);
6622
+ // Bumps a global counter so we can detect duplicate or leaked
6623
+ // `NativeDialog` instances across HMR cycles. Field initialiser
6624
+ // ordering: this MUST run before `_initHmrLifecycle()` below so the
6625
+ // log line in that helper can include the assigned id.
6626
+ this._diagInstanceIdAssign = (() => {
6627
+ const md = getDialogModuleDiag();
6628
+ md.instances += 1;
6629
+ this._diagInstanceId = md.instances;
6630
+ hmrDialogDiag(`NativeDialog ctor instanceId=${md.instances} hasParentDialog=${!!this._parentDialog} moduleEvalCount=${md.evals}`);
6631
+ return null;
6632
+ })();
6633
+ // Initialise after every dependency above so the subscriptions can call
6634
+ // back into `this.open(...)` and `this.openDialogs` safely. The result is
6635
+ // unused — we just want a side-effect at construction time.
6636
+ this._hmrInitMarker = this._initHmrLifecycle();
6637
+ /**
6638
+ * Tracks whether a restore has already been scheduled for this
6639
+ * `NativeDialog` instance's lifetime. We only need to restore once
6640
+ * per HMR cycle — the rxjs `ReplaySubject(1)` for
6641
+ * `postAngularBootstrap$` delivers both the *previous* cycle's
6642
+ * cached event (replay on subscribe) **and** the *current* cycle's
6643
+ * fresh event, and the constructor stash peek can independently
6644
+ * notice pending work. Without this guard each of those triggers
6645
+ * would queue its own `setTimeout` and the logs would show two or
6646
+ * three "scheduling restore" lines per save.
6647
+ *
6648
+ * The guard is per-instance and the stash itself is the source of
6649
+ * truth: `_restorePendingDialogs` calls `consumePendingHmrDialogs()`
6650
+ * which atomically clears the stash, so even if the guard somehow
6651
+ * fired twice, only the first call would do real work.
6652
+ */
6653
+ this._restoreScheduledForThisInstance = false;
6654
+ /**
6655
+ * Per-cycle guard to keep `_restorePendingDialogs` idempotent if more
6656
+ * than one subscriber fires for the same bootstrap event. The
6657
+ * regression we have seen (`postAngularBootstrap$ → restore` logged
6658
+ * twice in the same hot reload) was caused by the `NativeDialogModule`
6659
+ * also listing `NativeDialog` in its `providers` array. That has been
6660
+ * removed, but we keep this flag as a defensive net so a future stray
6661
+ * subscription does not consume the stash twice. The flag is reset
6662
+ * after the consume so subsequent HMR cycles can run their own
6663
+ * restore.
6664
+ */
6665
+ this._restoreInFlight = false;
5780
6666
  }
5781
6667
  /** Keeps track of the currently-open dialogs. */
5782
6668
  get openDialogs() {
@@ -5786,42 +6672,363 @@ class NativeDialog {
5786
6672
  get afterOpened() {
5787
6673
  return this._parentDialog ? this._parentDialog.afterOpened : this._afterOpenedAtThisLevel;
5788
6674
  }
5789
- _getAfterAllClosed() {
5790
- const parent = this._parentDialog;
5791
- return parent ? parent._getAfterAllClosed() : this._afterAllClosedAtThisLevel;
6675
+ _getAfterAllClosed() {
6676
+ const parent = this._parentDialog;
6677
+ return parent ? parent._getAfterAllClosed() : this._afterAllClosedAtThisLevel;
6678
+ }
6679
+ open(componentOrTemplateRef, config) {
6680
+ config = _applyConfigDefaults(config, this._defaultOptions || new NativeDialogConfig());
6681
+ if (config.id && this.getDialogById(config.id) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
6682
+ throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
6683
+ }
6684
+ const dialogRef = this._attachDialogContent(componentOrTemplateRef, config);
6685
+ this.openDialogs.push(dialogRef);
6686
+ this._openDialogMetadata.set(dialogRef, {
6687
+ componentClass: componentOrTemplateRef instanceof TemplateRef ? undefined : componentOrTemplateRef,
6688
+ config,
6689
+ });
6690
+ dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
6691
+ this.afterOpened.next(dialogRef);
6692
+ // Notify the dialog container that the content has been attached.
6693
+ // dialogContainer._initializeWithAttachedContent();
6694
+ return dialogRef;
6695
+ }
6696
+ /**
6697
+ * Closes all of the currently-open dialogs.
6698
+ */
6699
+ closeAll() {
6700
+ this._closeDialogs(this.openDialogs);
6701
+ }
6702
+ /**
6703
+ * Finds an open dialog by its id.
6704
+ * @param id ID to use when looking up the dialog.
6705
+ */
6706
+ getDialogById(id) {
6707
+ return this.openDialogs.find((dialog) => dialog.id === id);
6708
+ }
6709
+ ngOnDestroy() {
6710
+ hmrDialogDiag(`NativeDialog ngOnDestroy instanceId=${this._diagInstanceId} openCount=${this._openDialogsAtThisLevel.length} subCount=${this._hmrSubscriptions.length}`);
6711
+ // Only close the dialogs at this level on destroy
6712
+ // since the parent service may still be active.
6713
+ this._closeDialogs(this._openDialogsAtThisLevel);
6714
+ this._afterAllClosedAtThisLevel.complete();
6715
+ this._afterOpenedAtThisLevel.complete();
6716
+ for (const sub of this._hmrSubscriptions) {
6717
+ try {
6718
+ sub.unsubscribe();
6719
+ }
6720
+ catch {
6721
+ // Best-effort: tearing down the dialog service shouldn't prevent the
6722
+ // rest of the module disposal from completing.
6723
+ }
6724
+ }
6725
+ this._hmrSubscriptions = [];
6726
+ }
6727
+ /**
6728
+ * Wires up HMR capture/restore. Only the root-level dialog manages the
6729
+ * stash so a stack of `NativeDialog` instances inside a child injector
6730
+ * doesn't fight for it.
6731
+ *
6732
+ * Production short-circuit: `isAngularHmrEnabled()` returns `false` in
6733
+ * release builds and when no NS Vite / webpack HMR runtime is present,
6734
+ * so the long-lived subscriptions below never attach in shipping apps.
6735
+ *
6736
+ * `postAngularBootstrap$` is a `ReplaySubject(1)` (see `application.ts`)
6737
+ * which means a `NativeDialog` instantiated *after* the bootstrap event
6738
+ * has already fired (typical when the user app injects `NativeDialog`
6739
+ * lazily via a service like `view.service.ts`) still receives the
6740
+ * buffered event and runs the restore path.
6741
+ */
6742
+ _initHmrLifecycle() {
6743
+ if (this._parentDialog) {
6744
+ hmrDialogDiag(`_initHmrLifecycle skipped (has parent dialog) instanceId=${this._diagInstanceId}`);
6745
+ return null;
6746
+ }
6747
+ if (!isAngularHmrEnabled()) {
6748
+ return null;
6749
+ }
6750
+ hmrDialogDiag(`_initHmrLifecycle wiring up subscriptions instanceId=${this._diagInstanceId} moduleEvalCount=${getDialogModuleDiag().evals}`);
6751
+ const dispose = preAngularDisposal$.subscribe((event) => {
6752
+ if (event.moduleType !== 'main' || event.reason !== 'hotreload') {
6753
+ return;
6754
+ }
6755
+ hmrDialogDiag(`preAngularDisposal$ fired (reason=${event.reason}) instanceId=${this._diagInstanceId}`);
6756
+ this._captureOpenDialogsForHmr();
6757
+ });
6758
+ const bootstrap = postAngularBootstrap$.subscribe((event) => {
6759
+ if (event.moduleType !== 'main' || event.reason !== 'hotreload') {
6760
+ return;
6761
+ }
6762
+ hmrDialogDiag(`postAngularBootstrap$ fired (reason=${event.reason}) instanceId=${this._diagInstanceId}`);
6763
+ this._maybeScheduleRestore(`postAngularBootstrap$ (reason=${event.reason})`);
6764
+ });
6765
+ this._hmrSubscriptions.push(dispose, bootstrap);
6766
+ // Belt-and-suspenders: even though `postAngularBootstrap$` replays
6767
+ // the last event for late subscribers, also peek the global stash
6768
+ // here. This catches the case where `NativeDialog` is instantiated
6769
+ // lazily — *after* `emitModuleBootstrapEvent` has fired and the
6770
+ // ReplaySubject's buffered event no longer matches the current
6771
+ // cycle. The `_maybeScheduleRestore` guard makes this a no-op when
6772
+ // the bootstrap subscriber already queued work.
6773
+ const pendingNow = peekPendingHmrDialogs();
6774
+ hmrDialogDiag(`_initHmrLifecycle stash peek pending=${pendingNow.length} instanceId=${this._diagInstanceId}`);
6775
+ if (pendingNow.length > 0) {
6776
+ this._maybeScheduleRestore(`stash peek on ctor: ${pendingNow.length} pending dialog(s)`);
6777
+ }
6778
+ return null;
6779
+ }
6780
+ /**
6781
+ * Schedule a restore exactly once per `NativeDialog` instance.
6782
+ *
6783
+ * The work is deferred to the next macrotask for two reasons:
6784
+ *
6785
+ * 1. `postAngularBootstrap$.next(...)` is fired from inside
6786
+ * `emitModuleBootstrapEvent`, which itself runs inside the
6787
+ * `bootstrapApplication` callback — so the call stack still
6788
+ * contains Angular's ApplicationRef bootstrap pipeline. Doing
6789
+ * `this.open(...)` synchronously re-entered Angular DI while a
6790
+ * `providedIn: 'root'` factory could still be on the resolution
6791
+ * stack, which surfaced as `NG0200: Circular dependency detected
6792
+ * for NativeDialog`. Yielding to a macrotask lets the bootstrap
6793
+ * stack fully unwind first.
6794
+ * 2. The eventual `parent.showModal(...)` cannot present onto a
6795
+ * view controller whose view is not yet in the iOS window
6796
+ * hierarchy (see `_scheduleRestoreOpenWhenReady` for the second
6797
+ * wait stage); a synchronous attempt would silently no-op
6798
+ * because iOS rejects the present without throwing.
6799
+ */
6800
+ _maybeScheduleRestore(triggerDescription) {
6801
+ if (this._restoreScheduledForThisInstance) {
6802
+ hmrDialogDiag(`_maybeScheduleRestore SKIP duplicate trigger=${triggerDescription} instanceId=${this._diagInstanceId}`);
6803
+ return;
6804
+ }
6805
+ this._restoreScheduledForThisInstance = true;
6806
+ hmrDialogLog(`scheduling restore (trigger=${triggerDescription}) instanceId=${this._diagInstanceId}`);
6807
+ setTimeout(() => {
6808
+ void this._restorePendingDialogs();
6809
+ }, 0);
5792
6810
  }
5793
- open(componentOrTemplateRef, config) {
5794
- config = _applyConfigDefaults(config, this._defaultOptions || new NativeDialogConfig());
5795
- if (config.id && this.getDialogById(config.id) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
5796
- throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
6811
+ _captureOpenDialogsForHmr() {
6812
+ const candidates = this._openDialogsAtThisLevel.map((ref) => {
6813
+ const meta = this._openDialogMetadata.get(ref);
6814
+ return {
6815
+ ref,
6816
+ componentClass: meta?.componentClass,
6817
+ config: meta?.config ?? new NativeDialogConfig(),
6818
+ };
6819
+ });
6820
+ hmrDialogDiag(`_captureOpenDialogsForHmr instanceId=${this._diagInstanceId} candidates=${candidates.length} (${candidates.map((c) => `${c.componentClass?.name ?? '(template)'}|preserveOnHmr=${!!c.config?.preserveOnHmr}`).join(', ')})`);
6821
+ const captured = captureDialogsForHmr(candidates);
6822
+ if (captured.length > 0) {
6823
+ // Suppress the close animation that's about to fire as part of
6824
+ // `_closeAllModalViewsInternal()` during root-view replacement.
6825
+ // Without this the user sees a slide-down + slide-up flicker
6826
+ // wrapping every HMR reboot.
6827
+ for (const candidate of candidates) {
6828
+ suppressNativeCloseAnimation(candidate);
6829
+ }
6830
+ hmrDialogLog(`captured ${captured.length} dialog(s) for HMR restore [${captured.map((c) => c.componentName).join(', ')}]`);
6831
+ }
6832
+ else if (this._openDialogsAtThisLevel.length > 0) {
6833
+ hmrDialogLog(`skipped capture: ${this._openDialogsAtThisLevel.length} open dialog(s) but none preservable`);
6834
+ }
6835
+ if (captured.length > 0 && NativeScriptDebug.isLogEnabled()) {
6836
+ NativeScriptDebug.hmrLog(`captured ${captured.length} dialog(s) for HMR restore`);
6837
+ }
6838
+ }
6839
+ async _restorePendingDialogs() {
6840
+ if (this._restoreInFlight) {
6841
+ hmrDialogLog('skipping restore: already in flight');
6842
+ return;
6843
+ }
6844
+ const pending = consumePendingHmrDialogs();
6845
+ if (pending.length === 0) {
6846
+ return;
6847
+ }
6848
+ this._restoreInFlight = true;
6849
+ hmrDialogLog(`restoring ${pending.length} dialog(s) after reboot [${pending.map((c) => c.componentName).join(', ')}]`);
6850
+ if (NativeScriptDebug.isLogEnabled()) {
6851
+ NativeScriptDebug.hmrLog(`restoring ${pending.length} dialog(s) after HMR reboot`);
6852
+ }
6853
+ try {
6854
+ for (const captured of pending) {
6855
+ this._restoreSingleDialog(captured);
6856
+ }
6857
+ }
6858
+ finally {
6859
+ this._restoreInFlight = false;
5797
6860
  }
5798
- const dialogRef = this._attachDialogContent(componentOrTemplateRef, config);
5799
- this.openDialogs.push(dialogRef);
5800
- dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
5801
- this.afterOpened.next(dialogRef);
5802
- // Notify the dialog container that the content has been attached.
5803
- // dialogContainer._initializeWithAttachedContent();
5804
- return dialogRef;
5805
6861
  }
5806
6862
  /**
5807
- * Closes all of the currently-open dialogs.
6863
+ * Resolve the freshest known class object for the captured component
6864
+ * name and re-open the dialog through the normal `open` path, but
6865
+ * only once the new root view is actually attached to the iOS window
6866
+ * hierarchy.
6867
+ *
6868
+ * Why a class lookup at all: an HMR reboot calls
6869
+ * `ɵresetCompiledComponents()` which clears each component's `ɵcmp`
6870
+ * field but **leaves the class identity unchanged**. The patched
6871
+ * `ɵɵdefineComponent` then re-registers the same class object under
6872
+ * the same source name when Angular re-renders the component. A
6873
+ * fresh-class lookup therefore returns either the same object the
6874
+ * stash captured (most common) or, on the rare occasion that the
6875
+ * source file's `@Component` decorator was re-evaluated into a brand
6876
+ * new class object (e.g. the user added `@Component(...)` to a new
6877
+ * exported symbol), the live one. Either way, a single check is
6878
+ * enough — the previous retry schedule was a no-op in 100 % of
6879
+ * observed cycles because the captured class IS the live class.
5808
6880
  */
5809
- closeAll() {
5810
- this._closeDialogs(this.openDialogs);
6881
+ _restoreSingleDialog(captured) {
6882
+ const live = getFreshComponentClass(captured.componentName);
6883
+ const componentClass = live ?? captured.componentClass;
6884
+ const usingFresh = !!live && live !== captured.componentClass;
6885
+ // Detailed diagnostic to disambiguate the three reasons
6886
+ // `usingFreshClass=false` could log:
6887
+ // 1. liveDefined=false: the patched ɵɵdefineComponent never
6888
+ // registered a class for this name in the new realm. Most
6889
+ // likely cause: the component module was not re-evaluated
6890
+ // after the eviction (file not in `evictPaths`, or runtime
6891
+ // did not actually evict it).
6892
+ // 2. liveDefined=true but live === captured: the file WAS
6893
+ // re-evaluated, but the registry still hands back the same
6894
+ // class object. This shouldn't happen post-reboot for a
6895
+ // truly fresh realm; if it does, the captured snapshot was
6896
+ // taken from the same realm that's now serving the live
6897
+ // class — i.e. the disposal path is not actually disposing
6898
+ // the old realm before the new one boots.
6899
+ // 3. liveDefined=true and live !== captured: usingFresh path,
6900
+ // the registry IS doing its job. This is the success case.
6901
+ const liveDefined = !!live;
6902
+ const sameAsCapture = liveDefined && live === captured.componentClass;
6903
+ hmrDialogDiag(`_restoreSingleDialog name=${captured.componentName} liveDefined=${liveDefined} sameAsCapture=${sameAsCapture} usingFresh=${usingFresh} capturedFnName=${captured.componentClass?.name ?? '(none)'} liveFnName=${live?.name ?? '(none)'}`);
6904
+ this._scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh);
5811
6905
  }
5812
6906
  /**
5813
- * Finds an open dialog by its id.
5814
- * @param id ID to use when looking up the dialog.
6907
+ * Maximum time we'll wait for the new root view to attach to the
6908
+ * iOS window before giving up and trying the open anyway. NS Vite's
6909
+ * worst observed reboot-to-rootview-loaded gap is ~250 ms; one
6910
+ * second leaves headroom for slower devices without leaving the
6911
+ * captured dialog stuck in the stash if something genuinely goes
6912
+ * wrong.
5815
6913
  */
5816
- getDialogById(id) {
5817
- return this.openDialogs.find((dialog) => dialog.id === id);
6914
+ static { this._ROOT_VIEW_LOADED_TIMEOUT_MS = 1_000; }
6915
+ /**
6916
+ * Defer the actual `this.open(...)` call until the new root view's
6917
+ * underlying iOS `UIViewController.view` is in the window hierarchy.
6918
+ *
6919
+ * iOS silently rejects `presentViewControllerAnimatedCompletion` if
6920
+ * the parent controller's view is not yet attached to a `UIWindow`
6921
+ * (see `view/index.ios.ts::_showNativeModalView`, which logs to
6922
+ * `Trace` and returns without throwing). In dev that would surface
6923
+ * as a vanished modal with no actionable error.
6924
+ *
6925
+ * NS marks a view as `isLoaded === true` from
6926
+ * `UILayoutViewController.viewWillAppear`, which fires once iOS has
6927
+ * decided to add the view to its window. Listening for the
6928
+ * `loadedEvent` (one-shot) plus an extra macrotask gives UIKit a
6929
+ * chance to finish window attachment before we present.
6930
+ */
6931
+ _scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh) {
6932
+ const rootView = Application.getRootView();
6933
+ if (rootView && rootView.isLoaded) {
6934
+ // Even when isLoaded is already true we yield once: a previous
6935
+ // capture in the same hot reload cycle may have set isLoaded on
6936
+ // the *outgoing* root view and a new root view is about to take
6937
+ // over. A single setTimeout(0) gives `setWindowContent` a chance
6938
+ // to finish swapping `win.rootViewController` before we try to
6939
+ // present on top of it.
6940
+ setTimeout(() => this._performRestoreOpen(captured, componentClass, usingFresh), 0);
6941
+ return;
6942
+ }
6943
+ if (!rootView) {
6944
+ // No root view at all yet — extremely unusual at this point in
6945
+ // the bootstrap, but protect against it by polling. We use the
6946
+ // same timeout budget as the loaded path.
6947
+ this._pollForRootView(captured, componentClass, usingFresh, Date.now());
6948
+ return;
6949
+ }
6950
+ hmrDialogLog(`restore ${captured.componentName} waiting for root view loadedEvent`);
6951
+ let settled = false;
6952
+ const onLoaded = () => {
6953
+ if (settled)
6954
+ return;
6955
+ settled = true;
6956
+ try {
6957
+ rootView.off(View.loadedEvent, onLoaded);
6958
+ }
6959
+ catch {
6960
+ // off may throw on stale view bindings; the `settled` flag
6961
+ // already prevents a double-fire.
6962
+ }
6963
+ // Defer one tick after viewWillAppear so UIKit completes the
6964
+ // actual window attachment (`view.window` is set during the
6965
+ // view-controller transition that follows viewWillAppear).
6966
+ setTimeout(() => this._performRestoreOpen(captured, componentClass, usingFresh), 0);
6967
+ };
6968
+ try {
6969
+ rootView.once(View.loadedEvent, onLoaded);
6970
+ }
6971
+ catch {
6972
+ // If the event subscription fails (ancient core builds), fall
6973
+ // back to a tiny delay — better to attempt the open and have
6974
+ // iOS log a benign trace than to leak the dialog stash.
6975
+ setTimeout(() => onLoaded(), 50);
6976
+ }
6977
+ // Bound the wait so a never-loading root view can't permanently
6978
+ // pin the captured dialog in the stash.
6979
+ setTimeout(() => {
6980
+ if (settled)
6981
+ return;
6982
+ hmrDialogLog(`restore ${captured.componentName} root view never loaded within ${NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS}ms; attempting open anyway`);
6983
+ onLoaded();
6984
+ }, NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS);
6985
+ }
6986
+ _pollForRootView(captured, componentClass, usingFresh, startedAt) {
6987
+ const rootView = Application.getRootView();
6988
+ if (rootView) {
6989
+ this._scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh);
6990
+ return;
6991
+ }
6992
+ if (Date.now() - startedAt > NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS) {
6993
+ hmrDialogLog(`restore ${captured.componentName} aborted: no root view after ${NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS}ms`);
6994
+ abortCapturedDialog(captured);
6995
+ return;
6996
+ }
6997
+ setTimeout(() => this._pollForRootView(captured, componentClass, usingFresh, startedAt), 16);
5818
6998
  }
5819
- ngOnDestroy() {
5820
- // Only close the dialogs at this level on destroy
5821
- // since the parent service may still be active.
5822
- this._closeDialogs(this._openDialogsAtThisLevel);
5823
- this._afterAllClosedAtThisLevel.complete();
5824
- this._afterOpenedAtThisLevel.complete();
6999
+ _performRestoreOpen(captured, componentClass, usingFresh) {
7000
+ hmrDialogLog(`restore ${captured.componentName} usingFreshClass=${usingFresh}`);
7001
+ if (NativeScriptDebug.isLogEnabled() && usingFresh) {
7002
+ NativeScriptDebug.hmrLog(`HMR modal restore using fresh class for ${captured.componentName}`);
7003
+ }
7004
+ // Force the restored modal to open without animation so the round-
7005
+ // trip looks like an instant content refresh rather than a full
7006
+ // close-and-reopen sequence.
7007
+ const restoreConfig = buildNonAnimatedRestoreConfig(captured.config);
7008
+ try {
7009
+ const newRef = this.open(componentClass, restoreConfig);
7010
+ hmrDialogLog(`restore ${captured.componentName} → opened newRef.id=${newRef?.id ?? 'n/a'}`);
7011
+ newRef.afterClosed().subscribe({
7012
+ next: (value) => captured.graftAfterClosed(value),
7013
+ complete: () => captured.graftAfterClosed(undefined),
7014
+ });
7015
+ }
7016
+ catch (err) {
7017
+ abortCapturedDialog(captured);
7018
+ const message = err?.message ?? String(err);
7019
+ hmrDialogLog(`restore ${captured.componentName} FAILED: ${message}`);
7020
+ if (NativeScriptDebug.isLogEnabled()) {
7021
+ NativeScriptDebug.hmrLogError(`HMR modal restore failed: ${message}`);
7022
+ }
7023
+ }
7024
+ }
7025
+ /**
7026
+ * Test/debug helper: discard any captured modals without restoring them.
7027
+ * Mostly useful when projects want to opt out of restoration without
7028
+ * recompiling. Public callers should prefer `preserveOnHmr: false` instead.
7029
+ */
7030
+ static _clearPendingHmrDialogs() {
7031
+ clearPendingHmrDialogs();
5825
7032
  }
5826
7033
  /**
5827
7034
  * Attaches the user-provided component to the already-created dialog container.
@@ -5886,6 +7093,7 @@ class NativeDialog {
5886
7093
  const index = this.openDialogs.indexOf(dialogRef);
5887
7094
  if (index > -1) {
5888
7095
  this.openDialogs.splice(index, 1);
7096
+ this._openDialogMetadata.delete(dialogRef);
5889
7097
  // If all the dialogs were closed, remove/restore the `aria-hidden`
5890
7098
  // to a the siblings and emit to the `afterAllClosed` stream.
5891
7099
  if (!this.openDialogs.length) {
@@ -5922,6 +7130,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
5922
7130
  function _applyConfigDefaults(config, defaultOptions) {
5923
7131
  return { ...defaultOptions, ...config };
5924
7132
  }
7133
+ /**
7134
+ * Register `NativeDialog` with the application's HMR eager-instantiate
7135
+ * registry so the post-bootstrap pipeline forces an `injector.get()` on
7136
+ * the service in the same JS task as the bootstrap event. Without this,
7137
+ * `NativeDialog` is only constructed when something in user-app code
7138
+ * injects it (typically a wrapper service that opens modals). When that
7139
+ * injection happens lazily — on first user-driven modal open — captured
7140
+ * dialogs from a prior HMR cycle wait until the user reopens *something*
7141
+ * before the new realm even sees the stash. Eager instantiation makes
7142
+ * the restore work happen as early as possible during a hot reload while
7143
+ * staying gated to dev mode + HMR-active environments.
7144
+ *
7145
+ * Registered after the class declaration so the closure references the
7146
+ * fully-defined class rather than tripping the temporal dead zone.
7147
+ *
7148
+ * Idempotent: the registry de-dupes function references, so multiple
7149
+ * evaluations of this module across HMR cycles never accumulate stale
7150
+ * registrations.
7151
+ */
7152
+ if (isAngularHmrEnabled()) {
7153
+ const added = registerHmrEagerInstantiator((injector) => {
7154
+ try {
7155
+ const inst = injector.get(NativeDialog, null);
7156
+ hmrDialogDiag(`eager-instantiator fired NativeDialog=${inst ? `instance#${inst._diagInstanceId}` : 'null'}`);
7157
+ }
7158
+ catch (err) {
7159
+ hmrDialogDiag(`eager-instantiator threw: ${err?.message ?? err}`);
7160
+ // Some user-app providers may not include `NativeDialog` (e.g. a
7161
+ // module that doesn't depend on the dialog feature). The registry
7162
+ // contract is "best effort": failing to find the token must be a
7163
+ // silent no-op so unrelated apps aren't penalized.
7164
+ }
7165
+ });
7166
+ hmrDialogDiag(`registerHmrEagerInstantiator added=${added} (false means already present in registry)`);
7167
+ }
5925
7168
 
5926
7169
  /**
5927
7170
  * @license
@@ -5997,17 +7240,42 @@ function getClosestDialog(element, openDialogs) {
5997
7240
  return view ? openDialogs.find((dialog) => dialog.id === view['__ng_modal_id__']) : null;
5998
7241
  }
5999
7242
 
7243
+ /**
7244
+ * Convenience module that re-exports the `NativeDialogCloseDirective` for
7245
+ * template-driven `[nativeDialogClose]` usage.
7246
+ *
7247
+ * **Important**: `NativeDialog` itself is **not** listed in this module's
7248
+ * `providers` array. The service is `@Injectable({ providedIn: 'root' })`,
7249
+ * which already registers a single root-level instance and is fully
7250
+ * tree-shakeable. Listing it here as well caused Angular to treat the
7251
+ * module-level provider and the `providedIn: 'root'` factory as
7252
+ * *separate* registrations once the module was pulled into a standalone
7253
+ * app via `importProvidersFrom(NativeDialogModule, ...)`. The duplicate
7254
+ * registration triggered:
7255
+ *
7256
+ * - Two `NativeDialog` instances in the same root environment injector,
7257
+ * each subscribing to `postAngularBootstrap$`, which produced
7258
+ * duplicate restore attempts during HMR.
7259
+ * - `NG0200: Circular dependency detected for NativeDialog` while a
7260
+ * captured modal was being re-opened during HMR restore, because the
7261
+ * second resolution started while the first was still in progress.
7262
+ *
7263
+ * Removing the redundant entry collapses both providers back into a
7264
+ * single root-level instance, which is what `providedIn: 'root'`
7265
+ * documents. App authors who explicitly wire `NativeDialog` themselves
7266
+ * (e.g. in a feature module's `providers`) keep working unchanged
7267
+ * because they're targeting the same class symbol.
7268
+ */
6000
7269
  class NativeDialogModule {
6001
7270
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
6002
7271
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, imports: [NativeDialogCloseDirective], exports: [NativeDialogCloseDirective] }); }
6003
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, providers: [NativeDialog] }); }
7272
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule }); }
6004
7273
  }
6005
7274
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, decorators: [{
6006
7275
  type: NgModule,
6007
7276
  args: [{
6008
7277
  imports: [NativeDialogCloseDirective],
6009
7278
  exports: [NativeDialogCloseDirective],
6010
- providers: [NativeDialog],
6011
7279
  }]
6012
7280
  }] });
6013
7281
 
@@ -7886,9 +9154,37 @@ function cloneRoutesForBootstrap(routes) {
7886
9154
  const CURRENT_ROUTE_KEY = '__NS_ANGULAR_HMR_CURRENT_ROUTE__';
7887
9155
  const PENDING_START_PATH_KEY = '__NS_ANGULAR_HMR_PENDING_START_PATH__';
7888
9156
  const CAPTURE_ROUTE_KEY = '__NS_CAPTURE_ANGULAR_HMR_ROUTE__';
9157
+ // Stack of normalized URLs that mirrors Angular Router's back-stack while the
9158
+ // app is running, and is snapshotted into `PENDING_HISTORY_KEY` when an HMR
9159
+ // reboot is about to fire. After the new module bootstraps, the router replay
9160
+ // hook walks the stack to rebuild the back-stack so users keep their back
9161
+ // navigation across HMR cycles.
9162
+ const HISTORY_KEY = '__NS_ANGULAR_HMR_ROUTE_HISTORY__';
9163
+ const PENDING_HISTORY_KEY = '__NS_ANGULAR_HMR_PENDING_HISTORY__';
9164
+ // Window flag set while the new bootstrap is mid-replay of a captured route
9165
+ // stack. User-app code can consult this to skip default navigations that
9166
+ // would otherwise stomp the route the framework is restoring (e.g. a
9167
+ // bottom-nav component that defaults to its first tab on init when no
9168
+ // signal-backed selection exists).
9169
+ const RESTORING_KEY = '__NS_ANGULAR_HMR_RESTORING_ROUTE__';
9170
+ const RESTORING_TARGET_KEY = '__NS_ANGULAR_HMR_RESTORING_ROUTE_TARGET__';
7889
9171
  function getGlobalState() {
7890
9172
  return globalThis;
7891
9173
  }
9174
+ function readHistoryArray(key) {
9175
+ const g = getGlobalState();
9176
+ const raw = g[key];
9177
+ return Array.isArray(raw) ? raw.filter((entry) => typeof entry === 'string') : [];
9178
+ }
9179
+ function writeHistoryArray(key, history) {
9180
+ const g = getGlobalState();
9181
+ if (history.length > 0) {
9182
+ g[key] = history.slice();
9183
+ }
9184
+ else {
9185
+ delete g[key];
9186
+ }
9187
+ }
7892
9188
  function normalizeAngularHmrRouteUrl(value) {
7893
9189
  if (typeof value !== 'string') {
7894
9190
  return null;
@@ -7926,8 +9222,31 @@ function captureAngularHmrPendingStartPath(value, source = 'hmr-reboot') {
7926
9222
  return writeAngularHmrRouteState(value, { pending: true, source });
7927
9223
  }
7928
9224
  function readAngularHmrPendingStartPath() {
9225
+ // When a back-stack snapshot exists we boot to the bottom of the stack and
9226
+ // let `replayAngularHmrPendingForwardNavigations` walk the rest. Otherwise
9227
+ // fall back to the legacy single-URL slot so projects without history
9228
+ // tracking still land on the page they were viewing.
9229
+ const pendingHistory = readHistoryArray(PENDING_HISTORY_KEY);
9230
+ if (pendingHistory.length > 0) {
9231
+ // Open the restoring-route window so user-app default navigations
9232
+ // can step out of the framework's way until replay completes. The
9233
+ // forward-navigation walk in `NativeScriptAngularHmrRouteReplay`
9234
+ // closes the window after the final URL lands or fails. We pass
9235
+ // the deepest captured URL so consumers can compare against the
9236
+ // active router URL if they want fine-grained suppression.
9237
+ beginAngularHmrRouteRestore(pendingHistory[pendingHistory.length - 1]);
9238
+ return pendingHistory[0];
9239
+ }
7929
9240
  const g = getGlobalState();
7930
- return normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9241
+ const fallback = normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9242
+ if (fallback) {
9243
+ // Single-URL fallback path: user-app code should still suppress
9244
+ // default navigations briefly — the new bootstrap is about to
9245
+ // navigate to `fallback`, so a default tab init that fires first
9246
+ // would still stomp it.
9247
+ beginAngularHmrRouteRestore(fallback);
9248
+ }
9249
+ return fallback;
7931
9250
  }
7932
9251
  function invokeAngularHmrRouteCapture() {
7933
9252
  const g = getGlobalState();
@@ -7951,20 +9270,402 @@ function installAngularHmrRouteCaptureHook(capture) {
7951
9270
  }
7952
9271
  };
7953
9272
  }
9273
+ // ---- back-stack history primitives ------------------------------------------
9274
+ /**
9275
+ * Push a URL onto the live back-stack mirror. The mirror is collapsed when the
9276
+ * incoming URL equals the top — Angular fires multiple `NavigationEnd` events
9277
+ * for the same URL during certain `replaceUrl` scenarios and we don't want to
9278
+ * inflate the stack.
9279
+ */
9280
+ function pushAngularHmrRouteHistoryEntry(value) {
9281
+ const url = normalizeAngularHmrRouteUrl(value);
9282
+ if (!url) {
9283
+ return null;
9284
+ }
9285
+ const history = readHistoryArray(HISTORY_KEY);
9286
+ if (history.length > 0 && history[history.length - 1] === url) {
9287
+ return url;
9288
+ }
9289
+ history.push(url);
9290
+ writeHistoryArray(HISTORY_KEY, history);
9291
+ return url;
9292
+ }
9293
+ /**
9294
+ * Pop the top of the live back-stack mirror. Used when Angular reports a
9295
+ * `popstate`-triggered navigation so the mirror tracks back navigations.
9296
+ */
9297
+ function popAngularHmrRouteHistoryEntry() {
9298
+ const history = readHistoryArray(HISTORY_KEY);
9299
+ if (history.length === 0) {
9300
+ return null;
9301
+ }
9302
+ const popped = history.pop() ?? null;
9303
+ writeHistoryArray(HISTORY_KEY, history);
9304
+ return popped;
9305
+ }
9306
+ /**
9307
+ * Replace the top of the live back-stack mirror. Used when Angular reports a
9308
+ * `NavigationEnd` with `replaceUrl=true`, e.g. canonical-redirect cycles.
9309
+ */
9310
+ function replaceAngularHmrRouteHistoryTop(value) {
9311
+ const url = normalizeAngularHmrRouteUrl(value);
9312
+ if (!url) {
9313
+ return null;
9314
+ }
9315
+ const history = readHistoryArray(HISTORY_KEY);
9316
+ if (history.length === 0) {
9317
+ history.push(url);
9318
+ }
9319
+ else {
9320
+ history[history.length - 1] = url;
9321
+ }
9322
+ writeHistoryArray(HISTORY_KEY, history);
9323
+ return url;
9324
+ }
9325
+ /**
9326
+ * Read a defensive copy of the live back-stack mirror.
9327
+ */
9328
+ function readAngularHmrRouteHistory() {
9329
+ return readHistoryArray(HISTORY_KEY);
9330
+ }
9331
+ /**
9332
+ * Reset the live back-stack mirror. Used by tests and on bootstrap when the
9333
+ * router cannot replay the captured stack so we don't carry stale entries
9334
+ * forward.
9335
+ */
9336
+ function clearAngularHmrRouteHistory() {
9337
+ const g = getGlobalState();
9338
+ delete g[HISTORY_KEY];
9339
+ }
9340
+ /**
9341
+ * Snapshot the live back-stack mirror under the pending-history slot so the
9342
+ * next bootstrap can read it. Called from the HMR capture hook.
9343
+ *
9344
+ * The live mirror is cleared after the copy so the freshly bootstrapped app
9345
+ * starts from an empty back-stack. The replay walks the captured snapshot
9346
+ * via `NativeScriptAngularHmrRouteReplay` which fires `NavigationEnd` for
9347
+ * every URL it touches; the new tracker subscribes to those events and
9348
+ * naturally rebuilds the live mirror to match the snapshot. Without this
9349
+ * reset the live mirror would accumulate every URL the replay re-pushes
9350
+ * across HMR cycles, growing without bound and turning subsequent snapshots
9351
+ * into runaway forward-navigation walks (each replayed forward nav from
9352
+ * `/profile` back into `/talk` creates a fresh `TalkComponent` because
9353
+ * forward navigation never reuses the cache, so the leak shows up as
9354
+ * duplicated `Norrix is not enabled` / `BottomNavComponent Router Event:`
9355
+ * lines that double on every save).
9356
+ *
9357
+ * Returns the snapshot for diagnostics. Defensive: an empty live mirror
9358
+ * leaves the pending slot untouched so a single-page snapshot still works.
9359
+ */
9360
+ function snapshotAngularHmrRouteHistory() {
9361
+ const live = readHistoryArray(HISTORY_KEY);
9362
+ if (live.length === 0) {
9363
+ return [];
9364
+ }
9365
+ writeHistoryArray(PENDING_HISTORY_KEY, live);
9366
+ // Clear the live mirror so the next bootstrap starts from a clean slate.
9367
+ // The replay will repopulate it via the new tracker's NavigationEnd
9368
+ // subscription as it walks the captured stack.
9369
+ writeHistoryArray(HISTORY_KEY, []);
9370
+ return live.slice();
9371
+ }
9372
+ /**
9373
+ * Read the snapshotted back-stack pending replay on the new bootstrap.
9374
+ */
9375
+ function readAngularHmrPendingRouteHistory() {
9376
+ return readHistoryArray(PENDING_HISTORY_KEY);
9377
+ }
9378
+ /**
9379
+ * Read URLs to navigate forward through after the initial navigation finishes.
9380
+ * The first entry of the stack is the `START_PATH` consumed by the router; the
9381
+ * rest are forward navigations to push onto the new back-stack.
9382
+ */
9383
+ function readAngularHmrPendingForwardNavigations() {
9384
+ const pending = readHistoryArray(PENDING_HISTORY_KEY);
9385
+ if (pending.length <= 1) {
9386
+ return [];
9387
+ }
9388
+ return pending.slice(1);
9389
+ }
9390
+ /**
9391
+ * Clear the pending snapshot. The router replay calls this once it finishes
9392
+ * walking the stack so subsequent reboots start fresh.
9393
+ */
9394
+ function clearAngularHmrPendingRouteHistory() {
9395
+ const g = getGlobalState();
9396
+ delete g[PENDING_HISTORY_KEY];
9397
+ }
9398
+ // ---- restoring-route window flag --------------------------------------------
9399
+ /**
9400
+ * True while the Angular HMR layer is restoring a captured route stack
9401
+ * onto the freshly-bootstrapped router. The window opens just before
9402
+ * `START_PATH` resolves to a deep URL and closes once the router has
9403
+ * walked the entire forward navigation list (or aborted it).
9404
+ *
9405
+ * User-app code that runs default navigations on component init (e.g. a
9406
+ * bottom-nav defaulting to its first tab) can consult this flag to skip
9407
+ * its default navigation so the framework's restored route survives:
9408
+ *
9409
+ * ```ts
9410
+ * if (isAngularHmrRestoringRoute()) {
9411
+ * return; // framework is restoring a deeper route — leave it alone.
9412
+ * }
9413
+ * defaultTabNavigation();
9414
+ * ```
9415
+ *
9416
+ * Returns `false` outside of HMR or after the replay window has closed.
9417
+ * Production builds always see `false` because the framework never
9418
+ * opens the window there.
9419
+ */
9420
+ function isAngularHmrRestoringRoute() {
9421
+ const g = getGlobalState();
9422
+ return g[RESTORING_KEY] === true;
9423
+ }
9424
+ /**
9425
+ * The target route the framework is currently restoring, or `null` when
9426
+ * no replay is in progress. Useful when the consumer wants to compare
9427
+ * against the current router URL.
9428
+ */
9429
+ function getAngularHmrRestoringRoute() {
9430
+ const g = getGlobalState();
9431
+ const value = g[RESTORING_TARGET_KEY];
9432
+ return typeof value === 'string' && value ? value : null;
9433
+ }
9434
+ /**
9435
+ * Open the restoring-route window. Called by the framework when an HMR
9436
+ * bootstrap is about to navigate to a captured deep route — never call
9437
+ * this from user code.
9438
+ *
9439
+ * `targetUrl` is what the framework intends to land on; the value can
9440
+ * be read back via {@link getAngularHmrRestoringRoute}.
9441
+ */
9442
+ function beginAngularHmrRouteRestore(targetUrl) {
9443
+ const g = getGlobalState();
9444
+ g[RESTORING_KEY] = true;
9445
+ if (targetUrl) {
9446
+ g[RESTORING_TARGET_KEY] = targetUrl;
9447
+ }
9448
+ else {
9449
+ delete g[RESTORING_TARGET_KEY];
9450
+ }
9451
+ }
9452
+ /**
9453
+ * Close the restoring-route window. Called by the framework when the
9454
+ * replay finishes (NavigationEnd reached, replay aborted, or no
9455
+ * pending stack existed in the first place).
9456
+ */
9457
+ function endAngularHmrRouteRestore() {
9458
+ const g = getGlobalState();
9459
+ delete g[RESTORING_KEY];
9460
+ delete g[RESTORING_TARGET_KEY];
9461
+ }
9462
+
9463
+ /**
9464
+ * Grace period to keep `isAngularHmrRestoringRoute()` returning `true`
9465
+ * after `replayForwardNavigations()` finishes its last `navigateByUrl`.
9466
+ *
9467
+ * Why a grace period exists: NativeScript native views (TabView, BottomNavigation,
9468
+ * Frame, etc.) fire their `loaded` events asynchronously after the JS-side
9469
+ * `NavigationEnd`. User-app code wired to those events typically guards a
9470
+ * default navigation (e.g. "select first tab") with `isAngularHmrRestoringRoute()`.
9471
+ * If we close the window the instant the JS replay finishes, the loaded
9472
+ * event arrives a few hundred milliseconds later, the guard reports false,
9473
+ * and the default navigation stomps the freshly-restored route.
9474
+ *
9475
+ * 1000ms covers all the cases observed on iOS device + simulator without
9476
+ * leaving the window open long enough to interfere with genuine user
9477
+ * navigation. The fallback timeout (`fallback-timeout`) below is a safety
9478
+ * net for scenarios where this scheduled close never fires.
9479
+ */
9480
+ const REPLAY_COMPLETED_GRACE_MS = 1000;
9481
+ /**
9482
+ * Replays the back-stack snapshot captured by `NativeScriptAngularHmrRouteTracker`
9483
+ * during HMR. The router's initial navigation already lands on the bottom of
9484
+ * the stack (`stack[0]`); this service walks `stack[1..n]` so the user keeps
9485
+ * back navigation across HMR cycles.
9486
+ *
9487
+ * The replay is single-shot per bootstrap. Any failure (cancelled navigation,
9488
+ * unrouteable URL) aborts the rest of the replay so we don't fight the router
9489
+ * — the user keeps whichever subset of the stack we successfully re-pushed.
9490
+ */
9491
+ class NativeScriptAngularHmrRouteReplay {
9492
+ constructor(router) {
9493
+ this.router = router;
9494
+ if (!isAngularHmrEnabled()) {
9495
+ return;
9496
+ }
9497
+ const forwardNavigations = readAngularHmrPendingForwardNavigations();
9498
+ // The restoring window is opened by `readAngularHmrPendingStartPath()`
9499
+ // when `START_PATH` resolves to a deep route. If that path resolved
9500
+ // to nothing AND we have no forward navigations, there is nothing
9501
+ // to suppress and we must close the window if it was somehow left
9502
+ // open. Otherwise we keep it open until replay finishes.
9503
+ const restoringWindowOpen = isAngularHmrRestoringRoute();
9504
+ if (forwardNavigations.length === 0) {
9505
+ // Nothing to replay; clear the pending slot so a future navigation that
9506
+ // ends in the bootstrap window doesn't carry the snapshot forward.
9507
+ clearAngularHmrPendingRouteHistory();
9508
+ if (restoringWindowOpen) {
9509
+ // Single-URL restore (no back-stack to walk): keep the window
9510
+ // open until the initial navigation completes so user-app
9511
+ // default navigations don't fire before the framework's
9512
+ // restored URL settles. We then schedule the close with the
9513
+ // same grace period as the multi-URL replay path so async
9514
+ // native `loaded` handlers still see the flag.
9515
+ this.subscription = this.router.events
9516
+ .pipe(filter((event) => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError), take(1))
9517
+ .subscribe(() => this.scheduleRestoringWindowClose('initial-navigation-settled'));
9518
+ // Belt-and-braces: bootstrap can race with router init in
9519
+ // unusual cases. Close the window after a short timeout so we
9520
+ // never leave it stuck open and silently breaking default
9521
+ // navigations forever.
9522
+ this.windowFallbackTimeout = setTimeout(() => this.closeRestoringWindow('fallback-timeout'), 5000);
9523
+ }
9524
+ return;
9525
+ }
9526
+ if (NativeScriptDebug.isLogEnabled()) {
9527
+ NativeScriptDebug.hmrLog(`HMR back-stack replay queued: ${forwardNavigations.length} forward navigation(s)`);
9528
+ }
9529
+ this.subscription = this.router.events
9530
+ .pipe(filter((event) => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError), take(1))
9531
+ .subscribe((event) => {
9532
+ if (event instanceof NavigationEnd) {
9533
+ void this.replayForwardNavigations(forwardNavigations);
9534
+ }
9535
+ else {
9536
+ // Initial navigation never landed; replay would compound the problem.
9537
+ clearAngularHmrPendingRouteHistory();
9538
+ this.closeRestoringWindow('initial-navigation-failed');
9539
+ if (NativeScriptDebug.isLogEnabled()) {
9540
+ NativeScriptDebug.hmrLog('HMR back-stack replay skipped: initial navigation did not complete');
9541
+ }
9542
+ }
9543
+ });
9544
+ // Same belt-and-braces fallback as the single-URL path above.
9545
+ this.windowFallbackTimeout = setTimeout(() => this.closeRestoringWindow('fallback-timeout'), 10000);
9546
+ }
9547
+ ngOnDestroy() {
9548
+ this.subscription?.unsubscribe();
9549
+ if (this.windowFallbackTimeout !== undefined) {
9550
+ clearTimeout(this.windowFallbackTimeout);
9551
+ this.windowFallbackTimeout = undefined;
9552
+ }
9553
+ if (this.pendingCloseTimeout !== undefined) {
9554
+ clearTimeout(this.pendingCloseTimeout);
9555
+ this.pendingCloseTimeout = undefined;
9556
+ }
9557
+ // Defensive: never leave the restoring window open across module
9558
+ // destruction. A subsequent reboot would otherwise see it set and
9559
+ // suppress the next default navigation indefinitely.
9560
+ this.closeRestoringWindow('replay-service-destroyed');
9561
+ }
9562
+ closeRestoringWindow(reason) {
9563
+ if (this.pendingCloseTimeout !== undefined) {
9564
+ clearTimeout(this.pendingCloseTimeout);
9565
+ this.pendingCloseTimeout = undefined;
9566
+ }
9567
+ if (!isAngularHmrRestoringRoute()) {
9568
+ return;
9569
+ }
9570
+ endAngularHmrRouteRestore();
9571
+ if (this.windowFallbackTimeout !== undefined) {
9572
+ clearTimeout(this.windowFallbackTimeout);
9573
+ this.windowFallbackTimeout = undefined;
9574
+ }
9575
+ if (NativeScriptDebug.isLogEnabled()) {
9576
+ NativeScriptDebug.hmrLog(`HMR restoring-route window closed (${reason})`);
9577
+ }
9578
+ }
9579
+ /**
9580
+ * Schedule the restoring window to close after a small grace period
9581
+ * so that asynchronous user-app handlers (e.g. NativeScript native
9582
+ * `loaded` events on TabView / BottomNavigation / Frame) still observe
9583
+ * `isAngularHmrRestoringRoute() === true` and skip default navigations
9584
+ * that would otherwise stomp the freshly-restored route.
9585
+ *
9586
+ * The grace period is bounded by the existing `fallback-timeout` so
9587
+ * we never leave the flag set indefinitely even if `setTimeout` is
9588
+ * blocked by a misbehaving consumer.
9589
+ */
9590
+ scheduleRestoringWindowClose(reason) {
9591
+ if (!isAngularHmrRestoringRoute()) {
9592
+ return;
9593
+ }
9594
+ if (this.pendingCloseTimeout !== undefined) {
9595
+ clearTimeout(this.pendingCloseTimeout);
9596
+ }
9597
+ this.pendingCloseTimeout = setTimeout(() => {
9598
+ this.pendingCloseTimeout = undefined;
9599
+ this.closeRestoringWindow(reason);
9600
+ }, REPLAY_COMPLETED_GRACE_MS);
9601
+ }
9602
+ async replayForwardNavigations(urls) {
9603
+ let aborted = false;
9604
+ try {
9605
+ for (const url of urls) {
9606
+ const succeeded = await this.router.navigateByUrl(url).catch(() => false);
9607
+ if (!succeeded) {
9608
+ aborted = true;
9609
+ if (NativeScriptDebug.isLogEnabled()) {
9610
+ NativeScriptDebug.hmrLog(`HMR back-stack replay aborted at ${url}`);
9611
+ }
9612
+ return;
9613
+ }
9614
+ if (NativeScriptDebug.isLogEnabled()) {
9615
+ NativeScriptDebug.hmrLog(`HMR back-stack replay navigated to ${url}`);
9616
+ }
9617
+ }
9618
+ }
9619
+ finally {
9620
+ clearAngularHmrPendingRouteHistory();
9621
+ this.scheduleRestoringWindowClose(aborted ? 'replay-aborted' : 'replay-completed');
9622
+ }
9623
+ }
9624
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteReplay, deps: [{ token: i1$3.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
9625
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteReplay }); }
9626
+ }
9627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteReplay, decorators: [{
9628
+ type: Injectable
9629
+ }], ctorParameters: () => [{ type: i1$3.Router }] });
7954
9630
 
7955
9631
  class NativeScriptAngularHmrRouteTracker {
7956
9632
  constructor(router) {
7957
9633
  this.router = router;
7958
- if (!this.isHmrEnabled()) {
9634
+ // Tracks whether the current `NavigationStart..NavigationEnd` pair was kicked
9635
+ // off by a popstate (frame.goBack / NSLocationStrategy.back) so that on
9636
+ // `NavigationEnd` we can pop our mirror instead of pushing a duplicate entry.
9637
+ this.currentNavigationIsPopstate = false;
9638
+ this.currentNavigationReplaceUrl = false;
9639
+ if (!isAngularHmrEnabled()) {
7959
9640
  return;
7960
9641
  }
7961
9642
  this.disposeCaptureHook = this.installCaptureHook();
7962
9643
  this.captureCurrentRoute('bootstrap');
7963
9644
  this.subscription = this.router.events.subscribe((event) => {
9645
+ if (event instanceof NavigationStart) {
9646
+ this.currentNavigationIsPopstate = event.navigationTrigger === 'popstate';
9647
+ this.currentNavigationReplaceUrl = !!event.restoredState;
9648
+ return;
9649
+ }
7964
9650
  if (event instanceof NavigationEnd) {
7965
- writeAngularHmrRouteState(event.urlAfterRedirects || event.url, {
9651
+ const url = event.urlAfterRedirects || event.url;
9652
+ writeAngularHmrRouteState(url, {
7966
9653
  source: 'navigation-end',
7967
9654
  });
9655
+ if (this.currentNavigationIsPopstate) {
9656
+ // The user (or NSLocationStrategy.back()) walked the back-stack down
9657
+ // by one page; mirror that by dropping the top of our snapshot so a
9658
+ // subsequent HMR reboot doesn't carry the popped page back into view.
9659
+ popAngularHmrRouteHistoryEntry();
9660
+ }
9661
+ else if (this.currentNavigationReplaceUrl) {
9662
+ replaceAngularHmrRouteHistoryTop(url);
9663
+ }
9664
+ else {
9665
+ pushAngularHmrRouteHistoryEntry(url);
9666
+ }
9667
+ this.currentNavigationIsPopstate = false;
9668
+ this.currentNavigationReplaceUrl = false;
7968
9669
  }
7969
9670
  });
7970
9671
  }
@@ -7973,6 +9674,32 @@ class NativeScriptAngularHmrRouteTracker {
7973
9674
  this.disposeCaptureHook?.();
7974
9675
  }
7975
9676
  captureCurrentRoute(source) {
9677
+ if (source === 'hmr-reboot') {
9678
+ // Snapshot the live mirror first so the bootstrap can replay forward
9679
+ // navigations to rebuild the back-stack. The pending single-URL slot
9680
+ // remains useful as a fallback when the snapshot turns out to be empty
9681
+ // (e.g. bootstrap-time HMR before the first NavigationEnd).
9682
+ snapshotAngularHmrRouteHistory();
9683
+ }
9684
+ else if (source === 'bootstrap') {
9685
+ // Seed the live mirror with the current URL so the very first HMR
9686
+ // before any user navigation still has a stack of size one to snapshot.
9687
+ //
9688
+ // Skip empty / root URLs: at ENVIRONMENT_INITIALIZER time the router
9689
+ // has not run its initial navigation yet so `router.url` is "/" (or
9690
+ // an empty string). Pushing that here would seed the mirror with a
9691
+ // noise entry that becomes the bottom of the next snapshot, which in
9692
+ // turn becomes the next bootstrap's `START_PATH`. The router then
9693
+ // boots to "/" → redirects to the real default route → fires an
9694
+ // extra `NavigationEnd` that re-enters the replay path. The first
9695
+ // genuine `NavigationEnd` arrives a moment later through the event
9696
+ // subscription below and seeds the mirror with the real URL, so
9697
+ // dropping the seed here is safe.
9698
+ const seedUrl = this.router.url;
9699
+ if (seedUrl && seedUrl !== '/') {
9700
+ pushAngularHmrRouteHistoryEntry(seedUrl);
9701
+ }
9702
+ }
7976
9703
  return writeAngularHmrRouteState(this.router.url, {
7977
9704
  pending: source === 'hmr-reboot',
7978
9705
  source,
@@ -7981,10 +9708,6 @@ class NativeScriptAngularHmrRouteTracker {
7981
9708
  installCaptureHook() {
7982
9709
  return installAngularHmrRouteCaptureHook(() => this.captureCurrentRoute('hmr-reboot'));
7983
9710
  }
7984
- isHmrEnabled() {
7985
- const g = globalThis;
7986
- return !!g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__ || typeof g.__reboot_ng_modules__ === 'function';
7987
- }
7988
9711
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteTracker, deps: [{ token: i1$3.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
7989
9712
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteTracker }); }
7990
9713
  }
@@ -8018,10 +9741,11 @@ class NativeScriptRouterModule {
8018
9741
  NSRouteReuseStrategy,
8019
9742
  { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
8020
9743
  NativeScriptAngularHmrRouteTracker,
9744
+ NativeScriptAngularHmrRouteReplay,
8021
9745
  {
8022
9746
  provide: APP_BOOTSTRAP_LISTENER,
8023
9747
  multi: true,
8024
- deps: [NativeScriptAngularHmrRouteTracker],
9748
+ deps: [NativeScriptAngularHmrRouteTracker, NativeScriptAngularHmrRouteReplay],
8025
9749
  useFactory: () => () => undefined,
8026
9750
  },
8027
9751
  ],
@@ -8064,11 +9788,13 @@ function provideNativeScriptRouter(routes, ...features) {
8064
9788
  NSRouteReuseStrategy,
8065
9789
  { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
8066
9790
  NativeScriptAngularHmrRouteTracker,
9791
+ NativeScriptAngularHmrRouteReplay,
8067
9792
  {
8068
9793
  provide: ENVIRONMENT_INITIALIZER,
8069
9794
  multi: true,
8070
9795
  useValue: () => {
8071
9796
  inject(NativeScriptAngularHmrRouteTracker);
9797
+ inject(NativeScriptAngularHmrRouteReplay);
8072
9798
  },
8073
9799
  },
8074
9800
  // {provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener},
@@ -8462,5 +10188,5 @@ function provideNativeScriptNgZone(options) {
8462
10188
  * Generated bundle index. Do not edit.
8463
10189
  */
8464
10190
 
8465
- export { APP_ROOT_VIEW, ActionBarComponent, ActionBarScope, ActionItemDirective, AndroidFilterComponent, AppHostAsyncView, AppHostView, AppleFilterComponent, BasePortalOutlet, BaseValueAccessor, COMMON_PROVIDERS, CdkPortal, CdkPortalOutlet, CheckedValueAccessor, CommentNode, ComponentPortal, DEVICE, DISABLE_ROOT_VIEW_HANDLING, DateValueAccessor, DetachedLoader, DomPortal, ENABLE_REUSABE_VIEWS, EmulatedRenderer, FrameDirective, FramePageComponent, FramePageModule, FrameService, IOSFilterComponent, InjectableAnimationEngine, InvisibleNode, ItemContext, ListViewComponent, ModalDialogParams, ModalDialogService, NAMESPACE_FILTERS, NATIVESCRIPT_MODULE_PROVIDERS, NATIVESCRIPT_MODULE_STATIC_PROVIDERS, NATIVESCRIPT_ROOT_MODULE_ID, NATIVE_DIALOG_DATA, NATIVE_DIALOG_DEFAULT_OPTIONS, NSEmptyOutletComponent, NSFileSystem, NSLocationStrategy, NSRouteReuseStrategy, NSRouterLink, NSRouterLinkActive, NativeDialog, NativeDialogCloseDirective, NativeDialogConfig, NativeDialogModule, NativeDialogRef, NativeDialog as NativeDialogService, NativeModalRef, NativeScriptAnimationDriver, NativeScriptAnimationPlayer, NativeScriptAnimationsModule, NativeScriptCommonModule, NativeScriptDocument, NativeScriptDomPortalOutlet, NativeScriptFormsModule, NativeScriptHttpClientModule, NativeScriptLoadingService, NativeScriptModule, NativeScriptNgSafeEvent, NativeScriptNgZone, NativeScriptRendererFactory, NativeScriptRendererHelperService, NativeScriptRouterModule, NativeScriptSanitizer, NativescriptXhrFactory, NavigationButtonDirective, NgViewRef, NsHttpBackEnd, NsTemplatedItem, NumberValueAccessor, Outlet, PAGE_FACTORY, PREVENT_CHANGE_EVENTS_DURING_CD, PREVENT_SPECIFIC_EVENTS_DURING_CD, PageDirective, PageRoute, PageRouterOutlet, PageService, PlatformNamespaceFilter, Portal, PortalModule, RootCompositeModule, RootViewProxy, RouterExtensions, START_PATH, SelectedIndexValueAccessor, TEMPLATED_ITEMS_COMPONENT, TabViewDirective, TabViewItemDirective, TemplateKeyDirective, TemplatePortal, TextNode, TextValueAccessor, TimeValueAccessor, VisionOSFilterComponent, bootstrapApplication, createKeyframeAnimation, customFrameComponentFactory, customFrameDirectiveFactory, customPageFactory, customPageFactoryFromFrame, dashCaseToCamelCase, defaultNavOptions, defaultPageFactory, defaultPageFactoryProvider, detachViewFromParent, disableRootViewHanding, errorHandler, extractSingleViewRecursive, frameMeta, generateDetachedLoader, generateFallbackRootView, generateNativeScriptView, generateRandomId, generateRootLayoutAndProxy, getFirstNativeLikeView, getItemViewRoot, getSingleViewRecursive, getViewClass, getViewMeta, instantiateDefaultStyleNormalizer, instantiateSupportedAnimationDriver, isBlank, isContentView, isDetachedElement, isInvisibleNode, isJsObject, isKnownView, isLayout, isListLikeIterable, isPresent, isView, onAfterLivesync, onBeforeLivesync, once, platformNativeScript, platformNativeScriptDynamic, postAngularBootstrap$, preAngularDisposal$, provideLocationStrategy, provideNativeScriptHttpClient, provideNativeScriptNgZone, provideNativeScriptRouter, registerElement, registerNativeScriptViewComponents, rootRoute, runNativeScriptAngularApp, throwIfAlreadyLoaded, throwNoPortalAttachedError, throwNullPortalError, throwNullPortalOutletError, throwPortalAlreadyAttachedError, throwPortalOutletAlreadyDisposedError, throwUnknownPortalTypeError, COMPONENT_VARIABLE as ɵCOMPONENT_VARIABLE, CONTENT_ATTR as ɵCONTENT_ATTR, HOST_ATTR as ɵHOST_ATTR, NativeScriptDebug as ɵNativeScriptAngularDebug, viewUtil as ɵViewUtil, actionBarMeta as ɵactionBarMeta, elementMap as ɵelementMap, isActionItem as ɵisActionItem, isNavigationButton as ɵisNavigationButton };
10191
+ export { APP_ROOT_VIEW, ActionBarComponent, ActionBarScope, ActionItemDirective, AndroidFilterComponent, AppHostAsyncView, AppHostView, AppleFilterComponent, BasePortalOutlet, BaseValueAccessor, COMMON_PROVIDERS, CdkPortal, CdkPortalOutlet, CheckedValueAccessor, CommentNode, ComponentPortal, DEVICE, DISABLE_ROOT_VIEW_HANDLING, DateValueAccessor, DetachedLoader, DomPortal, ENABLE_REUSABE_VIEWS, EmulatedRenderer, FrameDirective, FramePageComponent, FramePageModule, FrameService, IOSFilterComponent, InjectableAnimationEngine, InvisibleNode, ItemContext, ListViewComponent, ModalDialogParams, ModalDialogService, NAMESPACE_FILTERS, NATIVESCRIPT_MODULE_PROVIDERS, NATIVESCRIPT_MODULE_STATIC_PROVIDERS, NATIVESCRIPT_ROOT_MODULE_ID, NATIVE_DIALOG_DATA, NATIVE_DIALOG_DEFAULT_OPTIONS, NSEmptyOutletComponent, NSFileSystem, NSLocationStrategy, NSRouteReuseStrategy, NSRouterLink, NSRouterLinkActive, NativeDialog, NativeDialogCloseDirective, NativeDialogConfig, NativeDialogModule, NativeDialogRef, NativeDialog as NativeDialogService, NativeModalRef, NativeScriptAnimationDriver, NativeScriptAnimationPlayer, NativeScriptAnimationsModule, NativeScriptCommonModule, NativeScriptDocument, NativeScriptDomPortalOutlet, NativeScriptFormsModule, NativeScriptHttpClientModule, NativeScriptLoadingService, NativeScriptModule, NativeScriptNgSafeEvent, NativeScriptNgZone, NativeScriptRendererFactory, NativeScriptRendererHelperService, NativeScriptRouterModule, NativeScriptSanitizer, NativescriptXhrFactory, NavigationButtonDirective, NgViewRef, NsHttpBackEnd, NsTemplatedItem, NumberValueAccessor, Outlet, PAGE_FACTORY, PREVENT_CHANGE_EVENTS_DURING_CD, PREVENT_SPECIFIC_EVENTS_DURING_CD, PageDirective, PageRoute, PageRouterOutlet, PageService, PlatformNamespaceFilter, Portal, PortalModule, RootCompositeModule, RootViewProxy, RouterExtensions, START_PATH, SelectedIndexValueAccessor, TEMPLATED_ITEMS_COMPONENT, TabViewDirective, TabViewItemDirective, TemplateKeyDirective, TemplatePortal, TextNode, TextValueAccessor, TimeValueAccessor, VisionOSFilterComponent, bootstrapApplication, createKeyframeAnimation, customFrameComponentFactory, customFrameDirectiveFactory, customPageFactory, customPageFactoryFromFrame, dashCaseToCamelCase, defaultNavOptions, defaultPageFactory, defaultPageFactoryProvider, detachViewFromParent, disableRootViewHanding, errorHandler, extractSingleViewRecursive, frameMeta, generateDetachedLoader, generateFallbackRootView, generateNativeScriptView, generateRandomId, generateRootLayoutAndProxy, getAngularHmrRestoringRoute, getFirstNativeLikeView, getItemViewRoot, getSingleViewRecursive, getViewClass, getViewMeta, instantiateDefaultStyleNormalizer, instantiateSupportedAnimationDriver, isAngularHmrRestoringRoute, isBlank, isContentView, isDetachedElement, isInvisibleNode, isJsObject, isKnownView, isLayout, isListLikeIterable, isPresent, isView, onAfterLivesync, onBeforeLivesync, once, platformNativeScript, platformNativeScriptDynamic, postAngularBootstrap$, preAngularDisposal$, provideLocationStrategy, provideNativeScriptHttpClient, provideNativeScriptNgZone, provideNativeScriptRouter, registerElement, registerNativeScriptViewComponents, rootRoute, runNativeScriptAngularApp, throwIfAlreadyLoaded, throwNoPortalAttachedError, throwNullPortalError, throwNullPortalOutletError, throwPortalAlreadyAttachedError, throwPortalOutletAlreadyDisposedError, throwUnknownPortalTypeError, COMPONENT_VARIABLE as ɵCOMPONENT_VARIABLE, CONTENT_ATTR as ɵCONTENT_ATTR, HOST_ATTR as ɵHOST_ATTR, NativeScriptDebug as ɵNativeScriptAngularDebug, viewUtil as ɵViewUtil, actionBarMeta as ɵactionBarMeta, elementMap as ɵelementMap, isActionItem as ɵisActionItem, isNavigationButton as ɵisNavigationButton };
8466
10192
  //# sourceMappingURL=nativescript-angular.mjs.map