@nativescript/core 9.1.0-next.3 → 9.1.0-next.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.
Files changed (79) hide show
  1. package/accessibility/accessibility-common.d.ts +8 -0
  2. package/accessibility/accessibility-common.js +28 -0
  3. package/accessibility/accessibility-common.js.map +1 -1
  4. package/application/application-common.d.ts +187 -3
  5. package/application/application-common.js +397 -54
  6. package/application/application-common.js.map +1 -1
  7. package/application/application-interfaces.d.ts +59 -1
  8. package/application/application.android.d.ts +21 -0
  9. package/application/application.android.js +189 -7
  10. package/application/application.android.js.map +1 -1
  11. package/application/application.d.ts +121 -66
  12. package/application/application.ios.d.ts +88 -26
  13. package/application/application.ios.js +474 -351
  14. package/application/application.ios.js.map +1 -1
  15. package/application/helpers-common.d.ts +5 -0
  16. package/application/helpers-common.js +23 -1
  17. package/application/helpers-common.js.map +1 -1
  18. package/application/scene-delegate-bridge.d.ts +55 -0
  19. package/application/scene-delegate-bridge.js +122 -0
  20. package/application/scene-delegate-bridge.js.map +1 -0
  21. package/config/config.interface.d.ts +38 -2
  22. package/css/system-classes.d.ts +9 -0
  23. package/css/system-classes.js +23 -0
  24. package/css/system-classes.js.map +1 -1
  25. package/data/observable/index.d.ts +5 -0
  26. package/data/observable/index.js +9 -0
  27. package/data/observable/index.js.map +1 -1
  28. package/global-types.d.ts +39 -33
  29. package/globals/global-utils.d.ts +6 -1
  30. package/globals/global-utils.js +10 -5
  31. package/globals/global-utils.js.map +1 -1
  32. package/index.d.ts +1 -0
  33. package/index.js +1 -0
  34. package/index.js.map +1 -1
  35. package/native-window/index.d.ts +426 -0
  36. package/native-window/index.js +4 -0
  37. package/native-window/index.js.map +1 -0
  38. package/native-window/native-window-common.d.ts +153 -0
  39. package/native-window/native-window-common.js +340 -0
  40. package/native-window/native-window-common.js.map +1 -0
  41. package/native-window/native-window-interfaces.d.ts +186 -0
  42. package/native-window/native-window-interfaces.js +90 -0
  43. package/native-window/native-window-interfaces.js.map +1 -0
  44. package/native-window/native-window.android.d.ts +59 -0
  45. package/native-window/native-window.android.js +175 -0
  46. package/native-window/native-window.android.js.map +1 -0
  47. package/native-window/native-window.ios.d.ts +57 -0
  48. package/native-window/native-window.ios.js +208 -0
  49. package/native-window/native-window.ios.js.map +1 -0
  50. package/native-window/window-base.d.ts +103 -0
  51. package/native-window/window-base.js +78 -0
  52. package/native-window/window-base.js.map +1 -0
  53. package/package.json +1 -1
  54. package/ui/core/view/index.android.js +3 -1
  55. package/ui/core/view/index.android.js.map +1 -1
  56. package/ui/core/view/index.d.ts +25 -0
  57. package/ui/core/view/view-common.d.ts +19 -0
  58. package/ui/core/view/view-common.js +34 -0
  59. package/ui/core/view/view-common.js.map +1 -1
  60. package/ui/core/view-base/index.js +6 -0
  61. package/ui/core/view-base/index.js.map +1 -1
  62. package/ui/frame/frame-common.d.ts +14 -2
  63. package/ui/frame/frame-common.js +17 -5
  64. package/ui/frame/frame-common.js.map +1 -1
  65. package/ui/frame/frame-constants.d.ts +9 -0
  66. package/ui/frame/frame-constants.js +10 -0
  67. package/ui/frame/frame-constants.js.map +1 -0
  68. package/ui/frame/frame-helper-for-android.d.ts +2 -2
  69. package/ui/frame/frame-helper-for-android.js +2 -2
  70. package/ui/frame/frame-helper-for-android.js.map +1 -1
  71. package/ui/frame/frame-interfaces.d.ts +1 -1
  72. package/ui/frame/frame-stack.d.ts +17 -1
  73. package/ui/frame/frame-stack.js +32 -5
  74. package/ui/frame/frame-stack.js.map +1 -1
  75. package/ui/frame/index.android.d.ts +1 -1
  76. package/ui/frame/index.android.js +111 -38
  77. package/ui/frame/index.android.js.map +1 -1
  78. package/ui/frame/index.d.ts +15 -5
  79. package/utils/native-helper.d.ts +2 -2
@@ -3,10 +3,14 @@ import { isEmbedded } from '../ui/embedding';
3
3
  import { IOSHelper } from '../ui/core/view/view-helper';
4
4
  import { getWindow } from '../utils/native-helper';
5
5
  import { SDK_VERSION } from '../utils/constants';
6
- import { ios as iosUtils, dataSerialize } from '../utils/native-helper';
7
- import { ApplicationCommon, SceneEvents } from './application-common';
6
+ import { ios as iosUtils, dataSerialize, dataDeserialize } from '../utils/native-helper';
7
+ import { ApplicationCommon } from './application-common';
8
+ import { deliverShortcutItem, forwardContinueUserActivity, forwardOpenURLContexts, oneShotCompletion } from './scene-delegate-bridge';
8
9
  import { Observable } from '../data/observable';
9
10
  import { Trace } from '../trace';
11
+ import { IOSNativeWindow } from '../native-window/native-window.ios';
12
+ import { NativeWindow } from '../native-window/native-window-common';
13
+ import { NativeWindowEvents } from '../native-window/native-window-interfaces';
10
14
  import { AccessibilityServiceEnabledPropName, CommonA11YServiceEnabledObservable, SharedA11YObservable, a11yServiceClasses, a11yServiceDisabledClass, a11yServiceEnabledClass, fontScaleCategoryClasses, fontScaleExtraLargeCategoryClass, fontScaleExtraSmallCategoryClass, fontScaleMediumCategoryClass, getCurrentA11YServiceClass, getCurrentFontScaleCategory, getCurrentFontScaleClass, getFontScaleCssClasses, setCurrentA11YServiceClass, setCurrentFontScaleCategory, setCurrentFontScaleClass, setFontScaleCssClasses, FontScaleCategory, getClosestValidFontScale, VALID_FONT_SCALES, setFontScale, getFontScale, setInitFontScale, getFontScaleCategory, setInitAccessibilityCssHelper, notifyAccessibilityFocusState, AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait, isA11yEnabled, setA11yEnabled, enforceArray, } from '../accessibility/accessibility-common';
11
15
  import { CoreTypes } from '../core-types';
12
16
  import { getiOSWindow, setA11yUpdatePropertiesCallback, setApplicationPropertiesCallback, setAppMainEntry, setiOSWindow, setRootView, setToggleApplicationEventListenersCallback } from './helpers-common';
@@ -39,6 +43,7 @@ var CADisplayLinkTarget = (function (_super) {
39
43
  return target;
40
44
  };
41
45
  CADisplayLinkTarget.prototype.onDisplayed = function (link) {
46
+ var _a;
42
47
  link.invalidate();
43
48
  var owner = this._owner.deref();
44
49
  if (!owner) {
@@ -50,6 +55,7 @@ var CADisplayLinkTarget = (function (_super) {
50
55
  object: owner,
51
56
  ios: UIApplication.sharedApplication,
52
57
  });
58
+ (_a = owner.primaryWindow) === null || _a === void 0 ? void 0 : _a._notifyEvent(NativeWindowEvents.displayed);
53
59
  owner.displayedLinkTarget = null;
54
60
  owner.displayedLink = null;
55
61
  };
@@ -113,8 +119,28 @@ if (supportsScenes()) {
113
119
  * Detected by the Info.plist existence 'UIApplicationSceneManifest'.
114
120
  * If this method is implemented when there is no manifest defined,
115
121
  * the app will boot to a white screen.
122
+ *
123
+ * Since we configure the delegate dynamically here, UISceneConfigurations
124
+ * does NOT need to be present in Info.plist — only UIApplicationSceneManifest is required.
125
+ *
126
+ * NativeScript only handles UIWindowSceneSessionRoleApplication by default.
127
+ * Other scene types (CarPlay, external displays, etc.) are ignored unless
128
+ * the user provides an `onSceneConfiguration` callback.
116
129
  */
117
130
  Responder.prototype.applicationConfigurationForConnectingSceneSessionOptions = function (application, connectingSceneSession, options) {
131
+ // Let the user intercept scene configuration for any/all scenes
132
+ const userHandler = Application.ios._onSceneConfiguration;
133
+ if (userHandler) {
134
+ const userConfig = userHandler(application, connectingSceneSession, options);
135
+ if (userConfig) {
136
+ return userConfig;
137
+ }
138
+ }
139
+ // Only handle the standard window scene role — skip CarPlay, external displays, etc.
140
+ if (connectingSceneSession.role !== UIWindowSceneSessionRoleApplication) {
141
+ // Return a bare configuration so iOS doesn't crash, but NativeScript won't manage it
142
+ return UISceneConfiguration.configurationWithNameSessionRole('Unmanaged', connectingSceneSession.role);
143
+ }
118
144
  const config = UISceneConfiguration.configurationWithNameSessionRole('Default Configuration', connectingSceneSession.role);
119
145
  config.sceneClass = UIWindowScene;
120
146
  config.delegateClass = SceneDelegate;
@@ -122,10 +148,20 @@ if (supportsScenes()) {
122
148
  };
123
149
  // scene session destruction handling
124
150
  Responder.prototype.applicationDidDiscardSceneSessions = function (application, sceneSessions) {
125
- // Note: we could emit an event here if needed
126
- // console.log('Scene sessions discarded:', sceneSessions.count);
151
+ Application.ios._onSceneSessionsDiscarded(sceneSessions);
127
152
  };
128
153
  }
154
+ /**
155
+ * Reads the payload `openWindow()` put on the activating NSUserActivity.
156
+ */
157
+ function getSceneConnectionData(connectionOptions) {
158
+ const activities = connectionOptions?.userActivities;
159
+ if (!activities || activities.count === 0) {
160
+ return undefined;
161
+ }
162
+ const activity = activities.allObjects.objectAtIndex(0);
163
+ return activity?.userInfo ? dataDeserialize(activity.userInfo) : undefined;
164
+ }
129
165
  var SceneDelegate = (function (_super) {
130
166
  __extends(SceneDelegate, _super);
131
167
  function SceneDelegate() {
@@ -148,48 +184,234 @@ var SceneDelegate = (function (_super) {
148
184
  if (!(scene instanceof UIWindowScene)) {
149
185
  return;
150
186
  }
151
- var isFirstScene = !Application.ios.getPrimaryScene() && !Application.hasLaunched();
152
- this._scene = scene;
153
- this._window = UIWindow.alloc().initWithWindowScene(scene);
154
- Application.ios._setWindowForScene(this._window, scene);
155
- Application.ios._setupWindowForScene(this._window, scene);
187
+ var windowScene = scene;
188
+ var isFirstScene = Application.ios._getWindows().length === 0;
189
+ this._scene = windowScene;
190
+ this._window = UIWindow.alloc().initWithWindowScene(windowScene);
191
+ if (!__VISIONOS__) {
192
+ this._window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
193
+ }
194
+ var nativeWindowId = IOSNativeWindow.getSceneId(windowScene);
195
+ var knownWindow = nativeWindowId ? Application.ios.getWindowById(nativeWindowId) : undefined;
196
+ var nativeWindow;
197
+ if ((knownWindow === null || knownWindow === void 0 ? void 0 : knownWindow.state) === "detached") {
198
+ nativeWindow = knownWindow;
199
+ nativeWindow._reattach(windowScene, this._window);
200
+ }
201
+ else {
202
+ var isPrimary_1 = isFirstScene || !Application.ios.primaryWindow;
203
+ nativeWindow = new IOSNativeWindow(windowScene, this._window, nativeWindowId, isPrimary_1);
204
+ Application.ios._registerWindow(nativeWindow);
205
+ }
206
+ var isPrimary = nativeWindow.isPrimary;
207
+ nativeWindow._notifyEvent(NativeWindowEvents.attached);
208
+ if (isPrimary) {
209
+ setiOSWindow(this._window);
210
+ }
211
+ nativeWindow.notify({
212
+ eventName: NativeWindowEvents.sceneWillConnect,
213
+ object: nativeWindow,
214
+ window: nativeWindow,
215
+ scene: windowScene,
216
+ uiWindow: this._window,
217
+ connectionOptions: connectionOptions,
218
+ });
156
219
  Application.ios.notify({
157
- eventName: SceneEvents.sceneWillConnect,
220
+ eventName: NativeWindowEvents.sceneWillConnect,
158
221
  object: Application.ios,
159
- scene: scene,
160
- window: this._window,
222
+ window: nativeWindow,
223
+ scene: windowScene,
224
+ uiWindow: this._window,
161
225
  connectionOptions: connectionOptions,
162
226
  });
163
- if (scene === Application.ios.getPrimaryScene()) {
227
+ if (isPrimary) {
164
228
  this._window.makeKeyAndVisible();
165
229
  }
166
- else {
167
- Application.ios.notify({
168
- eventName: SceneEvents.sceneContentSetup,
169
- object: Application.ios,
170
- scene: scene,
171
- window: this._window,
172
- connectionOptions: connectionOptions,
230
+ if (nativeWindow.role === "application") {
231
+ Application.ios._resolveWindowContent(nativeWindow, {
232
+ window: nativeWindow,
233
+ isPrimary: isPrimary,
234
+ data: getSceneConnectionData(connectionOptions),
235
+ ios: { connectionOptions: connectionOptions },
173
236
  });
174
237
  }
175
- if (isFirstScene) {
176
- Application.ios._notifySceneAppStarted();
177
- }
178
238
  };
179
239
  SceneDelegate.prototype.sceneDidBecomeActive = function (scene) {
240
+ var windowScene = scene;
241
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
242
+ if (nativeWindow) {
243
+ nativeWindow._notifyEvent(NativeWindowEvents.activate);
244
+ nativeWindow.notify({
245
+ eventName: NativeWindowEvents.sceneDidActivate,
246
+ object: nativeWindow,
247
+ window: nativeWindow,
248
+ scene: windowScene,
249
+ });
250
+ }
251
+ Application.ios.notify({
252
+ eventName: NativeWindowEvents.sceneDidActivate,
253
+ object: Application.ios,
254
+ window: nativeWindow,
255
+ scene: windowScene,
256
+ });
257
+ var rootView = nativeWindow === null || nativeWindow === void 0 ? void 0 : nativeWindow.rootView;
258
+ if (rootView && !rootView.isLoaded) {
259
+ rootView.callLoaded();
260
+ }
180
261
  };
181
262
  SceneDelegate.prototype.sceneWillResignActive = function (scene) {
263
+ var windowScene = scene;
264
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
265
+ if (nativeWindow) {
266
+ nativeWindow._notifyEvent(NativeWindowEvents.deactivate);
267
+ nativeWindow.notify({
268
+ eventName: NativeWindowEvents.sceneWillResignActive,
269
+ object: nativeWindow,
270
+ window: nativeWindow,
271
+ scene: windowScene,
272
+ });
273
+ }
182
274
  Application.ios.notify({
183
- eventName: SceneEvents.sceneWillResignActive,
275
+ eventName: NativeWindowEvents.sceneWillResignActive,
184
276
  object: Application.ios,
185
- scene: scene,
277
+ window: nativeWindow,
278
+ scene: windowScene,
186
279
  });
187
280
  };
188
281
  SceneDelegate.prototype.sceneWillEnterForeground = function (scene) {
282
+ var windowScene = scene;
283
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
284
+ if (nativeWindow) {
285
+ nativeWindow._notifyEvent(NativeWindowEvents.foreground);
286
+ nativeWindow.notify({
287
+ eventName: NativeWindowEvents.sceneWillEnterForeground,
288
+ object: nativeWindow,
289
+ window: nativeWindow,
290
+ scene: windowScene,
291
+ });
292
+ }
293
+ Application.ios.notify({
294
+ eventName: NativeWindowEvents.sceneWillEnterForeground,
295
+ object: Application.ios,
296
+ window: nativeWindow,
297
+ scene: windowScene,
298
+ });
189
299
  };
190
300
  SceneDelegate.prototype.sceneDidEnterBackground = function (scene) {
301
+ var windowScene = scene;
302
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
303
+ if (nativeWindow) {
304
+ nativeWindow._notifyEvent(NativeWindowEvents.background);
305
+ nativeWindow.notify({
306
+ eventName: NativeWindowEvents.sceneDidEnterBackground,
307
+ object: nativeWindow,
308
+ window: nativeWindow,
309
+ scene: windowScene,
310
+ });
311
+ }
312
+ Application.ios.notify({
313
+ eventName: NativeWindowEvents.sceneDidEnterBackground,
314
+ object: Application.ios,
315
+ window: nativeWindow,
316
+ scene: windowScene,
317
+ });
318
+ var rootView = nativeWindow === null || nativeWindow === void 0 ? void 0 : nativeWindow.rootView;
319
+ if (rootView && rootView.isLoaded) {
320
+ rootView.callUnloaded();
321
+ }
191
322
  };
192
323
  SceneDelegate.prototype.sceneDidDisconnect = function (scene) {
324
+ var windowScene = scene;
325
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
326
+ if (nativeWindow) {
327
+ var isClosing = nativeWindow._closeRequested || !nativeWindow._hasSessionIdentity;
328
+ if (isClosing) {
329
+ nativeWindow._notifyEvent(NativeWindowEvents.close);
330
+ }
331
+ nativeWindow.notify({
332
+ eventName: NativeWindowEvents.sceneDidDisconnect,
333
+ object: nativeWindow,
334
+ window: nativeWindow,
335
+ scene: windowScene,
336
+ });
337
+ if (isClosing) {
338
+ Application.ios._unregisterWindow(nativeWindow);
339
+ }
340
+ else {
341
+ nativeWindow._detach();
342
+ }
343
+ }
344
+ Application.ios.notify({
345
+ eventName: NativeWindowEvents.sceneDidDisconnect,
346
+ object: Application.ios,
347
+ window: nativeWindow,
348
+ scene: windowScene,
349
+ });
350
+ };
351
+ SceneDelegate.prototype.sceneOpenURLContexts = function (scene, URLContexts) {
352
+ var windowScene = scene;
353
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
354
+ if (nativeWindow) {
355
+ nativeWindow.notify({
356
+ eventName: NativeWindowEvents.sceneOpenURLContexts,
357
+ object: nativeWindow,
358
+ window: nativeWindow,
359
+ scene: windowScene,
360
+ urlContexts: URLContexts,
361
+ });
362
+ }
363
+ Application.ios.notify({
364
+ eventName: NativeWindowEvents.sceneOpenURLContexts,
365
+ object: Application.ios,
366
+ window: nativeWindow,
367
+ scene: windowScene,
368
+ urlContexts: URLContexts,
369
+ });
370
+ forwardOpenURLContexts(Application.ios.delegate, UIApplication.sharedApplication, URLContexts);
371
+ };
372
+ SceneDelegate.prototype.sceneContinueUserActivity = function (scene, userActivity) {
373
+ var windowScene = scene;
374
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
375
+ if (nativeWindow) {
376
+ nativeWindow.notify({
377
+ eventName: NativeWindowEvents.sceneContinueUserActivity,
378
+ object: nativeWindow,
379
+ window: nativeWindow,
380
+ scene: windowScene,
381
+ userActivity: userActivity,
382
+ });
383
+ }
384
+ Application.ios.notify({
385
+ eventName: NativeWindowEvents.sceneContinueUserActivity,
386
+ object: Application.ios,
387
+ window: nativeWindow,
388
+ scene: windowScene,
389
+ userActivity: userActivity,
390
+ });
391
+ forwardContinueUserActivity(Application.ios.delegate, UIApplication.sharedApplication, userActivity);
392
+ };
393
+ SceneDelegate.prototype.windowScenePerformActionForShortcutItemCompletionHandler = function (windowScene, shortcutItem, completionHandler) {
394
+ var nativeWindow = Application.ios._getWindowForScene(windowScene);
395
+ var deliver = oneShotCompletion(completionHandler);
396
+ if (nativeWindow) {
397
+ nativeWindow.notify({
398
+ eventName: NativeWindowEvents.scenePerformActionForShortcutItem,
399
+ object: nativeWindow,
400
+ window: nativeWindow,
401
+ scene: windowScene,
402
+ shortcutItem: shortcutItem,
403
+ completionHandler: deliver,
404
+ });
405
+ }
406
+ Application.ios.notify({
407
+ eventName: NativeWindowEvents.scenePerformActionForShortcutItem,
408
+ object: Application.ios,
409
+ window: nativeWindow,
410
+ scene: windowScene,
411
+ shortcutItem: shortcutItem,
412
+ completionHandler: deliver,
413
+ });
414
+ deliverShortcutItem(Application.ios.delegate, UIApplication.sharedApplication, shortcutItem, deliver);
193
415
  };
194
416
  SceneDelegate.ObjCProtocols = [UIWindowSceneDelegate];
195
417
  return SceneDelegate;
@@ -203,31 +425,19 @@ export class iOSApplication extends ApplicationCommon {
203
425
  constructor() {
204
426
  super();
205
427
  this._delegateHandlers = new Map();
206
- this.launchEventCalled = false;
207
- this._windowSceneMap = new Map();
208
- this._primaryScene = null;
209
- this._openedScenesById = new Map();
210
428
  this._notificationObservers = [];
211
429
  this._softRebootSceneDelegates = new Map();
212
430
  this.displayedOnce = false;
431
+ /**
432
+ * @deprecated Has no effect. Application initialization is signalled by the 'ready' event, which is never deferred.
433
+ */
213
434
  this.shouldDelayLaunchEvent = false;
214
435
  this.addNotificationObserver(UIApplicationDidFinishLaunchingNotification, this.didFinishLaunchingWithOptions.bind(this));
436
+ this.addNotificationObserver(UIApplicationDidBecomeActiveNotification, this.didBecomeActive.bind(this));
437
+ this.addNotificationObserver(UIApplicationDidEnterBackgroundNotification, this.didEnterBackground.bind(this));
215
438
  this.addNotificationObserver(UIApplicationWillTerminateNotification, this.willTerminate.bind(this));
216
439
  this.addNotificationObserver(UIApplicationDidReceiveMemoryWarningNotification, this.didReceiveMemoryWarning.bind(this));
217
440
  this.addNotificationObserver(UIApplicationDidChangeStatusBarOrientationNotification, this.didChangeStatusBarOrientation.bind(this));
218
- // Add scene lifecycle notification observers only if scenes are supported
219
- if (this.supportsScenes()) {
220
- this.addNotificationObserver('UISceneWillConnectNotification', this.sceneWillConnect.bind(this));
221
- this.addNotificationObserver('UISceneDidActivateNotification', this.sceneDidActivate.bind(this));
222
- this.addNotificationObserver('UISceneWillEnterForegroundNotification', this.sceneWillEnterForeground.bind(this));
223
- this.addNotificationObserver('UISceneDidEnterBackgroundNotification', this.sceneDidEnterBackground.bind(this));
224
- this.addNotificationObserver('UISceneDidDisconnectNotification', this.sceneDidDisconnect.bind(this));
225
- }
226
- else {
227
- // For scene-based apps, the below are not needed as they are handled by the scene notifications
228
- this.addNotificationObserver(UIApplicationDidBecomeActiveNotification, this.didBecomeActive.bind(this));
229
- this.addNotificationObserver(UIApplicationDidEnterBackgroundNotification, this.didEnterBackground.bind(this));
230
- }
231
441
  }
232
442
  getRootView() {
233
443
  return this._rootView;
@@ -290,6 +500,7 @@ export class iOSApplication extends ApplicationCommon {
290
500
  UIApplicationMain(0, null, null, this.delegate ? NSStringFromClass(this.delegate) : NSStringFromClass(Responder));
291
501
  }
292
502
  runAsEmbeddedApp() {
503
+ this.notifyReady();
293
504
  this._reattachNativeDelegatesAfterSoftReboot();
294
505
  // TODO: this rootView should be held alive until rootController dismissViewController is called.
295
506
  const rootView = this.createRootView(this._rootView, true);
@@ -322,8 +533,17 @@ export class iOSApplication extends ApplicationCommon {
322
533
  }
323
534
  if (targetScene) {
324
535
  window = UIWindow.alloc().initWithWindowScene(targetScene);
325
- this._setWindowForScene(window, targetScene);
326
- this._setupWindowForScene?.(window, targetScene);
536
+ if (!__VISIONOS__) {
537
+ window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
538
+ }
539
+ // The registry lives in JS and was lost with the previous isolate, so
540
+ // the still-connected scene needs a fresh NativeWindow to be reachable.
541
+ const isPrimary = !this.primaryWindow;
542
+ const nativeWindow = new IOSNativeWindow(targetScene, window, IOSNativeWindow.getSceneId(targetScene), isPrimary);
543
+ this._registerWindow(nativeWindow);
544
+ if (isPrimary) {
545
+ setiOSWindow(window);
546
+ }
327
547
  // If the scene's delegate was recreated after a soft reboot, point it
328
548
  // at the new window so `scene.delegate.window` queries resolve.
329
549
  const freshSceneDelegate = this._softRebootSceneDelegates.get(targetScene);
@@ -343,31 +563,16 @@ export class iOSApplication extends ApplicationCommon {
343
563
  if (embedderDelegate && !rootController) {
344
564
  return;
345
565
  }
346
- const controller = this.getViewController(rootView);
347
- rootView._setupAsRootView({});
348
- rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => {
349
- const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
350
- const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle);
351
- this.setSystemAppearance(newSystemAppearance);
352
- });
353
- rootView.on(IOSHelper.traitCollectionLayoutDirectionChangedEvent, () => {
354
- const layoutDirection = controller.traitCollection.layoutDirection;
355
- const newLayoutDirection = this.getLayoutDirectionValue(layoutDirection);
356
- this.setLayoutDirection(newLayoutDirection);
357
- });
358
- if (embedderDelegate) {
359
- // Embed into host app.
360
- // present over the host's existing root view controller.
361
- this.setViewControllerView(rootView);
362
- embedderDelegate.presentNativeScriptApp(controller);
363
- }
364
- else {
365
- // No embedder delegate = NativeScript owns the UIApplication.
366
- // Attach the root to the window.
367
- this.setViewControllerView(rootView);
368
- this.setWindowRootView(window, rootView);
566
+ let hostWindow = this.primaryWindow;
567
+ if (!hostWindow) {
568
+ // Only an embedder delegate makes this window a guest: without one NativeScript
569
+ // owns the UIApplication and the window has to attach its own content.
570
+ const role = embedderDelegate ? 'embedded' : 'application';
571
+ hostWindow = new IOSNativeWindow(window.windowScene ?? undefined, window, 'embedded-main', true, role);
572
+ this._registerWindow(hostWindow);
573
+ hostWindow._notifyEvent(NativeWindowEvents.attached);
369
574
  }
370
- this.initRootView(rootView);
575
+ hostWindow.setContent(rootView);
371
576
  this.notifyAppStarted();
372
577
  }
373
578
  /**
@@ -609,7 +814,6 @@ export class iOSApplication extends ApplicationCommon {
609
814
  }
610
815
  }
611
816
  notifyAppStarted(notification) {
612
- this.launchEventCalled = true;
613
817
  const root = this.notifyLaunch({
614
818
  ios: notification?.userInfo?.objectForKey('UIApplicationLaunchOptionsLocalNotificationKey') ?? null,
615
819
  });
@@ -622,10 +826,6 @@ export class iOSApplication extends ApplicationCommon {
622
826
  setiOSWindow(this.window);
623
827
  }
624
828
  }
625
- // Public method for scene-based app startup
626
- _notifySceneAppStarted() {
627
- this.notifyAppStarted();
628
- }
629
829
  _onLivesync(context) {
630
830
  // Handle application root module
631
831
  const isAppRootModuleChanged = context && context.path && context.path.includes(this.getMainEntry().moduleName) && context.type !== 'style';
@@ -640,14 +840,23 @@ export class iOSApplication extends ApplicationCommon {
640
840
  }
641
841
  }
642
842
  setWindowContent(view) {
843
+ const rootView = this.createRootView(view);
844
+ const primaryWindow = this.primaryWindow;
845
+ if (primaryWindow) {
846
+ primaryWindow.setContent(rootView);
847
+ return;
848
+ }
849
+ this.setWindowContentFallback(rootView);
850
+ }
851
+ /**
852
+ * Attaches content to the raw `UIWindow`. Every launch path registers a primary
853
+ * NativeWindow, so this only runs when no window is left to own the content.
854
+ */
855
+ setWindowContentFallback(rootView) {
643
856
  if (this._rootView) {
644
- // if we already have a root view, we reset it.
645
857
  this._rootView._onRootViewReset();
646
858
  }
647
- const rootView = this.createRootView(view);
648
859
  const controller = this.getViewController(rootView);
649
- this._rootView = rootView;
650
- setRootView(rootView);
651
860
  // setup view as styleScopeHost
652
861
  rootView._setupAsRootView({});
653
862
  this.setViewControllerView(rootView);
@@ -657,17 +866,7 @@ export class iOSApplication extends ApplicationCommon {
657
866
  if (!haveController) {
658
867
  win.makeKeyAndVisible();
659
868
  }
660
- this.initRootView(rootView);
661
- rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => {
662
- const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
663
- const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle);
664
- this.setSystemAppearance(newSystemAppearance);
665
- });
666
- rootView.on(IOSHelper.traitCollectionLayoutDirectionChangedEvent, () => {
667
- const layoutDirection = controller.traitCollection.layoutDirection;
668
- const newLayoutDirection = this.getLayoutDirectionValue(layoutDirection);
669
- this.setLayoutDirection(newLayoutDirection);
670
- });
869
+ this.adoptRootView(rootView);
671
870
  }
672
871
  // Observers
673
872
  didFinishLaunchingWithOptions(notification) {
@@ -685,6 +884,8 @@ export class iOSApplication extends ApplicationCommon {
685
884
  }
686
885
  }
687
886
  }
887
+ // Must precede every window registration below and every scene connect that follows.
888
+ this.notifyReady();
688
889
  this.setMaxRefreshRate();
689
890
  // Only set up window if NOT using scene-based lifecycle
690
891
  if (!this.supportsScenes()) {
@@ -698,9 +899,26 @@ export class iOSApplication extends ApplicationCommon {
698
899
  if (!__VISIONOS__) {
699
900
  this.window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
700
901
  }
701
- this.launchEventCalled = false;
702
- if (!this.shouldDelayLaunchEvent) {
703
- this.notifyAppStarted(notification);
902
+ if (!this.primaryWindow) {
903
+ const nativeWindow = new IOSNativeWindow(undefined, this.window, 'main', true, 'application');
904
+ this._registerWindow(nativeWindow);
905
+ nativeWindow._notifyEvent(NativeWindowEvents.attached);
906
+ }
907
+ const primaryWindow = this.primaryWindow;
908
+ const resolveContent = () => this._resolveWindowContent(primaryWindow, {
909
+ window: primaryWindow,
910
+ isPrimary: true,
911
+ }, {
912
+ launchData: {
913
+ ios: notification?.userInfo?.objectForKey('UIApplicationLaunchOptionsLocalNotificationKey') ?? null,
914
+ },
915
+ });
916
+ if (UIApplication.sharedApplication.applicationState === 2 /* UIApplicationState.Background */) {
917
+ // A background launch has no UI to build yet, so content waits for the first activation.
918
+ this._pendingWindowContentResolve = resolveContent;
919
+ }
920
+ else {
921
+ resolveContent();
704
922
  }
705
923
  }
706
924
  else {
@@ -708,17 +926,22 @@ export class iOSApplication extends ApplicationCommon {
708
926
  }
709
927
  }
710
928
  didBecomeActive(notification) {
711
- if (!this.launchEventCalled) {
712
- this.notifyAppStarted(notification);
929
+ const pendingWindowContentResolve = this._pendingWindowContentResolve;
930
+ if (pendingWindowContentResolve) {
931
+ this._pendingWindowContentResolve = null;
932
+ pendingWindowContentResolve();
713
933
  }
714
934
  const additionalData = {
715
935
  ios: UIApplication.sharedApplication,
716
936
  };
717
937
  this.setInBackground(false, additionalData);
718
938
  this.setSuspended(false, additionalData);
719
- const rootView = this._rootView;
720
- if (rootView && !rootView.isLoaded) {
721
- rootView.callLoaded();
939
+ // In scene mode the root view belongs to a window, so the scene delegate loads it.
940
+ if (!this.supportsScenes()) {
941
+ const rootView = this._rootView;
942
+ if (rootView && !rootView.isLoaded) {
943
+ rootView.callLoaded();
944
+ }
722
945
  }
723
946
  }
724
947
  didEnterBackground(notification) {
@@ -727,9 +950,11 @@ export class iOSApplication extends ApplicationCommon {
727
950
  };
728
951
  this.setInBackground(true, additionalData);
729
952
  this.setSuspended(true, additionalData);
730
- const rootView = this._rootView;
731
- if (rootView && rootView.isLoaded) {
732
- rootView.callUnloaded();
953
+ if (!this.supportsScenes()) {
954
+ const rootView = this._rootView;
955
+ if (rootView && rootView.isLoaded) {
956
+ rootView.callUnloaded();
957
+ }
733
958
  }
734
959
  }
735
960
  willTerminate(notification) {
@@ -751,157 +976,111 @@ export class iOSApplication extends ApplicationCommon {
751
976
  });
752
977
  }
753
978
  didChangeStatusBarOrientation(notification) {
754
- const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation;
755
- const newOrientation = this.getOrientationValue(statusBarOrientation);
756
- this.setOrientation(newOrientation);
757
- }
758
- // Scene lifecycle notification handlers
759
- sceneWillConnect(notification) {
760
- const scene = notification.object;
761
- if (!scene || !(scene instanceof UIWindowScene)) {
762
- return;
763
- }
764
- // Store as primary scene if it's the first one
765
- if (!this._primaryScene) {
766
- this._primaryScene = scene;
979
+ // The notification is app-wide, but scenes rotate independently, so every attached
980
+ // window is refreshed from its own scene.
981
+ for (const nativeWindow of this._windows) {
982
+ if (nativeWindow.state !== 'attached') {
983
+ continue;
984
+ }
985
+ const orientation = nativeWindow.ios?.scene?.interfaceOrientation ?? UIApplication.sharedApplication.statusBarOrientation;
986
+ nativeWindow._setOrientation(this.getOrientationValue(orientation));
767
987
  }
768
- this.notify({
769
- eventName: SceneEvents.sceneWillConnect,
770
- object: this,
771
- scene: scene,
772
- userInfo: notification.userInfo,
773
- });
774
988
  }
775
- sceneDidActivate(notification) {
776
- const scene = notification.object;
777
- this.notify({
778
- eventName: SceneEvents.sceneDidActivate,
779
- object: this,
780
- scene: scene,
781
- });
782
- // If this is the primary scene, trigger traditional app lifecycle
783
- if (scene === this._primaryScene) {
784
- const additionalData = {
785
- ios: UIApplication.sharedApplication,
786
- scene: scene,
787
- };
788
- this.setInBackground(false, additionalData);
789
- this.setSuspended(false, additionalData);
790
- if (this._rootView && !this._rootView.isLoaded) {
791
- this._rootView.callLoaded();
792
- }
989
+ // --- App-level root view mirror ---
990
+ /**
991
+ * Keeps the app-level root view state (`getRootView()`, the global root view and the
992
+ * `initRootView` event) following whatever the primary window shows.
993
+ */
994
+ mirrorPrimaryWindow(nativeWindow) {
995
+ if (this._mirroredWindow === nativeWindow) {
996
+ return;
997
+ }
998
+ this._mirroredWindow?.off(NativeWindowEvents.contentLoaded, this.onPrimaryWindowContentLoaded, this);
999
+ this._mirroredWindow = nativeWindow;
1000
+ nativeWindow.on(NativeWindowEvents.contentLoaded, this.onPrimaryWindowContentLoaded, this);
1001
+ if (nativeWindow.rootView && nativeWindow.rootView !== this._rootView) {
1002
+ this.adoptRootView(nativeWindow.rootView);
793
1003
  }
794
1004
  }
795
- sceneWillEnterForeground(notification) {
796
- const scene = notification.object;
797
- this.notify({
798
- eventName: SceneEvents.sceneWillEnterForeground,
799
- object: this,
800
- scene: scene,
801
- });
1005
+ onPrimaryWindowContentLoaded(data) {
1006
+ this.adoptRootView(data.window.rootView);
802
1007
  }
803
- sceneDidEnterBackground(notification) {
804
- const scene = notification.object;
805
- this.notify({
806
- eventName: SceneEvents.sceneDidEnterBackground,
807
- object: this,
808
- scene: scene,
809
- });
810
- // If this is the primary scene, trigger traditional app lifecycle
811
- if (scene === this._primaryScene) {
812
- const additionalData = {
813
- ios: UIApplication.sharedApplication,
814
- scene: scene,
815
- };
816
- this.setInBackground(true, additionalData);
817
- this.setSuspended(true, additionalData);
818
- if (this._rootView && this._rootView.isLoaded) {
819
- this._rootView.callUnloaded();
820
- }
1008
+ adoptRootView(rootView) {
1009
+ if (!rootView) {
1010
+ return;
821
1011
  }
1012
+ this._rootView = rootView;
1013
+ setRootView(rootView);
1014
+ this.initRootView(rootView, this._mirroredWindow);
822
1015
  }
823
- sceneDidDisconnect(notification) {
824
- const scene = notification.object;
825
- this._removeWindowForScene(scene);
826
- // If primary scene disconnected, clear it
827
- if (scene === this._primaryScene) {
828
- this._primaryScene = null;
829
- }
830
- if (this._primaryScene) {
831
- if (SDK_VERSION >= 17) {
832
- const request = UISceneSessionActivationRequest.requestWithSession(this._primaryScene.session);
833
- UIApplication.sharedApplication.activateSceneSessionForRequestErrorHandler(request, (err) => {
834
- if (err) {
835
- console.log('Failed to activate primary scene:', err.localizedDescription);
836
- }
837
- });
838
- }
839
- else {
840
- UIApplication.sharedApplication.requestSceneSessionActivationUserActivityOptionsErrorHandler(this._primaryScene.session, null, null, (err) => {
841
- if (err) {
842
- console.log('Failed to activate primary scene (legacy):', err.localizedDescription);
843
- }
844
- });
845
- }
1016
+ // --- NativeWindow registry ---
1017
+ /**
1018
+ * @internal - iOS reports discarded sessions for windows this JS context may never
1019
+ * have seen (they can arrive on a later launch), so unknown ids are ignored.
1020
+ */
1021
+ _onSceneSessionsDiscarded(sessions) {
1022
+ const all = sessions?.allObjects;
1023
+ if (!all) {
1024
+ return;
846
1025
  }
847
- this.notify({
848
- eventName: SceneEvents.sceneDidDisconnect,
849
- object: this,
850
- scene: scene,
851
- });
852
- }
853
- // Scene management helper methods
854
- _setWindowForScene(window, scene) {
855
- this._windowSceneMap.set(scene, window);
856
- }
857
- _removeWindowForScene(scene) {
858
- this._windowSceneMap.delete(scene);
859
- // also untrack opened scene id
860
- try {
861
- const s = scene;
862
- if (s && s.session) {
863
- const id = this._getSceneId(s);
864
- this._openedScenesById.delete(id);
1026
+ for (let i = 0; i < all.count; i++) {
1027
+ const persistentIdentifier = all.objectAtIndex(i)?.persistentIdentifier;
1028
+ const nativeWindow = persistentIdentifier ? this.getWindowById(`${persistentIdentifier}`) : undefined;
1029
+ if (!nativeWindow) {
1030
+ continue;
865
1031
  }
1032
+ nativeWindow._notifyEvent(NativeWindowEvents.close);
1033
+ this._unregisterWindow(nativeWindow);
866
1034
  }
867
- catch { }
868
1035
  }
1036
+ /**
1037
+ * @internal - Get a NativeWindow by its scene.
1038
+ */
869
1039
  _getWindowForScene(scene) {
870
- return this._windowSceneMap.get(scene);
1040
+ return this._windows.find((nw) => nw.ios?.scene === scene);
871
1041
  }
872
- _setupWindowForScene(window, scene) {
873
- if (!window) {
874
- return;
875
- }
876
- // track opened scene
877
- try {
878
- const id = this._getSceneId(scene);
879
- this._openedScenesById.set(id, scene);
1042
+ _onWindowRegistered(nativeWindow) {
1043
+ if (nativeWindow.isPrimary) {
1044
+ this.mirrorPrimaryWindow(nativeWindow);
880
1045
  }
881
- catch { }
882
- // Set up window background
883
- if (!__VISIONOS__) {
884
- window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
885
- }
886
- // If this is the primary scene, set up the main application content
887
- if (scene === this._primaryScene || !this._primaryScene) {
888
- this._primaryScene = scene;
889
- if (!getiOSWindow()) {
890
- setiOSWindow(window);
891
- }
892
- // During initial scene startup we must wait for launch to be notified first.
893
- // Some frameworks provide root content from launch handlers.
894
- // Guard: skip setWindowContent when no main entry is configured yet.
895
- // During Vite HMR dev boot, the placeholder calls Application.run() with
896
- // no entry; the real entry is set later when the HTTP-loaded main module
897
- // calls Application.run({ moduleName: ... }). Without this guard the
898
- // scene handler would throw "Main entry is missing" and leave the window
899
- // in a broken state (root view reset but no replacement created).
900
- if (this.hasLaunched() && this.getMainEntry()) {
901
- this.setWindowContent();
902
- }
1046
+ }
1047
+ _onPrimaryWindowPromoted(nativeWindow) {
1048
+ const promotedWindow = nativeWindow.ios?.uiWindow;
1049
+ if (promotedWindow) {
1050
+ setiOSWindow(promotedWindow);
903
1051
  }
1052
+ this.mirrorPrimaryWindow(nativeWindow);
904
1053
  }
1054
+ /**
1055
+ * Register a callback to intercept scene configuration.
1056
+ *
1057
+ * Called for every new scene session. Return a `UISceneConfiguration` to handle
1058
+ * the scene yourself (e.g. CarPlay, external display), or return `null`/`undefined`
1059
+ * to let NativeScript handle it with the default SceneDelegate.
1060
+ *
1061
+ * NativeScript only auto-manages `UIWindowSceneSessionRoleApplication` scenes.
1062
+ * All other scene roles are ignored unless you provide a configuration here.
1063
+ *
1064
+ * @example
1065
+ * ```ts
1066
+ * Application.ios.onSceneConfiguration = (app, session, options) => {
1067
+ * if (session.role === CPTemplateApplicationSceneSessionRoleApplication) {
1068
+ * const config = UISceneConfiguration.configurationWithNameSessionRole('CarPlay', session.role);
1069
+ * config.delegateClass = MyCarPlaySceneDelegate;
1070
+ * return config;
1071
+ * }
1072
+ * // Return null to let NativeScript handle the default window scene
1073
+ * return null;
1074
+ * };
1075
+ * ```
1076
+ */
1077
+ set onSceneConfiguration(handler) {
1078
+ this._onSceneConfiguration = handler;
1079
+ }
1080
+ get onSceneConfiguration() {
1081
+ return this._onSceneConfiguration;
1082
+ }
1083
+ // Scene management helper methods (kept for backward compat)
905
1084
  get sceneDelegate() {
906
1085
  if (!this._sceneDelegate) {
907
1086
  this._sceneDelegate = SceneDelegate.new();
@@ -915,10 +1094,12 @@ export class iOSApplication extends ApplicationCommon {
915
1094
  * Multi-window support
916
1095
  */
917
1096
  /**
918
- * Opens a new window with the specified data.
919
- * @param data The data to pass to the new window.
1097
+ * Opens a new window (scene).
1098
+ *
1099
+ * @param options Options for the new window. `options.data` is serialized into the
1100
+ * activating scene's `NSUserActivity.userInfo`.
920
1101
  */
921
- openWindow(data) {
1102
+ openWindow(options) {
922
1103
  if (!supportsMultipleScenes()) {
923
1104
  console.log('Cannot create a new scene - not supported on this device.');
924
1105
  return;
@@ -927,34 +1108,18 @@ export class iOSApplication extends ApplicationCommon {
927
1108
  const app = UIApplication.sharedApplication;
928
1109
  // iOS 17+
929
1110
  if (SDK_VERSION >= 17) {
930
- // Create a new scene activation request with proper role
931
1111
  let request;
932
1112
  try {
933
- // Use the correct factory method to create request with role
934
- // Based on the type definitions, this is the proper way
935
1113
  request = UISceneSessionActivationRequest.requestWithRole(UIWindowSceneSessionRoleApplication);
936
- // Note: may be useful to allow user defined activity type through optional string typed data in future
937
1114
  const activity = NSUserActivity.alloc().initWithActivityType(`${NSBundle.mainBundle.bundleIdentifier}.scene`);
938
- activity.userInfo = dataSerialize(data);
1115
+ activity.userInfo = dataSerialize(options?.data ?? {});
939
1116
  request.userActivity = activity;
940
- // Set proper options with requesting scene
941
- const options = UISceneActivationRequestOptions.new();
942
- // Note: explore secondary windows spawning other windows
943
- // and if this context needs to change in those cases
944
- const mainWindow = Application.ios.getPrimaryWindow();
945
- options.requestingScene = mainWindow?.windowScene;
946
- /**
947
- * Note: This does not work in testing but worth exploring further sometime
948
- * regarding the size/dimensions of opened secondary windows.
949
- * The initial size is ultimately determined by the system
950
- * based on available space and user context.
951
- */
952
- // Get the size restrictions from the window scene
953
- // const sizeRestrictions = (options.requestingScene as UIWindowScene).sizeRestrictions;
954
- // // Set your minimum and maximum dimensions
955
- // sizeRestrictions.minimumSize = CGSizeMake(320, 400);
956
- // sizeRestrictions.maximumSize = CGSizeMake(600, 800);
957
- request.options = options;
1117
+ const activationOptions = UISceneActivationRequestOptions.new();
1118
+ const primary = this.primaryWindow;
1119
+ if (primary?.ios?.scene) {
1120
+ activationOptions.requestingScene = primary.ios.scene;
1121
+ }
1122
+ request.options = activationOptions;
958
1123
  }
959
1124
  catch (roleError) {
960
1125
  console.log('Error creating request:', roleError);
@@ -963,32 +1128,25 @@ export class iOSApplication extends ApplicationCommon {
963
1128
  app.activateSceneSessionForRequestErrorHandler(request, (error) => {
964
1129
  if (error) {
965
1130
  console.log('Error creating new scene (iOS 17+):', error);
966
- // Log additional debugging info
967
1131
  if (error.userInfo) {
968
1132
  console.error(`Error userInfo: ${error.userInfo.description}`);
969
1133
  }
970
- // Handle specific error types
971
1134
  if (error.localizedDescription.includes('role') && error.localizedDescription.includes('nil')) {
972
- this.createSceneWithLegacyAPI(data);
1135
+ this.createSceneWithLegacyAPI(options?.data);
973
1136
  }
974
1137
  else if (error.domain === 'FBSWorkspaceErrorDomain' && error.code === 2) {
975
- this.createSceneWithLegacyAPI(data);
1138
+ this.createSceneWithLegacyAPI(options?.data);
976
1139
  }
977
1140
  }
978
1141
  });
979
1142
  }
980
- // iOS 13-16 - Use the legacy requestSceneSessionActivationUserActivityOptionsErrorHandler method
981
1143
  else if (SDK_VERSION >= 13 && SDK_VERSION < 17) {
982
- app.requestSceneSessionActivationUserActivityOptionsErrorHandler(null, // session
983
- null, // userActivity
984
- null, // options
985
- (error) => {
1144
+ app.requestSceneSessionActivationUserActivityOptionsErrorHandler(null, null, null, (error) => {
986
1145
  if (error) {
987
1146
  console.log('Error creating new scene (legacy):', error);
988
1147
  }
989
1148
  });
990
1149
  }
991
- // Fallback for older iOS versions or unsupported configurations
992
1150
  else {
993
1151
  console.log('Neither new nor legacy scene activation methods are available');
994
1152
  }
@@ -999,71 +1157,66 @@ export class iOSApplication extends ApplicationCommon {
999
1157
  }
1000
1158
  /**
1001
1159
  * Closes a secondary window/scene.
1002
- * Usage examples:
1003
- * - Application.ios.closeWindow() // best-effort close of a non-primary scene
1004
- * - Application.ios.closeWindow(button) // from a tap handler within the scene
1005
- * - Application.ios.closeWindow(window)
1006
- * - Application.ios.closeWindow(scene)
1007
- * - Application.ios.closeWindow('scene-id')
1160
+ * Accepts a NativeWindow, View, UIWindow, UIWindowScene, or string id.
1008
1161
  */
1009
1162
  closeWindow(target) {
1010
1163
  if (!__APPLE__) {
1011
1164
  return;
1012
1165
  }
1013
1166
  try {
1014
- const scene = this._resolveScene(target);
1015
- if (!scene) {
1016
- console.log('closeWindow: No scene resolved for target');
1017
- return;
1167
+ let nativeWindow;
1168
+ if (target instanceof NativeWindow) {
1169
+ nativeWindow = target;
1018
1170
  }
1019
- // Don't allow closing the primary scene
1020
- if (scene === this._primaryScene) {
1021
- console.log('closeWindow: Refusing to close the primary scene');
1022
- return;
1171
+ else {
1172
+ const scene = this._resolveScene(target);
1173
+ if (scene) {
1174
+ nativeWindow = this._getWindowForScene(scene);
1175
+ }
1023
1176
  }
1024
- const session = scene.session;
1025
- if (!session) {
1026
- console.log('closeWindow: Scene has no session to destroy');
1177
+ if (!nativeWindow) {
1178
+ console.log('closeWindow: No window resolved for target');
1027
1179
  return;
1028
1180
  }
1029
- const app = UIApplication.sharedApplication;
1030
- if (app.requestSceneSessionDestructionOptionsErrorHandler) {
1031
- app.requestSceneSessionDestructionOptionsErrorHandler(session, null, (error) => {
1032
- if (error) {
1033
- console.log('closeWindow: destruction error', error);
1034
- }
1035
- else {
1036
- // clean up tracked id
1037
- const id = this._getSceneId(scene);
1038
- this._openedScenesById.delete(id);
1039
- }
1040
- });
1041
- }
1042
- else {
1043
- console.info('closeWindow: Scene destruction API not available on this iOS version');
1044
- }
1181
+ nativeWindow.close();
1045
1182
  }
1046
1183
  catch (err) {
1047
1184
  console.log('closeWindow: Unexpected error', err);
1048
1185
  }
1049
1186
  }
1187
+ /**
1188
+ * @deprecated Use `getWindows()` instead.
1189
+ */
1050
1190
  getAllWindows() {
1051
- return Array.from(this._windowSceneMap.values());
1191
+ return this._windows.map((nw) => nw.ios?.uiWindow).filter(Boolean);
1052
1192
  }
1193
+ /**
1194
+ * @deprecated Use `getWindows()` instead.
1195
+ */
1053
1196
  getAllScenes() {
1054
- return Array.from(this._windowSceneMap.keys());
1197
+ return this._windows.map((nw) => nw.ios?.scene).filter(Boolean);
1055
1198
  }
1199
+ /**
1200
+ * @deprecated Use `getWindows()` instead.
1201
+ */
1056
1202
  getWindowScenes() {
1057
1203
  return this.getAllScenes().filter((scene) => scene instanceof UIWindowScene);
1058
1204
  }
1205
+ /**
1206
+ * @deprecated Use `primaryWindow?.ios?.uiWindow` instead.
1207
+ */
1059
1208
  getPrimaryWindow() {
1060
- if (this._primaryScene) {
1061
- return this._getWindowForScene(this._primaryScene) || getiOSWindow();
1209
+ const primary = this.primaryWindow;
1210
+ if (primary?.ios?.uiWindow) {
1211
+ return primary.ios.uiWindow;
1062
1212
  }
1063
1213
  return getiOSWindow();
1064
1214
  }
1215
+ /**
1216
+ * @deprecated Use `primaryWindow?.ios?.scene` instead.
1217
+ */
1065
1218
  getPrimaryScene() {
1066
- return this._primaryScene;
1219
+ return this.primaryWindow?.ios?.scene || null;
1067
1220
  }
1068
1221
  // Scene lifecycle management
1069
1222
  supportsScenes() {
@@ -1073,7 +1226,7 @@ export class iOSApplication extends ApplicationCommon {
1073
1226
  return supportsMultipleScenes();
1074
1227
  }
1075
1228
  isUsingSceneLifecycle() {
1076
- return this.supportsScenes() && this._windowSceneMap.size > 0;
1229
+ return this.supportsScenes() && this._windows.length > 0;
1077
1230
  }
1078
1231
  // Call this to set up scene-based configuration
1079
1232
  configureForScenes() {
@@ -1081,34 +1234,6 @@ export class iOSApplication extends ApplicationCommon {
1081
1234
  console.warn('Scene-based lifecycle is only supported on iOS 13+ iPad or visionOS with multi-scene enabled apps.');
1082
1235
  return;
1083
1236
  }
1084
- // Additional scene configuration can be added here
1085
- // For now, the notification observers are already set up in the constructor
1086
- }
1087
- // Stable scene id for lookups
1088
- _getSceneId(scene) {
1089
- try {
1090
- if (!scene) {
1091
- return 'Unknown';
1092
- }
1093
- // Prefer session persistentIdentifier when available (stable across lifetime)
1094
- const session = scene.session;
1095
- const persistentId = session && session.persistentIdentifier;
1096
- if (persistentId) {
1097
- return `${persistentId}`;
1098
- }
1099
- // Fallbacks
1100
- if (scene.hash != null) {
1101
- return `${scene.hash}`;
1102
- }
1103
- const desc = scene.description;
1104
- if (desc) {
1105
- return `${desc}`;
1106
- }
1107
- }
1108
- catch (err) {
1109
- // ignore
1110
- }
1111
- return 'Unknown';
1112
1237
  }
1113
1238
  // Resolve a UIWindowScene from various input types
1114
1239
  _resolveScene(target) {
@@ -1116,12 +1241,10 @@ export class iOSApplication extends ApplicationCommon {
1116
1241
  return null;
1117
1242
  }
1118
1243
  if (!target) {
1119
- // Try to pick a non-primary foreground active scene, else last known scene
1120
- const scenes = this.getWindowScenes?.() || [];
1121
- const nonPrimary = scenes.filter((s) => s !== this._primaryScene);
1122
- return nonPrimary[0] || scenes[0] || null;
1244
+ // Try to pick a non-primary window's scene
1245
+ const nonPrimary = this._windows.filter((nw) => !nw.isPrimary);
1246
+ return nonPrimary[0]?.ios?.scene || this.primaryWindow?.ios?.scene || null;
1123
1247
  }
1124
- // If a View was passed, derive its window.scene
1125
1248
  if (target && typeof target === 'object') {
1126
1249
  // UIWindowScene
1127
1250
  if (target.session && target.activationState !== undefined) {
@@ -1140,15 +1263,15 @@ export class iOSApplication extends ApplicationCommon {
1140
1263
  }
1141
1264
  // String id lookup
1142
1265
  if (typeof target === 'string') {
1143
- if (this._openedScenesById.has(target)) {
1144
- return this._openedScenesById.get(target);
1266
+ const found = this.getWindowById(target);
1267
+ if (found) {
1268
+ return found.ios?.scene || null;
1145
1269
  }
1146
- // Try matching by persistentIdentifier or hash among known scenes
1147
- const scenes = this.getWindowScenes?.() || [];
1148
- for (const s of scenes) {
1149
- const sid = this._getSceneId(s);
1150
- if (sid === target) {
1151
- return s;
1270
+ // Try matching among known scenes
1271
+ for (const nw of this._windows) {
1272
+ const scene = nw.ios?.scene;
1273
+ if (scene && IOSNativeWindow.getSceneId(scene) === target) {
1274
+ return scene;
1152
1275
  }
1153
1276
  }
1154
1277
  }
@@ -1161,7 +1284,7 @@ export class iOSApplication extends ApplicationCommon {
1161
1284
  }
1162
1285
  // Create user activity for the new scene
1163
1286
  const userActivity = NSUserActivity.alloc().initWithActivityType(`${NSBundle.mainBundle.bundleIdentifier}.scene`);
1164
- userActivity.userInfo = dataSerialize(data);
1287
+ userActivity.userInfo = dataSerialize(data ?? {});
1165
1288
  // Use the legacy API
1166
1289
  const options = UISceneActivationRequestOptions.new();
1167
1290
  options.requestingScene = windowScene;