From c1394132046bc838b195a2de0a7afabd9632276e Mon Sep 17 00:00:00 2001 From: Ombuweb Date: Sat, 19 Nov 2022 22:26:39 +0100 Subject: [PATCH 01/44] docs: initial content for Updating a NativeScript app --- .../updating-a-nativescript-app.md | 211 ++++++++++++++++++ content/sidebar.ts | 13 +- 2 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 content/guide/development-workflow/updating-a-nativescript-app.md diff --git a/content/guide/development-workflow/updating-a-nativescript-app.md b/content/guide/development-workflow/updating-a-nativescript-app.md new file mode 100644 index 00000000..e6d41ba7 --- /dev/null +++ b/content/guide/development-workflow/updating-a-nativescript-app.md @@ -0,0 +1,211 @@ +--- +title: Updating a NativeScript app +--- + +To upgrade a NativeScript application you need to upgrade several things: NativeScript CLI Tooling, the iOS and Android runtimes and the `@nativescript/core` module. In the steps below you will see how to do this. + +To get the latest version of the NativeScript CLI, run: + +```cli +npm install -g nativescript +``` + +#### Upgrading a NativeScript application + +You should execute the **update** command in the root folder of your project to upgrade it with the latest versions of iOS/Android runtimes and cross-platform modules. + + +```cli +ns update +``` + +In order to get the latest development release instead, pass **next** as argument: + +```cli +ns update next +``` + +You can also switch to specific version by passing it to the command: + +```cli +ns update 8.0.0 +``` + +::: tip Note +The command `ns update` is updating the `@nativescript/core`, `@nativescript/webpack`, and the runtimes (`@nativescript/android`and`@nativescript/ios`). The command is combining the next three commands in this article (`ns platform add`, `npm i --save @nativescript/core`and`npm i @nativescript/webpack --save-dev`). + +::: + +::: warning Important +When using the `--configs` flag, any previous configuration will be overwritten and lost. Consider saving any custom code that you have introduced in your `webpack.config.js` and reapplying the code after using the `--configs` flag. +::: + +#### Upgrading platforms + +Follow those steps in order to get the latest versions of Android and/or iOS runtimes. Navigate to the root folder of your project, and then if you are working on a Android project, type: + +```cli +ns platform remove android +ns platform add android +``` + +and/or (if you are working on a iOS version on a Mac): + +```cli +ns platform remove ios +ns platform add ios +``` + +#### Upgrading @nativescript/core + +The cross-platform modules are available as a npm package named [@nativescript/core](https://www.npmjs.com/package/@nativescript/core). + +In order to use them in your project, you will have to explicitly install the package, for example (assuming you are still in your main app project folder from the steps above): + +```cli +npm install @nativescript/core@latest --save +``` + +This installs the **@nativescript/core** package to the node_modules folder and adds it as a dependency to the package.json of the project. + +::: warning Important +The `ns create` command will create a new project, add the **@nativescript/core** package as a dependency to its package.json and install it. So each new project you create will have the **@nativescript/core** package installed and you do not have to install it explicitly. +::: + +Another place to find **@nativescript/core** package is [NativeScript Releases](https://github.com/NativeScript/NativeScript/releases/), where you can find a collection of the available @nativescript/core-\*.tgz packages for every release. You can download a selected release and install it by running: `npm install --save`. + +#### Upgrading Angular dependencies + +The Angular plugin is available as an npm package named [@nativescript/angular](https://www.npmjs.com/package/@nativescript/angular). To update the version of the plugin and the related dependency, the package should be explicitly installed, and the related Angular dependencies should be updated accordingly. To ease the update process, the plugin comes with an automated script `update-app-ng-deps` located in `` folder. + +```cli +npm i @nativescript/angular@latest --save +./node_modules/.bin/update-app-ng-deps +npm i +``` + +--- + +title: Running Latest Code +description: NativeScript Documentation - Running Latest Code +position: 40 +slug: latest-code +previous_url: /running-latest + +--- + +#### Running the Latest Code + +Often when working with open-source projects, one needs functionality that has not yet passed the full release cycle, or even functionality that is not yet fully implemented. We know that many of you are experimenters and want to try the latest and greatest features of NativeScript. That is why we tried to make this process simple and easy to follow. There are two ways to get the latest development code for NativeScript: + +- You can get it via npm. +- You can build the source code. + +#### Getting the latest development version via npm + +As an open-source project NativeScript keeps not only its source code but its build infrastructure open. That is why we choose [Travis CI](https://travis-ci.org/) for our nightly builds. Every commit in the master branch of all major NativeScript repos triggers a [Travis CI](https://travis-ci.org/) build which publishes an npm package that can be used directly. Follow those simple steps to get the latest development version of NativeScript: + +- Uninstall any existing NativeScript versions: + +```cli +npm uninstall -g nativescript +``` + +- Install the latest development version of NativeScript CLI: + +```cli +npm install -g nativescript@next +``` + +- Edit the package.json file in your project and replace @nativescript/core, @nativescript/android and @nativescript/ios versions with `next`: + +```json +{ + "description": "NativeScript Application", + "dependencies": { + "@nativescript/core": "next" + }, + "devDependencies": { + "@nativescript/android": "next", + "@nativescript/ios": "next" + } +} +``` + +Instead of editing the package.json file by hand, you could run the following commands: + +```cli +ns platform add ios@next +ns platform add android@next +ns plugin add @nativescript/core@next +``` + +- Run the `npm install` command to update the node modules: + +```cli +cd +npm install +``` + +You are now ready to use the latest development version of NativeScript. + +#### Building the source code + +##### Reasoning + + + +Building the source code is essential when one wants to contribute to an open source project. The statement is applicable for NativeScript as well. According to the [Contribution Guidelines](https://github.com/NativeScript/NativeScript/blob/master/tools/notes/CONTRIBUTING.md), suggesting a fix involves testing the latest code. + +#### Behind the curtains of running a NativeScript application + +1. `npm install nativescript -g` : Node Package Manager (npm) downloads and installs the [NativeScript CLI](https://www.npmjs.com/package/nativescript). +2. `ns create [AppName]` : The NativeScript CLI downloads the [Hello-World template](https://www.npmjs.com/package/@nativescript/template-hello-world) and unpacks it to a folder named after the app name you choose. At the same time, the CLI installs the [NativeScript cross-platform modules](https://www.npmjs.com/package/@nativescript/core). As a result, your application folder now contains an `app` folder, holding the files of your application ([source code](https://github.com/NativeScript/nativescript-app-templates/tree/master/packages/template-hello-world)) and a `node_modules` folder, having the cross-platform modules ([source code](https://github.com/NativeScript/NativeScript)). +3. `ns platform add android/ios` : The NativeScript CLI downloads the latest SemVer-compatible version of the specified runtime, unpacks it and applies transformations to the native (Android Studio or xCode) project (e.g., changes the project name). +4. `ns run android/ios` : The NativeScript CLI copies the files under the `app` folder to the `platforms/[android/ios]/.../app` folder following a specific logic so that these get used later by a native build tool (_gradle_/_xcode-build_). As a next step, the NativeScript CLI executes compilation, deployment and run commands of _gradle_ or _xcode-build_. +5. Any JavaScript code gets executed in a V8 or JavaScriptCore engine and embedded in the NativeScript runtimes. Each call to an actual native object gets marshalled via the runtimes to the underlying platform and vice-versa. The runtimes provide JavaScript handles to the native objects. + +#### Contents of the NativeScript repo + +The [NativeScript framework](https://github.com/NativeScript/NativeScript) is built using TypeScript. For that, one of the build steps is TypeScript compilation, which uses TypeScript declarations of the underlying native objects. These are really large files ([android17.d.ts](https://github.com/NativeScript/NativeScript/blob/master/packages/types-android/src/lib/android-17.d.ts) and [ios.d.ts](https://github.com/NativeScript/NativeScript/blob/master/packages/types-ios/src/lib/ios/ios.d.ts)). The TypeScript compilation with these two files loaded in memory takes a lot of time. To save development time and have as quick and stable feature output, the NativeScript team decided to keep several important applications inside the same repository so that all of them get compiled in a single pass. + +Having said that, each subfolder of the [apps](https://github.com/NativeScript/NativeScript/tree/master/apps) subfolder of the repo represents a single application. + + +#### Using the latest + +To use the latest: + +- Build the repo. +- Navigate to your project folder. +- Delete the `@nativescript/core` folder from the `node_modules` subfolder of your project (i.e., `rm -rf node_modules/@nativescript/core` for Linux or `rd /S /Q node_modules\@nativescript/core`). +- Install the newly built package (`npm install [PATH-TO-NATIVESCRIPT-REPO/bin/dist/nativescript-core-x.x.x.tgz]`). + +#### Handling internal breaking changes + +It is possible that an internal breaking change gets introduced involving an update to both the runtimes and the modules. An internal breaking change would mean that the public API of the tns_modules does not get affected, but a work in progress change in the runtimes requires a change in the internal code of the tns_modules themselves. + +When such a case happens, the [ios](https://github.com/NativeScript/ns-v8ios-runtime) and [android](https://github.com/NativeScript/android-runtime) runtimes must be built separately and updated via the CLI command of: +`ns platform update android/ios --frameworkPath=[Path-to-Runtime-Package]` + +#### Building the runtimes + +As the NativeScript framework gets distributed via npm, the runtimes are also packed as npm packages. For consistency reasons, the native builds (gradle/xcode-build) are wrapped by grunt builds that do the job. + +#### Building the Android runtime + +The [android runtime](https://github.com/NativeScript/android-runtime) depends on the [android-metadata-generator](https://github.com/NativeScript/android-metadata-generator). + +Provided you have all the dependencies set, the easiest way to have the Android runtime built is to clone the two repos to a single folder so that the two are sibling folders, `cd` into the `android-runtime` folder and run: + +```cli +gradle packar -PwidgetsPath=./widgets.jar +``` + +The resulting @nativescript/android-x.x.x.tgz package will get created in the `dist` folder. + +#### Building the iOS runtime + +Follow the instructions on setting up the dependencies for building the [ios runtime](https://github.com/NativeScript/ns-v8ios-runtime) in the repository README and then run `grunt package`. + +The build @nativescript/ios-x.x.x.tgx package will get created in the `dist` folder. diff --git a/content/sidebar.ts b/content/sidebar.ts index c95688b9..fed42f80 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -19,10 +19,15 @@ export default [ text: 'Creating a new Project', link: '/creating-a-new-project', }, - // { - // text: 'Development Workflow', - // link: '//#', - // }, + { + text: 'Development Workflow', + items: [ + { + text: 'Updating', + link: '/guide/development-workflow/updating-a-nativescript-app' + } + ] + }, { text: 'Tutorials', link: '/tutorials/', From f5f8d45601e2bd3f2b8f98817defa782e2a91944 Mon Sep 17 00:00:00 2001 From: Ombuweb Date: Mon, 21 Nov 2022 19:02:39 +0100 Subject: [PATCH 02/44] fix: shortened link --- .../updating-a-nativescript-app.md => updating-an-app.md} | 7 ++----- content/sidebar.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) rename content/guide/{development-workflow/updating-a-nativescript-app.md => updating-an-app.md} (97%) diff --git a/content/guide/development-workflow/updating-a-nativescript-app.md b/content/guide/updating-an-app.md similarity index 97% rename from content/guide/development-workflow/updating-a-nativescript-app.md rename to content/guide/updating-an-app.md index e6d41ba7..01d5a247 100644 --- a/content/guide/development-workflow/updating-a-nativescript-app.md +++ b/content/guide/updating-an-app.md @@ -1,5 +1,5 @@ --- -title: Updating a NativeScript app +title: Updating an app --- To upgrade a NativeScript application you need to upgrade several things: NativeScript CLI Tooling, the iOS and Android runtimes and the `@nativescript/core` module. In the steps below you will see how to do this. @@ -10,10 +10,7 @@ To get the latest version of the NativeScript CLI, run: npm install -g nativescript ``` -#### Upgrading a NativeScript application - -You should execute the **update** command in the root folder of your project to upgrade it with the latest versions of iOS/Android runtimes and cross-platform modules. - +To upgrade an app, run: ```cli ns update diff --git a/content/sidebar.ts b/content/sidebar.ts index fed42f80..b238fb4d 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -24,7 +24,7 @@ export default [ items: [ { text: 'Updating', - link: '/guide/development-workflow/updating-a-nativescript-app' + link: '/guide/updating-an-app' } ] }, From febb498afbc35e1d8085e7b10258a021583b3131 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:30:36 -0700 Subject: [PATCH 03/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 01d5a247..f0d62782 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -205,4 +205,4 @@ The resulting @nativescript/android-x.x.x.tgz package will get created in the `d Follow the instructions on setting up the dependencies for building the [ios runtime](https://github.com/NativeScript/ns-v8ios-runtime) in the repository README and then run `grunt package`. -The build @nativescript/ios-x.x.x.tgx package will get created in the `dist` folder. +The @nativescript/ios-x.x.x.tgx package will be generated in the dist folder. From d75e01a2fa591f965baf946c60aaa66010bf2409 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:31:02 -0700 Subject: [PATCH 04/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index f0d62782..27a0a903 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -193,7 +193,7 @@ As the NativeScript framework gets distributed via npm, the runtimes are also pa The [android runtime](https://github.com/NativeScript/android-runtime) depends on the [android-metadata-generator](https://github.com/NativeScript/android-metadata-generator). -Provided you have all the dependencies set, the easiest way to have the Android runtime built is to clone the two repos to a single folder so that the two are sibling folders, `cd` into the `android-runtime` folder and run: +If all dependencies are set, the simplest way to build the Android runtime is by cloning the two repositories into a single folder, ensuring they are sibling folders. Then navigate to the `android-runtime` directory and execute the specified command. ```cli gradle packar -PwidgetsPath=./widgets.jar From 4f8a731b9194c35659c3167699ddc6e0796b338f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:31:20 -0700 Subject: [PATCH 05/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 27a0a903..483a7b3f 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -187,7 +187,7 @@ When such a case happens, the [ios](https://github.com/NativeScript/ns-v8ios-run #### Building the runtimes -As the NativeScript framework gets distributed via npm, the runtimes are also packed as npm packages. For consistency reasons, the native builds (gradle/xcode-build) are wrapped by grunt builds that do the job. +The NativeScript framework and runtimes are distributed as npm packages. To maintain consistency, the native builds (gradle/xcode-build) are encapsulated within grunt builds. #### Building the Android runtime From 5367c0ef79b4178a7ff7a186cd35ba34e7cc3535 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:31:53 -0700 Subject: [PATCH 06/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 483a7b3f..e5103d31 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -180,7 +180,7 @@ To use the latest: #### Handling internal breaking changes -It is possible that an internal breaking change gets introduced involving an update to both the runtimes and the modules. An internal breaking change would mean that the public API of the tns_modules does not get affected, but a work in progress change in the runtimes requires a change in the internal code of the tns_modules themselves. +It is possible for an internal breaking change to be introduced, affecting both the runtimes and core. This type of change requires modifications to the internal code of @nativescript/core, while the public API remains unaffected. When such a case happens, the [ios](https://github.com/NativeScript/ns-v8ios-runtime) and [android](https://github.com/NativeScript/android-runtime) runtimes must be built separately and updated via the CLI command of: `ns platform update android/ios --frameworkPath=[Path-to-Runtime-Package]` From 6e8b0f30285f98dc2a94d8c00bf4d8c66198b6b1 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:32:35 -0700 Subject: [PATCH 07/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index e5103d31..61eab036 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -2,7 +2,7 @@ title: Updating an app --- -To upgrade a NativeScript application you need to upgrade several things: NativeScript CLI Tooling, the iOS and Android runtimes and the `@nativescript/core` module. In the steps below you will see how to do this. +To upgrade a NativeScript app, ensure to update the NativeScript CLI Tooling, iOS and Android runtimes, and @nativescript/core. The following steps demonstrate the upgrade process. To get the latest version of the NativeScript CLI, run: From 36edd116ebe360febe05139b980bd721c3597239 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:47:27 -0700 Subject: [PATCH 08/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 61eab036..8ade7c61 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -129,7 +129,7 @@ npm install -g nativescript@next } ``` -Instead of editing the package.json file by hand, you could run the following commands: +Instead of editing the package.json file directly, the following commands can be run: ```cli ns platform add ios@next From 7bc3031506527a652b33427d508ba2c41edd2aa3 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:48:41 -0700 Subject: [PATCH 09/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 8ade7c61..22427759 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -100,7 +100,7 @@ Often when working with open-source projects, one needs functionality that has n #### Getting the latest development version via npm -As an open-source project NativeScript keeps not only its source code but its build infrastructure open. That is why we choose [Travis CI](https://travis-ci.org/) for our nightly builds. Every commit in the master branch of all major NativeScript repos triggers a [Travis CI](https://travis-ci.org/) build which publishes an npm package that can be used directly. Follow those simple steps to get the latest development version of NativeScript: +As an open-source project NativeScript keeps not only its source code, but its build infrastructure open. Every commit in the main branch of all major NativeScript repos triggers a Github Action Workflow that publishes a npm package, which can be used directly. Follow these steps in order to get the latest development version of NativeScript: - Uninstall any existing NativeScript versions: From 01bce6797452aff68387751279f055638ed0e84f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:49:26 -0700 Subject: [PATCH 10/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 22427759..9025e8c5 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -73,7 +73,7 @@ Another place to find **@nativescript/core** package is [NativeScript Releases]( #### Upgrading Angular dependencies -The Angular plugin is available as an npm package named [@nativescript/angular](https://www.npmjs.com/package/@nativescript/angular). To update the version of the plugin and the related dependency, the package should be explicitly installed, and the related Angular dependencies should be updated accordingly. To ease the update process, the plugin comes with an automated script `update-app-ng-deps` located in `` folder. +The Angular plugin is available as [@nativescript/angular](https://www.npmjs.com/package/@nativescript/angular). To update the version of the plugin and the related dependency, the package should be explicitly installed, and the related Angular dependencies should be updated accordingly. To ease the update process, the plugin comes with an automated script `update-app-ng-deps` located in `` folder. ```cli npm i @nativescript/angular@latest --save From c10cad2fdcf16ad6cf67f0e3463a808261828c18 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 26 Aug 2023 09:50:12 -0700 Subject: [PATCH 11/44] Update content/guide/updating-an-app.md Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/updating-an-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/updating-an-app.md b/content/guide/updating-an-app.md index 9025e8c5..000458d2 100644 --- a/content/guide/updating-an-app.md +++ b/content/guide/updating-an-app.md @@ -93,7 +93,7 @@ previous_url: /running-latest #### Running the Latest Code -Often when working with open-source projects, one needs functionality that has not yet passed the full release cycle, or even functionality that is not yet fully implemented. We know that many of you are experimenters and want to try the latest and greatest features of NativeScript. That is why we tried to make this process simple and easy to follow. There are two ways to get the latest development code for NativeScript: +Often when working with open-source projects, at times there is a requirement for functionality that has not yet passed the full release cycle, or even functionality that is not yet fully implemented. We know that many developers using open-source software are experimenters and would wish to enjoy the option to try out the latest and greatest features of NativeScript. This is why we tried to make this process simple and easy to follow. There are two ways to get the latest development code for NativeScript: - You can get it via npm. - You can build the source code. From ea208d14ae66c20e9183382b1c26921ee72f3418 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:41:30 +0200 Subject: [PATCH 12/44] docs: Property System (#60) Co-authored-by: Nathan Walker --- content/guide/property-system.md | 329 +++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 333 insertions(+) create mode 100644 content/guide/property-system.md diff --git a/content/guide/property-system.md b/content/guide/property-system.md new file mode 100644 index 00000000..8ee32f0f --- /dev/null +++ b/content/guide/property-system.md @@ -0,0 +1,329 @@ +--- +title: Property +--- + +The property system in NativeScript allows for the exposure of native platform properties to JavaScript components with certain guarantees built-in; for instance: +- it will set properties only after the native platform object is created +- it allows for property definitions with custom value converters +- it auto detects if property values change +- it can suspend updating of values when desired +- it can auto request layout updates when properties change when desired + +Within NativeScript, the Property class serves as a fundamental property system, encompassing a straightforward enhancement to the [Object.defineProperty](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) function. This class also incorporates supplementary callbacks such as valueChange, valueConverter, and equalityComparer. + +## Using Property + +### Adding a new property to a View instance +Add new properties by creating a new `Property` with desired [options](#propertyoptions-interface). + +```ts +class MyButtonBase extends Button{} +export const textProperty = new Property({ + name: "text", + defaultValue: "", + affectsLayout: true +}) +``` +Then register the property with the desired class: + +```ts +textProperty.register(MyButtonBase); +``` +To apply the JS value to the exposed native view, call the `setNative` method on the property instance. + +```ts +[textProperty.setNative](value: string) { + this.nativeView.text = value +} +``` +To gain a comprehensive understanding of how the Property system is employed in NativeScript to expose a native UI component, we recommend reading the informative article titled [How to create a NativeScript plugin that uses native iOS & Android views (Part 1) - Label Marquee!](https://blog.nativescript.org/create-a-custom-view-plugin-marquee-label/). This article provides a practical real-world example that demonstrates the process of developing a NativeScript plugin while utilizing native iOS and Android views. +### Adding a new CSS property +To add a new CSS property, use the [CssProperty]() class. It extends the [Property]() class to accept the extra `cssName` option. CSS properties are registered with the [Style](https://docs.nativescript.org/api-reference/classes/style) class. + +```ts +import { CssProperty, Style } from "@nativescript-core" + +export const myOpacityProperty = new CssProperty({ + name: "myOpacity", + cssName: "my-opacity", + defaultValue: 1, + valueConverter: (v) => { + const x = parseFloat(v); + if (x < 0 || x > 1) { + throw new Error(`opacity accepts values in the range [0, 1]. Value: ${v}`); + } + + return x; + } +}); +myOpacityProperty.register(Style); +``` + +Subsequently, any instance of the [Style](https://docs.nativescript.org/api-reference/classes/style) class will have `myOpacity` property and developers can set it in CSS as follows: + +```css +.some-class { + my-opacity: 0.5 +} +``` + +#### Inheritable property +To create a CSS property that can be applied to a view and is inheritable by the children of that view, instanciate the [InheritedCssProperty](https://docs.nativescript.org/api-reference/classes/inheritedcssproperty) class passing it an [PropertyOptions](#property-options-interface) object. Then use the `setInheritedValue: (value: U)` method to set the inherited value. +The examples of inherited properties are the`FontSize`, `FontWeight`, `Color`, etc. + +```ts +import { Color, InheritedCssProperty, Style } from "@nativescript-core" + +export const selectedBackgroundColorProperty = new InheritedCssProperty({ + name: "selectedBackgroundColor", + cssName: "selected-background-color", + equalityComparer: Color.equals, + valueConverter: (v) => new Color(v) +}); +selectedBackgroundColorProperty.register(Style); +``` +#### Shorthand property +To create a CSS property that can be applied using the shorthand syntax rules, use the [ShorthandProperty](https://docs.nativescript.org/api-reference/classes/shorthandproperty) class, passing it the [ShorthandPropertyOptions](#shorthandpropertyoptions-interface) object. For example, instead of setting margin using seperate rules for each side, +```css +.title { + margin-top: 0; + margin-right: 10; + margin-bottom: 0; + margin-left: 10; +} +``` +you can use one rule for all the sides. +```css +.title { + margin: 0 10 0 10 +} +``` +To create a shorthand property, use the `CssProperty` class to define all the properties individually as normal. Then return the shorthand with the `getter()` method of the `ShorthandProperty` class. +Here's an example of how the margin shorthand is implemented: +```ts +const marginProperty = new ShorthandProperty({ + name: 'margin', + cssName: 'margin', + getter: function (this: Style) { + if (PercentLength.equals(this.marginTop, this.marginRight) && PercentLength.equals(this.marginTop, this.marginBottom) && PercentLength.equals(this.marginTop, this.marginLeft)) { + return this.marginTop; + } + + return `${PercentLength.convertToString(this.marginTop)} ${PercentLength.convertToString(this.marginRight)} ${PercentLength.convertToString(this.marginBottom)} ${PercentLength.convertToString(this.marginLeft)}`; + }, + converter: convertToMargins, +}); +marginProperty.register(Style); +### Creating a coercible property +To create a coercible property use the [CoercibleProperty](https://docs.nativescript.org/api-reference/classes/coercibleproperty) class passing it a [CoerciblePropertyOptions](#co) object. + +```ts +export const selectedIndexProperty = new CoercibleProperty({ + name: "selectedIndex", defaultValue: -1, + valueChanged: (target, oldValue, newValue) => { + target.notify({ eventName: SegmentedBar.selectedIndexChangedEvent, object: target, oldIndex: oldValue, newIndex: newValue }); + }, + + // in this case the coerce value will change depending on whether the actual number of items + // is more or less than the value we want to apply for selectedIndex + coerceValue: (target, value) => { + let items = target.items; + if (items) { + let max = items.length - 1; + if (value < 0) { + value = 0; + } + if (value > max) { + value = max; + } + } else { + value = -1; + } + + return value; + }, + + valueConverter: (v) => parseInt(v) +}); +selectedIndexProperty.register(SegmentedBar); +``` +Subsequently, when assigning a value to the property, invoke the `coerce()` method. +```ts +[itemsProperty.setNative](value: SegmentedBarItem[]) { + ... + selectedIndexProperty.coerce(this); +} +``` +## Property System Reference +### Property class +The Property class has the following members. +#### constructor +```ts +property = new Property(propertyOptions) +``` +### enumerable +```ts +isEnumerable: boolean = property.enumerable +``` +--- + +### configurable +```ts +isConfigurable: boolean = property.configurable +``` +--- +### writable +```ts +isWritable: boolean = property.writable +``` +--- + +#### name +```ts +propertyName: string = property.name +``` +--- +#### getDefault +```ts +[property.getDefault]() +``` +#### setNative +```ts +[property.setNative](value){ + +} +``` + +#### defaultValue +```ts +defaultValue: U = property.defaultValue +``` +--- +#### nativeValueChange +```ts +property.nativeValueChange(owner: T, value: U) +``` +--- +#### isStyleProperty +```ts +isStyleProperty: boolean = property.isStyleProperty +``` +--- +### get() +```ts +propertyValue: U = property.get() +``` +--- +### set() +```ts +property.set(value: U) +``` +--- +### register() +```ts +property.register(SomeClass) +``` +--- +### isSet() +```ts +isPropertySet: boolean = property.isSet(instance: T) +``` +--- +### overrideHandlers() + +```ts +property.overrideHandlers(options: PropertyOptions) +``` +--- + +### PropertyOptions Interface +#### name +```ts +{ + name: "propertyName" +} +``` + +--- +#### defaultValue +```ts +{ + defaultValue: someValue +} +``` + +--- +#### affectsLayout +```ts +{ + affectLayout: true +} +``` +--- +#### equalityComparer +```ts +{ + equalityComparer: (x,y)=>{ + // compare x to y and return boolean + return true + } +} +``` +#### valueChanged +```ts +{ + valueChanged: (target: T, oldValue: U, newValue: U)=>{ + + } +} +``` +#### valueConverter +```ts +{ + valueConverter: (value: string)=>{ + + return someValue + } +} +``` + +### CssPropertyOptions interface +#### cssName + ```ts +{ + cssName: "my-css-property" +} + ``` +The property name intended for utilization within a CSS rule. + +### ShorthandPropertyOptions Interface +```ts +{ + name: "newProperty" +} +``` + +--- +#### cssName + ```ts +{ + cssName: "my-css-property" +} + ``` + +--- +#### converter() +```ts +{ + +} +``` +---- +#### getter() +```ts +{ + getter: function (this: Style){ + return cssValue + } +} +``` diff --git a/content/sidebar.ts b/content/sidebar.ts index 1617472f..2a9b601c 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -160,6 +160,10 @@ export default [ text: 'Code Sharing', link: '/guide/code-sharing', }, + { + text: 'Property System', + link: '/guide/property-system' + }, { text: 'Shared Element Transitions', link: '/guide/shared-element-transitions', From efebc48642a280d02348b9d5573b2a97f52ae2e4 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 19:59:35 +0200 Subject: [PATCH 13/44] docs: Utils (#54) Co-authored-by: Nathan Walker --- content/guide/core/utils.md | 570 ++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 574 insertions(+) create mode 100644 content/guide/core/utils.md diff --git a/content/guide/core/utils.md b/content/guide/core/utils.md new file mode 100644 index 00000000..abf6f4d9 --- /dev/null +++ b/content/guide/core/utils.md @@ -0,0 +1,570 @@ +--- +title: Utils +--- + +The Utils object offers utilities properties and methods. + +## Using Utils + +### Validating a path for a resource or a local file + +To verify if a path is valid resource or local file path, use the [isFileOrResourcePath()](#isfileorresourcepath) method: + +```ts + +const isPathValid: boolean = Utils.isFileOrResourcePath('https://thiscatdoesnotexist.com/') // false + +// or + +const isPathValid: boolean = Utils.isFileOrResourcePath('res://icon') // true +``` + +### Check if a URI is a data URI + +To check if a specific [URI](https://en.wikipedia.org/wiki/Data_URI_scheme) is a valid data URI, use the [isDataURI()](#isdatauri) method. + +```ts +const isDataURI: boolean = Utils.isDataURI(`data:image/png;base64,iVBORw0KGgoAAA + ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 + //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU + 5ErkJggg==`) // true + +// or +const isDataURI: boolean = Utils.isDataURI(`base64,iVBORw0KGgoAAA + ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4 + //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU + 5ErkJggg==`) // false +``` + +### Opening a url +The [openUrl()](#openurl) method allows you to open a url and returns `true` if a browser tab with the specified url opened successfully or `false` otherwise. + +```ts +const didUrlOpen: boolean = Utils.openUrl('https://thiscatdoesnotexist.com/') + +``` + +### Escaping regex symbols +To escape regex metacharacters in string, use the [escapeRegexSymbols()](#escaperegexsymbols) method. +```ts + const escapedString: string = Utils.escapeRegexSymbols('$hello') // \$hello +``` +### Check if the application is running on a physical device + +```ts +const isOnPhysicalDevice: boolean = Utils.isRealDevice() +``` + +### Checking if a value is a number +To check if a value is a number, use the [isNumber()](#isnumber) method. + +```ts +const isANumber: boolean = Utils.isNumber(2) // true +const isNumber: boolean = Utils.isNumber("test") // false +const isNumber: boolean = Utils.isNumber(true) // false +``` + +### Get a class hierarchy of an object +To list all the classes an object is an instance of, use the [getBaseClasses()](#getbaseclasses) method: + +```ts +const labelHierarchy: Array = Utils.getBaseClasses(new Label()); + /*[ + Label, + TextBase, + TextBaseCommon, + View, + ViewCommon, + ViewBase, + Observable, + Object]* + / +``` + +### Hiding a keyboard + +To hide a soft keyboard on the screen, use the [dismissKeyboard()](#dismisskeyboard) method. + +```ts +Utils.dismissKeyboard() +``` + +## Utils API + +### MajorVersion +```ts +const majorVersion: number = Utils.ios +``` + +(`iOS only`) Gets the iOS device major version. For example, for `16.0` ,it returns `16`. + +--- + +### isFileOrResourcePath() +```ts +const isFileOrResourcePath: boolean = Utils.isFileOrResourcePath(path) +``` +Returns `true` if the specified path points to a resource or local file. + +---- + +### isDataURI() +```ts +const isDataURI: boolean = Utils.isDataURI(uri) +``` + +--- +### openUrl() + +```ts +const isUrlOpened: boolean = Utils.openUrl(url) +``` +Opens the passed url using the default application. + +--- +### escapeRegexSymbols() + +```ts +const escapedString: string = Utils.escapeRegexSymbols(string) +``` + +Escapes special regex characters (`.`, `*`, `^`, `$` and so on) in a string and returns a valid regex. + +--- +### convertString() +```ts +const toStr: number | boolean = Utils.convertString(value) +``` + +Converts a string value to a number or boolean. If it can not convert, it returns the passed string. + +--- +### GC() +```ts +Utils.GC() +``` + +A utility function that invokes garbage collection on the JavaScript side. + +--- +### queueMacrotask() +```ts +Utils.queueMacrotask(task: () => void) +``` + +Queues the passed function to be ran as a macroTask. + +--- + +### queueGC() +```ts +Utils.queueGC(delay, useThrottle) +``` +- _Optional_: `delay` time, in milliseconds, to wait before garbage collection starts. +- _Optional_: `useThrottle` If set to `true` throttling is used instead of default debounce strategy. + +A utility function that queues a garbage collection. Multiple calls in quick succession are debounced by default and only one gc will be executed after 900ms. + +--- +### debounce() +```ts + const debouncedFn =Utils.debounce(fn,delay) + + debouncedFn() +``` + +A simple debounce utility. +- `fn` The function to debounce. +- _Optional_:`delay` delays the bouncing, in milliseconds. Defaults to 300ms. +--- +### throttle() +```ts + const throttledFn =Utils.throttle(fn,delay) + +throttledFn() +``` + +A simple throttle utility. +- `fn` The function to throttle. +- _Optional_:`delay` delays the throttling, in milliseconds. Defaults to 300ms. + +--- +### isFontIconURI() +```ts +const isFontIconURI: boolean = Utils.isFontIconURI("font://") +``` +Returns true if the specified URI is a font icon URI. + +--- +### executeOnMainThread() +```ts +Utils.executeOnMainThread(fn: Function) + +``` +Checks if the current thread is the main thread. If it is, calls the passed function. Otherwise, it dispatches it to the main thread. + +--- +### executeOnUIThread() +```ts +Utils.executeOnUIThread(fn: Function) +``` +Runs the passed function on the UI Thread. + +--- +### mainThreadify() +```ts +Utils.mainThreadify(fn: Function): (...args: any[]) +``` + +Returns a function wrapper which executes the supplied function on the main thread. The wrapper behaves like the original function and passes all of its arguments BUT discards its return value. + +--- +### isMainThread() +```ts +const isMainThread: boolean = Utils.isMainThread() +``` +Boolean value indicating whether the current thread is the main thread. + +--- +### dispatchToMainThread() +```ts +Utils.dispatchToMainThread(fn: Function) +``` +Dispatches the passed function for execution on the main thread. + +--- +### releaseNativeObject() +```ts +Utils.releaseNativeObject(object: java.lang.Object | NSObject) +``` +Releases the reference to the wrapped native object. + +--- +### getModuleName() +```ts +const moduleName: string = Utils.getModuleName(path: string) +``` + +Gets module name from the specified path. + +--- +### openFile() +```ts +const didFileOpen: boolean = Utils.openFile(filePath, title) +``` +Opens the file at the specified `filePath`. +_Optional_: (`Android-only`) `title` is the title of the file viewer. + +--- +### isRealDevice() +```ts +const isOnPhysicalDevice: boolean = Utils.isRealDevice() +``` +Checks whether the application is running on a physical device and not on a simulator/emulator. + +--- +### getClass() +```ts +const objectClass: string = Utils.getClass(object) +``` +Gets the class name of an object. + +--- +### getBaseClasses() +```ts +const objectParentClasses: Array = Utils.getBaseClasses(object) +``` +Returns an entire class inheritance hierarchy for the specified object. + +--- +### getClassInfo() +```ts +const objectClassInfo: ClassInfo = Utils.getClassInfo(object) +``` +Gets the `ClassInfo` for an object. `ClassInfo` object has the following properties: +- `_name`: The name of the class of the object. + +--- +### isBoolean() +```ts +const isValueBoolean: boolean = Utils.isBoolean(someValue) +``` +Checks if the specified value is a valid boolean. + +--- +### isDefined() + +```ts +const isValueDefined: boolean = Utils.isDefined(someValue) +``` + +Checks if the specified value is not `undefined`. + +--- +### isUndefined() +```ts +const isUndefined: boolean = Utils.isUndefined(someValue) +``` + +Checks if a value is `undefined`. + +--- +### isNullOrUndefined() +```ts +const isNullOrUndefined: boolean = Utils.isNullOrUndefined(someValue) +``` + + Checks if a value is `null` or `undefined`. + +--- +### isFunction() +```ts +const isValueAFunction: boolean = Utils.isFunction(someValue) +``` +Checks if a value is a function. + +--- + +### isNumber() +```ts +const isNumber: boolean = Utils.isNumber(someValue) +``` +Checks if a value is a valid number. + +--- +### isObject() +```ts +const isObject: boolean = Utils.isObject(someValue) +``` +Returns true if a value is an object or array. + +--- +### isString() +```ts +const isString = boolean = Utils.isString(someValue) +``` +Checks if a value is a string. + +--- +### toUIString() +```ts +const stringified: string = Utils.toUIString(object) +``` + +Returns a string representation of an object to be shown in UI. + +--- + +### dataSerialize() +```ts +const serializedData = Utils.dataSerialize(data, wrapPrimitives) +``` + +Data serialization from JS > Native. +- _Optional_: `data` is the JavaScript data to serialize. +- _Optional_: `wrapPrimitives` is a `boolean` parameter indicating whether to wrap primitive data types. + +--- +### dataDeserialize() +```ts +const dataDeserialized = Utils.dataDeserialize(nativeData) +``` +Data deserialization from Native to JS. +- _Optional_ `nativeData` data to be deserialized. + +--- +### setInterval() +```ts +const timerID: number = Utils.setInterval((args)=>{ + +}, milliseconds,[arg1, arg2]) +``` +A timer method that allows you to run `callback` every `milliseconds`(milliseconds). It returns an id that is used to [stop](#clearinterval) the timer. + +--- +### clearInterval() +```ts +Utils.clearInterval(timerID) +``` +Stops the interval timer of the provided id. + +--- +### setTimeout() +```ts +const timerId: number = Utils.setTimeout((args)=>{ +}, milliseconds,[arg1, arg2]) +``` + +A timer method that allows you to wait `milliseconds`(milliseconds) before running the `callback`. It returns an id to be used to [stop](#clearinterval) the timer. + +--- + +### dismissKeyboard() +```ts +Utils.dismissKeyboard() +``` +Hides any keyboard on the screen. + +--- +### getApplication() + +```ts +const app: android.app.Application = Utils.android.getApplication() +``` +(`Android-only`)Gets the native Android application instance. Also see [native app](/guide/core/application#nativeapp). + +--- +### getApplicationContext() +```ts +Utils.android.getApplicationContext() +``` + +(`Android-only`) Gets the Android application [context](https://developer.android.com/reference/android/content/Context). + +--- +### getInputMethodManager() + +```ts +const inputMethodManager: android.view.inputmethod.InputMethodManager = Utils.android.getInputMethodManager() +``` + +(`Android-only`)Gets the native Android [InputMethodManager](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager) instance. + +--- +### showSoftInput() +```ts +Utils.android.showSoftInput(nativeView) +``` + +(`Android-only`)Shows a soft keyboard. `nativeView` is an `android.view.View` instance to disable the soft input for. + +--- +### stringArrayToStringSet() +```ts +const stringSet: java.util.HashSet = Utils.android.collections.stringArrayToStringSet(str: string[]) +``` +Converts string array into a String [hash set](http://developer.android.com/reference/java/util/HashSet.html). + +--- +### stringSetToStringArray() +```ts +const stringArray: string[] = Utils.android.collections.stringSetToStringArray(stringSet) +``` +Converts string hash set into array of strings. + +--- +### getDrawableId() +```ts +const drawableId: number = Utils.android.resources.getDrawableId(resourceName) +``` + +Gets the drawable id from a given resource name. + +--- +### getStringId() +```ts +const stringId: number = Utils.android.resources.getStringId(resourceName) +``` + +Gets the string id from a given resource name. + +--- + +### getPaletteColor() +```ts +const paletteColor: number = Utils.android.resources.getPaletteColor(resourceName, Utils.android.getApplicationContext()) + +``` +Gets a color from the current theme. + +--- + +### joinPaths() +```ts +const joinedPath: string = Utils.ios.joinPaths("photos", "cat.png") +``` + +Joins the passed strings into a path. + +--- +### getWindow() +```ts +const window: UIWindow = Utils.ios.getWindow() +``` +Gets the UIWindow of the app. + +--- + +### copyToClipboard() +```ts +Utils.copyToClipboard(value): +``` + +Copies the specified value to device clipboard. + +--- +### setWindowBackgroundColor() +```ts +Utils.ios.setWindowBackgroundColor(someColorString) +``` + +Sets the window background color of base view of the app. Often this is shown when opening a modal as the view underneath scales down revealing the window color. + +--- +### getCurrentAppPath() +```ts +const appPath: string = Utils.ios.getCurrentAppPath() +``` +Gets the root folder for the current application. Also see [currentApp()](/guide/core/file-system#currentapp) + +--- +### getVisibleViewController() +```ts +const visibleView: UIViewController = Utils.ios.getVisibleViewController(rootViewController: UIViewController) +``` + +Gets the currently visible(topmost) UIViewController.`rootViewController` is the root UIViewController instance to start searching from (normally window.rootViewController). + +--- + +### getRootViewController() +```ts +const rootView: UIViewController = Utils.ios.getRootViewController() +``` +Gets the root [UIViewController](https://developer.apple.com/documentation/uikit/uiviewcontroller) of the app. + +--- + +### getShadowLayer() +```ts +const shadowLayer: CALayer = Utils.ios.getShadowLayer(nativeView, name, create) +``` + +--- +- `nativeView` is a [UIView](https://developer.apple.com/documentation/uikit/uiview/) instance to find shadow layer with +- _Optional_: `name`(`string`) is the name of the shadow layer if looking for a specific layer. +- _Optional_: `create`(`boolean`) if set to `true`, it indicates that a new layer should be created if no layer is found. + +### createUIDocumentInteractionControllerDelegate() +Create a [UIDocumentInteractionControllerDelegate](https://developer.apple.com/documentation/uikit/uidocumentinteractioncontrollerdelegate) implementation for use with [UIDocumentInteractionController](https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller) + + +--- +### jsArrayToNSArray() +```ts +const jsArrayToNSArray : NSArray =Utils.ios.collections.jsArrayToNSArray(str: T[]) +``` +(`iOS only`)Converts JavaScript array with elements of type `T` to [NSArray](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/) of type `T`. + +--- +### nsArrayToJSArray() +```ts +const nsArrayToJSArray: Array = nsArrayToJSArray(a: NSArray) +``` +(`iOS only`)Converts NSArray of elements of a type `T` to an equivalent JavaScript array. + +--- + +## API Reference(s) + +- [@nativescript/core/utils](https://docs.nativescript.org/api-reference/modules#utils) module \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 2a9b601c..66d91772 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -147,6 +147,10 @@ export default [ text: 'ImageCache', link: '/guide/core/image-cache', }, + { + text: 'Utils', + link: '/guide/core/utils' + }, ], }, { From babdfedf4a8bfd5453ed8254fff9959a4b6060ea Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:56:35 +0200 Subject: [PATCH 14/44] docs: Navigation (#59) Co-authored-by: Nathan Walker --- content/guide/core/navigation.md | 356 +++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 360 insertions(+) create mode 100644 content/guide/core/navigation.md diff --git a/content/guide/core/navigation.md b/content/guide/core/navigation.md new file mode 100644 index 00000000..e565318f --- /dev/null +++ b/content/guide/core/navigation.md @@ -0,0 +1,356 @@ +--- +title: Navigation +description: Navigate from screen to screen in your app. +--- + + + +Navigation in NativeScript is enabled by the `Frame` class. + +## How to use the Frame class + +The following sections will show you how to access an instance of the Frame class and use it to navigate between pages in your app. + + +### Navigating to another page + +To navigate from one `Page` to another, first obtain the desired [Frame instance](#getting-a-frame-instance). +For simple navigation, call the [navigate](#navigate) method passing along the path of the page to navigate to. + +```ts +frame.navigate("~/pages/details/details-page") +``` + +:::tip Note +For a complete navigation example, have a look at [Setup navigation from home to details component](/tutorials/build-a-master-detail-app-with-plain-typescript#setup-navigation-from-home-to-details-component). +::: + +### Getting a Frame instance + +The following are some of the ways in which to obtain a Frame: + +1. Calling the static [topmost](#topmost) + +```ts +const frame = Frame.topmost(); +``` +2. Via the `frame` property of a [Page](/ui/page) + +For example, you can get the current Frame instance from a `tap` event's data as follows: + +```ts +onFlickTap(args: EventData): void { + const btn = args.object as Button; + const frame = btn.page.frame +} +``` +1. Calling the static [getFrameById](#getframebyid) with the id of the frame instance of interest + +```ts +const frame = Frame.getFrameById("frame-id") +``` + +### Navigating back + +To navigate back to the previous page, use the [goBack](#goback) method of the Frame instance. +```ts +frame.goBack() +``` + +### Avoid navigating back to the previous page + +To avoid navigating to the previous page, set the [clearHistory](#clearhistory) property of the [NavigationEntry](#navigation-entry-interface) object that you pass to the [navigate](#navigate) method to `true`. + +```ts +frame.navigate({ + moduleName: "details/details-page", + clearHistory: true +}) +``` +### Passing data between pages + +To pass data to the page you are navigating to, set the value of the [context](#context) property of the NavigationEntry to the data you would like to pass. +```ts +frame.navigate({ + moduleName: "details/details-page", + context: { id: 2 } +}) +``` + +To access the passed data, handle the `navigatedTo` event for the `details/details-page` page and access the `context` property on the event's [NavigatedData](https://docs.nativescript.org/api-reference/interfaces/navigateddata) object. + +```ts +import { NavigatedData } from "@nativescript/core" + +onNavigatedTo(args: NavigatedData) { + this.id = args.context.id + this.notifyPropertyChange("id", args.context.id) + } +``` +### Creating multiple frames + +If you need to create multiple frames, you can do so by wrapping them in a container layout. For example if you want to have 2 frames side-by-side, you can wrap them in a GridLayout: + +```xml + + + + +``` +## Frame class API + +The following are the properties and methods of the Frame class: + +### defaultAnimatedNavigation + +```ts +Frame.defaultAnimatedNavigation = true +``` + +Gets or sets if navigation transitions should be animated globally. + +--- +### defaultTransition +```ts +const defaultTransition: NavigationTransition = { + curve: CoreTypes.AnimationCurve.easeInOut, + duration: 500, + name: "slideRight" +} +Frame.defaultTransition = defaultTransition +//or +const defaultTransition: NavigationTransition = Frame.defaultTransition + +``` +Gets or sets the default [NavigationTransition](#navigation-transition-interface) for all frames across the app. To set a naviagation transtion for a specific frame instance, use the [transition](#transition) property. + +--- + +### backStack +```ts +const backStack: Array = frame.backStack +``` +Gets the back stack for a Frame instance. It is empty if the current page is the app's initial page(eg `main-page`) or if the [clearHistory](#clearhistory) property is set to `true`. + +--- +### currentPage +```ts +const page: Page = frame.currentPage +``` +Gets the Page instance the Frame is currently navigated to. + +--- +### currentEntry +```ts +const currentEntry: NavigationEntry = frame.currentEntry +``` +Gets the NavigationEntry instance the Frame is currently navigated to. + +--- +### animated +```ts +const animated: boolean = frame.animated. +//or +frame.animated = true +``` +Gets or sets if navigation transitions should be animated. + +--- +### transition +```ts +const transtion: NavigationTransition = frame.transition +//or +frame.transition = { + curve: CoreTypes.AnimationCurve.easeInOut, + duration: 500, + name: "slideRight", + } +``` +Gets or sets the default [navigation transition](#navigation-transition-interface) for this frame. + +--- +### actionBarVisibility +```ts +frame.actionBarVisibility = "never" +``` + +It controls the visibility of an actionbar across all the pages in a Frame instance. +Available values: +- `auto` +- `always` +- `never` (hides the action bar for all pages) + +--- +### navigate() +This method has the following overloads: +- `navigate(pageFileName: string)` - navigates to the page instance in the specified file. +- `navigate(create: () => Page)` - creates a new Page instance using the provided callback and navigates to that instance. + +- `navigate(entry: NavigationEntry)` is for a more customized navigation. It allows you to pass data to another page or set transition animation, etc. + +### goBack() + +```ts +frame.goBack(to) +``` +Navigates back using the navigation stack within a Frame. +- _Optional_ `to`: The back stack entry object for where to navigate back to. The object has the following properties: + +- `entry`(type: [NavigationEntry](#navigation-entry-interface)) + +- (`resolvedPage`)(type: Page) + +--- +### getFrameById() +```ts +const frame = Frame.getFrameById(id: string) +``` +Gets a frame with the specified id. + +--- + +### topmost() +```ts +const frame = Frame.topmost() +``` +Gets the topmost frame in the frames stack. + +--- + +### canGoBack() +```ts +const canGoBack = frame.canGoBack() +``` +Checks whether the goBack operation is available. + +--- +## Navigation Transition Interface +The Navigation Transition interface has the following members: + +### name +```ts +{ + name: "slideLeft" +} +``` +The type of the transition. Possible values: + +- `curl` (same as curlUp) (iOS only) +- `curlUp` (iOS only) +- `curlDown` (iOS only) +- `explode` (Android Lollipop(21) and up only) +- `fade` +- `flip` (same as flipRight) +- `flipRight` +- `flipLeft` +- `slide` (same as slideLeft) +- `slideLeft` +- `slideRight` +- `slideTop` +- `slideBottom` +--- + +### custom transition +```ts +{ + instance: customTransitionInstance +} +``` +This property allows you to define custom transition instance of the [Transition](https://docs.nativescript.org/api-reference/classes/transition) class. + +--- + +### duration +```ts +{ + duration: 500 +} +``` +The length of the transition in milliseconds + +--- +### curve +```ts +import { CoreTypes } from "@nativescript/core"; + +{ + curve: CoreTypes.AnimationCurve.easeInOut, +} +``` +A transition animation curve with available values contained in the [AnimationCurve enumeration](https://docs.nativescript.org/api-reference/modules/coretypes.animationcurve). +Alternatively, you can pass an instance of type [UIViewAnimationCurve](https://developer.apple.com/documentation/uikit/uiview/animationcurve) for iOS or [android.animation.TimeInterpolator](https://developer.android.com/reference/android/animation/TimeInterpolator) for Android. + +--- + +## Navigation Entry Interface + +### moduleName +```ts +{ + moduleName: "details/details-page" +} +``` +The name of the page (a View instance) to navigate to. + +--- +### create() +```ts +{ + create() { + const page = new Page() + const actionBar = new ActionBar() + actionBar.title = "Test heading" + + const stack = new StackLayout() + stack.backgroundColor = "#76ABEB" + + page.actionBar = actionBar; + page.content = stack + return page + }, + } +``` +Creates a the page(View instance) to be navigated to + +--- +### context +```ts +{ + context: any +} +``` +An object used to pass data to the page navigated to. + +--- + +### transitionAndroid + +_Optional_: Specifies a navigation transition for Android. This property has a higher priority than the [transition](#transition) property. + +--- +### transitioniOS + +_Optional_: Specifies a navigation transition for iOS. This property has a higher priority than the [transition](#transition) property. + +--- +### backstackVisible +```ts +frame.backstackVisible = true +``` + +A boolean value, if set to `true`, it records the navigation in the backstack. If set to `false`, the Page will be displayed but once navigated from it is not possible to navigate back. + +--- +### clearHistory +```ts +{ + clearHistory: true +} +``` +If set to `true`, it clears the navigation history. This very useful when navigating away from a login page. + +## API Reference(s) +- [Frame](https://docs.nativescript.org/api-reference/classes/frame) class +## Native Component +- `Android`: [org.nativescript.widgets.ContentLayout](https://github.com/NativeScript/NativeScript/blob/master/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/ContentLayout.java) +- `iOS`: [UINavigationController](https://developer.apple.com/documentation/uikit/uinavigationcontroller) \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 66d91772..3bfd1c71 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -156,6 +156,10 @@ export default [ { text: 'Advanced Concepts', items: [ + { + text: 'Navigation', + link: '/guide/ui/navigation', + }, { text: 'Multithreading', link: '/guide/multithreading', From 466e3561c04a2271cd259c2b2e9b08ab0162b090 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:01:41 +0200 Subject: [PATCH 15/44] docs: XmlParser (#53) Co-authored-by: Nathan Walker --- content/guide/core/xml-parser.md | 192 +++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 196 insertions(+) create mode 100644 content/guide/core/xml-parser.md diff --git a/content/guide/core/xml-parser.md b/content/guide/core/xml-parser.md new file mode 100644 index 00000000..dfc7e9d1 --- /dev/null +++ b/content/guide/core/xml-parser.md @@ -0,0 +1,192 @@ +--- +title: XmlParser +--- + +The XMLParser provides the ability to parse and extract data from XML documents. + +## Using XmlParser + +To parse an xml document, first institiate the class with the [ParserEvent](#parserevent) handler. + +```ts +const xmlParser = new XmlParser(this.onEventCallback); + +onEventCallback(event: ParserEvent) { + switch (event.eventType) { + + case ParserEventType.StartElement: + + if (event.attributes) { + for (const attributeName in event.attributes) { + if (event.attributes.hasOwnProperty(attributeName)) { + console.log({ + eventType: event.eventType, + elementName: event.elementName, + attributeName: attributeName, + result: event.attributes[attributeName], + significantText: null, + }); + } + } + } else { + console.log({ + eventType: event.eventType, + elementName: event.elementName, + attributeName: null, + result: null, + significantText: null, + }); + } + break; + case ParserEventType.EndElement: + + console.log({ + eventType: event.eventType, + elementName: event.elementName, + attributeName: null, + result: null, + significantText: null, + }); + break; + + case ParserEventType.Text: + const significantText = event.data.trim(); + + if (significantText !== '') { + console.log({ + eventType: event.eventType, + elementName: null, + attributeName: null, + result: null, + significantText: significantText, + }); + } + break; + default: + break; + } + } +``` +Then call the [parse](#parse) method on the instance passing it the data to be parsed. + +```ts + + xmlParser.parse(` + + I am first + I am second + + + + + `); +``` + +## XmlParser API + +### constructor + +```ts +const xmlParser = new XmlParser(onEvent: (event: ParserEvent) => void, onError?: (error: Error, position: Position) => void, processNamespaces?: boolean, angularSyntax?: boolean) + +``` + +Creates a new instance of the XmlParser class. +- `onEvent` is the callback to execute when a parser event occurs. + +- _Optional_: `onError` is the callback to execute when a parser error occurs. The `error` parameter contains the error and the `position` represents the position of the parsing error. +- _Optional_: `processNamespaces` specifies whether namespaces should be processed. + +--- + +### parse() + +```ts +xmlParser.parse(xmlString: string) +``` + +Parses the supplied xml string. + +--- +### ParserEvent + +The parser event data object has the following properties. + +#### eventType + +```ts +const eventType: ParserEventType = event.eventType +``` +Returns the type of the parser event. See [ParserEventType](#parsereventtype) + +--- + +#### position + +```ts +const position: Position = event.position +``` + +Returns the position (column number and line number) in the xml string where the event was generated. + +--- + +#### prefix + +```ts +const prefix: Position = event.prefix +``` + +If [namespace processing is enabled](#constructor), returns the prefix of the element if the eventType is `ParserEventType.StartElement` or `ParserEventType.EndElement`. + +--- + +#### namespace +```ts +``` +If [namespace processing is enabled](#constructor), returns the namespace of the element if the eventType is `ParserEventType.StartElement` or `ParserEventType.EndElement`. + +--- + +#### elementName + +Returns the name of the element if the eventType is `ParserEventType.StartElement` or `ParserEventType.EndElement`. + +--- +#### attributes + +Returns a JSON object with the attributes of an element if the eventType is `ParserEventType.StartElement`. + +--- + +#### data + +Returns the relevant data if the eventType is `ParserEventType.Text`, `ParserEventType.CDATA` or `ParserEventType.Comment`. + +--- +#### toString() +Returns a JSON string representation of this instance. + +--- +### ParserEventType +The following are the available parser event types: + +#### StartElement +Specifies the StartElement event type. + +--- +#### EndElement +Specifies the EndElement event type. + +--- +#### ParserEventType.Text +Specifies the Text event type. + +--- +#### ParserEventType.CDATA +Specifies the [CDATA](https://en.wikipedia.org/wiki/CDATA) event type. + +--- + +#### ParserEventType.Comment +Specifies the Comment event type. \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 3bfd1c71..7af6d2c9 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -147,6 +147,10 @@ export default [ text: 'ImageCache', link: '/guide/core/image-cache', }, + { + text: 'XmlParser', + link: '/guide/core/xml-parser' + }, { text: 'Utils', link: '/guide/core/utils' From 79a15b05a5a5d2bdb08e1906d80e9c4063de7608 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:06:05 +0200 Subject: [PATCH 16/44] docs: Screen (#51) Co-authored-by: Nathan Walker --- content/guide/core/screen.md | 66 ++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 +++ 2 files changed, 70 insertions(+) create mode 100644 content/guide/core/screen.md diff --git a/content/guide/core/screen.md b/content/guide/core/screen.md new file mode 100644 index 00000000..cc06d712 --- /dev/null +++ b/content/guide/core/screen.md @@ -0,0 +1,66 @@ +--- +title: Screen +--- + +The `Screen` class provides methods and properties to retrieve the screen size and scale of the device. + +## Importing Screen +To use the `Screen` class, import it from `@nativescript/core`. + +```ts +import { Screen } from '@nativescript/core'; +``` + +## Getting the Device Screen Size + +To obtain the device screen size, you can use the following properties: + +### heightDIPs +- Returns the absolute height of the device screen in density-independent pixels (DIPs). + +```ts +const heightDIPs: number = Screen.mainScreen.heightDIPs; +``` + +### widthDIPs +- Returns the absolute width of the device screen in density-independent pixels (DIPs). + +```ts +const widthDIPs: number = Screen.mainScreen.widthDIPs; +``` + +### heightPixels +- Returns the absolute height of the device screen in pixels. + +```ts +const heightPixels: number = Screen.mainScreen.heightPixels; +``` + +### widthPixels +- Returns the absolute width of the device screen in pixels. + +```ts +const widthPixels: number = Screen.mainScreen.widthPixels; +``` + +## Getting the Screen Display Density + +To obtain the screen display density, you can use the `scale` property, which returns the density of the display in DIPs. + +```ts +const scale: number = Screen.mainScreen.scale; // e.g., 2.75 +``` + +## API Reference + +### Properties + +- `heightDIPs`: Returns the absolute height of the screen in density-independent pixels (DIPs). +- `widthDIPs`: Returns the absolute width of the screen in density-independent pixels (DIPs). +- `scale`: Returns the density of the display in DIPs. +- `heightPixels`: Returns the absolute height of the screen in pixels. +- `widthPixels`: Returns the absolute width of the screen in pixels. + +The "absolute height" refers to the exact height of the device's screen, without taking into account any scaling factors or density considerations. It represents the true physical height of the screen, unaffected by the screen's pixel density or any scaling applied by the operating system. + +With the `Screen` class, you can easily retrieve the screen size and scale of the device, allowing you to create responsive and adaptive user interfaces in your NativeScript applications. \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 7af6d2c9..ed6a12ec 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -139,6 +139,10 @@ export default [ { text: '@nativescript/core', items: [ + { + text: 'Screen', + link: '/guide/core/screen' + }, { text: 'Device', link: '/guide/core/device', From 3c23453bb9e13e76d983971e48112f785bd1d725 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:11:14 +0200 Subject: [PATCH 17/44] docs: Platform (#49) Co-authored-by: Nathan Walker From f082c6f73abaad65b531758919aca7d1bdbe6de0 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:18:54 +0200 Subject: [PATCH 18/44] docs: Observable (#48) Co-authored-by: Nathan Walker --- content/guide/core/observable.md | 244 +++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 248 insertions(+) create mode 100644 content/guide/core/observable.md diff --git a/content/guide/core/observable.md b/content/guide/core/observable.md new file mode 100644 index 00000000..a4f90212 --- /dev/null +++ b/content/guide/core/observable.md @@ -0,0 +1,244 @@ +--- +title: Observable +--- + +The Observable class is what drives data binding in NativeScript. To bind the UI to the ViewModel, an instance of Observable is set to the `bindingContext` property of a `Page` or a layout container to drive [data binding]() in NativeScript. + +## How to use Observable + +The sections below show you the different ways you can use the Observable class. + +### Creating an Observable instance by subclassing + +Create a class extending it as shown below: + + +```ts +export class HelloWorldModel extends Observable { + + name = "Suzan Tomas"; + fruits = ["Chirimoya"]; + + constructor() { + super() + + this.on("propertyChange", (args: PropertyChangeData) => { + + if (args.propertyName == "name") { + this.fruits.unshift("Kaki") + + this.set("fruits", this.fruits) + } + }) + + } + + onTap(args: EventDaya){ + //handle the tap event + } +} +``` + +You can also use the [fromObject](#fromobject) or [fromObjectRecursive](#fromobjectrecursive) function to create an Observable instance. + + +### Emitting an event + +To emit a custom event, call the [notify()](#notify) method on the Observable instance: + +```ts +observable.notify({ + eventName: "custom-event" +}) +``` + +### Emitting an event for a property change + +To emit an event for a property change, use the [notifyPropertyChange()](#notifypropertychange) method: + +```ts +this.fruits.unshift("Kaki") + +this.notifyPropertyChange("fruits", this.fruits) +``` + +### Avoiding Event Handler Memory Leak + +To ensure that your app doesn't have memory leak caused by handlers that are no longer needed, use the [addWeakEventListener()](#addweakeventlistener) function: + + + +## Observable API + +### constructor + +```ts +const observable = new Observable() +``` + +--- + +### propertyChangeEvent +```ts +observable.on(Observable.propertyChangeEvent,(args:PropertyChangeData)=>{ + +}) +``` + +String value used when hooking to `propertyChange` event. + +--- +### addEventListener() + +```ts +Observable.addEventListener(eventNames, callback: (data: EventData) => void, thisArg) + +//or +observable.addEventListener(eventNames, callback: (data: EventData) => void, thisArg) +``` +It is a method used to add a listener for the specified event(s). + +- `eventNames` is a comma delimited string containing the names of the events to be listened to. +- `callback` is the function that gets called when any of the registered event occurs. +- _Optional_: `thisArg` is a parameter which will be used as `this` context for callback execution + +--- + +### on() +```ts +Observable.on(eventNames, callback: (data: EventData) => void, thisArg) + +//or +observable.on(eventNames, callback: (data: EventData) => void, thisArg) +``` +This is a shortcut alias to the [addEventListener](#addeventlistener) method. + +--- + +### once() +```ts +Observable.once(eventName, callback: (data: EventData) => void, thisArg) + +//or +observable.once(eventName, callback: (data: EventData) => void, thisArg) +``` + +Adds one-time listener function for the specified event. +- `eventName` is the name of the event to be listened to. +- `callback` is the function that gets called when the event occurs. +- _Optional_: `thisArg` is a parameter which will be used as `this` context for callback execution. + +--- +### addWeakEventListener() +### removeEventListener() + +```ts +Observable.removeEventListener(eventNames, callback, thisArg) + +//or +observable.removeEventListener(eventNames, callback, thisArg) +``` + +Removes listener(s) for the specified event name(s). +- `eventNames` is a comma delimited string containing the names of the events the specified listener listens to. +- _Optional_: The `callback` parameter points to a specific listener to be removed. If not defined, all listeners for the event names will be removed. + +- _Optional_: `thisArg` is a parameter used as `this` context in which the listener to be removed will be searched. + +--- +### off() + +```ts +Observable.off(eventNames, callback, thisArg) +//or +observable.off(eventName, callback, thisArg) +``` +This method is a shortcut alias to [removeEventListener()](#removeeventlistener). + +--- + +### set() + +```ts +observable.set(propertyName, value) +``` + +Updates the specified property with the provided value. + +--- + +### setProperty() + +```ts +observable.setProperty(propertyName, value) +``` + +Updates the specified property with the provided value and raises a [propertyChangeEvent](#propertychangeevent) + +--- + +### get() + +```ts +observable.get(propertyName) +``` + +Updates the specified property with the provided value. + +--- + +### notify() + +```ts +observable.notify({ + eventName: "some-event-name", + object +}) +``` + +Allows you to manually emit an event(custom or NativeScript provided) +- `eventName` is the name of the event to be emitted. +- _Optional_: `object` is an Observable instance that has raised the event. +--- + +### notifyPropertyChange() + +```ts +observable.notifyPropertyChange(propertyName, value, oldValue) +``` + +Notifies all the registered listeners for the propertyChange event. +- `propertyName` is the property whose value has changed. +- `value` the new value of the property. +- _Optional_: `oldValue` the previous value of the property. +--- + + +### hasListeners() +```ts +const hasListeners: boolean = observable.hasListeners(eventName) +``` + +Checks whether a listener is registered for the specified event name. + +--- + +### fromObject() +```ts +import { fromObject } from "@nativescript/core" + +const observable: Observable = fromObject(obj) +``` + +Creates an Observable instance and sets its properties to the properties of the specified object. + +--- + +### fromObjectRecursive() +```ts +import { fromObjectRecursive } from "@nativescript/core" + +const observable: Observable = fromObjectRecursive(obj) +``` + +Creates an Observable instance and sets its properties to the properties of the specified object. For each nested object (except for arrays and functions) in the supplied object, it creates an Observable instance. diff --git a/content/sidebar.ts b/content/sidebar.ts index ed6a12ec..9a378259 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -139,6 +139,10 @@ export default [ { text: '@nativescript/core', items: [ + { + text: 'Observable', + link: '/guide/core/observable' + }, { text: 'Screen', link: '/guide/core/screen' From 77ea700e7d41bfeb1674b23198c9b30b40194cab Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Mon, 17 Jul 2023 12:23:30 -0700 Subject: [PATCH 19/44] chore: cleanup --- content/guide/core/observable.md | 37 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/content/guide/core/observable.md b/content/guide/core/observable.md index a4f90212..861be321 100644 --- a/content/guide/core/observable.md +++ b/content/guide/core/observable.md @@ -2,15 +2,26 @@ title: Observable --- -The Observable class is what drives data binding in NativeScript. To bind the UI to the ViewModel, an instance of Observable is set to the `bindingContext` property of a `Page` or a layout container to drive [data binding]() in NativeScript. +Observable is a fundamental building block throughout @nativescript/core. + +:::warning Note +Not to be confused with [rxjs Observable](https://rxjs.dev/guide/observable), the `Observable` within NativeScript predates rxjs itself and the name has always stuck around. If you'd like us to rename this in the future, feel free to chime in via [RFC Discussions](https://github.com/NativeScript/rfcs/discussions). +::: + +`Observable` plays a vital role in enabling data binding in NativeScript. It serves as a key component for connecting the user interface (UI) with the underlying data in a ViewModel. By setting an instance of Observable as the bindingContext property of a Page or a layout container, you can establish a seamless connection that drives data binding within NativeScript. + +In simpler terms, an Observable allows you to easily synchronize and update the UI elements of your app with changes in the underlying data. It acts as a bridge that notifies the UI whenever there are modifications to the data, ensuring that the UI stays in sync with the latest values. This makes it convenient to build dynamic and reactive interfaces, as changes in the data will automatically trigger corresponding updates in the UI. + +By utilizing the power of Observables and data binding, you can create interactive and responsive NativeScript applications with minimal effort. + ## How to use Observable -The sections below show you the different ways you can use the Observable class. +The sections below show you different ways Observable can be used. -### Creating an Observable instance by subclassing +### Creating an Observable by subclassing -Create a class extending it as shown below: +Create a class extending Observable as shown below: ```ts @@ -39,12 +50,12 @@ export class HelloWorldModel extends Observable { } ``` -You can also use the [fromObject](#fromobject) or [fromObjectRecursive](#fromobjectrecursive) function to create an Observable instance. +You can also use the [fromObject](#fromobject) or [fromObjectRecursive](#fromobjectrecursive) function to create an Observable from data. ### Emitting an event -To emit a custom event, call the [notify()](#notify) method on the Observable instance: +To emit a custom event, call the [notify](#notify) method on the Observable instance: ```ts observable.notify({ @@ -54,7 +65,7 @@ observable.notify({ ### Emitting an event for a property change -To emit an event for a property change, use the [notifyPropertyChange()](#notifypropertychange) method: +To emit an event for a property change, use the [notifyPropertyChange](#notifypropertychange) method: ```ts this.fruits.unshift("Kaki") @@ -64,7 +75,7 @@ this.notifyPropertyChange("fruits", this.fruits) ### Avoiding Event Handler Memory Leak -To ensure that your app doesn't have memory leak caused by handlers that are no longer needed, use the [addWeakEventListener()](#addweakeventlistener) function: +To ensure that your app doesn't have memory leak caused by handlers that are no longer needed, use the [addWeakEventListener](#addweakeventlistener) function: @@ -80,7 +91,7 @@ const observable = new Observable() ### propertyChangeEvent ```ts -observable.on(Observable.propertyChangeEvent,(args:PropertyChangeData)=>{ +observable.on(Observable.propertyChangeEvent, (args:PropertyChangeData)=>{ }) ``` @@ -96,7 +107,7 @@ Observable.addEventListener(eventNames, callback: (data: EventData) => void, thi //or observable.addEventListener(eventNames, callback: (data: EventData) => void, thisArg) ``` -It is a method used to add a listener for the specified event(s). +Add a listener for the specified event(s). - `eventNames` is a comma delimited string containing the names of the events to be listened to. - `callback` is the function that gets called when any of the registered event occurs. @@ -111,7 +122,7 @@ Observable.on(eventNames, callback: (data: EventData) => void, thisArg) //or observable.on(eventNames, callback: (data: EventData) => void, thisArg) ``` -This is a shortcut alias to the [addEventListener](#addeventlistener) method. +A shortcut alias to [addEventListener](#addeventlistener). --- @@ -123,7 +134,7 @@ Observable.once(eventName, callback: (data: EventData) => void, thisArg) observable.once(eventName, callback: (data: EventData) => void, thisArg) ``` -Adds one-time listener function for the specified event. +Adds a one-time listener for the specified event. - `eventName` is the name of the event to be listened to. - `callback` is the function that gets called when the event occurs. - _Optional_: `thisArg` is a parameter which will be used as `this` context for callback execution. @@ -153,7 +164,7 @@ Observable.off(eventNames, callback, thisArg) //or observable.off(eventName, callback, thisArg) ``` -This method is a shortcut alias to [removeEventListener()](#removeeventlistener). +A shortcut alias to [removeEventListener](#removeeventlistener). --- From 561666ed7d0d68d4791fc53da86848c60ff91b62 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:31:48 +0200 Subject: [PATCH 20/44] docs: ImageSource (#46) Co-authored-by: Nathan Walker --- content/guide/core/image-source.md | 365 +++++++++++++++++++++++++++++ content/sidebar.ts | 64 ++++- 2 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 content/guide/core/image-source.md diff --git a/content/guide/core/image-source.md b/content/guide/core/image-source.md new file mode 100644 index 00000000..3f79ab06 --- /dev/null +++ b/content/guide/core/image-source.md @@ -0,0 +1,365 @@ +--- +title: ImagaSource +description: Create an instance from different sources. +--- + +ImageSource provides a common interface over ([android.graphics.Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html) for Android and [UIImage](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class) for iOS, allowing you to create an images from URLs, local files, base64 strings, etc. + + + +## How to use the ImageSource class + +The following sections demonstrate how to create an ImageSource from different sources. + +### Load an image using a resource name + +To load an image from the [App_Resources]() folder, use the [fromResource](#fromresource) or [fromResourceSync](#fromresourcesync) method: + +```ts +ImageSource.fromResource('logo') + .then((image: ImageSource) => { + console.log(image.width) + }) + .catch(err => { + console.error(err.stack) + }) +``` + +### Load an image from the device file system + +To load an image from a file, use any of [fromFile](#fromfile), [fromFileOrResourceSync](#fromfileorresourcesync) or the [fromFileSync](#fromfilesync) method: + +```ts +async function loadImage(){ + + try { + const imageFromFile: ImageSource = await ImageSource.fromFile(filePath) + + } catch (error) { + + } +``` + +### Create an image from a base64 string + +To create an image from a base64 string, use the [fromBase64](#tobase64string) or [fromBase64Sync](#frombase64sync) method: + +```ts +const base64Str = "some base64Str" +const image: ImageSource = ImageSource.fromBase64Sync(base64Str) + +``` +### Create an ImageSource from a font icon code +```ts +const font = new Font("sans serif") +const color = new Color("black") +const imageSource: ImageSource = ImageSource.fromFontIconCodeSync("\uf004", font, color) +``` +### Save an image to a file on the File System + +To save an ImageSource instance to a file, call the [saveToFile](#savetofile) or [saveToFileAsync](#savetofileasync) method on the instance. + +```ts +async function saveImage(){ + +try { + + const folderDest: Folder = knownFolders.documents() + + folderDest.getFile('/images/test.png') //1. Create the file + + const pathDest = path.join(folderDest.path, '/images/test.png') + const saved: boolean = await image.saveToFileAsync(pathDest, 'png') // Save to the file + if (saved) { + Dialogs.alert('Saved successfully') + } + } catch (err) { + Dialogs.alert(err) + } + +} +``` + +## ImageSource API + +### constructor() +```ts +const imageSource = new ImageSource(nativeSource) +``` + Creates a new ImageSource instance and sets the provided native source object. `nativeSource` object will update either the android or ios properties, depending on the target platform. + +--- +The ImageSource class provides the following image static methods loaders. + +### fromAsset() + +```ts +ImageSource.fromAsset(asset: ImageAsset) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` + +Loads an ImageSource instance from the specified [ImageAsset]() instance asynchronously. + +--- +### fromBase64() +```ts +ImageSource.fromBase64(base64String) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` +Loads an ImageSource instance from the specified base64 encoded string asynchronously + +--- + +### fromBase64Sync() +```ts +const imageSource: ImageSource = ImageSource.fromBase64Sync(base64String) +``` +Loads an ImageSource instance from the specified base64 encoded string synchronously. + +--- +### fromData() +```ts +ImageSource.fromData(data) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` +Asynchronously loads an ImageSource instance from the specified native image data(byte array) asynchronously. `data` can be a Stream on Android or [NSData](https://developer.apple.com/documentation/foundation/nsdata) on iOS. + +--- +### fromDataSync() + +```ts +const imageSource: ImageSource = ImageSource.fromDataSync(data); +``` + +Loads an ImageSource instance from the specified native image data(byte array). + +--- + +### fromFile() +```ts +ImageSource.fromFile(path) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` + +Loads an ImageSource instance from the specified file asynchronously. + +--- +### fromFileSync() +```ts +const imageSource: ImageSource = ImageSource.fromFileSync(data); +``` + +Loads an ImageSource instance from the specified file. + +### fromFileOrResourceSync() +```ts +const imageSource: ImageSource = ImageSource.fromFileOrResourceSync(path); +``` + +Create an ImageSource from the specified local file or resource (if specified with the `"res://"` prefix). + +--- + +### fromFontIconCodeSync() +```ts +const imageSource: ImageSource = ImageSource.fromFontIconCodeSync(source, font, color); +``` + +Creates a new ImageSource instance from the specified font icon code. + +- `source`: The unicode string. +- `font`: Font instance. +- `color`: Color instance. + +--- +### fromResource() +```ts +ImageSource.fromResource(name) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` +Creates an ImageSource from the specified resource name(without the extension) asynchronously. + +--- +### fromResourceSync() + +```ts +const imageSource: ImageSource = ImageSource.fromResourceSync(name) +``` + +Creates an ImageSource from the specified resource name(without the extension) synchronously. + +--- +### fromUrl() + +```ts +ImageSource.fromUrl(url) +.then((imageSource: ImageSource) =>{ +// handle the created image +}) +.catch(error =>{ + // handle errror +}) +``` + +Downloads and decodes the image from the provided url and creates a new ImageSource instance from it. + +--- + +#### The loaded ImageSource instance has the following properties and methods. + +### android +```ts +imageAndroid: android.graphics.BitMap = imageSource.android +``` +The Android-specific([Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html)) instance. + +--- + +### ios +```ts +imageIOS: UIImage = imageSource.ios +``` + The iOS-specific([UIImage](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/)) instance. + +--- + +### height +```ts +height: number = imageSource.height +``` +Gets the height of the instance. + +--- +### width +```ts +width: number = imageSource.width +``` +Gets the width of the instance. + +--- +### rotationAngle +```ts +rotationAngle: number = imageSource.rotationAngle +``` + +`Android-only`: Gets or sets the rotation angle that should be applied to the image. + +--- +### resizeAsync() +```ts +imageSource.resize(maxSize, options) +.then((resizedImage: ImageSource)=>{ + +}). +catch(error=>{ + +}) +``` + +Asynchronously returns a new ImageSource that is a resized version of the imageSource with the same aspect ratio, and the max dimension set to the provided maxSize. + +- `maxSize` is the maximum desired pixel dimension of the resulting image. +- _Optional_: (`Android` only) `options.filter` options.filter is a `boolean` which determines whether or not bilinear filtering should be used when scaling the bitmap. If `true` then bilinear filtering will be used when scaling which has better image quality at the cost of worse performance. If this is false then nearest-neighbor scaling is used instead which will have worse image quality but is faster. Recommended is to set filter to 'true' as the cost of bilinear filtering is typically minimal and the improved image quality is significant. + +--- +### resize() + +```ts +resizedImage: ImageSource = imageSource.resize(maxSize, options) +``` + +Returns a new ImageSource that is a resized version of the imageSource with the same aspect ratio, and the max dimension set to the provided `maxSize`. + +--- + +### saveToFile() +```ts +isSaved: boolean = imageSource.saveToFile(path, format, quality) +``` + +Saves this instance to the specified file, using the provided image `format` and `quality`. + +--- +### saveToFileAsync() + +```ts +imageSource.saveToFileAsync(path, format, quality) +.then((isSaved:boolean)=>{ + +}) +.catch(error=>{ + +}) +``` + +Asynchronously saves this instance to the specified file, using the provided image `format` and `quality`. + +- `path` (`string`) is the path of the file on the file system to save to. +- `format` (`'png' | 'jpeg' | 'jpg'`) is the format (encoding) of the image. +- _Optional_: `quality` specifies the quality of the encoding. It defaults to the maximum available quality, and varies on a scale of 0 to 100. + +--- +### setNativeSource() +```ts +imageSource.setNativeSource(nativeSource) +``` +Sets the provided native source object, either a Bitmap for Android or a UIImage for iOS. + +--- +### toBase64String() + +```ts +base64String : string = imageSource.toBase64String(format, quality) +``` + +Converts the image to base64 encoded string, using the provided image format and quality. + +--- +### toBase64StringAsync() + +```ts +imageSource.toBase64StringAsync(format, quality) + .then((base64String:string)=>{ + + }) + .catch(error=>{ + + }) +``` + +Asynchronously converts the image to base64 encoded string, using the provided image format and quality. + + +## API Reference(s) +- [ImageSource](https://docs.nativescript.org/api-reference/classes/imagesource) class + +## Native Component + +- `android`: [android.graphics.Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html) +- `iOS`: [UIImage](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class) + diff --git a/content/sidebar.ts b/content/sidebar.ts index 9a378259..80ef00cf 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -191,7 +191,67 @@ export default [ ], }, { - text: 'UI / Layout Containers', - items: [{ text: 'AbsoluteLayout', link: '/ui/absolute-layout' }], + text: 'UI', + items: [ + { + text: 'Image', + items: [ + { + text: 'ImageCache', + link: '/guide/ui/image-cache' + }, + { + text: 'ImageSource', + link: '/guide/ui/image-source' + } + ] + }, + // { text: 'Styling', link: '//#' }, + // { text: 'Interactivity', link: '//#' }, + { + text: 'Layout Containers', + items: [ + // { text: 'StackLayout', link: '//#' }, + // { text: 'GridLayout', link: '//#' }, + // { text: 'RootLayout', link: '//#' }, + // { text: 'FlexboxLayout', link: '//#' }, + // { text: 'WrapLayout', link: '//#' }, + { text: 'AbsoluteLayout', link: '/ui/absolute-layout' }, + ], + }, + // { + // text: 'Navigation Components', + // items: [ + // { text: 'Frame', link: '//#' }, + // { text: 'Page', link: '//#' }, + // { text: 'ActionBar', link: '//#' }, + // { text: 'ActionItem', link: '//#' }, + // { text: 'NavigationButton', link: '//#' }, + // ], + // }, + // { + // text: 'Components', + // items: [ + // { text: 'ActivityIndicator', link: '//#' }, + // { text: 'Button', link: '//#' }, + // { text: 'DatePicker', link: '//#' }, + // { text: 'HtmlView', link: '//#' }, + // { text: 'Image', link: '/' }, + // { text: 'Label', link: '//#' }, + // { text: 'ListPicker', link: '//#' }, + // { text: 'ListView', link: '//#' }, + // { text: 'Placeholder', link: '//#' }, + // { text: 'Progress', link: '//#' }, + // { text: 'ScrollView', link: '//#' }, + // { text: 'SearchBar', link: '//#' }, + // { text: 'SegmentedBar', link: '//#' }, + // { text: 'Slider', link: '//#' }, + // { text: 'Switch', link: '//#' }, + // { text: 'TabView', link: '//#' }, + // { text: 'TextField', link: '//#' }, + // { text: 'TextView', link: '//#' }, + // { text: 'TimePicker', link: '//#' }, + // { text: 'WevView', link: '//#' }, + ], }, ] as NSSidebarGroup[] From d8cc9e2193d507cc3c4cc7364f3a6dd6d0755128 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:27:58 +0200 Subject: [PATCH 21/44] feat(app-resources): Adding custom entitlements&more (#40) Co-authored-by: Nathan Walker --- content/project-structure/app-resources.md | 47 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/content/project-structure/app-resources.md b/content/project-structure/app-resources.md index 92c1429a..1a5192ec 100644 --- a/content/project-structure/app-resources.md +++ b/content/project-structure/app-resources.md @@ -230,4 +230,49 @@ Most things on iOS are controlled directly through the app's template code. ### Adding custom entitlements - +You can add custom entitlements to the `App_Resources/iOS/app.entitlements` + +For a list of available entitlements refer to [Apple's Entitlements documentation](https://developer.apple.com/documentation/bundleresources/entitlements?language=objc) + +### Adding ObjectiveC/Swift Code to an application + +You can add Objective-C/Swift source files to `App_Resources/iOS/src`. For Objective-C files, create a `.modulemap` file. To add a [CocoaPod](https://guides.cocoapods.org/using/getting-started.html), edit `App_Resources/iOS/Podfile`: + +```cli +App_Resources/ +├─ iOS/ +│ ├─src/ +│ │ ├─ Shimmer.swift +│ │ ├─ Shimmer.h +│ │ ├─ Shimmer.m +│ │ └─ module.modulemap +│ ├─Podfile +│ └─ +└─ ... more +``` + + + +```swift +extension UIView { + + @objc func startShimmering( + speed: Float = 1.4, + repeatCount: Float = MAXFLOAT + ) { + ... + } +} +``` + + +```objc +#import "Shimmer.h" + +@implementation UIView (Shimmer) +- (void)startShimmering +{ + ... +} +@end +``` \ No newline at end of file From b4371a94b25f1baa2c9c735922fc0121de40f9ac Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:06:21 +0200 Subject: [PATCH 22/44] docs: FPS Meter (#44) Co-authored-by: Nathan Walker --- content/guide/core/fps-meter.md | 107 ++++++++++++++++++++++++++++++++ content/sidebar.ts | 3 + 2 files changed, 110 insertions(+) create mode 100644 content/guide/core/fps-meter.md diff --git a/content/guide/core/fps-meter.md b/content/guide/core/fps-meter.md new file mode 100644 index 00000000..5de06447 --- /dev/null +++ b/content/guide/core/fps-meter.md @@ -0,0 +1,107 @@ +--- +title: FPS Meter +--- + +FPS Meter allows you capture the frames-per-second metrics of your application. + +## Using FPS Meter + +### Measuring FPS + +Register a callback that will receive the FPS metrics using the [addCallBack](#addcallback) method. Then call the [start](#start) method to begin measuring. + +```ts +import { + removeCallback, + start, + stop, + addCallback, + running +} from '@nativescript/core/fps-meter' + +let callbackId: number; + +export function startFPSMeter(args: EventData) { + + callbackId = addCallback((fps: number, minFps: number | undefined) => { + + console.log(`Frames per seconds: ${fps.toFixed(2)}`) + + console.log(minFps?.toFixed(2)) + }) + + start() + console.log('Is running: ', running()) +} +``` + +### Stop measuring FPS + +Remove the registered callback using its id and then call the [stop](#stop) method. + +```ts +export function stopFPSMeter(args: EventData) { + + removeCallback(callbackId) + + stop() +} +``` + +## FPS Meter API + +### addCallback + +```ts +const callbackId = addCallback(callback: (fps: number, minFps?: number) => void): number +``` + +Implement a callback function that will be executed whenever FPS data becomes available. Assign a unique identifier (`number`) to this callback, facilitating its future removal, if needed. + +--- + +### start + +```ts +start() +``` + +Starts the frames-per-second meter. + +--- + +### stop + +```ts +stop() +``` + +Stops the frames-per-second meter. + +--- +### removeCallback + +```ts +removeCallback(callbackId) +``` + +Removes the callback with the specified id. + +--- + +### running + +```ts +running() +``` + +Returns a boolean value indicating whether the frames-per-second meter is currently running. + +--- + +## Additional Resources + +### Native Component + +- Android: [android.view.Choreographer]() +- iOS: [CADisplayLink](https://developer.apple.com/documentation/quartzcore/cadisplaylink) \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 80ef00cf..c559e158 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -140,6 +140,9 @@ export default [ text: '@nativescript/core', items: [ { + text: 'FPS Meter', + link: '/guide/core/fps-meter' + }, text: 'Observable', link: '/guide/core/observable' }, From e74f23414ae42c7c7cc7ec40c78f47781b03bb2a Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:45:05 +0200 Subject: [PATCH 23/44] docs: initial content for Testing in NativeScript (#15) Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/testing.md | 325 +++++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 22 +++ 2 files changed, 347 insertions(+) create mode 100644 content/guide/testing.md diff --git a/content/guide/testing.md b/content/guide/testing.md new file mode 100644 index 00000000..76e04f14 --- /dev/null +++ b/content/guide/testing.md @@ -0,0 +1,325 @@ +--- +title: Testing +--- + +::: warning Note +Be sure you have prepare/built/run the app at least once before starting the unit test runner. +::: + +For more information about end-to-end testing, see [`@nativescript/detox` plugin](https://docs.nativescript.org/plugins/detox.html). + +Regularly writing and executing unit tests using tools like [Jasmine](http://jasmine.github.io/), [Mocha](https://mochajs.org/) with [Chai](http://chaijs.com/), or [QUnit](https://qunitjs.com/) through the NativeScript CLI helps ensure proper functioning of new features and prevents regressions in existing functionality during app development. + +To run your unit tests, the NativeScript CLI uses [Karma](http://karma-runner.github.io/latest/index.html). + +### Before You Begin + +Before writing and running unit tests, the completion of the following steps must be verified. + +1. [Install and configure the NativeScript CLI on your system.](/setup/) +1. If you don't have any projects, create a new project and navigate to the directory of the newly created directory. + + ```cli + ns create projectName + cd projectName + ``` + +1. If you want to create tests for an existing directory, navigate to the directory of the project. + + ```cli + cd existingProjectDirectory + ``` + +:::tip Note + +You don't need to explicitly add the platforms for which you want to test your project. The NativeScript CLI will configure your project when you begin to run your tests. + +::: + +### Configure Your Project + +The NativeScript CLI lets you choose between three widely popular unit testing frameworks: [Jasmine](http://jasmine.github.io/), [Mocha](https://mochajs.org/) with [Chai](http://chaijs.com/) and [QUnit](https://qunitjs.com/). You need to configure the project for unit testing by choosing a framework. You can use only one framework at a time. + +To initialize your project for unit testing, run the following command and, when prompted, use the keyboard arrows to select the framework that you want to use. + +```cli +ns test init +``` + +This operation applies the following changes to your project. + +- It creates the `app/tests` directory. You need to store all tests in this directory. This directory is excluded from release builds. +- It creates an `example.js` file in the `app/tests` directory. This sample test illustrates the basic syntax for the selected framework. +- It installs the nativescript-unit-test-runner npm module for the selected framework and its dev dependencies in `node_modules`. +- It creates `karma.conf.js` in the root of your project. This file contains the default configuration for the Karma server for the selected framework. + +:::tip Note + +To enable and write unit tests for TypeScript or Angular project install the TypeScript typings for the selected testing framework. + +::: + + + +```cli +npm i @types/jasmine --save-dev +``` + + + +```cli +npm i @types/mocha --save-dev +``` + + + +```cli +npm i @types/qunit --save-dev +``` + +### Write Your Tests + +With the NativeScript CLI, you can extensively test **all JavaScript-related functionality**. You cannot test styling and UI which are not applied or created via JavaScript. + +When creating tests for a new or existing functionality, keep in mind the following specifics. + +- You need to create your tests as JavaScript files in the `app/tests` directory. The NativeScript CLI recognizes JavaScript files stored in `app/tests` as unit tests. +- You need to write tests which comply with the testing framework specification you have chosen for the project. +- You need to export the functionality that you want to test in the code of your NativeScript project. +- You need to require the module which exposes the functionality that you want to test in the code of your unit tests. + +When creating tests for a new or existing functionality, keep in mind the following limitations. + +- You cannot require the file or module in which `Application.run()` is called. +- You cannot use more than one testing framework per project. +- You cannot test styling and UI which are not applied or created via JavaScript. + +The following samples test the initial value of the counter and the message in the Hello World template. These tests show the specifics and limitations outlined above. + +```js +var mainViewModel = require('../main-view-model') //Require the main view model to expose the functionality inside it. + +describe('Hello World Sample Test:', function () { + it('Check counter.', function () { + expect(mainViewModel.createViewModel().counter).toEqual(42) //Check if the counter equals 42. + }) + it('Check message.', function () { + expect(mainViewModel.createViewModel().message).toBe('42 taps left') //Check if the message is "42 taps left". + }) +}) +``` + + + +```js +// (Angular w/TypeScript) +// As our intention is to test an Angular component that contains annotations +// we need to include the reflect-metadata dependency. +import 'reflect-metadata' + +// A sample Jasmine test +describe('A suite', function () { + it('contains spec with an expectation', function () { + expect(true).toBe(true) + }) +}) +``` + + + +```js +var mainViewModel = require('../main-view-model') //Require the main view model to expose the functionality inside it. + +describe('Hello World Sample Test:', function () { + it('Counter should be 42 on start.', function () { + assert.equal(mainViewModel.createViewModel().counter, 42) //Assert that the counter equals 42. + }) + it('Message should be "42 taps left" on start.', function () { + assert.equal(mainViewModel.createViewModel().message, '42 taps left') //Assert that the message is "42 taps left". + }) +}) +``` + + + +```js +var mainViewModel = require('../main-view-model') //Require the main view model to expose the functionality inside it. + +QUnit.test('Hello World Sample Test:', function (assert) { + assert.equal( + mainViewModel.createViewModel().counter, + 42, + 'Counter, 42; equal succeeds.' + ) //Assert that the counter equals 42. + assert.equal( + mainViewModel.createViewModel().message, + '42 taps left', + 'Message, 42 taps left; equal succeeds.' + ) //Assert that the message is "42 taps left". +}) +``` + +### Angular TestBed Integration + +To use TestBed you have to alter your `karma.conf.js` to: + +```js + // list of files / patterns to load in the browser + files: [ + 'src/tests/setup.ts', + 'src/tests/**/*.spec.ts' + ], + +``` + +The file `src/tests/setup.ts` should look like this for jasmine: + +```js +import 'nativescript-angular/zone-js/testing.jasmine' +import { nsTestBedInit } from 'nativescript-angular/testing' +nsTestBedInit() +``` + +or if using mocha: + +```js +import 'nativescript-angular/zone-js/testing.mocha' +import { nsTestBedInit } from 'nativescript-angular/testing' +nsTestBedInit() +``` + +Then you can use it within the spec files, e.g. `example.spec.ts`: + +```js +import { Component, ElementRef, NgZone, Renderer2 } from '@angular/core'; +import { ComponentFixture, async } from '@angular/core/testing'; +import { StackLayout } from '@nativescript/core'; +import { + nsTestBedAfterEach, + nsTestBedBeforeEach, + nsTestBedRender +} from 'nativescript-angular/testing'; + +@Component({ + template: ` + + ` +}) +export class ZonedRenderer { + constructor(public elementRef: ElementRef, public renderer: Renderer2) {} +} + +describe('Renderer E2E', () => { + beforeEach(nsTestBedBeforeEach([ZonedRenderer])); + afterEach(nsTestBedAfterEach(false)); + afterAll(() => {}); + + it('executes events inside NgZone when listen is called outside NgZone', async(() => { + const eventName = 'someEvent'; + const view = new StackLayout(); + const eventArg = { eventName, object: view }; + const callback = arg => { + expect(arg).toEqual(eventArg); + expect(NgZone.isInAngularZone()).toBeTruthy(); + }; + nsTestBedRender(ZonedRenderer).then( + (fixture: ComponentFixture) => { + fixture.ngZone.runOutsideAngular(() => { + fixture.componentInstance.renderer.listen( + view, + eventName, + callback + ); + + view.notify(eventArg); + }); + } + ); + })); +}); + +``` + +### Run Your Tests + +After you have completed your test suite, you can run it on physical devices or in the native emulators. + +#### Requirements + +Before running your tests, verify that your development machine and your testing devices meet the following prerequisites. + +- The Android native emulators on which you want to run your tests must be running on your development machine. To verify that your machine recognizes the devices, run the following command. + + ```cli + ns device + ``` + +- The physical devices on which you want to run your tests must be connected to your development machine. To verify that your machine recognizes the devices, run the following command. + + ```cli + ns device + ``` + +- The physical devices on which you want to run your tests must be able to resolve the IP of your development machine. To verify that the device can access the Karma server, connect the device and the development machine to the same Wi-Fi network or establish USB or Bluetooth tethering between the device and the development machine. +- Port 9876 must be allowed on your development machine. The Karma server uses this port to communicate with the testing device. + +#### Run the Tests + +To execute your test suite on any connected Android devices or running Android emulators, run the following command. + +```cli +ns test android +``` + +To execute your test suite on connected iOS devices, run the following command. + +```cli +ns test ios +``` + +To execute your test suite in the iOS Simulator, run the following command. + +```cli +ns test ios --emulator +``` + +To execute your test suite in CI make sure to add `--justlaunch`. This parameter will exit the simulator. + +```cli +ns test ios --emulator --justlaunch +``` + +Each execution of `ns test` consists of the following steps, performed automatically. + +1. The CLI starts a Karma server on the development machine. +1. The CLI prepares, builds and deploys your project, if not already deployed. If already deployed, the CLI synchronizes changes to the application package. +1. The CLI embeds the NativeScript unit test runner and your host network and Karma configuration in the deployed package. +1. The CLI launches the main module of the NativeScript unit test runner instead of launching the main module of your app. +1. The NativeScript unit test runner uses the embedded network configuration to try to connect to the Karma server on the development machine. +1. When the connection between the NativeScript unit test runner and the Karma server is established, the test runner begins the execution of the unit tests. +1. When the execution completes, the NativeScript unit test runner reports the results to the Karma server. +1. The Karma server reports the results on the command line. + +#### Re-Run Tests on Code Change + +The NativeScript can continuously monitor your code for changes and when such changes occur, it can deploy those changes to your testing devices and re-run your tests. + +To enable this behavior, run your `ns test` command with the `--watch` flag. For example: + +```cli +ns test android --watch +ns test ios --watch +ns test ios --emulator --watch +``` + +The NativeScript CLI remains active and re-runs tests on code change. To unlock the console, press `Ctrl+C` to stop the process. + +#### Configure the Karma Server + +When you configure your project for unit testing, the NativeScript CLI adds `karma.conf.js` to the root of your project. This file contains the default configuration of the Karma server, including default port and selected testing framework. You can edit this file to customize your Karma server. + +When you modify `karma.conf.js`, make sure that your changes meet the specification of the [Karma Configuration File](http://karma-runner.github.io/1.0/intro/configuration.html). + +### Continuous Integration + +To integrate the NativeScript unit test runner into a continuous integration process, you need to configure a Karma reporter, for example, the [JUnit reporter](https://github.com/karma-runner/karma-junit-reporter). diff --git a/content/sidebar.ts b/content/sidebar.ts index c559e158..47fb015f 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -18,6 +18,27 @@ export default [ text: 'Creating a Project', link: '/guide/creating-a-project', }, + { + text: 'Development Workflow', + items: [ + { + text: 'Testing', + link: '/guide/testing' + }, + { + text: 'Debugging', + link: '/guide/debugging', + }, + ], + }, + { + text: 'Tutorials', + link: '/tutorials/', + }, + { + text: 'Publishing', + link: '/guide/publishing/', + }, // { // text: 'Tutorials', // link: '/tutorials/', @@ -143,6 +164,7 @@ export default [ text: 'FPS Meter', link: '/guide/core/fps-meter' }, + { text: 'Observable', link: '/guide/core/observable' }, From a1336699397b96a800105c3f391319367d33c68a Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:56:45 +0200 Subject: [PATCH 24/44] docs: initial content for Android Marshalling (#32) Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/android-marshalling.md | 823 +++++++++++++++++++++++++++ content/sidebar.ts | 9 + 2 files changed, 832 insertions(+) create mode 100644 content/guide/android-marshalling.md diff --git a/content/guide/android-marshalling.md b/content/guide/android-marshalling.md new file mode 100644 index 00000000..de51fe62 --- /dev/null +++ b/content/guide/android-marshalling.md @@ -0,0 +1,823 @@ +--- +title: Android Marshalling +--- + +NativeScript seamlessly handles data type conversion between JavaScript and Java/Kotlin, utilizing type inference and dedicated wrappers, ensuring smooth integration and type safety in cross-platform development. + +## String Conversion +### Converting JavaScript String to Java string types + +JavaScript [String](http://www.w3schools.com/jsref/jsref_obj_string.asp) maps to [java.lang.String](http://developer.android.com/reference/java/lang/String.html): + +```js +var context = ...; +var button = new android.widget.Button(context); +var text = "My Button"; +button.setText(text); +``` +`button.setText(text)` - `text` is converted to `java.lang.String` + +### Converting JavaScript String to Kotlin string types + +JavaScript [String](http://www.w3schools.com/jsref/jsref_obj_string.asp) maps to [kotlin.String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html): + +```js +const kotlinClass = new com.example.KotlinClassWithStringProperty() +var text = 'My Button' +kotlinClass.setStringProperty(text) / +``` +`kotlinClass.setStringProperty(text)` - JavaScript `text` is converted to `kotlin.String` + +### Converting Java string types to JavaScript String +Both [java.lang.String](http://developer.android.com/reference/java/lang/String.html) and [java.lang.Character](http://docs.oracle.com/javase/7/docs/api/java/lang/Character.html) types are projected as JavaScript [String](http://www.w3schools.com/jsref/jsref_obj_string.asp): + +```js +var file = new java.io.File('/path/to/file') +var path = file.getPath() +``` + +`getPath()` - returns `java.lang.String`, converted to JS `String` + +### Converting Kotlin string types to JavaScript String + +Both [kotlin.String](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html) and [kotlin.Char](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-char/index.html) types are projected as JavaScript [String](http://www.w3schools.com/jsref/jsref_obj_string.asp): + +```kotlin +package com.example + +class KotlinClassWithStringAndCharProperty { + val stringProperty: String = "string property" + val charProperty: Char = 'c' +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithStringAndCharProperty() +var str1 = kotlinClass.getStringProperty() // returns kotlin.String, converted to JS String +var str2 = kotlinClass.getCharProperty() // returns kotlin.Char, converted to JS String +``` + +- `getStringProperty()`- returns `kotlin.String`, converted to `JS String` +- `getCharProperty()` - returns `kotlin.Char`, converted to JS `String` + +## Boolean conversion + +### JavaScript Boolean to Java boolean type +JavaScript [Boolean](http://www.w3schools.com/js/js_booleans.asp) maps to Java [primitive boolean](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html). + +```js +var context = ...; +var button = new android.widget.Button(context); +var enabled = false; +button.setEnabled(enabled); +``` + +`button.setEnabled(enabled)` - JavaScript Boolean `enabled` is converted to Java primitive boolean. + +### JavaScript Boolean to Kotlin boolean type +JavaScript [Boolean](http://www.w3schools.com/js/js_booleans.asp) maps to Kotlin class [Boolean](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-boolean/index.html). + +### Converting from Java boolean to JavaScript boolean + +Both the primitive [boolean](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Boolean](http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html) types are projected as JavaScript [Boolean](http://www.w3schools.com/jsref/jsref_obj_boolean.asp): + +```js +var context = ... +var button = new android.widget.Button(context); +var enabled = button.isEnabled(); +``` + +`isEnabled()` - returns `primitive boolean`, converted to JS `Boolean` + + +### Converting from Kotlin boolean to JavaScript boolean + +Kotlin's boolean type [kotlin.Boolean](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-boolean/index.html) is mapped to JavaScript [Boolean](http://www.w3schools.com/jsref/jsref_obj_boolean.asp): + + +```kotlin +package com.example + +class KotlinClassWithBooleanProperty { + val booleanProperty: Boolean = false +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithBooleanProperty() +var enabled = kotlinClass.getBooleanProperty() // returns Kotlin Boolean, converted to JS Boolean +``` + +`getBooleanProperty()` - returns `Kotlin Boolean`, converted to JS `Boolean`. + + +## Numeric data types + +### Converting JavaScript Number to Java/Kotlin numeric types + +Java and Kotlin have several primitive numeric types while JavaScript has the `Number` type only. Additionally, unlike JavaScript, Java and Kotlin support [Method Overloading](http://en.wikipedia.org/wiki/Function_overloading), which makes the numeric conversion more complex. + +Consider the following examples: + +```java +class MyObject extends java.lang.Object { + public void myMethod(byte value){ + } + + public void myMethod(short value){ + } + + public void myMethod(int value){ + } + + public void myMethod(long value){ + } + + public void myMethod(float value){ + } + + public void myMethod(double value){ + } +} +``` + +```kotlin +class MyObject : Any() { + fun myMethod(value: Byte) {} + + fun myMethod(value: Short) {} + + fun myMethod(value: Int) {} + + fun myMethod(value: Long) {} + + fun myMethod(value: Float) {} + + fun myMethod(value: Double) {} +} +``` + +The following logic applies when calling `myMethod` on a `myObject` instance from JavaScript: + +```js +var myObject = new MyObject() +``` +#### Implicit conversion + +- **integer** conversion: + +When you call + +```js +myObject.myMethod(10) +``` + +the runtime implicitly converts the JavaScript `10(Number)` to Java/Kotlin `Int` and then calls the `myMethod(Int)` method. + +::: warning Note +If there is no myMethod(Int) implementation, the Android runtime will try to choose the best possible overload with least conversion loss. If no such method is found an exception will be raised. +::: + +- **floating-point** conversion: + +```js +myObject.myMethod(10.5) // myMethod(Double) will be called. +``` +The JavaScript `10.5` `Number` gets converted to Java/Kotlin `double` and then myMethod(ouble) gets called. + +::: warning Note +In a scenario where no myMethod(double) implementation exists, the Runtime will attempt to select the most suitable possible overload, with the least amount of conversion loss. If no such method is found an exception thrown. +::: + +#### Explicit conversion + +To explicitly convert from a JavaScript `Number` to Java/Kotlin numeric data types, and call a specific method overload, NativeScript provides the following functions in the global scope: + +- `byte(number)` → (`Java primitive byte` | `Kotlin Byte` )
+ \- The number value will be truncated and only its first byte of the whole part will be used. +- `short(number)` → Java primitive short + +### Converting Java numeric types to JavaScript Number + +The following Java types are converted to the JavaScript [Number](http://www.w3schools.com/jsref/jsref_obj_number.asp): + +- Primitive [byte](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Byte](http://docs.oracle.com/javase/7/docs/api/java/lang/Byte.html) : + +```js +var byte = new java.lang.Byte('1') +var jsByteValue = byte.byteValue() // returns primitive byte, converted to Number +``` + +- Primitive [short](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Short](http://docs.oracle.com/javase/7/docs/api/java/lang/Short.html) : + +```js +var short = new java.lang.Short('1') +var jsShortValue = short.shortValue() // returns primitive short, converted to Number +``` + +- Primitive [int](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Integer](http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html): + +```js +var int = new java.lang.Integer('1') +var jsIntValue = int.intValue() // returns primitive int, converted to Number +``` +`intValue()` - returns `primitive int`, converted to `Number` +- Primitive [float](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Float](http://docs.oracle.com/javase/7/docs/api/java/lang/Float.html) : + +```js +var float = new java.lang.Float('1.5') +var jsFloatValue = float.floatValue() // returns primitive float, converted to Number +``` +`floatValue()` returns a `primitive float`, converted to `Number`. + +- Primitive [double](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) and reference [java.lang.Double](http://docs.oracle.com/javase/7/docs/api/java/lang/Double.html) : + +```js +var double = new java.lang.Double('1.5') +var jsDoubleValue = double.doubleValue() // returns primitive double, converted to Number +``` +`doubleValue()` returns a `primitive float`, converted to `Number`. +- Long & Primitive long + +[java.lang.Long](http://docs.oracle.com/javase/7/docs/api/java/lang/Long.html) and its primitive equivalent are special types which are projected to JavaScript by applying the following rules: + + - If the value is in the interval `(-2^53, 2^53)` then it is converted to [Number](http://www.w3schools.com/jsref/jsref_obj_number.asp) + - Else a special object with the following characteristics is created: + - Has Number.NaN set as a prototype + - Has value property set to the string representation of the Java long value + - Its `valueOf()` method returns NaN + - Its `toString()` method returns the string representation of the Java long value + +```java +public class TestClass { + public long getLongNumber54Bits(){ + return 1 << 54; + } + public long getLongNumber53Bits(){ + return 1 << 53; + } +} +``` + +```js +var testClass = new TestClass() +var jsNumber = testClass.getLongNumber53Bits() +var specialObject = testClass.getLongNumber54Bits() +``` + +`jsNumber` is a JavaScript Number and `specialObject` is the special object discussed above. + +### Converting Kotlin numeric types to JavaScript Number + +Similar to the conversion from Java numeric data types to JavaScript Number, the following Kotlin numeric data types are converted to JavaScript Number type: + +- Kotlin's byte type [kotlin.Byte](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-byte/index.html): + + +```kotlin +package com.example + +class KotlinClassWithByteProperty { + val byteProperty: Byte = 42 +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithByteProperty() +var jsByteValue = kotlinClass.getByteProperty() // returns Kotlin Byte, converted to Number +``` + +- Kotlin's short type [kotlin.Short](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-short/index.html): + +```kotlin +package com.example + +class KotlinClassWithShortProperty { + val shortProperty: Short = 42 +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithShortProperty() +var jsShortValue = kotlinClass.getShortProperty() +``` +`getShortProperty()` - returns `Kotlin Short`, converted to `Number` + +- Kotlin's integer type [kotlin.Int](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/index.html): + +```kotlin +package com.example + +class KotlinClassWithIntProperty { + val intProperty: Int = 42 +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithIntProperty() +var jsIntValue = kotlinClass.getIntProperty() +``` +`getIntProperty()` - returns `Kotlin Int`, converted to `Number` + +- Kotlin's float type [kotlin.Float](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-float/index.html): + +```kotlin +package com.example + +class KotlinClassWithFloatProperty { + val floatProperty: Float = 42.0f +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithFloatProperty() +``` +`getFloatProperty()` - returns `Kotlin Float`, converted to `Number` + +- Kotlin's double type [kotlin.Double](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/index.html): + +```kotlin +package com.example + +class KotlinClassWithDoubleProperty { + val doubleProperty: Double = 42.0 +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithDoubleProperty() +var jsDoubleValue = kotlinClass.getDoubleProperty() +``` +`getDoubleProperty()` - returns `Kotlin double`, converted to `Number` + +- Kotlin's long type [kotlin.Long](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-long/index.html) is a special type which is projected to JavaScript by applying the following rules: + + - If the value is in the interval `(-2^53, 2^53)` then it is converted to [Number](http://www.w3schools.com/jsref/jsref_obj_number.asp) + - Else a special object with the following characteristics is created: + - Has Number.NaN set as a prototype + - Has value property set to the string representation of the Kotlin long value + - Its valueOf() method returns NaN + - Its toString() method returns the string representation of the Kotlin long value + +```kotlin +package com.example + +class KotlinClassWithLongProperties { + val longNumber54Bits: Long + get() = (1 shl 54).toLong() + val longNumber53Bits: Long + get() = (1 shl 53).toLong() +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithLongProperties() +var jsNumber = kotlinClass.getLongNumber53Bits() +var specialObject = kotlinClass.getLongNumber54Bits() +``` +`jsNumber` is a JavaScript Number and `specialObject` is the special object discussed above. + +## Converting Undefined & Null + +JavaScript [Undefined](http://www.w3schools.com/jsref/jsref_undefined.asp) & [Null](https://www.w3schools.com/js/js_type_conversion.asp) maps to Java and Kotlin [null literal](http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.7) (or null pointer). + +```js +var context = ...; +var button = new android.widget.Button(context); +button.setOnClickListener(undefined); +``` +In the abve example, the Java call will be made using the `null` keyword. + +## Array conversion + +A JavaScript [Array](http://www.w3schools.com/jsref/jsref_obj_array.asp) is implicitly converted to a [Java Array](http://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html) or a [Kotlin Array](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/index.html), using the above described rules for type conversion of the array's elements. For example: + +```js +var items = ['One', 'Two', 'Three'] +var myObject = new MyObject() +myObject.myMethod(items) +``` + +```java +class MyObject extends java.lang.Object { + public void myMethod(java.lang.String[] items){ + } +} +``` + +```kotlin +class MyObject : Any() { + fun myMethod(items: Array) {} +} +``` + +### Converting from Java/Kotlin arrays to JavaScript array +Array in Java/Kotlin is a special [java.lang.Object](http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html) that have an implicit Class associated. A Java/Kotlin Array is projected to JavaScript as a special JavaScript proxy object with the following characteristics: + +- Has length property +- Has registered indexed getter and setter callbacks, which: + - If the array contains elements of type convertible to a JavaScript type, then accessing the i-th element will return a converted type + - If the array contains elements of type non-convertible to JavaScript, then accessing the i-th element will return a proxy object over the Java/Kotlin type see [Accessing APIs](#accessing-apis) + + + +```js +var directory = new java.io.File('path/to/myDir') +var files = directory.listFiles() // files is a special object as described above +var singleFile = files[0] // the indexed getter callback is triggered and a proxy object over the java.io.File is returned +``` + +```kotlin +package com.example + +class KotlinClassWithStringArrayProperty { + val stringArrayProperty: Array = arrayOf("element1", "element2", "element3") +} +``` + +```js +var kotlinClass = new com.example.KotlinClassWithStringArrayProperty() +var kotlinArray = kotlinClass.getStringArrayProperty() // kotlinArray is a special object as described above +var firstStringElement = kotlinArray[0] // the indexed getter callback is triggered and the kotlin.String is returned as a JS string +``` + +::: warning Note +A Java/Kotlin Array is intentionally not converted to a JavaScript [Array](http://www.w3schools.com/jsref/jsref_obj_array.asp) for the sake of performance, especially when it comes to large arrays. +::: + +##### Array of Objects + +In scenarios where the creation of Java/Kotlin arrays from JavaScript is mandatory. In the given scenario, we have extended the built-in JavaScript Array object by adding a custom method named create. By doing so, we have augmented the default functionality of the Array object with our own implementation to cater to specific requirements in built-in JavaScript [`Array` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). Here are some examples how to use `Array.create` method: + +```js +// the following statement is equivalent to byte[] byteArr = new byte[10]; +var byteArr = Array.create('byte', 10) + +// the following statement is equivalent to String[] stringArr = new String[10]; +var stringArr = Array.create(java.lang.String, 10) +``` + +Here is the full specification for `Array.create` method + +```js +Array.create(elementClassName, length) +``` + +```js +Array.create(javaClassCtorFunction, length) +``` + +The first signature accepts a `string` type for an `elementClassName`. This option becomes beneficial when the creation of a Java array of primitive types (e.g. `char`, `boolean`, `byte`, `short`, `int`, `long`, `float` and `double`) is required. This also applicable when the creation of Java jagged arrays is needed. For this scenario `elementClassName` must be the standard JNI class notation. Here are some examples: + +```js +// equivalent to int[][] jaggedIntArray2 = new int[10][]; +var jaggedIntArray2 = Array.create('[I', 10) + +// equivalent to boolean[][][] jaggedBooleanArray3 = new boolean[10][][]; +var jaggedBooleanArray3 = Array.create('[[Z', 10) + +// equivalent to Object[][][][] jaggedObjectArray4 = new Object[10][][][]; +var jaggedObjectArray4 = Array.create('[[[Ljava.lang.Object;', 10) +``` + +The second signature requires you to provide the `javaClassCtorFunction`, which must be the JavaScript constructor function representing the desired Java type. Here are some examples: + +```js +// equivalent to String[] stringArr = new String[10]; +var stringArr = Array.create(java.lang.String, 10) + +// equivalent to Object[] objectArr = new Object[10]; +var objectArr = Array.create(java.lang.Object, 10) +``` + +#### Array of Primitive Types + +When dealing with arrays of primitive types, automatic marshalling is not supported. To pass them as arguments to a method, you'll need to use wrapper classes (e.g., Integer, Double) to convert the primitives into objects, allowing for automatic marshalling. + +```java +public static void myMethod(int[] someParam) +``` + +Then yoy need to invoke it as follows: + +```js +let arr = Array.create('int', 3) +arr[0] = 1 +arr[1] = 2 +arr[2] = 3 + +SomeObject.myMethod(arr) // assuming the method is accepting an array of primitive types +``` + +However there are some other helpful classes we can use to create a few other arrays of primitive types + +```js +const byteArray = java.nio.ByteBuffer.wrap([1]).array() +const shortArray = java.nio.ShortBuffer.wrap([1]).array() +const intArray = java.nio.IntBuffer.wrap([1]).array() +const longArray = java.nio.LongBuffer.wrap([1]).array() +const floatArray = java.nio.FloatBuffer.wrap([1]).array() +const doubleArray = java.nio.DoubleBuffer.wrap([1]).array() +``` + +##### Two-Dimensional Arrays of Primitive Types + +The above scenario gets more tricky with two-dimensional arrays. Consider a Java method that accepts as an argument a two-dimensional array: + +```java +public static void myMethod(java.lang.Integer[][] someParam) +``` + +The marshalled JavaScript code will look like this: + +```js +let arr = Array.create('[Ljava.lang.Integer;', 2) +let elements = Array.create('java.lang.Integer', 3) +elements[0] = new java.lang.Integer(1) +elements[1] = new java.lang.Integer(2) +elements[2] = new java.lang.Integer(3) +arr[0] = elements + +SomeObject.myMethod(arr) // assuming the method is accepting a two-dimensional array of primitive types +``` + + +```kotlin +interface Printer { + fun print(content: String) + fun print(content: String, offset: Int) +} + +interface Copier { + fun copy(content: String): String +} + +interface Writer { + fun write(arr: Array) + fun writeLine(arr: Array) +} +``` + +Implementing the interfaces: + +```java +public class MyVersatileCopywriter implements Printer, Copier, Writer { + public void print(String content) { ... } + + public void print(String content, int offset) { ... } + + public String copy(String content) { ... } + + public void write(Object[] arr) { ... } + + public void writeLine(Object[] arr) { ... } +} +``` + +```kotlin +class MyVersatileCopywriter: Printer, Copier, Writer{ + + override fun print(content: String) { ... } + + override fun print(content: String, offset: Int) { ... } + + override fun copy(content: String): String { ... } + + override fun write(arr: Array) { ... } + + override fun writeLine(arr: Array) { ... } +} +``` +The same result can be achieved in NativeScript by extending any valid object that inherits [Java Object](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html). + +- In JavaScript - Declare an `interfaces` array in the implementation +- Using Typescript syntax - apply a `decorator` to the extended class (note `@Interfaces([...])`) + +Using Javascript syntax - attach `interfaces` array to implementation object of the extend call + +```js +let MyVersatileCopyWriter = java.lang.Object.extend({ + interfaces: [com.a.b.Printer, com.a.b.Copier, com.a.b.Writer], /* the interfaces that will be inherited by the resulting class */ + print: function() { ... }, /* implementing the 'print' methods from Printer */ + copy: function() { ... }, /* implementing the 'copy' method from Copier */ + write: function() { ... }, /* implementing the 'write' method from Writer */ + writeLine: function() { ... }, /* implementing the 'writeLine' method from Writer */ + toString: function() { ... } /* override `java.lang.Object's` `toString */ +}); +``` + +```ts +@Interfaces([com.a.b.Printer, com.a.b.Copier, com.a.b.Writer]) /* the interfaces that will be inherited by the resulting MyVersatileCopyWriter class */ +class MyVersatileCopyWriter extends java.lang.Object { + constructor() { + super(); + return global.__native(this); + } + + print() { ... } + copy() { ... } + write() { ... } + writeLine() { ... } +} +``` + +:::warning +- Implementing two interfaces with the same method signature will generate just 1 method. It is the implementor's responsibility to define how the method will behave for both interfaces + +- Implementing two interfaces with the same _method name_, _parameter number_, but **different return type** (`void a()` vs `boolean a()`) will result in a compilation error. +::: + +:::warning Note +Java/Kotlin method overloads are handled by the developer by explicitly checking the arguments count of the invoked function +::: + +```ts +class MyVersatileCopyWriter extends ... { + constructor() { + super(); + return global.__native(this); + } + ... + print() { + let content = ""; + let offset = 0; + + if (arguments.length == 2) { + offset = arguments[1]; + } + + content = arguments[0]; + + // do stuff + } + ... +} +``` +:::tip Note +In OOP, when a class extends another class aka inheritance, the new class not only gains access to the interface methods it must implement, but also has the ability to override methods from the extended class. Moreover, it can introduce new methods specific to the new class's functionality. This enables the new class to extend and enhance the behaviour of its parent class while providing additional functionality of its own. +::: + +## Java nested types in NativeScript + +```java +public class Outer { + public class Inner { + // inner and nested class + } + + public static class Nested { + // nested but not inner class + } +} +``` +```java +//Instantiate nested types +Outer outer = new Outer(); +Outer.Inner inner1 = outer.new Inner(); + +Outer.Inner inner2 = new Outer().new Inner(); + +Outer.Nested nested = new Outer.Nested() +``` +```ts +var outer = new Outer(); + +var inner1 = new outer.Inner(); + +var inner2 = new new Outer().Inner(); + +var nested = new Outer.Nested(); + +```` +## Kotlin Types + +All Kotlin types are projected to JavaScript using the Package and Class proxies as described in + +## Kotlin Companion objects + +You can access Kotlin's [companion objects](https://khan.github.io/kotlin-for-python-developers/#objects-and-companion-objects#companion-objects) via the `Companion` field: + +```kotlin +package com.example + +class KotlinClassWithCompanion { + companion object { + fun getDataFromCompanion() = "some data" + } +} +``` + +```js +var companion = com.example.KotlinClassWithCompanion.Companion +var data = companion.getDataFromCompanion() +``` + +## Kotlin Object + +To access the Kotlin's [objects](https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html#object-declarations), use the `INSTANCE` field: + +```kotlin +package com.example + +object KotlinObject { + fun getDataFromObject() = "some data" +} +``` + +```js +var objectInstance = com.example.KotlinObject.INSTANCE +var data = objectInstance.getDataFromObject() +``` + +## Accessing Kotlin properties + +To access the Kotlin's [properties](https://kotlinlang.org/docs/reference/properties.html#properties-and-fields), use their compiler-generated get/set methods. Non-boolean Kotlin properties could be used in NativeScript applications as JS fields as well. + + +```kotlin +package com.example + +class KotlinClassWithStringProperty(var stringProperty: kotlin.String) +``` + +```js +var kotlinClass = new com.example.KotlinClassWithStringProperty() + +var propertyValue = kotlinClass.getStringPropert() +kotlinClass.setStringProperty('example') + +propertyValue = kotlinClass.stringProperty +kotlinClass.stringProperty = 'second example' +``` + +## Accessing Kotlin package-level functions + +In order to use a Kotlin [package-level function](https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#package-level-functions), the class where it's defined should be known. Let's take a look at an example: + +```kotlin +package com.example + +fun getRandomNumber() = 42 +``` + +```js +var randomNumber = com.example.FunctionsKt.getRandomNumber() +``` + +In the example above, the class `FunctionsKt` is autogenerated by the Kotlin compiler and its name is based on the name of the file where the functions are defined. Kotlin supports annotating a file to have a user provided name and this simplifies using package-level functions: + +```js +var randomNumber = com.example.UtilityFunctions.getRandomNumber() +``` + +```kotlin +@file:JvmName("UtilityFunctions") +package com.example + +fun getRandomNumber() = 42 +``` + +## Accessing Kotlin extension functions + +In order to use an extension function, the class where it's defined is required to be known. Also, when invoking this function, the first parameter should be an instance of the type for which the function is defined. Let's take a look at an example: + +```kotlin +package com.example + +import java.util.ArrayList + +fun ArrayList.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) { + val temp = this[firstElementIndex] + this[firstElementIndex] = this[secondElementIndex] + this[secondElementIndex] = temp +} +``` +```js +var arrayList = new java.util.ArrayList() +arrayList.add('firstElement') +arrayList.add('secondElement') +com.example.Extensions.switchPlaces(arrayList, 0, 1) +``` + +In the example above, the class `ExtensionsKt` is autogenerated by the Kotlin compiler and its name is derived on the name of the file where the functions are defined. Kotlin supports annotating a file to have a user provided name, simplifying using package-level functions: + +```kotlin +@file:JvmName("ExtensionFunctions") +package com.example + +import java.util.ArrayList + +fun ArrayList.switchPlaces(firstElementIndex: Int, secondElementIndex: Int) { + val temp = this[firstElementIndex] + this[firstElementIndex] = this[secondElementIndex] + this[secondElementIndex] = temp +} +``` + +```js +var arrayList = new java.util.ArrayList() +arrayList.add('firstElement') +arrayList.add('secondElement') +com.example.ExtensionFunctions.switchPlaces(arrayList, 0, 1) +``` + diff --git a/content/sidebar.ts b/content/sidebar.ts index 47fb015f..9459d2ed 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -205,6 +205,15 @@ export default [ text: 'Code Sharing', link: '/guide/code-sharing', }, + { + text: 'Marshalling', + items: [ + { + text: 'Android Marshalling', + link: '/guide/android-marshalling', + }, + ], + }, { text: 'Property System', link: '/guide/property-system' From 9da77e91945b1db09b9a0219b296c0febca6bb0b Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:20:58 +0200 Subject: [PATCH 25/44] docs: Application (#34) Co-authored-by: Igor Randjelovic Co-authored-by: Nathan Walker --- .vitepress/theme/style.css | 2 +- .vitepress/theme/vitepress-theme.mjs | 2 +- content/guide/core/application.md | 404 +++++++++++++++++++++++++++ content/sidebar.ts | 5 + 4 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 content/guide/core/application.md diff --git a/.vitepress/theme/style.css b/.vitepress/theme/style.css index 830a3077..d96a1f7c 100644 --- a/.vitepress/theme/style.css +++ b/.vitepress/theme/style.css @@ -1 +1 @@ -*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}@media (min-width: 1920px){.container{max-width:1920px}}.form-input,.form-textarea,.form-select,.form-multiselect{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}.form-input:focus,.form-textarea:focus,.form-select:focus,.form-multiselect:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}.form-select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em;background-color:var(--tw-prose-quote-borders);border-radius:.375rem;padding-right:.375rem;padding-left:.375rem}.prose :where(code):not(:where([class~=not-prose] *)):before{content:normal}.prose :where(code):not(:where([class~=not-prose] *)):after{content:normal}.prose :where(a code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose] *)){vertical-align:top}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(video):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.7142857}.prose-sm :where(p):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-top:.8888889em;margin-bottom:.8888889em}.prose-sm :where(blockquote):not(:where([class~=not-prose] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-left:1.1111111em}.prose-sm :where(h1):not(:where([class~=not-prose] *)){font-size:2.1428571em;margin-top:0;margin-bottom:.8em;line-height:1.2}.prose-sm :where(h2):not(:where([class~=not-prose] *)){font-size:1.4285714em;margin-top:1.6em;margin-bottom:.8em;line-height:1.4}.prose-sm :where(h3):not(:where([class~=not-prose] *)){font-size:1.2857143em;margin-top:1.5555556em;margin-bottom:.4444444em;line-height:1.5555556}.prose-sm :where(h4):not(:where([class~=not-prose] *)){margin-top:1.4285714em;margin-bottom:.5714286em;line-height:1.4285714}.prose-sm :where(img):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(video):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(figcaption):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.3333333;margin-top:.6666667em}.prose-sm :where(code):not(:where([class~=not-prose] *)){font-size:.8571429em}.prose-sm :where(h2 code):not(:where([class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose] *)){font-size:.8888889em}.prose-sm :where(pre):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.6666667;margin-top:1.6666667em;margin-bottom:1.6666667em;border-radius:.25rem;padding:.6666667em 1em}.prose-sm :where(ol):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-left:1.5714286em}.prose-sm :where(ul):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-left:1.5714286em}.prose-sm :where(li):not(:where([class~=not-prose] *)){margin-top:.2857143em;margin-bottom:.2857143em}.prose-sm :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.4285714em}.prose-sm :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.4285714em}.prose-sm :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(hr):not(:where([class~=not-prose] *)){margin-top:2.8571429em;margin-bottom:2.8571429em}.prose-sm :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose] *)){padding-right:1em;padding-bottom:.6666667em;padding-left:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.6666667em 1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose-sm :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-base :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose-base :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-base :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-lg :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-lg :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8em;margin-bottom:.8em}.prose-xl :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-2xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8333333em;margin-bottom:.8333333em}.prose-2xl :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-2xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-slate{--tw-prose-body: #334155;--tw-prose-headings: #0f172a;--tw-prose-lead: #475569;--tw-prose-links: #0f172a;--tw-prose-bold: #0f172a;--tw-prose-counters: #64748b;--tw-prose-bullets: #cbd5e1;--tw-prose-hr: #e2e8f0;--tw-prose-quotes: #0f172a;--tw-prose-quote-borders: #e2e8f0;--tw-prose-captions: #64748b;--tw-prose-code: #0f172a;--tw-prose-pre-code: #e2e8f0;--tw-prose-pre-bg: #1e293b;--tw-prose-th-borders: #cbd5e1;--tw-prose-td-borders: #e2e8f0;--tw-prose-invert-body: #cbd5e1;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #94a3b8;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #94a3b8;--tw-prose-invert-bullets: #475569;--tw-prose-invert-hr: #334155;--tw-prose-invert-quotes: #f1f5f9;--tw-prose-invert-quote-borders: #334155;--tw-prose-invert-captions: #94a3b8;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #cbd5e1;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #475569;--tw-prose-invert-td-borders: #334155}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-y-0{top:0;bottom:0}.inset-x-0{left:0;right:0}.right-0{right:0}.top-12{top:3rem}.bottom-0{bottom:0}.top-24{top:6rem}.top-\[72px\]{top:72px}.top-0{top:0}.left-0{left:0}.-top-0\.5{top:-.125rem}.-top-0{top:-0px}.-left-6{left:-1.5rem}.top-0\.5{top:.125rem}.left-24{left:6rem}.right-3{right:.75rem}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.order-1{order:1}.m-0{margin:0}.-m-3{margin:-.75rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-4{margin-left:-1rem;margin-right:-1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-0{margin-top:0;margin-bottom:0}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.ml-3{margin-left:.75rem}.mt-10{margin-top:2.5rem}.mb-4{margin-bottom:1rem}.-mt-px{margin-top:-1px}.mr-6{margin-right:1.5rem}.ml-4{margin-left:1rem}.mr-4{margin-right:1rem}.mb-16{margin-bottom:4rem}.-mb-px{margin-bottom:-1px}.ml-2{margin-left:.5rem}.mt-12{margin-top:3rem}.mt-1{margin-top:.25rem}.ml-auto{margin-left:auto}.-ml-0\.5{margin-left:-.125rem}.-ml-0{margin-left:-0px}.mr-1\.5{margin-right:.375rem}.mr-1{margin-right:.25rem}.mb-2{margin-bottom:.5rem}.mb-0{margin-bottom:0}.mt-0\.5{margin-top:.125rem}.mr-2{margin-right:.5rem}.mt-0{margin-top:0}.ml-1{margin-left:.25rem}.-mr-1{margin-right:-.25rem}.ml-10{margin-left:2.5rem}.mt-3{margin-top:.75rem}.-mr-3{margin-right:-.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-8{height:2rem}.h-12{height:3rem}.h-6{height:1.5rem}.h-5{height:1.25rem}.h-full{height:100%}.h-4{height:1rem}.h-3{height:.75rem}.h-0\.5{height:.125rem}.h-0{height:0px}.h-2{height:.5rem}.h-\[42px\]{height:42px}.h-10{height:2.5rem}.h-screen{height:100vh}.max-h-full{max-height:100%}.max-h-\[400px\]{max-height:400px}.min-h-screen{min-height:100vh}.w-auto{width:auto}.w-\[50vw\]{width:50vw}.w-px{width:1px}.w-64{width:16rem}.w-full{width:100%}.w-6{width:1.5rem}.w-11{width:2.75rem}.w-5{width:1.25rem}.w-4{width:1rem}.w-3{width:.75rem}.w-20{width:5rem}.w-0\.5{width:.125rem}.w-0{width:0px}.w-screen{width:100vw}.w-72{width:18rem}.w-1\/4{width:25%}.w-1\/6{width:16.666667%}.w-2\/3{width:66.666667%}.w-10{width:2.5rem}.min-w-0{min-width:0px}.min-w-full{min-width:100%}.min-w-\[540px\]{min-width:540px}.max-w-none{max-width:none}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-7xl{max-width:80rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.origin-top{transform-origin:top}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-1{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.columns-1{-moz-columns:1;columns:1}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.content-center{align-content:center}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-6{gap:1.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.-space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(-.25rem * var(--tw-space-x-reverse));margin-left:calc(-.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-x-5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.25rem * var(--tw-space-x-reverse));margin-left:calc(1.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-slate-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(226 232 240 / var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(209 213 219 / var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-visible{overflow-y:visible}.overflow-y-scroll{overflow-y:scroll}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded-\[36px\]{border-radius:36px}.rounded-\[16px\]{border-radius:16px}.rounded-lg{border-radius:.5rem}.rounded{border-radius:.25rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.rounded-b-\[20px\]{border-bottom-right-radius:20px;border-bottom-left-radius:20px}.rounded-b-\[4px\]{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.rounded-b-\[16px\]{border-bottom-right-radius:16px;border-bottom-left-radius:16px}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-b-md{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l-0{border-left-width:0px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-l-2{border-left-width:2px}.border-t-0{border-top-width:0px}.border-b-4{border-bottom-width:4px}.border-white\/10{border-color:#ffffff1a}.border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.border-ns-blue{--tw-border-opacity: 1;border-color:rgb(101 173 241 / var(--tw-border-opacity))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.bg-white\/20{background-color:#fff3}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-ns-blue{--tw-bg-opacity: 1;background-color:rgb(101 173 241 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.bg-gray-200\/80{background-color:#e5e7ebcc}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-amber-100\/70{background-color:#fef3c7b3}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity))}.bg-pink-100{--tw-bg-opacity: 1;background-color:rgb(252 231 243 / var(--tw-bg-opacity))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity))}.bg-lime-100{--tw-bg-opacity: 1;background-color:rgb(236 252 203 / var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity))}.bg-slate-300\/40{background-color:#cbd5e166}.bg-opacity-30{--tw-bg-opacity: .3}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-slate-800{--tw-gradient-from: #1e293b;--tw-gradient-to: rgb(30 41 59 / 0);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-4{padding:1rem}.p-2{padding:.5rem}.p-1{padding:.25rem}.p-0\.5{padding:.125rem}.p-0{padding:0}.p-3{padding:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.pt-4{padding-top:1rem}.pr-4{padding-right:1rem}.pb-16{padding-bottom:4rem}.pt-10{padding-top:2.5rem}.pr-24{padding-right:6rem}.pr-2{padding-right:.5rem}.pl-4{padding-left:1rem}.pt-6{padding-top:1.5rem}.pl-2{padding-left:.5rem}.pb-2{padding-bottom:.5rem}.pb-\[70vh\]{padding-bottom:70vh}.pt-16{padding-top:4rem}.pb-10{padding-bottom:2.5rem}.pr-3{padding-right:.75rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pr-2\.5{padding-right:.625rem}.pb-2\.5{padding-bottom:.625rem}.pt-5{padding-top:1.25rem}.pb-6{padding-bottom:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-baseline{vertical-align:baseline}.font-sans{font-family:Inter var,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-6{line-height:1.5rem}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-ns-blue{--tw-text-opacity: 1;color:rgb(101 173 241 / var(--tw-text-opacity))}.text-white\/90{color:#ffffffe6}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-amber-900{--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity))}.text-pink-800{--tw-text-opacity: 1;color:rgb(157 23 77 / var(--tw-text-opacity))}.text-sky-800{--tw-text-opacity: 1;color:rgb(7 89 133 / var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity))}.text-lime-800{--tw-text-opacity: 1;color:rgb(63 98 18 / var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}.text-pink-500{--tw-text-opacity: 1;color:rgb(236 72 153 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-white{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity))}.ring-ns-blue{--tw-ring-opacity: 1;--tw-ring-color: rgb(101 173 241 / var(--tw-ring-opacity))}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-100{transition-duration:.1s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-150{transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.hover\:border-blue-600:hover{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity))}.hover\:border-blue-100:hover{--tw-border-opacity: 1;border-color:rgb(219 234 254 / var(--tw-border-opacity))}.hover\:bg-blue-400:hover{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.hover\:bg-black\/5:hover{background-color:#0000000d}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:text-slate-500:hover{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.hover\:text-slate-700:hover{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.hover\:text-gray-100:hover{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.hover\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.hover\:text-slate-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:border-cyan-300:focus{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.focus\:border-ns-blue:focus{--tw-border-opacity: 1;border-color:rgb(101 173 241 / var(--tw-border-opacity))}.focus\:bg-white\/20:focus{background-color:#fff3}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-inset:focus{--tw-ring-inset: inset}.focus\:ring-indigo-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-transparent:focus{--tw-ring-offset-color: transparent}.group:hover .group-hover\:text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.group:hover .group-hover\:text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.prose-a\:font-semibold :is(:where(a):not(:where([class~=not-prose] *))){font-weight:600}.prose-lead\:text-slate-500 :is(:where([class~=lead]):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .dark\:prose-invert{--tw-prose-body: var(--tw-prose-invert-body);--tw-prose-headings: var(--tw-prose-invert-headings);--tw-prose-lead: var(--tw-prose-invert-lead);--tw-prose-links: var(--tw-prose-invert-links);--tw-prose-bold: var(--tw-prose-invert-bold);--tw-prose-counters: var(--tw-prose-invert-counters);--tw-prose-bullets: var(--tw-prose-invert-bullets);--tw-prose-hr: var(--tw-prose-invert-hr);--tw-prose-quotes: var(--tw-prose-invert-quotes);--tw-prose-quote-borders: var(--tw-prose-invert-quote-borders);--tw-prose-captions: var(--tw-prose-invert-captions);--tw-prose-code: var(--tw-prose-invert-code);--tw-prose-pre-code: var(--tw-prose-invert-pre-code);--tw-prose-pre-bg: var(--tw-prose-invert-pre-bg);--tw-prose-th-borders: var(--tw-prose-invert-th-borders);--tw-prose-td-borders: var(--tw-prose-invert-td-borders)}.dark .dark\:-top-px{top:-1px}.dark .dark\:mt-px{margin-top:1px}.dark .dark\:block{display:block}.dark .dark\:hidden{display:none}.dark .dark\:divide-slate-700>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(51 65 85 / var(--tw-divide-opacity))}.dark .dark\:border-t{border-top-width:1px}.dark .dark\:border-b{border-bottom-width:1px}.dark .dark\:border-white\/20{border-color:#fff3}.dark .dark\:border-white\/5{border-color:#ffffff0d}.dark .dark\:border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.dark .dark\:border-transparent{border-color:transparent}.dark .dark\:border-white\/10{border-color:#ffffff1a}.dark .dark\:border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.dark .dark\:border-cyan-400{--tw-border-opacity: 1;border-color:rgb(34 211 238 / var(--tw-border-opacity))}.dark .dark\:border-slate-300\/30{border-color:#cbd5e14d}.dark .dark\:border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.dark .dark\:border-slate-50\/\[0\.06\]{border-color:#f8fafc0f}.dark .dark\:bg-transparent{background-color:transparent}.dark .dark\:bg-white\/10{background-color:#ffffff1a}.dark .dark\:bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.dark .dark\:bg-cyan-500{--tw-bg-opacity: 1;background-color:rgb(6 182 212 / var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.dark .dark\:bg-gray-900\/70{background-color:#111827b3}.dark .dark\:bg-amber-400\/10{background-color:#fbbf241a}.dark .dark\:bg-slate-900\/75{background-color:#0f172abf}.dark .dark\:bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark .dark\:bg-slate-900\/70{background-color:#0f172ab3}.dark .dark\:bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.dark .dark\:text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.dark .dark\:text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark .dark\:text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark .dark\:text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .dark\:text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.dark .dark\:text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:text-white\/90{color:#ffffffe6}.dark .dark\:text-white\/50{color:#ffffff80}.dark .dark\:text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity))}.dark .dark\:text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.dark .dark\:text-slate-300\/40{color:#cbd5e166}.dark .dark\:text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity))}.dark .dark\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark .dark\:shadow-cyan-300\/5{--tw-shadow-color: rgb(103 232 249 / .05);--tw-shadow: var(--tw-shadow-colored)}.dark .dark\:shadow-cyan-400\/20{--tw-shadow-color: rgb(34 211 238 / .2);--tw-shadow: var(--tw-shadow-colored)}.dark .dark\:ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark .dark\:ring-slate-800{--tw-ring-opacity: 1;--tw-ring-color: rgb(30 41 59 / var(--tw-ring-opacity))}.dark .dark\:ring-cyan-700{--tw-ring-opacity: 1;--tw-ring-color: rgb(14 116 144 / var(--tw-ring-opacity))}.dark .dark\:ring-amber-300\/30{--tw-ring-color: rgb(252 211 77 / .3)}.dark .dark\:ring-slate-300\/10{--tw-ring-color: rgb(203 213 225 / .1)}.dark .dark\:hover\:border-cyan-400:hover{--tw-border-opacity: 1;border-color:rgb(34 211 238 / var(--tw-border-opacity))}.dark .dark\:hover\:border-white\/40:hover{border-color:#fff6}.dark .dark\:hover\:bg-cyan-400:hover{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity))}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}.dark .dark\:hover\:bg-white\/5:hover{background-color:#ffffff0d}.dark .dark\:hover\:bg-slate-700\/50:hover{background-color:#33415580}.dark .dark\:hover\:text-cyan-300:hover{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-300:hover{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.dark .dark\:hover\:text-cyan-600:hover{--tw-text-opacity: 1;color:rgb(8 145 178 / var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-400:hover{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark .dark\:hover\:text-amber-200:hover{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity))}.dark .dark\:focus\:border-cyan-300:focus{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.dark .dark\:focus\:bg-white\/10:focus{background-color:#ffffff1a}.dark .group:hover .dark\:group-hover\:text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .group:hover .dark\:group-hover\:text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:prose-a\:text-cyan-400 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.dark .dark\:prose-hr\:border-slate-800 :is(:where(hr):not(:where([class~=not-prose] *))){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.dark .dark\:prose-lead\:text-slate-400 :is(:where([class~=lead]):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:mx-0{margin-left:0;margin-right:0}.sm\:h-10{height:2.5rem}.sm\:min-w-full{min-width:100%}.sm\:columns-2{-moz-columns:2;columns:2}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:gap-8{gap:2rem}.sm\:p-8{padding:2rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:pl-0{padding-left:0}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media (min-width: 768px){.md\:order-3{order:3}.md\:order-2{order:2}.md\:mt-0{margin-top:0}.md\:flex{display:flex}.md\:w-1\/2{width:50%}.md\:columns-3{-moz-columns:3;columns:3}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}}@media (min-width: 1024px){.lg\:relative{position:relative}.lg\:sticky{position:sticky}.lg\:z-50{z-index:50}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-1\/3{width:33.333333%}.lg\:w-auto{width:auto}.lg\:flex-none{flex:none}.lg\:columns-4{-moz-columns:4;columns:4}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-end{justify-content:flex-end}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.lg\:space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.lg\:divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}.lg\:border-b{border-bottom-width:1px}.lg\:border-slate-900\/10{border-color:#0f172a1a}.lg\:bg-white\/20{background-color:#fff3}.lg\:px-6{padding-left:1.5rem;padding-right:1.5rem}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-3{padding-left:.75rem;padding-right:.75rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:py-1{padding-top:.25rem;padding-bottom:.25rem}.lg\:pr-20{padding-right:5rem}.lg\:hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark .lg\:dark\:bg-white\/10{background-color:#ffffff1a}.dark .lg\:dark\:ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark .lg\:dark\:hover\:ring-cyan-400\/50:hover{--tw-ring-color: rgb(34 211 238 / .5)}}@media (min-width: 1280px){.xl\:block{display:block}.xl\:w-72{width:18rem}.xl\:max-w-xs{max-width:20rem}.xl\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width: 1536px){.\32xl\:container{width:100%}@media (min-width: 640px){.\32xl\:container{max-width:640px}}@media (min-width: 768px){.\32xl\:container{max-width:768px}}@media (min-width: 1024px){.\32xl\:container{max-width:1024px}}@media (min-width: 1280px){.\32xl\:container{max-width:1280px}}@media (min-width: 1536px){.\32xl\:container{max-width:1536px}}@media (min-width: 1920px){.\32xl\:container{max-width:1920px}}.\32xl\:max-h-\[600px\]{max-height:600px}.\32xl\:columns-3{-moz-columns:3;columns:3}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.\[\&_a\]\:text-blue-400 a{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity))}.\[\&_a\]\:text-blue-500 a{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.\[\&_a\]\:no-underline a{text-decoration-line:none}.dark .dark\:\[\&_a\]\:text-cyan-400 a{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.dark .dark\:\[\@supports\(backdrop-filter\:blur\(0\)\)\]\:bg-slate-900\/80{background-color:#0f172acc}}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider);background-color:#020617}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:#94a3b8;white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:#fff}.vp-code-group input:checked+label{color:#fff}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-] pre{margin-top:0}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}[class*=language-]{position:relative}[class*=language-]:not(.token){border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity));font-size:1.125rem;line-height:1.75rem;--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark [class*=language-]:not(.token){background-color:#1e293b99;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1)}[class*=language-] pre{position:relative;overflow-x:auto;background-color:transparent}[class*=language-] .lang{position:absolute;right:0;top:0;padding:1rem;font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity));opacity:1}[class*=language-] button{z-index:1;position:absolute;right:0;top:0;padding:1rem;font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity));opacity:0;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}[class*=language-] button:before{content:"Copy"}[class*=language-] button:hover{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}[class*=language-]:hover button{opacity:1}[class*=language-]:hover .lang{opacity:0;pointer-events:none}.highlight-lines{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;padding-top:1rem}.highlight-lines .highlighted{background-color:rgb(217 70 239 / var(--tw-bg-opacity));--tw-bg-opacity: .1}div[class*=language-].line-numbers-mode{padding-left:3rem}.line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;width:3.5rem;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-right-width:2px;border-color:rgb(17 24 39 / var(--tw-border-opacity));--tw-border-opacity: .5;padding-top:1rem;padding-right:1rem;text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity));z-index:3}:root{--shiki-color-text: #e5e7eb;--shiki-color-background: #111827;--shiki-token-constant: #fde047;--shiki-token-string: #22d3ee;--shiki-token-comment: #9ca3af;--shiki-token-keyword: #f472b6;--shiki-token-parameter: #aa0000;--shiki-token-function: #f0abfc;--shiki-token-string-expression: #22d3ee;--shiki-token-punctuation: #e5e7eb;--shiki-token-link: #ee0000}.custom-block.info,.custom-block.tip,.custom-block.warning,.custom-block.danger,.custom-block.details{margin-top:1.5rem;margin-bottom:1rem;overflow-x:auto;border-radius:.375rem;padding:.75rem 1.5rem}.custom-block [class*=language-]:not(.token){margin-left:-1.5rem;margin-right:-1.5rem;margin-top:.75rem;border-radius:0;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.custom-block [class*=language-]:not(.token):last-of-type{margin-bottom:-.75rem}.custom-block pre{margin:0;padding-left:1.5rem;padding-right:1.5rem}.custom-block.details>summary{margin-bottom:.25rem;cursor:pointer;font-weight:700}.custom-block>.custom-block-title{margin:0;font-weight:700}.custom-block>p{margin:0;margin-top:.75rem;line-height:1.625}.custom-block.info{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .custom-block.info{background-color:#1e293b1a;--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }.custom-block.tip{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(20 83 45 / var(--tw-text-opacity))}.dark .custom-block.tip{background-color:#1665341a;--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(134 239 172 / .1) }.custom-block.warning{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity))}.dark .custom-block.warning{background-color:#92400e1a;--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }.custom-block.danger{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(127 29 29 / var(--tw-text-opacity))}.dark .custom-block.danger{background-color:#991b1b1a;--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(252 165 165 / .1) }.custom-block.details{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .custom-block.details{background-color:#1e293b1a;--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }html.dark{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark a.header-anchor,a.header-anchor{position:absolute;left:-2rem;display:inline-flex;align-items:center;color:transparent;opacity:0;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}a.header-anchor:before{height:1.5rem;width:1.5rem;border-radius:.375rem;background-position:center;background-repeat:no-repeat;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity))}.dark a.header-anchor:before{--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity))}a.header-anchor:before{content:" ";background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}a.header-anchor:hover:before{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%231e293b' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}.dark a.header-anchor:hover:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%2322d3ee' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}[id]:hover>.header-anchor{opacity:1}h1,h2,h3,h4,h5,h6,h7{overflow-wrap:break-word}vite-error-overlay{z-index:9999999999}.resize-observer[data-v-b329ee4c]{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%;border:none;background-color:transparent;pointer-events:none;display:block;overflow:hidden;opacity:0}.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.v-popper__popper{z-index:10000;top:0;left:0;outline:none}.v-popper__popper.v-popper__popper--hidden{visibility:hidden;opacity:0;transition:opacity .15s,visibility .15s;pointer-events:none}.v-popper__popper.v-popper__popper--shown{visibility:visible;opacity:1;transition:opacity .15s}.v-popper__popper.v-popper__popper--skip-transition,.v-popper__popper.v-popper__popper--skip-transition>.v-popper__wrapper{transition:none!important}.v-popper__backdrop{position:absolute;top:0;left:0;width:100%;height:100%;display:none}.v-popper__inner{position:relative;box-sizing:border-box;overflow-y:auto}.v-popper__inner>div{position:relative;z-index:1;max-width:inherit;max-height:inherit}.v-popper__arrow-container{position:absolute;width:10px;height:10px}.v-popper__popper--arrow-overflow .v-popper__arrow-container,.v-popper__popper--no-positioning .v-popper__arrow-container{display:none}.v-popper__arrow-inner,.v-popper__arrow-outer{border-style:solid;position:absolute;top:0;left:0;width:0;height:0}.v-popper__arrow-inner{visibility:hidden;border-width:7px}.v-popper__arrow-outer{border-width:6px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner{left:-2px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{left:-1px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer{border-bottom-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-container{top:0}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{border-top-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-top-color:transparent!important}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner{top:-4px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{top:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{top:-1px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{border-left-width:0;border-left-color:transparent!important;border-top-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{left:-4px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{left:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-container{right:-10px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer{border-right-width:0;border-top-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner{left:-2px}.v-popper--theme-dropdown .v-popper__inner{background:#fff;color:#000;border-radius:6px;border:1px solid #ddd;box-shadow:0 6px 30px #0000001a}.v-popper--theme-dropdown .v-popper__arrow-inner{visibility:visible;border-color:#fff}.v-popper--theme-dropdown .v-popper__arrow-outer{border-color:#ddd}.v-popper--theme-tooltip .v-popper__inner{background:rgba(0,0,0,.8);color:#fff;border-radius:6px;padding:7px 12px 6px}.v-popper--theme-tooltip .v-popper__arrow-outer{border-color:#000c}/*! @docsearch/css 3.5.1 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::-moz-placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.algolia-search-box{position:absolute;top:0;right:0;bottom:0;left:0;opacity:.75}.DocSearch-Button{margin:0;border-radius:0;width:100%;opacity:0;border:0}.DocSearch-Button:hover{box-shadow:none}.DocSearch-Search-Icon,.DocSearch-Button-Placeholder,.DocSearch-Button-Key{display:none}@media (min-width: 768px){.DocSearch--active{overflow-y:scroll!important}}#docsearch-input:focus{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.DocSearch.DocSearch-Container{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.DocSearch{--docsearch-primary-color: #65adf1;--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-text-color: #111827;--docsearch-muted-color: #9ca3af;--docsearch-container-background: rgba(15, 23, 42, .2)}[id]{scroll-margin-top:88px;outline:2px solid transparent;outline-offset:2px}.h-sidebar{height:calc(100vh - 72px)}.code-tab-container [class*=language-]{margin:-1px 0 0;border-top-left-radius:0;border-top-right-radius:0}.code-tab-container [class*=language-] pre{margin:0}.device~.device{margin-top:1.5rem}@media (min-width: 640px){.device~.device{margin-top:0}}.device img{max-height:400px;-o-object-fit:contain;object-fit:contain}@media (min-width: 1536px){.device img{max-height:600px}}.step-list{counter-reset:StepList}.step-list li{position:relative}.step-list li::marker{color:transparent}.step-list li:before{counter-increment:StepList;content:counter(StepList) " ";position:absolute;left:-1.5rem;top:.125rem;display:flex;height:1.5rem;width:1.5rem;align-items:center;justify-content:center;border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity));font-size:.75rem;line-height:1rem;font-weight:700;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark .step-list li:before{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity))} +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter var,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}@media (min-width: 1920px){.container{max-width:1920px}}.form-input,.form-textarea,.form-select,.form-multiselect{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}.form-input:focus,.form-textarea:focus,.form-select:focus,.form-multiselect:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}.form-select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em;background-color:var(--tw-prose-quote-borders);border-radius:.375rem;padding-right:.375rem;padding-left:.375rem}.prose :where(code):not(:where([class~=not-prose] *)):before{content:normal}.prose :where(code):not(:where([class~=not-prose] *)):after{content:normal}.prose :where(a code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose] *)){vertical-align:top}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(video):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-sm{font-size:.875rem;line-height:1.7142857}.prose-sm :where(p):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em}.prose-sm :where([class~=lead]):not(:where([class~=not-prose] *)){font-size:1.2857143em;line-height:1.5555556;margin-top:.8888889em;margin-bottom:.8888889em}.prose-sm :where(blockquote):not(:where([class~=not-prose] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-left:1.1111111em}.prose-sm :where(h1):not(:where([class~=not-prose] *)){font-size:2.1428571em;margin-top:0;margin-bottom:.8em;line-height:1.2}.prose-sm :where(h2):not(:where([class~=not-prose] *)){font-size:1.4285714em;margin-top:1.6em;margin-bottom:.8em;line-height:1.4}.prose-sm :where(h3):not(:where([class~=not-prose] *)){font-size:1.2857143em;margin-top:1.5555556em;margin-bottom:.4444444em;line-height:1.5555556}.prose-sm :where(h4):not(:where([class~=not-prose] *)){margin-top:1.4285714em;margin-bottom:.5714286em;line-height:1.4285714}.prose-sm :where(img):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(video):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure):not(:where([class~=not-prose] *)){margin-top:1.7142857em;margin-bottom:1.7142857em}.prose-sm :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose-sm :where(figcaption):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.3333333;margin-top:.6666667em}.prose-sm :where(code):not(:where([class~=not-prose] *)){font-size:.8571429em}.prose-sm :where(h2 code):not(:where([class~=not-prose] *)){font-size:.9em}.prose-sm :where(h3 code):not(:where([class~=not-prose] *)){font-size:.8888889em}.prose-sm :where(pre):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.6666667;margin-top:1.6666667em;margin-bottom:1.6666667em;border-radius:.25rem;padding:.6666667em 1em}.prose-sm :where(ol):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-left:1.5714286em}.prose-sm :where(ul):not(:where([class~=not-prose] *)){margin-top:1.1428571em;margin-bottom:1.1428571em;padding-left:1.5714286em}.prose-sm :where(li):not(:where([class~=not-prose] *)){margin-top:.2857143em;margin-bottom:.2857143em}.prose-sm :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.4285714em}.prose-sm :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.4285714em}.prose-sm :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.1428571em}.prose-sm :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.1428571em}.prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(hr):not(:where([class~=not-prose] *)){margin-top:2.8571429em;margin-bottom:2.8571429em}.prose-sm :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(table):not(:where([class~=not-prose] *)){font-size:.8571429em;line-height:1.5}.prose-sm :where(thead th):not(:where([class~=not-prose] *)){padding-right:1em;padding-bottom:.6666667em;padding-left:1em}.prose-sm :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose-sm :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.6666667em 1em}.prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose-sm :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-sm :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-base :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose-base :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose-base :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose-base :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-base :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-lg :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-lg :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-lg :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8em;margin-bottom:.8em}.prose-xl :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.2em}.prose-xl :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.2em}.prose-xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-2xl :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.8333333em;margin-bottom:.8333333em}.prose-2xl :where(.prose>ul>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ul>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>ol>li>*:first-child):not(:where([class~=not-prose] *)){margin-top:1.3333333em}.prose-2xl :where(.prose>ol>li>*:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose-2xl :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.prose-slate{--tw-prose-body: #334155;--tw-prose-headings: #0f172a;--tw-prose-lead: #475569;--tw-prose-links: #0f172a;--tw-prose-bold: #0f172a;--tw-prose-counters: #64748b;--tw-prose-bullets: #cbd5e1;--tw-prose-hr: #e2e8f0;--tw-prose-quotes: #0f172a;--tw-prose-quote-borders: #e2e8f0;--tw-prose-captions: #64748b;--tw-prose-code: #0f172a;--tw-prose-pre-code: #e2e8f0;--tw-prose-pre-bg: #1e293b;--tw-prose-th-borders: #cbd5e1;--tw-prose-td-borders: #e2e8f0;--tw-prose-invert-body: #cbd5e1;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #94a3b8;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #94a3b8;--tw-prose-invert-bullets: #475569;--tw-prose-invert-hr: #334155;--tw-prose-invert-quotes: #f1f5f9;--tw-prose-invert-quote-borders: #334155;--tw-prose-invert-captions: #94a3b8;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #cbd5e1;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #475569;--tw-prose-invert-td-borders: #334155}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-y-0{top:0;bottom:0}.inset-x-0{left:0;right:0}.right-0{right:0}.top-12{top:3rem}.bottom-0{bottom:0}.top-24{top:6rem}.top-\[72px\]{top:72px}.top-0{top:0}.left-0{left:0}.-top-0\.5{top:-.125rem}.-top-0{top:-0px}.-left-6{left:-1.5rem}.top-0\.5{top:.125rem}.left-24{left:6rem}.right-3{right:.75rem}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.order-1{order:1}.m-0{margin:0}.-m-3{margin:-.75rem}.mx-auto{margin-left:auto;margin-right:auto}.-mx-4{margin-left:-1rem;margin-right:-1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-0{margin-top:0;margin-bottom:0}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.ml-3{margin-left:.75rem}.mt-10{margin-top:2.5rem}.mb-4{margin-bottom:1rem}.-mt-px{margin-top:-1px}.mr-6{margin-right:1.5rem}.ml-4{margin-left:1rem}.mr-4{margin-right:1rem}.mb-16{margin-bottom:4rem}.-mb-px{margin-bottom:-1px}.ml-2{margin-left:.5rem}.mt-12{margin-top:3rem}.mt-1{margin-top:.25rem}.ml-auto{margin-left:auto}.-ml-0\.5{margin-left:-.125rem}.-ml-0{margin-left:-0px}.mr-1\.5{margin-right:.375rem}.mr-1{margin-right:.25rem}.mb-2{margin-bottom:.5rem}.mb-0{margin-bottom:0}.mt-0\.5{margin-top:.125rem}.mr-2{margin-right:.5rem}.mt-0{margin-top:0}.ml-1{margin-left:.25rem}.-mr-1{margin-right:-.25rem}.ml-10{margin-left:2.5rem}.mt-3{margin-top:.75rem}.-mr-3{margin-right:-.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-8{height:2rem}.h-12{height:3rem}.h-6{height:1.5rem}.h-5{height:1.25rem}.h-full{height:100%}.h-4{height:1rem}.h-3{height:.75rem}.h-0\.5{height:.125rem}.h-0{height:0px}.h-2{height:.5rem}.h-\[42px\]{height:42px}.h-10{height:2.5rem}.h-screen{height:100vh}.max-h-full{max-height:100%}.max-h-\[400px\]{max-height:400px}.min-h-screen{min-height:100vh}.w-auto{width:auto}.w-\[50vw\]{width:50vw}.w-px{width:1px}.w-64{width:16rem}.w-full{width:100%}.w-6{width:1.5rem}.w-11{width:2.75rem}.w-5{width:1.25rem}.w-4{width:1rem}.w-3{width:.75rem}.w-20{width:5rem}.w-0\.5{width:.125rem}.w-0{width:0px}.w-screen{width:100vw}.w-72{width:18rem}.w-1\/4{width:25%}.w-1\/6{width:16.666667%}.w-2\/3{width:66.666667%}.w-10{width:2.5rem}.min-w-0{min-width:0px}.min-w-full{min-width:100%}.min-w-\[540px\]{min-width:540px}.max-w-none{max-width:none}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-7xl{max-width:80rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.origin-top{transform-origin:top}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-1{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.columns-1{-moz-columns:1;columns:1}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.content-center{align-content:center}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-6{gap:1.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.-space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(-.25rem * var(--tw-space-x-reverse));margin-left:calc(-.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-x-5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.25rem * var(--tw-space-x-reverse));margin-left:calc(1.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-slate-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(226 232 240 / var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(209 213 219 / var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-visible{overflow-y:visible}.overflow-y-scroll{overflow-y:scroll}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded-\[36px\]{border-radius:36px}.rounded-\[16px\]{border-radius:16px}.rounded-lg{border-radius:.5rem}.rounded{border-radius:.25rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.rounded-b-\[20px\]{border-bottom-right-radius:20px;border-bottom-left-radius:20px}.rounded-b-\[4px\]{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.rounded-b-\[16px\]{border-bottom-right-radius:16px;border-bottom-left-radius:16px}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-b-md{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l-0{border-left-width:0px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-l-2{border-left-width:2px}.border-t-0{border-top-width:0px}.border-b-4{border-bottom-width:4px}.border-white\/10{border-color:#ffffff1a}.border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.border-ns-blue{--tw-border-opacity: 1;border-color:rgb(101 173 241 / var(--tw-border-opacity))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.bg-white\/20{background-color:#fff3}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-ns-blue{--tw-bg-opacity: 1;background-color:rgb(101 173 241 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.bg-gray-200\/80{background-color:#e5e7ebcc}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-amber-100\/70{background-color:#fef3c7b3}.bg-red-100{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity))}.bg-pink-100{--tw-bg-opacity: 1;background-color:rgb(252 231 243 / var(--tw-bg-opacity))}.bg-sky-100{--tw-bg-opacity: 1;background-color:rgb(224 242 254 / var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity))}.bg-lime-100{--tw-bg-opacity: 1;background-color:rgb(236 252 203 / var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgb(219 234 254 / var(--tw-bg-opacity))}.bg-slate-300\/40{background-color:#cbd5e166}.bg-opacity-30{--tw-bg-opacity: .3}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-slate-800{--tw-gradient-from: #1e293b;--tw-gradient-to: rgb(30 41 59 / 0);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-4{padding:1rem}.p-2{padding:.5rem}.p-1{padding:.25rem}.p-0\.5{padding:.125rem}.p-0{padding:0}.p-3{padding:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-0{padding-top:0;padding-bottom:0}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.pt-4{padding-top:1rem}.pr-4{padding-right:1rem}.pb-16{padding-bottom:4rem}.pt-10{padding-top:2.5rem}.pr-24{padding-right:6rem}.pr-2{padding-right:.5rem}.pl-4{padding-left:1rem}.pt-6{padding-top:1.5rem}.pl-2{padding-left:.5rem}.pb-2{padding-bottom:.5rem}.pb-\[70vh\]{padding-bottom:70vh}.pt-16{padding-top:4rem}.pb-10{padding-bottom:2.5rem}.pr-3{padding-right:.75rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pr-2\.5{padding-right:.625rem}.pb-2\.5{padding-bottom:.625rem}.pt-5{padding-top:1.25rem}.pb-6{padding-bottom:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-baseline{vertical-align:baseline}.font-sans{font-family:Inter var,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-6{line-height:1.5rem}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.text-cyan-200{--tw-text-opacity: 1;color:rgb(165 243 252 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.text-ns-blue{--tw-text-opacity: 1;color:rgb(101 173 241 / var(--tw-text-opacity))}.text-white\/90{color:#ffffffe6}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-amber-900{--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity))}.text-red-800{--tw-text-opacity: 1;color:rgb(153 27 27 / var(--tw-text-opacity))}.text-pink-800{--tw-text-opacity: 1;color:rgb(157 23 77 / var(--tw-text-opacity))}.text-sky-800{--tw-text-opacity: 1;color:rgb(7 89 133 / var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity: 1;color:rgb(133 77 14 / var(--tw-text-opacity))}.text-lime-800{--tw-text-opacity: 1;color:rgb(63 98 18 / var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-blue-800{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}.text-pink-500{--tw-text-opacity: 1;color:rgb(236 72 153 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-50{opacity:.5}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-white{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity))}.ring-ns-blue{--tw-ring-opacity: 1;--tw-ring-color: rgb(101 173 241 / var(--tw-ring-opacity))}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-100{transition-duration:.1s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-150{transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.hover\:border-blue-600:hover{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity))}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity))}.hover\:border-blue-100:hover{--tw-border-opacity: 1;border-color:rgb(219 234 254 / var(--tw-border-opacity))}.hover\:bg-blue-400:hover{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.hover\:bg-black\/5:hover{background-color:#0000000d}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.hover\:text-slate-500:hover{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.hover\:text-slate-700:hover{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.hover\:text-gray-100:hover{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.hover\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.hover\:text-slate-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:border-cyan-300:focus{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity))}.focus\:border-ns-blue:focus{--tw-border-opacity: 1;border-color:rgb(101 173 241 / var(--tw-border-opacity))}.focus\:bg-white\/20:focus{background-color:#fff3}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-inset:focus{--tw-ring-inset: inset}.focus\:ring-indigo-600:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-transparent:focus{--tw-ring-offset-color: transparent}.group:hover .group-hover\:text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.group:hover .group-hover\:text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.prose-a\:font-semibold :is(:where(a):not(:where([class~=not-prose] *))){font-weight:600}.prose-lead\:text-slate-500 :is(:where([class~=lead]):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .dark\:prose-invert{--tw-prose-body: var(--tw-prose-invert-body);--tw-prose-headings: var(--tw-prose-invert-headings);--tw-prose-lead: var(--tw-prose-invert-lead);--tw-prose-links: var(--tw-prose-invert-links);--tw-prose-bold: var(--tw-prose-invert-bold);--tw-prose-counters: var(--tw-prose-invert-counters);--tw-prose-bullets: var(--tw-prose-invert-bullets);--tw-prose-hr: var(--tw-prose-invert-hr);--tw-prose-quotes: var(--tw-prose-invert-quotes);--tw-prose-quote-borders: var(--tw-prose-invert-quote-borders);--tw-prose-captions: var(--tw-prose-invert-captions);--tw-prose-code: var(--tw-prose-invert-code);--tw-prose-pre-code: var(--tw-prose-invert-pre-code);--tw-prose-pre-bg: var(--tw-prose-invert-pre-bg);--tw-prose-th-borders: var(--tw-prose-invert-th-borders);--tw-prose-td-borders: var(--tw-prose-invert-td-borders)}.dark .dark\:-top-px{top:-1px}.dark .dark\:mt-px{margin-top:1px}.dark .dark\:block{display:block}.dark .dark\:hidden{display:none}.dark .dark\:divide-slate-700>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(51 65 85 / var(--tw-divide-opacity))}.dark .dark\:border-t{border-top-width:1px}.dark .dark\:border-b{border-bottom-width:1px}.dark .dark\:border-white\/20{border-color:#fff3}.dark .dark\:border-white\/5{border-color:#ffffff0d}.dark .dark\:border-cyan-300{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.dark .dark\:border-transparent{border-color:transparent}.dark .dark\:border-white\/10{border-color:#ffffff1a}.dark .dark\:border-slate-800{--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.dark .dark\:border-cyan-400{--tw-border-opacity: 1;border-color:rgb(34 211 238 / var(--tw-border-opacity))}.dark .dark\:border-slate-300\/30{border-color:#cbd5e14d}.dark .dark\:border-slate-700{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}.dark .dark\:border-slate-50\/\[0\.06\]{border-color:#f8fafc0f}.dark .dark\:bg-transparent{background-color:transparent}.dark .dark\:bg-white\/10{background-color:#ffffff1a}.dark .dark\:bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.dark .dark\:bg-cyan-500{--tw-bg-opacity: 1;background-color:rgb(6 182 212 / var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity))}.dark .dark\:bg-gray-900\/70{background-color:#111827b3}.dark .dark\:bg-amber-400\/10{background-color:#fbbf241a}.dark .dark\:bg-slate-900\/75{background-color:#0f172abf}.dark .dark\:bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark .dark\:bg-slate-900\/70{background-color:#0f172ab3}.dark .dark\:bg-slate-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.dark .dark\:text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.dark .dark\:text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark .dark\:text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark .dark\:text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .dark\:text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.dark .dark\:text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:text-white\/90{color:#ffffffe6}.dark .dark\:text-white\/50{color:#ffffff80}.dark .dark\:text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.dark .dark\:text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity))}.dark .dark\:text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.dark .dark\:text-slate-300\/40{color:#cbd5e166}.dark .dark\:text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity))}.dark .dark\:shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark .dark\:shadow-cyan-300\/5{--tw-shadow-color: rgb(103 232 249 / .05);--tw-shadow: var(--tw-shadow-colored)}.dark .dark\:shadow-cyan-400\/20{--tw-shadow-color: rgb(34 211 238 / .2);--tw-shadow: var(--tw-shadow-colored)}.dark .dark\:ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark .dark\:ring-slate-800{--tw-ring-opacity: 1;--tw-ring-color: rgb(30 41 59 / var(--tw-ring-opacity))}.dark .dark\:ring-cyan-700{--tw-ring-opacity: 1;--tw-ring-color: rgb(14 116 144 / var(--tw-ring-opacity))}.dark .dark\:ring-amber-300\/30{--tw-ring-color: rgb(252 211 77 / .3)}.dark .dark\:ring-slate-300\/10{--tw-ring-color: rgb(203 213 225 / .1)}.dark .dark\:hover\:border-cyan-400:hover{--tw-border-opacity: 1;border-color:rgb(34 211 238 / var(--tw-border-opacity))}.dark .dark\:hover\:border-white\/40:hover{border-color:#fff6}.dark .dark\:hover\:bg-cyan-400:hover{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity))}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}.dark .dark\:hover\:bg-white\/5:hover{background-color:#ffffff0d}.dark .dark\:hover\:bg-slate-700\/50:hover{background-color:#33415580}.dark .dark\:hover\:text-cyan-300:hover{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-300:hover{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.dark .dark\:hover\:text-cyan-600:hover{--tw-text-opacity: 1;color:rgb(8 145 178 / var(--tw-text-opacity))}.dark .dark\:hover\:text-slate-400:hover{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.dark .dark\:hover\:text-amber-200:hover{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity))}.dark .dark\:focus\:border-cyan-300:focus{--tw-border-opacity: 1;border-color:rgb(103 232 249 / var(--tw-border-opacity))}.dark .dark\:focus\:bg-white\/10:focus{background-color:#ffffff1a}.dark .group:hover .dark\:group-hover\:text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.dark .group:hover .dark\:group-hover\:text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}.dark .dark\:prose-a\:text-cyan-400 :is(:where(a):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}.dark .dark\:prose-hr\:border-slate-800 :is(:where(hr):not(:where([class~=not-prose] *))){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}.dark .dark\:prose-lead\:text-slate-400 :is(:where([class~=lead]):not(:where([class~=not-prose] *))){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:mx-0{margin-left:0;margin-right:0}.sm\:h-10{height:2.5rem}.sm\:min-w-full{min-width:100%}.sm\:columns-2{-moz-columns:2;columns:2}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:gap-8{gap:2rem}.sm\:p-8{padding:2rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:px-3{padding-left:.75rem;padding-right:.75rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:pl-0{padding-left:0}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}}@media (min-width: 768px){.md\:order-3{order:3}.md\:order-2{order:2}.md\:mt-0{margin-top:0}.md\:flex{display:flex}.md\:w-1\/2{width:50%}.md\:columns-3{-moz-columns:3;columns:3}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}}@media (min-width: 1024px){.lg\:relative{position:relative}.lg\:sticky{position:sticky}.lg\:z-50{z-index:50}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:w-1\/3{width:33.333333%}.lg\:w-auto{width:auto}.lg\:flex-none{flex:none}.lg\:columns-4{-moz-columns:4;columns:4}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-end{justify-content:flex-end}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.lg\:space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.lg\:divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse: 0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}.lg\:border-b{border-bottom-width:1px}.lg\:border-slate-900\/10{border-color:#0f172a1a}.lg\:bg-white\/20{background-color:#fff3}.lg\:px-6{padding-left:1.5rem;padding-right:1.5rem}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-3{padding-left:.75rem;padding-right:.75rem}.lg\:py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.lg\:py-1{padding-top:.25rem;padding-bottom:.25rem}.lg\:pr-20{padding-right:5rem}.lg\:hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark .lg\:dark\:bg-white\/10{background-color:#ffffff1a}.dark .lg\:dark\:ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.dark .lg\:dark\:hover\:ring-cyan-400\/50:hover{--tw-ring-color: rgb(34 211 238 / .5)}}@media (min-width: 1280px){.xl\:block{display:block}.xl\:w-72{width:18rem}.xl\:max-w-xs{max-width:20rem}.xl\:px-10{padding-left:2.5rem;padding-right:2.5rem}}@media (min-width: 1536px){.\32xl\:container{width:100%}@media (min-width: 640px){.\32xl\:container{max-width:640px}}@media (min-width: 768px){.\32xl\:container{max-width:768px}}@media (min-width: 1024px){.\32xl\:container{max-width:1024px}}@media (min-width: 1280px){.\32xl\:container{max-width:1280px}}@media (min-width: 1536px){.\32xl\:container{max-width:1536px}}@media (min-width: 1920px){.\32xl\:container{max-width:1920px}}.\32xl\:max-h-\[600px\]{max-height:600px}.\32xl\:columns-3{-moz-columns:3;columns:3}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.\[\&_a\]\:text-blue-400 a{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity))}.\[\&_a\]\:text-blue-500 a{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.\[\&_a\]\:no-underline a{text-decoration-line:none}.dark .dark\:\[\&_a\]\:text-cyan-400 a{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity))}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){.dark .dark\:\[\@supports\(backdrop-filter\:blur\(0\)\)\]\:bg-slate-900\/80{background-color:#0f172acc}}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider);background-color:#020617}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:#94a3b8;white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:#fff}.vp-code-group input:checked+label{color:#fff}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-] pre{margin-top:0}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}[class*=language-]{position:relative}[class*=language-]:not(.token){border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity));font-size:1.125rem;line-height:1.75rem;--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark [class*=language-]:not(.token){background-color:#1e293b99;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1)}[class*=language-] pre{position:relative;overflow-x:auto;background-color:transparent}[class*=language-] .lang{position:absolute;right:0;top:0;padding:1rem;font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity));opacity:1}[class*=language-] button{z-index:1;position:absolute;right:0;top:0;padding:1rem;font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity));opacity:0;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}[class*=language-] button:before{content:"Copy"}[class*=language-] button:hover{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity))}[class*=language-]:hover button{opacity:1}[class*=language-]:hover .lang{opacity:0;pointer-events:none}.highlight-lines{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden;padding-top:1rem}.highlight-lines .highlighted{background-color:rgb(217 70 239 / var(--tw-bg-opacity));--tw-bg-opacity: .1}div[class*=language-].line-numbers-mode{padding-left:3rem}.line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;width:3.5rem;-webkit-user-select:none;-moz-user-select:none;user-select:none;border-right-width:2px;border-color:rgb(17 24 39 / var(--tw-border-opacity));--tw-border-opacity: .5;padding-top:1rem;padding-right:1rem;text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity));z-index:3}:root{--shiki-color-text: #e5e7eb;--shiki-color-background: #111827;--shiki-token-constant: #fde047;--shiki-token-string: #22d3ee;--shiki-token-comment: #9ca3af;--shiki-token-keyword: #f472b6;--shiki-token-parameter: #aa0000;--shiki-token-function: #f0abfc;--shiki-token-string-expression: #22d3ee;--shiki-token-punctuation: #e5e7eb;--shiki-token-link: #ee0000}.custom-block.info,.custom-block.tip,.custom-block.warning,.custom-block.danger,.custom-block.details{margin-top:1.5rem;margin-bottom:1rem;overflow-x:auto;border-radius:.375rem;padding:.75rem 1.5rem}.custom-block [class*=language-]:not(.token){margin-left:-1.5rem;margin-right:-1.5rem;margin-top:.75rem;border-radius:0;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.custom-block [class*=language-]:not(.token):last-of-type{margin-bottom:-.75rem}.custom-block pre{margin:0;padding-left:1.5rem;padding-right:1.5rem}.custom-block.details>summary{margin-bottom:.25rem;cursor:pointer;font-weight:700}.custom-block>.custom-block-title{margin:0;font-weight:700}.custom-block>p{margin:0;margin-top:.75rem;line-height:1.625}.custom-block.info{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .custom-block.info{background-color:#1e293b1a;--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }.custom-block.tip{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(20 83 45 / var(--tw-text-opacity))}.dark .custom-block.tip{background-color:#1665341a;--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(134 239 172 / .1) }.custom-block.warning{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(120 53 15 / var(--tw-text-opacity))}.dark .custom-block.warning{background-color:#92400e1a;--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }.custom-block.danger{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(127 29 29 / var(--tw-text-opacity))}.dark .custom-block.danger{background-color:#991b1b1a;--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(252 165 165 / .1) }.custom-block.details{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.dark .custom-block.details{background-color:#1e293b1a;--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity));--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-color: rgb(203 213 225 / .1) }html.dark{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark a.header-anchor,a.header-anchor{position:absolute;left:-2rem;display:inline-flex;align-items:center;color:transparent;opacity:0;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}a.header-anchor:before{height:1.5rem;width:1.5rem;border-radius:.375rem;background-position:center;background-repeat:no-repeat;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity))}.dark a.header-anchor:before{--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity))}a.header-anchor:before{content:" ";background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}a.header-anchor:hover:before{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%231e293b' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}.dark a.header-anchor:hover:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' aria-hidden='true'%3E%3Cpath d='M3.75 1v10M8.25 1v10M1 3.75h10M1 8.25h10' stroke='%2322d3ee' stroke-width='1.5' stroke-linecap='round'%3E%3C/path%3E%3C/svg%3E")}[id]:hover>.header-anchor{opacity:1}h1,h2,h3,h4,h5,h6,h7{overflow-wrap:break-word}vite-error-overlay{z-index:9999999999}.resize-observer[data-v-b329ee4c]{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%;border:none;background-color:transparent;pointer-events:none;display:block;overflow:hidden;opacity:0}.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.v-popper__popper{z-index:10000;top:0;left:0;outline:none}.v-popper__popper.v-popper__popper--hidden{visibility:hidden;opacity:0;transition:opacity .15s,visibility .15s;pointer-events:none}.v-popper__popper.v-popper__popper--shown{visibility:visible;opacity:1;transition:opacity .15s}.v-popper__popper.v-popper__popper--skip-transition,.v-popper__popper.v-popper__popper--skip-transition>.v-popper__wrapper{transition:none!important}.v-popper__backdrop{position:absolute;top:0;left:0;width:100%;height:100%;display:none}.v-popper__inner{position:relative;box-sizing:border-box;overflow-y:auto}.v-popper__inner>div{position:relative;z-index:1;max-width:inherit;max-height:inherit}.v-popper__arrow-container{position:absolute;width:10px;height:10px}.v-popper__popper--arrow-overflow .v-popper__arrow-container,.v-popper__popper--no-positioning .v-popper__arrow-container{display:none}.v-popper__arrow-inner,.v-popper__arrow-outer{border-style:solid;position:absolute;top:0;left:0;width:0;height:0}.v-popper__arrow-inner{visibility:hidden;border-width:7px}.v-popper__arrow-outer{border-width:6px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner{left:-2px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{left:-1px}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-outer{border-bottom-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=top] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-container{top:0}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{border-top-width:0;border-left-color:transparent!important;border-right-color:transparent!important;border-top-color:transparent!important}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-inner{top:-4px}.v-popper__popper[data-popper-placement^=bottom] .v-popper__arrow-outer{top:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{top:-2px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{top:-1px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{border-left-width:0;border-left-color:transparent!important;border-top-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-inner{left:-4px}.v-popper__popper[data-popper-placement^=right] .v-popper__arrow-outer{left:-6px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-container{right:-10px}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner,.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-outer{border-right-width:0;border-top-color:transparent!important;border-right-color:transparent!important;border-bottom-color:transparent!important}.v-popper__popper[data-popper-placement^=left] .v-popper__arrow-inner{left:-2px}.v-popper--theme-dropdown .v-popper__inner{background:#fff;color:#000;border-radius:6px;border:1px solid #ddd;box-shadow:0 6px 30px #0000001a}.v-popper--theme-dropdown .v-popper__arrow-inner{visibility:visible;border-color:#fff}.v-popper--theme-dropdown .v-popper__arrow-outer{border-color:#ddd}.v-popper--theme-tooltip .v-popper__inner{background:rgba(0,0,0,.8);color:#fff;border-radius:6px;padding:7px 12px 6px}.v-popper--theme-tooltip .v-popper__arrow-outer{border-color:#000c}/*! @docsearch/css 3.5.1 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::-moz-placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:rgba(0,0,0,.2);transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.algolia-search-box{position:absolute;top:0;right:0;bottom:0;left:0;opacity:.75}.DocSearch-Button{margin:0;border-radius:0;width:100%;opacity:0;border:0}.DocSearch-Button:hover{box-shadow:none}.DocSearch-Search-Icon,.DocSearch-Button-Placeholder,.DocSearch-Button-Key{display:none}@media (min-width: 768px){.DocSearch--active{overflow-y:scroll!important}}#docsearch-input:focus{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.DocSearch.DocSearch-Container{--tw-backdrop-blur: blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.DocSearch{--docsearch-primary-color: #65adf1;--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-text-color: #111827;--docsearch-muted-color: #9ca3af;--docsearch-container-background: rgba(15, 23, 42, .2)}[id]{scroll-margin-top:88px;outline:2px solid transparent;outline-offset:2px}.h-sidebar{height:calc(100vh - 72px)}.code-tab-container [class*=language-]{margin:-1px 0 0;border-top-left-radius:0;border-top-right-radius:0}.code-tab-container [class*=language-] pre{margin:0}.device~.device{margin-top:1.5rem}@media (min-width: 640px){.device~.device{margin-top:0}}.device img{max-height:400px;-o-object-fit:contain;object-fit:contain}@media (min-width: 1536px){.device img{max-height:600px}}.step-list{counter-reset:StepList}.step-list li{position:relative}.step-list li::marker{color:transparent}.step-list li:before{counter-increment:StepList;content:counter(StepList) " ";position:absolute;left:-1.5rem;top:.125rem;display:flex;height:1.5rem;width:1.5rem;align-items:center;justify-content:center;border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity));font-size:.75rem;line-height:1rem;font-weight:700;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark .step-list li:before{--tw-bg-opacity: 1;background-color:rgb(153 27 27 / var(--tw-bg-opacity))} \ No newline at end of file diff --git a/.vitepress/theme/vitepress-theme.mjs b/.vitepress/theme/vitepress-theme.mjs index 030432a9..75475db7 100644 --- a/.vitepress/theme/vitepress-theme.mjs +++ b/.vitepress/theme/vitepress-theme.mjs @@ -9815,4 +9815,4 @@ const W4 = { key: 0 }, K4 = { key: 0 }, G4 = ["id"], J4 = { class: "columns-1 ga }); export { ug as default -}; +}; \ No newline at end of file diff --git a/content/guide/core/application.md b/content/guide/core/application.md new file mode 100644 index 00000000..c7d982c0 --- /dev/null +++ b/content/guide/core/application.md @@ -0,0 +1,404 @@ +--- +title: Application +description: Handling application global state +--- + +The Application class provides the wrapper around [android.app.Application](https://developer.android.com/reference/android/app/Application) for Android and [UIApplication](https://developer.apple.com/documentation/uikit/uiapplication?language=objc) for iOS. With this class you handle the app's lifecycle events, send Broadcasts on Android or add a Notification observer on IOS, etc. + +## Use Application class + +### Register a broadcast receiver + +To register a broadcast receiver, you follow these 3 steps: +1. Import the `Application` class from `@nativescript/core`. +```ts +import { Application, isAndroid } from '@nativescript/core'; +``` +2. Get the wrapper object for [android.app.Application](https://developer.android.com/reference/android/app/Application) instance. +Use the `android` property to get the wrapper around [android.app.Application](https://developer.android.com/reference/android/app/Application). + +```ts +const androidApp: AndroidApplication = Application.android +``` + +3. Call the [registerBroadcastReceiver](#registerbroadcastreceiver) method. +Call the `registerBroadcastReceiver` method on `androidApp`. +```ts +androidApp.registerBroadcastReceiver() +``` + +For a complete example that shows how to register a broadcast receiver with a custom intent filter, visit the following link: + + +For system intent filters, see [Standard Broadcast Actions](https://developer.android.com/reference/android/content/Intent#standard-broadcast-actions). + +### Unregister a broadcast receiver + +To unregister a broadcast receiver, call the [unregisterBroadcastReceiver](#unregisterbroadcastreceiver) on the wrapper around an [android.app.Application](https://developer.android.com/reference/android/app/Application) passing it the intent filter for which to unregister the broacast receiver. The example below unregisters a broadcast receiver for the `android.content.Intent.ACTION_BATTERY_CHANGED` intent filter. + +```ts +import { Application, isAndroid } from '@nativescript/core'; +if(isAndroid){ +const androidApp: AndroidApplication = Application.android + +androidApp.unregisterBroadcastReceiver(android.content.Intent.ACTION_BATTERY_CHANGED); +} + +``` + +### Add a notification observer + +To add an iOS notification observer, follow the steps below: + +1. Import the `Application` class from `@nativescript/core`. + +```ts +import { Application, isIOS } from '@nativescript/core'; +``` + +2. Get the wrapper object for [UIApplication](https://developer.apple.com/documentation/uikit/uiapplication?language=objc) instance. +```ts +const iOSApp: iOSApplication = Application.ios +``` + +3. Call the `addNotificationObserver` method. +Call the `addNotificationObserver` passing it the name of the notification([NSNotificationName](https://developer.apple.com/documentation/foundation/nsnotificationname)) you would like to observe as the first parameter and as a second parameter, a callback function to be called when that notification occurs. + +```ts +const observer: any = iOSApp.addNotificationObserver( + UIDeviceOrientationDidChangeNotification, + (notification: NSNotification) => { + //Handle the notification + } +) +``` + +Find the complete example [here](https://stackblitz.com/edit/nativescript-stackblitz-templates-khnhes?file=app%2Fapp.ts%3AL14) + +### Remove a notification observer + +To remove a notification observer, use the `removeNotificationObserver` method on a `Application.ios` reference the observer id, returned by the `addNotificationObserver` as the first argument and the name of the notification to stop observing. + +```ts +iOSApp.removeNotificationObserver(observer, UIDeviceBatteryStateDidChangeNotification) +``` + + +## Cross platform application events +This class allows you to listen to the following lifecycle events on both platforms. + +```ts +Application.on('orientationChanged', (args: ApplicationEventData) => { + // handle the event +}) +``` + +:::details More events + +- `livesync` +- `cssChanged` +- `launch` +- `displayed` +- `suspend` +- `resume` +- `exit` +- `lowMemory` +- `uncaughtError` +- `discardedError` +- `orientationChanged` +- `systemAppearanceChanged` +- `fontScaleChanged` + +::: +## getResources() +```ts +resources: any = Application.getResources() +``` +Gets application-level static resources. + +--- +## setResources() +```ts +Application.setResources(resources) +``` +Sets application-level static resources. + +--- +## setCssFileName() +```ts +Application.setCssFileName(filePath) +``` +Sets css file name for the application. + +--- +## getCssFileName() +```ts +cssFileName: string = Application.getCssFileName() +``` +Gets css file name for the application. + +--- +## loadAppCss() +```ts +loadedCss: any = Applicatioin.loadAppCss() +``` +Loads immediately the app.css. By default the app.css file is loaded shortly after "loaded". For the Android snapshot the CSS can be parsed during the snapshot generation, as the CSS does not depend on runtime APIs, and loadAppCss will be called explicitly. + +--- +## addCss() +```ts +Application.addCss(cssText, attributeScoped) +``` +Adds new values to the application styles. +- `cssText` - A valid CSS styles to be add to the current application styles. +- _Optional_: `attributeScoped` - sets whether the styles are attribute scoped. Adding attribute scoped styles does not perform a full application styling refresh. + +--- + +## Android Reference + +### android + +```ts +androidApp: AndroidApplication = Application.android +``` + +The property gives you the `AndroidApplication` object, a Nativescript wrapper, around the native android application instance. + +--- + +### nativeApp + +```ts +nativeApp: android.app.Application = androidApp.nativeApp +// or +nativeApp: UIApplication = iOSApp.nativeApp +``` + +This is a native application reference. + +For Android, it is the [android.app.Application](http://developer.android.com/reference/android/app/Application.html) instance keeping track of the global application state. From this object you can get methods such as getFilesDir(), onLowMemory(),etc. + + +For iOS, it returns the reference to a [UIApplication](https://developer.apple.com/documentation/uikit/uiapplication?language=objc) instance for the application. + +--- + +### foregroundActivity + +```ts +foregroundActivity = androidApp.foregroundActivity +``` + +Gets the currently visible(topmost) [android Activity](http://developer.android.com/reference/android/app/Activity.html). + +--- + +### startActivity + +```ts +startActivity = androidApp.startActivity +``` + +Gets the main (start) Activity for the application. + +--- + +### paused + +```ts +isSuspended: boolean = androidApp.paused +``` + +Returns `true` if the main application activity is not running (suspended), otherwise false is returned. + +--- + +### backgrounded + +```ts +isInBackground: boolean = androidApp.backgrounded +``` + +Returns `true` if the main application activity is in background + +--- + +### registerBroadcastReceiver + +```ts +receiver = androidApp.registerBroadcastReceiver(intentFilter, onReceiveCallback) +``` + +Registers a [BroadcastReceiver](https://developer.android.com/reference/android/content/BroadcastReceiver) to be run in the main activity thread. The receiver will be called with any broadcast Intent that matches the intent filter. + +`onReceiveCallback`: a callback function that will be called each time a broadcast is received. + +--- + +### getRegisteredBroadcastReceiver + +```ts +androidApp.getRegisteredBroadcastReceiver(intentFilter) +``` + +Gets a registered BroadcastReceiver for the specified intent filter. + +--- + +### unregisterBroadcastReceiver + +```ts +androidApp.unregisterBroadcastReceiver(intentFilter) +``` + +Unregisters a previously registered BroadcastReceiver for the specified intent filter. + +--- + +### Android Activity lifecycles events + +To handle the application lifecycle events for Android, use `on` method of the + +```ts +androidApp.on('activityResumed', args => { + //handle the event here +}) +``` + +:::details More Android Activity lifecycles events + +- `activityCreated` +- `activityDestroyed` +- `activityStarted` +- `activityPaused` +- `activityStopped` +- `saveActivityState` +- `activityResult` +- `activityBackPressed` +- `activityNewIntent` +- `activityRequestPermissions` + +::: + +--- +## iOS Reference +### ios + +```ts +iOSApp = Application.ios +``` + +The property gives you the `iOSApplication` object, Nativescript wrapper, the around the native iOS application instance. + +--- + +### rootController + +```ts +rootController: UIViewController = iOSApp.rootController +``` + +The root view controller for the iOS application. + +--- + +### window + +This property gives the key [window](https://developer.apple.com/documentation/uikit/uiwindow), the container for your app views and one of its roles is to deliver touch events to the views. Views are the user interface items such as button, label or scrollview. + +--- + +### delegate(iOS lifecycles events) + +```js +const MyDelegate = (function (_super) { + __extends(MyDelegate, _super) + function MyDelegate() { + _super.apply(this, arguments) + } + MyDelegate.prototype.applicationDidFinishLaunchingWithOptions = function ( + application, + launchOptions + ) { + console.log('applicationWillFinishLaunchingWithOptions: ' + launchOptions) + return true + } + MyDelegate.prototype.applicationDidBecomeActive = function (application) { + console.log('applicationDidBecomeActive: ' + application) + } + MyDelegate.ObjCProtocols = [UIApplicationDelegate] + return MyDelegate +})(UIResponder) + +Application.ios.delegate = MyDelegate +``` + +```ts +@NativeClass() +class MyDelegate extends UIResponder implements UIApplicationDelegate { + public static ObjCProtocols = [UIApplicationDelegate] + + applicationDidFinishLaunchingWithOptions( + application: UIApplication, + launchOptions: NSDictionary + ): boolean { + console.log('applicationWillFinishLaunchingWithOptions: ' + launchOptions) + + return true + } + + applicationDidBecomeActive(application: UIApplication): void { + console.log('applicationDidBecomeActive: ' + application) + } +} +Application.ios.delegate = MyDelegate +``` + +The iOS system monitors the different states of your application and emits an event at each state. To handle these lifecycle events, you have to write a class that extends UIResponder and implements `UIApplicationDelegate` classes and set the delegate property to that class. You then overwrite the methods from `UIApplicationDelegate` to handle the events. + +For a complete list of the iOS lifecycle events, visit [UIApplicationDelegate](https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc). + +--- + +### orientation + +```ts +orientation = androidApp.orientation +// or +orientation = iOSApp.orientation +``` + +Gets or sets the orientation of the application.
Possible values: `portrait`\| `landscape`\| `unknown` + +--- + +### systemAppearance + +```ts +systemAppearance = androidApp.systemAppearance +// or +systemAppearance = iOSApp.systemAppearance +``` + +Returns whether the system appearance is `dark`, `light` or `null`(for iOS <= 11). + +--- + + + +:::details References + +## API References + +| Name | Type | +| ------------------------------------------------------------------------------------------------- | -------- | +| [@nativescript/core/application](https://docs.nativescript.org/api-reference/modules#application) | `Module` | + +## Native Component + +| Android | iOS | +| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | +| [android.app.Application](https://developer.android.com/reference/android/app/Application) | [UIApplication](https://developer.apple.com/documentation/uikit/uiapplication?language=objc) | + +::: \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 9459d2ed..4457bd24 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -21,6 +21,10 @@ export default [ { text: 'Development Workflow', items: [ + { + text: 'Running', + link: '/guide/running', + }, { text: 'Testing', link: '/guide/testing' @@ -160,6 +164,7 @@ export default [ { text: '@nativescript/core', items: [ + { text: 'Application', link: '/guide/core/application' }, { text: 'FPS Meter', link: '/guide/core/fps-meter' From 547030b1e1e856b821866ec76266f0223aa1c853 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 21:41:19 +0200 Subject: [PATCH 26/44] docs: Http (#45) Co-authored-by: Nathan Walker --- content/guide/core/http.md | 288 +++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 292 insertions(+) create mode 100644 content/guide/core/http.md diff --git a/content/guide/core/http.md b/content/guide/core/http.md new file mode 100644 index 00000000..1242bea7 --- /dev/null +++ b/content/guide/core/http.md @@ -0,0 +1,288 @@ +--- +title: Http +--- + +Http provides utility methods to make all sorts of http networking requests. + + +## Using Http + +### GET request with a response body as a string + +A simple way to make a GET request returning a response as a string is to use the [getString()](#getstring) method: + +```ts +Http.getString('https://httpbin.org/get').then( + (result: string) => { + // do something with the string response + }, + e => {} +) +``` + +### GET request with a response body as JSON + +Use the [getJSON()](#getjson) method for a GET request with a response as a JSON: + +```ts +Http.getJSON('https://httpbin.org/get').then( + (result) => { + console.log(result) + }, + e => {} +) +``` + +### GET request with a response body as a file + +Use the [getFile()](#getfile) method for a GET request with a response as a [File](): + +```ts +Http.getFile('https://d1lfyz5kwt8vu9.cloudfront.net/nativescript-logo-2021.png').then( + (resultFile: File )=> { + // The returned result will be File object + }, + e => {} +) +``` + +### GET request with a response body as an image + +Use the [getImage()](#getimage) method for a GET request with a response as an image([ImageSource]()): + +```ts +Http.getImage('https://httpbin.org/image/jpeg').then( + (res: ImageSource) => { + +}, + e => {} +) +``` + +:::tip Note +The above methods can also take, instead of the url string, an [HttpRequestOptions](#httprequestoptions-interface) object. Also, alternatively to the above methods, you can use the [request](#) method and call the appropriate(e.g `.toJSON()`, `.toString()`) method on the [HttpResponse](#httpresponse-interface) object's `content` to get the response type you want. +::: + +### Making a POST request +To make a POST request, use the [request()](#request) method: + +```ts + Http.request({ + url: 'https://httpbin.org/get', + method: "POST", + headers: { "Content-Type": "application/json" }, + content: JSON.stringify({ + username: "tony", + password: "pass" + }) + }).then((response) => { + const result = response.content?.toJSON(); +}, (e) => { + // error +}) +``` + +## API + +### getString() + +```ts +Http.getString(url: string): Promise +``` +Downloads the content from the specified URL as a string. + +--- +### getJSON() + +```ts +Http.getJSON(url: string): Promise +``` + +Downloads the content from the specified URL as a string and returns its JSON.parse representation. + +--- + +### getImage() +```ts +Http.getImage(url: string): Promise +``` +Downloads the content from the specified URL and attempts to decode it as an image. + +--- + +### getFile() +```ts +Http.getFile(url: string, destinationFilePath?: string): Promise +``` +Downloads the content from the specified URL and attempts to save it as file. `destinationFilePath` is an optional location of where you want to save the returned file. + +--- + +### request() +```ts +Http.request(options: HttpRequestOptions): Promise +``` + +Makes a generic http request using the provided options and returns a [HttpResponse](#httpresponse-interface) Object. + +--- + +### HttpRequestOptions interface + +The HttpRequestOptions interface has the following members: + +#### url +```ts +const requestOptions = { + url: "https://httpbin.org" +} +``` + +A string value representing the request url. + +--- + +#### method + +```ts +const requestOptions = { + url: "https://httpbin.org", + method: "POST" +} +``` +Gets or sets the request method. + +--- + +#### headers + +```ts +const requestOptions = { + + headers: { "header-name" : "header-value" }, +} +``` +_Optional_: gets or sets the request headers in JSON format. + +--- + +#### content + +```ts +const requestOptions = { + + content: formData, +} + +//or + +const requestOptions = { + + content: "some string", +} +``` +_Optional_: gets or sets the request body. + +--- + +#### timeout +```ts +const requestOptions = { + timeout : 22333 +} +``` + +_Optional_: gets or sets the request timeout in milliseconds. + +--- + +#### dontFollowRedirects + +```ts +const requestOptions = { + dontFollowRedirects : true +} +``` + +_Optional_: boolean value that sets wether to **not** follow server's redirected request. + +--- + + +### HttpResponse interface + +#### statusCode + +Gets the response status code. + +_Type_: `number` + +--- + +#### content + +Gets the response content. +_Type_: [HttpContent](#httpcontent-interface) + +--- + +#### headers + +Gets the response headers + +--- + +### HttpContent interface + +#### raw + +```ts +response.content?.raw +``` + +Gets the response body as raw data. + +--- + +#### toString() + +```ts +response.content?.toString() +``` + +Gets the response body as string. + +--- + +#### toJSON() + +```ts +response.content?.toJSON(encoding) +``` + +Gets the response body as JSON object. The `encoding` is optional of type `'UTF8' | 'GBK'`. + +--- + +#### toImage() + +```ts +response.content?.toImage() +.then((image: ImageSource)=>{ + +}).catch(error=>{ + +}) +``` +Gets the response body as [ImageSource](). + +--- + +#### toFile() + +```ts +const file: File = response.content?.toFile(destinationFilePath) +``` +Gets the response body as a file. `destinationFilePath` is an optional location of where you want to save the returned file. + + diff --git a/content/sidebar.ts b/content/sidebar.ts index 4457bd24..2c338467 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -165,6 +165,10 @@ export default [ text: '@nativescript/core', items: [ { text: 'Application', link: '/guide/core/application' }, + { + text: 'Http', + link: '/guide/core/http' + }, { text: 'FPS Meter', link: '/guide/core/fps-meter' From 3482b67ab1a15f3dcf1819f5b9a5c8fc2f1a16fb Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 21:54:18 +0200 Subject: [PATCH 27/44] docs: FileSystem (#42) Co-authored-by: Nathan Walker --- content/guide/core/file-system.md | 756 ++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 760 insertions(+) create mode 100644 content/guide/core/file-system.md diff --git a/content/guide/core/file-system.md b/content/guide/core/file-system.md new file mode 100644 index 00000000..ea359c27 --- /dev/null +++ b/content/guide/core/file-system.md @@ -0,0 +1,756 @@ +--- +title: FileSystem +description: Work with the device file system +--- + +File system handing with @nativescript/core provides easy-to-use APIs for working with files and folders in a device's file system like managing files, folders, paths, and separators, etc. + +## How to work with files and folders + +### Accessing the Documents folder + +To get the root Documents folder, call the `documents()` method on `knownFolders`: + +```ts +import { knownFolders } from '@nativescript/core' + +const documents = knownFolders.documents() +``` + +### Accessing the temporary(Caches) folder + +```ts +import { knownFolders } from '@nativescript/core' + +const cache = knownFolders.temp() +``` + +### Accessing the root folder for the app + +```ts +import { knownFolders } from '@nativescript/core' + +const appRootFolder = knownFolders.currentApp() +``` + +### Creating a folder + +To create a folder, call the `getFolder()` method on an instance of `Folder` (any of the root folders above or one you create) and pass it the name of the folder. + +```ts +folder.getFolder("folder-name") +``` + +You can also use the [fromPath](#frompath) static method of the Folder class. + +```ts +const folderPath = path.join(knownFolders.documents().path, 'music') +const folder: Folder = Folder.fromPath(folderPath) +``` + +### Renaming a folder + +To rename a folder, use the `rename` or `renameSync` method: + +```ts +folder + .rename(newName) + .then(res => { + // Folder Successfully renamed. + + }) + .catch(err => { + //Folder couldn't be renamed + console.error(err) + }) +``` +### Check if a folder exists + +```ts +const folderExists: boolean = Folder.exists('folder-path') +``` + +### Accessing a folder's content + +To get a folder's files and folders, use the `getEntitiesSync` or `getEntities` method: + + +```ts +folder + .getEntities() + .then((entities: FileSystemEntity[]) => { + // do something + }) + .catch(err => { + console.log(err) + }) + + ``` + +### Deleting the contents of a Folder + +To delete all the content of a Folder, use the [clear](#clear) or [clearSync](#clearsync) method of a Folder instance: + +```ts +folder.clear().then((result)=>{ +// successful delete +}).catch((rerror)=>{ + +}) + +// OR + +folder.clearSync((error)=>{ + +}) +``` + +### Deleting a folder + +To delete a folder, use the `remove` or `removeSync` method: + +```ts +folder.remove().then((value: boolean)=>{ + +console.log(value) + +}).catch((error)=>{ + +console.error(error.message) +}) +``` + +### Creating, writing to and reading from a text file + +- To create a file, call the `getFile()` method on an instance of the `Folder` class and pass it the file name with the extension. + +```ts +folder.getFile("my-text.txt") +//or +const filePath = path.join(folder.path, 'my-text.txt') +const file = File.fromPath(filePath) +``` + +- To save a text to a file, use the `writeText` or `writeTextSync` method: + +```ts +file + .writeText('Some text') + .then(result => { + // Succeeded writing to the file. + + }) + .catch(error => { + console.log(error) + }) + +``` +- To extract data from a text file, you can use the readText or readTextSync method of the file instance. Here's an example of how to use it: +```ts + file.readText().then(res => { + // Succeeded read from file. + + }) + .catch(error => { + + }) +``` +### Check if a file exists + +To check if a file exists, you can use [exists](#exists) with the file path: + +```ts +const exists = File.exists(filePath) +``` + +### Renaming a file + +To rename a file, use the `rename` or `renameSync` method: + +```ts +file.rename("new-name.ext").then((value)=>{ + +}).catch(err=>{ + +}) +``` + +### Saving a binary data to a file + +To save binary data to a file, use either the `write` or `writeSync` method: + +```ts +file + .write(binary data) + .then(result => { + // Succeeded writing to the file. + + }) + .catch(error => { + console.log(error) + }) + +``` + + +### Reading a binary data from a file + +To read binary data, use the `read` or `readSync` method of the File instance. For example, you can save an image on the device and retrive it as follows: + +```ts +async readAnImage() { + + const folder = knownFolders.documents() + const filePath = path.join(folder.path, 'cat.png') + const imageFile = File.fromPath(filePath) + + try { + + const imageSource = await ImageSource.fromFile('~/assets/images/download.jpeg') + const saved: boolean = imageSource.saveToFile(filePath, "png") + + if (saved) { + // READ BINARY DATA + const imageBinary = await imageFile.read() + const img = await ImageSource.fromData(imageBinary) + } + + } catch (err) { + + } +} +``` + +### Deleting a file + +To remove a file, use the [remove](#remove) or [removeSync](#removesync) method of the File instance: + +```ts +file + .remove() + .then((res: boolean) => { + // Success removing the file. + }) + .catch(err => { + }) + ``` + + ### Normalizing a path + + To normalize a path use the `normalize` or create the path with the `join` method from the [path](#path-operations) object: + +```ts +const testPath = '///test.txt' +let documentsFolder = knownFolders.documents() +path.normalize(documentsFolder.path + testPath) +``` + +## FileSystem API + +### documents() + +```ts +const folder: Folder = knownFolders.documents() +``` + +Gets the Documents folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. + +--- + +### externalDocuments() + +```ts +const folder: Folder = knownFolders.externalDocuments() +``` + +Gets the Documents folder available for the current application from an external storage source. This folder has private access, with availability limited to the application, meaning it is not accessible to outside users, bots or external apps. + +- On Android, for such read or write access, the flags `READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE` permissions need to be set to true. This can be done by adding the following XML code to the `AndroidManifest.xml` file: + +```xml + + +``` + +- There is no external storage on iOS, it is the same as [documents()](#documents). + +--- + +### temp() + +```ts +const folder: Folder = knownFolders.temp() +``` +Gets the Temporary (Caches) folder available for the current application. This Folder is private for the application and not accessible from Users/External apps. + +--- + +### currentApp() + +```ts +const folder: Folder = knownFolders.currentApp() +``` + +Gets the root folder for the current application. + +--- + +### iOS known folders + +The following methods allow to access to iOS known folders. + +#### library() +```ts +const folder: Folder = knownFolders.ios.library() +``` +Gets the NSLibraryDirectory. + +--- + +#### developer() +```ts +const folder: Folder = knownFolders.ios.developer() +``` +Gets the NSDeveloperDirectory. + +--- + +#### desktop() +```ts +const folder: Folder = knownFolders.ios.desktop() +``` +Gets the NSDesktopDirectory. + +--- + +#### downloads() +```ts +const folder: Folder = knownFolders.ios.downloads() +``` +Gets the NSDownloadsDirectory. + +--- + +#### movies() +```ts +const folder: Folder = knownFolders.ios.movies() +``` +Gets the NSMoviesDirectory. + +--- + +#### music() +```ts +const folder: Folder = knownFolders.ios.music() +``` +Gets the NSMusicDirectory. + +--- + +#### pictures() +```ts +const folder: Folder = knownFolders.ios.pictures() +``` +Gets the NSPicturesDirectory. + +--- + +#### sharedPublic() +```ts +const folder: Folder = knownFolders.ios.sharedPublic() +``` +Gets the NSSharedPublicDirectory. + +--- + +### fromPath() + +```ts + +const file: File = File.fromPath(path) + +``` + +or + +```ts +const folder: Folder = Folder.fromPath(path) +``` + +Gets or creates a Folder or File entity at the specified path. + +--- + +### getFolder +```ts +const folder: Folder = folder.getFolder(name) +``` + +Gets or creates a Folder entity with the specified `name` within a Folder. + +--- + + +### exists + +```ts +const folderExists: boolean = Folder.exists(path) +``` + +or + +```ts +const file: boolean = File.exists(path) +``` + +Checks whether a Folder or File with the specified path already exists. + +--- + +### isKnown + +```ts +const isItAKnownFolder: boolean = folder.isKnown +``` +Determines whether this instance is a known folder (accessed through the `knownFolders` object). + +--- +Both the File and Folder classes extend the FileSystemEntity which has the following API: + +### lastModified + +```ts +const lastModified: Date = entity.lastModified +``` + +Gets the Date object specifying the last time this entity was modified. + +--- + +### name + +```ts +const name: string = entity.name +``` + +Gets the name of the entity. + +--- + +### path + +```ts +const path: string = entity.path +``` + +Gets the fully-qualified path (including the extension for a File) of the entity. + +--- + +### parent + +```ts +const parent: Folder = entity.parent +``` +Gets the Folder object representing the parent of this entity. +Will be null for a root folder like Documents or Temporary. +This property is readonly. + +--- + +### remove() + +```ts +const result = await entity.remove() +``` + +Asynchronously removes (deletes) the current Entity from the file system. + +--- + +### removeSync() + +```ts +entity.removeSync(onError?: (error: any) => any): void +``` + +Removes (deletes) the current Entity from the file system. + +--- + +### rename() + +```ts +entity.rename(newName: string): Promise +``` + +Asynchronously renames the current entity using the specified name. + +--- + +### renameSync() + +```ts +entity.renameSync(newName: string, onError?: (error: any) => any) +``` + +Renames the current entity synchronously, using the specified name. + +--- + +### getEntities() + +```ts +folder.getEntities(): Promise> +``` +Asynchronously gets all the top-level entities residing within a folder. + +--- + +### getEntitiesSync() + +```ts +folder.getEntitiesSync(onError?: (error: any) => any): Array +``` + +Gets all the top-level entities residing within this folder synchronously. `onError` is a optional function to be called if some error occurs. + +--- + +### eachEntity() + +```ts +folder.eachEntity(onEntity: (entity: FileSystemEntity) => boolean) +``` + +Enumerates all the top-level FileSystem entities within a folder. `onEntity` is a callback that receives the current entity. + +--- +### getFile() +```ts +folder.getFile(name: string): File +``` +Gets or creates a File entity with the specified name within this Folder + +--- + +### extension + +```ts +const fileExt: string = file.extension +``` + +Gets the extension of the file. + +--- + +### size + +```ts +const fileSize: number = file.size +``` + +Gets the extension of the file. + +--- + +### isLocked + +```ts +const isFileLocked: boolean = file.isLocked +``` + +Gets a boolean value indicating whether the file is currently locked, meaning a background operation associated with this file is running. + +--- + +### readText() + +```ts +const text = await file.readText(encoding) +``` + +Asynchronously reads the content of the file as a string using an optional encoding value. If you do not pass any encoding, `UTF-8` is used. + +--- + +### readTextSync() + +```ts +file.readTextSync(onError?: (error: any) => any, encoding?: string): string +``` + +Reads the content of the file as a string synchronously, using the specified encoding (defaults to UTF-8). `onError` is a function to be called if some IO-error occurs. + +--- + + +### read + +```ts +const fileContent = await file.read() +``` + +Reads the binary content of the file asynchronously. + +--- + +### readSync() +```ts +const fileContent = file.readSync(onError) +``` +Reads the binary content of the file synchronously. `onError` is a function to be called if some IO-error occurs. + +| Parameter | Type | Description | +| --- | --- | --- | +| `onError?` | `(error: any) => any` | An optional function to be called if some IO-error occurs. | + +--- + +### writeText() + +```ts +const result = await file.writeText(content, encoding) +``` +Asynchronously writes the content of the file as a string using the specified encoding (defaults to UTF-8). + +| Parameter | Type | Description | +| --- | --- | --- | +| `content` | `string` | The content to write. | +| `encoding?` | `string` | An optional encoding value. If you do not pass any encoding, `UTF-8` is used. | + +--- + +### writeTextSync() + +```ts +file.writeTextSync(content, onError, encoding): void +``` + +Synchronously writes the content of the file as a string using the specified encoding (defaults to UTF-8). + +| Parameter | Type | Description | +| --- | --- | --- | +| `content` | `string` | The content to write. | +| `onError?` | `(error: any) => any` | An optional function to be called if some IO-error occurs. | +| `encoding?` | `string` | An optional encoding value. If you do not pass any encoding, `UTF-8` is used. | + +--- + +### write() + +```ts +await file.write(content) +``` + +Writes the provided binary content to the file asynchronously. + +| Parameter | Type | Description | +| --- | --- | --- | +| `content` | `any` | The content to write. | + +--- +### writeSync() +```ts +file.writeSync(content: any, onError?: (error: any) => any): void +``` + +Writes the provided binary content to the file synchronously. + +| Parameter | Type | Description | +| --- | --- | --- | +| `content` | `any` | The content to write. | +| `onError?` | `(error: any) => any` | An optional function to be called if some IO-error occurs. | + +--- + +### contains() + +```ts +const containsEntity: boolean = folder.contains(name) +``` +Checks whether this Folder contains an Entity with the specified name. + +| Parameter | Type | Description | +| --- | --- | --- | +| `name` | `string` | The name of the entity to check for. | + +--- + +### clear() + +```ts +const result = await folder.clear() +``` +Asynchronously deletes all the files and folders (recursively), contained within the Folder. + +--- + +### clearSync() + +```ts +folder.clearSync(onError) +``` +Synchronously deletes all the files and folders (recursively), contained within the Folder. + +| Parameter | Type | Description | +| --- | --- | --- | +| `onError?` | `(error: any) => any` | An optional function to be called if some IO-error occurs. | + +--- + +### path operations + +#### normalize + +```ts +const normalizedPath: string = path.normalize(path) +``` + +Normalizes a path, taking care of occurrences like `..` and `//`. + +| Parameter | Type | Description | +| --- | --- | --- | +| `path` | `string` | The path to normalize. | + +--- + +#### join() + +```ts +const joinedPath: string = path.join(...paths) +``` + +Joins all the provided string components, forming a valid and normalized path. + +| Parameter | Type | Description | +| --- | --- | --- | +| `paths` | `string[]` | The components of the path to join. | + +--- + +#### separator + +```ts +pathSeparator: string = path.separator +``` + +Gets the string used to separate file paths. + +--- + + +## API References + +- [File](https://docs.nativescript.org/api-reference/classes/file) class +- [Folder](https://docs.nativescript.org/api-reference/classes/folder) class +- [FileSystemEntity](https://docs.nativescript.org/api-reference/classes/folder) class +- [knownFolders](https://docs.nativescript.org/api-reference/modules/knownfolders) module +- [path](https://docs.nativescript.org/api-reference/modules/path) module + +## Native Component + +- Android: [java.io.File](https://developer.android.com/reference/java/io/File) + +- iOS: [NSFileManager](https://developer.apple.com/documentation/foundation/nsfilemanager) + diff --git a/content/sidebar.ts b/content/sidebar.ts index 2c338467..b4b19fa1 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -173,6 +173,10 @@ export default [ text: 'FPS Meter', link: '/guide/core/fps-meter' }, + { + text: 'FileSystem', + link: '/guide/core/file-system' + }, { text: 'Observable', link: '/guide/core/observable' From 4d837ef52389558a8e9af421460b301d2889eed9 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:04:55 +0200 Subject: [PATCH 28/44] docs: Core Color class (#41) Co-authored-by: Nathan Walker Co-authored-by: Igor Randjelovic Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/core/color.md | 392 ++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 396 insertions(+) create mode 100644 content/guide/core/color.md diff --git a/content/guide/core/color.md b/content/guide/core/color.md new file mode 100644 index 00000000..561daecb --- /dev/null +++ b/content/guide/core/color.md @@ -0,0 +1,392 @@ +--- +title: Color +description: Create a color object with any color representation and use it in your NativeScript apps. +--- + +The `Color` class enables the creation of a color object: + - using color components (alpha, red, green, blue) ranging from 0 to 255, + - using various color representations like ARGB, color names, hex values, and more. + +The `ios` and `android` properties of the `Color` class instance return the native platform instance of [UIColor](https://developer.apple.com/documentation/uikit/uicolor) on iOS and [Color](https://developer.android.com/reference/android/graphics/Color) on Android. + +## How to use the Color class + + +### Create a color object from a hex value + +```ts +const color = new Color('#FF00CC'); +const colorShortHex = new Color('#F0C'); +``` + +### Create a color object from an alpha value + +```ts +const colorARGB = new Color(100, 255, 100, 100); +``` + +## Color API + +The Color class offers the following properties and methods. + +### constructor + +Creates a color object. The Color class offers the following constructor overloads: + +```ts +const color = new Color(knownColor) +``` +Creates a Color instance from a known color name. +- `knownColor` : A color name string such as `'red'`, `'purple'`, `'orange'`. + +```ts +const color = new Color(hex) +``` +Creates a Color instance from a color hexidecimal code. + +- `hex`: A string of a hexidecimal color value such as `'#fff'` or `'#FF00CC'`. + +```ts +const color = new Color(argb) +``` +Creates a Color instance from a number representing a color with an alpha. +- `argb`: A number such as `4293377432` as, representing color. + +```ts +const color = new Color(alpha: number, red: number, green:number, blue: number, type?: 'rgb' | 'hsl' | 'hsv') +``` +--- + +### a + +```ts +colorAlpha: number = color.a +``` + +Gets the Alpha component of the color. This is a `read-only` property. + +--- + +### r + +```ts +colorRed: number = color.r +``` + +Gets the Red component of the color. This is a `read-only` property. + +--- + +### g + +```ts +colorGreen: number = color.g +``` + +Gets the Green component of the color. This is a `read-only` property. + +--- + +### b + +```ts +colorBlue: number = color.b +``` + +Gets the Blue component of the color. This is a `read-only` property. + +--- + +### argb + +```ts +colorARGB : number = color.argb +``` + +Gets the Argb Number representation of this color where each 8 bits represent a single color component. This is a `read-only` property. + +--- + +### hex + +```ts +colorHex: string = color.hex +``` + +Gets the Hexadecimal string representation of the color. + +--- + +### name + +```ts +colorName: string =color.name +``` + +Gets the known name of this instance. Defined only if it has been constructed from a known color name - e.g. "red". + +--- + +### android + +```ts +androidColor = color.android +``` + +Gets the android-specific integer value representation. Same as the `ARGB` one. + +--- + +### ios + +```ts +iOSColor: UIColor = color.ios +``` + +Gets the iOS-specific [UIColor](https://developer.apple.com/documentation/uikit/uicolor) value representation. + +--- + +### Color.equals() + +```ts +areColorsEqual: boolean = Color.equals(value1: Color, value2: Color) +``` + +A static Color class method that compares two `Color` instances and returns `true` if they are the same or `false` otherwise. + +--- + +### Color.isValid() + +```ts +isValidColorValue: boolean = Color.isValid(value) +``` + +A static Color class method that validates if a value can be converted to a color. + +--- + +### Color.fromIosColor() + +```ts +colorFromIosColor: Color = Color.fromIosColor(value) +``` + +Creates a Color instance from iOS-specific UIColor value representation. `value` is of type [UIColor](https://developer.apple.com/documentation/uikit/uicolor). + +--- + +### Color.mix() + +```ts +colorMix: Color = Color.mix(color1: Color, color2: Color, amount: number) +``` + +A static method that creates a Color instance from mixture of two colors. + +--- + +### Color.fromHSL() + +```ts +colorNew: Color = Color.fromHSL(a, h, s, l) +``` + +A static method that returns a new Color from HSL. + +--- + +### Color.fromHSV() + +```ts +colorNew: Color = Color.fromHSV(a, h, s, v) +``` + +A static method that returns a new Color from HSV. + +--- + +### equals() + +```ts +color.equals(value) +``` + +A Color instance method that checks whether the color instance on which the method is called equals the Color instance passed to the method. + +--- + +### isDark() + +```ts +color.isDark() +``` + +A Color instance method that returns `true` if the color is dark or returns`false` otherwise. A color is dark when `brightenss < 128`. + +--- + +### isLight() + +```ts +color.isLight() +``` + +A Color instance method that returns `true` if the color is light or returns`false` otherwise.A color is light when `brightenss >= 128` + +--- + +### getBrightness() + +```ts +colorBrightness: number = color.getBrightness() +``` + +Returns the color's [brightness](http://www.w3.org/TR/AERT#color-contrast) value. + +--- + +### getLuminance + +```ts +colorLuminance: number = color.getLuminance() +``` + +Returns the color's [luminance](http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef) value. + +--- + +### setAlpha() + +```ts +colorWithAlpha: Color = color.setAlpha(a) +``` +Adds the specified alpha to the color instance on which the method is called and returns the result as a new Color instance. + +`a` is a value between `0` and `255`. + +--- + +### toHsl() + +```ts +colorHsl: { h: number; s: number; l: number; a: number } = color.toHsl() +``` + +Returns the hsl(`{ h: number; s: number; l: number; a: number }`) representation of the color. + +--- + +### toHslString() + +```ts +colorHslString: string = color.toHslString() +``` + +Returns the [CSS hsl](https://www.w3schools.com/Css/css_colors_hsl.asp) representation of the color. + +--- + +### toHsv() + +```ts +colorHsv: { h: number; s: number; v: number; a: number } = color.toHsv() +``` + +Returns the hsv(`{ h: number; s: number; v: number; a: number }`) representation of the color + +--- + +### toHsvString() + +```ts +colorHsvString: string = color.toHsvString() +``` + +Returns the [CSS rgb](https://www.w3schools.com/Css/css_colors_rgb.asp) representation of the color. + +--- + +### desaturate() + +```ts +colorDesaturated: Color = color.desaturate(amount) +``` + +Desaturates the color by the specified amount. `amount` is a number between `0` and `100` inclusive. Providing `100` is the same as calling [greyscale](#greyscale). + +--- + +### saturate() + +```ts +colorSaturated: Color = color.saturate(amount) +``` + +Saturates the color by the specified amount.`amount` is a number between `0` and `100` inclusive. + +--- + +### greyscale() + +```ts +colorGrayscaled: Color = color.greyscale() +``` + +Completely desaturates a color into greyscale. Same as calling [desaturate(100)](#desaturate). + +--- + +### lighten() + +```ts +colorLightened: Color = color.lighten(amount) +``` + +Lightens the color by the specified amount.`amount` is a number between `0` and `100` inclusive. Providing `100` returns white. + +--- + +### brighten() + +```ts +colorBrightened: Color = color.brighten(amount) +``` + +Brightens the color by the specified amount.`amount` is a number between `0` and `100` inclusive. + +--- + +### darken() + +```ts +colorDarkened: Color = color.darken(amount: number) +``` + +Darkens the color by the specified amount.`amount` is a number between `0` and `100` inclusive. `100` returns black. + +--- + +### spin() + +```ts +colorSpinned: Color = color.spin(amount) +``` + +Spins the hue by the given amount, from -`360` to `360`. Calling with `0`, `360`, or -`360` does nothing since it sets the hue back to what it was before. + +--- +### complement() + +```ts +colorComplement: Color = color.complement() +``` + +Returns a Color instance that is the complement of the current color. + +--- + +## Native Component +- `Android`: [android.graphics.Color](https://developer.android.com/reference/android/graphics/Color) +- `iOS`: [UIColor](https://developer.apple.com/documentation/uikit/uicolor?language=objc) diff --git a/content/sidebar.ts b/content/sidebar.ts index b4b19fa1..94571d45 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -166,6 +166,10 @@ export default [ items: [ { text: 'Application', link: '/guide/core/application' }, { + text: 'Color', + link: '/guide/core/color' + }, + { text: 'Http', link: '/guide/core/http' }, From 2742db853a0d7100856f54f86113a150e4c18dd9 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:13:34 +0200 Subject: [PATCH 29/44] docs: iOS Runtime Types (#39) Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/ios-runtime-types.md | 190 +++++++++++++++++++++++++++++ content/sidebar.ts | 10 +- 2 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 content/guide/ios-runtime-types.md diff --git a/content/guide/ios-runtime-types.md b/content/guide/ios-runtime-types.md new file mode 100644 index 00000000..616ed6be --- /dev/null +++ b/content/guide/ios-runtime-types.md @@ -0,0 +1,190 @@ +--- +title: iOS Runtime Types +--- + +NativeScript provides the `interop` module for working with native C types, pointers, pointer arithmetic and memory. + + + + +## Using interop + +### Creating a pointer + +The `Pointer` type allows you to represent a void*. + +#### Using the constructor + +To create a new pointer with the given offset, use the constructor: + +```ts + const interop1 = new interop.Pointer(34) +``` + +#### Creating a pointer by adding an offset from an existing pointer + + ```ts +const interop2 = interop1.add(4) + ``` + +#### Creating a pointer by removing an offset to an existing pointer + + ```ts +const interop3 = interop1.subtract(3) + ``` + +## interop API + +### Pointer.toNumber() + +```ts + +pointerInstance.toNumber() + +``` + +Converts the value of a `Pointer` instance to a number. + +--- + +### adopt + +```ts + +interop.adopt(ptr: Pointer): AdoptedPointer + +``` + +Makes the specified pointer adopted. + +--- + +### free + +```ts + +interop.free(ptr: Pointer): void + +``` + +Releases the memory of the specified unadopted pointer. + +--- + +### sizeof + +```ts + +interop.sizeof(type: any): number + +``` +Returns the size of the provided type. The `type` can be: a class constructor (of Objective-C interface), an instance (wrapper of Objective-C interface), struct constructor, struct instance, reference, protocol, function (for c function), fundamental types + +--- + +### alloc + +```ts + +interop.alloc(size: number): AdoptedPointer + +``` + +Allocates memory `size` in bytes. + +--- + +### handleof + +```ts + +interop.handleof(instance: any): Pointer + +``` + +A JavaScript object can obtain a reference to the underlying native object. The instance can represent a class constructor (for an Objective-C interface), an instance (wrapper of an Objective-C interface), a struct instance, a reference, a protocol, a function (for C functions), or a block. + +--- + +### bufferFromData + +```ts + +interop.bufferFromData(data: NSData): ArrayBuffer + +``` + +Wraps an NSData instance in an ArrayBuffer. + +--- + + +### new Reference(value) + +Creates a new reference around a JavaScript `value`. The native representation of the type will be determined during the first time the Reference is used in an operation involving marshalling. + +--- + +### new Reference(type: Type\, data: Pointer) + +Creates a reference from the pointer with a given type. + +--- + +### new Reference(type: Type\, value: any) + +Creates a new reference around a `value` of a certain `type`. + +--- + +### new FunctionReference(func: T) + +Creates a function reference that can be marshalled as a native function pointer. The JavaScript reference must be allocated in memory as long as the native code needs the function. + +### interop types + + +- "void": Type\ +- bool: Type\ +- int8: Type\ +- uint8: Type\ +- int16: Type\ +- uint16: Type\ +- int32: Type\ +- uint32: Type\ +- int64: Type\ +- uint64: Type\ +- float: Type\ +- double: Type\ + +- UTF8CString: Type\\> +- unichar: Type\ + +- id: Type\ +- protocol: Type\ +- "class": Type\ +- selector: Type\ + +---- + +### new StructType() + +Create a new instance of the struct. + +--- + +### new StructType (obj: T) + +Create a new instance of the struct and initialize it from the fields of the provided object. + +--- + +### equals +```ts +someStructTypeIntastance.equals(left: T, right: T): boolean + +``` + +Checks two structs for equality. + +--- \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 94571d45..5652508e 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -226,10 +226,14 @@ export default [ text: 'Marshalling', items: [ { - text: 'Android Marshalling', - link: '/guide/android-marshalling', + text: 'iOS', + link: '/guide/ios-runtime-types', }, - ], + { + text: 'Android', + link: '/guide/android-marshalling', + } + ] }, { text: 'Property System', From a4e30de811d4b22ba1af2f4b9e0400be972d3053 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:19:07 +0200 Subject: [PATCH 30/44] docs: initial content for the Trace class (#37) Co-authored-by: Nathan Walker --- content/guide/core/tracing.md | 250 ++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 254 insertions(+) create mode 100644 content/guide/core/tracing.md diff --git a/content/guide/core/tracing.md b/content/guide/core/tracing.md new file mode 100644 index 00000000..d608de19 --- /dev/null +++ b/content/guide/core/tracing.md @@ -0,0 +1,250 @@ +--- +title: Tracing +--- + +The Trace class allows you to: + +- control the [categories of messages](#addcategories-and-setcategories) you log to the console +- [disable](#disable) multiple messages logging at once +- [handle errors](/guide/error-handling) + +## Using Trace + +The following 3 steps outline the basic usage of the Trace class. + +The first two step should be executed earlier in your applicaton, usually in the [app.ts](/project-structure/src/main-js-ts) file. + +1. Specify atleast one category to trace: + +```ts +Trace.setCategories("category")) +// or +Trace.setCategories(Trace.categories.concat("category1","category2")); +//or +Trace.addCategories("categ1, categ2") +``` +If you don't set any category or the category you pass to `Trace.write()` is not one of the registered, `Trace.write`'s message won't be logged. + +2. Enable tracing + +```ts +Trace.enable() +``` + +3. Instead of using console.log(), use Trace.write() to log your messages. Pass the message content, a category name, and optionally, a message type as arguments to Trace.write(). Make sure that the category you provide is one of the categories you have previously set using Trace.setCategories(). + +```ts +Trace.write('some message', 'category') +``` + +### Creating custom Trace writer +For an example, see `app/trace/trace-writer.ts` in the editor below. + + +#### Registering custom trace writer + +To utilize a custom TraceWriter instance, you need to register it with the Trace module by using the addWriter() method. This ensures that your custom writer is recognized and incorporated into the Trace functionality. + +```ts +Trace.clearWriters(); +Trace.addWriter(new TimestampConsoleWriter()) +``` + +### Error handling + +The Trace module allows to create a custom error handler, register(in the [app.ts](/project-structure/main-js-ts)) it with [setErrorHandler](#seterrorhandler), and pass the errors to it with [error](#error). + +```ts +const errorHandler: TraceErrorHandler = { + handlerError(err) { + // Option 1 (development) - throw the error + throw err + + // Option 2 (development) - logging the error via write method provided from trace module + Trace.write(err, 'unhandled-error', type.error) + + // (production) - custom functionality for error handling + reportToAnalytics(err) + } +} + +// Register errorHandler +Trace.setErrorHandler(errorHandler) + +``` + +## Trace API + +### addCategories and setCategories + +```ts +Trace.addCategories(categories: string) +``` + +Adds categories to existing categories the module will trace. +`categories` is a comma-separated list of categories. + +:::details Available categories + +- Trace.categories.VisualTreeEvents = `"VisualTreeEvents"` +- Trace.categories.Layout = `"Layout"` +- Trace.categories.Style = `"Style"` +- Trace.categories.ViewHierarchy = `"ViewHierarchy"` +- Trace.categories.NativeLifecycle = `"NativeLifecycle"` +- Trace.categories.Debug = `"Debug"` +- Trace.categories.Navigation = `"Navigation"` +- Trace.categories.Test = `"Test"` +- Trace.categories.Binding = `"Binding"` +- Trace.categories.BindingError = `"BindingError"` +- Trace.categories.Error = `"Error"` +- Trace.categories.Animation = `"Animation"` +- Trace.categories.Transition = `"Transition"` +- Trace.categories.Livesync = `"Livesync"` +- Trace.categories.ModuleNameResolver = `"ModuleNameResolver"` +- Trace.categories.All(All of the categories above). +- Trace.categories.concat(`"cat1"`, `"cat2"`, `"cat3"`, `"cat4"`). + +::: + +--- + +### addWriter + +```ts +Trace.addWriter(writer: TraceWriter) +``` + +Adds a TraceWriter instance to the trace module. + +--- + +### clearWriters + +```ts +Trace.clearWriters() +``` + +Clears all the writers from the trace module. Call this methods before adding a custom trace writer to avoid pre-registered writers from interfering with it. + +--- + +### disable + +If you've placed several `console.log`s in your code for debugging, you might want to comment them out before you send the app to production. That may mean tediously having to locate each `console.log`. By calling once `Trace.disable()`, you disable all the `Trace.write()`calls in your code. + +```ts +Trace.disable() + +``` + +Disables the tracing. + +--- + +### enable + +```ts +Trace.enable() + +``` + +Enables the tracing. + +--- + +### error + +```ts +Trace.error(error: string | Error) +``` + +Passes an error to the registered TraceErrorHandler. + +--- + +### getErrorHandler + +```ts +Trace.getErrorHandler() +``` + +Gets the registered `TraceErrorHandler`. + +--- + +### setErrorHandler + +```ts +Trace.setErrorHandler(handler: TraceErrorHandler) +``` + +Registers an error handler. + +--- + +### isCategorySet + +```ts +Trace.isCategorySet(category: string) +``` + +Check if a category is already set in trace module. + +--- + +### isEnabled() + +```ts +Trace.isEnabled() +``` + +Returns a `boolean` for whether tracing is enabled or not. + +--- + +### notifyEvent + +```ts +Trace.notifyEvent(object: Object, name: string, data?: any) +``` + +Notifies all the attached listeners for an event that has occurred in the sender object. + +--- + +### removeWriter + +```ts +Trace.removeWriter(writer: TraceWriter) +``` + +Removes a `TraceWriter` instance from the trace module. + +--- + +### write + +```ts +Trace.write(message: any, category: string, type?: number) +``` + +Writes a message using the available writers. + +:::details Message types + +- `log = 0` +- `info = 1` +- `warn = 2` +- `error = 3` + +::: + +--- + + +## API References + +| Name | Type | +| ------------------------------------------------------------------------------------- | -------- | +| [@nativescript/core/trace](https://docs.nativescript.org/api-reference/modules/trace) | `Module` | + diff --git a/content/sidebar.ts b/content/sidebar.ts index 5652508e..67d8bbe8 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -197,6 +197,10 @@ export default [ text: 'ImageCache', link: '/guide/core/image-cache', }, + { + text: 'Trace', + link: '/guide/core/tracing' + }, { text: 'XmlParser', link: '/guide/core/xml-parser' From 29910b72641f779b475a7327f5835556e2ccd204 Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:43:47 +0200 Subject: [PATCH 31/44] docs: initial content for the Connectity class (#36) Co-authored-by: wSedlacek Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> Co-authored-by: Igor Randjelovic Co-authored-by: Jason Cassidy <47318351+jcassidyav@users.noreply.github.com> --- content/guide/core/connectivity.md | 138 +++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 142 insertions(+) create mode 100644 content/guide/core/connectivity.md diff --git a/content/guide/core/connectivity.md b/content/guide/core/connectivity.md new file mode 100644 index 00000000..0da244a1 --- /dev/null +++ b/content/guide/core/connectivity.md @@ -0,0 +1,138 @@ +--- +title: Connectivity +description: Get the current device network connection type and monitor changes in the connection type. +--- + +`Connectivity` provides easy-to-use APIs consolidating the appropriate platform native APIs to interact with the network's connection type and availability. + + +## Using Connectivity + +To use Connectivity, import it from `@nativescript/core`. + +```ts +import { Connectivity } from '@nativescript/core' +``` + +### Getting the current connection type + +To check what type of network is currently connected use the [getConnectionType()](#getConnectionType) method. + +```ts +const connectionType: number = Connectivity.getConnectionType() + +if (connectionType) { // `Connectivity.connectionType.none`` is `0` so truthiness can be used to determine if the device is connected to any type of network + fetch('https://httpbin.org/get') + .then((response) => reponse.text()) + .then((result) => console.log(`Fetched ${result} with ${connectionType}`)); +} else { + console.log("Not connected to a network.") +} +``` + +Conditional behavior can easily be added for certain types of connections using the `Connectivity.connectionType` enum. + +```ts +if (connectionType === Connectivity.connectionType.wifi || connectionType === Connectivity.connectionType.ethernet) { + // Download large file +} else { + // Download mobile friendly file +} +``` + +### Monitor changes to the connection type + +Using the [startMonitoring()](#startMonitoring) method changes to the connection type can be observed. + +```ts +Connectivity.startMonitoring((change: number) => { + switch(change) { + case Connectivity.connectionType.wifi: + case Connectivity.connectionType.ethernet: + console.log("Connected to home network"); + break; + case Connectivity.connectionType.mobile: + case Connectivity.connectionType.bluetooth: + console.log("Connected to mobile network"); + break; + case Connectivity.connectionType.vpn: + console.log("Connected to vpn network"); + break; + default: + console.log("Not connected to any network"); + break; + } +}); +``` + +If you wish to halt the monitoring of connectivity changes, use the [stopMonitoring()](#stopMonitoring) function. + +```ts +Connectivity.stopMonitoring(); +``` + +## API + +### getConnectionType() + +```ts +Connectivity.getConnectionType(): number +``` + +This method retrieves the current connection type. It returns a number value representing one of the `connectionType` enumeration values. + +::: warning Note +For Android, the `android.permission.ACCESS_NETWORK_STATE` permission must be added to the `AndroidManifest.xml` file to use this method. +::: + +--- + +### startMonitoring() + +```ts +Connectivity.startMonitoring(connectionTypeChangedCallback: (newConnectionType: number) => void): void +``` + +This method initiates the monitoring of the network connection type. +The `connectionTypeChangedCallback` is a function that will be invoked when the network connection type changes. + +--- + +### stopMonitoring() + +```ts +Connectivity.stopMonitoring() +``` + +This method halts the monitoring of the network connection type. + +--- + +### connectionType + +```ts +Connectivity.connectionType: number +``` + +This enumeration defines the different possible connection types. + +:::details More connection types + +- `none = 0` +- `wifi = 1` +- `mobile = 2`, +- `ethernet = 3`, +- `bluetooth = 4`, +- `vpn = 5` + +::: + +--- + +## API Reference(s) +- [@nativescript/core/connectivity](https://docs.nativescript.org/api-reference/modules.html#connectivity) module +- [connectionType](https://docs.nativescript.org/api-reference/modules.html#connectivity) enum + +## Native Component +- `Android`: [CONNECTIVITY_SERVICE (android.content.Context)](https://developer.android.com/reference/android/content/Context) +- `iOS`: [SCNetworkReachability](https://developer.apple.com/documentation/systemconfiguration/scnetworkreachability-g7d) diff --git a/content/sidebar.ts b/content/sidebar.ts index 67d8bbe8..9d6244e6 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -169,6 +169,10 @@ export default [ text: 'Color', link: '/guide/core/color' }, + { + text: 'Connectivity', + link: '/guide/core/connectivity', + }, { text: 'Http', link: '/guide/core/http' From c675fe2a87488b5a82929709360e157338bfe9fa Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:49:43 +0200 Subject: [PATCH 32/44] docs: initial content for ApplicationSettings (#35) Co-authored-by: Nathan Walker --- content/guide/core/application-settings.md | 136 +++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 140 insertions(+) create mode 100644 content/guide/core/application-settings.md diff --git a/content/guide/core/application-settings.md b/content/guide/core/application-settings.md new file mode 100644 index 00000000..a6d4b59d --- /dev/null +++ b/content/guide/core/application-settings.md @@ -0,0 +1,136 @@ +--- +title: ApplicationSettings +description: Persist data locally on the device storage +--- + +`ApplicationSettings` allows you to store and retrieve data from the device local storage via getters and setters for storing and retrieving different data types. Use the appropriate setter to avoid errors. + +## How to use ApplicationSettings + + + +### Store a string value + +To store a string value, use the [setString](#setstring) method: + +```ts +ApplicationSettings.setString("username", "Wolfgang"); +``` +:::tip :green_circle: Tip +You can use this method with the `JSON.stringify()` (as shown in the `saveObjectAsString` method in the StackBlitz demo app at the link above) to store an object or an array as a string. Then, use `JSON.parse()` to convert the result of [getString()](#getstring) back to the object or array. +::: + +### Store a boolean value + +To store a boolean value, call the [setBoolean](#setboolean) method passing the key as the first argument and the value as second argument. + +```ts +ApplicationSettings.setBoolean("isTurnedOn", true); +``` + +### Store a numeric value + +To store a number, use the [setNumber()](#setnumber) method: + +```ts +ApplicationSettings.setNumber("locationX", 54.321); +``` + +## ApplicationSettings API + +### setString() + +```ts +ApplicationSettings.setString(key: string, value: string) +``` + +Stores a string value for the specified key. + +--- + +### getString() + +```ts +ApplicationSettings.getString(key: string, deafaultValue?: string) +``` + +Gets a value (if existing) for a key as a `String` object. A default value can be provided in case there is no existing value. + +--- + +### setNumber() + +```ts +ApplicationSettings.setNumber(key: string, value: number) +``` + +Sets a `Number` object for a key. + +--- + +### getNumber() + +```ts +ApplicationSettings.getNumber(key: string, deafaultValue?: number) +``` + +Gets a value (if existing) for a key as a `Number` object. A default value can be provided in case the value does not exist. + +--- + +### setBoolean() + +```ts +ApplicationSettings.setBoolean(key: string, value: boolean) +``` + +Sets a `boolean` for a key. + +--- + +### getBoolean() + +```ts +ApplicationSettings.getBoolean(key: string, deafaultValue?: boolean) +``` + +Gets a value (if existing) for a key as a `boolean`. A default value can be provided in case the value does not exist.value. + +--- + +### remove() + +```ts +ApplicationSettings.remove(key: string) +``` + +Removes the key and its value from the device storage. + +--- + +### clear() + +```ts +ApplicationSettings.clear() +``` + +Removes all values from the device storage. + +--- + +### getAllKeys() + +```ts +ApplicationSettings.getAllKeys(): Array +``` +Returns an array of all stored keys or an empty array if no keys exist in the device storage. + +--- + + +## API Reference(s) +- [ApplicationSettings](https://docs.nativescript.org/api-reference/modules#applicationsettings) module + +## Native Component +- `Android`: [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences) +- `iOS`: [NSUserDefaults](https://developer.apple.com/documentation/foundation/nsuserdefaults) diff --git a/content/sidebar.ts b/content/sidebar.ts index 9d6244e6..1ac2fa7a 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -165,6 +165,10 @@ export default [ text: '@nativescript/core', items: [ { text: 'Application', link: '/guide/core/application' }, + { + text: 'ApplicationSettings', + link: '/guide/core/application-settings', + }, { text: 'Color', link: '/guide/core/color' From 9614c824a78bdc2a99131837cac9a2fd21bb0f6e Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sun, 13 Aug 2023 23:09:54 +0200 Subject: [PATCH 33/44] docs: initial content for Metadata in NativeScript (#33) Co-authored-by: Nathan Walker --- content/guide/metadata.md | 133 ++++++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 ++ 2 files changed, 137 insertions(+) create mode 100644 content/guide/metadata.md diff --git a/content/guide/metadata.md b/content/guide/metadata.md new file mode 100644 index 00000000..c6695347 --- /dev/null +++ b/content/guide/metadata.md @@ -0,0 +1,133 @@ +--- +title: Metadata +--- + +NativeScript empowers JavaScript with platform iOS and Android APIs through **_metadata_**. The platform metadata contains all the necessary information about each of the supported native classes, interfaces, protocols, structures, enumerations, functions, variables, etc. and is generated at build time by examining the native libraries from the iOS/Android SDKs and any third-party libraries and frameworks that are used by the NativeScript app. + +::: warning Note +You cannot use APIs that are not present in the metadata. By default, if `--compileSdk` argument isn't provided while building, metadata will be built against the latest Android [Platform SDK](https://developer.android.com/about/versions/nougat/index.html) installed on the workstation. See [metadata limitations](#metadata-limitations). +::: + +### Metadata Filtering + +By default NativeScript includes all supported entities in the metadata. This allows app and plugin authors to freely call any native API from JavaScript. While it is benefitial during development, in some cases having metadata for all the APIs is undesirable and unnecessary. For example, there could be security implications involved (mentioning names of entities that shouldn't be known in the metadata binary files for example); performance could be degraded at runtime (due to larger metadata which has to be consulted when an unknown entry is encountered or at startup); or app size could increase due to unnecessary metadata which is never used. + +To give developers control over the generated metadata there's support for black and whitelisting symbols by their native name. + +### Metadata filtering rules in plugins + +Plugins can declare their list of APIs that are called from JavaScript using a file named `native-api-usage.json`, located in each of the platform directories (`platforms/android` or `platforms/ios`). Its format is similar to: + +```json +{ + "uses": ["java.util:List"] +} +``` + +### Metadata filtering rules in apps + +Applications have the final word of what filtering will be applied to metadata. They provide similar `native-api-usage.json` files, located in `App_Resources/Android` and `App_Resources/iOS`, having the following format: + +```json +{ + "whitelist-plugins-usages": true, + "whitelist": ["java.util:Base64*"], + "blacklist": ["java.util:Locale*"] +} +``` + +### Rules syntax + +Each list comprises pattern entries with the following characteristics: + +- Entries are of the form `[:pattern2]` +- On Android, **_pattern1_** is matched against Java package names, while the optional **_pattern2_** -- against classes, interfaces, enums. +- On iOS, **_pattern1_** is matched against Clang module/submodule names, while the optional **_pattern2_** -- against structs, global functions, enums, Objective-C interfaces, protocols, categories, constants, etc. +- Patterns support wildcards (**"\*"** - any number of characters and **"?"** - any single character). +- An unspecified or empty pattern is equivalent to being set to **"\*"** (matching everything) + +### Rules semantics + +After analyzing the filtering rules for a platform, {N} CLI builds final whitelist and blacklist files and outputs them in the native project to be used by the corresponding metadata generator. The blacklist is always equal to the one specified by the app. While the whitelist depends on the `whitelist-plugins-usages` flag: + +- If it is `true`, the final whitelist is a concatenation of all plugin usage lists with the app's whitelist +- Otherwise, it is equal to the app's whitelist + +These two lists unambiguously determine how filtering is performed: + +1. If the whitelist is empty, then everything is considered whitelisted by default +2. If it contains at least one rule, only entities matching a rule are considered whitelisted +3. All entities which are not whitelisted or match a rule in the blacklist are stripped from metadata +4. All other entities are included in the metadata + +### Examples + +Sample filtering specifications can be found in `@nativescript/core` plugin's repository: + +- [Android API usage list](https://github.com/NativeScript/NativeScript/blob/master/packages/core/platforms/android/native-api-usage.json) +- [iOS API usage list](https://github.com/NativeScript/NativeScript/blob/master/packages/core/platforms/ios/native-api-usage.json) + +### Troubleshooting + +Missing metadata entities could result in bugs at runtime. For example, if a native class has been accidentally filtered out, its constructor function will be `undefined` and this will lead to an exception when its attempted to be called. Figuring out what is the reason for something being `undefined` could be quite difficult because the reasons can vary. To check whether metadata filtering is to blame or not you should examine metadata generator's verbose logs after a successful build: + +- On iOS they are located in `platforms/ios/build/-/metadata-generation-stderr-.txt` (e.g. `platforms/ios/build/Debug-iphonesimulator/metadata-generation-stderr-x86_64.txt`); +- On Android they are located in `platforms/android/build-tools/buildMetadata.log` + +For each global symbol that is discovered by the generator, there should be a line providing information whether it was included in the metadata or not, and which rules or what exception caused this. Examples: + +- `verbose: Blacklisted kCFBuddhistCalendar from CoreFoundation.CFLocale (disabled by 'CoreFoundation*:*')` - when there are no whitelist rules a blacklisted symbol will show only the rule which disabled it +- `verbose: Blacklisted NSString from Foundation.NSString` - when there is at least one whitelist rule, some blacklisted symbols will not specify a rule. This means that the symbol didn't match any of the whitelist rules. +- `verbose: Blacklisted PHImageContentModeDefault from Photos.PhotosTypes (enabled by 'Photos.PhotosTypes:*', disabled by 'Photos.PhotosTypes:PHImageContentMode*')`, `verbose: Blacklisted String from java.lang (enabled by java.lang:*, disabled by java.lang:String)` - a blacklisted entry which matches both a whitelist rule and a blacklist rule will specify both. +- `verbose: Included NSObject from ObjectiveC.NSObject` - when there are no whitelist rules an included symbol won't specify a rule which caused it to be included +- `verbose: Included PHCollectionListType from Photos.PhotosTypes (enabled by 'Photos.PhotosTypes:*')`, `verbose: Included StrictMath from java.lang (enabled by java.lang:*)` - when a symbol is included because it matched a rule from the whitelist (and didn't match any from the blacklist) it will print that rule +- `verbose: Exception [Name: 'vfwprintf', JsName: 'vfwprintf', Module: 'Darwin.C.wchar', File: '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk/usr/include/wchar.h'] : Can't create type dependency. --> [Type Decayed] : Can't create type dependency. --> [Type Typedef] : VaList type is not supported.` - if a symbol is not included because it isn't supported for some reason it will be stated in the logged exception. In this case the symbol cannot be used from JavaScript because NativeScript doesn't support calling functions with variable argument lists. +- `verbose: Exception [Name: 'GLKVector3Make', JsName: 'GLKVector3Make', Module: 'GLKit.GLKVector3', File: '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk/System/Library/Frameworks/GLKit.framework/Headers/GLKVector3.h'] : Can't create type dependency. --> [Type Typedef] : Can't create type dependency. --> [Type Elaborated] : Can't create type dependency. --> [Type Record] : The record is an union.` - Another example of an unsupported symbol, this time the reason is that `union`s are unsupported + +## Android Metadata + +The NativeScript Metadata is the mapping between the JavaScript and the Android world. Besides a full list with all the available classes and methods, the metadata contains the [JNI](http://developer.android.com/training/articles/perf-jni.html) signature for each accessible Android method/field. It is pre-generated in a binary format, and embedded in the application package (apk), storing the minimal required information thus providing small size and highly efficient read access. The generation process uses bytecode reading to parse all publicly available types in the Android libraries supplied to the NativeScript project. The generator works as part of the Android build process, meaning that no user interaction is required for it to work. + +![Metadata](/assets/images/metadata_diagram.png) + +### Metadata API Level + +Only Android public APIs (**including those of any plugins added to the project**) present in the metadata will be accessible in JavaScript/TypeScript. That means, if an application is built with metadata for API level 23 (Android Marshmallow 6.0 – 6.0.1), the application user might have problems when running the application on an older device, for example with API levels 17 through 19 (Android KitKat 4.4 – 4.4.4). You can use SDK version conditionals to help these cases. + +Metadata is built automatically for each application. The metadata API level, or simply put, what API level the metadata is built for, is determined by the `--compileSdk` flag passed to the build. By default the NativeScript CLI automatically detects the highest Android API level installed on the developer's machine and passes it to the build implicitly. This `--compileSdk` flag can be supplied explicitly when starting a build: `ns run android --compileSdk=1`. + +#### Metadata Limitations + +Let's look at the Android [TextView](https://developer.android.com/reference/android/widget/TextView.html). +In API level 21 a method called `getLetterSpacing` was added meaning an application developer can use the `getLetterSpacing` method only on two conditions: + +- The built metadata is >= 21 +- The device that the application will be running on is >= 21 + +#### Possible Implications When Working With Android APIs + +##### Implication A: Building against lower API level. + +If an application is built with `--compileSdk` flag pointing to a lower Android API level, for example 19, the generated metadata will also be for API level 19. In that case making calls to API in Levels 21 and up will not be possible, because the metadata comprises of meta information about API level <= 19. + +This problem is easily solved by not specifying a `--compileSdk` flag and using the default behavior. + +##### Implication B: Building against higher API level. + +What happens when an application is built with a higher API level(e.g. 23), but runs on a device with a lower API level(e.g. 20)? +First the metadata is built for API level 23. If the JavaScript code calls a method introduced after API level 20 the Android runtime will try to call this method because it will recognize it in the metadata, but when the actual native call is made on the lower level device, an exception will be thrown because this method won't be present on the device. + +This problem is solved by detecting the API level at runtime and using the available API. + +Detecting the API Level in JavaScript: + +```js +if (android.os.Build.VERSION.SDK_INT >= 21) { + // your api level-specific code +} +``` + +## iOS Metadata + +This is our own custom data format for listing the iOS APIs we are aware of and can handle. It stores the minimal required information and provides a small size and highly efficient read access. iOS supports type introspection to some extent but along with the C APIs embedded all the way in the native APIs we had to store a lot of extra information. The Metadata is pre-generated at compile time from the SDK header files and embedded in the app package (ipa). + diff --git a/content/sidebar.ts b/content/sidebar.ts index 1ac2fa7a..822b9aa6 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -234,6 +234,10 @@ export default [ text: 'Code Sharing', link: '/guide/code-sharing', }, + { + text: 'Metadata', + link: '/guide/metadata' + }, { text: 'Marshalling', items: [ From cc5678103231aa66a326d2e8141305723277b55f Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:49:10 +0200 Subject: [PATCH 34/44] docs: initial content for iOS Marshalling (#31) Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/ios-marshalling.md | 353 +++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 357 insertions(+) create mode 100644 content/guide/ios-marshalling.md diff --git a/content/guide/ios-marshalling.md b/content/guide/ios-marshalling.md new file mode 100644 index 00000000..843ba28d --- /dev/null +++ b/content/guide/ios-marshalling.md @@ -0,0 +1,353 @@ +--- +title: iOS Marshalling +--- + +NativeScript for iOS handles the conversion between JavaScript and Objective-C data types. The following is a thorough but not exhaustive list of rules and exceptions NativeScript abides by when exposing Objective-C APIs in JavaScript. + +## Converting Objective-C Classes and Objects + +Classes can have instance methods, static methods, and properties. NativeScript exposes an Objective-C class and its members as a JavaScript constructor function with an associated prototype according to the [prototypal inheritance model](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain). This means that each static method on an Objective-C class becomes a function on its JavaScript constructor function, each instance method becomes a function on the JavaScript prototype, and each property becomes a property descriptor on the same prototype. Every JavaScript constructor function created to expose an Objective-C class is arranged in a prototype chain that mirrors the class hierarchy in Objective-C: if `NSMutableArray` extends `NSArray`, which in turn extends `NSObject` in Objective-C, then in JavaScript the prototype of the `NSObject` constructor function is the prototype of `NSArray`, which in turn is the prototype of `NSMutableArray`. + +To illustrate: + +```objc +@interface NSArray : NSObject + ++ (instancetype)arrayWithArray:(NSArray *)anArray; + +- (id)objectAtIndex:(NSUInteger)index; + +@property (readonly) NSUInteger count; + +@end +``` + +```js +var NSArray = { + __proto__: NSObject, + + arrayWithArray: function () { + [native code] + } +} + +NSArray.prototype = { + __proto__: NSObject.prototype, + + constructor: NSArray, + + objectAtIndex: function () { + [native code] + }, + + get count() { + [native code] + } +} +``` + +Instances of Objective-C classes exist in JavaScript as special "wrapper" exotic objects - they keep track of and reference native objects, as well as manage their memory. When a native API returns an Objective-C object, NativeScript constructs such a wrapper for it in case one doesn't already exist. Wrappers have a prototype just like regular JavaScript objects. This prototype is the same as the prototype of the JavaScript constructor function that exposes the class the native object is an instance of. In essence: + +```js +const tableViewController = new UITableViewController() // returns a wrapper around a UITableViewController instance +Object.getPrototypeOf(tableViewController) === UITableViewController.prototype // returns true +``` + +There is only one JavaScript wrapper around an Objective-C object, always. This means that Objective-C wrappers maintain JavaScript identity equality: + +```js +tableViewController.tableView === tableViewController.tableView +``` + +To call native APIs that expect Objective-C classes or objects just pass the JavaScript constructor function for the class, or the wrapper for the object. + +If an API is declared as accepting a `Class` in Objective-C, the argument in JavaScript is the constructor function: + +```objc +NSString *className = NSStringFromClass([NSArray class]); +``` + +```js +const className = NSStringFromClass(NSArray) +``` + +Conversely, if an API is declared as accepting an instance of a specific class such as `NSDate`, the argument is a wrapper around an object inheriting from that class. + +```objc +NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; +NSDate *date = [NSDate date]; +NSString *formattedDate = [formatter stringFromDate:date]; +``` + +```js +const formatter = new NSDateFormatter() +const date = NSDate.date() +const formattedDate = formatter.stringFromDate(date) +``` + +An API expecting the `id` data type in Objective-C means it will accept any Objective-C class or object in JavaScript. + +```objc +NSMutableArray *array = [[NSMutableArray alloc] init]; +Class buttonClass = [UIButton class]; +UIButton *button = [[buttonClass alloc] init]; +[array setObject:buttonClass atIndex:0]; +[array setObject:button atIndex:1]; +``` + +```js +const array = new NSMutableArray() +const buttonClass = UIButton +const button = new buttonClass() +array.setObjectAtIndex(buttonClass, 0) +array.setObjectAtIndex(button, 1) +``` + +## Calling Objective-C/Swift methods with multiple arguments + +Consider the following `NSMutableArray` selector: `replaceObjectsInRange:withObjectsFromArray:range:`. + +In JavaScript it is represented by: `replaceObjectsInRangeWithObjectsFromArrayRange(objectsToRange, sourceArray, sourceRange)` (argument names are arbitrary). + + +In Objective-C, when generating the function name for a method, it follows a convention of appending the names of the arguments defined by the Objective-C selector. The function name starts with a lowercase letter for the first argument and appends subsequent arguments with a capital letter. + +This naming convention helps to create unique and descriptive function names based on the arguments of the method. By incorporating the argument names into the function name, it provides clarity and readability when working with Objective-C APIs. + +It's important to note that this convention is specific to Objective-C and may differ from naming conventions in other programming languages. + +For an example of how to extend an Objective-C/Swift class, have a look at [Extending iOS classes in NativeScript](/guide/subclassing/extending-classes-and-conforming-protocols-ios#extending-ios-classes-in-nativescript) + +## Converting JavaScript array to CGFloat array + +The below code shows how to convert a JavaScript array to a `CGFloat` array to pass it to an Objective-C method expecting `CGFloat` as an argument: + +```js +const CGFloatArray = interop.sizeof(interop.types.id) == 4 ? Float32Array : Float64Array +const jsArray = [4.5, 0, 1e-5, -1242e10, -4.5, 34, -34, -1e-6] + +FloatArraySample.dumpFloats(CGFloatArray.from(jsArray), jsArray.length) +``` + +```objc +@interface FloatArraySample ++ (void)dumpFloats:(CGFloat*) arr withCount:(int)cnt; +@end + +@implementation TNSBaseInterface + ++ (void)dumpFloats:(CGFloat*) arr withCount:(int)cnt { + for(int i = 0; i < cnt; i++) { + NSLog(@"arr[%d] = %f", i, arr[i]); + } +} +@end +``` + +::: warning Note +Keep in mind that `CGFloat` is architecture dependent. On 32-bit devices, we need to use `Float32Array` and `Float64Array` -- on 64-bit ones. A straightforward way to verify the device/emulator architecture is to check the pointer size via `interop.sizeof(interop.types.id)`. The return value for the pointer size will be 4 bytes for 32-bit architectures and 8 bytes - for 64-bit ones. For further info, check out [CGFloat's documentation](https://developer.apple.com/documentation/coregraphics/cgfloat). +::: + +## Primitive Exceptions + +NativeScript considers instances of `NSNull`, `NSNumber`, `NSString` and `NSDate` to be "primitives". This means that instances of these classes won't be exposed in JavaScript via a wrapper exotic object, instead they will be converted to the equivalent JavaScript data type: `NSNull` becomes `null`, `NSNumber` becomes `number` or `boolean`, `NSString` becomes `string` and `NSDate` becomes `Date`. The exception to this are the methods on those classes declared as returning `instancetype` - init methods and factory methods. This means that a call to `NSString.stringWithString` whose return type in Objective-C is `instancetype` will return a wrapper around an `NSString` instance, rather than a JavaScript string. This applies for all methods on `NSNull`, `NSNumber`, `NSString` and `NSDate` returning `instancetype`. + +On the other hand, any API that expects a `NSNull`, `NSNumber`, `NSString` or `NSDate` instance in Objective-C can be called either with a wrapper object or a JavaScript value - `null`, `number` or `boolean`, `string` or `Date`, in JavaScript. The conversion is automatically handled by NativeScript. + +### Converting numeric types + +```ts +console.log(`pow(2.5, 3) = ${Math.pow(2.5, 3)}`); +``` +The iOS Runtime converts JavaScript number literals to native doubles and utilizes the native pow(double x, double y) function. The resulting native integer is automatically converted back to a JavaScript number and then passed as an argument to console.log() for output.. + +### Converting string +```ts +let button = UIButton.new(); +button.setTitleForState('Button title', UIControlState.Normal); +console.log(button.titleLabel.text); +``` + +`Button title` is converted to `NSString` and the returned `NSString` is converted to JavaScript `string`. + +### Converting boolean + +```ts +let str = NSString.stringWithString('YES'); +let isTrue = str.boolValue; +``` + +## Objective-C Protocols + +Protocols in Objective-C serve a similar purpose as interfaces in other programming languages. They define a blueprint or contract that specifies the members (methods, properties, etc.) that a class should implement. Protocols are exposed as empty objects in JavaScript. Protocols are usually only referenced when [subclassing](/guide/subclassing/extending-classes-and-conforming-protocols-ios) an Objective-C class or when checking whether an object or class conforms to a protocol. + + +```objc +BOOL isCopying = [NSArray conformsToProtocol:@protocol(NSCopying)]; +``` + +```js +const isCopying = NSArray.conformsToProtocol(NSCopying) +``` +To implement Objective-C/Swift protocols in NativeScript, hava look at [Conforming to Objective-C/Swift protocols in NativeScript](/guide/subclassing/extending-classes-and-conforming-protocols-ios#conforming-to-objective-c-swift-protocols-in-nativescript) + +## Objective-C Selectors + +In Objective-C, a `SEL` is a data type that represents a method name in an Objective-C class. NativeScript exposes this data type in the form of a JavaScript string. When working with APIs in Objective-C, if an API expects a selector value, the corresponding JavaScript projection in NativeScript will expect a string representing the method name. + +```objc +NSMutableString *aString = [[NSMutableString alloc] init]; +BOOL hasAppend = [aString respondsToSelector:@selector(appendString:)]; +``` + +```js +const aString = NSMutableString.alloc().init() +const hasAppend = aString.respondsToSelector('appendString:') +``` + +## Objective-C Blocks + +[Objective-C blocks](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html) are anonymous functions in Objective-C. They can be closures, just like JavaScript functions, and are often used as callbacks. NativeScript implicitly exposes an Objective-C block as a JavaScript function. Any API that accepts a block in Objective-C accepts a JavaScript function when called in JavaScript: + +```objc +NSURL *url = [NSURL URLWithString:@"http://example.com"]; +NSURLRequest *request = [NSURLRequest requestWithURL:url]; +[NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + NSLog(@"request complete"); +}]; +``` + +```js +const url = NSURL.URLWithString('http://example.com') +const request = NSURLRequest.requestWithURL(url) +NSURLConnection.sendAsynchronousRequestQueueCompletionHandler( + request, + null, + (response, data, connectionError) => { + console.log('request complete') + } +) +``` + +Blocks in Objective-C, especially those that function as closures, require proper retention and release to avoid memory leaks. However, in NativeScript, this memory management is handled automatically. When a block is exposed as a JavaScript function, it is released as soon as the function is garbage collected. Conversely, a JavaScript function that is implicitly converted to a block will not be garbage collected as long as the block is retained. + +## CoreFoundation Objects + +iOS contains both an Objective-C standard library (the Foundation framework) and a pure C standard library (Core Foundation). Core Foundation is modeled after Foundation to a great extent and implements a limited object model. Data types such as CFDictionaryRef and CFBundleRef are Core Foundation objects. Core Foundation objects are retained and released just like Objective-C objects, using the CFRetain and CFRelease functions. NativeScript implements automatic memory management for functions that are annotated as returning a retained Core Foundation object. For those that are not annotated, NativeScript returns an Unmanaged type that wraps the Core Foundation instance. This makes you partially responsible for keeping the instance alivee. You could either + +- Call takeRetainedValue() which would return managed reference to the wrapped instance, decrementing the reference count while doing so +- Call takeUnretainedValue() which would return managed reference to the wrapped instance _without_ decrementing the reference count + +### Toll-free Bridging + +Core Foundation has the concept of [Toll-free bridged types](https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html) - data types which can be used interchangeably with their Objective-C counterparts. When dealing with a toll-free bridged type NativeScript always treats it as its Objective-C counterpart. Core Foundation objects on the [toll-free bridged types list](https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/TP40010677-SW4) are exposed as if they were instances of the equivalent Objective-C class. This means that a `CFDictionaryRef` value in JavaScript has the same methods on its prototype as if it were a `NSDictionary` object. Unlike regular Core Foundation objects, toll-free bridged types are automatically memory managed by NativeScript, so there is no need to retain or release them using `CFRetain` and `CFRelease`. + +### Null Values + +Objective-C has three null values - `NULL`, `Nil` and `nil`. `NULL` means a regular C pointer to zero, `Nil` is a `NULL` pointer to an Objective-C class, and `nil` is a `NULL` pointer to an Objective-C object. They are implicitly converted to `null` in JavaScript. When calling a native API with a `null` argument NativeScript converts the JavaScript null value to a C pointer to zero. Some APIs require their arguments to not be pointers to zero - invoking them with null in JavaScript can potentially crash the application without a chance to recover. + +## Numeric Types + +Integer and floating point data types in Objective-C are converted to JavaScript numbers. This includes types such as `char`, `int`, `long`, `float`, `double`, `NSInteger` and their unsigned variants. However, integer values larger than ±253 will lose their precision because the JavaScript number type is limited in size to 53-bit integers. + +## Struct Types + +NativeScript exposes Objective-C structures as JavaScript objects. The properties on such an object are the same as the fields on the structure it exposes. APIs that expect a struct type in Objective-C can be called with a JavaScript object with the same shape as the structure: + +```objc +CGRect rect = { + .origin = { + .x = 0, + .y = 0 + }, + .size = { + .width = 100, + .height = 100 + } +}; +UIView *view = [[UIView alloc] initWithFrame:rect]; +``` + +```js +const rect = { + origin: { + x: 0, + y: 0 + }, + size: { + width: 100, + height: 100 + } +} +const view = UIView.alloc().initWithFrame(rect) +``` + +More information on how NativeScript deals with structures is available [here](#C-Structures). + + + +## `NSError **` marshalling + +### Native to JavaScript + +```objc +@interface NSFileManager : NSObject ++ (NSFileManager *)defaultManager; +- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error; +@end +``` + +We can use this method from JavaScript in the following way: + +```js +const fileManager = NSFileManager.defaultManager +const bundlePath = NSBundle.mainBundle.bundlePath + +console.log(fileManager.contentsOfDirectoryAtPathError(bundlePath, null)) +``` + +If we want to check the error using out parameters: + +```js +const errorRef = new interop.Reference() +fileManager.contentsOfDirectoryAtPathError('/not-existing-path', errorRef) +console.log(errorRef.value) // NSError: "The folder '/not-existing-path' doesn't exist." +``` + +Or we can skip passing the **last NSError \*\*** out parameter and a JavaScript error will be thrown if the `NSError **` is set from native: + +```js +try { + fileManager.contentsOfDirectoryAtPathError('/not-existing-path') +} catch (e) { + console.log(e) // NSError: "The folder '/not-existing-path' doesn't exist." +} +``` + +### JavaScript to Native + +When overriding a method having **NSError ** out parameter in the end** any thrown JavaScript error will be wrapped and set to the `NSError **` argument (if given). + +### Pointer Types + +Languages in the C family, including iOS SDK, utilize the concept of a pointer data type. Pointers are values that represent the memory location of another value. However, JavaScript, unlike C-based languages, does not have native support for pointers. To bridge this gap, NativeScript introduces the Reference object. References are special objects designed to enable JavaScript to handle and interact with pointer values. They provide a mechanism for JavaScript to reason about and access memory locations. To illustrate, consider the following example: + +```objc +NSFileManager *fileManager = [NSFileManager defaultManager]; +BOOL isDirectory; +BOOL exists = [fileManager fileExistsAtPath:@"/var/log" isDirectory:&isDirectory]; +if (isDirectory) { + NSLog(@"The path is actually a directory"); +} +``` + +This code snippet invokes the Objective C `fileExistsAtPath:isDirectory:` selector method of the NSFileManager class. The method takes an NSString as its first argument and a pointer to a boolean value as its second argument. When executed, the method updates the boolean value directly using the provided pointer, allowing the `isDirectory` variable to be modified. The code can be expressed via NativeScript in the following manner: + +```js +const fileManager = NSFileManager.defaultManager +const isDirectory = new interop.Reference() +const exists = fileManager.fileExistsAtPathIsDirectory('/var/log', isDirectory) +if (isDirectory.value) { + console.log('The path is actually a directory') +} +``` \ No newline at end of file diff --git a/content/sidebar.ts b/content/sidebar.ts index 822b9aa6..f1b8af69 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -241,6 +241,10 @@ export default [ { text: 'Marshalling', items: [ + { + text: 'iOS Marshalling', + link: '/guide/ios-marshalling', + }, { text: 'iOS', link: '/guide/ios-runtime-types', From 55df3b98f43001fa4cbf8b8524dd1b1fffdb130d Mon Sep 17 00:00:00 2001 From: Nandesora Tjihero <78957708+Ombuweb@users.noreply.github.com> Date: Sat, 26 Aug 2023 04:38:07 +0200 Subject: [PATCH 35/44] docs: data binding (#6) Co-authored-by: Nathan Walker Co-authored-by: Sean Kelly <36159246+SeanKelly369@users.noreply.github.com> --- content/guide/data-binding.md | 277 ++++++++++++++++++++++++++++++++++ content/sidebar.ts | 4 + 2 files changed, 281 insertions(+) create mode 100644 content/guide/data-binding.md diff --git a/content/guide/data-binding.md b/content/guide/data-binding.md new file mode 100644 index 00000000..f5b34e09 --- /dev/null +++ b/content/guide/data-binding.md @@ -0,0 +1,277 @@ +--- +title: Data Binding in NativeScript +--- + +_Data Binding_ refers to a connection (_binding_) and data flow between _ViewModel_ (Model) and _User Interface_ (UI). + +It gets activated through three steps: + +1. Create a ViewModel(let's call it DataModel) class extending the `Observable` class +2. Make DataModel available to the UI by setting `page.bindingContext` = `new DataModel()` +3. Using the mustach syntax ({{ }} ), bind the UI components properties to the members of the `DataModel` instance. + +When you look at a new project, you see an example of those steps applied. + +There are various ways to manage data flow, often referred to as data bindings: + +- **One-way (to UI) data binding** - This is the most common form of data binding. An example would be binding a string property in the Model to a Label component. + +```xml +