@ng-native/nativescript 0.0.0-stage → 0.18.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ashley Hunter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,50 @@
1
- # Temporary Holding Version
1
+ # @ng-native/nativescript
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Every iOS and Android API from TypeScript, and native views built in TypeScript, through
4
+ [NativeScript](https://nativescript.org)'s runtime.
5
+
6
+ Alpha: APIs may change before 1.0.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npx expo install @ng-native/nativescript @nativescript/react-native react-native-worklets
12
+ npm install --save-dev @nativescript/types-android
13
+ ```
14
+
15
+ Add `"@ng-native/nativescript"` to the plugins in `app.json`, then rebuild the app: NativeScript
16
+ needs a development build, not Expo Go.
17
+
18
+ ## Example
19
+
20
+ ```ts
21
+ import { Component, inject } from '@angular/core';
22
+ import { Text } from '@ng-native/components';
23
+ import { NativeScript } from '@ng-native/nativescript';
24
+
25
+ @Component({
26
+ selector: 'app-device-name',
27
+ imports: [Text],
28
+ template: `<text>{{ model }}</text>`,
29
+ })
30
+ export class DeviceName {
31
+ private readonly native = inject(NativeScript);
32
+
33
+ protected readonly model = this.native.call({
34
+ ios: () => UIDevice.currentDevice.model,
35
+ android: () => android.os.Build.MODEL,
36
+ });
37
+ }
38
+ ```
39
+
40
+ With `provideNativeScript()` among the providers `mount` is given.
41
+
42
+ ## What's in the package
43
+
44
+ - `provideNativeScript()`: installs the runtime, at bootstrap or as a route is entered.
45
+ - `NativeScript`: `call()` on the JavaScript thread, and `runOnUI()` on the main thread.
46
+ - `<ns-view>` and `nsView()`: a UIView or an Android View built in a worklet, sized by the template.
47
+ - `androidContext()`: the Activity to build an Android view with.
48
+
49
+ The documentation is at
50
+ [ng-native.com/packages/nativescript](https://ng-native.com/packages/nativescript).
package/app.plugin.cjs ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `"plugins": ["@ng-native/nativescript"]` in app.json: NativeScript's own config plugin, which
3
+ * links the runtime and its metadata on iOS and applies its Gradle script on Android, without the
4
+ * Babel config it would write. `@ng-native/metro` supplies that one, with the worklets plugin
5
+ * configured for NativeScript's globals, so an app has no `babel.config.js` to keep.
6
+ *
7
+ * On Android it sets two lines in `gradle.properties`, for a release build, which
8
+ * `@nativescript/react-native` 9.1.2 can neither build nor run on Expo SDK 58 as it is:
9
+ *
10
+ * - `android.enableMinifyInReleaseBuilds=false`, over the `true` SDK 58's prebuild writes. With R8
11
+ * on, NativeScript's metadata filtering adds keep rules to a list Android Gradle Plugin 9 makes
12
+ * immutable, and the build fails with `UnsupportedOperationException` in `nativescript.gradle`;
13
+ * and NativeScript ships no keep rules for its own runtime, so R8 renames the classes its native
14
+ * code looks up by name and the app aborts at launch with `JNI DETECTED ERROR: mid == null`.
15
+ * - `nsSkipStaticBindings=true`. The release task that pre-generates Java for named subclasses
16
+ * bundles the app with React Native's CLI, which an Expo app does not have; skipped, those classes
17
+ * are generated on the device at startup, as in a debug build.
18
+ *
19
+ * Options are NativeScript's, such as `{ "metadata": { "includePods": ["SomeObjCSDK"] } }`.
20
+ */
21
+ const RELEASE = { 'android.enableMinifyInReleaseBuilds': 'false', nsSkipStaticBindings: 'true' };
22
+
23
+ // ponytail: drop once @nativescript/react-native ships keep rules AGP 9 accepts and bundles
24
+ // through the app's own React Native CLI (`react.cliFile` and `react.bundleCommand`).
25
+ function releaseProperties(properties) {
26
+ for (const [key, value] of Object.entries(RELEASE)) {
27
+ const found = properties.find((entry) => entry.type === 'property' && entry.key === key);
28
+ if (found) found.value = value;
29
+ else properties.push({ type: 'property', key, value });
30
+ }
31
+ return properties;
32
+ }
33
+
34
+ module.exports = function withNativeScript(config, options = {}) {
35
+ const projectRoot = config._internal?.projectRoot ?? process.cwd();
36
+ let plugin;
37
+ try {
38
+ plugin = require(
39
+ require.resolve('@nativescript/react-native/app.plugin.js', { paths: [projectRoot] }),
40
+ );
41
+ } catch (cause) {
42
+ throw new Error(
43
+ '[angular-native] @ng-native/nativescript needs @nativescript/react-native. Install it with ' +
44
+ '"npx expo install @nativescript/react-native react-native-worklets".',
45
+ { cause },
46
+ );
47
+ }
48
+ const { withGradleProperties } = require(
49
+ require.resolve('expo/config-plugins', { paths: [projectRoot] }),
50
+ );
51
+ const configured = plugin(config, { ...options, babelPlugin: false });
52
+ return withGradleProperties(configured, (gradle) => {
53
+ gradle.modResults = releaseProperties(gradle.modResults);
54
+ return gradle;
55
+ });
56
+ };
57
+
58
+ module.exports.releaseProperties = releaseProperties;
@@ -0,0 +1,2 @@
1
+ export * from './native-script.ts';
2
+ export * from './ns-view.ts';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./native-script.js";
2
+ export * from "./ns-view.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC","sourcesContent":["export * from './native-script.ts';\nexport * from './ns-view.ts';\n"]}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * NativeScript's runtime, installed once and reached through one service.
3
+ *
4
+ * `@nativescript/react-native` puts every iOS SDK symbol and every Android class on the JavaScript
5
+ * runtime over JSI. The service installs it, picks the function for the platform the app is on, and
6
+ * says what is wrong in a build without it, so a component does none of that.
7
+ *
8
+ * ```ts
9
+ * bootstrapApplication(App, { providers: [provideNativeScript()] });
10
+ *
11
+ * export class DeviceName {
12
+ * private readonly native = inject(NativeScript);
13
+ * protected readonly model = this.native.call({
14
+ * ios: () => UIDevice.currentDevice.model,
15
+ * android: () => android.os.Build.MODEL,
16
+ * });
17
+ * }
18
+ * ```
19
+ */
20
+ import { InjectionToken, type EnvironmentProviders } from '@angular/core';
21
+ import * as i0 from "@angular/core";
22
+ export type NativeScriptPlatform = 'ios' | 'android';
23
+ /** What the service needs of NativeScript, on the platform the app is on. */
24
+ export interface NativeScriptRuntime {
25
+ readonly platform: NativeScriptPlatform;
26
+ /** Install the native API on the JavaScript runtime, with iOS's classes as globals. Idempotent. */
27
+ install(): void;
28
+ /** Run a worklet on the platform's main thread. */
29
+ runOnUI<A extends unknown[], R>(worklet: (...args: A) => R, ...args: A): Promise<R>;
30
+ /** Load a system framework whose classes the metadata names but the binary does not link. */
31
+ loadFramework(name: string): boolean;
32
+ /**
33
+ * The handle `NativeScriptUIView` shows a view by: a UIView's pointer on iOS, a registry name on
34
+ * Android. A worklet, so the main thread can be handed it. Throws for a value that is not a view.
35
+ */
36
+ nativeViewHandle(view: unknown): string;
37
+ /** Why there is no runtime on a platform that should have one: a build without it, or Expo Go. */
38
+ readonly missing?: Error;
39
+ }
40
+ /**
41
+ * The Context to build an Android view with, the current Activity, as `new
42
+ * android.widget.TextView(androidContext())` needs. `null` on iOS, in Node, and before NativeScript
43
+ * is installed. A function of this package's, so a component file can name it without importing
44
+ * `@nativescript/react-native`, which Node cannot load.
45
+ */
46
+ export declare function androidContext(): unknown;
47
+ /**
48
+ * The runtime for the platform the app is on, or null where there is none to install: Node, where
49
+ * a test runs, and the web. On a device without the native half, Expo Go among them, it is one
50
+ * that says so as it is used: the rest of the app still runs, and the feature that needs NativeScript
51
+ * fails with what to do about it, as a missing Expo module does.
52
+ */
53
+ export declare function nativeScriptRuntime(): NativeScriptRuntime | null;
54
+ /**
55
+ * NativeScript, installed. Injecting it installs the runtime, once for the process; a test that
56
+ * renders a component using it gets a service whose `platform` is null and whose calls answer
57
+ * `undefined`, with no simulator.
58
+ */
59
+ export declare class NativeScript {
60
+ /** Overridden in a test to stand in for the native runtime. */
61
+ static readonly RUNTIME: InjectionToken<NativeScriptRuntime | null>;
62
+ private readonly runtime;
63
+ private readonly errors;
64
+ /** The platform NativeScript is installed on; null in Node and on the web. */
65
+ readonly platform: NativeScriptPlatform | null;
66
+ constructor();
67
+ /**
68
+ * Call the function for this platform, synchronously, on the JavaScript thread, and answer what
69
+ * it returns. `undefined` on a platform the object has no function for, and where there is no
70
+ * runtime. Reading a value or calling a non-UI API is fine here on both platforms; a view is
71
+ * touched through `runOnUI`. Throws in a build without NativeScript, rather than leave the
72
+ * function to fail on the first native class it names.
73
+ */
74
+ call<T>(calls: {
75
+ readonly ios?: () => T;
76
+ readonly android?: () => T;
77
+ }): T | undefined;
78
+ /**
79
+ * Run a worklet on the platform's main thread, where views can be touched, and answer what it
80
+ * returns. On iOS it runs on the Worklets UI runtime and captures what it reads by value; on
81
+ * Android it borrows this runtime while the JavaScript thread waits. Either way pass state as
82
+ * arguments rather than reading `this` inside it.
83
+ */
84
+ runOnUI<A extends unknown[], R>(worklet: (...args: A) => R, ...args: A): Promise<R | undefined>;
85
+ /** The handle `<ns-view>` binds for a view; see `NativeScriptRuntime.nativeViewHandle`. */
86
+ readonly nativeViewHandle: (view: unknown) => string;
87
+ /** Load each named system framework (iOS), such as SceneKit. */
88
+ loadFrameworks(names: readonly string[]): void;
89
+ static ɵfac: i0.ɵɵFactoryDeclaration<NativeScript, never>;
90
+ static ɵprov: i0.ɵɵInjectableDeclaration<any>;
91
+ }
92
+ export interface NativeScriptOptions {
93
+ /** System frameworks to load on iOS before anything under these providers runs. */
94
+ readonly frameworks?: readonly string[];
95
+ }
96
+ /**
97
+ * Install NativeScript as these providers' injector is created: at bootstrap for the app's, or as
98
+ * a route is first entered for a route's, so a feature that needs SceneKit pays for it there.
99
+ */
100
+ export declare function provideNativeScript(options?: NativeScriptOptions): EnvironmentProviders;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * NativeScript's runtime, installed once and reached through one service.
3
+ *
4
+ * `@nativescript/react-native` puts every iOS SDK symbol and every Android class on the JavaScript
5
+ * runtime over JSI. The service installs it, picks the function for the platform the app is on, and
6
+ * says what is wrong in a build without it, so a component does none of that.
7
+ *
8
+ * ```ts
9
+ * bootstrapApplication(App, { providers: [provideNativeScript()] });
10
+ *
11
+ * export class DeviceName {
12
+ * private readonly native = inject(NativeScript);
13
+ * protected readonly model = this.native.call({
14
+ * ios: () => UIDevice.currentDevice.model,
15
+ * android: () => android.os.Build.MODEL,
16
+ * });
17
+ * }
18
+ * ```
19
+ */
20
+ import { ErrorHandler, InjectionToken, Service, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core';
21
+ import * as i0 from "@angular/core";
22
+ /** NativeScript's `androidContext`, once a runtime is made: see `androidContext` below. */
23
+ let contextOf = null;
24
+ /**
25
+ * The Context to build an Android view with, the current Activity, as `new
26
+ * android.widget.TextView(androidContext())` needs. `null` on iOS, in Node, and before NativeScript
27
+ * is installed. A function of this package's, so a component file can name it without importing
28
+ * `@nativescript/react-native`, which Node cannot load.
29
+ */
30
+ export function androidContext() {
31
+ return contextOf?.() ?? null;
32
+ }
33
+ const MISSING = '[angular-native] @nativescript/react-native is not in this build of the app. Install it, add ' +
34
+ '"@ng-native/nativescript" to the plugins in app.json and rebuild ("npx expo run:ios" or ' +
35
+ '"npx expo run:android"). Expo Go cannot load it.';
36
+ /**
37
+ * The runtime for the platform the app is on, or null where there is none to install: Node, where
38
+ * a test runs, and the web. On a device without the native half, Expo Go among them, it is one
39
+ * that says so as it is used: the rest of the app still runs, and the feature that needs NativeScript
40
+ * fails with what to do about it, as a missing Expo module does.
41
+ */
42
+ export function nativeScriptRuntime() {
43
+ let platform;
44
+ try {
45
+ platform = require('react-native').Platform.OS;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ if (platform !== 'ios' && platform !== 'android')
51
+ return null;
52
+ const ns = require('@nativescript/react-native').default;
53
+ if (!ns.isAvailable()) {
54
+ const missing = new Error(MISSING);
55
+ return {
56
+ platform,
57
+ missing,
58
+ install: () => { },
59
+ runOnUI: () => Promise.reject(missing),
60
+ loadFramework: () => true,
61
+ nativeViewHandle: () => {
62
+ throw missing;
63
+ },
64
+ };
65
+ }
66
+ contextOf = platform === 'android' ? ns.androidContext : null;
67
+ return {
68
+ platform,
69
+ // Globals on iOS: `UIDevice.currentDevice` rather than `NativeScript.getClass('UIDevice')`. The
70
+ // package defaults them off so UIKit is not touched from the JavaScript thread by accident;
71
+ // `runOnUI` is still the way to touch a view. Android's packages are globals either way.
72
+ install: () => void ns.init('', { globals: platform === 'ios' }),
73
+ runOnUI: (worklet, ...args) => ns.runOnUI(worklet, ...args),
74
+ // Android loads classes from the APK's classpath; there is nothing to load.
75
+ loadFramework: (name) => platform !== 'ios' || ns.loadFramework(name),
76
+ nativeViewHandle: ns.nativeViewHandle,
77
+ };
78
+ }
79
+ /** Where there is no runtime. A worklet, as the runtime's own is; an object, as `nsView` explains. */
80
+ const noRuntime = {
81
+ nativeViewHandle(view) {
82
+ 'worklet';
83
+ throw new Error(`[angular-native] No NativeScript runtime to take ${String(view)}'s handle.`);
84
+ },
85
+ };
86
+ /**
87
+ * NativeScript, installed. Injecting it installs the runtime, once for the process; a test that
88
+ * renders a component using it gets a service whose `platform` is null and whose calls answer
89
+ * `undefined`, with no simulator.
90
+ */
91
+ export class NativeScript {
92
+ /** Overridden in a test to stand in for the native runtime. */
93
+ static RUNTIME = new InjectionToken('angular-native.nativeScriptRuntime', { factory: nativeScriptRuntime });
94
+ runtime = inject(NativeScript.RUNTIME);
95
+ errors = inject(ErrorHandler);
96
+ /** The platform NativeScript is installed on; null in Node and on the web. */
97
+ platform = this.runtime?.platform ?? null;
98
+ constructor() {
99
+ this.runtime?.install();
100
+ }
101
+ /**
102
+ * Call the function for this platform, synchronously, on the JavaScript thread, and answer what
103
+ * it returns. `undefined` on a platform the object has no function for, and where there is no
104
+ * runtime. Reading a value or calling a non-UI API is fine here on both platforms; a view is
105
+ * touched through `runOnUI`. Throws in a build without NativeScript, rather than leave the
106
+ * function to fail on the first native class it names.
107
+ */
108
+ call(calls) {
109
+ const call = this.platform && calls[this.platform];
110
+ if (call && this.runtime?.missing)
111
+ throw this.runtime.missing;
112
+ return call ? call() : undefined;
113
+ }
114
+ /**
115
+ * Run a worklet on the platform's main thread, where views can be touched, and answer what it
116
+ * returns. On iOS it runs on the Worklets UI runtime and captures what it reads by value; on
117
+ * Android it borrows this runtime while the JavaScript thread waits. Either way pass state as
118
+ * arguments rather than reading `this` inside it.
119
+ */
120
+ runOnUI(worklet, ...args) {
121
+ return this.runtime ? this.runtime.runOnUI(worklet, ...args) : Promise.resolve(undefined);
122
+ }
123
+ /** The handle `<ns-view>` binds for a view; see `NativeScriptRuntime.nativeViewHandle`. */
124
+ nativeViewHandle = this.runtime?.nativeViewHandle ?? noRuntime.nativeViewHandle;
125
+ /** Load each named system framework (iOS), such as SceneKit. */
126
+ loadFrameworks(names) {
127
+ if (this.platform !== 'ios')
128
+ return;
129
+ for (const name of names) {
130
+ if (!this.runtime.loadFramework(name)) {
131
+ this.errors.handleError(new Error(`[angular-native] NativeScript could not load ${name}.`));
132
+ }
133
+ }
134
+ }
135
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeScript, deps: [], target: i0.ɵɵFactoryTarget.Service });
136
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: NativeScript });
137
+ }
138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeScript, decorators: [{
139
+ type: Service
140
+ }], ctorParameters: () => [] });
141
+ /**
142
+ * Install NativeScript as these providers' injector is created: at bootstrap for the app's, or as
143
+ * a route is first entered for a route's, so a feature that needs SceneKit pays for it there.
144
+ */
145
+ export function provideNativeScript(options = {}) {
146
+ return makeEnvironmentProviders([
147
+ provideEnvironmentInitializer(() => inject(NativeScript).loadFrameworks(options.frameworks ?? [])),
148
+ ]);
149
+ }
150
+ //# sourceMappingURL=native-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-script.js","sourceRoot":"","sources":["../src/native-script.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NativeScript's runtime, installed once and reached through one service.\n *\n * `@nativescript/react-native` puts every iOS SDK symbol and every Android class on the JavaScript\n * runtime over JSI. The service installs it, picks the function for the platform the app is on, and\n * says what is wrong in a build without it, so a component does none of that.\n *\n * ```ts\n * bootstrapApplication(App, { providers: [provideNativeScript()] });\n *\n * export class DeviceName {\n * private readonly native = inject(NativeScript);\n * protected readonly model = this.native.call({\n * ios: () => UIDevice.currentDevice.model,\n * android: () => android.os.Build.MODEL,\n * });\n * }\n * ```\n */\nimport {\n ErrorHandler,\n InjectionToken,\n Service,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n} from '@angular/core';\n\nexport type NativeScriptPlatform = 'ios' | 'android';\n\n/** What the service needs of NativeScript, on the platform the app is on. */\nexport interface NativeScriptRuntime {\n readonly platform: NativeScriptPlatform;\n /** Install the native API on the JavaScript runtime, with iOS's classes as globals. Idempotent. */\n install(): void;\n /** Run a worklet on the platform's main thread. */\n runOnUI<A extends unknown[], R>(worklet: (...args: A) => R, ...args: A): Promise<R>;\n /** Load a system framework whose classes the metadata names but the binary does not link. */\n loadFramework(name: string): boolean;\n /**\n * The handle `NativeScriptUIView` shows a view by: a UIView's pointer on iOS, a registry name on\n * Android. A worklet, so the main thread can be handed it. Throws for a value that is not a view.\n */\n nativeViewHandle(view: unknown): string;\n /** Why there is no runtime on a platform that should have one: a build without it, or Expo Go. */\n readonly missing?: Error;\n}\n\n/** The subset of `@nativescript/react-native`'s default export used here. */\ninterface NativeScriptModule {\n isAvailable(): boolean;\n init(metadataPath?: string, options?: { globals?: boolean }): boolean;\n runOnUI<A extends unknown[], R>(worklet: (...args: A) => R, ...args: A): Promise<R>;\n loadFramework(name: string): boolean;\n nativeViewHandle(view: unknown): string;\n androidContext(): unknown;\n}\n\n/** NativeScript's `androidContext`, once a runtime is made: see `androidContext` below. */\nlet contextOf: (() => unknown) | null = null;\n\n/**\n * The Context to build an Android view with, the current Activity, as `new\n * android.widget.TextView(androidContext())` needs. `null` on iOS, in Node, and before NativeScript\n * is installed. A function of this package's, so a component file can name it without importing\n * `@nativescript/react-native`, which Node cannot load.\n */\nexport function androidContext(): unknown {\n return contextOf?.() ?? null;\n}\n\nconst MISSING =\n '[angular-native] @nativescript/react-native is not in this build of the app. Install it, add ' +\n '\"@ng-native/nativescript\" to the plugins in app.json and rebuild (\"npx expo run:ios\" or ' +\n '\"npx expo run:android\"). Expo Go cannot load it.';\n\n/**\n * The runtime for the platform the app is on, or null where there is none to install: Node, where\n * a test runs, and the web. On a device without the native half, Expo Go among them, it is one\n * that says so as it is used: the rest of the app still runs, and the feature that needs NativeScript\n * fails with what to do about it, as a missing Expo module does.\n */\nexport function nativeScriptRuntime(): NativeScriptRuntime | null {\n let platform: string;\n try {\n platform = (require('react-native') as { Platform: { OS: string } }).Platform.OS;\n } catch {\n return null;\n }\n if (platform !== 'ios' && platform !== 'android') return null;\n const ns = (require('@nativescript/react-native') as { default: NativeScriptModule }).default;\n if (!ns.isAvailable()) {\n const missing = new Error(MISSING);\n return {\n platform,\n missing,\n install: () => {},\n runOnUI: () => Promise.reject(missing),\n loadFramework: () => true,\n nativeViewHandle: () => {\n throw missing;\n },\n };\n }\n contextOf = platform === 'android' ? ns.androidContext : null;\n return {\n platform,\n // Globals on iOS: `UIDevice.currentDevice` rather than `NativeScript.getClass('UIDevice')`. The\n // package defaults them off so UIKit is not touched from the JavaScript thread by accident;\n // `runOnUI` is still the way to touch a view. Android's packages are globals either way.\n install: () => void ns.init('', { globals: platform === 'ios' }),\n runOnUI: (worklet, ...args) => ns.runOnUI(worklet, ...args),\n // Android loads classes from the APK's classpath; there is nothing to load.\n loadFramework: (name) => platform !== 'ios' || ns.loadFramework(name),\n nativeViewHandle: ns.nativeViewHandle,\n };\n}\n\n/** Where there is no runtime. A worklet, as the runtime's own is; an object, as `nsView` explains. */\nconst noRuntime = {\n nativeViewHandle(view: unknown): string {\n 'worklet';\n throw new Error(`[angular-native] No NativeScript runtime to take ${String(view)}'s handle.`);\n },\n};\n\n/**\n * NativeScript, installed. Injecting it installs the runtime, once for the process; a test that\n * renders a component using it gets a service whose `platform` is null and whose calls answer\n * `undefined`, with no simulator.\n */\n@Service()\nexport class NativeScript {\n /** Overridden in a test to stand in for the native runtime. */\n static readonly RUNTIME = new InjectionToken<NativeScriptRuntime | null>(\n 'angular-native.nativeScriptRuntime',\n { factory: nativeScriptRuntime },\n );\n\n private readonly runtime = inject(NativeScript.RUNTIME);\n private readonly errors = inject(ErrorHandler);\n\n /** The platform NativeScript is installed on; null in Node and on the web. */\n readonly platform: NativeScriptPlatform | null = this.runtime?.platform ?? null;\n\n constructor() {\n this.runtime?.install();\n }\n\n /**\n * Call the function for this platform, synchronously, on the JavaScript thread, and answer what\n * it returns. `undefined` on a platform the object has no function for, and where there is no\n * runtime. Reading a value or calling a non-UI API is fine here on both platforms; a view is\n * touched through `runOnUI`. Throws in a build without NativeScript, rather than leave the\n * function to fail on the first native class it names.\n */\n call<T>(calls: { readonly ios?: () => T; readonly android?: () => T }): T | undefined {\n const call = this.platform && calls[this.platform];\n if (call && this.runtime?.missing) throw this.runtime.missing;\n return call ? call() : undefined;\n }\n\n /**\n * Run a worklet on the platform's main thread, where views can be touched, and answer what it\n * returns. On iOS it runs on the Worklets UI runtime and captures what it reads by value; on\n * Android it borrows this runtime while the JavaScript thread waits. Either way pass state as\n * arguments rather than reading `this` inside it.\n */\n runOnUI<A extends unknown[], R>(worklet: (...args: A) => R, ...args: A): Promise<R | undefined> {\n return this.runtime ? this.runtime.runOnUI(worklet, ...args) : Promise.resolve(undefined);\n }\n\n /** The handle `<ns-view>` binds for a view; see `NativeScriptRuntime.nativeViewHandle`. */\n readonly nativeViewHandle: (view: unknown) => string =\n this.runtime?.nativeViewHandle ?? noRuntime.nativeViewHandle;\n\n /** Load each named system framework (iOS), such as SceneKit. */\n loadFrameworks(names: readonly string[]): void {\n if (this.platform !== 'ios') return;\n for (const name of names) {\n if (!this.runtime!.loadFramework(name)) {\n this.errors.handleError(new Error(`[angular-native] NativeScript could not load ${name}.`));\n }\n }\n }\n}\n\nexport interface NativeScriptOptions {\n /** System frameworks to load on iOS before anything under these providers runs. */\n readonly frameworks?: readonly string[];\n}\n\n/**\n * Install NativeScript as these providers' injector is created: at bootstrap for the app's, or as\n * a route is first entered for a route's, so a feature that needs SceneKit pays for it there.\n */\nexport function provideNativeScript(options: NativeScriptOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideEnvironmentInitializer(() =>\n inject(NativeScript).loadFrameworks(options.frameworks ?? []),\n ),\n ]);\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import * as i0 from "@angular/core";
2
+ /** How to build a native view and keep it in step with its props. Both functions are worklets. */
3
+ export interface NsViewSpec<P, V> {
4
+ create(props: P): V;
5
+ update?(view: V, props: P): void;
6
+ }
7
+ /**
8
+ * Declare a native view. An identity function, there to infer `P` and `V` across both functions;
9
+ * written as an object so the HMR transform, which wraps a module's top-level functions and with
10
+ * them a worklet's `__workletHash`, leaves the worklets alone.
11
+ */
12
+ export declare function nsView<P, V>(spec: NsViewSpec<P, V>): NsViewSpec<P, V>;
13
+ export declare class NsView<P = unknown> {
14
+ private readonly native;
15
+ private readonly errors;
16
+ /** What to build. A different spec builds a new view in place of the old one. */
17
+ readonly view: import("@angular/core").InputSignal<NsViewSpec<P, any>>;
18
+ /** Passed to `create`, then to `update` each time it changes. Copied to iOS's UI runtime. */
19
+ readonly props: import("@angular/core").InputSignal<P>;
20
+ /** The view's pointer; `null` once it is gone, since a prop set to `undefined` is not removed. */
21
+ protected readonly handle: import("@angular/core").WritableSignal<string | null | undefined>;
22
+ constructor();
23
+ static ɵfac: i0.ɵɵFactoryDeclaration<NsView<any>, never>;
24
+ static ɵcmp: i0.ɵɵComponentDeclaration<NsView<any>, "ns-view", never, { "view": { "alias": "view"; "required": true; "isSignal": true; }; "props": { "alias": "props"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
25
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * `<ns-view>`: a native view built in TypeScript, a UIView or an `android.view.View`, laid out by the
3
+ * template like any other view.
4
+ *
5
+ * ```ts
6
+ * const badge = nsView<{ title: string }, UILabel>({
7
+ * create(props) {
8
+ * 'worklet';
9
+ * const label = UILabel.new();
10
+ * label.text = props.title;
11
+ * return label;
12
+ * },
13
+ * update(label, props) {
14
+ * 'worklet';
15
+ * label.text = props.title;
16
+ * },
17
+ * });
18
+ *
19
+ * @Component({
20
+ * imports: [NsView],
21
+ * template: `<ns-view class="h-12" [view]="badge" [props]="{ title: title() }" />`,
22
+ * })
23
+ * ```
24
+ *
25
+ * `@nativescript/react-native` ships a Fabric view, `NativeScriptUIView`, on both platforms, that
26
+ * shows the view whose handle it is given as `nativeViewHandle`. The element commits as that view;
27
+ * `create` runs on the main thread, the view it returns is kept there under the element's id, and
28
+ * its handle comes back to be bound. `update` runs there too, each time `props` changes. The
29
+ * package's own `defineUIKitView` does the same for React, and is a React component, so it cannot be
30
+ * used here.
31
+ */
32
+ import { Component, DestroyRef, ErrorHandler, effect, inject, input, signal } from '@angular/core';
33
+ import { registerViewName } from '@ng-native/fabric';
34
+ import { NativeScript } from "./native-script.js";
35
+ import * as i0 from "@angular/core";
36
+ /**
37
+ * Declare a native view. An identity function, there to infer `P` and `V` across both functions;
38
+ * written as an object so the HMR transform, which wraps a module's top-level functions and with
39
+ * them a worklet's `__workletHash`, leaves the worklets alone.
40
+ */
41
+ export function nsView(spec) {
42
+ return spec;
43
+ }
44
+ registerViewName('ns-view', 'NativeScriptUIView', { collapsable: false });
45
+ /** The views built so far, on the main thread's runtime, keyed by element. Holding one keeps it alive. */
46
+ const VIEWS = '__ngNsViews';
47
+ /** Runs on the main thread. One object so none of these is a top-level function: see `nsView`. */
48
+ const ui = {
49
+ mount(id, create, props, handleOf) {
50
+ 'worklet';
51
+ const g = globalThis;
52
+ const views = (g[VIEWS] ??= new Map());
53
+ // The view this element showed goes first, so a create that fails leaves it empty.
54
+ views.delete(id);
55
+ const view = create(props);
56
+ // iOS takes any NSObject's pointer, a controller's too, and shows nothing for one that is not
57
+ // a UIView, so that is checked here; Android's handle refuses what is not a View itself.
58
+ const notAView = g['UIView'] && view?.isKindOfClass?.(g['UIView']) !== true;
59
+ let handle;
60
+ try {
61
+ if (!notAView && view != null)
62
+ handle = handleOf(view);
63
+ }
64
+ catch {
65
+ // Reported below, as the value it was.
66
+ }
67
+ if (handle === undefined) {
68
+ throw new Error(`[angular-native] An nsView's create returned ${String(view)}, where it returns a view.`);
69
+ }
70
+ views.set(id, view);
71
+ return handle;
72
+ },
73
+ update(id, update, props) {
74
+ 'worklet';
75
+ const view = globalThis[VIEWS]?.get(id);
76
+ if (view)
77
+ update(view, props);
78
+ },
79
+ unmount(id) {
80
+ 'worklet';
81
+ globalThis[VIEWS]?.delete(id);
82
+ },
83
+ };
84
+ let nextId = 0;
85
+ export class NsView {
86
+ native = inject(NativeScript);
87
+ errors = inject(ErrorHandler);
88
+ /** What to build. A different spec builds a new view in place of the old one. */
89
+ view = input.required(/* @ts-ignore */
90
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
91
+ /** Passed to `create`, then to `update` each time it changes. Copied to iOS's UI runtime. */
92
+ props = input.required(/* @ts-ignore */
93
+ ...(ngDevMode ? [{ debugName: "props" }] : /* istanbul ignore next */ []));
94
+ /** The view's pointer; `null` once it is gone, since a prop set to `undefined` is not removed. */
95
+ handle = signal(undefined, /* @ts-ignore */
96
+ ...(ngDevMode ? [{ debugName: "handle" }] : /* istanbul ignore next */ []));
97
+ constructor() {
98
+ const id = `ns-view-${nextId++}`;
99
+ const report = (error) => this.errors.handleError(error);
100
+ let built;
101
+ effect(() => {
102
+ const spec = this.view();
103
+ const props = this.props();
104
+ if (!this.native.platform)
105
+ return;
106
+ if (spec !== built) {
107
+ built = spec;
108
+ const create = spec.create;
109
+ this.native.runOnUI(ui.mount, id, create, props, this.native.nativeViewHandle).then((handle) => built === spec && this.handle.set(handle), (error) => {
110
+ if (built === spec)
111
+ this.handle.set(null);
112
+ report(error);
113
+ });
114
+ }
115
+ else if (spec.update) {
116
+ this.native
117
+ .runOnUI(ui.update, id, spec.update, props)
118
+ .catch(report);
119
+ }
120
+ });
121
+ inject(DestroyRef).onDestroy(() => {
122
+ if (built)
123
+ this.native.runOnUI(ui.unmount, id).catch(report);
124
+ });
125
+ }
126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NsView, deps: [], target: i0.ɵɵFactoryTarget.Component });
127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: NsView, isStandalone: true, selector: "ns-view", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: true, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "nativeViewHandle": "handle()" } }, ngImport: i0, template: '', isInline: true });
128
+ }
129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NsView, decorators: [{
130
+ type: Component,
131
+ args: [{
132
+ selector: 'ns-view',
133
+ template: '',
134
+ host: { '[nativeViewHandle]': 'handle()' },
135
+ }]
136
+ }], ctorParameters: () => [], propDecorators: { view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: true }] }], props: [{ type: i0.Input, args: [{ isSignal: true, alias: "props", required: true }] }] } });
137
+ //# sourceMappingURL=ns-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ns-view.js","sourceRoot":"","sources":["../src/ns-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `<ns-view>`: a native view built in TypeScript, a UIView or an `android.view.View`, laid out by the\n * template like any other view.\n *\n * ```ts\n * const badge = nsView<{ title: string }, UILabel>({\n * create(props) {\n * 'worklet';\n * const label = UILabel.new();\n * label.text = props.title;\n * return label;\n * },\n * update(label, props) {\n * 'worklet';\n * label.text = props.title;\n * },\n * });\n *\n * @Component({\n * imports: [NsView],\n * template: `<ns-view class=\"h-12\" [view]=\"badge\" [props]=\"{ title: title() }\" />`,\n * })\n * ```\n *\n * `@nativescript/react-native` ships a Fabric view, `NativeScriptUIView`, on both platforms, that\n * shows the view whose handle it is given as `nativeViewHandle`. The element commits as that view;\n * `create` runs on the main thread, the view it returns is kept there under the element's id, and\n * its handle comes back to be bound. `update` runs there too, each time `props` changes. The\n * package's own `defineUIKitView` does the same for React, and is a React component, so it cannot be\n * used here.\n */\nimport { Component, DestroyRef, ErrorHandler, effect, inject, input, signal } from '@angular/core';\nimport { registerViewName } from '@ng-native/fabric';\nimport { NativeScript } from './native-script.ts';\n\n/** How to build a native view and keep it in step with its props. Both functions are worklets. */\nexport interface NsViewSpec<P, V> {\n create(props: P): V;\n update?(view: V, props: P): void;\n}\n\n/**\n * Declare a native view. An identity function, there to infer `P` and `V` across both functions;\n * written as an object so the HMR transform, which wraps a module's top-level functions and with\n * them a worklet's `__workletHash`, leaves the worklets alone.\n */\nexport function nsView<P, V>(spec: NsViewSpec<P, V>): NsViewSpec<P, V> {\n return spec;\n}\n\nregisterViewName('ns-view', 'NativeScriptUIView', { collapsable: false });\n\n/** The views built so far, on the main thread's runtime, keyed by element. Holding one keeps it alive. */\nconst VIEWS = '__ngNsViews';\n\n/** Runs on the main thread. One object so none of these is a top-level function: see `nsView`. */\nconst ui = {\n mount(\n id: string,\n create: (props: unknown) => unknown,\n props: unknown,\n handleOf: (view: unknown) => string,\n ): string {\n 'worklet';\n const g = globalThis as Record<string, any>;\n const views: Map<string, unknown> = (g[VIEWS] ??= new Map());\n // The view this element showed goes first, so a create that fails leaves it empty.\n views.delete(id);\n const view = create(props) as { isKindOfClass?(of: unknown): boolean } | null;\n // iOS takes any NSObject's pointer, a controller's too, and shows nothing for one that is not\n // a UIView, so that is checked here; Android's handle refuses what is not a View itself.\n const notAView = g['UIView'] && view?.isKindOfClass?.(g['UIView']) !== true;\n let handle: string | undefined;\n try {\n if (!notAView && view != null) handle = handleOf(view);\n } catch {\n // Reported below, as the value it was.\n }\n if (handle === undefined) {\n throw new Error(\n `[angular-native] An nsView's create returned ${String(view)}, where it returns a view.`,\n );\n }\n views.set(id, view);\n return handle;\n },\n update(id: string, update: (view: unknown, props: unknown) => void, props: unknown): void {\n 'worklet';\n const view = (globalThis as Record<string, any>)[VIEWS]?.get(id);\n if (view) update(view, props);\n },\n unmount(id: string): void {\n 'worklet';\n (globalThis as Record<string, any>)[VIEWS]?.delete(id);\n },\n};\n\nlet nextId = 0;\n\n@Component({\n selector: 'ns-view',\n template: '',\n host: { '[nativeViewHandle]': 'handle()' },\n})\nexport class NsView<P = unknown> {\n private readonly native = inject(NativeScript);\n private readonly errors = inject(ErrorHandler);\n\n /** What to build. A different spec builds a new view in place of the old one. */\n readonly view = input.required<NsViewSpec<P, any>>();\n /** Passed to `create`, then to `update` each time it changes. Copied to iOS's UI runtime. */\n readonly props = input.required<P>();\n\n /** The view's pointer; `null` once it is gone, since a prop set to `undefined` is not removed. */\n protected readonly handle = signal<string | null | undefined>(undefined);\n\n constructor() {\n const id = `ns-view-${nextId++}`;\n const report = (error: unknown) => this.errors.handleError(error);\n let built: NsViewSpec<P, any> | undefined;\n\n effect(() => {\n const spec = this.view();\n const props = this.props();\n if (!this.native.platform) return;\n if (spec !== built) {\n built = spec;\n const create = spec.create as (props: unknown) => unknown;\n this.native.runOnUI(ui.mount, id, create, props, this.native.nativeViewHandle).then(\n (handle) => built === spec && this.handle.set(handle),\n (error: unknown) => {\n if (built === spec) this.handle.set(null);\n report(error);\n },\n );\n } else if (spec.update) {\n this.native\n .runOnUI(ui.update, id, spec.update as (view: unknown, props: unknown) => void, props)\n .catch(report);\n }\n });\n\n inject(DestroyRef).onDestroy(() => {\n if (built) this.native.runOnUI(ui.unmount, id).catch(report);\n });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,70 @@
1
1
  {
2
2
  "name": "@ng-native/nativescript",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.18.2",
4
+ "description": "NativeScript's iOS and Android APIs in Angular Native: provideNativeScript, a NativeScript service and a <native-view> for a UIView built in TypeScript.",
5
+ "keywords": [
6
+ "angular",
7
+ "react-native",
8
+ "angular-native",
9
+ "nativescript",
10
+ "ios",
11
+ "android",
12
+ "jsi"
13
+ ],
14
+ "type": "module",
15
+ "main": "dist/index.js",
16
+ "dependencies": {
17
+ "@ng-native/fabric": "0.18.2"
18
+ },
19
+ "peerDependencies": {
20
+ "@angular/core": "^22.0.0",
21
+ "@nativescript/react-native": "^9.1.2",
22
+ "react-native": ">=0.86.0",
23
+ "react-native-worklets": ">=0.8.0"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "react-native": {
27
+ "optional": true
28
+ }
29
+ },
30
+ "nx": {
31
+ "tags": [
32
+ "scope:nativescript",
33
+ "layer:angular"
34
+ ]
35
+ },
36
+ "license": "MIT",
37
+ "homepage": "https://ng-native.com/guide/native-modules/nativescript",
38
+ "bugs": {
39
+ "url": "https://github.com/ng-native/ng-native/issues"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/ng-native/ng-native.git",
44
+ "directory": "packages/nativescript"
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "app.plugin.cjs"
49
+ ],
50
+ "devDependencies": {
51
+ "@angular/core": "22.2.0",
52
+ "@babel/core": "^7.29.7"
53
+ },
54
+ "exports": {
55
+ ".": {
56
+ "types": "./dist/index.d.ts",
57
+ "default": "./dist/index.js"
58
+ },
59
+ "./app.plugin.cjs": "./app.plugin.cjs",
60
+ "./package.json": "./package.json"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "scripts": {
66
+ "build": "ngc -p tsconfig.build.json",
67
+ "typecheck": "tsc -p tsconfig.json --noEmit"
68
+ },
69
+ "types": "dist/index.d.ts"
6
70
  }