Merge remote-tracking branch 'origin/refactor/circular-deps' into feat/list-view-sticky-headers

This commit is contained in:
Nathan Walker
2025-09-01 15:38:51 -07:00
178 changed files with 28645 additions and 15817 deletions

View File

@@ -15,6 +15,7 @@ import type { ApplicationEventData, CssChangedEventData, DiscardedErrorEventData
import { readyInitAccessibilityCssHelper, readyInitFontScale } from '../accessibility/accessibility-common';
import { getAppMainEntry, isAppInBackground, setAppInBackground, setAppMainEntry } from './helpers-common';
import { getNativeScriptGlobals } from '../globals/global-utils';
import { SDK_VERSION } from '../utils/constants';
// prettier-ignore
const ORIENTATION_CSS_CLASSES = [
@@ -29,6 +30,54 @@ const SYSTEM_APPEARANCE_CSS_CLASSES = [
`${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.dark}`,
];
// SDK Version CSS classes
let sdkVersionClasses: string[] = [];
export function initializeSdkVersionClass(rootView: View): void {
const majorVersion = Math.floor(SDK_VERSION);
sdkVersionClasses = [];
let platformPrefix = '';
if (__APPLE__) {
platformPrefix = __VISIONOS__ ? 'ns-visionos' : 'ns-ios';
} else if (__ANDROID__) {
platformPrefix = 'ns-android';
}
if (platformPrefix) {
// Add exact version class (e.g., .ns-ios-26 or .ns-android-36)
// this acts like 'gte' for that major version range
// e.g., if user wants iOS 27, they can add .ns-ios-27 specifiers
sdkVersionClasses.push(`${platformPrefix}-${majorVersion}`);
}
// Apply the SDK version classes to root views
applySdkVersionClass(rootView);
}
function applySdkVersionClass(rootView: View): void {
if (!sdkVersionClasses.length) {
return;
}
if (!rootView) {
return;
}
// Batch apply all SDK version classes to root view for better performance
const classesToAdd = sdkVersionClasses.filter((className) => !rootView.cssClasses.has(className));
classesToAdd.forEach((className) => rootView.cssClasses.add(className));
// Apply to modal views only if there are any
const rootModalViews = <Array<View>>rootView._getRootModalViews();
if (rootModalViews.length > 0) {
rootModalViews.forEach((rootModalView) => {
const modalClassesToAdd = sdkVersionClasses.filter((className) => !rootModalView.cssClasses.has(className));
modalClassesToAdd.forEach((className) => rootModalView.cssClasses.add(className));
});
}
}
const globalEvents = getNativeScriptGlobals().events;
// helper interface to correctly type Application event handlers

View File

@@ -3,7 +3,7 @@ import type { View } from '../ui/core/view';
import { AndroidActivityCallbacks, NavigationEntry } from '../ui/frame/frame-common';
import { SDK_VERSION } from '../utils/constants';
import { android as androidUtils } from '../utils';
import { ApplicationCommon } from './application-common';
import { ApplicationCommon, initializeSdkVersionClass } from './application-common';
import type { AndroidActivityBundleEventData, AndroidActivityEventData, ApplicationEventData } from './application-interfaces';
import { Observable } from '../data/observable';
import { Trace } from '../trace';
@@ -715,6 +715,9 @@ export function ensureClasses() {
setFontScaleCssClasses(new Map(VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`])));
accessibilityServiceObservable = new AccessibilityServiceEnabledObservable();
// Initialize SDK version CSS class once
initializeSdkVersionClass(Application.getRootView());
}
export function updateCurrentHelperClasses(applyRootCssClass: (cssClasses: string[], newCssClass: string) => void): void {

View File

@@ -6,7 +6,7 @@ import type { NavigationEntry } from '../ui/frame/frame-interfaces';
import { getWindow } from '../utils/native-helper';
import { SDK_VERSION } from '../utils/constants';
import { ios as iosUtils } from '../utils/native-helper';
import { ApplicationCommon } from './application-common';
import { ApplicationCommon, initializeSdkVersionClass } from './application-common';
import { ApplicationEventData } from './application-interfaces';
import { Observable } from '../data/observable';
import { Trace } from '../trace';
@@ -441,6 +441,20 @@ export class iOSApplication extends ApplicationCommon {
// Observers
@profile
private didFinishLaunchingWithOptions(notification: NSNotification) {
if (__DEV__) {
/**
* v9+ runtime crash handling
* When crash occurs during boot, we let runtime take over
*/
if (notification.userInfo) {
const isBootCrash = notification.userInfo.objectForKey('NativeScriptBootCrash');
if (isBootCrash) {
// fatal crash will show in console without app exiting
// allowing hot reload fixes to continue
return;
}
}
}
this.setMaxRefreshRate();
// ensures window is assigned to proper window scene
setiOSWindow(this.window);
@@ -935,6 +949,9 @@ export function ensureClasses() {
setFontScaleCssClasses(new Map(VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`])));
accessibilityServiceObservable = new AccessibilityServiceEnabledObservable();
// Initialize SDK version CSS class once
initializeSdkVersionClass(Application.getRootView());
}
export function updateCurrentHelperClasses(applyRootCssClass: (cssClasses: string[], newCssClass: string) => void): void {

View File

@@ -1,6 +1,6 @@
import { Observable } from '../data/observable';
import { trace as profilingTrace, time, uptime, level as profilingLevel } from '../profiling';
console.log('here in globals/global-utils!');
// console.log('here in globals/global-utils!');
/**
* Manages internal framework global state

View File

@@ -10,11 +10,11 @@ import * as wgc from '../wgc';
import * as cryptoImpl from '../wgc/crypto';
import * as subtleCryptoImpl from '../wgc/crypto/SubtleCrypto';
console.log('here in globals/index!');
console.log(`typeof __dirname:`, typeof __dirname);
// console.log('here in globals/index!');
// console.log(`typeof __dirname:`, typeof __dirname);
// commonjs builds will have __dirname defined, but esm builds will not
global.__dirname = typeof __dirname !== 'undefined' ? __dirname : import.meta.dirname;
console.log('global.__dirname', global.__dirname);
// console.log('global.__dirname', global.__dirname);
if (typeof global.__metadata === 'undefined') {
/**
@@ -243,7 +243,7 @@ global.Experimental = function (target: Object, key?: string | symbol, descripto
};
const modules: Map<string, { moduleId: string; loader: ModuleLoader }> = new Map<string, { moduleId: string; loader: ModuleLoader }>();
console.log(`globals/index, __COMMONJS__:`, __COMMONJS__);
// console.log(`globals/index, __COMMONJS__:`, __COMMONJS__);
global.loadModule = function loadModule(name: string): any {
const moduleInfo = modules.get(name);
if (moduleInfo) {
@@ -265,9 +265,6 @@ global.loadModule = function loadModule(name: string): any {
}
}
}
// If no resolver found the module, return null
console.warn(`Module '${name}' could not be loaded by any resolver.`);
return null;
};
function registerOnGlobalContext(moduleName: string, exportName: string): void {
@@ -301,7 +298,7 @@ export function installPolyfills(moduleName: string, exportNames: string[]) {
if (!global.NativeScriptHasPolyfilled) {
global.NativeScriptHasPolyfilled = true;
console.log('Installing polyfills...');
// console.log('Installing polyfills...');
// DOM api polyfills
const glb = global as any;

View File

@@ -13,7 +13,9 @@ import './debugger/webinspector-css';
* NOTE: This likely should not be in core because errors can happen on boot before core is fully loaded. Ideally the runtime should provide this in full but unsure.
*/
import { File, knownFolders } from './file-system';
import { SourceMapConsumer } from 'source-map-js';
// import/destructure style helps commonjs/esm build issues
import * as sourceMapJs from 'source-map-js';
const { SourceMapConsumer } = sourceMapJs;
// note: webpack config can by default use 'source-map' files with runtimes v9+
// helps avoid having to decode the inline base64 source maps
@@ -22,7 +24,7 @@ const usingSourceMapFiles = true;
let loadedSourceMaps: Map<string, any>;
let consumerCache: Map<string, any>;
function getConsumer(mapPath: string, sourceMap: any): SourceMapConsumer {
function getConsumer(mapPath: string, sourceMap: any) {
if (!consumerCache) {
consumerCache = new Map();
}
@@ -114,7 +116,7 @@ function remapStack(raw: string): string {
* Allows the runtime to remap stack traces before displaying them in the in-flight error screens.
*/
(global as any).__ns_remapStack = (rawStack: string) => {
console.log('Remapping stack trace...');
// console.log('Remapping stack trace...');
return remapStack(rawStack);
};
/**

View File

@@ -1,6 +1,6 @@
{
"name": "@nativescript/core",
"version": "9.0.0-alpha.8",
"version": "9.0.0-alpha.13",
"description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.",
"type": "module",
"main": "index",
@@ -15,6 +15,7 @@
"**/*.d.ts",
"**/*.js",
"**/*.mjs",
"**/*.cjs",
"**/*.map",
"**/platforms/ios/**",
"platforms/android/**",
@@ -56,17 +57,17 @@
"preuninstall": "node cli-hooks/preuninstall.mjs"
},
"dependencies": {
"@csstools/css-calc": "~2.1.2",
"@csstools/css-color-parser": "^3.0.8",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"@nativescript/hook": "~3.0.1",
"acorn": "^8.7.0",
"css-tree": "^1.1.2",
"css-what": "^6.1.0",
"@csstools/css-calc": "~2.1.4",
"@csstools/css-color-parser": "^3.0.10",
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4",
"@nativescript/hook": "~3.0.4",
"acorn": "^8.15.0",
"css-tree": "^3.1.0",
"css-what": "^7.0.0",
"emoji-regex": "^10.2.1",
"source-map": "0.6.1",
"source-map-js": "^1.2.0",
"source-map": "0.7.6",
"source-map-js": "^1.2.1",
"tslib": "^2.0.0"
},
"nativescript": {

View File

@@ -58,23 +58,35 @@ export function clearTimeout(id: number): void {
}
export function setInterval(callback: Function, milliseconds = 0, ...args): number {
// Cast to Number
milliseconds += 0;
const id = createHandlerAndGetId();
const handler = timeoutHandler;
const invoke = () => callback(...args);
const zoneBound = zonedCallback(invoke);
const startOffset = milliseconds > 0 ? Date.now() % milliseconds : 0;
function nextCallMs() {
return milliseconds > 0 ? milliseconds - ((Date.now() - startOffset) % milliseconds) : milliseconds;
}
let nextDueTime = Date.now() + milliseconds;
const runnable = new java.lang.Runnable({
run: () => {
const executionStart = Date.now();
zoneBound();
if (timeoutCallbacks[id]) {
handler.postDelayed(runnable, long(nextCallMs()));
const executionTime = Date.now() - executionStart;
// Update the next due time based on when this callback was supposed to execute
nextDueTime += milliseconds;
// If the callback took longer than the interval, skip ahead to avoid catch-up
const now = Date.now();
if (nextDueTime <= now) {
// Calculate how many intervals we should skip
const missedIntervals = Math.floor((now - nextDueTime) / milliseconds);
nextDueTime += (missedIntervals + 1) * milliseconds;
}
const delay = Math.max(0, nextDueTime - now);
handler.postDelayed(runnable, long(delay));
}
},
});
@@ -84,8 +96,7 @@ export function setInterval(callback: Function, milliseconds = 0, ...args): numb
timeoutCallbacksCb[id] = callback;
}
timeoutHandler.postDelayed(runnable, long(nextCallMs()));
handler.postDelayed(runnable, long(milliseconds));
return id;
}

View File

@@ -303,6 +303,9 @@ export class ActionBar extends ActionBarBase {
navigationItem.accessibilityLabel = this.accessibilityLabel;
navigationItem.accessibilityLanguage = this.accessibilityLanguage;
navigationItem.accessibilityHint = this.accessibilityHint;
// Configure large title support for this navigation item
this.checkLargeTitleSupport(navigationItem);
}
private populateMenuItems(navigationItem: UINavigationItem) {
@@ -506,6 +509,8 @@ export class ActionBar extends ActionBarBase {
return;
}
console.log('ActionBar._onTitlePropertyChanged', this.title);
if (page.frame) {
page.frame._updateActionBar(page);
}
@@ -669,4 +674,26 @@ export class ActionBar extends ActionBarBase {
this.navBar.prefersLargeTitles = value;
}
}
private checkLargeTitleSupport(navigationItem: UINavigationItem) {
const navBar = this.navBar;
if (!navBar) {
return;
}
// Configure large title display mode only when not using a custom titleView
if (SDK_VERSION >= 11) {
if (this.iosLargeTitle) {
// Always show large title for this navigation item when large titles are enabled
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
} else {
if (SDK_VERSION >= 26) {
// Explicitly disable large titles for this navigation item
// Due to overlapping title issue in iOS 26
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Never;
} else {
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Automatic;
}
}
}
}
}

View File

@@ -1,10 +1,11 @@
import type { Point, Position } from './view-interfaces';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant } from './view-common';
import { isAccessibilityServiceEnabled } from '../../../application';
import { updateA11yPropertiesCallback } from '../../../application/helpers-common';
import { ShowModalOptions, hiddenProperty } from '../view-base';
import { Trace } from '../../../trace';
import { layout, ios as iosUtils, SDK_VERSION, getWindow } from '../../../utils';
import { layout, ios as iosUtils, getWindow } from '../../../utils';
import { SDK_VERSION, supportsGlass } from '../../../utils/constants';
import { IOSHelper } from './view-helper';
import { ios as iosBackground, Background } from '../../styling/background';
import { perspectiveProperty, visibilityProperty, opacityProperty, rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty } from '../../styling/style-properties';
@@ -15,6 +16,7 @@ import { CoreTypes } from '../../../core-types';
import type { ModalTransition } from '../../transition/modal-transition';
import { SharedTransition } from '../../transition/shared-transition';
import { NativeScriptUIView } from '../../utils';
import { Color } from '../../../color';
export * from './view-common';
export * from './view-helper';
@@ -51,6 +53,12 @@ export class View extends ViewCommon {
*/
_nativeBackgroundState: 'unset' | 'invalid' | 'drawn';
/**
* Glass effect configuration
*/
private _glassEffectView: UIVisualEffectView;
private _glassEffectMeasure: NodeJS.Timeout;
get isLayoutRequired(): boolean {
return (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
}
@@ -887,6 +895,60 @@ export class View extends ViewCommon {
}
}
[iosGlassEffectProperty.setNative](value: GlassEffectType) {
if (!this.nativeViewProtected || !supportsGlass()) {
return;
}
if (this._glassEffectView) {
this._glassEffectView.removeFromSuperview();
this._glassEffectView = null;
}
if (!value) {
return;
}
let effect: UIGlassEffect;
if (typeof value === 'string') {
effect = UIGlassEffect.effectWithStyle(this.toUIGlassStyle(value));
} else {
if (value.variant === 'identity') {
return;
}
effect = UIGlassEffect.effectWithStyle(this.toUIGlassStyle(value.variant));
if (value.interactive) {
effect.interactive = true;
}
if (value.tint) {
effect.tintColor = typeof value.tint === 'string' ? new Color(value.tint).ios : value.tint;
}
}
this._glassEffectView = UIVisualEffectView.alloc().initWithEffect(effect);
// let touches pass to content
this._glassEffectView.userInteractionEnabled = false;
this._glassEffectView.clipsToBounds = true;
// size & autoresize
if (this._glassEffectMeasure) {
clearTimeout(this._glassEffectMeasure);
}
this._glassEffectMeasure = setTimeout(() => {
const size = this.nativeViewProtected.bounds.size;
this._glassEffectView.frame = CGRectMake(0, 0, size.width, size.height);
this._glassEffectView.autoresizingMask = 2;
this.nativeViewProtected.insertSubviewAtIndex(this._glassEffectView, 0);
});
}
public toUIGlassStyle(value?: GlassEffectVariant) {
if (supportsGlass()) {
switch (value) {
case 'regular':
return UIGlassEffectStyle?.Regular ?? 0;
case 'clear':
return UIGlassEffectStyle?.Clear ?? 1;
}
}
return 1;
}
public sendAccessibilityEvent(options: Partial<AccessibilityEventOptions>): void {
if (!isAccessibilityServiceEnabled()) {
return;

View File

@@ -1288,6 +1288,7 @@ export const isUserInteractionEnabledProperty = new Property<ViewCommon, boolean
});
isUserInteractionEnabledProperty.register(ViewCommon);
// Apple only
export const iosOverflowSafeAreaProperty = new Property<ViewCommon, boolean>({
name: 'iosOverflowSafeArea',
defaultValue: false,
@@ -1308,6 +1309,17 @@ export const iosIgnoreSafeAreaProperty = new InheritedProperty({
});
iosIgnoreSafeAreaProperty.register(ViewCommon);
/**
* Glass effects
*/
export type GlassEffectVariant = 'regular' | 'clear' | 'identity';
export type GlassEffectConfig = { variant?: GlassEffectVariant; interactive?: boolean; tint: string | Color };
export type GlassEffectType = GlassEffectVariant | GlassEffectConfig;
export const iosGlassEffectProperty = new Property<ViewCommon, GlassEffectType>({
name: 'iosGlassEffect',
});
iosGlassEffectProperty.register(ViewCommon);
export const visionHoverStyleProperty = new Property<ViewCommon, string | VisionHoverOptions>({
name: 'visionHoverStyle',
valueChanged(view, oldValue, newValue) {
@@ -1324,6 +1336,7 @@ const visionIgnoreHoverStyleProperty = new Property<ViewCommon, boolean>({
valueConverter: booleanConverter,
});
visionIgnoreHoverStyleProperty.register(ViewCommon);
// Apple only end
const touchAnimationProperty = new Property<ViewCommon, boolean | TouchAnimationOptions>({
name: 'touchAnimation',

View File

@@ -9,15 +9,6 @@ export enum NavigationType {
replace,
}
export interface TransitionState {
enterTransitionListener: any;
exitTransitionListener: any;
reenterTransitionListener: any;
returnTransitionListener: any;
transitionName: string;
entry: BackstackEntry;
}
export interface ViewEntry {
moduleName?: string;
create?: () => View;
@@ -34,6 +25,17 @@ export interface NavigationEntry extends ViewEntry {
clearHistory?: boolean;
}
export interface BackstackEntry {
entry: NavigationEntry;
resolvedPage: Page;
navDepth: number;
fragmentTag: string;
fragment?: any;
viewSavedState?: any;
frameId?: number;
recreated?: boolean;
}
export interface NavigationContext {
entry?: BackstackEntry;
/**
@@ -91,6 +93,15 @@ export interface NavigationData extends EventData {
isBack?: boolean;
}
export interface TransitionState {
enterTransitionListener: any;
exitTransitionListener: any;
reenterTransitionListener: any;
returnTransitionListener: any;
transitionName: string;
entry: BackstackEntry;
}
export interface AndroidFrame extends Observable {
rootViewGroup: any /* android.view.ViewGroup */;
activity: any /* androidx.appcompat.app.AppCompatActivity */;

View File

@@ -16,7 +16,7 @@ export { ControlStateChangeListener } from './core/control-state-change';
export { ViewBase, eachDescendant, getAncestor, getViewById, booleanConverter, querySelectorAll } from './core/view-base';
export type { ShowModalOptions } from './core/view-base';
export { View, CSSType, ContainerView, ViewHelper, AndroidHelper, IOSHelper, isUserInteractionEnabledProperty, PseudoClassHandler, CustomLayoutView } from './core/view';
export type { Template, KeyedTemplate, AddArrayFromBuilder, AddChildFromBuilder } from './core/view';
export type { Template, KeyedTemplate, AddArrayFromBuilder, AddChildFromBuilder, GlassEffectConfig, GlassEffectType, GlassEffectVariant } from './core/view';
export type { ShownModallyData, Size } from './core/view/view-interfaces';
export { Property, CoercibleProperty, InheritedProperty, CssProperty, InheritedCssProperty, ShorthandProperty, CssAnimationProperty, makeParser, makeValidator } from './core/properties';
export { unsetValue } from './core/properties/property-shared';

View File

@@ -1,7 +1,6 @@
import { SwitchBase, checkedProperty, offBackgroundColorProperty } from './switch-common';
import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties';
import { Color } from '../../color';
import { layout } from '../../utils';
import { SDK_VERSION } from '../../utils/constants';
export * from './switch-common';
@@ -29,15 +28,12 @@ class SwitchChangeHandlerImpl extends NSObject {
};
}
const zeroSize = { width: 0, height: 0 };
export class Switch extends SwitchBase {
nativeViewProtected: UISwitch;
private _handler: NSObject;
constructor() {
super();
this.width = 51;
this.height = 31;
}
public createNativeView() {
@@ -92,17 +88,6 @@ export class Switch extends SwitchBase {
return this.nativeViewProtected;
}
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
// It can't be anything different from 51x31
const nativeSize = this.nativeViewProtected.sizeThatFits(zeroSize);
this.width = nativeSize.width;
this.height = nativeSize.height;
const widthAndState = Switch.resolveSizeAndState(layout.toDevicePixels(nativeSize.width), layout.toDevicePixels(51), layout.EXACTLY, 0);
const heightAndState = Switch.resolveSizeAndState(layout.toDevicePixels(nativeSize.height), layout.toDevicePixels(31), layout.EXACTLY, 0);
this.setMeasuredDimension(widthAndState, heightAndState);
}
[checkedProperty.getDefault](): boolean {
return false;
}

View File

@@ -1 +1,4 @@
export const SDK_VERSION = android.os.Build.VERSION.SDK_INT;
export function supportsGlass(): boolean {
return false;
}

View File

@@ -1 +1,2 @@
export const SDK_VERSION: number;
export function supportsGlass(): boolean;

View File

@@ -1 +1,4 @@
export const SDK_VERSION = parseFloat(UIDevice.currentDevice.systemVersion);
export function supportsGlass(): boolean {
return __APPLE__ && SDK_VERSION >= 26;
}

View File

@@ -1,6 +1,6 @@
import { getNativeApp } from '../application/helpers-common';
console.log('__dirname:', global.__dirname);
// console.log('__dirname:', global.__dirname);
function getCurrentAppPath(): string {
if (__APPLE__) {