@nativescript/angular 21.0.1-alpha.6 → 21.0.1-alpha.8
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.
|
@@ -22,6 +22,7 @@ class InvisibleNode extends View {
|
|
|
22
22
|
this.name = name;
|
|
23
23
|
this.nodeType = 1;
|
|
24
24
|
this.nodeName = getClassName(this);
|
|
25
|
+
this.tagName = this.nodeName;
|
|
25
26
|
}
|
|
26
27
|
toString() {
|
|
27
28
|
return `${this.nodeName}(${this.id})-${this.name}`;
|
|
@@ -3144,6 +3145,12 @@ class ViewUtil {
|
|
|
3144
3145
|
}
|
|
3145
3146
|
const ngView = view;
|
|
3146
3147
|
ngView.nodeName = name;
|
|
3148
|
+
// Angular 21+ reads `rootElement.tagName.toLowerCase()` during component bootstrap
|
|
3149
|
+
// (`locateHostElement`) to reject `<script>` host elements. Native Views have no
|
|
3150
|
+
// intrinsic `tagName`, so without this assignment the boot throws
|
|
3151
|
+
// `Cannot read properties of undefined (reading 'toLowerCase')`. Mirror DOM
|
|
3152
|
+
// conventions where `tagName` equals `nodeName` for element nodes.
|
|
3153
|
+
ngView.tagName = name;
|
|
3147
3154
|
ngView.meta = getViewMeta(name);
|
|
3148
3155
|
// we're setting the node type of the view
|
|
3149
3156
|
// to 'element' because of checks done in the
|
|
@@ -3545,22 +3552,35 @@ class NativeScriptRenderer {
|
|
|
3545
3552
|
if (NativeScriptDebug.enabled) {
|
|
3546
3553
|
NativeScriptDebug.rendererLog(`NativeScriptRenderer.selectRootElement: ${selectorOrNode}`);
|
|
3547
3554
|
}
|
|
3555
|
+
// Angular 21+ reads `rootElement.tagName.toLowerCase()` after this call
|
|
3556
|
+
// (`locateHostElement`) to reject `<script>` hosts. Guarantee every return
|
|
3557
|
+
// path produces a View with a non-empty string `tagName`; otherwise the
|
|
3558
|
+
// bootstrap throws `Cannot read properties of undefined (reading 'toLowerCase')`.
|
|
3559
|
+
const ensureTagName = (view, fallback) => {
|
|
3560
|
+
if (view && typeof view.tagName !== 'string') {
|
|
3561
|
+
try {
|
|
3562
|
+
view.tagName = view.nodeName || fallback || 'view';
|
|
3563
|
+
}
|
|
3564
|
+
catch { }
|
|
3565
|
+
}
|
|
3566
|
+
return view;
|
|
3567
|
+
};
|
|
3548
3568
|
if (selectorOrNode instanceof View) {
|
|
3549
|
-
return selectorOrNode;
|
|
3569
|
+
return ensureTagName(selectorOrNode, '');
|
|
3550
3570
|
}
|
|
3551
3571
|
if (selectorOrNode && selectorOrNode[0] === '#') {
|
|
3552
3572
|
const result = getViewById(this.rootView, selectorOrNode.slice(1));
|
|
3553
|
-
return (result || this.rootView);
|
|
3573
|
+
return ensureTagName((result || this.rootView), selectorOrNode);
|
|
3554
3574
|
}
|
|
3555
3575
|
if (typeof selectorOrNode === 'string') {
|
|
3556
3576
|
const view = this.viewUtil.createView(selectorOrNode);
|
|
3557
3577
|
if (getFirstNativeLikeView(view) === view) {
|
|
3558
3578
|
// view is nativelike!
|
|
3559
3579
|
this.appendChild(this.rootView, view);
|
|
3560
|
-
return view;
|
|
3580
|
+
return ensureTagName(view, selectorOrNode);
|
|
3561
3581
|
}
|
|
3562
3582
|
}
|
|
3563
|
-
return this.rootView;
|
|
3583
|
+
return ensureTagName(this.rootView, '');
|
|
3564
3584
|
}
|
|
3565
3585
|
parentNode(node) {
|
|
3566
3586
|
if (NativeScriptDebug.enabled) {
|
|
@@ -6399,6 +6419,120 @@ function buildNonAnimatedRestoreConfig(original) {
|
|
|
6399
6419
|
return cloned;
|
|
6400
6420
|
}
|
|
6401
6421
|
|
|
6422
|
+
/**
|
|
6423
|
+
* Pure helpers that mirror NativeScript's modal-host properties
|
|
6424
|
+
* (`_dialogFragment` on Android, `viewController` on iOS) from the
|
|
6425
|
+
* ContentView wrapper that `attachComponentPortal` presents as the
|
|
6426
|
+
* modal down onto every native-like descendant.
|
|
6427
|
+
*
|
|
6428
|
+
* Why? `parentView.showModal(targetView, ...)` stamps those props on
|
|
6429
|
+
* `targetView` itself — but user template code reads them off the
|
|
6430
|
+
* rendered template root that lives *inside* the wrapper (e.g.
|
|
6431
|
+
* `onLoaded($event)` → `args.object._dialogFragment.getDialog()`).
|
|
6432
|
+
* Without mirroring, every fresh modal open + every HMR
|
|
6433
|
+
* `ɵɵreplaceMetadata` re-render hands the user `undefined`, which is
|
|
6434
|
+
* how we hit:
|
|
6435
|
+
*
|
|
6436
|
+
* Cannot read properties of undefined (reading 'getDialog')
|
|
6437
|
+
* at CheckinComponent.onLoaded
|
|
6438
|
+
*
|
|
6439
|
+
* The helpers live in a standalone module on purpose, mirroring
|
|
6440
|
+
* `dialog-hmr-animation.ts`: they don't pull `@angular/core` or
|
|
6441
|
+
* `@nativescript/core`, so they're trivially unit-testable in the
|
|
6442
|
+
* Jest Node runner without an ESM transform or a NativeScript
|
|
6443
|
+
* runtime stub.
|
|
6444
|
+
*/
|
|
6445
|
+
/**
|
|
6446
|
+
* Marker property used to make {@link installPvcModalHostPropPropagation}
|
|
6447
|
+
* idempotent. Exported only so the spec can assert install
|
|
6448
|
+
* idempotency without re-declaring the constant.
|
|
6449
|
+
*/
|
|
6450
|
+
const PVC_ADD_VIEW_WRAPPED_MARKER = '__ng_modal_propagate_addview__';
|
|
6451
|
+
/**
|
|
6452
|
+
* Mirror `wrapper`'s modal-host props onto every descendant of
|
|
6453
|
+
* `root` via NativeScript's logical `eachChildView` walk.
|
|
6454
|
+
*
|
|
6455
|
+
* - The wrapper itself is **never** written to. NS core owns that
|
|
6456
|
+
* assignment and we must not shadow the canonical reference.
|
|
6457
|
+
* - Writes are idempotent: a descendant that already holds the same
|
|
6458
|
+
* reference is skipped, so repeat calls (HMR re-renders, multiple
|
|
6459
|
+
* PVC adds in one render pass) stay cheap.
|
|
6460
|
+
* - No-op when the modal isn't shown yet, has already closed, or
|
|
6461
|
+
* when either input is missing. NS clears both props to null on
|
|
6462
|
+
* close, so propagating after that point would only persist stale
|
|
6463
|
+
* references on the descendants.
|
|
6464
|
+
*/
|
|
6465
|
+
function propagateModalHostPropsToDescendants(wrapper, root) {
|
|
6466
|
+
if (!wrapper || !root) {
|
|
6467
|
+
return;
|
|
6468
|
+
}
|
|
6469
|
+
const dialogFragment = wrapper._dialogFragment;
|
|
6470
|
+
const viewController = wrapper.viewController;
|
|
6471
|
+
if (dialogFragment == null && viewController == null) {
|
|
6472
|
+
return;
|
|
6473
|
+
}
|
|
6474
|
+
const visit = (view) => {
|
|
6475
|
+
if (!view) {
|
|
6476
|
+
return;
|
|
6477
|
+
}
|
|
6478
|
+
if (view !== wrapper) {
|
|
6479
|
+
if (dialogFragment !== undefined && view._dialogFragment !== dialogFragment) {
|
|
6480
|
+
view._dialogFragment = dialogFragment;
|
|
6481
|
+
}
|
|
6482
|
+
if (viewController !== undefined && view.viewController !== viewController) {
|
|
6483
|
+
view.viewController = viewController;
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
view.eachChildView?.((child) => {
|
|
6487
|
+
visit(child);
|
|
6488
|
+
return true;
|
|
6489
|
+
});
|
|
6490
|
+
};
|
|
6491
|
+
visit(root);
|
|
6492
|
+
}
|
|
6493
|
+
/**
|
|
6494
|
+
* Idempotently wrap `host._addView` so every child added after
|
|
6495
|
+
* install — typically the new template root produced by Angular's
|
|
6496
|
+
* `ɵɵreplaceMetadata` HMR cycle — has `wrapper`'s modal-host props
|
|
6497
|
+
* mirrored onto it (and its current subtree) **before** NS attaches
|
|
6498
|
+
* the view.
|
|
6499
|
+
*
|
|
6500
|
+
* Why "before"? `_addView` on a loaded parent synchronously calls
|
|
6501
|
+
* `child.callLoaded()` deep inside, which fires the `loaded` event
|
|
6502
|
+
* chain on the new template root. User code (e.g.
|
|
6503
|
+
* `onLoaded($event)` → `args.object._dialogFragment.getDialog()`)
|
|
6504
|
+
* runs from inside that synchronous call. The props **must** be
|
|
6505
|
+
* present on the child by the time `_addView` runs — pre-hook
|
|
6506
|
+
* position is the only place that guarantees this without
|
|
6507
|
+
* monkey-patching NS internals.
|
|
6508
|
+
*
|
|
6509
|
+
* The wrap reads `wrapper._dialogFragment` / `wrapper.viewController`
|
|
6510
|
+
* lazily (per call) via {@link propagateModalHostPropsToDescendants},
|
|
6511
|
+
* so a wrap installed while the modal is open keeps doing the right
|
|
6512
|
+
* thing if HMR re-render happens later, and gracefully no-ops after
|
|
6513
|
+
* the modal closes (both props become null on the wrapper).
|
|
6514
|
+
*
|
|
6515
|
+
* No-op when `host` lacks `_addView` (e.g. a non-View element) or
|
|
6516
|
+
* when the wrap is already installed (`PVC_ADD_VIEW_WRAPPED_MARKER`
|
|
6517
|
+
* sentinel). The wrap is intentionally NOT removable — the host
|
|
6518
|
+
* lives only as long as the modal, so the wrap is GC'd with it.
|
|
6519
|
+
*/
|
|
6520
|
+
function installPvcModalHostPropPropagation(host, wrapper) {
|
|
6521
|
+
if (!host || !wrapper) {
|
|
6522
|
+
return;
|
|
6523
|
+
}
|
|
6524
|
+
const target = host;
|
|
6525
|
+
if (target[PVC_ADD_VIEW_WRAPPED_MARKER] || typeof target._addView !== 'function') {
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
const origAddView = target._addView.bind(target);
|
|
6529
|
+
target._addView = (view, atIndex) => {
|
|
6530
|
+
propagateModalHostPropsToDescendants(wrapper, view);
|
|
6531
|
+
return origAddView(view, atIndex);
|
|
6532
|
+
};
|
|
6533
|
+
target[PVC_ADD_VIEW_WRAPPED_MARKER] = true;
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6402
6536
|
let NativeModalRef = class NativeModalRef {
|
|
6403
6537
|
constructor(_config, _injector, location) {
|
|
6404
6538
|
this._config = _config;
|
|
@@ -6528,6 +6662,25 @@ let NativeModalRef = class NativeModalRef {
|
|
|
6528
6662
|
},
|
|
6529
6663
|
cancelable: !this._config.disableClose,
|
|
6530
6664
|
});
|
|
6665
|
+
// After `showModal`, NativeScript core has stamped
|
|
6666
|
+
// `_dialogFragment` (Android) / `viewController` (iOS) on
|
|
6667
|
+
// `targetView` itself — but user template code (`onLoaded($event)`
|
|
6668
|
+
// → `args.object._dialogFragment.getDialog()...`) reads those
|
|
6669
|
+
// props on the rendered template root, which is a *descendant*
|
|
6670
|
+
// of `targetView`, not `targetView` itself. Mirror them down so
|
|
6671
|
+
// the template root (and any nested loaded handler) sees the
|
|
6672
|
+
// real host objects instead of `undefined`, then install a
|
|
6673
|
+
// lazy wrap on the host PVC's `_addView` so future child
|
|
6674
|
+
// additions — typically the new template root produced by
|
|
6675
|
+
// Angular's `ɵɵreplaceMetadata` HMR cycle — get the same
|
|
6676
|
+
// props mirrored *before* NS attaches the view and fires
|
|
6677
|
+
// its `loaded` event chain. See `modal-host-props.ts` for the
|
|
6678
|
+
// full rationale.
|
|
6679
|
+
propagateModalHostPropsToDescendants(targetView, targetView);
|
|
6680
|
+
const hostView = componentRef.location?.nativeElement;
|
|
6681
|
+
if (hostView) {
|
|
6682
|
+
installPvcModalHostPropPropagation(hostView, targetView);
|
|
6683
|
+
}
|
|
6531
6684
|
return componentRef;
|
|
6532
6685
|
}
|
|
6533
6686
|
_startExitAnimation() {
|