@nativescript/angular 21.0.1-alpha.2 → 21.0.1-alpha.4

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,64 +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
- static { this.hmrTraceCategory = 'ns-ng-hmr'; }
368
- // TODO: migrate all usage to this - avoids extraneous method executions
369
- static { this.enabled = Trace.isEnabled(); }
370
- static isLogEnabled() {
371
- return Trace.isEnabled();
372
- }
373
- static animationsLog(message) {
374
- Trace.write(message, NativeScriptDebug.animationsTraceCategory);
375
- }
376
- static rendererLog(msg) {
377
- Trace.write(msg, NativeScriptDebug.rendererTraceCategory);
378
- }
379
- static rendererError(message) {
380
- Trace.write(message, NativeScriptDebug.rendererTraceCategory, Trace.messageType.error);
381
- }
382
- static viewUtilLog(msg) {
383
- Trace.write(msg, NativeScriptDebug.viewUtilCategory);
384
- }
385
- static routerLog(message) {
386
- Trace.write(message, NativeScriptDebug.routerTraceCategory);
387
- }
388
- static routerError(message) {
389
- Trace.write(message, NativeScriptDebug.routerTraceCategory, Trace.messageType.error);
390
- }
391
- static routeReuseStrategyLog(message) {
392
- Trace.write(message, NativeScriptDebug.routeReuseStrategyTraceCategory);
393
- }
394
- static styleError(message) {
395
- Trace.write(message, Trace.categories.Style, Trace.messageType.error);
396
- }
397
- static listViewLog(message) {
398
- Trace.write(message, NativeScriptDebug.listViewTraceCategory);
399
- }
400
- static listViewError(message) {
401
- Trace.write(message, NativeScriptDebug.listViewTraceCategory, Trace.messageType.error);
402
- }
403
- static bootstrapLog(message) {
404
- Trace.write(message, NativeScriptDebug.bootstrapCategory);
405
- }
406
- static bootstrapLogError(message) {
407
- Trace.write(message, NativeScriptDebug.bootstrapCategory, Trace.messageType.error);
408
- }
409
- static hmrLog(message) {
410
- Trace.write(message, NativeScriptDebug.hmrTraceCategory);
411
- }
412
- static hmrLogError(message) {
413
- Trace.write(message, NativeScriptDebug.hmrTraceCategory, Trace.messageType.error);
414
- }
415
- }
416
-
417
913
  class FrameService {
418
914
  // TODO: Add any methods that are needed to handle frame/page navigation
419
915
  getFrame() {
@@ -1457,13 +1953,43 @@ function createAngularRootTransitionGuard(globalObj = globalThis, timers = { set
1457
1953
  // This is crucial because HMR imports a fresh @angular/core with empty LView tracking
1458
1954
  // We need to use the original one that has the registered LViews
1459
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();
1460
1974
  const angularHmrGlobal = globalThis;
1461
- angularHmrGlobal.__NS_REMEMBER_ANGULAR_CORE__ = (core) => setAngularCoreForHmr(core, angularHmrGlobal);
1975
+ angularHmrGlobal.__NS_REMEMBER_ANGULAR_CORE__ = (core) => {
1976
+ setAngularCoreForHmr(core, angularHmrGlobal);
1977
+ };
1462
1978
  function disableRootViewHanding(view) {
1463
1979
  view.__disable_root_view_handling = true;
1464
1980
  }
1465
1981
  const preAngularDisposal$ = new Subject();
1466
- 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);
1467
1993
  /**
1468
1994
  * @deprecated
1469
1995
  */
@@ -1480,14 +2006,34 @@ if (import.meta['webpackHot']) {
1480
2006
  };
1481
2007
  }
1482
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
+ }
1483
2021
  postAngularBootstrap$.next({
1484
2022
  moduleType: name,
1485
2023
  reference: ref,
1486
2024
  reason,
1487
2025
  });
2026
+ if (NativeScriptDebug.isLogEnabled()) {
2027
+ NativeScriptDebug.hmrLog(`postAngularBootstrap$.next() emitted name=${name} reason=${reason}`);
2028
+ }
1488
2029
  }
1489
2030
  function destroyRef(ref, name, reason) {
1490
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
+ }
1491
2037
  if (ref instanceof PlatformRef) {
1492
2038
  preAngularDisposal$.next({
1493
2039
  moduleType: 'platform',
@@ -1503,6 +2049,9 @@ function destroyRef(ref, name, reason) {
1503
2049
  });
1504
2050
  }
1505
2051
  ref.destroy();
2052
+ if (traceEnabled) {
2053
+ NativeScriptDebug.hmrLog(`destroyRef DONE kind=${refKind} name=${name ?? '(none)'}`);
2054
+ }
1506
2055
  }
1507
2056
  }
1508
2057
  function runZoneSyncTask(fn) {
@@ -1985,9 +2534,14 @@ function runNativeScriptAngularApp(options) {
1985
2534
  disposePlatform('hotreload');
1986
2535
  };
1987
2536
  global['__reboot_ng_modules__'] = (shouldDisposePlatform = false) => {
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();
1988
2542
  const traceEnabled = NativeScriptDebug.isLogEnabled();
1989
2543
  if (traceEnabled) {
1990
- NativeScriptDebug.hmrLog(`__reboot_ng_modules__ called shouldDisposePlatform=${shouldDisposePlatform} bootstrapId=${bootstrapId} hasMainModuleRef=${!!mainModuleRef}`);
2544
+ NativeScriptDebug.hmrLog(`__reboot_ng_modules__ called cycle=${cycleNum} shouldDisposePlatform=${shouldDisposePlatform} bootstrapId=${bootstrapId} hasMainModuleRef=${!!mainModuleRef}`);
1991
2545
  }
1992
2546
  try {
1993
2547
  global['__NS_CAPTURE_ANGULAR_HMR_ROUTE__']?.();
@@ -1995,17 +2549,17 @@ function runNativeScriptAngularApp(options) {
1995
2549
  catch { }
1996
2550
  disposeLastModules('hotreload');
1997
2551
  if (traceEnabled) {
1998
- NativeScriptDebug.hmrLog(`after disposeLastModules bootstrapId=${bootstrapId}`);
2552
+ NativeScriptDebug.hmrLog(`after disposeLastModules cycle=${cycleNum} bootstrapId=${bootstrapId}`);
1999
2553
  }
2000
2554
  if (shouldDisposePlatform) {
2001
2555
  disposePlatform('hotreload');
2002
2556
  }
2003
2557
  if (traceEnabled) {
2004
- NativeScriptDebug.hmrLog('calling bootstrapRoot');
2558
+ NativeScriptDebug.hmrLog(`calling bootstrapRoot cycle=${cycleNum}`);
2005
2559
  }
2006
2560
  bootstrapRoot('hotreload');
2007
2561
  if (traceEnabled) {
2008
- NativeScriptDebug.hmrLog(`bootstrapRoot returned bootstrapId=${bootstrapId}`);
2562
+ NativeScriptDebug.hmrLog(`bootstrapRoot returned cycle=${cycleNum} bootstrapId=${bootstrapId}`);
2009
2563
  }
2010
2564
  };
2011
2565
  if (isWebpackHot) {
@@ -5530,6 +6084,22 @@ class NativeDialogConfig {
5530
6084
  */
5531
6085
  this.closeOnNavigation = true;
5532
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;
5533
6103
  // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling.
5534
6104
  }
5535
6105
  }
@@ -5652,6 +6222,183 @@ class NativeDialogRef {
5652
6222
  }
5653
6223
  }
5654
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
+
5655
6402
  let NativeModalRef = class NativeModalRef {
5656
6403
  constructor(_config, _injector, location) {
5657
6404
  this._config = _config;
@@ -5679,11 +6426,18 @@ let NativeModalRef = class NativeModalRef {
5679
6426
  this._closeCallback = once(async () => {
5680
6427
  this.stateChanged.next({ state: 'closing' });
5681
6428
  if (!this._isDismissed) {
5682
- this.modalViewRef.firstNativeLikeView?.closeModal();
6429
+ // Prefer `modalView` (the actual presented view) over the
6430
+ // legacy `firstNativeLikeView`. Both paths ultimately reach
6431
+ // the same `_closeModalCallback` via parent walk, but going
6432
+ // through the presented view is one hop instead of three and
6433
+ // works even if the rendered first root has been replaced by
6434
+ // an HMR `ɵɵreplaceMetadata` cycle.
6435
+ const closeTarget = this.modalView ?? this.modalViewRef.firstNativeLikeView;
6436
+ closeTarget?.closeModal();
5683
6437
  }
5684
6438
  await this.location?._closeModalNavigation();
5685
6439
  // this.detachedLoaderRef?.destroy();
5686
- if (this.modalViewRef?.firstNativeLikeView.isLoaded) {
6440
+ if (this.modalViewRef?.firstNativeLikeView?.isLoaded) {
5687
6441
  fromEvent(this.modalViewRef.firstNativeLikeView, 'unloaded')
5688
6442
  .pipe(take(1))
5689
6443
  .subscribe(() => this.stateChanged.next({ state: 'closed' }));
@@ -5736,19 +6490,34 @@ let NativeModalRef = class NativeModalRef {
5736
6490
  }
5737
6491
  attachComponentPortal(portal) {
5738
6492
  this.startModalNavigation();
6493
+ // `targetView` is a stable ContentView wrapper we own. The Angular
6494
+ // component's host (a `ProxyViewContainer`) is attached as its
6495
+ // content via the portal outlet below. We present `targetView`
6496
+ // itself as the modal — *not* the first rendered template root —
6497
+ // so that component-level HMR (`ɵɵreplaceMetadata`) can re-render
6498
+ // into the PVC and the modal automatically displays the new
6499
+ // content via the wrapper. Presenting the rendered first root
6500
+ // directly worked for the initial open but left subsequent HMR
6501
+ // updates rendering into a detached PVC, producing a blank modal.
5739
6502
  const targetView = new ContentView();
5740
6503
  this.portalOutlet = new NativeScriptDomPortalOutlet(targetView, this._config.componentFactoryResolver || this._injector.get(ComponentFactoryResolver), this._injector.get(ApplicationRef), this._injector);
5741
6504
  const componentRef = this.portalOutlet.attach(portal);
5742
6505
  componentRef.changeDetectorRef.detectChanges();
5743
6506
  this.modalViewRef = new NgViewRef(componentRef);
6507
+ this.modalView = targetView;
5744
6508
  if (this.modalViewRef.firstNativeLikeView !== this.modalViewRef.view) {
5745
6509
  this.modalViewRef.view._ngDialogRoot = this.modalViewRef.firstNativeLikeView;
5746
6510
  }
5747
- this.modalViewRef.firstNativeLikeView['__ng_modal_id__'] = this._id;
5748
- // if we don't detach the view from its parent, ios gets mad
5749
- this.modalViewRef.detachNativeLikeView();
6511
+ // Tag both the wrapper (the actual modal root) and the rendered
6512
+ // first root so `getClosestDialog`'s parent walk finds the modal
6513
+ // id regardless of whether it starts from a view inside the
6514
+ // template or from the wrapper.
6515
+ targetView['__ng_modal_id__'] = this._id;
6516
+ if (this.modalViewRef.firstNativeLikeView) {
6517
+ this.modalViewRef.firstNativeLikeView['__ng_modal_id__'] = this._id;
6518
+ }
5750
6519
  const userOptions = this._config.nativeOptions || {};
5751
- this.parentView.showModal(this.modalViewRef.firstNativeLikeView, {
6520
+ this.parentView.showModal(targetView, {
5752
6521
  context: null,
5753
6522
  ...userOptions,
5754
6523
  closeCallback: async () => {
@@ -5786,6 +6555,54 @@ NativeModalRef = __decorate([
5786
6555
  * Use of this source code is governed by an MIT-style license that can be
5787
6556
  * found in the LICENSE file at https://angular.io/license
5788
6557
  */
6558
+ /**
6559
+ * Dialog HMR lifecycle log.
6560
+ */
6561
+ function hmrDialogLog(message) {
6562
+ if (!isAngularHmrEnabled()) {
6563
+ return;
6564
+ }
6565
+ if (!NativeScriptDebug.isLogEnabled()) {
6566
+ return;
6567
+ }
6568
+ NativeScriptDebug.hmrLog(`[dialog] ${message}`);
6569
+ }
6570
+ /**
6571
+ * Lower-level dialog HMR wiring trace (module-realm count, NativeDialog
6572
+ * instance count, registry hits/misses). Distinct from `hmrDialogLog`
6573
+ * for greppability — both fan into the same Trace category so a single
6574
+ * `Trace.setCategories(NativeScriptDebug.hmrTraceCategory)` toggle
6575
+ * surfaces them all.
6576
+ */
6577
+ function hmrDialogDiag(message) {
6578
+ if (!isAngularHmrEnabled()) {
6579
+ return;
6580
+ }
6581
+ if (!NativeScriptDebug.isLogEnabled()) {
6582
+ return;
6583
+ }
6584
+ NativeScriptDebug.hmrLog(`[dialog-diag] ${message}`);
6585
+ }
6586
+ /**
6587
+ * Module-evaluation marker. Increments on every fresh evaluation of
6588
+ * `dialog-services.ts`. If we see this number rise on every HMR cycle,
6589
+ * the file is being re-evaluated (good). If it stays flat, the module
6590
+ * is being served from cache (bad — class identities won't change).
6591
+ */
6592
+ const DIALOG_MODULE_DIAG_KEY = '__NS_HMR_DIAG_DIALOG_MODULE__';
6593
+ function getDialogModuleDiag() {
6594
+ const slot = globalThis;
6595
+ if (!slot[DIALOG_MODULE_DIAG_KEY]) {
6596
+ slot[DIALOG_MODULE_DIAG_KEY] = { evals: 0, instances: 0, lastEvalAt: 0 };
6597
+ }
6598
+ return slot[DIALOG_MODULE_DIAG_KEY];
6599
+ }
6600
+ {
6601
+ const md = getDialogModuleDiag();
6602
+ md.evals += 1;
6603
+ md.lastEvalAt = Date.now();
6604
+ hmrDialogDiag(`module-eval count=${md.evals} (file=dialog-services.ts) timestamp=${md.lastEvalAt}`);
6605
+ }
5789
6606
  /** Injection token that can be used to access the data that was passed in to a dialog. */
5790
6607
  const NATIVE_DIALOG_DATA = new InjectionToken('NativeDialogData');
5791
6608
  /** Injection token that can be used to specify default dialog options. */
@@ -5799,6 +6616,14 @@ class NativeDialog {
5799
6616
  this._openDialogsAtThisLevel = [];
5800
6617
  this._afterAllClosedAtThisLevel = new Subject();
5801
6618
  this._afterOpenedAtThisLevel = new Subject();
6619
+ /**
6620
+ * Maps each open dialog ref back to the `(componentClass, config)` pair it
6621
+ * was opened with so the HMR snapshot can replay the call later. Dialogs
6622
+ * opened with a `TemplateRef` are tracked with `componentClass: undefined`
6623
+ * — the HMR layer skips them automatically.
6624
+ */
6625
+ this._openDialogMetadata = new WeakMap();
6626
+ this._hmrSubscriptions = [];
5802
6627
  // TODO (jelbourn): tighten the typing right-hand side of this expression.
5803
6628
  /**
5804
6629
  * Stream that emits when all open dialog have finished closing.
@@ -5816,6 +6641,50 @@ class NativeDialog {
5816
6641
  this._nativeModalType = NativeModalRef;
5817
6642
  this._dialogDataToken = NATIVE_DIALOG_DATA;
5818
6643
  this.locationStrategy = inject(NSLocationStrategy);
6644
+ // Bumps a global counter so we can detect duplicate or leaked
6645
+ // `NativeDialog` instances across HMR cycles. Field initialiser
6646
+ // ordering: this MUST run before `_initHmrLifecycle()` below so the
6647
+ // log line in that helper can include the assigned id.
6648
+ this._diagInstanceIdAssign = (() => {
6649
+ const md = getDialogModuleDiag();
6650
+ md.instances += 1;
6651
+ this._diagInstanceId = md.instances;
6652
+ hmrDialogDiag(`NativeDialog ctor instanceId=${md.instances} hasParentDialog=${!!this._parentDialog} moduleEvalCount=${md.evals}`);
6653
+ return null;
6654
+ })();
6655
+ // Initialise after every dependency above so the subscriptions can call
6656
+ // back into `this.open(...)` and `this.openDialogs` safely. The result is
6657
+ // unused — we just want a side-effect at construction time.
6658
+ this._hmrInitMarker = this._initHmrLifecycle();
6659
+ /**
6660
+ * Tracks whether a restore has already been scheduled for this
6661
+ * `NativeDialog` instance's lifetime. We only need to restore once
6662
+ * per HMR cycle — the rxjs `ReplaySubject(1)` for
6663
+ * `postAngularBootstrap$` delivers both the *previous* cycle's
6664
+ * cached event (replay on subscribe) **and** the *current* cycle's
6665
+ * fresh event, and the constructor stash peek can independently
6666
+ * notice pending work. Without this guard each of those triggers
6667
+ * would queue its own `setTimeout` and the logs would show two or
6668
+ * three "scheduling restore" lines per save.
6669
+ *
6670
+ * The guard is per-instance and the stash itself is the source of
6671
+ * truth: `_restorePendingDialogs` calls `consumePendingHmrDialogs()`
6672
+ * which atomically clears the stash, so even if the guard somehow
6673
+ * fired twice, only the first call would do real work.
6674
+ */
6675
+ this._restoreScheduledForThisInstance = false;
6676
+ /**
6677
+ * Per-cycle guard to keep `_restorePendingDialogs` idempotent if more
6678
+ * than one subscriber fires for the same bootstrap event. The
6679
+ * regression we have seen (`postAngularBootstrap$ → restore` logged
6680
+ * twice in the same hot reload) was caused by the `NativeDialogModule`
6681
+ * also listing `NativeDialog` in its `providers` array. That has been
6682
+ * removed, but we keep this flag as a defensive net so a future stray
6683
+ * subscription does not consume the stash twice. The flag is reset
6684
+ * after the consume so subsequent HMR cycles can run their own
6685
+ * restore.
6686
+ */
6687
+ this._restoreInFlight = false;
5819
6688
  }
5820
6689
  /** Keeps track of the currently-open dialogs. */
5821
6690
  get openDialogs() {
@@ -5834,33 +6703,354 @@ class NativeDialog {
5834
6703
  if (config.id && this.getDialogById(config.id) && (typeof ngDevMode === 'undefined' || ngDevMode)) {
5835
6704
  throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
5836
6705
  }
5837
- const dialogRef = this._attachDialogContent(componentOrTemplateRef, config);
5838
- this.openDialogs.push(dialogRef);
5839
- dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
5840
- this.afterOpened.next(dialogRef);
5841
- // Notify the dialog container that the content has been attached.
5842
- // dialogContainer._initializeWithAttachedContent();
5843
- return dialogRef;
6706
+ const dialogRef = this._attachDialogContent(componentOrTemplateRef, config);
6707
+ this.openDialogs.push(dialogRef);
6708
+ this._openDialogMetadata.set(dialogRef, {
6709
+ componentClass: componentOrTemplateRef instanceof TemplateRef ? undefined : componentOrTemplateRef,
6710
+ config,
6711
+ });
6712
+ dialogRef.afterClosed().subscribe(() => this._removeOpenDialog(dialogRef));
6713
+ this.afterOpened.next(dialogRef);
6714
+ // Notify the dialog container that the content has been attached.
6715
+ // dialogContainer._initializeWithAttachedContent();
6716
+ return dialogRef;
6717
+ }
6718
+ /**
6719
+ * Closes all of the currently-open dialogs.
6720
+ */
6721
+ closeAll() {
6722
+ this._closeDialogs(this.openDialogs);
6723
+ }
6724
+ /**
6725
+ * Finds an open dialog by its id.
6726
+ * @param id ID to use when looking up the dialog.
6727
+ */
6728
+ getDialogById(id) {
6729
+ return this.openDialogs.find((dialog) => dialog.id === id);
6730
+ }
6731
+ ngOnDestroy() {
6732
+ hmrDialogDiag(`NativeDialog ngOnDestroy instanceId=${this._diagInstanceId} openCount=${this._openDialogsAtThisLevel.length} subCount=${this._hmrSubscriptions.length}`);
6733
+ // Only close the dialogs at this level on destroy
6734
+ // since the parent service may still be active.
6735
+ this._closeDialogs(this._openDialogsAtThisLevel);
6736
+ this._afterAllClosedAtThisLevel.complete();
6737
+ this._afterOpenedAtThisLevel.complete();
6738
+ for (const sub of this._hmrSubscriptions) {
6739
+ try {
6740
+ sub.unsubscribe();
6741
+ }
6742
+ catch {
6743
+ // Best-effort: tearing down the dialog service shouldn't prevent the
6744
+ // rest of the module disposal from completing.
6745
+ }
6746
+ }
6747
+ this._hmrSubscriptions = [];
6748
+ }
6749
+ /**
6750
+ * Wires up HMR capture/restore. Only the root-level dialog manages the
6751
+ * stash so a stack of `NativeDialog` instances inside a child injector
6752
+ * doesn't fight for it.
6753
+ *
6754
+ * Production short-circuit: `isAngularHmrEnabled()` returns `false` in
6755
+ * release builds and when no NS Vite / webpack HMR runtime is present,
6756
+ * so the long-lived subscriptions below never attach in shipping apps.
6757
+ *
6758
+ * `postAngularBootstrap$` is a `ReplaySubject(1)` (see `application.ts`)
6759
+ * which means a `NativeDialog` instantiated *after* the bootstrap event
6760
+ * has already fired (typical when the user app injects `NativeDialog`
6761
+ * lazily via a service like `view.service.ts`) still receives the
6762
+ * buffered event and runs the restore path.
6763
+ */
6764
+ _initHmrLifecycle() {
6765
+ if (this._parentDialog) {
6766
+ hmrDialogDiag(`_initHmrLifecycle skipped (has parent dialog) instanceId=${this._diagInstanceId}`);
6767
+ return null;
6768
+ }
6769
+ if (!isAngularHmrEnabled()) {
6770
+ return null;
6771
+ }
6772
+ hmrDialogDiag(`_initHmrLifecycle wiring up subscriptions instanceId=${this._diagInstanceId} moduleEvalCount=${getDialogModuleDiag().evals}`);
6773
+ const dispose = preAngularDisposal$.subscribe((event) => {
6774
+ if (event.moduleType !== 'main' || event.reason !== 'hotreload') {
6775
+ return;
6776
+ }
6777
+ hmrDialogDiag(`preAngularDisposal$ fired (reason=${event.reason}) instanceId=${this._diagInstanceId}`);
6778
+ this._captureOpenDialogsForHmr();
6779
+ });
6780
+ const bootstrap = postAngularBootstrap$.subscribe((event) => {
6781
+ if (event.moduleType !== 'main' || event.reason !== 'hotreload') {
6782
+ return;
6783
+ }
6784
+ hmrDialogDiag(`postAngularBootstrap$ fired (reason=${event.reason}) instanceId=${this._diagInstanceId}`);
6785
+ this._maybeScheduleRestore(`postAngularBootstrap$ (reason=${event.reason})`);
6786
+ });
6787
+ this._hmrSubscriptions.push(dispose, bootstrap);
6788
+ // Belt-and-suspenders: even though `postAngularBootstrap$` replays
6789
+ // the last event for late subscribers, also peek the global stash
6790
+ // here. This catches the case where `NativeDialog` is instantiated
6791
+ // lazily — *after* `emitModuleBootstrapEvent` has fired and the
6792
+ // ReplaySubject's buffered event no longer matches the current
6793
+ // cycle. The `_maybeScheduleRestore` guard makes this a no-op when
6794
+ // the bootstrap subscriber already queued work.
6795
+ const pendingNow = peekPendingHmrDialogs();
6796
+ hmrDialogDiag(`_initHmrLifecycle stash peek pending=${pendingNow.length} instanceId=${this._diagInstanceId}`);
6797
+ if (pendingNow.length > 0) {
6798
+ this._maybeScheduleRestore(`stash peek on ctor: ${pendingNow.length} pending dialog(s)`);
6799
+ }
6800
+ return null;
6801
+ }
6802
+ /**
6803
+ * Schedule a restore exactly once per `NativeDialog` instance.
6804
+ *
6805
+ * The work is deferred to the next macrotask for two reasons:
6806
+ *
6807
+ * 1. `postAngularBootstrap$.next(...)` is fired from inside
6808
+ * `emitModuleBootstrapEvent`, which itself runs inside the
6809
+ * `bootstrapApplication` callback — so the call stack still
6810
+ * contains Angular's ApplicationRef bootstrap pipeline. Doing
6811
+ * `this.open(...)` synchronously re-entered Angular DI while a
6812
+ * `providedIn: 'root'` factory could still be on the resolution
6813
+ * stack, which surfaced as `NG0200: Circular dependency detected
6814
+ * for NativeDialog`. Yielding to a macrotask lets the bootstrap
6815
+ * stack fully unwind first.
6816
+ * 2. The eventual `parent.showModal(...)` cannot present onto a
6817
+ * view controller whose view is not yet in the iOS window
6818
+ * hierarchy (see `_scheduleRestoreOpenWhenReady` for the second
6819
+ * wait stage); a synchronous attempt would silently no-op
6820
+ * because iOS rejects the present without throwing.
6821
+ */
6822
+ _maybeScheduleRestore(triggerDescription) {
6823
+ if (this._restoreScheduledForThisInstance) {
6824
+ hmrDialogDiag(`_maybeScheduleRestore SKIP duplicate trigger=${triggerDescription} instanceId=${this._diagInstanceId}`);
6825
+ return;
6826
+ }
6827
+ this._restoreScheduledForThisInstance = true;
6828
+ hmrDialogLog(`scheduling restore (trigger=${triggerDescription}) instanceId=${this._diagInstanceId}`);
6829
+ setTimeout(() => {
6830
+ void this._restorePendingDialogs();
6831
+ }, 0);
6832
+ }
6833
+ _captureOpenDialogsForHmr() {
6834
+ const candidates = this._openDialogsAtThisLevel.map((ref) => {
6835
+ const meta = this._openDialogMetadata.get(ref);
6836
+ return {
6837
+ ref,
6838
+ componentClass: meta?.componentClass,
6839
+ config: meta?.config ?? new NativeDialogConfig(),
6840
+ };
6841
+ });
6842
+ hmrDialogDiag(`_captureOpenDialogsForHmr instanceId=${this._diagInstanceId} candidates=${candidates.length} (${candidates.map((c) => `${c.componentClass?.name ?? '(template)'}|preserveOnHmr=${!!c.config?.preserveOnHmr}`).join(', ')})`);
6843
+ const captured = captureDialogsForHmr(candidates);
6844
+ if (captured.length > 0) {
6845
+ // Suppress the close animation that's about to fire as part of
6846
+ // `_closeAllModalViewsInternal()` during root-view replacement.
6847
+ // Without this the user sees a slide-down + slide-up flicker
6848
+ // wrapping every HMR reboot.
6849
+ for (const candidate of candidates) {
6850
+ suppressNativeCloseAnimation(candidate);
6851
+ }
6852
+ hmrDialogLog(`captured ${captured.length} dialog(s) for HMR restore [${captured.map((c) => c.componentName).join(', ')}]`);
6853
+ }
6854
+ else if (this._openDialogsAtThisLevel.length > 0) {
6855
+ hmrDialogLog(`skipped capture: ${this._openDialogsAtThisLevel.length} open dialog(s) but none preservable`);
6856
+ }
6857
+ if (captured.length > 0 && NativeScriptDebug.isLogEnabled()) {
6858
+ NativeScriptDebug.hmrLog(`captured ${captured.length} dialog(s) for HMR restore`);
6859
+ }
6860
+ }
6861
+ async _restorePendingDialogs() {
6862
+ if (this._restoreInFlight) {
6863
+ hmrDialogLog('skipping restore: already in flight');
6864
+ return;
6865
+ }
6866
+ const pending = consumePendingHmrDialogs();
6867
+ if (pending.length === 0) {
6868
+ return;
6869
+ }
6870
+ this._restoreInFlight = true;
6871
+ hmrDialogLog(`restoring ${pending.length} dialog(s) after reboot [${pending.map((c) => c.componentName).join(', ')}]`);
6872
+ if (NativeScriptDebug.isLogEnabled()) {
6873
+ NativeScriptDebug.hmrLog(`restoring ${pending.length} dialog(s) after HMR reboot`);
6874
+ }
6875
+ try {
6876
+ for (const captured of pending) {
6877
+ this._restoreSingleDialog(captured);
6878
+ }
6879
+ }
6880
+ finally {
6881
+ this._restoreInFlight = false;
6882
+ }
6883
+ }
6884
+ /**
6885
+ * Resolve the freshest known class object for the captured component
6886
+ * name and re-open the dialog through the normal `open` path, but
6887
+ * only once the new root view is actually attached to the iOS window
6888
+ * hierarchy.
6889
+ *
6890
+ * Why a class lookup at all: an HMR reboot calls
6891
+ * `ɵresetCompiledComponents()` which clears each component's `ɵcmp`
6892
+ * field but **leaves the class identity unchanged**. The patched
6893
+ * `ɵɵdefineComponent` then re-registers the same class object under
6894
+ * the same source name when Angular re-renders the component. A
6895
+ * fresh-class lookup therefore returns either the same object the
6896
+ * stash captured (most common) or, on the rare occasion that the
6897
+ * source file's `@Component` decorator was re-evaluated into a brand
6898
+ * new class object (e.g. the user added `@Component(...)` to a new
6899
+ * exported symbol), the live one. Either way, a single check is
6900
+ * enough — the previous retry schedule was a no-op in 100 % of
6901
+ * observed cycles because the captured class IS the live class.
6902
+ */
6903
+ _restoreSingleDialog(captured) {
6904
+ const live = getFreshComponentClass(captured.componentName);
6905
+ const componentClass = live ?? captured.componentClass;
6906
+ const usingFresh = !!live && live !== captured.componentClass;
6907
+ // Detailed diagnostic to disambiguate the three reasons
6908
+ // `usingFreshClass=false` could log:
6909
+ // 1. liveDefined=false: the patched ɵɵdefineComponent never
6910
+ // registered a class for this name in the new realm. Most
6911
+ // likely cause: the component module was not re-evaluated
6912
+ // after the eviction (file not in `evictPaths`, or runtime
6913
+ // did not actually evict it).
6914
+ // 2. liveDefined=true but live === captured: the file WAS
6915
+ // re-evaluated, but the registry still hands back the same
6916
+ // class object. This shouldn't happen post-reboot for a
6917
+ // truly fresh realm; if it does, the captured snapshot was
6918
+ // taken from the same realm that's now serving the live
6919
+ // class — i.e. the disposal path is not actually disposing
6920
+ // the old realm before the new one boots.
6921
+ // 3. liveDefined=true and live !== captured: usingFresh path,
6922
+ // the registry IS doing its job. This is the success case.
6923
+ const liveDefined = !!live;
6924
+ const sameAsCapture = liveDefined && live === captured.componentClass;
6925
+ hmrDialogDiag(`_restoreSingleDialog name=${captured.componentName} liveDefined=${liveDefined} sameAsCapture=${sameAsCapture} usingFresh=${usingFresh} capturedFnName=${captured.componentClass?.name ?? '(none)'} liveFnName=${live?.name ?? '(none)'}`);
6926
+ this._scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh);
6927
+ }
6928
+ /**
6929
+ * Maximum time we'll wait for the new root view to attach to the
6930
+ * iOS window before giving up and trying the open anyway. NS Vite's
6931
+ * worst observed reboot-to-rootview-loaded gap is ~250 ms; one
6932
+ * second leaves headroom for slower devices without leaving the
6933
+ * captured dialog stuck in the stash if something genuinely goes
6934
+ * wrong.
6935
+ */
6936
+ static { this._ROOT_VIEW_LOADED_TIMEOUT_MS = 1_000; }
6937
+ /**
6938
+ * Defer the actual `this.open(...)` call until the new root view's
6939
+ * underlying iOS `UIViewController.view` is in the window hierarchy.
6940
+ *
6941
+ * iOS silently rejects `presentViewControllerAnimatedCompletion` if
6942
+ * the parent controller's view is not yet attached to a `UIWindow`
6943
+ * (see `view/index.ios.ts::_showNativeModalView`, which logs to
6944
+ * `Trace` and returns without throwing). In dev that would surface
6945
+ * as a vanished modal with no actionable error.
6946
+ *
6947
+ * NS marks a view as `isLoaded === true` from
6948
+ * `UILayoutViewController.viewWillAppear`, which fires once iOS has
6949
+ * decided to add the view to its window. Listening for the
6950
+ * `loadedEvent` (one-shot) plus an extra macrotask gives UIKit a
6951
+ * chance to finish window attachment before we present.
6952
+ */
6953
+ _scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh) {
6954
+ const rootView = Application.getRootView();
6955
+ if (rootView && rootView.isLoaded) {
6956
+ // Even when isLoaded is already true we yield once: a previous
6957
+ // capture in the same hot reload cycle may have set isLoaded on
6958
+ // the *outgoing* root view and a new root view is about to take
6959
+ // over. A single setTimeout(0) gives `setWindowContent` a chance
6960
+ // to finish swapping `win.rootViewController` before we try to
6961
+ // present on top of it.
6962
+ setTimeout(() => this._performRestoreOpen(captured, componentClass, usingFresh), 0);
6963
+ return;
6964
+ }
6965
+ if (!rootView) {
6966
+ // No root view at all yet — extremely unusual at this point in
6967
+ // the bootstrap, but protect against it by polling. We use the
6968
+ // same timeout budget as the loaded path.
6969
+ this._pollForRootView(captured, componentClass, usingFresh, Date.now());
6970
+ return;
6971
+ }
6972
+ hmrDialogLog(`restore ${captured.componentName} waiting for root view loadedEvent`);
6973
+ let settled = false;
6974
+ const onLoaded = () => {
6975
+ if (settled)
6976
+ return;
6977
+ settled = true;
6978
+ try {
6979
+ rootView.off(View.loadedEvent, onLoaded);
6980
+ }
6981
+ catch {
6982
+ // off may throw on stale view bindings; the `settled` flag
6983
+ // already prevents a double-fire.
6984
+ }
6985
+ // Defer one tick after viewWillAppear so UIKit completes the
6986
+ // actual window attachment (`view.window` is set during the
6987
+ // view-controller transition that follows viewWillAppear).
6988
+ setTimeout(() => this._performRestoreOpen(captured, componentClass, usingFresh), 0);
6989
+ };
6990
+ try {
6991
+ rootView.once(View.loadedEvent, onLoaded);
6992
+ }
6993
+ catch {
6994
+ // If the event subscription fails (ancient core builds), fall
6995
+ // back to a tiny delay — better to attempt the open and have
6996
+ // iOS log a benign trace than to leak the dialog stash.
6997
+ setTimeout(() => onLoaded(), 50);
6998
+ }
6999
+ // Bound the wait so a never-loading root view can't permanently
7000
+ // pin the captured dialog in the stash.
7001
+ setTimeout(() => {
7002
+ if (settled)
7003
+ return;
7004
+ hmrDialogLog(`restore ${captured.componentName} root view never loaded within ${NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS}ms; attempting open anyway`);
7005
+ onLoaded();
7006
+ }, NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS);
7007
+ }
7008
+ _pollForRootView(captured, componentClass, usingFresh, startedAt) {
7009
+ const rootView = Application.getRootView();
7010
+ if (rootView) {
7011
+ this._scheduleRestoreOpenWhenReady(captured, componentClass, usingFresh);
7012
+ return;
7013
+ }
7014
+ if (Date.now() - startedAt > NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS) {
7015
+ hmrDialogLog(`restore ${captured.componentName} aborted: no root view after ${NativeDialog._ROOT_VIEW_LOADED_TIMEOUT_MS}ms`);
7016
+ abortCapturedDialog(captured);
7017
+ return;
7018
+ }
7019
+ setTimeout(() => this._pollForRootView(captured, componentClass, usingFresh, startedAt), 16);
5844
7020
  }
5845
- /**
5846
- * Closes all of the currently-open dialogs.
5847
- */
5848
- closeAll() {
5849
- this._closeDialogs(this.openDialogs);
7021
+ _performRestoreOpen(captured, componentClass, usingFresh) {
7022
+ hmrDialogLog(`restore ${captured.componentName} usingFreshClass=${usingFresh}`);
7023
+ if (NativeScriptDebug.isLogEnabled() && usingFresh) {
7024
+ NativeScriptDebug.hmrLog(`HMR modal restore using fresh class for ${captured.componentName}`);
7025
+ }
7026
+ // Force the restored modal to open without animation so the round-
7027
+ // trip looks like an instant content refresh rather than a full
7028
+ // close-and-reopen sequence.
7029
+ const restoreConfig = buildNonAnimatedRestoreConfig(captured.config);
7030
+ try {
7031
+ const newRef = this.open(componentClass, restoreConfig);
7032
+ hmrDialogLog(`restore ${captured.componentName} → opened newRef.id=${newRef?.id ?? 'n/a'}`);
7033
+ newRef.afterClosed().subscribe({
7034
+ next: (value) => captured.graftAfterClosed(value),
7035
+ complete: () => captured.graftAfterClosed(undefined),
7036
+ });
7037
+ }
7038
+ catch (err) {
7039
+ abortCapturedDialog(captured);
7040
+ const message = err?.message ?? String(err);
7041
+ hmrDialogLog(`restore ${captured.componentName} FAILED: ${message}`);
7042
+ if (NativeScriptDebug.isLogEnabled()) {
7043
+ NativeScriptDebug.hmrLogError(`HMR modal restore failed: ${message}`);
7044
+ }
7045
+ }
5850
7046
  }
5851
7047
  /**
5852
- * Finds an open dialog by its id.
5853
- * @param id ID to use when looking up the dialog.
7048
+ * Test/debug helper: discard any captured modals without restoring them.
7049
+ * Mostly useful when projects want to opt out of restoration without
7050
+ * recompiling. Public callers should prefer `preserveOnHmr: false` instead.
5854
7051
  */
5855
- getDialogById(id) {
5856
- return this.openDialogs.find((dialog) => dialog.id === id);
5857
- }
5858
- ngOnDestroy() {
5859
- // Only close the dialogs at this level on destroy
5860
- // since the parent service may still be active.
5861
- this._closeDialogs(this._openDialogsAtThisLevel);
5862
- this._afterAllClosedAtThisLevel.complete();
5863
- this._afterOpenedAtThisLevel.complete();
7052
+ static _clearPendingHmrDialogs() {
7053
+ clearPendingHmrDialogs();
5864
7054
  }
5865
7055
  /**
5866
7056
  * Attaches the user-provided component to the already-created dialog container.
@@ -5925,6 +7115,7 @@ class NativeDialog {
5925
7115
  const index = this.openDialogs.indexOf(dialogRef);
5926
7116
  if (index > -1) {
5927
7117
  this.openDialogs.splice(index, 1);
7118
+ this._openDialogMetadata.delete(dialogRef);
5928
7119
  // If all the dialogs were closed, remove/restore the `aria-hidden`
5929
7120
  // to a the siblings and emit to the `afterAllClosed` stream.
5930
7121
  if (!this.openDialogs.length) {
@@ -5961,6 +7152,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
5961
7152
  function _applyConfigDefaults(config, defaultOptions) {
5962
7153
  return { ...defaultOptions, ...config };
5963
7154
  }
7155
+ /**
7156
+ * Register `NativeDialog` with the application's HMR eager-instantiate
7157
+ * registry so the post-bootstrap pipeline forces an `injector.get()` on
7158
+ * the service in the same JS task as the bootstrap event. Without this,
7159
+ * `NativeDialog` is only constructed when something in user-app code
7160
+ * injects it (typically a wrapper service that opens modals). When that
7161
+ * injection happens lazily — on first user-driven modal open — captured
7162
+ * dialogs from a prior HMR cycle wait until the user reopens *something*
7163
+ * before the new realm even sees the stash. Eager instantiation makes
7164
+ * the restore work happen as early as possible during a hot reload while
7165
+ * staying gated to dev mode + HMR-active environments.
7166
+ *
7167
+ * Registered after the class declaration so the closure references the
7168
+ * fully-defined class rather than tripping the temporal dead zone.
7169
+ *
7170
+ * Idempotent: the registry de-dupes function references, so multiple
7171
+ * evaluations of this module across HMR cycles never accumulate stale
7172
+ * registrations.
7173
+ */
7174
+ if (isAngularHmrEnabled()) {
7175
+ const added = registerHmrEagerInstantiator((injector) => {
7176
+ try {
7177
+ const inst = injector.get(NativeDialog, null);
7178
+ hmrDialogDiag(`eager-instantiator fired NativeDialog=${inst ? `instance#${inst._diagInstanceId}` : 'null'}`);
7179
+ }
7180
+ catch (err) {
7181
+ hmrDialogDiag(`eager-instantiator threw: ${err?.message ?? err}`);
7182
+ // Some user-app providers may not include `NativeDialog` (e.g. a
7183
+ // module that doesn't depend on the dialog feature). The registry
7184
+ // contract is "best effort": failing to find the token must be a
7185
+ // silent no-op so unrelated apps aren't penalized.
7186
+ }
7187
+ });
7188
+ hmrDialogDiag(`registerHmrEagerInstantiator added=${added} (false means already present in registry)`);
7189
+ }
5964
7190
 
5965
7191
  /**
5966
7192
  * @license
@@ -6033,102 +7259,634 @@ function getClosestDialog(element, openDialogs) {
6033
7259
  while (view && !Object.hasOwnProperty.call(view, '__ng_modal_id__')) {
6034
7260
  view = view.parent;
6035
7261
  }
6036
- return view ? openDialogs.find((dialog) => dialog.id === view['__ng_modal_id__']) : null;
6037
- }
6038
-
6039
- class NativeDialogModule {
6040
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
6041
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, imports: [NativeDialogCloseDirective], exports: [NativeDialogCloseDirective] }); }
6042
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, providers: [NativeDialog] }); }
6043
- }
6044
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, decorators: [{
6045
- type: NgModule,
6046
- args: [{
6047
- imports: [NativeDialogCloseDirective],
6048
- exports: [NativeDialogCloseDirective],
6049
- providers: [NativeDialog],
6050
- }]
6051
- }] });
6052
-
6053
- // Allows greater flexibility with `file-system` and Angular
6054
- // Also provides a way for `file-system` to be mocked for testing
6055
- class NSFileSystem {
6056
- currentApp() {
6057
- return knownFolders.currentApp();
7262
+ return view ? openDialogs.find((dialog) => dialog.id === view['__ng_modal_id__']) : null;
7263
+ }
7264
+
7265
+ /**
7266
+ * Convenience module that re-exports the `NativeDialogCloseDirective` for
7267
+ * template-driven `[nativeDialogClose]` usage.
7268
+ *
7269
+ * **Important**: `NativeDialog` itself is **not** listed in this module's
7270
+ * `providers` array. The service is `@Injectable({ providedIn: 'root' })`,
7271
+ * which already registers a single root-level instance and is fully
7272
+ * tree-shakeable. Listing it here as well caused Angular to treat the
7273
+ * module-level provider and the `providedIn: 'root'` factory as
7274
+ * *separate* registrations once the module was pulled into a standalone
7275
+ * app via `importProvidersFrom(NativeDialogModule, ...)`. The duplicate
7276
+ * registration triggered:
7277
+ *
7278
+ * - Two `NativeDialog` instances in the same root environment injector,
7279
+ * each subscribing to `postAngularBootstrap$`, which produced
7280
+ * duplicate restore attempts during HMR.
7281
+ * - `NG0200: Circular dependency detected for NativeDialog` while a
7282
+ * captured modal was being re-opened during HMR restore, because the
7283
+ * second resolution started while the first was still in progress.
7284
+ *
7285
+ * Removing the redundant entry collapses both providers back into a
7286
+ * single root-level instance, which is what `providedIn: 'root'`
7287
+ * documents. App authors who explicitly wire `NativeDialog` themselves
7288
+ * (e.g. in a feature module's `providers`) keep working unchanged
7289
+ * because they're targeting the same class symbol.
7290
+ */
7291
+ class NativeDialogModule {
7292
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7293
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, imports: [NativeDialogCloseDirective], exports: [NativeDialogCloseDirective] }); }
7294
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule }); }
7295
+ }
7296
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeDialogModule, decorators: [{
7297
+ type: NgModule,
7298
+ args: [{
7299
+ imports: [NativeDialogCloseDirective],
7300
+ exports: [NativeDialogCloseDirective],
7301
+ }]
7302
+ }] });
7303
+
7304
+ // Allows greater flexibility with `file-system` and Angular
7305
+ // Also provides a way for `file-system` to be mocked for testing
7306
+ class NSFileSystem {
7307
+ currentApp() {
7308
+ return knownFolders.currentApp();
7309
+ }
7310
+ fileFromPath(path) {
7311
+ return File.fromPath(path);
7312
+ }
7313
+ fileExists(path) {
7314
+ return File.exists(path);
7315
+ }
7316
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7317
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem }); }
7318
+ }
7319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem, decorators: [{
7320
+ type: Injectable
7321
+ }] });
7322
+
7323
+ /**
7324
+ * creates a DetachedLoader either linked to the ViewContainerRef or the ApplicationRef if ViewContainerRef is not defined
7325
+ * @param resolver component factory resolver
7326
+ * @param injector default injector, unused if viewContainerRef is set
7327
+ * @param viewContainerRef where the view should live in the angular tree
7328
+ * @returns reference to the DetachedLoader
7329
+ */
7330
+ function generateDetachedLoader(resolver, injector, viewContainerRef) {
7331
+ injector = viewContainerRef?.injector || injector;
7332
+ const detachedFactory = resolver.resolveComponentFactory(DetachedLoader);
7333
+ const detachedLoaderRef = viewContainerRef?.createComponent(detachedFactory) || detachedFactory.create(injector);
7334
+ if (!viewContainerRef) {
7335
+ injector.get(ApplicationRef).attachView(detachedLoaderRef.hostView);
7336
+ }
7337
+ detachedLoaderRef.changeDetectorRef.detectChanges();
7338
+ return detachedLoaderRef;
7339
+ }
7340
+ /**
7341
+ * Generates a NgViewRef from a component or template. @see NgViewRef
7342
+ * Pass keepNativeViewAttached as `true` if you don't want the first native view to be detached from its parent.
7343
+ * For opening modals and others, the firstNativeLikeView should be detached.
7344
+ * @param typeOrTemplate ComponentType or TemplateRef that should be instanced
7345
+ * @param options options for creating the view
7346
+ * @returns NgViewRef
7347
+ */
7348
+ function generateNativeScriptView(typeOrTemplate, options) {
7349
+ let detachedLoaderRef = options.detachedLoaderRef;
7350
+ const reusingDetachedLoader = !!detachedLoaderRef;
7351
+ if (reusingDetachedLoader) {
7352
+ options.viewContainerRef = detachedLoaderRef.instance.vc;
7353
+ }
7354
+ const injector = options.viewContainerRef?.injector || options.injector;
7355
+ const resolver = options.resolver || injector.get(ComponentFactoryResolver);
7356
+ if (!detachedLoaderRef && (options.viewContainerRef || typeOrTemplate instanceof TemplateRef)) {
7357
+ detachedLoaderRef = generateDetachedLoader(resolver, injector, options.viewContainerRef);
7358
+ }
7359
+ let portal;
7360
+ if (typeOrTemplate instanceof TemplateRef) {
7361
+ portal = new TemplatePortal(typeOrTemplate, detachedLoaderRef.instance.vc);
7362
+ }
7363
+ else {
7364
+ portal = new ComponentPortal(typeOrTemplate, detachedLoaderRef?.instance.vc);
7365
+ }
7366
+ const parentView = new ContentView();
7367
+ const portalOutlet = new NativeScriptDomPortalOutlet(parentView, resolver, injector.get(ApplicationRef), injector);
7368
+ const componentOrTemplateRef = portalOutlet.attach(portal);
7369
+ componentOrTemplateRef.onDestroy(() => {
7370
+ portalOutlet.dispose();
7371
+ });
7372
+ if (detachedLoaderRef && !reusingDetachedLoader) {
7373
+ componentOrTemplateRef.onDestroy(() => {
7374
+ detachedLoaderRef.destroy();
7375
+ });
7376
+ }
7377
+ const viewRef = new NgViewRef(componentOrTemplateRef);
7378
+ viewRef.detachedLoaderRef = detachedLoaderRef;
7379
+ if (!options.keepNativeViewAttached) {
7380
+ viewRef.detachNativeLikeView();
7381
+ }
7382
+ return viewRef;
7383
+ }
7384
+
7385
+ /**
7386
+ * Framework-agnostic Hot-Module-Replacement state cache.
7387
+ *
7388
+ * The {@link HmrCacheStore} class is intentionally free of any Angular
7389
+ * imports so a future `@nativescript/solid` (or any other framework
7390
+ * binding) can lift this file as-is and wrap it with its own DI
7391
+ * primitive. The Angular DI wrapper lives in
7392
+ * `./hmr-cache.service.ts`.
7393
+ *
7394
+ * # What it does
7395
+ *
7396
+ * The NativeScript iOS runtime exposes a Vite-spec compliant
7397
+ * `import.meta.hot` on every imported module (see
7398
+ * `@nativescript/ios` →
7399
+ * `runtime/HMRSupport.{h,mm}::InitializeImportMetaHot`). The runtime
7400
+ * keeps a per-module persistent `data` object alive in C++ across V8
7401
+ * evaluation cycles and canonicalizes the module path so the same
7402
+ * bucket survives the URL variations Vite cycles through during a
7403
+ * save (HMR boot/live tags, versioned bridge paths, common script
7404
+ * extensions). When `@nativescript/vite`'s Angular HMR client calls
7405
+ * `globalThis.__nsRunHmrDispose()` before `__reboot_ng_modules__`,
7406
+ * every registered `dispose(cb)` fires and is handed the same `data`
7407
+ * object the next module evaluation will read from.
7408
+ *
7409
+ * `HmrCacheStore` rides on top of that primitive:
7410
+ *
7411
+ * 1. On construction, it copies any previously-stashed entries out
7412
+ * of `import.meta.hot.data['ns-hmr-cache']` (or whatever
7413
+ * `storageKey` the caller picked) into an in-memory `Map`.
7414
+ * 2. It registers a single `import.meta.hot.dispose` callback that
7415
+ * writes the in-memory `Map` back as a plain object before the
7416
+ * next reboot.
7417
+ * 3. Every `set` re-orders the key to the end of the `Map` (LRU);
7418
+ * when `size > maxEntries`, the oldest entry is evicted. This
7419
+ * stops a long dev session from accumulating unbounded state for
7420
+ * features the developer no longer touches.
7421
+ * 4. It subscribes to a custom HMR event (default
7422
+ * `'ns:cache-invalidate'`) so a Vite plugin or dev server can
7423
+ * push targeted cache evictions — e.g. "the OData schema for
7424
+ * `/safety/forms` changed, drop anything that depends on it".
7425
+ *
7426
+ * In production / `--no-hmr` builds `import.meta.hot` is `undefined`,
7427
+ * the store collapses to a pure in-memory cache that lives for the
7428
+ * lifetime of the process, and the public API is identical so callers
7429
+ * never need to special-case build modes.
7430
+ *
7431
+ * # Why a class and not a plain object
7432
+ *
7433
+ * Encapsulating the LRU bookkeeping behind named methods (`get`,
7434
+ * `set`, `invalidate`, `scope`) lets us evolve the eviction policy
7435
+ * (e.g. add TTLs, weighted entries, structured-clone enforcement)
7436
+ * without touching every call site. The framework wrappers expose
7437
+ * the same surface so app authors learn one API regardless of which
7438
+ * framework they use.
7439
+ */
7440
+ const DEFAULT_MAX_ENTRIES = 256;
7441
+ const DEFAULT_STORAGE_KEY = 'ns-hmr-cache';
7442
+ const DEFAULT_INVALIDATE_EVENT = 'ns:cache-invalidate';
7443
+ /**
7444
+ * Read `import.meta.hot` defensively so the package compiles even
7445
+ * under TypeScript configs that don't extend an `ImportMeta` interface
7446
+ * with a `hot` field. The cast goes through `unknown` so the local
7447
+ * type is the only one in play; `vite/client`-aware apps still get
7448
+ * their own typing on `import.meta.hot` at every call site outside
7449
+ * this module.
7450
+ */
7451
+ function readImportMetaHot() {
7452
+ try {
7453
+ const meta = typeof import.meta !== 'undefined'
7454
+ ? import.meta
7455
+ : undefined;
7456
+ return meta?.hot;
7457
+ }
7458
+ catch {
7459
+ return undefined;
7460
+ }
7461
+ }
7462
+ class HmrCacheStore {
7463
+ /**
7464
+ * @param initialEntries Entries to seed the store with (typically
7465
+ * the previous session's snapshot read from
7466
+ * `import.meta.hot.data`).
7467
+ * @param options See {@link HmrCacheStoreOptions}.
7468
+ */
7469
+ constructor(initialEntries = [], options = {}) {
7470
+ this._map = new Map(initialEntries);
7471
+ const requested = options.maxEntries;
7472
+ this._maxEntries =
7473
+ typeof requested === 'number' && requested > 0
7474
+ ? Math.floor(requested)
7475
+ : 0;
7476
+ this._log = options.log ?? (() => { });
7477
+ // Trim seed if it overshoots the configured ceiling — possible if
7478
+ // a previous session ran with a larger `maxEntries` than this one.
7479
+ this._enforceMaxEntries();
7480
+ }
7481
+ get(key) {
7482
+ if (!this._map.has(key)) {
7483
+ return undefined;
7484
+ }
7485
+ // LRU touch: re-insert so the entry moves to the end of the
7486
+ // insertion-order Map.
7487
+ const value = this._map.get(key);
7488
+ this._map.delete(key);
7489
+ this._map.set(key, value);
7490
+ return value;
7491
+ }
7492
+ set(key, value) {
7493
+ if (this._map.has(key)) {
7494
+ // Delete-then-set keeps insertion order monotonic.
7495
+ this._map.delete(key);
7496
+ }
7497
+ this._map.set(key, value);
7498
+ this._enforceMaxEntries();
7499
+ }
7500
+ has(key) {
7501
+ return this._map.has(key);
7502
+ }
7503
+ delete(key) {
7504
+ this._map.delete(key);
7505
+ }
7506
+ /**
7507
+ * Drop a specific entry, or every entry when `key` is omitted.
7508
+ * Equivalent to {@link delete} (with key) or {@link clear} (without)
7509
+ * — exposed as a single method so callers and event handlers can
7510
+ * forward an optional key without branching.
7511
+ */
7512
+ invalidate(key) {
7513
+ if (key === undefined || key === null) {
7514
+ this.clear();
7515
+ return;
7516
+ }
7517
+ this.delete(key);
7518
+ }
7519
+ /** Drop every cached entry. */
7520
+ clear() {
7521
+ this._map.clear();
7522
+ }
7523
+ /** Total number of cached entries across all scopes. */
7524
+ size() {
7525
+ return this._map.size;
7526
+ }
7527
+ /** Snapshot of every key currently in the cache. */
7528
+ keys() {
7529
+ return Array.from(this._map.keys());
7530
+ }
7531
+ /**
7532
+ * Returns a namespaced view of this store. All keys passed to the
7533
+ * returned object are auto-prefixed with `<prefix>:`. Useful so
7534
+ * each feature module can avoid stomping on neighbours' keys
7535
+ * without repeating the prefix at every call site.
7536
+ *
7537
+ * @example
7538
+ * ```ts
7539
+ * const cache = createDefaultHmrCacheStore();
7540
+ * const submissions = cache.scope('page-my-submissions');
7541
+ * submissions.set('items', [...]); // stored under 'page-my-submissions:items'
7542
+ * ```
7543
+ */
7544
+ scope(prefix) {
7545
+ if (!prefix) {
7546
+ throw new Error('[HmrCacheStore] scope() requires a non-empty prefix');
7547
+ }
7548
+ const fullPrefix = `${prefix}:`;
7549
+ const parent = this;
7550
+ return {
7551
+ prefix: fullPrefix,
7552
+ get(key) {
7553
+ return parent.get(fullPrefix + key);
7554
+ },
7555
+ set(key, value) {
7556
+ parent.set(fullPrefix + key, value);
7557
+ },
7558
+ has(key) {
7559
+ return parent.has(fullPrefix + key);
7560
+ },
7561
+ delete(key) {
7562
+ parent.delete(fullPrefix + key);
7563
+ },
7564
+ clear() {
7565
+ for (const k of parent.keys()) {
7566
+ if (k.startsWith(fullPrefix)) {
7567
+ parent.delete(k);
7568
+ }
7569
+ }
7570
+ },
7571
+ size() {
7572
+ let n = 0;
7573
+ for (const k of parent.keys()) {
7574
+ if (k.startsWith(fullPrefix)) {
7575
+ n++;
7576
+ }
7577
+ }
7578
+ return n;
7579
+ },
7580
+ };
7581
+ }
7582
+ /**
7583
+ * Serialize every entry into a plain object suitable for stashing
7584
+ * on `import.meta.hot.data`. Used by the dispose callback in
7585
+ * {@link createDefaultHmrCacheStore} and re-exported for callers
7586
+ * that want to integrate with another persistence layer (e.g. a
7587
+ * test harness that snapshots between cases).
7588
+ */
7589
+ toObject() {
7590
+ const out = {};
7591
+ for (const [k, v] of this._map.entries()) {
7592
+ out[k] = v;
7593
+ }
7594
+ return out;
7595
+ }
7596
+ _enforceMaxEntries() {
7597
+ if (this._maxEntries <= 0) {
7598
+ return;
7599
+ }
7600
+ while (this._map.size > this._maxEntries) {
7601
+ const oldestKey = this._map.keys().next().value;
7602
+ if (oldestKey === undefined) {
7603
+ return;
7604
+ }
7605
+ this._map.delete(oldestKey);
7606
+ this._log(`[HmrCacheStore] evicted oldest key="${oldestKey}" (size now ${this._map.size}/${this._maxEntries})`);
7607
+ }
7608
+ }
7609
+ }
7610
+ /**
7611
+ * Build an {@link HmrCacheStore} bound to the current module's
7612
+ * `import.meta.hot` context — i.e. the store's data survives HMR
7613
+ * reboots and listens for the `'ns:cache-invalidate'` custom event.
7614
+ *
7615
+ * Caller responsibility: invoke this from the module that "owns" the
7616
+ * cache. `import.meta` is per-module, so the dispose callback will be
7617
+ * registered against whichever module physically calls this function.
7618
+ * In `@nativescript/angular` the canonical owner is
7619
+ * `hmr-cache.service.ts`; in `@nativescript/solid` it would be the
7620
+ * equivalent solid-side module.
7621
+ *
7622
+ * Returns a freshly-constructed store. Callers should treat it as a
7623
+ * singleton — calling this twice from the same module yields two
7624
+ * independent stores, which is almost never what you want.
7625
+ */
7626
+ function createDefaultHmrCacheStore(options = {}) {
7627
+ const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
7628
+ const invalidateEventName = options.invalidateEventName ?? DEFAULT_INVALIDATE_EVENT;
7629
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
7630
+ const log = options.log ?? (() => { });
7631
+ const hot = readImportMetaHot();
7632
+ // Read previous session's snapshot (if any) and seed the store.
7633
+ const previousSnapshot = (hot?.data?.[storageKey] ?? {});
7634
+ const previousEntries = Object.entries(previousSnapshot);
7635
+ const store = new HmrCacheStore(previousEntries, {
7636
+ maxEntries,
7637
+ storageKey,
7638
+ invalidateEventName,
7639
+ log,
7640
+ });
7641
+ if (hot) {
7642
+ if (previousEntries.length) {
7643
+ log(`[HmrCacheStore] rehydrated ${previousEntries.length} entr${previousEntries.length === 1 ? 'y' : 'ies'} from previous HMR session (storageKey="${storageKey}")`);
7644
+ }
7645
+ // Stash the live store back on dispose so the next reboot finds
7646
+ // exactly what's in memory right now.
7647
+ hot.dispose((data) => {
7648
+ const snapshot = store.toObject();
7649
+ data[storageKey] = snapshot;
7650
+ log(`[HmrCacheStore] dispose stashed ${Object.keys(snapshot).length} entr${Object.keys(snapshot).length === 1 ? 'y' : 'ies'} (storageKey="${storageKey}")`);
7651
+ });
7652
+ // Listen for server-side invalidation events. Vite plugins push
7653
+ // these via `server.ws.send({ type: 'custom', event: 'ns:cache-invalidate', data: { key? } })`.
7654
+ // The HMR client forwards them to `import.meta.hot.on`. If the
7655
+ // runtime doesn't expose `on` (older `@nativescript/ios`), this
7656
+ // is a clean no-op.
7657
+ if (typeof hot.on === 'function') {
7658
+ hot.on(invalidateEventName, (payload) => {
7659
+ const targetKey = payload?.key;
7660
+ store.invalidate(targetKey);
7661
+ log(targetKey
7662
+ ? `[HmrCacheStore] server-side invalidate dropped key="${targetKey}"`
7663
+ : `[HmrCacheStore] server-side invalidate cleared all entries`);
7664
+ });
7665
+ }
7666
+ }
7667
+ return store;
7668
+ }
7669
+
7670
+ /**
7671
+ * Skip the API call your component already paid for last save.
7672
+ *
7673
+ * Inject {@link HmrCacheService} from any Angular component or service
7674
+ * to read and write a per-app key/value cache that **survives the
7675
+ * `__reboot_ng_modules__` cycle** triggered by every HMR file save.
7676
+ * Backed by `@nativescript/ios`'s native `import.meta.hot.data`
7677
+ * (`runtime/HMRSupport.{h,mm}`) and drained via
7678
+ * `@nativescript/vite`'s `globalThis.__nsRunHmrDispose()` hook before
7679
+ * Angular tears down its realm, so the same value the previous
7680
+ * component instance produced is handed straight to the freshly-
7681
+ * instantiated one — no network round-trip, no spinner flash.
7682
+ *
7683
+ * In production / `--no-hmr` builds `import.meta.hot` is `undefined`
7684
+ * and the cache collapses to a plain in-memory object that lives for
7685
+ * the lifetime of the process. The public API is identical, so callers
7686
+ * never need to special-case build modes.
7687
+ *
7688
+ * @example Skip the initial fetch on save
7689
+ * ```ts
7690
+ * import { HmrCacheService } from '@nativescript/angular';
7691
+ *
7692
+ * @Component({...})
7693
+ * export class MyComponent implements OnInit {
7694
+ * private hmrCache = inject(HmrCacheService);
7695
+ *
7696
+ * ngOnInit() {
7697
+ * const cached = this.hmrCache.get<MyResult>('my-feature:items');
7698
+ * if (cached) {
7699
+ * this.applyResult(cached);
7700
+ * return;
7701
+ * }
7702
+ * this.api.load().subscribe((result) => {
7703
+ * this.hmrCache.set('my-feature:items', result);
7704
+ * this.applyResult(result);
7705
+ * });
7706
+ * }
7707
+ * }
7708
+ * ```
7709
+ *
7710
+ * @example Namespaced via {@link scope}
7711
+ * ```ts
7712
+ * private cache = inject(HmrCacheService).scope('page-my-submissions');
7713
+ * // …
7714
+ * this.cache.set('items', items); // → 'page-my-submissions:items'
7715
+ * this.cache.get('items'); // ← 'page-my-submissions:items'
7716
+ * ```
7717
+ *
7718
+ * @example Server-side invalidation from a Vite plugin
7719
+ * ```ts
7720
+ * // vite.config.ts
7721
+ * export default defineConfig({
7722
+ * plugins: [
7723
+ * {
7724
+ * name: 'my-schema-watcher',
7725
+ * configureServer(server) {
7726
+ * server.watcher.on('change', (path) => {
7727
+ * if (path.endsWith('schema.json')) {
7728
+ * server.ws.send({
7729
+ * type: 'custom',
7730
+ * event: 'ns:cache-invalidate',
7731
+ * data: { key: 'my-feature:items' },
7732
+ * });
7733
+ * }
7734
+ * });
7735
+ * },
7736
+ * },
7737
+ * ],
7738
+ * });
7739
+ * ```
7740
+ *
7741
+ * Memory ceiling: the cache LRU-evicts at 256 entries by default. Pass
7742
+ * a custom ceiling via {@link configureHmrCache} if your app churns
7743
+ * through more keys than that in a typical dev session, or set `0` for
7744
+ * unlimited (unbounded growth — only safe for short-lived dev work).
7745
+ *
7746
+ * @see HmrCacheStore — the framework-agnostic engine. Stable enough to
7747
+ * lift into `@nativescript/solid` / other framework bindings without
7748
+ * modification.
7749
+ */
7750
+ class HmrCacheService {
7751
+ constructor() {
7752
+ this._store = getOrCreateSharedStore();
7753
+ /**
7754
+ * `true` when `import.meta.hot` is wired (i.e. NativeScript Vite HMR
7755
+ * is active and `@nativescript/ios` is recent enough to expose
7756
+ * `import.meta.hot.data`). `false` in production / `--no-hmr` /
7757
+ * legacy webpack builds.
7758
+ *
7759
+ * Most callers should NOT branch on this — the public API works
7760
+ * identically in both cases. Use it only when you want to opt OUT
7761
+ * of caching in production (e.g. always fetch fresh data when not
7762
+ * developing).
7763
+ */
7764
+ this.isHmr = isAngularHmrEnabled() && hasImportMetaHot();
7765
+ }
7766
+ get(key) {
7767
+ return this._store.get(key);
7768
+ }
7769
+ set(key, value) {
7770
+ this._store.set(key, value);
7771
+ }
7772
+ has(key) {
7773
+ return this._store.has(key);
7774
+ }
7775
+ delete(key) {
7776
+ this._store.delete(key);
7777
+ }
7778
+ /**
7779
+ * Drop a single entry, or every entry when `key` is omitted. Same
7780
+ * shape as the `'ns:cache-invalidate'` HMR-event payload the store
7781
+ * listens for, so application code can call this directly to mirror
7782
+ * a server-side eviction.
7783
+ */
7784
+ invalidate(key) {
7785
+ this._store.invalidate(key);
7786
+ }
7787
+ /** Drop every cached entry. Equivalent to `invalidate()` with no key. */
7788
+ clear() {
7789
+ this._store.clear();
7790
+ }
7791
+ /** Total number of entries across every scope. */
7792
+ size() {
7793
+ return this._store.size();
6058
7794
  }
6059
- fileFromPath(path) {
6060
- return File.fromPath(path);
7795
+ /** Snapshot of every key currently cached. Useful for debug overlays. */
7796
+ keys() {
7797
+ return this._store.keys();
6061
7798
  }
6062
- fileExists(path) {
6063
- return File.exists(path);
7799
+ /**
7800
+ * Returns a namespaced view of the cache. All `get` / `set` /
7801
+ * `has` / `delete` calls on the returned object are auto-prefixed
7802
+ * with `<scopeName>:`. Recommended over global keys so feature
7803
+ * modules don't accidentally collide.
7804
+ *
7805
+ * @example
7806
+ * ```ts
7807
+ * private cache = inject(HmrCacheService).scope('page-my-submissions');
7808
+ * // …
7809
+ * this.cache.set('items', items); // → 'page-my-submissions:items'
7810
+ * ```
7811
+ */
7812
+ scope(scopeName) {
7813
+ return this._store.scope(scopeName);
6064
7814
  }
6065
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6066
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem }); }
7815
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: HmrCacheService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7816
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: HmrCacheService, providedIn: 'root' }); }
6067
7817
  }
6068
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NSFileSystem, decorators: [{
6069
- type: Injectable
7818
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: HmrCacheService, decorators: [{
7819
+ type: Injectable,
7820
+ args: [{ providedIn: 'root' }]
6070
7821
  }] });
6071
-
6072
7822
  /**
6073
- * creates a DetachedLoader either linked to the ViewContainerRef or the ApplicationRef if ViewContainerRef is not defined
6074
- * @param resolver component factory resolver
6075
- * @param injector default injector, unused if viewContainerRef is set
6076
- * @param viewContainerRef where the view should live in the angular tree
6077
- * @returns reference to the DetachedLoader
7823
+ * Override the default cache configuration. Must be called BEFORE the
7824
+ * first injection of {@link HmrCacheService} (i.e. before Angular
7825
+ * bootstrap, or as the very first statement in `main.ts`); otherwise
7826
+ * the call is a no-op because the singleton store has already been
7827
+ * built with the previous (or default) options.
7828
+ *
7829
+ * Typical use case: bumping `maxEntries` for a large multi-feature
7830
+ * monorepo dev session, or pointing a custom `invalidateEventName` at
7831
+ * a Vite plugin that prefixes its events with the project name.
7832
+ *
7833
+ * Returns `true` if the configuration was applied, `false` if the
7834
+ * store had already been instantiated by an earlier injection.
6078
7835
  */
6079
- function generateDetachedLoader(resolver, injector, viewContainerRef) {
6080
- injector = viewContainerRef?.injector || injector;
6081
- const detachedFactory = resolver.resolveComponentFactory(DetachedLoader);
6082
- const detachedLoaderRef = viewContainerRef?.createComponent(detachedFactory) || detachedFactory.create(injector);
6083
- if (!viewContainerRef) {
6084
- injector.get(ApplicationRef).attachView(detachedLoaderRef.hostView);
7836
+ function configureHmrCache(options) {
7837
+ if (sharedStore !== null) {
7838
+ return false;
6085
7839
  }
6086
- detachedLoaderRef.changeDetectorRef.detectChanges();
6087
- return detachedLoaderRef;
7840
+ pendingOptions = options;
7841
+ return true;
6088
7842
  }
6089
7843
  /**
6090
- * Generates a NgViewRef from a component or template. @see NgViewRef
6091
- * Pass keepNativeViewAttached as `true` if you don't want the first native view to be detached from its parent.
6092
- * For opening modals and others, the firstNativeLikeView should be detached.
6093
- * @param typeOrTemplate ComponentType or TemplateRef that should be instanced
6094
- * @param options options for creating the view
6095
- * @returns NgViewRef
7844
+ * Read-only access to the underlying {@link HmrCacheStore}. Exposed
7845
+ * for advanced integrations that want to reuse the LRU + dispose +
7846
+ * server-side-invalidate plumbing without going through Angular's
7847
+ * dependency injection (e.g. a non-component utility that's loaded
7848
+ * before the Angular platform has bootstrapped). Application code
7849
+ * should prefer {@link HmrCacheService}.
6096
7850
  */
6097
- function generateNativeScriptView(typeOrTemplate, options) {
6098
- let detachedLoaderRef = options.detachedLoaderRef;
6099
- const reusingDetachedLoader = !!detachedLoaderRef;
6100
- if (reusingDetachedLoader) {
6101
- options.viewContainerRef = detachedLoaderRef.instance.vc;
6102
- }
6103
- const injector = options.viewContainerRef?.injector || options.injector;
6104
- const resolver = options.resolver || injector.get(ComponentFactoryResolver);
6105
- if (!detachedLoaderRef && (options.viewContainerRef || typeOrTemplate instanceof TemplateRef)) {
6106
- detachedLoaderRef = generateDetachedLoader(resolver, injector, options.viewContainerRef);
6107
- }
6108
- let portal;
6109
- if (typeOrTemplate instanceof TemplateRef) {
6110
- portal = new TemplatePortal(typeOrTemplate, detachedLoaderRef.instance.vc);
6111
- }
6112
- else {
6113
- portal = new ComponentPortal(typeOrTemplate, detachedLoaderRef?.instance.vc);
6114
- }
6115
- const parentView = new ContentView();
6116
- const portalOutlet = new NativeScriptDomPortalOutlet(parentView, resolver, injector.get(ApplicationRef), injector);
6117
- const componentOrTemplateRef = portalOutlet.attach(portal);
6118
- componentOrTemplateRef.onDestroy(() => {
6119
- portalOutlet.dispose();
6120
- });
6121
- if (detachedLoaderRef && !reusingDetachedLoader) {
6122
- componentOrTemplateRef.onDestroy(() => {
6123
- detachedLoaderRef.destroy();
6124
- });
7851
+ function getHmrCacheStore() {
7852
+ return getOrCreateSharedStore();
7853
+ }
7854
+ let sharedStore = null;
7855
+ let pendingOptions = null;
7856
+ function getOrCreateSharedStore() {
7857
+ if (sharedStore !== null) {
7858
+ return sharedStore;
7859
+ }
7860
+ const options = {
7861
+ log: (msg) => {
7862
+ // Keep cache diagnostics on the same channel as other HMR
7863
+ // helpers so devs can grep one trace category and see the full
7864
+ // picture during a save cycle. NativeScriptDebug.bootstrapLog
7865
+ // is the conventional sink for HMR-adjacent lifecycle logs.
7866
+ try {
7867
+ NativeScriptDebug.bootstrapLog(msg);
7868
+ }
7869
+ catch {
7870
+ // Defensive: never crash the cache if the trace channel is
7871
+ // unavailable (e.g. tests that import this file in isolation).
7872
+ }
7873
+ },
7874
+ ...pendingOptions,
7875
+ };
7876
+ sharedStore = createDefaultHmrCacheStore(options);
7877
+ pendingOptions = null;
7878
+ return sharedStore;
7879
+ }
7880
+ function hasImportMetaHot() {
7881
+ try {
7882
+ const meta = typeof import.meta !== 'undefined'
7883
+ ? import.meta
7884
+ : undefined;
7885
+ return !!meta?.hot;
6125
7886
  }
6126
- const viewRef = new NgViewRef(componentOrTemplateRef);
6127
- viewRef.detachedLoaderRef = detachedLoaderRef;
6128
- if (!options.keepNativeViewAttached) {
6129
- viewRef.detachNativeLikeView();
7887
+ catch {
7888
+ return false;
6130
7889
  }
6131
- return viewRef;
6132
7890
  }
6133
7891
 
6134
7892
  class BaseValueAccessor {
@@ -7925,9 +9683,37 @@ function cloneRoutesForBootstrap(routes) {
7925
9683
  const CURRENT_ROUTE_KEY = '__NS_ANGULAR_HMR_CURRENT_ROUTE__';
7926
9684
  const PENDING_START_PATH_KEY = '__NS_ANGULAR_HMR_PENDING_START_PATH__';
7927
9685
  const CAPTURE_ROUTE_KEY = '__NS_CAPTURE_ANGULAR_HMR_ROUTE__';
9686
+ // Stack of normalized URLs that mirrors Angular Router's back-stack while the
9687
+ // app is running, and is snapshotted into `PENDING_HISTORY_KEY` when an HMR
9688
+ // reboot is about to fire. After the new module bootstraps, the router replay
9689
+ // hook walks the stack to rebuild the back-stack so users keep their back
9690
+ // navigation across HMR cycles.
9691
+ const HISTORY_KEY = '__NS_ANGULAR_HMR_ROUTE_HISTORY__';
9692
+ const PENDING_HISTORY_KEY = '__NS_ANGULAR_HMR_PENDING_HISTORY__';
9693
+ // Window flag set while the new bootstrap is mid-replay of a captured route
9694
+ // stack. User-app code can consult this to skip default navigations that
9695
+ // would otherwise stomp the route the framework is restoring (e.g. a
9696
+ // bottom-nav component that defaults to its first tab on init when no
9697
+ // signal-backed selection exists).
9698
+ const RESTORING_KEY = '__NS_ANGULAR_HMR_RESTORING_ROUTE__';
9699
+ const RESTORING_TARGET_KEY = '__NS_ANGULAR_HMR_RESTORING_ROUTE_TARGET__';
7928
9700
  function getGlobalState() {
7929
9701
  return globalThis;
7930
9702
  }
9703
+ function readHistoryArray(key) {
9704
+ const g = getGlobalState();
9705
+ const raw = g[key];
9706
+ return Array.isArray(raw) ? raw.filter((entry) => typeof entry === 'string') : [];
9707
+ }
9708
+ function writeHistoryArray(key, history) {
9709
+ const g = getGlobalState();
9710
+ if (history.length > 0) {
9711
+ g[key] = history.slice();
9712
+ }
9713
+ else {
9714
+ delete g[key];
9715
+ }
9716
+ }
7931
9717
  function normalizeAngularHmrRouteUrl(value) {
7932
9718
  if (typeof value !== 'string') {
7933
9719
  return null;
@@ -7965,8 +9751,31 @@ function captureAngularHmrPendingStartPath(value, source = 'hmr-reboot') {
7965
9751
  return writeAngularHmrRouteState(value, { pending: true, source });
7966
9752
  }
7967
9753
  function readAngularHmrPendingStartPath() {
9754
+ // When a back-stack snapshot exists we boot to the bottom of the stack and
9755
+ // let `replayAngularHmrPendingForwardNavigations` walk the rest. Otherwise
9756
+ // fall back to the legacy single-URL slot so projects without history
9757
+ // tracking still land on the page they were viewing.
9758
+ const pendingHistory = readHistoryArray(PENDING_HISTORY_KEY);
9759
+ if (pendingHistory.length > 0) {
9760
+ // Open the restoring-route window so user-app default navigations
9761
+ // can step out of the framework's way until replay completes. The
9762
+ // forward-navigation walk in `NativeScriptAngularHmrRouteReplay`
9763
+ // closes the window after the final URL lands or fails. We pass
9764
+ // the deepest captured URL so consumers can compare against the
9765
+ // active router URL if they want fine-grained suppression.
9766
+ beginAngularHmrRouteRestore(pendingHistory[pendingHistory.length - 1]);
9767
+ return pendingHistory[0];
9768
+ }
7968
9769
  const g = getGlobalState();
7969
- return normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9770
+ const fallback = normalizeAngularHmrRouteUrl(g[PENDING_START_PATH_KEY]?.url ?? g[PENDING_START_PATH_KEY]) || '';
9771
+ if (fallback) {
9772
+ // Single-URL fallback path: user-app code should still suppress
9773
+ // default navigations briefly — the new bootstrap is about to
9774
+ // navigate to `fallback`, so a default tab init that fires first
9775
+ // would still stomp it.
9776
+ beginAngularHmrRouteRestore(fallback);
9777
+ }
9778
+ return fallback;
7970
9779
  }
7971
9780
  function invokeAngularHmrRouteCapture() {
7972
9781
  const g = getGlobalState();
@@ -7990,20 +9799,402 @@ function installAngularHmrRouteCaptureHook(capture) {
7990
9799
  }
7991
9800
  };
7992
9801
  }
9802
+ // ---- back-stack history primitives ------------------------------------------
9803
+ /**
9804
+ * Push a URL onto the live back-stack mirror. The mirror is collapsed when the
9805
+ * incoming URL equals the top — Angular fires multiple `NavigationEnd` events
9806
+ * for the same URL during certain `replaceUrl` scenarios and we don't want to
9807
+ * inflate the stack.
9808
+ */
9809
+ function pushAngularHmrRouteHistoryEntry(value) {
9810
+ const url = normalizeAngularHmrRouteUrl(value);
9811
+ if (!url) {
9812
+ return null;
9813
+ }
9814
+ const history = readHistoryArray(HISTORY_KEY);
9815
+ if (history.length > 0 && history[history.length - 1] === url) {
9816
+ return url;
9817
+ }
9818
+ history.push(url);
9819
+ writeHistoryArray(HISTORY_KEY, history);
9820
+ return url;
9821
+ }
9822
+ /**
9823
+ * Pop the top of the live back-stack mirror. Used when Angular reports a
9824
+ * `popstate`-triggered navigation so the mirror tracks back navigations.
9825
+ */
9826
+ function popAngularHmrRouteHistoryEntry() {
9827
+ const history = readHistoryArray(HISTORY_KEY);
9828
+ if (history.length === 0) {
9829
+ return null;
9830
+ }
9831
+ const popped = history.pop() ?? null;
9832
+ writeHistoryArray(HISTORY_KEY, history);
9833
+ return popped;
9834
+ }
9835
+ /**
9836
+ * Replace the top of the live back-stack mirror. Used when Angular reports a
9837
+ * `NavigationEnd` with `replaceUrl=true`, e.g. canonical-redirect cycles.
9838
+ */
9839
+ function replaceAngularHmrRouteHistoryTop(value) {
9840
+ const url = normalizeAngularHmrRouteUrl(value);
9841
+ if (!url) {
9842
+ return null;
9843
+ }
9844
+ const history = readHistoryArray(HISTORY_KEY);
9845
+ if (history.length === 0) {
9846
+ history.push(url);
9847
+ }
9848
+ else {
9849
+ history[history.length - 1] = url;
9850
+ }
9851
+ writeHistoryArray(HISTORY_KEY, history);
9852
+ return url;
9853
+ }
9854
+ /**
9855
+ * Read a defensive copy of the live back-stack mirror.
9856
+ */
9857
+ function readAngularHmrRouteHistory() {
9858
+ return readHistoryArray(HISTORY_KEY);
9859
+ }
9860
+ /**
9861
+ * Reset the live back-stack mirror. Used by tests and on bootstrap when the
9862
+ * router cannot replay the captured stack so we don't carry stale entries
9863
+ * forward.
9864
+ */
9865
+ function clearAngularHmrRouteHistory() {
9866
+ const g = getGlobalState();
9867
+ delete g[HISTORY_KEY];
9868
+ }
9869
+ /**
9870
+ * Snapshot the live back-stack mirror under the pending-history slot so the
9871
+ * next bootstrap can read it. Called from the HMR capture hook.
9872
+ *
9873
+ * The live mirror is cleared after the copy so the freshly bootstrapped app
9874
+ * starts from an empty back-stack. The replay walks the captured snapshot
9875
+ * via `NativeScriptAngularHmrRouteReplay` which fires `NavigationEnd` for
9876
+ * every URL it touches; the new tracker subscribes to those events and
9877
+ * naturally rebuilds the live mirror to match the snapshot. Without this
9878
+ * reset the live mirror would accumulate every URL the replay re-pushes
9879
+ * across HMR cycles, growing without bound and turning subsequent snapshots
9880
+ * into runaway forward-navigation walks (each replayed forward nav from
9881
+ * `/profile` back into `/talk` creates a fresh `TalkComponent` because
9882
+ * forward navigation never reuses the cache, so the leak shows up as
9883
+ * duplicated `Norrix is not enabled` / `BottomNavComponent Router Event:`
9884
+ * lines that double on every save).
9885
+ *
9886
+ * Returns the snapshot for diagnostics. Defensive: an empty live mirror
9887
+ * leaves the pending slot untouched so a single-page snapshot still works.
9888
+ */
9889
+ function snapshotAngularHmrRouteHistory() {
9890
+ const live = readHistoryArray(HISTORY_KEY);
9891
+ if (live.length === 0) {
9892
+ return [];
9893
+ }
9894
+ writeHistoryArray(PENDING_HISTORY_KEY, live);
9895
+ // Clear the live mirror so the next bootstrap starts from a clean slate.
9896
+ // The replay will repopulate it via the new tracker's NavigationEnd
9897
+ // subscription as it walks the captured stack.
9898
+ writeHistoryArray(HISTORY_KEY, []);
9899
+ return live.slice();
9900
+ }
9901
+ /**
9902
+ * Read the snapshotted back-stack pending replay on the new bootstrap.
9903
+ */
9904
+ function readAngularHmrPendingRouteHistory() {
9905
+ return readHistoryArray(PENDING_HISTORY_KEY);
9906
+ }
9907
+ /**
9908
+ * Read URLs to navigate forward through after the initial navigation finishes.
9909
+ * The first entry of the stack is the `START_PATH` consumed by the router; the
9910
+ * rest are forward navigations to push onto the new back-stack.
9911
+ */
9912
+ function readAngularHmrPendingForwardNavigations() {
9913
+ const pending = readHistoryArray(PENDING_HISTORY_KEY);
9914
+ if (pending.length <= 1) {
9915
+ return [];
9916
+ }
9917
+ return pending.slice(1);
9918
+ }
9919
+ /**
9920
+ * Clear the pending snapshot. The router replay calls this once it finishes
9921
+ * walking the stack so subsequent reboots start fresh.
9922
+ */
9923
+ function clearAngularHmrPendingRouteHistory() {
9924
+ const g = getGlobalState();
9925
+ delete g[PENDING_HISTORY_KEY];
9926
+ }
9927
+ // ---- restoring-route window flag --------------------------------------------
9928
+ /**
9929
+ * True while the Angular HMR layer is restoring a captured route stack
9930
+ * onto the freshly-bootstrapped router. The window opens just before
9931
+ * `START_PATH` resolves to a deep URL and closes once the router has
9932
+ * walked the entire forward navigation list (or aborted it).
9933
+ *
9934
+ * User-app code that runs default navigations on component init (e.g. a
9935
+ * bottom-nav defaulting to its first tab) can consult this flag to skip
9936
+ * its default navigation so the framework's restored route survives:
9937
+ *
9938
+ * ```ts
9939
+ * if (isAngularHmrRestoringRoute()) {
9940
+ * return; // framework is restoring a deeper route — leave it alone.
9941
+ * }
9942
+ * defaultTabNavigation();
9943
+ * ```
9944
+ *
9945
+ * Returns `false` outside of HMR or after the replay window has closed.
9946
+ * Production builds always see `false` because the framework never
9947
+ * opens the window there.
9948
+ */
9949
+ function isAngularHmrRestoringRoute() {
9950
+ const g = getGlobalState();
9951
+ return g[RESTORING_KEY] === true;
9952
+ }
9953
+ /**
9954
+ * The target route the framework is currently restoring, or `null` when
9955
+ * no replay is in progress. Useful when the consumer wants to compare
9956
+ * against the current router URL.
9957
+ */
9958
+ function getAngularHmrRestoringRoute() {
9959
+ const g = getGlobalState();
9960
+ const value = g[RESTORING_TARGET_KEY];
9961
+ return typeof value === 'string' && value ? value : null;
9962
+ }
9963
+ /**
9964
+ * Open the restoring-route window. Called by the framework when an HMR
9965
+ * bootstrap is about to navigate to a captured deep route — never call
9966
+ * this from user code.
9967
+ *
9968
+ * `targetUrl` is what the framework intends to land on; the value can
9969
+ * be read back via {@link getAngularHmrRestoringRoute}.
9970
+ */
9971
+ function beginAngularHmrRouteRestore(targetUrl) {
9972
+ const g = getGlobalState();
9973
+ g[RESTORING_KEY] = true;
9974
+ if (targetUrl) {
9975
+ g[RESTORING_TARGET_KEY] = targetUrl;
9976
+ }
9977
+ else {
9978
+ delete g[RESTORING_TARGET_KEY];
9979
+ }
9980
+ }
9981
+ /**
9982
+ * Close the restoring-route window. Called by the framework when the
9983
+ * replay finishes (NavigationEnd reached, replay aborted, or no
9984
+ * pending stack existed in the first place).
9985
+ */
9986
+ function endAngularHmrRouteRestore() {
9987
+ const g = getGlobalState();
9988
+ delete g[RESTORING_KEY];
9989
+ delete g[RESTORING_TARGET_KEY];
9990
+ }
9991
+
9992
+ /**
9993
+ * Grace period to keep `isAngularHmrRestoringRoute()` returning `true`
9994
+ * after `replayForwardNavigations()` finishes its last `navigateByUrl`.
9995
+ *
9996
+ * Why a grace period exists: NativeScript native views (TabView, BottomNavigation,
9997
+ * Frame, etc.) fire their `loaded` events asynchronously after the JS-side
9998
+ * `NavigationEnd`. User-app code wired to those events typically guards a
9999
+ * default navigation (e.g. "select first tab") with `isAngularHmrRestoringRoute()`.
10000
+ * If we close the window the instant the JS replay finishes, the loaded
10001
+ * event arrives a few hundred milliseconds later, the guard reports false,
10002
+ * and the default navigation stomps the freshly-restored route.
10003
+ *
10004
+ * 1000ms covers all the cases observed on iOS device + simulator without
10005
+ * leaving the window open long enough to interfere with genuine user
10006
+ * navigation. The fallback timeout (`fallback-timeout`) below is a safety
10007
+ * net for scenarios where this scheduled close never fires.
10008
+ */
10009
+ const REPLAY_COMPLETED_GRACE_MS = 1000;
10010
+ /**
10011
+ * Replays the back-stack snapshot captured by `NativeScriptAngularHmrRouteTracker`
10012
+ * during HMR. The router's initial navigation already lands on the bottom of
10013
+ * the stack (`stack[0]`); this service walks `stack[1..n]` so the user keeps
10014
+ * back navigation across HMR cycles.
10015
+ *
10016
+ * The replay is single-shot per bootstrap. Any failure (cancelled navigation,
10017
+ * unrouteable URL) aborts the rest of the replay so we don't fight the router
10018
+ * — the user keeps whichever subset of the stack we successfully re-pushed.
10019
+ */
10020
+ class NativeScriptAngularHmrRouteReplay {
10021
+ constructor(router) {
10022
+ this.router = router;
10023
+ if (!isAngularHmrEnabled()) {
10024
+ return;
10025
+ }
10026
+ const forwardNavigations = readAngularHmrPendingForwardNavigations();
10027
+ // The restoring window is opened by `readAngularHmrPendingStartPath()`
10028
+ // when `START_PATH` resolves to a deep route. If that path resolved
10029
+ // to nothing AND we have no forward navigations, there is nothing
10030
+ // to suppress and we must close the window if it was somehow left
10031
+ // open. Otherwise we keep it open until replay finishes.
10032
+ const restoringWindowOpen = isAngularHmrRestoringRoute();
10033
+ if (forwardNavigations.length === 0) {
10034
+ // Nothing to replay; clear the pending slot so a future navigation that
10035
+ // ends in the bootstrap window doesn't carry the snapshot forward.
10036
+ clearAngularHmrPendingRouteHistory();
10037
+ if (restoringWindowOpen) {
10038
+ // Single-URL restore (no back-stack to walk): keep the window
10039
+ // open until the initial navigation completes so user-app
10040
+ // default navigations don't fire before the framework's
10041
+ // restored URL settles. We then schedule the close with the
10042
+ // same grace period as the multi-URL replay path so async
10043
+ // native `loaded` handlers still see the flag.
10044
+ this.subscription = this.router.events
10045
+ .pipe(filter((event) => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError), take(1))
10046
+ .subscribe(() => this.scheduleRestoringWindowClose('initial-navigation-settled'));
10047
+ // Belt-and-braces: bootstrap can race with router init in
10048
+ // unusual cases. Close the window after a short timeout so we
10049
+ // never leave it stuck open and silently breaking default
10050
+ // navigations forever.
10051
+ this.windowFallbackTimeout = setTimeout(() => this.closeRestoringWindow('fallback-timeout'), 5000);
10052
+ }
10053
+ return;
10054
+ }
10055
+ if (NativeScriptDebug.isLogEnabled()) {
10056
+ NativeScriptDebug.hmrLog(`HMR back-stack replay queued: ${forwardNavigations.length} forward navigation(s)`);
10057
+ }
10058
+ this.subscription = this.router.events
10059
+ .pipe(filter((event) => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError), take(1))
10060
+ .subscribe((event) => {
10061
+ if (event instanceof NavigationEnd) {
10062
+ void this.replayForwardNavigations(forwardNavigations);
10063
+ }
10064
+ else {
10065
+ // Initial navigation never landed; replay would compound the problem.
10066
+ clearAngularHmrPendingRouteHistory();
10067
+ this.closeRestoringWindow('initial-navigation-failed');
10068
+ if (NativeScriptDebug.isLogEnabled()) {
10069
+ NativeScriptDebug.hmrLog('HMR back-stack replay skipped: initial navigation did not complete');
10070
+ }
10071
+ }
10072
+ });
10073
+ // Same belt-and-braces fallback as the single-URL path above.
10074
+ this.windowFallbackTimeout = setTimeout(() => this.closeRestoringWindow('fallback-timeout'), 10000);
10075
+ }
10076
+ ngOnDestroy() {
10077
+ this.subscription?.unsubscribe();
10078
+ if (this.windowFallbackTimeout !== undefined) {
10079
+ clearTimeout(this.windowFallbackTimeout);
10080
+ this.windowFallbackTimeout = undefined;
10081
+ }
10082
+ if (this.pendingCloseTimeout !== undefined) {
10083
+ clearTimeout(this.pendingCloseTimeout);
10084
+ this.pendingCloseTimeout = undefined;
10085
+ }
10086
+ // Defensive: never leave the restoring window open across module
10087
+ // destruction. A subsequent reboot would otherwise see it set and
10088
+ // suppress the next default navigation indefinitely.
10089
+ this.closeRestoringWindow('replay-service-destroyed');
10090
+ }
10091
+ closeRestoringWindow(reason) {
10092
+ if (this.pendingCloseTimeout !== undefined) {
10093
+ clearTimeout(this.pendingCloseTimeout);
10094
+ this.pendingCloseTimeout = undefined;
10095
+ }
10096
+ if (!isAngularHmrRestoringRoute()) {
10097
+ return;
10098
+ }
10099
+ endAngularHmrRouteRestore();
10100
+ if (this.windowFallbackTimeout !== undefined) {
10101
+ clearTimeout(this.windowFallbackTimeout);
10102
+ this.windowFallbackTimeout = undefined;
10103
+ }
10104
+ if (NativeScriptDebug.isLogEnabled()) {
10105
+ NativeScriptDebug.hmrLog(`HMR restoring-route window closed (${reason})`);
10106
+ }
10107
+ }
10108
+ /**
10109
+ * Schedule the restoring window to close after a small grace period
10110
+ * so that asynchronous user-app handlers (e.g. NativeScript native
10111
+ * `loaded` events on TabView / BottomNavigation / Frame) still observe
10112
+ * `isAngularHmrRestoringRoute() === true` and skip default navigations
10113
+ * that would otherwise stomp the freshly-restored route.
10114
+ *
10115
+ * The grace period is bounded by the existing `fallback-timeout` so
10116
+ * we never leave the flag set indefinitely even if `setTimeout` is
10117
+ * blocked by a misbehaving consumer.
10118
+ */
10119
+ scheduleRestoringWindowClose(reason) {
10120
+ if (!isAngularHmrRestoringRoute()) {
10121
+ return;
10122
+ }
10123
+ if (this.pendingCloseTimeout !== undefined) {
10124
+ clearTimeout(this.pendingCloseTimeout);
10125
+ }
10126
+ this.pendingCloseTimeout = setTimeout(() => {
10127
+ this.pendingCloseTimeout = undefined;
10128
+ this.closeRestoringWindow(reason);
10129
+ }, REPLAY_COMPLETED_GRACE_MS);
10130
+ }
10131
+ async replayForwardNavigations(urls) {
10132
+ let aborted = false;
10133
+ try {
10134
+ for (const url of urls) {
10135
+ const succeeded = await this.router.navigateByUrl(url).catch(() => false);
10136
+ if (!succeeded) {
10137
+ aborted = true;
10138
+ if (NativeScriptDebug.isLogEnabled()) {
10139
+ NativeScriptDebug.hmrLog(`HMR back-stack replay aborted at ${url}`);
10140
+ }
10141
+ return;
10142
+ }
10143
+ if (NativeScriptDebug.isLogEnabled()) {
10144
+ NativeScriptDebug.hmrLog(`HMR back-stack replay navigated to ${url}`);
10145
+ }
10146
+ }
10147
+ }
10148
+ finally {
10149
+ clearAngularHmrPendingRouteHistory();
10150
+ this.scheduleRestoringWindowClose(aborted ? 'replay-aborted' : 'replay-completed');
10151
+ }
10152
+ }
10153
+ 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 }); }
10154
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteReplay }); }
10155
+ }
10156
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteReplay, decorators: [{
10157
+ type: Injectable
10158
+ }], ctorParameters: () => [{ type: i1$3.Router }] });
7993
10159
 
7994
10160
  class NativeScriptAngularHmrRouteTracker {
7995
10161
  constructor(router) {
7996
10162
  this.router = router;
7997
- if (!this.isHmrEnabled()) {
10163
+ // Tracks whether the current `NavigationStart..NavigationEnd` pair was kicked
10164
+ // off by a popstate (frame.goBack / NSLocationStrategy.back) so that on
10165
+ // `NavigationEnd` we can pop our mirror instead of pushing a duplicate entry.
10166
+ this.currentNavigationIsPopstate = false;
10167
+ this.currentNavigationReplaceUrl = false;
10168
+ if (!isAngularHmrEnabled()) {
7998
10169
  return;
7999
10170
  }
8000
10171
  this.disposeCaptureHook = this.installCaptureHook();
8001
10172
  this.captureCurrentRoute('bootstrap');
8002
10173
  this.subscription = this.router.events.subscribe((event) => {
10174
+ if (event instanceof NavigationStart) {
10175
+ this.currentNavigationIsPopstate = event.navigationTrigger === 'popstate';
10176
+ this.currentNavigationReplaceUrl = !!event.restoredState;
10177
+ return;
10178
+ }
8003
10179
  if (event instanceof NavigationEnd) {
8004
- writeAngularHmrRouteState(event.urlAfterRedirects || event.url, {
10180
+ const url = event.urlAfterRedirects || event.url;
10181
+ writeAngularHmrRouteState(url, {
8005
10182
  source: 'navigation-end',
8006
10183
  });
10184
+ if (this.currentNavigationIsPopstate) {
10185
+ // The user (or NSLocationStrategy.back()) walked the back-stack down
10186
+ // by one page; mirror that by dropping the top of our snapshot so a
10187
+ // subsequent HMR reboot doesn't carry the popped page back into view.
10188
+ popAngularHmrRouteHistoryEntry();
10189
+ }
10190
+ else if (this.currentNavigationReplaceUrl) {
10191
+ replaceAngularHmrRouteHistoryTop(url);
10192
+ }
10193
+ else {
10194
+ pushAngularHmrRouteHistoryEntry(url);
10195
+ }
10196
+ this.currentNavigationIsPopstate = false;
10197
+ this.currentNavigationReplaceUrl = false;
8007
10198
  }
8008
10199
  });
8009
10200
  }
@@ -8012,6 +10203,32 @@ class NativeScriptAngularHmrRouteTracker {
8012
10203
  this.disposeCaptureHook?.();
8013
10204
  }
8014
10205
  captureCurrentRoute(source) {
10206
+ if (source === 'hmr-reboot') {
10207
+ // Snapshot the live mirror first so the bootstrap can replay forward
10208
+ // navigations to rebuild the back-stack. The pending single-URL slot
10209
+ // remains useful as a fallback when the snapshot turns out to be empty
10210
+ // (e.g. bootstrap-time HMR before the first NavigationEnd).
10211
+ snapshotAngularHmrRouteHistory();
10212
+ }
10213
+ else if (source === 'bootstrap') {
10214
+ // Seed the live mirror with the current URL so the very first HMR
10215
+ // before any user navigation still has a stack of size one to snapshot.
10216
+ //
10217
+ // Skip empty / root URLs: at ENVIRONMENT_INITIALIZER time the router
10218
+ // has not run its initial navigation yet so `router.url` is "/" (or
10219
+ // an empty string). Pushing that here would seed the mirror with a
10220
+ // noise entry that becomes the bottom of the next snapshot, which in
10221
+ // turn becomes the next bootstrap's `START_PATH`. The router then
10222
+ // boots to "/" → redirects to the real default route → fires an
10223
+ // extra `NavigationEnd` that re-enters the replay path. The first
10224
+ // genuine `NavigationEnd` arrives a moment later through the event
10225
+ // subscription below and seeds the mirror with the real URL, so
10226
+ // dropping the seed here is safe.
10227
+ const seedUrl = this.router.url;
10228
+ if (seedUrl && seedUrl !== '/') {
10229
+ pushAngularHmrRouteHistoryEntry(seedUrl);
10230
+ }
10231
+ }
8015
10232
  return writeAngularHmrRouteState(this.router.url, {
8016
10233
  pending: source === 'hmr-reboot',
8017
10234
  source,
@@ -8020,10 +10237,6 @@ class NativeScriptAngularHmrRouteTracker {
8020
10237
  installCaptureHook() {
8021
10238
  return installAngularHmrRouteCaptureHook(() => this.captureCurrentRoute('hmr-reboot'));
8022
10239
  }
8023
- isHmrEnabled() {
8024
- const g = globalThis;
8025
- return !!g.__NS_DEV_PLACEHOLDER_ROOT_EARLY__ || typeof g.__reboot_ng_modules__ === 'function';
8026
- }
8027
10240
  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 }); }
8028
10241
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: NativeScriptAngularHmrRouteTracker }); }
8029
10242
  }
@@ -8057,10 +10270,11 @@ class NativeScriptRouterModule {
8057
10270
  NSRouteReuseStrategy,
8058
10271
  { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
8059
10272
  NativeScriptAngularHmrRouteTracker,
10273
+ NativeScriptAngularHmrRouteReplay,
8060
10274
  {
8061
10275
  provide: APP_BOOTSTRAP_LISTENER,
8062
10276
  multi: true,
8063
- deps: [NativeScriptAngularHmrRouteTracker],
10277
+ deps: [NativeScriptAngularHmrRouteTracker, NativeScriptAngularHmrRouteReplay],
8064
10278
  useFactory: () => () => undefined,
8065
10279
  },
8066
10280
  ],
@@ -8103,11 +10317,13 @@ function provideNativeScriptRouter(routes, ...features) {
8103
10317
  NSRouteReuseStrategy,
8104
10318
  { provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
8105
10319
  NativeScriptAngularHmrRouteTracker,
10320
+ NativeScriptAngularHmrRouteReplay,
8106
10321
  {
8107
10322
  provide: ENVIRONMENT_INITIALIZER,
8108
10323
  multi: true,
8109
10324
  useValue: () => {
8110
10325
  inject(NativeScriptAngularHmrRouteTracker);
10326
+ inject(NativeScriptAngularHmrRouteReplay);
8111
10327
  },
8112
10328
  },
8113
10329
  // {provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: getBootstrapListener},
@@ -8501,5 +10717,5 @@ function provideNativeScriptNgZone(options) {
8501
10717
  * Generated bundle index. Do not edit.
8502
10718
  */
8503
10719
 
8504
- 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 };
10720
+ 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, HmrCacheService, HmrCacheStore, 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, configureHmrCache, createDefaultHmrCacheStore, createKeyframeAnimation, customFrameComponentFactory, customFrameDirectiveFactory, customPageFactory, customPageFactoryFromFrame, dashCaseToCamelCase, defaultNavOptions, defaultPageFactory, defaultPageFactoryProvider, detachViewFromParent, disableRootViewHanding, errorHandler, extractSingleViewRecursive, frameMeta, generateDetachedLoader, generateFallbackRootView, generateNativeScriptView, generateRandomId, generateRootLayoutAndProxy, getAngularHmrRestoringRoute, getFirstNativeLikeView, getHmrCacheStore, 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 };
8505
10721
  //# sourceMappingURL=nativescript-angular.mjs.map