@ng-native/nativescript 0.0.0 → 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 +21 -0
- package/README.md +48 -1
- package/app.plugin.cjs +58 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/native-script.d.ts +100 -0
- package/dist/native-script.js +150 -0
- package/dist/native-script.js.map +1 -0
- package/dist/ns-view.d.ts +25 -0
- package/dist/ns-view.js +137 -0
- package/dist/ns-view.js.map +1 -0
- package/package.json +67 -4
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
1
|
# @ng-native/nativescript
|
|
2
2
|
|
|
3
|
-
|
|
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;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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
|
+
}
|
package/dist/ns-view.js
ADDED
|
@@ -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,7 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/nativescript",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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
|
+
},
|
|
5
36
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
|
|
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"
|
|
70
|
+
}
|