@nativescript/vite 8.0.0-alpha.67 → 8.0.0-alpha.68
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.
- package/configuration/angular.js +78 -182
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +10 -139
- package/configuration/base.js.map +1 -1
- package/helpers/app-css-state.d.ts +20 -0
- package/helpers/app-css-state.js.map +1 -1
- package/helpers/main-entry.js +44 -4
- package/helpers/main-entry.js.map +1 -1
- package/helpers/ns-core-external-urls.d.ts +32 -0
- package/helpers/ns-core-external-urls.js +107 -0
- package/helpers/ns-core-external-urls.js.map +1 -0
- package/helpers/typescript-check.js +2 -2
- package/helpers/typescript-check.js.map +1 -1
- package/helpers/workers.d.ts +0 -8
- package/helpers/workers.js +43 -381
- package/helpers/workers.js.map +1 -1
- package/hmr/client/framework-client-strategy.d.ts +17 -9
- package/hmr/client/framework-client-strategy.js +3 -4
- package/hmr/client/framework-client-strategy.js.map +1 -1
- package/hmr/client/hmr-pending-overlay.js +2 -21
- package/hmr/client/hmr-pending-overlay.js.map +1 -1
- package/hmr/client/index.d.ts +9 -1
- package/hmr/client/index.js +34 -2621
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/overlay-driver.d.ts +10 -0
- package/hmr/client/overlay-driver.js +33 -0
- package/hmr/client/overlay-driver.js.map +1 -0
- package/hmr/client/protocol-dispatch.d.ts +8 -0
- package/hmr/client/protocol-dispatch.js +309 -0
- package/hmr/client/protocol-dispatch.js.map +1 -0
- package/hmr/client/root-reset.d.ts +15 -0
- package/hmr/client/root-reset.js +358 -0
- package/hmr/client/root-reset.js.map +1 -0
- package/hmr/client/strategy-loader.d.ts +24 -0
- package/hmr/client/strategy-loader.js +83 -0
- package/hmr/client/strategy-loader.js.map +1 -0
- package/hmr/client/update-pipeline.d.ts +27 -0
- package/hmr/client/update-pipeline.js +464 -0
- package/hmr/client/update-pipeline.js.map +1 -0
- package/hmr/client/utils.js +3 -2
- package/hmr/client/utils.js.map +1 -1
- package/hmr/client/ws-transport.d.ts +21 -0
- package/hmr/client/ws-transport.js +344 -0
- package/hmr/client/ws-transport.js.map +1 -0
- package/hmr/frameworks/angular/build/angular-linker.js +4 -0
- package/hmr/frameworks/angular/build/angular-linker.js.map +1 -1
- package/hmr/frameworks/angular/build/linker-stats.d.ts +18 -0
- package/hmr/frameworks/angular/build/linker-stats.js +29 -0
- package/hmr/frameworks/angular/build/linker-stats.js.map +1 -0
- package/hmr/frameworks/angular/build/ts-fallback-transform.d.ts +5 -0
- package/hmr/frameworks/angular/build/ts-fallback-transform.js +138 -0
- package/hmr/frameworks/angular/build/ts-fallback-transform.js.map +1 -0
- package/hmr/frameworks/angular/build/worker-url-preserve.d.ts +5 -0
- package/hmr/frameworks/angular/build/worker-url-preserve.js +140 -0
- package/hmr/frameworks/angular/build/worker-url-preserve.js.map +1 -0
- package/hmr/frameworks/angular/client/index.d.ts +3 -0
- package/hmr/frameworks/angular/client/index.js +28 -42
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/ng-component-route.d.ts +43 -0
- package/hmr/frameworks/angular/server/ng-component-route.js +113 -0
- package/hmr/frameworks/angular/server/ng-component-route.js.map +1 -0
- package/hmr/frameworks/angular/server/strategy.js +15 -11
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.d.ts +12 -0
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js +39 -0
- package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -1
- package/hmr/frameworks/react/client/strategy.d.ts +12 -0
- package/hmr/frameworks/react/client/strategy.js +27 -0
- package/hmr/frameworks/react/client/strategy.js.map +1 -0
- package/hmr/frameworks/solid/client/strategy.d.ts +7 -0
- package/hmr/frameworks/solid/client/strategy.js +391 -0
- package/hmr/frameworks/solid/client/strategy.js.map +1 -0
- package/hmr/frameworks/solid/server/strategy.js +2 -0
- package/hmr/frameworks/solid/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/client/strategy.d.ts +7 -0
- package/hmr/frameworks/typescript/client/strategy.js +418 -0
- package/hmr/frameworks/typescript/client/strategy.js.map +1 -0
- package/hmr/frameworks/typescript/server/strategy.js +2 -0
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/index.d.ts +1 -1
- package/hmr/frameworks/vue/client/index.js +40 -319
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/navigate-app.d.ts +3 -0
- package/hmr/frameworks/vue/client/navigate-app.js +178 -0
- package/hmr/frameworks/vue/client/navigate-app.js.map +1 -0
- package/hmr/frameworks/vue/client/strategy.d.ts +1 -1
- package/hmr/frameworks/vue/client/strategy.js +9 -14
- package/hmr/frameworks/vue/client/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +2 -20
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -1
- package/hmr/frameworks/vue/server/strategy.js +3 -0
- package/hmr/frameworks/vue/server/strategy.js.map +1 -1
- package/hmr/server/css-connect-sync.d.ts +28 -0
- package/hmr/server/css-connect-sync.js +78 -0
- package/hmr/server/css-connect-sync.js.map +1 -0
- package/hmr/server/framework-strategy.d.ts +30 -0
- package/hmr/server/websocket-hmr-pending.d.ts +0 -8
- package/hmr/server/websocket-hmr-pending.js +0 -22
- package/hmr/server/websocket-hmr-pending.js.map +1 -1
- package/hmr/server/websocket-hot-update.d.ts +3 -0
- package/hmr/server/websocket-hot-update.js +63 -36
- package/hmr/server/websocket-hot-update.js.map +1 -1
- package/hmr/server/websocket-module-bindings.js +23 -5
- package/hmr/server/websocket-module-bindings.js.map +1 -1
- package/hmr/server/websocket-module-specifiers.d.ts +0 -5
- package/hmr/server/websocket-module-specifiers.js +0 -28
- package/hmr/server/websocket-module-specifiers.js.map +1 -1
- package/hmr/server/websocket-ns-m-paths.d.ts +18 -0
- package/hmr/server/websocket-ns-m-paths.js +79 -0
- package/hmr/server/websocket-ns-m-paths.js.map +1 -1
- package/hmr/server/websocket-ns-m-request.d.ts +2 -1
- package/hmr/server/websocket-ns-m-request.js +3 -2
- package/hmr/server/websocket-ns-m-request.js.map +1 -1
- package/hmr/server/websocket-ns-m.js +100 -374
- package/hmr/server/websocket-ns-m.js.map +1 -1
- package/hmr/server/websocket-served-module-helpers.d.ts +7 -0
- package/hmr/server/websocket-served-module-helpers.js +93 -69
- package/hmr/server/websocket-served-module-helpers.js.map +1 -1
- package/hmr/server/websocket.d.ts +0 -1
- package/hmr/server/websocket.js +27 -2
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/ns-globals.d.ts +0 -1
- package/hmr/shared/ns-globals.js.map +1 -1
- package/hmr/shared/protocol.d.ts +14 -0
- package/hmr/shared/protocol.js.map +1 -1
- package/hmr/shared/runtime/dev-overlay-ios.d.ts +87 -0
- package/hmr/shared/runtime/dev-overlay-ios.js +521 -0
- package/hmr/shared/runtime/dev-overlay-ios.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay-shared.d.ts +58 -0
- package/hmr/shared/runtime/dev-overlay-shared.js +116 -0
- package/hmr/shared/runtime/dev-overlay-shared.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay-tree.d.ts +59 -0
- package/hmr/shared/runtime/dev-overlay-tree.js +712 -0
- package/hmr/shared/runtime/dev-overlay-tree.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +15 -107
- package/hmr/shared/runtime/dev-overlay.js +19 -1336
- package/hmr/shared/runtime/dev-overlay.js.map +1 -1
- package/hmr/shared/runtime/overlay-flag.d.ts +9 -0
- package/hmr/shared/runtime/overlay-flag.js +34 -0
- package/hmr/shared/runtime/overlay-flag.js.map +1 -0
- package/hmr/shared/runtime/root-placeholder.js +3 -2
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.js +2 -1
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -1
- package/hmr/shared/runtime/vendor-resolve.d.ts +22 -0
- package/hmr/shared/runtime/vendor-resolve.js +45 -0
- package/hmr/shared/runtime/vendor-resolve.js.map +1 -0
- package/hmr/shared/vendor/manifest-collect.js +0 -3
- package/hmr/shared/vendor/manifest-collect.js.map +1 -1
- package/package.json +1 -3
- package/runtime/core-aliases-early.js +3 -2
- package/runtime/core-aliases-early.js.map +1 -1
- package/helpers/module-runner-patch.d.ts +0 -3
- package/helpers/module-runner-patch.js +0 -63
- package/helpers/module-runner-patch.js.map +0 -1
- package/hmr/server/websocket-css-hot-update.d.ts +0 -33
- package/hmr/server/websocket-css-hot-update.js +0 -65
- package/hmr/server/websocket-css-hot-update.js.map +0 -1
- package/hmr/shared/runtime/http-only-boot.d.ts +0 -2
- package/hmr/shared/runtime/http-only-boot.js +0 -172
- package/hmr/shared/runtime/http-only-boot.js.map +0 -1
- package/hmr/vendor-bootstrap.d.ts +0 -1
- package/hmr/vendor-bootstrap.js +0 -30
- package/hmr/vendor-bootstrap.js.map +0 -1
- package/shims/react-reconciler-constants.d.ts +0 -14
- package/shims/react-reconciler-constants.js +0 -21
- package/shims/react-reconciler-constants.js.map +0 -1
- package/shims/react-reconciler.d.ts +0 -8
- package/shims/react-reconciler.js +0 -15
- package/shims/react-reconciler.js.map +0 -1
|
@@ -1,28 +1,24 @@
|
|
|
1
|
-
import { BOOT_PLACEHOLDER_MOTION, computeBootProgressFillScale, formatBootDetailLine, formatBootPrimaryLine } from './boot-placeholder-ui.js';
|
|
2
|
-
import { BOOT_TITLE, DEFAULT_SNAPSHOT, createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
3
|
-
import { getGlobalScope } from './global-scope.js';
|
|
4
|
-
// Re-export the snapshot model so existing `./dev-overlay.js` importers keep working.
|
|
5
|
-
export { createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
6
|
-
const DEFAULT_OVERLAY_POSITION = 'top';
|
|
7
|
-
function getOverlayGlobal() {
|
|
8
|
-
return getGlobalScope();
|
|
9
|
-
}
|
|
10
1
|
/**
|
|
11
|
-
*
|
|
2
|
+
* Dev overlay public API: boot / connection / update stage setters plus the
|
|
3
|
+
* auto-hide timing policy. Rendering is delegated to two backends behind the
|
|
4
|
+
* shared snapshot/state model (`dev-overlay-shared.ts`):
|
|
12
5
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
6
|
+
* - `dev-overlay-ios.ts` — dedicated UIWindow above `UIWindowLevelAlert`, so
|
|
7
|
+
* the overlay stacks above modals/sheets/system alerts (preferred on iOS)
|
|
8
|
+
* - `dev-overlay-tree.ts` — boot placeholder + in-tree toast chip (Android,
|
|
9
|
+
* and the fallback when the iOS window can't be built)
|
|
17
10
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
11
|
+
import { DEFAULT_SNAPSHOT, createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
12
|
+
import { getHmrDevOverlayPosition, getOverlayGlobal, getRuntimeState } from './dev-overlay-shared.js';
|
|
13
|
+
import { applySnapshotToBootRefs, applySnapshotToLiveRefs, buildBootOverlayRefs, ensureLiveOverlayRefs, updateBootStatusLabel } from './dev-overlay-tree.js';
|
|
14
|
+
import { applySnapshotToIosRefs, ensureIosOverlayRefs, getIosOverlayHost } from './dev-overlay-ios.js';
|
|
15
|
+
import { resolveOverlayEnabled } from './overlay-flag.js';
|
|
16
|
+
// Re-export the snapshot model and backend pure helpers so existing
|
|
17
|
+
// `./dev-overlay.js` importers keep working.
|
|
18
|
+
export { createBootOverlaySnapshot, createConnectionOverlaySnapshot, createUpdateOverlaySnapshot } from './dev-overlay-snapshots.js';
|
|
19
|
+
export { getHmrDevOverlayPosition } from './dev-overlay-shared.js';
|
|
20
|
+
export { computeAndroidToastMargin } from './dev-overlay-tree.js';
|
|
21
|
+
export { computeIosOverlayLayout, computeIosOverlayWindowLevel } from './dev-overlay-ios.js';
|
|
26
22
|
/**
|
|
27
23
|
* Imperative setter for the live-overlay position. Re-applies the
|
|
28
24
|
* current snapshot so the change is visible without waiting for the
|
|
@@ -38,1302 +34,6 @@ export function setHmrDevOverlayPosition(position) {
|
|
|
38
34
|
const state = getRuntimeState();
|
|
39
35
|
applyRuntimeSnapshot(state.snapshot);
|
|
40
36
|
}
|
|
41
|
-
function getRuntimeState() {
|
|
42
|
-
const g = getOverlayGlobal();
|
|
43
|
-
if (!g.__NS_HMR_DEV_OVERLAY_STATE__) {
|
|
44
|
-
g.__NS_HMR_DEV_OVERLAY_STATE__ = {
|
|
45
|
-
snapshot: { ...DEFAULT_SNAPSHOT },
|
|
46
|
-
bootRefs: null,
|
|
47
|
-
liveRefs: null,
|
|
48
|
-
iosRefs: null,
|
|
49
|
-
iosBuildFailed: false,
|
|
50
|
-
verbose: false,
|
|
51
|
-
updateAutoHideTimer: null,
|
|
52
|
-
updateCycleStartedAt: 0,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const state = g.__NS_HMR_DEV_OVERLAY_STATE__;
|
|
56
|
-
// Backfill newer fields for legacy state objects (e.g. after hot reload)
|
|
57
|
-
// so we never observe an undefined iosRefs/iosBuildFailed at runtime.
|
|
58
|
-
if (typeof state.iosRefs === 'undefined')
|
|
59
|
-
state.iosRefs = null;
|
|
60
|
-
if (typeof state.iosBuildFailed === 'undefined')
|
|
61
|
-
state.iosBuildFailed = false;
|
|
62
|
-
if (typeof state.updateAutoHideTimer === 'undefined')
|
|
63
|
-
state.updateAutoHideTimer = null;
|
|
64
|
-
if (typeof state.updateCycleStartedAt !== 'number')
|
|
65
|
-
state.updateCycleStartedAt = 0;
|
|
66
|
-
return state;
|
|
67
|
-
}
|
|
68
|
-
function resolveCoreExport(name) {
|
|
69
|
-
const g = getOverlayGlobal();
|
|
70
|
-
try {
|
|
71
|
-
const reg = g.__nsVendorRegistry;
|
|
72
|
-
if (reg && typeof reg.get === 'function') {
|
|
73
|
-
const mod = reg.get('@nativescript/core');
|
|
74
|
-
const ns = (mod && (mod.default ?? mod)) || mod;
|
|
75
|
-
if (ns && ns[name]) {
|
|
76
|
-
return ns[name];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch { }
|
|
81
|
-
try {
|
|
82
|
-
if (g && g[name]) {
|
|
83
|
-
return g[name];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch { }
|
|
87
|
-
try {
|
|
88
|
-
const req = g.__nsVendorRequire || g.__nsRequire || g.require;
|
|
89
|
-
if (typeof req === 'function') {
|
|
90
|
-
const mod = req('@nativescript/core');
|
|
91
|
-
const ns = (mod && (mod.default ?? mod)) || mod;
|
|
92
|
-
if (ns && ns[name]) {
|
|
93
|
-
return ns[name];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch { }
|
|
98
|
-
try {
|
|
99
|
-
const nativeReq = g.__nativeRequire;
|
|
100
|
-
if (typeof nativeReq === 'function') {
|
|
101
|
-
const mod = nativeReq('@nativescript/core', '/');
|
|
102
|
-
const ns = (mod && (mod.default ?? mod)) || mod;
|
|
103
|
-
if (ns && ns[name]) {
|
|
104
|
-
return ns[name];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch { }
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
function asColor(value) {
|
|
112
|
-
try {
|
|
113
|
-
const Color = resolveCoreExport('Color');
|
|
114
|
-
if (Color) {
|
|
115
|
-
return new Color(value);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch { }
|
|
119
|
-
return value;
|
|
120
|
-
}
|
|
121
|
-
function formatStatusText(snapshot) {
|
|
122
|
-
const progressText = typeof snapshot.progress === 'number' ? ` (${Math.round(Number(snapshot.progress))}%)` : '';
|
|
123
|
-
const primary = `${snapshot.phase || ''}${progressText}`.trim();
|
|
124
|
-
return [primary, snapshot.detail].filter(Boolean).join('\n');
|
|
125
|
-
}
|
|
126
|
-
function buildBootOverlayRefs(snapshot) {
|
|
127
|
-
const Page = resolveCoreExport('Page');
|
|
128
|
-
const StackLayout = resolveCoreExport('StackLayout');
|
|
129
|
-
const Label = resolveCoreExport('Label');
|
|
130
|
-
const ActivityIndicator = resolveCoreExport('ActivityIndicator');
|
|
131
|
-
if (!Page || !StackLayout || !Label) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
const page = new Page();
|
|
135
|
-
page.actionBarHidden = true;
|
|
136
|
-
const root = new StackLayout();
|
|
137
|
-
root.padding = 24;
|
|
138
|
-
root.verticalAlignment = 'middle';
|
|
139
|
-
root.horizontalAlignment = 'stretch';
|
|
140
|
-
const titleLabel = new Label();
|
|
141
|
-
titleLabel.text = BOOT_TITLE;
|
|
142
|
-
titleLabel.textAlignment = 'center';
|
|
143
|
-
titleLabel.textWrap = true;
|
|
144
|
-
titleLabel.fontSize = 22;
|
|
145
|
-
titleLabel.fontWeight = '600';
|
|
146
|
-
const statusLabel = new Label();
|
|
147
|
-
statusLabel.textAlignment = 'center';
|
|
148
|
-
statusLabel.textWrap = true;
|
|
149
|
-
statusLabel.fontSize = 14;
|
|
150
|
-
statusLabel.marginTop = 12;
|
|
151
|
-
const activityIndicator = ActivityIndicator
|
|
152
|
-
? (() => {
|
|
153
|
-
const indicator = new ActivityIndicator();
|
|
154
|
-
indicator.marginTop = 16;
|
|
155
|
-
indicator.width = 28;
|
|
156
|
-
indicator.height = 28;
|
|
157
|
-
indicator.horizontalAlignment = 'center';
|
|
158
|
-
return indicator;
|
|
159
|
-
})()
|
|
160
|
-
: null;
|
|
161
|
-
root.addChild(titleLabel);
|
|
162
|
-
root.addChild(statusLabel);
|
|
163
|
-
if (activityIndicator) {
|
|
164
|
-
root.addChild(activityIndicator);
|
|
165
|
-
}
|
|
166
|
-
page.content = root;
|
|
167
|
-
const refs = {
|
|
168
|
-
page,
|
|
169
|
-
root,
|
|
170
|
-
titleLabel,
|
|
171
|
-
statusLabel,
|
|
172
|
-
activityIndicator,
|
|
173
|
-
};
|
|
174
|
-
applySnapshotToBootRefs(refs, snapshot);
|
|
175
|
-
return refs;
|
|
176
|
-
}
|
|
177
|
-
function applySnapshotToBootRefs(refs, snapshot) {
|
|
178
|
-
if (!refs) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
refs.page.actionBarHidden = true;
|
|
182
|
-
refs.page.backgroundColor = asColor(snapshot.tone === 'error' ? '#b4181068' : '#a1771683');
|
|
183
|
-
refs.root.visibility = snapshot.visible && snapshot.mode === 'boot' ? 'visible' : 'collapse';
|
|
184
|
-
refs.titleLabel.text = BOOT_TITLE;
|
|
185
|
-
refs.titleLabel.color = asColor(snapshot.tone === 'error' ? '#b41810e6' : '#563e3fb1');
|
|
186
|
-
refs.statusLabel.text = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
|
|
187
|
-
refs.statusLabel.color = asColor(snapshot.tone === 'error' ? '#b41810e6' : '#563e3fb1');
|
|
188
|
-
if (refs.activityIndicator) {
|
|
189
|
-
refs.activityIndicator.busy = !!snapshot.busy;
|
|
190
|
-
refs.activityIndicator.visibility = snapshot.visible && snapshot.mode === 'boot' && snapshot.busy ? 'visible' : 'collapse';
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function findBootActivityIndicator() {
|
|
194
|
-
const g = getOverlayGlobal();
|
|
195
|
-
if (g.__NS_DEV_BOOT_ACTIVITY_INDICATOR__) {
|
|
196
|
-
return g.__NS_DEV_BOOT_ACTIVITY_INDICATOR__;
|
|
197
|
-
}
|
|
198
|
-
try {
|
|
199
|
-
const frame = g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__;
|
|
200
|
-
if (!frame)
|
|
201
|
-
return null;
|
|
202
|
-
const page = frame.currentPage || frame._currentEntry?.resolvedPage;
|
|
203
|
-
const content = page?.content;
|
|
204
|
-
const children = content?.getChildrenCount?.() || 0;
|
|
205
|
-
if (children >= 3) {
|
|
206
|
-
const indicator = content.getChildAt?.(2);
|
|
207
|
-
if (indicator && typeof indicator.busy !== 'undefined') {
|
|
208
|
-
g.__NS_DEV_BOOT_ACTIVITY_INDICATOR__ = indicator;
|
|
209
|
-
return indicator;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
catch { }
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
function findBootStatusLabel() {
|
|
217
|
-
const g = getOverlayGlobal();
|
|
218
|
-
// Try direct reference first
|
|
219
|
-
if (g.__NS_DEV_BOOT_STATUS_LABEL__) {
|
|
220
|
-
return g.__NS_DEV_BOOT_STATUS_LABEL__;
|
|
221
|
-
}
|
|
222
|
-
// Fallback: walk from placeholder root view
|
|
223
|
-
try {
|
|
224
|
-
const frame = g.__NS_DEV_PLACEHOLDER_ROOT_VIEW__;
|
|
225
|
-
if (!frame)
|
|
226
|
-
return null;
|
|
227
|
-
const page = frame.currentPage || frame._currentEntry?.resolvedPage;
|
|
228
|
-
if (!page)
|
|
229
|
-
return null;
|
|
230
|
-
const content = page.content;
|
|
231
|
-
if (!content)
|
|
232
|
-
return null;
|
|
233
|
-
// StackLayout with children: titleLabel, statusLabel
|
|
234
|
-
const children = content.getChildrenCount?.() || 0;
|
|
235
|
-
if (children >= 2) {
|
|
236
|
-
const label = content.getChildAt?.(1);
|
|
237
|
-
if (label && typeof label.text !== 'undefined') {
|
|
238
|
-
// Cache for next time
|
|
239
|
-
g.__NS_DEV_BOOT_STATUS_LABEL__ = label;
|
|
240
|
-
return label;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch { }
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
function findBootDetailLabel() {
|
|
248
|
-
const g = getOverlayGlobal();
|
|
249
|
-
return g.__NS_DEV_BOOT_DETAIL_LABEL__ || null;
|
|
250
|
-
}
|
|
251
|
-
function findBootProgressFill() {
|
|
252
|
-
const g = getOverlayGlobal();
|
|
253
|
-
return g.__NS_DEV_BOOT_PROGRESS_FILL__ || null;
|
|
254
|
-
}
|
|
255
|
-
function updateBootStatusLabel(snapshot) {
|
|
256
|
-
const statusLabel = findBootStatusLabel();
|
|
257
|
-
const detailLabel = findBootDetailLabel();
|
|
258
|
-
const progressFill = findBootProgressFill();
|
|
259
|
-
const activityIndicator = findBootActivityIndicator();
|
|
260
|
-
// New (card) layout: phase line + detail line live in separate
|
|
261
|
-
// labels so the typography can differ. Legacy (single-label)
|
|
262
|
-
// layout: keep the original combined "phase (X%)\ndetail" text so
|
|
263
|
-
// nothing visually regresses for runtimes still attached to the
|
|
264
|
-
// older placeholder shape.
|
|
265
|
-
const hasSplitLabels = !!detailLabel;
|
|
266
|
-
const phaseLine = formatBootPrimaryLine(snapshot);
|
|
267
|
-
const detailLine = formatBootDetailLine(snapshot);
|
|
268
|
-
const combinedText = formatStatusText(snapshot) || 'Preparing the HTTP HMR bootstrap (4%)';
|
|
269
|
-
if (!statusLabel) {
|
|
270
|
-
if (activityIndicator) {
|
|
271
|
-
try {
|
|
272
|
-
activityIndicator.busy = !!snapshot.busy;
|
|
273
|
-
activityIndicator.visibility = snapshot.visible && snapshot.mode === 'boot' && snapshot.busy ? 'visible' : 'collapse';
|
|
274
|
-
}
|
|
275
|
-
catch { }
|
|
276
|
-
}
|
|
277
|
-
applyBootProgressFill(progressFill, snapshot);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
statusLabel.text = hasSplitLabels ? phaseLine || 'Preparing the HTTP HMR bootstrap' : combinedText;
|
|
282
|
-
// Card layout uses the calibrated phase-text colour from the
|
|
283
|
-
// palette; legacy single-label layout keeps the original muted
|
|
284
|
-
// brown so we don't visually regress mid-session.
|
|
285
|
-
const phaseColorHex = snapshot.tone === 'error' ? '#B91C1C' : hasSplitLabels ? '#475569' : '#563e3fb1';
|
|
286
|
-
statusLabel.color = asColor(phaseColorHex);
|
|
287
|
-
if (typeof statusLabel.requestLayout === 'function') {
|
|
288
|
-
statusLabel.requestLayout();
|
|
289
|
-
}
|
|
290
|
-
const parent = statusLabel.parent;
|
|
291
|
-
if (parent && typeof parent.requestLayout === 'function') {
|
|
292
|
-
parent.requestLayout();
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
catch { }
|
|
296
|
-
if (detailLabel) {
|
|
297
|
-
try {
|
|
298
|
-
detailLabel.text = detailLine;
|
|
299
|
-
detailLabel.color = asColor(snapshot.tone === 'error' ? '#DC2626' : '#94A3B8');
|
|
300
|
-
detailLabel.visibility = detailLine ? 'visible' : 'collapse';
|
|
301
|
-
}
|
|
302
|
-
catch { }
|
|
303
|
-
}
|
|
304
|
-
applyBootProgressFill(progressFill, snapshot);
|
|
305
|
-
if (activityIndicator) {
|
|
306
|
-
try {
|
|
307
|
-
activityIndicator.busy = !!snapshot.busy;
|
|
308
|
-
activityIndicator.visibility = snapshot.visible && snapshot.mode === 'boot' && snapshot.busy ? 'visible' : 'collapse';
|
|
309
|
-
}
|
|
310
|
-
catch { }
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// Drive the progress fill scaleX from the snapshot. Uses NS's view
|
|
314
|
-
// animate API for a smooth 220 ms easeOut between heartbeat ticks; a
|
|
315
|
-
// monotonic ratchet on `globalThis.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__`
|
|
316
|
-
// guards against the fill snapping backwards if a less-progressed
|
|
317
|
-
// snapshot ever lands between ticks (mirrors the JS-side
|
|
318
|
-
// `applyMonotonicBootProgress` contract).
|
|
319
|
-
function applyBootProgressFill(progressFill, snapshot) {
|
|
320
|
-
if (!progressFill)
|
|
321
|
-
return;
|
|
322
|
-
const g = getOverlayGlobal();
|
|
323
|
-
const isError = snapshot.tone === 'error';
|
|
324
|
-
progressFill.backgroundColor = asColor(isError ? '#B41810' : '#3B6FE5');
|
|
325
|
-
const targetScale = computeBootProgressFillScale(snapshot.progress ?? null);
|
|
326
|
-
const previousRaw = Number(g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__);
|
|
327
|
-
const previous = Number.isFinite(previousRaw) ? previousRaw : 0;
|
|
328
|
-
const next = Math.max(previous, targetScale);
|
|
329
|
-
g.__NS_DEV_BOOT_PROGRESS_LAST_SCALE__ = next;
|
|
330
|
-
try {
|
|
331
|
-
// NS view.animate scales around `originX`/`originY`; the
|
|
332
|
-
// placeholder builder pins `originX = 0` so the fill grows
|
|
333
|
-
// rightward. animate() may be unavailable in some headless
|
|
334
|
-
// test environments — fall through to a direct property set.
|
|
335
|
-
if (typeof progressFill.animate === 'function') {
|
|
336
|
-
progressFill
|
|
337
|
-
.animate({
|
|
338
|
-
scale: { x: next, y: 1 },
|
|
339
|
-
duration: BOOT_PLACEHOLDER_MOTION.progressDurationMs,
|
|
340
|
-
curve: 'easeOut',
|
|
341
|
-
})
|
|
342
|
-
.catch(() => {
|
|
343
|
-
try {
|
|
344
|
-
progressFill.scaleX = next;
|
|
345
|
-
}
|
|
346
|
-
catch { }
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
progressFill.scaleX = next;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
catch {
|
|
354
|
-
progressFill.scaleX = next;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function resolveActivePage() {
|
|
358
|
-
try {
|
|
359
|
-
const Frame = resolveCoreExport('Frame');
|
|
360
|
-
const frame = Frame?.topmost?.() || null;
|
|
361
|
-
const currentPage = frame?.currentPage || frame?._currentEntry?.resolvedPage || null;
|
|
362
|
-
if (currentPage) {
|
|
363
|
-
return currentPage;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
catch { }
|
|
367
|
-
try {
|
|
368
|
-
const Application = resolveCoreExport('Application');
|
|
369
|
-
const rootView = Application?.getRootView?.() || null;
|
|
370
|
-
if (rootView?.currentPage) {
|
|
371
|
-
return rootView.currentPage;
|
|
372
|
-
}
|
|
373
|
-
if (typeof rootView?.content !== 'undefined') {
|
|
374
|
-
return rootView;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
catch { }
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
function readCachedAndroidSafeAreaInsets() {
|
|
381
|
-
const g = getOverlayGlobal();
|
|
382
|
-
const cached = g.__NS_HMR_ANDROID_SAFE_INSETS__;
|
|
383
|
-
if (cached && typeof cached.top === 'number') {
|
|
384
|
-
return cached;
|
|
385
|
-
}
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Read the Android system-bar safe-area insets (status bar / navigation
|
|
390
|
-
* bar / display cutout) in device-independent pixels.
|
|
391
|
-
*
|
|
392
|
-
* NativeScript runs every Android activity edge-to-edge, so the overlay
|
|
393
|
-
* wrapper fills the whole window — we need these insets to keep the toast
|
|
394
|
-
* chip out from under the system bars (see `computeAndroidToastMargin`).
|
|
395
|
-
*
|
|
396
|
-
* Returns null on non-Android runtimes (iOS / web / vitest) so callers
|
|
397
|
-
* fall back to base margins. The last good reading is cached on the
|
|
398
|
-
* global so a transient null (e.g. insets queried before the decor view
|
|
399
|
-
* is attached) reuses the previous value instead of snapping the chip
|
|
400
|
-
* back under the status bar.
|
|
401
|
-
*/
|
|
402
|
-
function readAndroidSafeAreaInsets() {
|
|
403
|
-
const g = getOverlayGlobal();
|
|
404
|
-
// `android.*` is exposed as a global only on the NativeScript Android
|
|
405
|
-
// runtime; its absence means iOS, web, or a test environment.
|
|
406
|
-
const androidNs = g.android || (typeof globalThis.android !== 'undefined' ? globalThis.android : undefined);
|
|
407
|
-
if (!androidNs) {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
try {
|
|
411
|
-
const Application = resolveCoreExport('Application');
|
|
412
|
-
const activity = Application?.android?.foregroundActivity || Application?.android?.startActivity;
|
|
413
|
-
const decorView = activity?.getWindow?.()?.getDecorView?.();
|
|
414
|
-
const rootInsets = decorView?.getRootWindowInsets?.();
|
|
415
|
-
if (!rootInsets) {
|
|
416
|
-
return readCachedAndroidSafeAreaInsets();
|
|
417
|
-
}
|
|
418
|
-
let density = 1;
|
|
419
|
-
try {
|
|
420
|
-
const metricsDensity = Number(activity?.getResources?.()?.getDisplayMetrics?.()?.density);
|
|
421
|
-
if (Number.isFinite(metricsDensity) && metricsDensity > 0) {
|
|
422
|
-
density = metricsDensity;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
catch { }
|
|
426
|
-
let px = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
427
|
-
const WindowInsets = androidNs.view?.WindowInsets;
|
|
428
|
-
const Type = WindowInsets?.Type;
|
|
429
|
-
if (typeof rootInsets.getInsets === 'function' && Type && typeof Type.systemBars === 'function') {
|
|
430
|
-
// API 30+: query the system bars + display cutout so notches /
|
|
431
|
-
// camera holes are respected in landscape.
|
|
432
|
-
let mask = Type.systemBars();
|
|
433
|
-
if (typeof Type.displayCutout === 'function') {
|
|
434
|
-
mask = mask | Type.displayCutout();
|
|
435
|
-
}
|
|
436
|
-
const i = rootInsets.getInsets(mask);
|
|
437
|
-
px = { top: Number(i?.top) || 0, bottom: Number(i?.bottom) || 0, left: Number(i?.left) || 0, right: Number(i?.right) || 0 };
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
// API < 30 fallback.
|
|
441
|
-
px = {
|
|
442
|
-
top: Number(rootInsets.getSystemWindowInsetTop?.()) || 0,
|
|
443
|
-
bottom: Number(rootInsets.getSystemWindowInsetBottom?.()) || 0,
|
|
444
|
-
left: Number(rootInsets.getSystemWindowInsetLeft?.()) || 0,
|
|
445
|
-
right: Number(rootInsets.getSystemWindowInsetRight?.()) || 0,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
const insets = {
|
|
449
|
-
top: px.top / density,
|
|
450
|
-
bottom: px.bottom / density,
|
|
451
|
-
left: px.left / density,
|
|
452
|
-
right: px.right / density,
|
|
453
|
-
};
|
|
454
|
-
g.__NS_HMR_ANDROID_SAFE_INSETS__ = insets;
|
|
455
|
-
return insets;
|
|
456
|
-
}
|
|
457
|
-
catch {
|
|
458
|
-
return readCachedAndroidSafeAreaInsets();
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
function buildLiveOverlayView(snapshot) {
|
|
462
|
-
const GridLayout = resolveCoreExport('GridLayout');
|
|
463
|
-
const StackLayout = resolveCoreExport('StackLayout');
|
|
464
|
-
const Label = resolveCoreExport('Label');
|
|
465
|
-
if (!GridLayout || !StackLayout || !Label) {
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
const overlay = new GridLayout();
|
|
469
|
-
overlay.visibility = 'collapse';
|
|
470
|
-
overlay.width = '100%';
|
|
471
|
-
overlay.height = '100%';
|
|
472
|
-
overlay.horizontalAlignment = 'stretch';
|
|
473
|
-
overlay.verticalAlignment = 'stretch';
|
|
474
|
-
// Toast mode lets touches reach the underlying app. We flip
|
|
475
|
-
// isUserInteractionEnabled in applySnapshotToLiveRefs based on
|
|
476
|
-
// the resolved position, but keep it false here as a safe default
|
|
477
|
-
// (the panel itself is purely informational).
|
|
478
|
-
try {
|
|
479
|
-
overlay.isUserInteractionEnabled = false;
|
|
480
|
-
}
|
|
481
|
-
catch { }
|
|
482
|
-
const panel = new StackLayout();
|
|
483
|
-
panel.horizontalAlignment = 'center';
|
|
484
|
-
// Vertical alignment is overridden in applySnapshotToLiveRefs
|
|
485
|
-
// based on getHmrDevOverlayPosition(); 'middle' is the default
|
|
486
|
-
panel.verticalAlignment = 'middle';
|
|
487
|
-
panel.width = 320;
|
|
488
|
-
panel.margin = 24;
|
|
489
|
-
panel.padding = 16;
|
|
490
|
-
const titleLabel = new Label();
|
|
491
|
-
titleLabel.textAlignment = 'center';
|
|
492
|
-
titleLabel.textWrap = true;
|
|
493
|
-
titleLabel.fontSize = 18;
|
|
494
|
-
titleLabel.fontWeight = '600';
|
|
495
|
-
const statusLabel = new Label();
|
|
496
|
-
statusLabel.textAlignment = 'center';
|
|
497
|
-
statusLabel.textWrap = true;
|
|
498
|
-
statusLabel.fontSize = 13;
|
|
499
|
-
statusLabel.marginTop = 10;
|
|
500
|
-
panel.addChild(titleLabel);
|
|
501
|
-
panel.addChild(statusLabel);
|
|
502
|
-
overlay.addChild(panel);
|
|
503
|
-
const refs = {
|
|
504
|
-
overlay,
|
|
505
|
-
titleLabel,
|
|
506
|
-
statusLabel,
|
|
507
|
-
wasVisible: false,
|
|
508
|
-
currentPosition: getHmrDevOverlayPosition(),
|
|
509
|
-
};
|
|
510
|
-
applySnapshotToLiveRefs(refs, snapshot);
|
|
511
|
-
return refs;
|
|
512
|
-
}
|
|
513
|
-
function ensureLiveOverlayRefs(snapshot) {
|
|
514
|
-
const state = getRuntimeState();
|
|
515
|
-
const page = resolveActivePage();
|
|
516
|
-
if (!page) {
|
|
517
|
-
return null;
|
|
518
|
-
}
|
|
519
|
-
if (state.liveRefs) {
|
|
520
|
-
if (state.liveRefs.page === page && state.liveRefs.overlay?.parent) {
|
|
521
|
-
return state.liveRefs;
|
|
522
|
-
}
|
|
523
|
-
try {
|
|
524
|
-
state.liveRefs.overlay.visibility = 'collapse';
|
|
525
|
-
}
|
|
526
|
-
catch { }
|
|
527
|
-
state.liveRefs = null;
|
|
528
|
-
}
|
|
529
|
-
const GridLayout = resolveCoreExport('GridLayout');
|
|
530
|
-
if (!GridLayout || typeof page.content === 'undefined') {
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
let wrapper = page.content;
|
|
534
|
-
if (!wrapper || !wrapper.__ns_hmr_overlay_wrapper__) {
|
|
535
|
-
const currentContent = page.content || null;
|
|
536
|
-
wrapper = new GridLayout();
|
|
537
|
-
wrapper.width = '100%';
|
|
538
|
-
wrapper.height = '100%';
|
|
539
|
-
wrapper.__ns_hmr_overlay_wrapper__ = true;
|
|
540
|
-
try {
|
|
541
|
-
page.content = null;
|
|
542
|
-
}
|
|
543
|
-
catch { }
|
|
544
|
-
if (currentContent) {
|
|
545
|
-
wrapper.addChild(currentContent);
|
|
546
|
-
}
|
|
547
|
-
page.content = wrapper;
|
|
548
|
-
}
|
|
549
|
-
const liveView = buildLiveOverlayView(snapshot);
|
|
550
|
-
if (!liveView) {
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
wrapper.addChild(liveView.overlay);
|
|
554
|
-
state.liveRefs = {
|
|
555
|
-
page,
|
|
556
|
-
wrapper,
|
|
557
|
-
...liveView,
|
|
558
|
-
};
|
|
559
|
-
return state.liveRefs;
|
|
560
|
-
}
|
|
561
|
-
function applySnapshotToLiveRefs(refs, snapshot) {
|
|
562
|
-
if (!refs) {
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
// 'update' mode shares the live (in-tree) overlay chrome with
|
|
566
|
-
// 'connection'. Both render a small panel inside the page; only
|
|
567
|
-
// the colours, text, and (now) panel position change with the
|
|
568
|
-
// snapshot's tone and the configured overlay position.
|
|
569
|
-
const visible = snapshot.visible && (snapshot.mode === 'connection' || snapshot.mode === 'update');
|
|
570
|
-
const wasVisible = !!refs.wasVisible;
|
|
571
|
-
const position = getHmrDevOverlayPosition();
|
|
572
|
-
const previousPosition = refs.currentPosition || position;
|
|
573
|
-
const isToast = position !== 'center';
|
|
574
|
-
refs.titleLabel.text = snapshot.title;
|
|
575
|
-
refs.statusLabel.text = formatStatusText(snapshot);
|
|
576
|
-
const textColor = snapshot.tone === 'error' ? '#b41810e6' : snapshot.tone === 'success' ? '#0e6e2fff' : '#563e3fb1';
|
|
577
|
-
refs.titleLabel.color = asColor(textColor);
|
|
578
|
-
refs.statusLabel.color = asColor(textColor);
|
|
579
|
-
// Backdrop tints (centered modal only). Toast modes use a fully
|
|
580
|
-
// transparent backdrop so the rest of the app stays visible AND
|
|
581
|
-
// reachable; the panel itself carries enough colour to stand out.
|
|
582
|
-
if (isToast) {
|
|
583
|
-
refs.overlay.backgroundColor = asColor('transparent');
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
// Original wash-by-tone for centered:
|
|
587
|
-
// error → red wash (matches existing UX)
|
|
588
|
-
// success → richer green wash so the apply event is visible
|
|
589
|
-
// on bright app backgrounds
|
|
590
|
-
// default → warm orange (existing connection-overlay look)
|
|
591
|
-
const overlayBg = snapshot.tone === 'error' ? '#b4181068' : snapshot.tone === 'success' ? '#1f883d80' : '#a1771683';
|
|
592
|
-
refs.overlay.backgroundColor = asColor(overlayBg);
|
|
593
|
-
}
|
|
594
|
-
// Panel chrome — toast and centered share the same chip look,
|
|
595
|
-
// just position differs. We keep the slightly richer green tint
|
|
596
|
-
// for the HMR success state so it pops without needing the
|
|
597
|
-
// backdrop wash.
|
|
598
|
-
let panel = null;
|
|
599
|
-
try {
|
|
600
|
-
panel = refs.titleLabel.parent;
|
|
601
|
-
if (panel) {
|
|
602
|
-
const panelBg = snapshot.tone === 'success' ? '#E6F8E9FF' : '#FFFFFFFF';
|
|
603
|
-
panel.backgroundColor = asColor(panelBg);
|
|
604
|
-
panel.opacity = 1;
|
|
605
|
-
panel.padding = 16;
|
|
606
|
-
try {
|
|
607
|
-
panel.borderRadius = 12;
|
|
608
|
-
}
|
|
609
|
-
catch { }
|
|
610
|
-
// Position-aware alignment. The wrapper GridLayout fills the
|
|
611
|
-
// page content area. NativeScript runs every Android activity
|
|
612
|
-
// edge-to-edge, so that area extends under the status bar and
|
|
613
|
-
// navigation bar — we fold the system-bar safe-area insets into
|
|
614
|
-
// the chip's margin (via computeAndroidToastMargin) so a top
|
|
615
|
-
// chip clears the status bar and a bottom chip clears the nav
|
|
616
|
-
// bar instead of being clipped behind them. readAndroidSafeAreaInsets
|
|
617
|
-
// returns null off-Android, so the margins collapse to their base
|
|
618
|
-
// values (no behavioural change on iOS's in-tree fallback / tests).
|
|
619
|
-
try {
|
|
620
|
-
if (position === 'top' || position === 'bottom') {
|
|
621
|
-
const m = computeAndroidToastMargin({ position, safeInsets: readAndroidSafeAreaInsets() });
|
|
622
|
-
panel.verticalAlignment = position;
|
|
623
|
-
panel.margin = `${m.top} ${m.right} ${m.bottom} ${m.left}`;
|
|
624
|
-
}
|
|
625
|
-
else {
|
|
626
|
-
panel.verticalAlignment = 'middle';
|
|
627
|
-
panel.margin = 24;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
catch { }
|
|
631
|
-
// Force a tight re-measure so the StackLayout panel hugs the
|
|
632
|
-
// CURRENT title+status text. Connection stages can swap between
|
|
633
|
-
// frames whose text wraps to a different number of lines (e.g. a
|
|
634
|
-
// long 'reconnecting' "Retrying ws://…" detail vs. the short
|
|
635
|
-
// 'offline' message). Without an explicit relayout the panel can
|
|
636
|
-
// retain the tallest height it ever measured, leaving an oversized
|
|
637
|
-
// gap between title and message. Mirrors updateBootStatusLabel.
|
|
638
|
-
try {
|
|
639
|
-
refs.titleLabel.requestLayout?.();
|
|
640
|
-
refs.statusLabel.requestLayout?.();
|
|
641
|
-
panel.requestLayout?.();
|
|
642
|
-
}
|
|
643
|
-
catch { }
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
catch { }
|
|
647
|
-
// Touch passthrough for toast; centered mode keeps the
|
|
648
|
-
// blocking modal so the dim backdrop is meaningful.
|
|
649
|
-
try {
|
|
650
|
-
refs.overlay.isUserInteractionEnabled = !isToast;
|
|
651
|
-
}
|
|
652
|
-
catch { }
|
|
653
|
-
const positionChanged = previousPosition !== position;
|
|
654
|
-
const justAppeared = visible && (!wasVisible || positionChanged);
|
|
655
|
-
const justDismissed = !visible && wasVisible;
|
|
656
|
-
if (justAppeared) {
|
|
657
|
-
refs.overlay.visibility = 'visible';
|
|
658
|
-
if (isToast && panel && typeof panel.animate === 'function') {
|
|
659
|
-
animateLivePanelIn(panel, position);
|
|
660
|
-
}
|
|
661
|
-
else if (panel) {
|
|
662
|
-
try {
|
|
663
|
-
panel.translateY = 0;
|
|
664
|
-
panel.opacity = 1;
|
|
665
|
-
}
|
|
666
|
-
catch { }
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
else if (justDismissed) {
|
|
670
|
-
if (isToast && panel && typeof panel.animate === 'function') {
|
|
671
|
-
animateLivePanelOut(panel, previousPosition, () => {
|
|
672
|
-
try {
|
|
673
|
-
refs.overlay.visibility = 'collapse';
|
|
674
|
-
}
|
|
675
|
-
catch { }
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
refs.overlay.visibility = 'collapse';
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
else {
|
|
683
|
-
// Steady-state refresh (e.g. offline → reconnecting → offline while
|
|
684
|
-
// already visible): no slide animation fires here, so clear any
|
|
685
|
-
// transform residue a previously-interrupted slide-in/out may have
|
|
686
|
-
// left on the panel. Otherwise the chip can appear shifted or
|
|
687
|
-
// faded across rapid connection-stage swaps.
|
|
688
|
-
if (visible && panel) {
|
|
689
|
-
try {
|
|
690
|
-
panel.translateY = 0;
|
|
691
|
-
panel.opacity = 1;
|
|
692
|
-
}
|
|
693
|
-
catch { }
|
|
694
|
-
}
|
|
695
|
-
refs.overlay.visibility = visible ? 'visible' : 'collapse';
|
|
696
|
-
}
|
|
697
|
-
if (typeof refs.wasVisible !== 'undefined')
|
|
698
|
-
refs.wasVisible = visible;
|
|
699
|
-
if (typeof refs.currentPosition !== 'undefined')
|
|
700
|
-
refs.currentPosition = position;
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Slide-in animation for the in-tree toast panel.
|
|
704
|
-
*
|
|
705
|
-
* NativeScript's `View.animate({ translate, opacity, duration, curve })`
|
|
706
|
-
* is widely available across Core versions, so we don't depend on any
|
|
707
|
-
* specific curve enum being importable here. We use a moderate-to-snappy
|
|
708
|
-
* 320ms ease-out which feels close to a UIView spring without needing
|
|
709
|
-
* platform-specific APIs.
|
|
710
|
-
*/
|
|
711
|
-
function animateLivePanelIn(panel, position) {
|
|
712
|
-
if (!panel || typeof panel.animate !== 'function')
|
|
713
|
-
return;
|
|
714
|
-
try {
|
|
715
|
-
const startY = position === 'bottom' ? 80 : -80;
|
|
716
|
-
panel.translateY = startY;
|
|
717
|
-
panel.opacity = 0;
|
|
718
|
-
const result = panel.animate({
|
|
719
|
-
translate: { x: 0, y: 0 },
|
|
720
|
-
opacity: 1,
|
|
721
|
-
duration: 320,
|
|
722
|
-
curve: 'easeOut',
|
|
723
|
-
});
|
|
724
|
-
if (result && typeof result.catch === 'function') {
|
|
725
|
-
result.catch(() => {
|
|
726
|
-
try {
|
|
727
|
-
panel.translateY = 0;
|
|
728
|
-
panel.opacity = 1;
|
|
729
|
-
}
|
|
730
|
-
catch { }
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
catch {
|
|
735
|
-
try {
|
|
736
|
-
panel.translateY = 0;
|
|
737
|
-
panel.opacity = 1;
|
|
738
|
-
}
|
|
739
|
-
catch { }
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
function animateLivePanelOut(panel, position, onComplete) {
|
|
743
|
-
if (!panel || typeof panel.animate !== 'function') {
|
|
744
|
-
onComplete();
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
try {
|
|
748
|
-
const targetY = position === 'bottom' ? 80 : -80;
|
|
749
|
-
const result = panel.animate({
|
|
750
|
-
translate: { x: 0, y: targetY },
|
|
751
|
-
opacity: 0,
|
|
752
|
-
duration: 220,
|
|
753
|
-
curve: 'easeIn',
|
|
754
|
-
});
|
|
755
|
-
const finish = () => {
|
|
756
|
-
try {
|
|
757
|
-
panel.translateY = 0;
|
|
758
|
-
panel.opacity = 1;
|
|
759
|
-
}
|
|
760
|
-
catch { }
|
|
761
|
-
onComplete();
|
|
762
|
-
};
|
|
763
|
-
if (result && typeof result.then === 'function') {
|
|
764
|
-
result.then(finish, finish);
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
finish();
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
catch {
|
|
771
|
-
onComplete();
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
// pure helpers for iOS window promotion. Factored out so the layout
|
|
775
|
-
// math and window-level selection stay unit-testable without booting a
|
|
776
|
-
// simulator. See `dev-overlay.spec.ts`.
|
|
777
|
-
/**
|
|
778
|
-
* Returns the UIWindow level we use for the live/connection overlay. We lift
|
|
779
|
-
* above `UIWindowLevelAlert` so system alerts (and any app-presented modal)
|
|
780
|
-
* stack underneath. When the platform does not expose `UIWindowLevelAlert`
|
|
781
|
-
* we fall back to the documented constant value (2000).
|
|
782
|
-
*/
|
|
783
|
-
export function computeIosOverlayWindowLevel(baseAlert) {
|
|
784
|
-
if (typeof baseAlert === 'number' && Number.isFinite(baseAlert)) {
|
|
785
|
-
return baseAlert + 1;
|
|
786
|
-
}
|
|
787
|
-
return 2000 + 1;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Layout math for the live overlay when it runs inside its own UIWindow.
|
|
791
|
-
* Pure, deterministic and independent of UIKit so we can verify the rules
|
|
792
|
-
* (max panel width, position-aware placement, safe-area clamping, sane
|
|
793
|
-
* defaults) from tests.
|
|
794
|
-
*
|
|
795
|
-
* `position` controls where the panel sits vertically:
|
|
796
|
-
* - 'top': hugs `safeInsets.top + toastVerticalInset` so the chip
|
|
797
|
-
* sits just below the notch / Dynamic Island.
|
|
798
|
-
* - 'bottom': hugs `viewHeight - safeInsets.bottom - panelHeight -
|
|
799
|
-
* toastVerticalInset` so the chip sits just above the
|
|
800
|
-
* home indicator / nav bar.
|
|
801
|
-
* - 'center': original modal placement (vertically centered, clamped
|
|
802
|
-
* so it never crosses the top safe-area inset).
|
|
803
|
-
*/
|
|
804
|
-
export function computeIosOverlayLayout(input) {
|
|
805
|
-
const viewWidth = Math.max(0, Number(input.viewWidth) || 0);
|
|
806
|
-
const viewHeight = Math.max(0, Number(input.viewHeight) || 0);
|
|
807
|
-
const safeInsets = {
|
|
808
|
-
top: Math.max(0, Number(input.safeInsets?.top ?? 0) || 0),
|
|
809
|
-
bottom: Math.max(0, Number(input.safeInsets?.bottom ?? 0) || 0),
|
|
810
|
-
left: Math.max(0, Number(input.safeInsets?.left ?? 0) || 0),
|
|
811
|
-
right: Math.max(0, Number(input.safeInsets?.right ?? 0) || 0),
|
|
812
|
-
};
|
|
813
|
-
const titleHeight = Math.max(0, Number(input.titleHeight) || 0);
|
|
814
|
-
const statusHeight = Math.max(0, Number(input.statusHeight) || 0);
|
|
815
|
-
const horizontalMargin = Math.max(0, Number(input.horizontalMargin ?? 24));
|
|
816
|
-
const maxPanelWidth = Math.max(0, Number(input.maxPanelWidth ?? 340));
|
|
817
|
-
const panelPadding = Math.max(0, Number(input.panelPadding ?? 16));
|
|
818
|
-
const interLabelSpacing = Math.max(0, Number(input.interLabelSpacing ?? 10));
|
|
819
|
-
const minTopInset = Math.max(0, Number(input.minTopInset ?? 20));
|
|
820
|
-
// Default to 'center' on the pure function so the existing
|
|
821
|
-
// snapshot/layout tests remain stable; the runtime call site
|
|
822
|
-
// (layoutIosOverlayRefs) reads the configured position from
|
|
823
|
-
// `getHmrDevOverlayPosition()` and forwards it explicitly.
|
|
824
|
-
const position = input.position ?? 'center';
|
|
825
|
-
// Distance between the panel and the safe-area edge in toast
|
|
826
|
-
// modes. 8pt mirrors the typical iOS notification chip inset and
|
|
827
|
-
// keeps the chip from hugging the notch / home indicator.
|
|
828
|
-
const toastVerticalInset = Math.max(0, Number(input.toastVerticalInset ?? 8));
|
|
829
|
-
const available = Math.max(0, viewWidth - 2 * horizontalMargin - safeInsets.left - safeInsets.right);
|
|
830
|
-
const panelWidth = Math.min(maxPanelWidth, available);
|
|
831
|
-
const innerWidth = Math.max(0, panelWidth - 2 * panelPadding);
|
|
832
|
-
const spacing = titleHeight > 0 && statusHeight > 0 ? interLabelSpacing : 0;
|
|
833
|
-
const panelHeight = panelPadding * 2 + titleHeight + spacing + statusHeight;
|
|
834
|
-
const panelX = Math.max(0, (viewWidth - panelWidth) / 2);
|
|
835
|
-
let panelY;
|
|
836
|
-
if (position === 'top') {
|
|
837
|
-
// Pin to the top safe-area inset (just below notch / Dynamic
|
|
838
|
-
// Island). Clamp non-negative for fully-NaN input.
|
|
839
|
-
panelY = Math.max(0, safeInsets.top + toastVerticalInset);
|
|
840
|
-
}
|
|
841
|
-
else if (position === 'bottom') {
|
|
842
|
-
// Pin to the bottom safe-area inset (just above home indicator
|
|
843
|
-
// / nav bar). If the panel can't fit between the safe-area
|
|
844
|
-
// insets we fall back to the top safe-area edge so the chip is
|
|
845
|
-
// always visible (rather than getting clipped off-screen).
|
|
846
|
-
const desired = viewHeight - safeInsets.bottom - panelHeight - toastVerticalInset;
|
|
847
|
-
panelY = Math.max(safeInsets.top + minTopInset, desired);
|
|
848
|
-
}
|
|
849
|
-
else {
|
|
850
|
-
// Center vertically, but never cross the top safe-area inset
|
|
851
|
-
// (notch/Dynamic Island). Original modal placement.
|
|
852
|
-
const centered = (viewHeight - panelHeight) / 2;
|
|
853
|
-
panelY = Math.max(safeInsets.top + minTopInset, centered);
|
|
854
|
-
}
|
|
855
|
-
return {
|
|
856
|
-
backdrop: { x: 0, y: 0, width: viewWidth, height: viewHeight },
|
|
857
|
-
panel: { x: panelX, y: panelY, width: panelWidth, height: panelHeight },
|
|
858
|
-
title: { x: panelPadding, y: panelPadding, width: innerWidth, height: titleHeight },
|
|
859
|
-
status: {
|
|
860
|
-
x: panelPadding,
|
|
861
|
-
y: panelPadding + titleHeight + spacing,
|
|
862
|
-
width: innerWidth,
|
|
863
|
-
height: statusHeight,
|
|
864
|
-
},
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Margin math for the in-tree (Android) live/update toast chip.
|
|
869
|
-
*
|
|
870
|
-
* NativeScript enables edge-to-edge on every Android activity
|
|
871
|
-
* (`@nativescript/core` → `application.android.ts` → `enableEdgeToEdge`),
|
|
872
|
-
* so the page content — and therefore the overlay wrapper we inject into
|
|
873
|
-
* it — extends underneath the status bar and navigation bar. A fixed
|
|
874
|
-
* top/bottom margin leaves the chip tucked behind the system bars, where
|
|
875
|
-
* it gets clipped (Android 15 / API 35 enforces edge-to-edge, so this is
|
|
876
|
-
* now the norm rather than the exception).
|
|
877
|
-
*
|
|
878
|
-
* This pure helper folds the system-bar safe-area insets into the chip's
|
|
879
|
-
* margin so the toast always clears the status bar (top position) /
|
|
880
|
-
* navigation bar (bottom position). It is deterministic and free of
|
|
881
|
-
* native deps so the rules stay unit-testable — the runtime reads the
|
|
882
|
-
* live insets via `readAndroidSafeAreaInsets()` and forwards them here.
|
|
883
|
-
*
|
|
884
|
-
* - 'top': margin.top = baseVerticalInset + safeInsets.top
|
|
885
|
-
* - 'bottom': margin.bottom = baseVerticalInset + safeInsets.bottom
|
|
886
|
-
* - 'center': fixed `centerMargin` all around (vertically centered, so
|
|
887
|
-
* it never approaches the system bars on normal viewports).
|
|
888
|
-
*
|
|
889
|
-
* Horizontal margins always include the relevant safe-area inset so a
|
|
890
|
-
* landscape display cutout / gesture pill doesn't clip the chip edge.
|
|
891
|
-
*/
|
|
892
|
-
export function computeAndroidToastMargin(input) {
|
|
893
|
-
// Clamp to a finite, non-negative number; fall back to `fallback`
|
|
894
|
-
// for missing/NaN input so a glitched measurement can't produce a
|
|
895
|
-
// negative or NaN margin (which NativeScript would silently drop).
|
|
896
|
-
const clamp = (value, fallback) => {
|
|
897
|
-
const n = Number(value);
|
|
898
|
-
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
899
|
-
};
|
|
900
|
-
const safe = {
|
|
901
|
-
top: clamp(input.safeInsets?.top, 0),
|
|
902
|
-
bottom: clamp(input.safeInsets?.bottom, 0),
|
|
903
|
-
left: clamp(input.safeInsets?.left, 0),
|
|
904
|
-
right: clamp(input.safeInsets?.right, 0),
|
|
905
|
-
};
|
|
906
|
-
const baseVerticalInset = clamp(input.baseVerticalInset, 8);
|
|
907
|
-
const baseHorizontalInset = clamp(input.baseHorizontalInset, 16);
|
|
908
|
-
const centerMargin = clamp(input.centerMargin, 24);
|
|
909
|
-
if (input.position === 'center') {
|
|
910
|
-
return { top: centerMargin, right: centerMargin, bottom: centerMargin, left: centerMargin };
|
|
911
|
-
}
|
|
912
|
-
const left = Math.round(baseHorizontalInset + safe.left);
|
|
913
|
-
const right = Math.round(baseHorizontalInset + safe.right);
|
|
914
|
-
if (input.position === 'bottom') {
|
|
915
|
-
return { top: 0, right, bottom: Math.round(baseVerticalInset + safe.bottom), left };
|
|
916
|
-
}
|
|
917
|
-
// 'top' (and any unexpected value) hugs the status-bar safe area.
|
|
918
|
-
return { top: Math.round(baseVerticalInset + safe.top), right, bottom: 0, left };
|
|
919
|
-
}
|
|
920
|
-
/**
|
|
921
|
-
* Returns the iOS UIKit symbols we rely on if we're running on an iOS runtime
|
|
922
|
-
* with the metadata bridge available. Returns null on Android, web, or in
|
|
923
|
-
* tests so callers can gracefully fall back to the in-tree overlay.
|
|
924
|
-
*/
|
|
925
|
-
function getIosOverlayHost() {
|
|
926
|
-
const g = getOverlayGlobal();
|
|
927
|
-
if (typeof g.UIWindow === 'undefined' || typeof g.UIApplication === 'undefined' || typeof g.UIViewController === 'undefined' || typeof g.UIView === 'undefined' || typeof g.UILabel === 'undefined' || typeof g.UIColor === 'undefined' || typeof g.UIFont === 'undefined' || typeof g.UIScreen === 'undefined') {
|
|
928
|
-
return null;
|
|
929
|
-
}
|
|
930
|
-
return {
|
|
931
|
-
UIWindow: g.UIWindow,
|
|
932
|
-
UIViewController: g.UIViewController,
|
|
933
|
-
UIView: g.UIView,
|
|
934
|
-
UILabel: g.UILabel,
|
|
935
|
-
UIColor: g.UIColor,
|
|
936
|
-
UIFont: g.UIFont,
|
|
937
|
-
UIApplication: g.UIApplication,
|
|
938
|
-
UIScreen: g.UIScreen,
|
|
939
|
-
UIWindowLevelAlert: typeof g.UIWindowLevelAlert === 'number' ? g.UIWindowLevelAlert : undefined,
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Walks UIApplication.sharedApplication windows and returns the first active
|
|
944
|
-
* UIWindowScene we can locate. On iOS 13+ every UIWindow is attached to a
|
|
945
|
-
* scene, and we must initialise our overlay window the same way or the OS
|
|
946
|
-
* will silently refuse to render it. Returns null when no scene is found
|
|
947
|
-
* (older iOS versions or non-UI environments).
|
|
948
|
-
*/
|
|
949
|
-
function findActiveWindowScene(host) {
|
|
950
|
-
try {
|
|
951
|
-
const app = host.UIApplication.sharedApplication;
|
|
952
|
-
const windows = app?.windows;
|
|
953
|
-
if (!windows || typeof windows.count !== 'number')
|
|
954
|
-
return null;
|
|
955
|
-
for (let i = 0; i < windows.count; i++) {
|
|
956
|
-
const w = windows.objectAtIndex(i);
|
|
957
|
-
const scene = w && w.windowScene;
|
|
958
|
-
if (scene)
|
|
959
|
-
return scene;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
catch { }
|
|
963
|
-
return null;
|
|
964
|
-
}
|
|
965
|
-
function buildIosOverlayRefs(state) {
|
|
966
|
-
const host = getIosOverlayHost();
|
|
967
|
-
if (!host)
|
|
968
|
-
return null;
|
|
969
|
-
// Without a scene we can't build a modern UIWindow that actually renders.
|
|
970
|
-
// Fall back to the in-tree overlay rather than show nothing.
|
|
971
|
-
const scene = findActiveWindowScene(host);
|
|
972
|
-
if (!scene) {
|
|
973
|
-
if (state.verbose) {
|
|
974
|
-
console.info('[ns-hmr-overlay] no active UIWindowScene; skipping iOS overlay promotion');
|
|
975
|
-
}
|
|
976
|
-
return null;
|
|
977
|
-
}
|
|
978
|
-
try {
|
|
979
|
-
const { UIWindow, UIViewController, UIView, UILabel, UIColor, UIFont } = host;
|
|
980
|
-
const window = UIWindow.alloc().initWithWindowScene(scene);
|
|
981
|
-
window.windowLevel = computeIosOverlayWindowLevel(host.UIWindowLevelAlert ?? null);
|
|
982
|
-
window.backgroundColor = UIColor.clearColor;
|
|
983
|
-
window.hidden = true;
|
|
984
|
-
const controller = UIViewController.new();
|
|
985
|
-
controller.view.backgroundColor = UIColor.clearColor;
|
|
986
|
-
window.rootViewController = controller;
|
|
987
|
-
// UIViewAutoresizing bit masks. We mirror the UIKit constants here to
|
|
988
|
-
// avoid depending on symbols the metadata bridge does not always
|
|
989
|
-
// expose as top-level globals.
|
|
990
|
-
const FLEXIBLE_LEFT_MARGIN = 1 << 0;
|
|
991
|
-
const FLEXIBLE_WIDTH = 1 << 1;
|
|
992
|
-
const FLEXIBLE_RIGHT_MARGIN = 1 << 2;
|
|
993
|
-
const FLEXIBLE_TOP_MARGIN = 1 << 3;
|
|
994
|
-
const FLEXIBLE_HEIGHT = 1 << 4;
|
|
995
|
-
const FLEXIBLE_BOTTOM_MARGIN = 1 << 5;
|
|
996
|
-
const backdrop = UIView.new();
|
|
997
|
-
backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
998
|
-
backdrop.autoresizingMask = FLEXIBLE_WIDTH | FLEXIBLE_HEIGHT;
|
|
999
|
-
controller.view.addSubview(backdrop);
|
|
1000
|
-
const panel = UIView.new();
|
|
1001
|
-
panel.backgroundColor = UIColor.whiteColor;
|
|
1002
|
-
panel.autoresizingMask = FLEXIBLE_LEFT_MARGIN | FLEXIBLE_RIGHT_MARGIN | FLEXIBLE_TOP_MARGIN | FLEXIBLE_BOTTOM_MARGIN;
|
|
1003
|
-
try {
|
|
1004
|
-
panel.layer.cornerRadius = 14;
|
|
1005
|
-
panel.layer.masksToBounds = true;
|
|
1006
|
-
}
|
|
1007
|
-
catch { }
|
|
1008
|
-
controller.view.addSubview(panel);
|
|
1009
|
-
const titleLabel = UILabel.new();
|
|
1010
|
-
titleLabel.numberOfLines = 0;
|
|
1011
|
-
titleLabel.textAlignment = 1; // NSTextAlignmentCenter
|
|
1012
|
-
titleLabel.font = UIFont.boldSystemFontOfSize(16);
|
|
1013
|
-
titleLabel.textColor = UIColor.blackColor;
|
|
1014
|
-
panel.addSubview(titleLabel);
|
|
1015
|
-
const statusLabel = UILabel.new();
|
|
1016
|
-
statusLabel.numberOfLines = 0;
|
|
1017
|
-
statusLabel.textAlignment = 1;
|
|
1018
|
-
statusLabel.font = UIFont.systemFontOfSize(13);
|
|
1019
|
-
statusLabel.textColor = UIColor.darkGrayColor;
|
|
1020
|
-
panel.addSubview(statusLabel);
|
|
1021
|
-
// Subtle drop-shadow so the toast chip reads against light app
|
|
1022
|
-
// content (white-on-white is invisible). The error / centered
|
|
1023
|
-
// branches still get the dim backdrop, so the shadow is mostly
|
|
1024
|
-
// a no-op for them — but it's a one-time setup.
|
|
1025
|
-
try {
|
|
1026
|
-
panel.layer.shadowColor = UIColor.blackColor.CGColor;
|
|
1027
|
-
panel.layer.shadowOpacity = 0.18;
|
|
1028
|
-
panel.layer.shadowRadius = 8;
|
|
1029
|
-
panel.layer.shadowOffset = { width: 0, height: 2 };
|
|
1030
|
-
panel.layer.masksToBounds = false;
|
|
1031
|
-
}
|
|
1032
|
-
catch { }
|
|
1033
|
-
// `wasVisible` / `currentPosition` are mutated by
|
|
1034
|
-
// applySnapshotToIosRefs when the snapshot triggers a slide-in
|
|
1035
|
-
// or slide-out. They start in the "hidden" state so the very
|
|
1036
|
-
// first visible snapshot animates in cleanly.
|
|
1037
|
-
return {
|
|
1038
|
-
window,
|
|
1039
|
-
controller,
|
|
1040
|
-
backdrop,
|
|
1041
|
-
panel,
|
|
1042
|
-
titleLabel,
|
|
1043
|
-
statusLabel,
|
|
1044
|
-
wasVisible: false,
|
|
1045
|
-
currentPosition: getHmrDevOverlayPosition(),
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
catch (err) {
|
|
1049
|
-
console.warn('[ns-hmr-overlay] iOS overlay construction failed:', err?.message || err);
|
|
1050
|
-
return null;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
function ensureIosOverlayRefs(state) {
|
|
1054
|
-
if (state.iosRefs)
|
|
1055
|
-
return state.iosRefs;
|
|
1056
|
-
if (state.iosBuildFailed)
|
|
1057
|
-
return null;
|
|
1058
|
-
const built = buildIosOverlayRefs(state);
|
|
1059
|
-
if (built) {
|
|
1060
|
-
state.iosRefs = built;
|
|
1061
|
-
}
|
|
1062
|
-
else {
|
|
1063
|
-
// Remember failure so we don't hammer construction on every snapshot
|
|
1064
|
-
// update — the in-tree path will take over for this session.
|
|
1065
|
-
state.iosBuildFailed = true;
|
|
1066
|
-
}
|
|
1067
|
-
return state.iosRefs;
|
|
1068
|
-
}
|
|
1069
|
-
function layoutIosOverlayRefs(refs, position) {
|
|
1070
|
-
try {
|
|
1071
|
-
const bounds = refs.controller.view.bounds;
|
|
1072
|
-
const viewWidth = Number(bounds?.size?.width) || 0;
|
|
1073
|
-
const viewHeight = Number(bounds?.size?.height) || 0;
|
|
1074
|
-
const raw = refs.controller.view.safeAreaInsets;
|
|
1075
|
-
const safeInsets = raw
|
|
1076
|
-
? {
|
|
1077
|
-
top: Number(raw.top) || 0,
|
|
1078
|
-
bottom: Number(raw.bottom) || 0,
|
|
1079
|
-
left: Number(raw.left) || 0,
|
|
1080
|
-
right: Number(raw.right) || 0,
|
|
1081
|
-
}
|
|
1082
|
-
: { top: 0, bottom: 0, left: 0, right: 0 };
|
|
1083
|
-
// Ask UIKit what the labels want given the panel inner width. We use a
|
|
1084
|
-
// generous height bound so nothing clips on long reconnect strings.
|
|
1085
|
-
const panelPadding = 16;
|
|
1086
|
-
const horizontalMargin = 24;
|
|
1087
|
-
const maxPanelWidth = 340;
|
|
1088
|
-
const innerWidth = Math.max(0, Math.min(maxPanelWidth, viewWidth - 2 * horizontalMargin - safeInsets.left - safeInsets.right) - 2 * panelPadding);
|
|
1089
|
-
const titleFit = refs.titleLabel.sizeThatFits({ width: innerWidth, height: 10000 }) || { height: 0 };
|
|
1090
|
-
const statusFit = refs.statusLabel.sizeThatFits({ width: innerWidth, height: 10000 }) || { height: 0 };
|
|
1091
|
-
const layout = computeIosOverlayLayout({
|
|
1092
|
-
viewWidth,
|
|
1093
|
-
viewHeight,
|
|
1094
|
-
safeInsets,
|
|
1095
|
-
titleHeight: Number(titleFit.height) || 0,
|
|
1096
|
-
statusHeight: Number(statusFit.height) || 0,
|
|
1097
|
-
maxPanelWidth,
|
|
1098
|
-
horizontalMargin,
|
|
1099
|
-
panelPadding,
|
|
1100
|
-
position,
|
|
1101
|
-
});
|
|
1102
|
-
const toCgRect = (rect) => ({
|
|
1103
|
-
origin: { x: rect.x, y: rect.y },
|
|
1104
|
-
size: { width: rect.width, height: rect.height },
|
|
1105
|
-
});
|
|
1106
|
-
refs.backdrop.frame = toCgRect(layout.backdrop);
|
|
1107
|
-
refs.panel.frame = toCgRect(layout.panel);
|
|
1108
|
-
refs.titleLabel.frame = toCgRect(layout.title);
|
|
1109
|
-
refs.statusLabel.frame = toCgRect(layout.status);
|
|
1110
|
-
return layout;
|
|
1111
|
-
}
|
|
1112
|
-
catch (err) {
|
|
1113
|
-
console.warn('[ns-hmr-overlay] iOS overlay layout failed:', err?.message || err);
|
|
1114
|
-
return null;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Slide-in animation for the iOS toast panel. Off-screen start frame
|
|
1119
|
-
* lives just above (top) or below (bottom) the visible area; the panel
|
|
1120
|
-
* snaps to its target frame with a spring so the motion feels physical
|
|
1121
|
-
* without the heavy "settle" overshoot of a hard spring (damping 0.85
|
|
1122
|
-
* lands quickly with a small overshoot).
|
|
1123
|
-
*/
|
|
1124
|
-
function animateIosPanelIn(refs, position, layout) {
|
|
1125
|
-
const g = getOverlayGlobal();
|
|
1126
|
-
const UIView = g?.UIView;
|
|
1127
|
-
if (!UIView)
|
|
1128
|
-
return;
|
|
1129
|
-
try {
|
|
1130
|
-
const targetFrame = {
|
|
1131
|
-
origin: { x: layout.panel.x, y: layout.panel.y },
|
|
1132
|
-
size: { width: layout.panel.width, height: layout.panel.height },
|
|
1133
|
-
};
|
|
1134
|
-
// Off-screen start: distance includes a small fudge so the
|
|
1135
|
-
// shadow blur tail isn't visible at t=0.
|
|
1136
|
-
const startY = position === 'bottom' ? layout.backdrop.height + 24 : -(layout.panel.height + 24);
|
|
1137
|
-
refs.panel.frame = {
|
|
1138
|
-
origin: { x: layout.panel.x, y: startY },
|
|
1139
|
-
size: { width: layout.panel.width, height: layout.panel.height },
|
|
1140
|
-
};
|
|
1141
|
-
refs.panel.alpha = 0;
|
|
1142
|
-
try {
|
|
1143
|
-
if (typeof UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion === 'function') {
|
|
1144
|
-
UIView.animateWithDurationDelayUsingSpringWithDampingInitialSpringVelocityOptionsAnimationsCompletion(0.42, 0, 0.85, 0.7, 0, () => {
|
|
1145
|
-
refs.panel.frame = targetFrame;
|
|
1146
|
-
refs.panel.alpha = 1;
|
|
1147
|
-
}, null);
|
|
1148
|
-
}
|
|
1149
|
-
else if (typeof UIView.animateWithDurationAnimations === 'function') {
|
|
1150
|
-
UIView.animateWithDurationAnimations(0.32, () => {
|
|
1151
|
-
refs.panel.frame = targetFrame;
|
|
1152
|
-
refs.panel.alpha = 1;
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
else {
|
|
1156
|
-
refs.panel.frame = targetFrame;
|
|
1157
|
-
refs.panel.alpha = 1;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
catch {
|
|
1161
|
-
refs.panel.frame = targetFrame;
|
|
1162
|
-
refs.panel.alpha = 1;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
catch { }
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Slide-out animation for the iOS toast panel. Mirrors animateIosPanelIn:
|
|
1169
|
-
* the panel travels to the nearest off-screen edge while fading out so
|
|
1170
|
-
* the dismissal still feels intentional even on fast HMR cycles.
|
|
1171
|
-
*/
|
|
1172
|
-
function animateIosPanelOut(refs, position, onComplete) {
|
|
1173
|
-
const g = getOverlayGlobal();
|
|
1174
|
-
const UIView = g?.UIView;
|
|
1175
|
-
const currentFrame = refs.panel?.frame;
|
|
1176
|
-
if (!UIView || !currentFrame) {
|
|
1177
|
-
onComplete();
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
try {
|
|
1181
|
-
const bounds = refs.controller?.view?.bounds;
|
|
1182
|
-
const viewHeight = Number(bounds?.size?.height) || 0;
|
|
1183
|
-
const targetY = position === 'bottom' ? viewHeight + 24 : -(Number(currentFrame.size?.height) + 24);
|
|
1184
|
-
const startFrame = currentFrame;
|
|
1185
|
-
const targetFrame = {
|
|
1186
|
-
origin: { x: Number(startFrame.origin?.x) || 0, y: targetY },
|
|
1187
|
-
size: startFrame.size,
|
|
1188
|
-
};
|
|
1189
|
-
try {
|
|
1190
|
-
if (typeof UIView.animateWithDurationDelayOptionsAnimationsCompletion === 'function') {
|
|
1191
|
-
// UIViewAnimationOptionCurveEaseIn = 1 << 16 — accelerate
|
|
1192
|
-
// out so the dismissal doesn't drag on screen.
|
|
1193
|
-
UIView.animateWithDurationDelayOptionsAnimationsCompletion(0.22, 0, 1 << 16, () => {
|
|
1194
|
-
refs.panel.frame = targetFrame;
|
|
1195
|
-
refs.panel.alpha = 0;
|
|
1196
|
-
}, () => onComplete());
|
|
1197
|
-
}
|
|
1198
|
-
else if (typeof UIView.animateWithDurationAnimationsCompletion === 'function') {
|
|
1199
|
-
UIView.animateWithDurationAnimationsCompletion(0.22, () => {
|
|
1200
|
-
refs.panel.frame = targetFrame;
|
|
1201
|
-
refs.panel.alpha = 0;
|
|
1202
|
-
}, () => onComplete());
|
|
1203
|
-
}
|
|
1204
|
-
else {
|
|
1205
|
-
refs.panel.alpha = 0;
|
|
1206
|
-
onComplete();
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
catch {
|
|
1210
|
-
refs.panel.alpha = 0;
|
|
1211
|
-
onComplete();
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
catch {
|
|
1215
|
-
onComplete();
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
function applySnapshotToIosRefs(refs, snapshot) {
|
|
1219
|
-
if (!refs)
|
|
1220
|
-
return false;
|
|
1221
|
-
try {
|
|
1222
|
-
// 'update' mode rides the same dedicated UIWindow as
|
|
1223
|
-
// 'connection' so the HMR apply overlay always stacks above
|
|
1224
|
-
// modals/sheets/system alerts. The window is constructed
|
|
1225
|
-
// lazily (ensureIosOverlayRefs) and reused for the lifetime of
|
|
1226
|
-
// the dev session.
|
|
1227
|
-
const visible = snapshot.visible && (snapshot.mode === 'connection' || snapshot.mode === 'update');
|
|
1228
|
-
const wasVisible = !!refs.wasVisible;
|
|
1229
|
-
const position = getHmrDevOverlayPosition();
|
|
1230
|
-
const previousPosition = refs.currentPosition;
|
|
1231
|
-
const isToast = position !== 'center';
|
|
1232
|
-
// Touches pass through the overlay window in toast mode so
|
|
1233
|
-
// the user can keep tapping the app while the HMR chip is
|
|
1234
|
-
// shown. In centered mode we keep the blocking
|
|
1235
|
-
// behaviour (the dim backdrop is itself a hint to wait).
|
|
1236
|
-
try {
|
|
1237
|
-
refs.window.userInteractionEnabled = !isToast;
|
|
1238
|
-
}
|
|
1239
|
-
catch { }
|
|
1240
|
-
if (!visible) {
|
|
1241
|
-
// Animate out before hiding the window so the dismissal
|
|
1242
|
-
// has a discoverable motion. Only animate when previously
|
|
1243
|
-
// visible and in toast mode — centered modal hides instantly.
|
|
1244
|
-
if (wasVisible && isToast) {
|
|
1245
|
-
animateIosPanelOut(refs, previousPosition, () => {
|
|
1246
|
-
try {
|
|
1247
|
-
refs.window.hidden = true;
|
|
1248
|
-
}
|
|
1249
|
-
catch { }
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
else {
|
|
1253
|
-
refs.window.hidden = true;
|
|
1254
|
-
}
|
|
1255
|
-
refs.wasVisible = false;
|
|
1256
|
-
refs.currentPosition = position;
|
|
1257
|
-
return true;
|
|
1258
|
-
}
|
|
1259
|
-
refs.window.hidden = false;
|
|
1260
|
-
refs.titleLabel.text = snapshot.title || '';
|
|
1261
|
-
refs.statusLabel.text = formatStatusText(snapshot);
|
|
1262
|
-
const host = getIosOverlayHost();
|
|
1263
|
-
if (host) {
|
|
1264
|
-
const { UIColor } = host;
|
|
1265
|
-
const isError = snapshot.tone === 'error';
|
|
1266
|
-
const isSuccess = snapshot.tone === 'success';
|
|
1267
|
-
try {
|
|
1268
|
-
if (isError) {
|
|
1269
|
-
// Red panel + dark red text (existing UX).
|
|
1270
|
-
refs.panel.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(1, 0.96, 0.96, 1);
|
|
1271
|
-
refs.titleLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.7, 0.1, 0.06, 1);
|
|
1272
|
-
refs.statusLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.7, 0.1, 0.06, 0.9);
|
|
1273
|
-
}
|
|
1274
|
-
else if (isSuccess) {
|
|
1275
|
-
// Slightly more saturated green panel + dark-green
|
|
1276
|
-
// text. The previous 0.94/0.99/0.95 background was
|
|
1277
|
-
// nearly indistinguishable from white on most
|
|
1278
|
-
// devices; this bump keeps long detail strings
|
|
1279
|
-
// readable while making the apply event obviously
|
|
1280
|
-
// "happening right now".
|
|
1281
|
-
refs.panel.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0.9, 0.97, 0.91, 1);
|
|
1282
|
-
refs.titleLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.05, 0.43, 0.18, 1);
|
|
1283
|
-
refs.statusLabel.textColor = UIColor.colorWithRedGreenBlueAlpha(0.05, 0.43, 0.18, 1);
|
|
1284
|
-
}
|
|
1285
|
-
else {
|
|
1286
|
-
// Default (info / warn) — existing connection look.
|
|
1287
|
-
refs.panel.backgroundColor = UIColor.whiteColor;
|
|
1288
|
-
refs.titleLabel.textColor = UIColor.blackColor;
|
|
1289
|
-
refs.statusLabel.textColor = UIColor.darkGrayColor;
|
|
1290
|
-
}
|
|
1291
|
-
// Backdrop dims only in centered mode; toast mode keeps
|
|
1292
|
-
// the rest of the app fully visible/usable. Errors get
|
|
1293
|
-
// a slightly stronger dim in centered mode because the
|
|
1294
|
-
// user MUST notice them.
|
|
1295
|
-
if (isToast) {
|
|
1296
|
-
refs.backdrop.backgroundColor = UIColor.clearColor;
|
|
1297
|
-
}
|
|
1298
|
-
else if (isError) {
|
|
1299
|
-
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
1300
|
-
}
|
|
1301
|
-
else if (isSuccess) {
|
|
1302
|
-
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0.15, 0.05, 0.28);
|
|
1303
|
-
}
|
|
1304
|
-
else {
|
|
1305
|
-
refs.backdrop.backgroundColor = UIColor.colorWithRedGreenBlueAlpha(0, 0, 0, 0.35);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
catch { }
|
|
1309
|
-
}
|
|
1310
|
-
const layout = layoutIosOverlayRefs(refs, position);
|
|
1311
|
-
// Slide-in animation only fires on the actual hidden→visible
|
|
1312
|
-
// transition (or on a position swap — e.g. dev toggling top
|
|
1313
|
-
// to bottom mid-cycle). Subsequent updates within the same
|
|
1314
|
-
// visible cycle just refresh text/colours without re-animating.
|
|
1315
|
-
const positionChanged = previousPosition !== position;
|
|
1316
|
-
const justAppeared = !wasVisible || positionChanged;
|
|
1317
|
-
if (justAppeared && isToast && layout) {
|
|
1318
|
-
animateIosPanelIn(refs, position, layout);
|
|
1319
|
-
}
|
|
1320
|
-
else if (justAppeared && !isToast) {
|
|
1321
|
-
// Centered modal: ensure alpha is reset to 1 in case a
|
|
1322
|
-
// previous toast-mode dismissal left it at 0.
|
|
1323
|
-
try {
|
|
1324
|
-
refs.panel.alpha = 1;
|
|
1325
|
-
}
|
|
1326
|
-
catch { }
|
|
1327
|
-
}
|
|
1328
|
-
refs.wasVisible = true;
|
|
1329
|
-
refs.currentPosition = position;
|
|
1330
|
-
return true;
|
|
1331
|
-
}
|
|
1332
|
-
catch (err) {
|
|
1333
|
-
console.warn('[ns-hmr-overlay] iOS overlay apply failed:', err?.message || err);
|
|
1334
|
-
return false;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
37
|
function applyRuntimeSnapshot(snapshot) {
|
|
1338
38
|
const state = getRuntimeState();
|
|
1339
39
|
state.snapshot = snapshot;
|
|
@@ -1534,23 +234,6 @@ function createOverlayApi() {
|
|
|
1534
234
|
},
|
|
1535
235
|
};
|
|
1536
236
|
}
|
|
1537
|
-
// The NS_VITE_PROGRESS_OVERLAY opt-out. Read the build-time define (const-substituted
|
|
1538
|
-
// into served modules) first, then fall back to the globalThis seed, defaulting to
|
|
1539
|
-
// enabled — mirrors the gate used by the per-framework update overlays.
|
|
1540
|
-
function isHmrProgressOverlayEnabled() {
|
|
1541
|
-
try {
|
|
1542
|
-
if (typeof __NS_HMR_PROGRESS_OVERLAY_ENABLED__ === 'boolean')
|
|
1543
|
-
return __NS_HMR_PROGRESS_OVERLAY_ENABLED__;
|
|
1544
|
-
}
|
|
1545
|
-
catch { }
|
|
1546
|
-
try {
|
|
1547
|
-
const seeded = getGlobalScope().__NS_HMR_PROGRESS_OVERLAY_ENABLED__;
|
|
1548
|
-
if (typeof seeded === 'boolean')
|
|
1549
|
-
return seeded;
|
|
1550
|
-
}
|
|
1551
|
-
catch { }
|
|
1552
|
-
return true;
|
|
1553
|
-
}
|
|
1554
237
|
// When the overlay is opted out, every surface (boot / connection / update / pending)
|
|
1555
238
|
// becomes a no-op so nothing is ever drawn. The boot placeholder root and the
|
|
1556
239
|
// `__NS_HMR_BOOT_COMPLETE__` signal are owned by `root-placeholder.ts` and are
|
|
@@ -1581,7 +264,7 @@ export function ensureHmrDevOverlayRuntimeInstalled(verbose) {
|
|
|
1581
264
|
const state = getRuntimeState();
|
|
1582
265
|
state.verbose = state.verbose || !!verbose;
|
|
1583
266
|
if (!g.__NS_HMR_DEV_OVERLAY__) {
|
|
1584
|
-
g.__NS_HMR_DEV_OVERLAY__ =
|
|
267
|
+
g.__NS_HMR_DEV_OVERLAY__ = resolveOverlayEnabled() ? createOverlayApi() : createNoopOverlayApi();
|
|
1585
268
|
}
|
|
1586
269
|
return g.__NS_HMR_DEV_OVERLAY__;
|
|
1587
270
|
}
|