@webview-native-ui/engine 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,59 +1,58 @@
1
- # WebView Native UI Engine
1
+ # Beyond the WebView.
2
2
 
3
- **Bring Web UI above the native layer.**
3
+ **Bring Native UI seamlessly into Web apps.**
4
4
 
5
- _Visually, through seamless native-to-Web projection._
5
+ Build your screens with the flexibility of the Web and the controls and capabilities of native platforms. WebView Native UI Engine helps them work together as one experience, while keeping your Web layouts and navigation.
6
+
7
+ ## Web UI. Native UI. One experience.
8
+
9
+ A native component should feel like part of the screen around it. For example:
10
+
11
+ - **Scroll together.** A native ad moves with the surrounding Web content as the reader scrolls through a feed.
12
+ - **Layer together.** A Web alert appears over native navigation controls. Close it, and those controls are ready for native interaction again.
13
+
14
+ Scrolling and overlays are examples of a broader goal: letting Web UI and Native UI coexist throughout the screen's lifecycle.
6
15
 
7
16
  ![Conceptual view of a Web overlay above a native surface and WebView content](docs/assets/hero.png)
8
17
 
9
- _Conceptual view. Native appearance is projected into the Web layer during overlay presentation._
18
+ _Conceptual view of overlay presentation. The Engine projects native appearance into the Web layer; it does not reorder the actual native view within the DOM._
10
19
 
11
- Build WebView apps where native controls and Web content share the same scrolling screens, transitions and overlays.
20
+ https://github.com/user-attachments/assets/2768f014-7325-49c7-b848-25052756c36d
12
21
 
13
- ## Beyond the WebView boundary
22
+ native controls beneath a Web alert, followed by a native ad scrolling with Web content. Recorded on iOS Simulator.
14
23
 
15
- WebView apps combine the Web's flexible layouts with access to native platform capabilities. But native UI and the Web page live in separate rendering layers. A native control placed above the WebView does not automatically participate in DOM scrolling, clipping or stacking order. A Web modal cannot simply cover it with a higher `z-index`.
24
+ ## Built for plugin developers
16
25
 
17
- Crossing that boundary takes more than a bridge call. It requires coordinating placement, appearance, input, rendering and restoration across two rendering systems on each platform. Rebuilding that coordination for every component makes otherwise useful designs expensive to deliver and maintain.
26
+ WebView Native UI Engine is a library for **Capacitor and Cordova plugin developers**. Integrate it into a plugin to bring native components into Web-driven screens, without making each application build its own coordination between rendering layers.
18
27
 
19
- WebView Native UI Engine makes that work reusable. It lets applications combine native UI with Web-driven screens and interactions, while sharing the machinery that moves presentation between the two layers. The goal is to expand what a WebView app can be built to do—and make that practical to ship.
28
+ A native view above a WebView does not automatically participate in DOM scrolling, clipping or stacking order. A Web modal cannot simply cover it with a higher `z-index`. The Engine coordinates presentation across that boundary, so you can focus on the controls and native capabilities your plugin provides.
20
29
 
21
- ## What this makes possible
30
+ Use it for toolbars, tab bars, controls and capturable SDK views. Keep your existing plugin API and business logic; applications gain these capabilities through the plugins they use.
22
31
 
23
- - **Native UI within Web-designed screens.** Combine native toolbars, tab bars, player controls and eligible SDK views with the application's DOM layout and navigation.
24
- - **Web overlays that coexist with native content.** Present modals and other overlays over native surfaces, then return to the interactive native UI when they close.
25
- - **Scrolling and transitions that retain native appearance.** Carry the native surface's captured appearance through Web-layer movement, without recreating its styling in CSS.
26
- - **Shared infrastructure across components and platforms.** Reuse the same projection and restoration lifecycle on iOS and Android instead of implementing it independently in every integration.
32
+ ## How it works
27
33
 
28
- ## The approach
34
+ The plugin supplies a native view and a DOM container that defines its place in the Web layout. The Engine coordinates DOM measurement, supported native scroll tracking and the handoff between native and Web presentation.
29
35
 
30
- Use the live native view where it belongs, and a prepared visual projection in the DOM when the Web layer needs to own presentation. Restore the interactive native view when it can take over again. An existing DOM container gives the Web projection its place in the application's layout and layer structure.
36
+ When the Web layer needs to own presentation, the Engine displays a prepared image of the native component in that container. When native presentation can resume, it coordinates placement and restoration of the interactive view.
31
37
 
32
- The Engine coordinates this handoff; the integrating renderer still creates and positions its views. A projected image preserves appearance, not live interaction or video. Capture availability and layout support determine when a projection can be used, with an explicit fallback when it cannot. See [concepts and limits](docs/concepts.md) for the detailed contract and limits.
38
+ The image is a temporary visual projection, not a second interactive control. It does not forward clicks or play live video. Capture eligibility and platform rendering limits still apply, including differences in Liquid Glass highlights. When a suitable image is unavailable, the integration uses its configured Web fallback. See [concepts and limits](docs/concepts.md) and [platform support](docs/reference/platforms.md) before choosing a component.
33
39
 
34
- ## Choose your starting point
40
+ ## Start building
35
41
 
36
- Applications gain these capabilities through plugins that integrate the Engine.
37
- Plugin authors retain their existing native controls and public APIs; the Engine
38
- supplies the coordination between native and Web presentation. It supports iOS
39
- and Android with Capacitor or Cordova. Ionic and Angular are not required.
42
+ The Engine supports **iOS and Android**. Ionic and Angular are optional. Start with a native view and its DOM container, then connect the lifecycle notifications your UI needs. The integration guides cover runtime startup and optional framework support.
40
43
 
41
- | Start here | Purpose |
42
- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
43
- | [Documentation](docs/README.md) | Reading order: basic initialization, optional behavior, advanced APIs and reference. |
44
- | [Concepts and limits](docs/concepts.md) | Decide whether projection fits your component. |
45
- | [Basic initialization](docs/getting-started/initialization.md) | Minimum setup and required inputs, before framework-specific integration. |
44
+ | Start here | Why |
45
+ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
46
+ | [Basic initialization](docs/getting-started/initialization.md) | Register a native view and its DOM container with the minimum required setup. |
47
+ | [Capacitor plugin guide](docs/getting-started/capacitor.md) | Connect the Engine to an existing Capacitor plugin. |
48
+ | [Cordova plugin guide](docs/getting-started/cordova.md) | Use the same lifecycle through Cordova's native hooks. |
49
+ | [Documentation](docs/README.md) | Find optional settings, renderer APIs and reference by the problem you need to solve. |
46
50
 
47
- Native registration starts the Web runtime; the base integration needs no separate
48
- application-side Engine initializer. The API is pre-1.0: pin matching Engine
49
- versions across npm and native dependencies. Android placement requires API 23+,
50
- with composited snapshots available from API 26+.
51
+ Use matching Engine versions across npm and native dependencies, and retain dependency lockfiles for reproducible builds. Android placement requires API 23+, with composited snapshots available from API 26+.
51
52
 
52
- ## Reference and contributing
53
+ ## Contributing
53
54
 
54
- The [documentation index](docs/README.md) links to configuration, advanced APIs,
55
- container support and platform contracts. For repository development and publishing,
56
- see [development and releases](docs/maintenance.md).
55
+ For repository development, verification and publishing, see [development and releases](docs/maintenance.md).
57
56
 
58
57
  ## License
59
58
 
@@ -16,7 +16,7 @@ const send = (id, request) => new Promise((resolve, reject) => {
16
16
  pending.set(key, { resolve, reject, timer });
17
17
  const message = JSON.stringify({ key, request });
18
18
  const host = globalThis;
19
- const channel = host.webkit?.messageHandlers?.[id] ?? host[id] ?? host.nativeUIEngine;
19
+ const channel = host.webkit?.messageHandlers?.[id] ?? host.nativeUIEngine;
20
20
  if (!channel) {
21
21
  clearTimeout(timer);
22
22
  pending.delete(key);
@@ -1,6 +1,12 @@
1
1
  /** Owns asynchronous projection creation and disposal for DOM-backed surfaces. */
2
2
  export const createWebProjectionSurfaces = (options = {}) => {
3
3
  const entries = new Map();
4
+ const retiring = new Set();
5
+ const retire = (disposal) => {
6
+ const pending = disposal.finally(() => retiring.delete(pending));
7
+ retiring.add(pending);
8
+ return pending;
9
+ };
4
10
  let stopped = false;
5
11
  const values = () => [...entries.values()].flatMap((entry) => (entry.surface ? [entry.surface] : []));
6
12
  let doc;
@@ -53,11 +59,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
53
59
  if (!entry)
54
60
  return;
55
61
  entries.delete(owner);
56
- if (entry.surface)
57
- await entry.surface.destroy();
58
- // A failed creation has nothing to dispose; ensure() reports that error.
59
- else
60
- await entry.ready.catch(() => { });
62
+ await retire(entry.surface ? entry.surface.destroy() : entry.ready.catch(() => { }));
61
63
  };
62
64
  return {
63
65
  get: (owner) => entries.get(owner)?.surface,
@@ -89,7 +91,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
89
91
  if (stopped || entries.get(owner) !== entry || !owner.isConnected) {
90
92
  if (entries.get(owner) === entry)
91
93
  entries.delete(owner);
92
- await surface.destroy();
94
+ await retire(surface.destroy());
93
95
  return undefined;
94
96
  }
95
97
  entry.surface = surface;
@@ -110,7 +112,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
110
112
  listeners?.abort();
111
113
  if (motionFrame)
112
114
  doc?.defaultView?.cancelAnimationFrame(motionFrame);
113
- await Promise.all([...entries.keys()].map(remove));
115
+ await Promise.all([...retiring, ...[...entries.keys()].map(remove)]);
114
116
  },
115
117
  };
116
118
  };
@@ -2,7 +2,7 @@ import { projectionURL } from '../native/resource-url.js';
2
2
  import { bounded, boundedListener } from '../native/bridge.js';
3
3
  import { bindProjectionEvents } from './events.js';
4
4
  import { snapshotStyles, snapshotShadowStyles } from './styles.js';
5
- const layout = (candidates, positionIndependent = false) => JSON.stringify(candidates.map(({ control: c }) => [
5
+ const layout = ({ control: c }, positionIndependent = false) => [
6
6
  c.id,
7
7
  c.kind,
8
8
  c.placement,
@@ -12,7 +12,7 @@ const layout = (candidates, positionIndependent = false) => JSON.stringify(candi
12
12
  c.height,
13
13
  c.dark,
14
14
  c.rtl,
15
- ]));
15
+ ];
16
16
  /** Images are prepared after accepted native updates, never on the overlay's critical path. */
17
17
  const createProjectionEngine = async (scope, transport, callbacks) => {
18
18
  const captureDelayMs = callbacks.captureDelayMs ?? 50;
@@ -48,7 +48,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
48
48
  const relative = origin
49
49
  ? { ...candidate, control: { ...candidate.control, x: candidate.control.x - origin.x, y: candidate.control.y - origin.y } }
50
50
  : candidate;
51
- return [scopeIDs.get(candidate.scope), layout([relative], !!callbacks.opaqueUnderlay)];
51
+ return [scopeIDs.get(candidate.scope), layout(relative, !!callbacks.opaqueUnderlay)];
52
52
  }));
53
53
  const sheets = new Map();
54
54
  const rootFor = (element) => {
@@ -249,8 +249,9 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
249
249
  // One crop may cover a button group, but must have one fixed fallback destination.
250
250
  if (result.parts.some((part) => new Set(part.ids.map((id) => selected.find((candidate) => candidate.control.id === id).scope)).size !== 1))
251
251
  return;
252
- await bounded(Promise.all(result.parts.map(async (part) => {
253
- const target = selected.find((candidate) => part.ids.includes(candidate.control.id)).scope.element;
252
+ const partSources = result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)));
253
+ await bounded(Promise.all(result.parts.map(async (part, index) => {
254
+ const target = partSources[index].scope.element;
254
255
  let pool = imagePool.get(target);
255
256
  if (!pool) {
256
257
  pool = [];
@@ -283,9 +284,9 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
283
284
  data: result,
284
285
  images,
285
286
  sources: Array.from(new Set(selected.flatMap((item) => [item.element, ...(item.sources ?? [])]))),
286
- targets: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).element),
287
- scopes: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).scope),
288
- parents: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).scope.element.parentNode),
287
+ targets: partSources.map((source) => source.element),
288
+ scopes: partSources.map((source) => source.scope),
289
+ parents: partSources.map((source) => source.scope.element.parentNode),
289
290
  frames: [],
290
291
  viewport: size,
291
292
  revision,
package/docs/README.md CHANGED
@@ -1,18 +1,24 @@
1
1
  # Documentation
2
2
 
3
- Start with the smallest integration that meets your component's needs. Basic
4
- registration is enough for a single native view; optional settings adapt it to
5
- changing content and application lifecycle. Advanced APIs are a separate route for
6
- renderers that already own batching or placement.
3
+ Build Web-driven screens with native controls and capabilities. These guides
4
+ explain how to integrate the Engine into a plugin so Web UI and Native UI can
5
+ coexist. Start with [the shared model](concepts.md): how a native component belongs in a Web layout,
6
+ which layer presents it, and how interaction returns to the live view. Scrolling
7
+ and overlays are examples of that coordination, not separate integration models.
8
+
9
+ Then choose the smallest integration that fits your ownership model. Register a
10
+ native view and its DOM container, connect the lifecycle notifications your UI
11
+ needs, and add advanced APIs only if you already own a renderer's batching or placement.
7
12
 
8
13
  ## Choose by the problem you need to solve
9
14
 
10
- | Case | Start here | Why |
11
- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
12
- | A Web modal must cover a plugin's native tab bar | [Basic registration](getting-started/initialization.md), then the framework integration guide | Register the view and its DOM host once; the Engine owns capture and restoration. |
13
- | A hidden fallback returns with the old toggle selection | [Value bindings](configuration/values.md) | Reflect current native data on the fallback host, independently of where it is displayed. |
14
- | Background toolbar buttons and foreground modal buttons need different handoff lifetimes | [Renderer surfaces](advanced/renderer.md) | Separate owners so closing one surface cannot restore another's controls. |
15
- | An existing renderer overlaps updates or loses a native acknowledgement | [Update queue](advanced/update-queue.md) | Serialize fresh-state passes and reconcile the connection without replaying user actions. |
15
+ | Case | Start here | Why |
16
+ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
17
+ | An eligible native SDK view belongs in a scrolling Web feed | [Basic registration](getting-started/initialization.md) and [platform limits](reference/platforms.md) | Bind it to its DOM container; native tracking or temporary image presentation depends on the platform and layout. |
18
+ | A Web modal must cover a plugin's native tab bar | [Basic registration](getting-started/initialization.md), then the framework integration guide | Register the view and its DOM host, then connect overlay lifecycle; the Engine coordinates handoff and restoration. |
19
+ | A hidden fallback returns with the old toggle selection | [Value bindings](configuration/values.md) | Reflect current native data on the fallback host, independently of where it is displayed. |
20
+ | Background toolbar buttons and foreground modal buttons need different handoff lifetimes | [Renderer surfaces](advanced/renderer.md) | Separate owners so closing one surface cannot restore another's controls. |
21
+ | An existing renderer overlaps updates or loses a native acknowledgement | [Update queue](advanced/update-queue.md) | Serialize fresh-state passes and reconcile the connection without replaying user actions. |
16
22
 
17
23
  The full reading order follows. Skip advanced APIs unless your renderer owns the
18
24
  work described in their cases.
package/docs/concepts.md CHANGED
@@ -1,21 +1,46 @@
1
1
  # Native UI in Web-driven screens
2
2
 
3
- Use the Engine when a native view must coexist with Web scrolling, transitions or
4
- overlays. A native tab bar, for example, can remain interactive during normal use,
5
- yet visually sit beneath a Web modal when that modal opens. The application keeps
6
- its Web navigation and layout instead of recreating the entire screen natively.
3
+ The goal is to combine the flexibility of Web layouts and navigation with native
4
+ controls and capabilities in one screen. Applications keep their Web-driven UI;
5
+ plugins supply native components. The Engine coordinates presentation between
6
+ them so each component can coexist with the surrounding screen throughout layout
7
+ changes, interaction and navigation.
7
8
 
8
9
  ## Why a bridge call is not enough
9
10
 
10
- A native view above a WebView is outside the DOM stacking tree. Increasing a Web
11
- modal's `z-index` cannot cover it. Hiding the native view reveals whatever the Web
12
- page draws underneath, which may look different; showing it again before layout
13
- has settled can expose the wrong position.
11
+ Creating a native view does not give it a place in the DOM. A view above the
12
+ WebView does not automatically follow a scrolling container or participate in
13
+ its clipping and stacking order. A higher Web `z-index` cannot cover it. Simply
14
+ hiding and showing it can reveal a different appearance or restore it at an
15
+ outdated position.
14
16
 
15
- The Engine handles the visual handoff between those layers. It prepares an image
16
- while native content is available, presents that image in the application's DOM
17
- when needed, and keeps it until native restoration is acknowledged. Preparation
18
- is separate from presentation so opening an overlay does not wait for capture.
17
+ The shared integration model is a native view paired with a DOM container. The
18
+ container identifies where the component belongs in the Web screen. The plugin
19
+ provides native content and reports changes; the Engine coordinates measurement,
20
+ prepared appearance and the handoff between presentation layers.
21
+
22
+ ## What coexistence looks like
23
+
24
+ These situations share the same presentation model. Each integration connects the lifecycle and value notifications it needs:
25
+
26
+ | Situation | Desired experience | How the shared model helps |
27
+ | -------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
28
+ | An eligible native advertisement sits in a Web feed | It stays aligned with the content as the reader scrolls. | The DOM container supplies geometry; supported native tracking or temporary Web projection follows the movement. |
29
+ | A Web alert opens over native navigation controls | The alert covers their appearance, then native interaction returns when it closes. | Lifecycle notifications or the Ionic adapter request coverage; the Engine coordinates image handoff and restoration. |
30
+ | A page or modal changes layout | Its native controls return at the current location rather than an old rectangle. | Geometry and restoration are coordinated with the owning container's lifetime. |
31
+ | A native toggle changes while its Web fallback is hidden | Returning to HTML does not reveal the old value. | Optional value bindings reflect component data independently of presentation. |
32
+
33
+ Scrolling does not always mean moving a live native view. Supported iOS overflow
34
+ layouts can use native scroll bindings; Android uses temporary image presentation,
35
+ and unsupported layouts follow the configured fallback policy. Images do not
36
+ provide live SDK interaction. See [platform limits](reference/platforms.md) and
37
+ [scroll binding constraints](advanced/native-handoff.md#native-scroll-tracking).
38
+
39
+ The Engine prepares images while native content is available and retains them
40
+ through the handoff until native restoration is acknowledged. Preparation is
41
+ separate from presentation so opening an overlay does not wait for capture.
42
+ Application-specific lifecycle decisions still need notifications; registering a
43
+ view does not automatically detect every framework's overlays.
19
44
 
20
45
  ## Three presentations, with different purposes
21
46
 
@@ -91,7 +116,7 @@ does not promise pixel-identical capture of every native view.
91
116
 
92
117
  Validate your actual backgrounds, overlay transitions and restoration on supported
93
118
  devices. If capture is unavailable, decide what the user should see in the Web
94
- fallback before integrating. Read the [capture and platform reference](reference/projection.md)
95
- for eligibility, timing, storage and failure policies.
119
+ fallback before integrating. Read the [capture and platform reference](reference/platforms.md)
120
+ and [projection contract](reference/projection.md) for eligibility and failure policies.
96
121
 
97
122
  [Documentation overview](README.md)
@@ -17,7 +17,7 @@ same position: include that successful selection notification in `receiveListene
17
17
  | `captureDelayMs` | Prepare images after a 50 ms debounce. | A tab highlight is still animating when its selection event arrives: wait for settled pixels. See [capture timing](capture.md); this does not delay overlay opening. |
18
18
  | `canCapture` | Returns true; Engine geometry and platform checks still apply. | An SDK switches from capturable static content to restricted video: prevent an ineligible capture. Return eligibility for the current content. |
19
19
  | `failedFallback` | Use the ordinary Web fallback when no suitable image is available. | An SDK view has no usable Web replacement: keep it native during automatic motion only if the renderer maintains its placement. Explicit Web coverage and `none` still take precedence. See [failure policy](../reference/projection.md#native-surface-requirements). |
20
- | `allowedOrigins` | iOS: `capacitor://localhost`, `app://localhost`, `https://localhost`, `http://localhost`. Android: `https://localhost`, `http://localhost`. | An app changes its local hostname: match that exact trusted origin so its channel can connect. On Android use the same origins for `install` and every registration. |
20
+ | `allowedOrigins` | Capacitor: bundled `server.url` or local hostname/scheme; localhost defaults when no app server config exists. Cordova: localhost defaults. | Override for programmatic configuration or a narrower trusted origin. On Android set `install` before navigation; registrations inherit it. See [origin matching](../reference/projection.md). |
21
21
  | `runtime` | Capacitor. | **Required for Cordova integrations**: select Cordova so notifications and resource URLs use its transport. |
22
22
  | `captureMode` (Android) | `COMPOSITED`, including the visible background. | An opaque card scrolls past adjacent content: `VIEW` captures the card alone instead of surrounding window pixels. Do not use it for translucent content, video or SurfaceView. See [capture eligibility](../reference/platforms.md#android-capture-modes). |
23
23
 
@@ -1,6 +1,6 @@
1
1
  # Capacitor plugin integration
2
2
 
3
- Add Web projection and restoration to an existing Capacitor plugin without changing its public `registerPlugin()` export. The plugin keeps its native renderer and application API; the Engine coordinates its native view with a DOM fallback.
3
+ Let an existing Capacitor plugin's native components coexist with Web layouts, navigation and overlays without changing its public `registerPlugin()` export. The plugin keeps its native renderer and application API; registration connects each native view to its DOM container using the [shared presentation model](../concepts.md).
4
4
 
5
5
  This guide covers dependencies, registration, notifications and teardown. Start with
6
6
  [native initialization](initialization.md) for the minimal call, required inputs
@@ -40,9 +40,9 @@ The primary native distribution is Swift Package Manager. Depend on the reposito
40
40
  .product(name: "WebViewNativeUIEngine", package: "engine")
41
41
  ```
42
42
 
43
- While the API is pre-1.0, pin the same Engine release across npm, SwiftPM, Gradle and CocoaPods, and upgrade deliberately. Applications should retain their dependency lockfiles for reproducible builds.
43
+ Use the same Engine release across npm, SwiftPM, Gradle and CocoaPods, and upgrade deliberately. Applications should retain their dependency lockfiles for reproducible builds.
44
44
 
45
- SwiftPM resolves this repository's version tags such as `v0.0.1`. For unreleased integration tests, use a local checkout or an explicit tested commit.
45
+ SwiftPM resolves this repository's version tags such as `v1.0.0`. For unreleased integration tests, use a local checkout or an explicit tested commit.
46
46
 
47
47
  `import WebViewNativeUIEngine` requires no Capacitor registration. The npm companion supplies DOM measurement and Web image handoff; SwiftPM resolves the native dependency independently of npm hoisting.
48
48
 
@@ -1,5 +1,7 @@
1
1
  # Cordova plugin integration
2
2
 
3
+ Let a Cordova plugin's native components coexist with Web layouts, navigation and overlays using the [same presentation model](../concepts.md) as Capacitor.
4
+
3
5
  The same npm package, Swift library and Android AAR support Capacitor and Cordova. Engine is a library used by plugin maintainers, not a separately registered Cordova plugin. A Cordova application does not need `@capacitor/core` or a Capacitor native SDK.
4
6
 
5
7
  The consuming plugin still owns its native view and the corresponding DOM, including placement, size and stacking order. Supply an existing selector such as `native-ad` or `#player`. The DOM may be added after initialization. Image preparation, scroll handling, overlays and restoration use the common Engine implementation.
@@ -32,7 +34,7 @@ frameworks report their own transition lifecycle through notifications.
32
34
  Replace `<engine-version>` in every dependency below with the same published
33
35
  release. Read that release tag’s documentation so the examples match its APIs.
34
36
 
35
- Use the same Engine release for the Web package and native libraries. The API is pre-1.0; pin versions and retain dependency lockfiles.
37
+ Use the same Engine release for the Web package and native libraries. Pin matching versions and retain dependency lockfiles for reproducible builds.
36
38
 
37
39
  ```sh
38
40
  npm install --save-exact '@webview-native-ui/engine@<engine-version>'
@@ -2,7 +2,10 @@
2
2
 
3
3
  Start with `NativeUIProjection` when a plugin owns a native view and a corresponding
4
4
  Web container. Registration starts the runtime, binds that container and coordinates
5
- image handoff and restoration. You do not need the renderer APIs for this route.
5
+ presentation between the native view and the Web layout. This is the same starting
6
+ point for a component in a scrolling feed or beneath a Web overlay. You do not need
7
+ the renderer APIs for this route. See [the shared model](../concepts.md) for how
8
+ these situations relate and where platform limits apply.
6
9
 
7
10
  Use the [Capacitor](capacitor.md) or [Cordova](cordova.md) guide for
8
11
  dependency installation, plugin lifecycle hooks and notification forwarding.
@@ -46,7 +49,20 @@ projection = NativeUIProjection(
46
49
  )
47
50
  ```
48
51
 
49
- Both snippets belong inside the integration guide's error boundary; retain the
52
+ Java can use the same defaults without supplying every optional argument:
53
+
54
+ ```java
55
+ NativeUIProjection.install(getBridge().getWebView());
56
+ projection = new NativeUIProjection(
57
+ getActivity().getWindow(),
58
+ getBridge().getWebView(),
59
+ getPluginHandle().getId(),
60
+ "#native-ui-fallback",
61
+ () -> nativeView
62
+ );
63
+ ```
64
+
65
+ These snippets belong inside the integration guide's error boundary; retain the
50
66
  existing Web fallback if initialization fails. `projection` and `nativeView` are
51
67
  properties of your plugin. Dispose the registration with `destroy()` on iOS or
52
68
  `close()` on Android when that renderer is retired. All native lifecycle calls
@@ -100,8 +100,8 @@ precision alone does not resolve this difference.
100
100
  Left: **iPhone 15 Pro Max, iOS 27.2**. Right: **iPad mini (6th generation),
101
101
  iPadOS 27.2**. Each device screenshot shows the captured **16-bit Display P3 PNG
102
102
  in WKWebView above** and the **live UIKit tab bar below**, at the same size within
103
- that device. The screenshots were placed side by side without rescaling or changing
104
- their shared color profile. These are physical-device results, not Simulator captures.
103
+ that device. For this comparison, the iPhone screenshot was resized to match the iPad
104
+ screenshot's height; their shared color profile was preserved. These are physical-device results, not Simulator captures.
105
105
 
106
106
  The system screenshot preserves the live appearance, but the application-level
107
107
  capture does not reproduce it completely. On the tested iPhone, the difference was
@@ -125,7 +125,7 @@ Listeners follow host reparenting and slot reassignment and are removed on teard
125
125
 
126
126
  Scrolling is internal: iOS uses a uniquely matched native scroll binding when available; otherwise, including Android, scrolling temporarily uses the prepared image and restores native content after motion settles. Automatic scroll handling never overrides explicit `web` or `none`. Unsupported capture or geometry keeps the normal DOM fallback; it does not wait for a new capture on the transition path. The latest explicit destination survives WebView reload, and old-document replies cannot complete a new document's requests.
127
127
 
128
- By default, the internal channel is limited to the main frame on Capacitor's localhost origins. Pass the application's exact `allowedOrigins` for a different configured origin. Android requires WebView's `WEB_MESSAGE_LISTENER` and `DOCUMENT_START_SCRIPT` features; registration fails explicitly on unsupported WebView implementations. Do not use wildcard origins for remotely navigable WebViews.
128
+ The internal channel is limited to the main frame. Capacitor origins come from the bundled `capacitor.config.json`: `server.url` takes precedence over the local hostname/scheme. To override them, pass exact `allowedOrigins` (on Android, to `install` before navigation; registrations inherit them). Neither `allowNavigation` nor the current WebView URL expands this set. Android requires WebView's `WEB_MESSAGE_LISTENER` and `DOCUMENT_START_SCRIPT` features; registration fails explicitly on unsupported WebView implementations. Do not use wildcard origins for remotely navigable WebViews.
129
129
 
130
130
  For capture modes, translucent surfaces, image color and private storage, see
131
131
  [capture and platform limits](platforms.md). Custom renderer bridge and scroll
@@ -79,7 +79,7 @@ public final class NativeUIProjection: NSObject, WKScriptMessageHandler {
79
79
 
80
80
  public convenience init(webView: WKWebView?, plugin: String, projectionSelector: String,
81
81
  receiveListeners: ReceiveListeners = .init(),
82
- allowedOrigins: Set<String> = ["capacitor://localhost", "app://localhost", "https://localhost", "http://localhost"],
82
+ allowedOrigins: Set<String>? = nil,
83
83
  failedFallback: FailedFallback = .web, runtime: Runtime = .capacitor,
84
84
  captureDelayMs: Double = 50,
85
85
  values: [String: ValueBinding] = [:], readValues: (() -> [String: Any])? = nil,
@@ -91,12 +91,14 @@ public final class NativeUIProjection: NSObject, WKScriptMessageHandler {
91
91
  }
92
92
  init(webView: WKWebView?, plugin: String, projectionSelector: String,
93
93
  receiveListeners: ReceiveListeners = .init(),
94
- allowedOrigins: Set<String> = ["capacitor://localhost", "app://localhost", "https://localhost", "http://localhost"],
94
+ allowedOrigins: Set<String>? = nil,
95
95
  failedFallback: FailedFallback = .web, runtime: Runtime = .capacitor,
96
96
  captureDelayMs: Double = 50,
97
97
  values: [String: ValueBinding] = [:], readValues: (() -> [String: Any])? = nil,
98
98
  canCapture: @escaping () -> Bool = { true }, snapshots: NativeUICoveredSnapshots,
99
99
  view: @escaping () -> UIView?) throws {
100
+ let allowedOrigins = try allowedOrigins
101
+ ?? (runtime == .capacitor ? ProjectionOrigins.configured() : ProjectionOrigins.defaults)
100
102
  // JSONSerialization produces valid UTF-8.
101
103
  // swiftlint:disable:next force_unwrapping
102
104
  self.allowedOriginsJSON = String(data: try JSONSerialization.data(withJSONObject: Array(allowedOrigins)), encoding: .utf8)!
@@ -273,7 +275,7 @@ public final class NativeUIProjection: NSObject, WKScriptMessageHandler {
273
275
  restoreAccepted = true
274
276
  complete(["shown": valid && snapshots.cover(
275
277
  token, in: webView,
276
- views: Array(Dictionary(grouping: selected.values, by: { ObjectIdentifier($0) }).values.compactMap { $0.first }),
278
+ views: Array(selected.values),
277
279
  transient: true, frames: frames
278
280
  )])
279
281
  case "restore":
@@ -0,0 +1,31 @@
1
+ import Foundation
2
+ import WebKit
3
+
4
+ enum ProjectionOrigins {
5
+ static let defaults: Set<String> = ["capacitor://localhost", "app://localhost", "https://localhost", "http://localhost"]
6
+
7
+ static func configured(in bundle: Bundle = .main) throws -> Set<String> {
8
+ guard let url = bundle.url(forResource: "capacitor.config", withExtension: "json") else { return defaults }
9
+ guard let configuration = try JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any] else { throw URLError(.badURL) }
10
+ return try configured(configuration)
11
+ }
12
+
13
+ static func configured(_ configuration: [String: Any]) throws -> Set<String> {
14
+ guard let server = configuration["server"] as? [String: Any] else { return defaults }
15
+ var scheme = server["iosScheme"] as? String ?? "capacitor"
16
+ if WKWebView.handlesURLScheme(scheme) || scheme.range(of: "^[a-z][a-z0-9.+-]*$", options: [.caseInsensitive, .regularExpression]) == nil {
17
+ scheme = "capacitor"
18
+ }
19
+ let host = server["hostname"] as? String ?? "localhost"
20
+ guard let url = URLComponents(string: server["url"] as? String ?? "\(scheme)://\(host)"),
21
+ let scheme = url.scheme?.lowercased(), let host = url.host?.lowercased(),
22
+ !host.isEmpty, !host.contains("*") else { throw URLError(.badURL) }
23
+ var origin = URLComponents()
24
+ origin.scheme = scheme
25
+ origin.host = host
26
+ origin.port = url.port
27
+ if (scheme == "https" && url.port == 443) || (scheme == "http" && url.port == 80) { origin.port = nil }
28
+ guard let value = origin.string else { throw URLError(.badURL) }
29
+ return [value]
30
+ }
31
+ }
@@ -1,4 +1,4 @@
1
- "use strict";var WebViewNativeUIEngineRuntime=(()=>{var Ge=Object.defineProperty;var ut=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var pt=Object.prototype.hasOwnProperty;var mt=(t,r)=>{for(var o in r)Ge(t,o,{get:r[o],enumerable:!0})},vt=(t,r,o,v)=>{if(r&&typeof r=="object"||typeof r=="function")for(let y of ft(r))!pt.call(t,y)&&y!==o&&Ge(t,y,{get:()=>r[y],enumerable:!(v=ut(r,y))||v.enumerable});return t};var ht=t=>vt(Ge({},"__esModule",{value:!0}),t);var Ot={};mt(Ot,{hide:()=>kt,notify:()=>Mt,receive:()=>xt,show:()=>Ht,start:()=>Ct,stop:()=>Rt});var tt=(t,r,o)=>{for(let[v,y]of Object.entries(r)){if(!/^[a-z][a-z0-9-]*$/.test(v))throw new TypeError("Native value keys must be lowercase attribute names");let f=(Object.hasOwn(o,v)?o[v]:void 0)?.property;if(f&&(!/^[a-zA-Z][\w]*$/.test(f)||/^on/i.test(f)||["innerHTML","outerHTML","textContent","innerText","style","constructor","prototype"].includes(f)))throw new TypeError("Native value binding requires a data property");let P=JSON.stringify(y);if(P===void 0)throw new TypeError("Native values must be JSON values");if(f){let p=t;JSON.stringify(p[f])!==P&&(p[f]=y)}else{let p=`data-native-ui-value-${v}`,M=typeof y=="string"?y:P;t.getAttribute(p)!==M&&t.setAttribute(p,M)}}};var yt=Symbol.for("webview-native-ui-engine.projection-placement");var Ue=t=>t?.[yt];var gt=(t,r)=>{if(t.matches("ion-toast, ion-menu"))return!1;let o=t;if(o.showBackdrop===!1)return!1;if(!t.matches("ion-modal"))return!0;let v=r??o.initialBreakpoint;return v===void 0||v>(o.backdropBreakpoint??0)},wt=Symbol.for("@webview-native-ui/engine/overlays/v1"),bt=t=>{let r=new Set,o=new Set,v=new Set,y=new WeakMap,m=new WeakSet,f=new Map,P=[],p={},M=new Map,x,j=g=>{for(let d of[...v])try{d(g)}catch(w){console.error("Native UI overlay listener failed",w)}},B=(g,d,w)=>{d==="willPresent"||d==="didPresent"?(m.delete(g),r.add(g)):d==="didDismiss"&&(m.add(g),r.delete(g),o.delete(g),y.delete(g));let c=w?.detail?.breakpoint;typeof c=="number"&&c>0&&y.set(g,c),j({element:g,phase:d,event:w})},k=(g=!1)=>{let d=!1;for(let w of r)(!w.isConnected||w.ownerDocument!==t||!P.some(({selector:c})=>w.matches(c)))&&(r.delete(w),o.delete(w),d=!0);for(let w of t.querySelectorAll(P.map(({selector:c})=>c).join(",")))!r.has(w)&&!m.has(w)&&(g||!w.matches("ion-popover"))&&(w.presented||w.classList.contains("show-menu"))&&(r.add(w),d=!0);d&&j({phase:"change"})};return{get active(){return r},subscribe(g){return v.add(g),()=>{v.delete(g)}},setNative(g,d){o.has(g)!==d&&(d?o.add(g):o.delete(g),j({element:g,phase:"change"}))},isExcluded(g){return!!p.exclude&&g.matches(p.exclude)},isNative(g){return o.has(g)},get stack(){let g=[...r].filter(w=>w.isConnected&&w.ownerDocument===t),d=w=>{let c=Number.parseFloat(t.defaultView.getComputedStyle(w).zIndex);return Number.isFinite(c)?c:w.overlayIndex??0};return g.sort((w,c)=>d(w)-d(c)),g},isCovered(g){let d=this.stack,w=d.reduce((c,W,R)=>W.contains(g)?R:c,-1);return d.some((c,W)=>{if(o.has(c)||p.exclude&&c.matches(p.exclude))return!1;let R=p.covers?.(c,g);return R!==void 0?R:W>w&&!c.contains(g)&&(c.matches("ion-menu")||gt(c,y.get(c)))})},enable(g,d){let w={...p,...d},c=[...g,...w.additional??[]];w.exclude&&t.querySelector(w.exclude);let W=new Map;for(let l of c){t.querySelector(l.selector);for(let[V,$]of Object.entries(l.events)){let G=W.get($);if(G&&G.phase!==V)throw new TypeError(`Overlay event "${$}" has conflicting phases`);W.set($,{phase:V,selector:G?`${G.selector}, ${l.selector}`:l.selector})}}p=w,P=c,M=W;for(let l of M.keys()){if(f.has(l))continue;let V=$=>{let G=M.get(l),ye=$.composedPath().find(J=>J instanceof t.defaultView.HTMLElement&&J.matches(G.selector));ye&&B(ye,G.phase,$)};t.addEventListener(l,V,!0),f.set(l,()=>t.removeEventListener(l,V,!0))}for(let[l,V]of f)M.has(l)||(V(),f.delete(l));let R=!x;x||(x=new MutationObserver(()=>k()),x.observe(t.documentElement,{childList:!0,subtree:!0}),t.addEventListener("nativeUIOverlayAdopted",l=>{let V=l.detail;V?.ownerDocument===t&&B(V,"willPresent",l)})),k(R),(R||d)&&j({phase:"change"})}}},nt=t=>{let r=t;return r[wt]??=bt(t)};function*Ne(t){let r=t;for(;r;){let o=r.getRootNode();r=r.assignedSlot??r.parentElement??(o instanceof ShadowRoot?o.host:null),r&&(yield r)}}var Et=({x:t,y:r,width:o,height:v})=>({x:t,y:r,width:o,height:v}),ot=t=>{let r=t.getBoundingClientRect(),o=getComputedStyle(t),v=parseFloat(o.borderLeftWidth)||0,y=parseFloat(o.borderTopWidth)||0;return{x:r.left+v,y:r.top+y,width:r.width-v-(parseFloat(o.borderRightWidth)||0),height:r.height-y-(parseFloat(o.borderBottomWidth)||0)}},Ke=(t,r,o)=>{let v=[t,...Ne(t)],y=v.indexOf(r);if(y<=0||r===t.ownerDocument.scrollingElement)return;let m=t.ownerDocument.defaultView;if(m.visualViewport&&(m.visualViewport.scale!==1||m.visualViewport.offsetLeft||m.visualViewport.offsetTop))return;let f=0,P=0,p=m.innerWidth,M=m.innerHeight;for(let[k,g]of v.entries()){let d=getComputedStyle(g);if(d.transform!=="none"&&!new DOMMatrixReadOnly(d.transform).isIdentity||d.zoom&&d.zoom!=="normal"&&Number(d.zoom)!==1||[d.translate,d.rotate,d.scale,d.perspective].some(R=>R&&R!=="none")||d.writingMode&&d.writingMode!=="horizontal-tb"||k<y&&/^(fixed|sticky)$/.test(d.position))return;if(k===0)continue;if(d.scrollbarGutter&&d.scrollbarGutter!=="auto")return;let w=/^(auto|scroll|hidden|clip)$/.test(d.overflowX),c=/^(auto|scroll|hidden|clip)$/.test(d.overflowY);if(k<y&&(w||c))return;let W=g===t.ownerDocument.scrollingElement;if(k>y&&((w||W)&&g.scrollWidth>g.clientWidth||(c||W)&&g.scrollHeight>g.clientHeight)||(w||c)&&[d.borderTopLeftRadius,d.borderTopRightRadius,d.borderBottomLeftRadius,d.borderBottomRightRadius].some(R=>parseFloat(R)>0)||d.clipPath&&d.clipPath!=="none"||d.maskImage&&d.maskImage!=="none")return;if(k>=y){let R=ot(g);w&&(f=Math.max(f,R.x),p=Math.min(p,R.x+R.width)),c&&(P=Math.max(P,R.y),M=Math.min(M,R.y+R.height))}}let x=getComputedStyle(r);if(!/^(auto|scroll)$/.test(x.overflowX)&&!/^(auto|scroll)$/.test(x.overflowY))return;let j=ot(r);if(p<=f||M<=P||!j.width||!j.height)return;let B=Et(t.getBoundingClientRect());return{id:o(r),offsetX:r.scrollLeft,offsetY:r.scrollTop,viewport:j,clip:{x:f,y:P,width:p-f,height:M-P},contentWidth:r.scrollWidth,contentHeight:r.scrollHeight,frame:{...B,x:B.x-j.x+r.scrollLeft,y:B.y-j.y+r.scrollTop}}};var it=t=>{let r=globalThis;if(r.Capacitor?.convertFileSrc)return r.Capacitor.convertFileSrc(t);if(!t.startsWith("file:"))return t;if(r.WkWebView?.convertFilePath)return r.WkWebView.convertFilePath(t);throw new Error("Native UI file URL requires a host resource handler")};var he=t=>new Promise((r,o)=>{let v=setTimeout(()=>o(new Error("Native UI bridge timed out")),5e3);t.then(r,o).finally(()=>clearTimeout(v))}),Xe=async t=>{try{return await he(t)}catch(r){throw t.then(o=>o.remove()).catch(()=>{}),r}};var Ye=async(t,r)=>{let o=new Map;for(let P of t.update??[])o.set(P,{updated:!0});for(let P of["native","web","none"])for(let p of t.projection?.[P]??[]){let M=o.get(p)??{updated:!1};if(M.destination&&M.destination!==P)throw new TypeError(`Projection event "${p}" has conflicting destinations`);o.set(p,{...M,destination:P})}let v=!1,y=[],m=async()=>{v=!0;let p=(await Promise.allSettled(y.splice(0).map(M=>Promise.resolve().then(()=>M.remove())))).find(M=>M.status==="rejected");if(p?.status==="rejected")throw p.reason},f=[...o].map(async([P,p])=>{let M=await Xe(Promise.resolve().then(()=>t.addListener(P,x=>{!v&&(!t.accepts||t.accepts(x))&&r(p.destination,p.updated)})));v?await M.remove():y.push(M)});try{return await Promise.all(f),{remove:m}}catch(P){throw await m().catch(()=>{}),P}};var xe=":is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])",rt=`
1
+ "use strict";var WebViewNativeUIEngineRuntime=(()=>{var Ge=Object.defineProperty;var pt=Object.getOwnPropertyDescriptor;var mt=Object.getOwnPropertyNames;var vt=Object.prototype.hasOwnProperty;var ht=(t,i)=>{for(var s in i)Ge(t,s,{get:i[s],enumerable:!0})},yt=(t,i,s,v)=>{if(i&&typeof i=="object"||typeof i=="function")for(let y of mt(i))!vt.call(t,y)&&y!==s&&Ge(t,y,{get:()=>i[y],enumerable:!(v=pt(i,y))||v.enumerable});return t};var gt=t=>yt(Ge({},"__esModule",{value:!0}),t);var Wt={};ht(Wt,{hide:()=>Ct,notify:()=>St,receive:()=>kt,show:()=>Rt,start:()=>At,stop:()=>Ot});var tt=(t,i,s)=>{for(let[v,y]of Object.entries(i)){if(!/^[a-z][a-z0-9-]*$/.test(v))throw new TypeError("Native value keys must be lowercase attribute names");let f=(Object.hasOwn(s,v)?s[v]:void 0)?.property;if(f&&(!/^[a-zA-Z][\w]*$/.test(f)||/^on/i.test(f)||["innerHTML","outerHTML","textContent","innerText","style","constructor","prototype"].includes(f)))throw new TypeError("Native value binding requires a data property");let P=JSON.stringify(y);if(P===void 0)throw new TypeError("Native values must be JSON values");if(f){let p=t;JSON.stringify(p[f])!==P&&(p[f]=y)}else{let p=`data-native-ui-value-${v}`,M=typeof y=="string"?y:P;t.getAttribute(p)!==M&&t.setAttribute(p,M)}}};var wt=Symbol.for("webview-native-ui-engine.projection-placement");var Ue=t=>t?.[wt];var bt=(t,i)=>{if(t.matches("ion-toast, ion-menu"))return!1;let s=t;if(s.showBackdrop===!1)return!1;if(!t.matches("ion-modal"))return!0;let v=i??s.initialBreakpoint;return v===void 0||v>(s.backdropBreakpoint??0)},Et=Symbol.for("@webview-native-ui/engine/overlays/v1"),Pt=t=>{let i=new Set,s=new Set,v=new Set,y=new WeakMap,m=new WeakSet,f=new Map,P=[],p={},M=new Map,S,j=g=>{for(let d of[...v])try{d(g)}catch(w){console.error("Native UI overlay listener failed",w)}},B=(g,d,w)=>{d==="willPresent"||d==="didPresent"?(m.delete(g),i.add(g)):d==="didDismiss"&&(m.add(g),i.delete(g),s.delete(g),y.delete(g));let c=w?.detail?.breakpoint;typeof c=="number"&&c>0&&y.set(g,c),j({element:g,phase:d,event:w})},H=(g=!1)=>{let d=!1;for(let w of i)(!w.isConnected||w.ownerDocument!==t||!P.some(({selector:c})=>w.matches(c)))&&(i.delete(w),s.delete(w),d=!0);for(let w of t.querySelectorAll(P.map(({selector:c})=>c).join(",")))!i.has(w)&&!m.has(w)&&(g||!w.matches("ion-popover"))&&(w.presented||w.classList.contains("show-menu"))&&(i.add(w),d=!0);d&&j({phase:"change"})};return{get active(){return i},subscribe(g){return v.add(g),()=>{v.delete(g)}},setNative(g,d){s.has(g)!==d&&(d?s.add(g):s.delete(g),j({element:g,phase:"change"}))},isExcluded(g){return!!p.exclude&&g.matches(p.exclude)},isNative(g){return s.has(g)},get stack(){let g=[...i].filter(w=>w.isConnected&&w.ownerDocument===t),d=w=>{let c=Number.parseFloat(t.defaultView.getComputedStyle(w).zIndex);return Number.isFinite(c)?c:w.overlayIndex??0};return g.sort((w,c)=>d(w)-d(c)),g},isCovered(g){let d=this.stack,w=d.reduce((c,W,k)=>W.contains(g)?k:c,-1);return d.some((c,W)=>{if(s.has(c)||p.exclude&&c.matches(p.exclude))return!1;let k=p.covers?.(c,g);return k!==void 0?k:W>w&&!c.contains(g)&&(c.matches("ion-menu")||bt(c,y.get(c)))})},enable(g,d){let w={...p,...d},c=[...g,...w.additional??[]];w.exclude&&t.querySelector(w.exclude);let W=new Map;for(let l of c){t.querySelector(l.selector);for(let[V,$]of Object.entries(l.events)){let J=W.get($);if(J&&J.phase!==V)throw new TypeError(`Overlay event "${$}" has conflicting phases`);W.set($,{phase:V,selector:J?`${J.selector}, ${l.selector}`:l.selector})}}p=w,P=c,M=W;for(let l of M.keys()){if(f.has(l))continue;let V=$=>{let J=M.get(l),ge=$.composedPath().find(_=>_ instanceof t.defaultView.HTMLElement&&_.matches(J.selector));ge&&B(ge,J.phase,$)};t.addEventListener(l,V,!0),f.set(l,()=>t.removeEventListener(l,V,!0))}for(let[l,V]of f)M.has(l)||(V(),f.delete(l));let k=!S;S||(S=new MutationObserver(()=>H()),S.observe(t.documentElement,{childList:!0,subtree:!0}),t.addEventListener("nativeUIOverlayAdopted",l=>{let V=l.detail;V?.ownerDocument===t&&B(V,"willPresent",l)})),H(k),(k||d)&&j({phase:"change"})}}},nt=t=>{let i=t;return i[Et]??=Pt(t)};function*De(t){let i=t;for(;i;){let s=i.getRootNode();i=i.assignedSlot??i.parentElement??(s instanceof ShadowRoot?s.host:null),i&&(yield i)}}var Tt=({x:t,y:i,width:s,height:v})=>({x:t,y:i,width:s,height:v}),ot=t=>{let i=t.getBoundingClientRect(),s=getComputedStyle(t),v=parseFloat(s.borderLeftWidth)||0,y=parseFloat(s.borderTopWidth)||0;return{x:i.left+v,y:i.top+y,width:i.width-v-(parseFloat(s.borderRightWidth)||0),height:i.height-y-(parseFloat(s.borderBottomWidth)||0)}},Ke=(t,i,s)=>{let v=[t,...De(t)],y=v.indexOf(i);if(y<=0||i===t.ownerDocument.scrollingElement)return;let m=t.ownerDocument.defaultView;if(m.visualViewport&&(m.visualViewport.scale!==1||m.visualViewport.offsetLeft||m.visualViewport.offsetTop))return;let f=0,P=0,p=m.innerWidth,M=m.innerHeight;for(let[H,g]of v.entries()){let d=getComputedStyle(g);if(d.transform!=="none"&&!new DOMMatrixReadOnly(d.transform).isIdentity||d.zoom&&d.zoom!=="normal"&&Number(d.zoom)!==1||[d.translate,d.rotate,d.scale,d.perspective].some(k=>k&&k!=="none")||d.writingMode&&d.writingMode!=="horizontal-tb"||H<y&&/^(fixed|sticky)$/.test(d.position))return;if(H===0)continue;if(d.scrollbarGutter&&d.scrollbarGutter!=="auto")return;let w=/^(auto|scroll|hidden|clip)$/.test(d.overflowX),c=/^(auto|scroll|hidden|clip)$/.test(d.overflowY);if(H<y&&(w||c))return;let W=g===t.ownerDocument.scrollingElement;if(H>y&&((w||W)&&g.scrollWidth>g.clientWidth||(c||W)&&g.scrollHeight>g.clientHeight)||(w||c)&&[d.borderTopLeftRadius,d.borderTopRightRadius,d.borderBottomLeftRadius,d.borderBottomRightRadius].some(k=>parseFloat(k)>0)||d.clipPath&&d.clipPath!=="none"||d.maskImage&&d.maskImage!=="none")return;if(H>=y){let k=ot(g);w&&(f=Math.max(f,k.x),p=Math.min(p,k.x+k.width)),c&&(P=Math.max(P,k.y),M=Math.min(M,k.y+k.height))}}let S=getComputedStyle(i);if(!/^(auto|scroll)$/.test(S.overflowX)&&!/^(auto|scroll)$/.test(S.overflowY))return;let j=ot(i);if(p<=f||M<=P||!j.width||!j.height)return;let B=Tt(t.getBoundingClientRect());return{id:s(i),offsetX:i.scrollLeft,offsetY:i.scrollTop,viewport:j,clip:{x:f,y:P,width:p-f,height:M-P},contentWidth:i.scrollWidth,contentHeight:i.scrollHeight,frame:{...B,x:B.x-j.x+i.scrollLeft,y:B.y-j.y+i.scrollTop}}};var it=t=>{let i=globalThis;if(i.Capacitor?.convertFileSrc)return i.Capacitor.convertFileSrc(t);if(!t.startsWith("file:"))return t;if(i.WkWebView?.convertFilePath)return i.WkWebView.convertFilePath(t);throw new Error("Native UI file URL requires a host resource handler")};var ye=t=>new Promise((i,s)=>{let v=setTimeout(()=>s(new Error("Native UI bridge timed out")),5e3);t.then(i,s).finally(()=>clearTimeout(v))}),Ye=async t=>{try{return await ye(t)}catch(i){throw t.then(s=>s.remove()).catch(()=>{}),i}};var qe=async(t,i)=>{let s=new Map;for(let P of t.update??[])s.set(P,{updated:!0});for(let P of["native","web","none"])for(let p of t.projection?.[P]??[]){let M=s.get(p)??{updated:!1};if(M.destination&&M.destination!==P)throw new TypeError(`Projection event "${p}" has conflicting destinations`);s.set(p,{...M,destination:P})}let v=!1,y=[],m=async()=>{v=!0;let p=(await Promise.allSettled(y.splice(0).map(M=>Promise.resolve().then(()=>M.remove())))).find(M=>M.status==="rejected");if(p?.status==="rejected")throw p.reason},f=[...s].map(async([P,p])=>{let M=await Ye(Promise.resolve().then(()=>t.addListener(P,S=>{!v&&(!t.accepts||t.accepts(S))&&i(p.destination,p.updated)})));v?await M.remove():y.push(M)});try{return await Promise.all(f),{remove:m}}catch(P){throw await m().catch(()=>{}),P}};var xe=":is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])",rt=`
2
2
  @layer native-ui-projection {
3
3
  ${xe}, ${xe} *, ${xe}::part(native) { visibility: hidden !important; }
4
4
  ${xe}::before, ${xe}::after,
@@ -19,4 +19,4 @@ ${xe} *::before, ${xe} *::after {
19
19
  visibility: hidden !important;
20
20
  }
21
21
  }
22
- `;var Pt=(t,r=!1)=>JSON.stringify(t.map(({control:o})=>[o.id,o.kind,o.placement,r?0:o.x,r?0:o.y,o.width,o.height,o.dark,o.rtl])),Tt=async(t,r,o)=>{let v=o.captureDelayMs??50;if(!Number.isFinite(v)||v<0||v>2147483647)throw new RangeError("captureDelayMs must be a finite, nonnegative timer interval");let y="native",m=t.element.ownerDocument,f=m.defaultView,P=Array.from(f.crypto.getRandomValues(new Uint32Array(4)),e=>e.toString(16)).join("-"),p=e=>e.isConnected&&e.ownerDocument===m,M=e=>!!e?.isConnected&&e.ownerDocument===m,x=new Map,j=new Set,B=e=>{j.delete(e),e.style.setProperty("display","none","important"),e.removeAttribute("data-native-ui-projection-image"),e.removeAttribute("src"),He()},k=r.send.bind(r),g="data-native-ui-snapshot",d=`${t.namespace}:sources-change`,w=new Set,c=new Set,W=new Set,R=new WeakMap,l=0,V=e=>JSON.stringify(e.map(i=>{R.has(i.scope)||R.set(i.scope,++l);let n=o.transient&&!o.opaqueUnderlay?i.scope.element.getBoundingClientRect():void 0,a=n?{...i,control:{...i.control,x:i.control.x-n.x,y:i.control.y-n.y}}:i;return[R.get(i.scope),Pt([a],!!o.opaqueUnderlay)]})),$=new Map,G=e=>{let i=e.getRootNode();return i.nodeType===11&&"host"in i?i:m},ye=e=>{if($.has(e))return;let i=m.createElement("style");i.setAttribute("data-native-ui-snapshot-base",""),i.textContent=rt+(e.nodeType===11?st:""),(e.nodeType===11?e:e.head).append(i),$.set(e,i)},J=e=>{let i=[...e.getAnimations({subtree:!0}),...[...K.get(e)??[]].flatMap(n=>n.shadowRoot?.getAnimations()??[])];for(let n of i)"transitionProperty"in n&&n.transitionProperty==="visibility"&&n.cancel()},K=new Map,ee=e=>{let i=K.get(e)??new Set;K.set(e,i);let n=e.hasAttribute("data-native-ui-snapshot-source")||e.hasAttribute("data-native-ui-snapshot-prepared"),a=T=>{if(!T.hasAttribute("data-native-ui-projection-node")){if(T.shadowRoot){i.add(T),ye(T.shadowRoot);for(let S of T.shadowRoot.children)a(S)}for(let S of T.children)a(S)}};a(e);for(let T of i)T.toggleAttribute("data-native-ui-snapshot-content",n);J(e)},pe=()=>`${f.innerWidth}:${f.innerHeight}:${f.devicePixelRatio}`,re=!1,le=!1,te=0,de=0,L,me,b,H=!1,D=!1,Pe=0,Te=0,oe=0,ue=0,I=0,fe=-1,A=!1,Fe=!1,Y,He=()=>{let e=new Set(Y?.candidates.map(n=>n.scope.element));for(let[n,a]of x){let T=!A&&n.isConnected&&e.has(n);if(T&&(b||D||re))continue;let S=T?2*Math.max(1,L?.scopes.filter(O=>O.element===n).length??0):0,C=Math.max(0,S-a.filter(O=>j.has(O)).length),U=a.filter(O=>j.has(O)||C-- >0?!0:(O.remove(),!1));U.length?x.set(n,U):x.delete(n)}let i=new Set([...Y?.candidates.map(n=>n.scope.element.getRootNode())??[],...b?.scopes.map(n=>n.element.getRootNode())??[],...L?.scopes.map(n=>n.element.getRootNode())??[],...[...j].map(n=>n.getRootNode()),...[...K.values()].flatMap(n=>[...n].map(a=>a.shadowRoot))]);for(let[n,a]of $)n.nodeType===11&&!i.has(n)&&(a.remove(),$.delete(n))},ge=new Set,Ve=(e,i,n)=>{let a=()=>{ge.delete(a)&&(k({action:e,token:i}).catch(()=>{}),e==="restored"&&He())};ge.add(a),n&&!A?f.requestAnimationFrame(()=>f.requestAnimationFrame(a)):a()},q=new Set,ke=()=>{q.forEach(e=>W.delete(e)),q.clear()},Le=(e,i=!1)=>{!e||e===L||e===b||(e.images.forEach(n=>{B(n)}),Ve("release",e.data.token,i))},se=e=>e.filter(i=>{if(!M(i.scope.element)||![i.element,...i.sources??[]].every(p))return!1;if(i.control.scroll){let n=i.element.getBoundingClientRect(),a=i.control.scroll.clip;if(n.right<=a.x||n.bottom<=a.y||n.left>=a.x+a.width||n.top>=a.y+a.height)return!1}return o.eligible?.(i)??!0}),ze=async()=>{if(ue=0,A||y!=="native"||Fe||H||b||o.blocked()||!Y||L?.revision===Y.revision&&fe===I)return;let{revision:e,candidates:i}=Y,n=oe,a=I,T=pe(),S=se(i);if(!S.length)return;Fe=!0;let C,U=!1,O=[];try{let ae=!1,ce=k({action:"prepare",revision:o.captureRevision?.()??e,viewportWidth:f.innerWidth,ids:S.map(E=>E.control.id)});ce.then(E=>{ae&&E.token&&k({action:"release",token:E.token}).catch(()=>{})}).catch(()=>{});try{C=await he(ce)}finally{ae=!0}if(!C.token||!C.parts?.length)return;let We=new Set(S.map(E=>E.control.id));if(C.parts.some(E=>!E.url.startsWith("file:")&&!/^\/__native_ui_engine__\/nativeUI[a-fA-F0-9]+\/[a-fA-F0-9-]+-[0-9]+\.png$/.test(E.url)||!E.ids.length||E.ids.some(_=>!We.has(_))||![E.x,E.y,E.width,E.height].every(Number.isFinite)||E.width<=0||E.height<=0)||new Set(C.parts.flatMap(E=>E.ids)).size!==We.size||C.parts.some(E=>new Set(E.ids.map(_=>S.find(Se=>Se.control.id===_).scope)).size!==1)||(await he(Promise.all(C.parts.map(async E=>{let _=S.find(_e=>E.ids.includes(_e.control.id)).scope.element,Se=x.get(_);Se||(Se=[],x.set(_,Se));let ve=Se.find(_e=>!j.has(_e));return ve||(ve=m.createElement("img"),ve.setAttribute("data-native-ui-projection-node",""),Se.push(ve)),j.add(ve),O.push(ve),ve.src=it(E.url),await ve.decode(),ve}))),A||y!=="native"||H||b||o.blocked()||oe!==n||pe()!==T||Y?.revision!==e||S.some(E=>!M(E.scope.element)||![E.element,...E.sources??[]].every(p))))return;let De=L,et={data:C,images:O,sources:Array.from(new Set(S.flatMap(E=>[E.element,...E.sources??[]]))),targets:C.parts.map(E=>S.find(_=>E.ids.includes(_.control.id)).element),scopes:C.parts.map(E=>S.find(_=>E.ids.includes(_.control.id)).scope),parents:C.parts.map(E=>S.find(_=>E.ids.includes(_.control.id)).scope.element.parentNode),frames:[],viewport:T,revision:e,layout:V(S)};try{if(!Q(et))throw new Error("Projection destination is unavailable")}catch(E){Ae();try{Q(),L?.sources.forEach(_=>{W.add(_),q.add(_)})}catch{ne()}throw E}L=et,ke(),L.sources.forEach(E=>{W.add(E),q.add(E)}),o.opaqueUnderlay&&k({action:"prepared",token:C.token}).catch(()=>{}),f.dispatchEvent(new Event(d)),fe=a,U=!0,Le(De)}catch{}finally{U||O.forEach(ae=>{B(ae)}),C?.token&&!U&&k({action:"release",token:C.token}).catch(()=>{}),Fe=!1,Y&&(Y.revision!==e||I!==a)&&ie()}},ie=()=>{f.clearTimeout(ue),!A&&y==="native"&&!b&&!H&&!o.blocked()&&(ue=f.setTimeout(()=>{ze()},v))},Re,Je=(e,i)=>{if(i.some(a=>a.scope.element.ownerDocument!==m||[a.element,...a.sources??[]].some(T=>T.ownerDocument!==m)))throw new TypeError("All projection sources and fallback scopes must share one Document");if(o.blocked())return;!b&&L&&L.layout!==V(se(i))&&je();let n=V(i);Re!==n&&(Re=n,I++,oe++),Y?.revision!==e&&oe++,Y={revision:e,candidates:i},He(),(L?.revision!==e||fe!==I)&&ie()},Me=e=>{!b&&L&&e&&Y?.candidates.some(i=>i.scrollElement===e&&L.sources.includes(i.element))&&je(),b&&z(),I++,oe++,ie()},Ce=await Xe(r.listen(({active:e})=>{H=e,oe++,f.clearTimeout(ue),e||ie()})),Oe=new Set,we=e=>{K.get(e)?.forEach(i=>i.removeAttribute("data-native-ui-snapshot-content")),Oe.delete(e)&&e.removeAttribute("data-native-ui-snapshot-prepared"),e.removeAttribute("data-native-ui-snapshot-visible"),e.removeAttribute(g),e.removeAttribute("data-native-ui-snapshot-positioned"),e.removeAttribute("data-native-ui-snapshot-source"),J(e),K.delete(e)},Ae=()=>{de++,f.cancelAnimationFrame(te);for(let e of Z.keys())e.style.setProperty("opacity","0","important");Z.clear(),te=0,re=!1,le=!1,ke(),b?.sources.forEach(e=>{w.delete(e),c.delete(e)}),h.forEach(we),h.clear(),$.forEach((e,i)=>{(A||i.nodeType===11&&!i.host.isConnected)&&(e.remove(),$.delete(i))})},ne=(e=b,i=D)=>{if(b!==e)return;let n=new Set([b,L]);Ae(),b=void 0,L=void 0,D=!1,f.clearTimeout(Pe),n.forEach(a=>Le(a,i)),o.wake()},$e=()=>{if(!b)return;if(L!==b){ne();return}let e=b;de++,f.cancelAnimationFrame(te),te=0,re=le=!1,e.sources.forEach(i=>{w.delete(i),c.delete(i)}),h.forEach(i=>i.removeAttribute("data-native-ui-snapshot-source")),N(e,!!o.opaqueUnderlay),b=void 0,D=!1,f.clearTimeout(Pe),Ve("restored",e.data.token,!0),e.sources.forEach(i=>{W.add(i),q.add(i)}),o.wake()},s=()=>{D&&$e(),ge.forEach(e=>e())},u=e=>e.scopes.map(i=>M(i.element)?i.element:void 0),h=new Set,N=(e,i)=>{for(let n of h)n.toggleAttribute("data-native-ui-snapshot-visible",i),ee(n);e.images.forEach(n=>n.style.setProperty("opacity",i?"1":"0","important"))},F=new Map,Z=new Map,z=()=>{let e=[];for(let[i,n]of Z){let{target:a,frame:T}=n;if(!a.isConnected){i.remove();continue}let S=a.shadowRoot??a;i.parentNode!==S&&S.insertBefore(i,null);let C=a.getBoundingClientRect(),U=i.getBoundingClientRect(),O=parseFloat(i.style.width)||T.width,ae=parseFloat(i.style.height)||T.height,ce=U.width/O||1,We=U.height/ae||1,De=n.scale??(n.scale={x:ce,y:We});e.push({element:i,left:(parseFloat(i.style.left)||0)+(C.x+T.x*ce/De.x-U.x)/ce,top:(parseFloat(i.style.top)||0)+(C.y+T.y*We/De.y-U.y)/We,width:T.width/De.x,height:T.height/De.y})}for(let{element:i,...n}of e)for(let[a,T]of Object.entries(n)){let S=`${T}px`;i.style.getPropertyValue(a)!==S&&i.style.setProperty(a,S,"important")}},Q=(e=b??L)=>{if(!e||D)return!1;if(!e.sources.every(p)||u(e).some(n=>!n))return je(),!1;let i=u(e);if(b){if(i.some(a=>!a?.isConnected||!$.has(G(a))))return ne(b,!1),!1;let n=new Set;for(let[a,T]of e.targets.entries()){let S=F.get(T),C=i[a];!S||!C?.isConnected||(C.setAttribute(g,S),C.setAttribute("data-native-ui-snapshot-source",""),n.add(C))}for(let a of h)n.has(a)||we(a);return h.clear(),n.forEach(a=>h.add(a)),N(e,le),!0}x.forEach(n=>n.forEach(a=>{e.images.includes(a)||(a.style.setProperty("display","none","important"),a.removeAttribute("data-native-ui-projection-image"))})),F.clear(),Z.clear(),h.forEach(we),h.clear();for(let n of new Set(i)){if(!n)continue;h.add(n),ye(G(n));let a=i.flatMap((U,O)=>U===n?[O]:[]),T=`${P}-${a[0]}`;n.setAttribute(g,T),n!==e.targets[a[0]]&&(n.setAttribute("data-native-ui-snapshot-prepared",""),Oe.add(n)),n.toggleAttribute("data-native-ui-snapshot-visible",!!o.opaqueUnderlay),ee(n);let S=n.getBoundingClientRect();n.toggleAttribute("data-native-ui-snapshot-positioned",f.getComputedStyle(n).position==="static");let C=n.shadowRoot??n;for(let U of[...a].reverse()){let O=e.images[U],ae=e.data.parts[U],ce=e.frames[U]??{x:o.opaqueUnderlay?0:ae.x-S.x,y:o.opaqueUnderlay?0:ae.y-S.y,width:ae.width,height:ae.height};e.frames[U]=ce,F.set(e.targets[U],T),O.inert=!0,O.setAttribute("aria-hidden","true"),O.setAttribute("data-native-ui-projection-image",T),O.style.cssText="all:initial!important;position:absolute!important;display:block!important;visibility:visible!important;pointer-events:none!important;image-rendering:pixelated!important;will-change:opacity!important;opacity:0!important;",O.style.setProperty("width",`${ce.width}px`,"important"),O.style.setProperty("height",`${ce.height}px`,"important"),O.style.setProperty("left","0px","important"),O.style.setProperty("top","0px","important"),O.style.setProperty("opacity",o.opaqueUnderlay?"1":"0","important"),O.parentNode!==C&&C.insertBefore(O,null),Z.set(O,{target:n,frame:ce})}}return e.parents=e.scopes.map(n=>n.element.parentNode),z(),!0},X=e=>e.images.map((i,n)=>{let a=i.getBoundingClientRect();return{...e.data.parts[n],x:a.x,y:a.y,width:a.width,height:a.height}}),Ie=()=>{if(s(),A||y==="none"||b)return;let e=L;if(!e||e.viewport!==pe()||!e.sources.every(p)||o.current&&!o.current(e.targets)||e.targets.some(a=>!F.has(a))||u(e).some(a=>!a?.isConnected)||[...h].some(a=>!a.isConnected))return;e.scopes.some((a,T)=>a.element.parentNode!==e.parents[T])&&Q(),z();let i=++de,n=()=>de===i;re=!0,le=!!o.opaqueUnderlay,b=e,oe++,f.clearTimeout(ue),e.sources.forEach(a=>{w.add(a)}),h.forEach(a=>a.setAttribute("data-native-ui-snapshot-source","")),N(e,le),te=f.requestAnimationFrame(()=>{te=0,n()&&(re=!1,o.wake())}),he(k({action:"cover",token:e.data.token,...o.transient?{transient:!0,viewportWidth:f.innerWidth,parts:X(e)}:{}})).then(a=>{if(n()){if(re=!1,a?.shown!==!0){ne(e,!1);return}D||(le=!0,e.sources.forEach(T=>c.add(T)),Q(),z()),f.cancelAnimationFrame(te),te=f.requestAnimationFrame(()=>{te=0,n()&&k({action:"painted",token:e.data.token}).catch(()=>{n()&&ne(e,!1)})}),o.wake()}}).catch(()=>{n()&&ne(e,!1)})},Ze=()=>{if(!(!b||A||D)){if(!b.sources.every(p)||u(b).some(e=>!e)||o.current&&!o.current(b.targets)){ne(b,!1);return}if(!o.blocked()){if(z(),o.transient){let e=b;if(u(e).some(a=>!a?.isConnected)){ne(e,!1);return}let n=X(e);o.opaqueUnderlay||n.every((a,T)=>a.width===e.data.parts[T].width&&a.height===e.data.parts[T].height)?e.data={...e.data,parts:n}:L=void 0,k({action:"restore",token:e.data.token,viewportWidth:f.innerWidth,parts:n}).catch(()=>{})}Te=o.revision?.()??Y?.revision??0,D=!0,b.sources.forEach(e=>w.delete(e)),Pe=f.setTimeout(()=>{D&&ne(b,!1)},250),o.wake()}}},dt=(e,i,n)=>{!D||!b||n===void 0||n<=Te||!o.transient&&e||b.data.parts.every(a=>a.ids.every(T=>i?.includes(T)))&&($e(),ie())},je=(e=!1)=>{ge.forEach(n=>n()),oe++,f.clearTimeout(ue);let i=L;if(L=void 0,(e||A)&&(me=void 0),o.retainPreparedUntilRelease&&!b&&!e&&!A){if(i){let n={};me=n,he(k({action:"release",token:i.data.token})).catch(()=>{}).finally(()=>{if(i.images.forEach(a=>{B(a)}),me===n&&(me=void 0,!(L||b))){for(let a of h)a.getAttribute(g)!==F.get(a)&&(h.delete(a),Oe.delete(a));Ae(),F.clear(),f.dispatchEvent(new Event(d))}})}if(me)return}Le(i),b?ne(b,!1):(Ae(),F.clear(),f.dispatchEvent(new Event(d)))},Qe;try{o.events&&(Qe=await Ye(o.events,(e,i)=>{if(!A){if(e&&(y=e),y==="none"){je();return}e==="web"&&Ie(),e==="native"&&(Ze(),ie()),i&&Me()}}))}catch(e){throw A=!0,je(),await he(Promise.resolve(Ce.remove())).catch(()=>{}),e}return{get owners(){return[...w]},get visibleOwners(){return[...c]},get preparedOwners(){return[...W]},consider:Je,changed:Me,moved:z,get token(){return D?void 0:b?.data.token},begin:Ie,get restoring(){return D},get handoffPending(){return re},retireRestoration:s,end:Ze,updated:dt,invalidate:je,async destroy(){A=!0,je(),x.forEach(e=>e.forEach(i=>i.remove())),x.clear(),await Promise.allSettled([he(Promise.resolve().then(()=>Ce?.remove())),he(Promise.resolve().then(()=>Qe?.remove()))])}}},at=async(t,r,o)=>{let v=await Tt(t,r,o),y=v.consider;return Object.assign(v,{consider:(m,f)=>y(m,f.map(P=>({...P,scope:t})))})};var ct=(t,r)=>{if(t.nodeType!==1||t.namespaceURI!=="http://www.w3.org/1999/xhtml"||!t.ownerDocument||!("style"in t))throw new TypeError("A Web projection scope requires an HTMLElement fallback");if(t.matches("img, input, textarea, select, video, audio, canvas, iframe, embed, object, br, hr, area, base, link, meta, source, track, wbr"))throw new TypeError("A Web projection fallback must be a container element; use the existing fallback container");return Object.freeze({element:t,namespace:r})};var lt=async(t,r,o={addListener:async(v,y)=>globalThis.Capacitor.addListener(t.plugin,v,y)})=>{let v="view",y=t.receiveListeners,m=!1,f=!1,P="native",p="native",M=nt(document),x=()=>P==="native"&&l&&M.isCovered(l)?"web":P,j=!1,B=!1,k=!1,g=!1,d,w,c,W=!1,R=()=>{let s=W;W=!0;try{c?.begin()}finally{W=s}},l,V=0,$,G=()=>{V++,$?.(),$=void 0},ye=async()=>{if(!t.values||!l||m)return;let s=l;G();let u=V,h=()=>!m&&u===V&&l===s&&fe()===s&&s.isConnected;if(s.localName.includes("-")&&!s.matches(":defined")&&Object.values(t.values).some(F=>F.property)){if(await Promise.race([s.ownerDocument.defaultView.customElements.whenDefined(s.localName),new Promise(F=>{$=F})]),!h())return;$=void 0}let N=await r.send({action:"values"}).catch(()=>{});if(h()&&N?.values&&typeof N.values=="object"&&!Array.isArray(N.values))try{tt(s,N.values,t.values)}catch(F){console.warn("[Native UI Engine] Cannot reflect values",F)}},J={},K,ee=0,pe=new Set,re=()=>{},le=new Promise(s=>{re=s}),te=()=>{pe.clear(),re(),le=new Promise(s=>{re=s})},de=()=>{ee++,K=void 0;let s=Ue(l);s?.owner===J&&(s.projected=!1,s.owner=void 0)},L={listen:s=>r.listen(u=>{s(u),!u.active&&B&&(B=!1,Re()),u.precovered&&Je(u.precovered)}),send:s=>{if(!t.placementHandoff)return r.send(s);if(s.action==="visibility"&&!s.visible&&te(),s.action==="visibility"&&s.visible&&pe.size){let z=H,Q=ee,X=l,Ie=le;return Promise.race([Promise.allSettled([...pe]),Ie]).then(()=>m||Ie!==le||z!==H||Q!==ee||X!==l||!X?.isConnected||fe()!==l||x()!=="native"?{}:L.send(s))}let u=Ue(l);if(s.action==="cover"){let z=K===s.token&&u?.owner===J&&u.projected,Q=++ee;return K=s.token,u&&(u.owner=J,u.projected=!!z),r.send(s).then(X=>(Q===ee&&u===Ue(l)&&u?.owner===J&&(u.projected=X?.shown===!0),X)).catch(X=>{throw Q===ee&&u?.owner===J&&(de(),u.invalidate()),X})}let h=s.action==="release"&&s.token===K,N=K&&(s.action==="restore"||h||s.action==="reset"||s.action==="visibility"&&s.visible);N&&ee++;let F=ee,Z=()=>{F!==ee||u?.owner!==J||(de(),u.invalidate())};try{let z=N&&u?.owner===J?{...s,token:"token"in s?s.token:K,placement:u.measure()}:s,Q=r.send(z).then(async X=>(s.action==="release"&&X?.requiresPlacement&&(u&&(!u.owner||u.owner===J)?X=await r.send({...s,placement:u.measure()}):(await r.send({action:"visibility",visible:!1}),X=await r.send(s))),F===ee&&(s.action==="resume"||h||s.action==="reset"||s.action==="visibility")&&de(),X)).catch(X=>{throw Z(),X});return s.action!=="release"||!t.nativeDragHandoff?Q:(pe.add(Q),Q.finally(()=>pe.delete(Q)))}catch(z){return Z(),Promise.reject(z)}}},me=0,b=0,H=0,D,Pe=!1,Te=!1,oe=!1,ue=document.createElement("style"),I=`data-native-ui-session-${t.id.replace(/[^a-zA-Z0-9-]/g,"")}`;ue.textContent=`[${I}="native"], [${I}="native"] *, [${I}="none"], [${I}="none"] * {visibility:hidden!important}`,document.documentElement.append(ue);let fe=()=>{let s=document.querySelectorAll(t.projectionSelector);return s.length===1&&s[0]instanceof HTMLElement?s[0]:void 0},A=()=>{let s=x();l&&l.getAttribute("data-native-ui-destination")!==s&&l.setAttribute("data-native-ui-destination",s),l&&(p==="web"||p==="native"&&k?l.removeAttribute(I):l.setAttribute(I,p))},Fe=()=>{if(!l?.isConnected)return[];let s=l.getBoundingClientRect(),u={x:s.x,y:s.y,width:s.width,height:s.height},h=d&&Ke(l,d,()=>t.id);return[{element:l,scrollElement:d,control:{id:v,...u,...h?{scroll:h}:{}}}]},Y=async()=>{if(m)return;let s=H,u=t.failedFallback==="native"&&x()==="native"&&!!l?.isConnected;await L.send({action:"visibility",visible:u}).catch(()=>{}),!(m||s!==H)&&(u||p==="none"?A():l?.removeAttribute(I))},He=()=>{if(!j||p!=="native"||!l?.isConnected||c?.token||c?.restoring)return;let s=t.failedFallback==="native";L.send({action:"visibility",visible:s}).catch(()=>{}),s?A():l.removeAttribute(I)},ge=()=>{if(m||W)return;if(He(),p==="web"&&c&&!c.token&&!c.restoring){L.send({action:"visibility",visible:!1}).catch(()=>{}),l?.removeAttribute(I);return}if(!c?.restoring||D===c||p!=="native")return;D=c;let s=H,u=c;L.send({action:"resume"}).then(()=>{m||s!==H||u!==c||fe()!==l||(k=!1,u.updated(void 0,[v],++me),A(),q())}).catch(()=>{s!==H||m||(u.invalidate(),Y())}).finally(()=>{D===u&&(D=void 0),ge()})},Ve=async()=>{if(m||Pe)return;if(fe()!==l){H++,j=!1,B=!1,clearTimeout(w),de(),l?.removeAttribute(I),l?.removeAttribute("data-native-ui-destination"),G();let u=c,h=l;c=void 0,l=void 0,Le.disconnect(),Me();try{h&&await L.send({action:"visibility",visible:!1})}finally{await u?.destroy()}if(m)return;l=fe(),k=!1,l&&(Le.observe(l),ye())}if(Me(),!j){if(c?.restoring){ge();return}if(p=x(),!l){await L.send({action:"visibility",visible:!1});return}if(!c){Pe=!0;let u=H;try{let h=await at(ct(l,t.id),L,{wake:ge,captureDelayMs:t.captureDelayMs,retainPreparedUntilRelease:t.nativeDragHandoff,blocked:()=>p!=="native"||j||k&&!c?.token,revision:()=>me,captureRevision:t.captureMode==="view"?()=>b:void 0,transient:!0,opaqueUnderlay:t.captureMode==="view"});if(m||u!==H){await h.destroy();return}c=h}finally{Pe=!1}}if(!m){if(p==="native"&&k){await L.send({action:"visibility",visible:!1}),A();return}if(p==="native"){let u=H;if(await L.send({action:"visibility",visible:!0}),m||u!==H||p!=="native"||j)return;A(),d=[...Ne(l)].find(Z=>{let z=getComputedStyle(Z);return/auto|scroll/.test(z.overflowX+z.overflowY)&&(Z.scrollHeight>Z.clientHeight||Z.scrollWidth>Z.clientWidth)});let h=d&&Ke(l,d,()=>t.id),N=t.nativeDragHandoff&&t.captureMode==="view"&&d?{viewport:d.getBoundingClientRect(),offsetX:d.scrollLeft,offsetY:d.scrollTop,rangeX:Math.max(0,d.scrollWidth-d.clientWidth),rangeY:Math.max(0,d.scrollHeight-d.clientHeight)}:void 0,F=await L.send({action:"track",viewportWidth:innerWidth,...h?{scroll:h}:{},...N?{gesture:N}:{}});if(m||u!==H)return;g=F?.tracked===!0,c.consider(t.captureMode==="view"?b:++me,Fe())}else await L.send({action:"visibility",visible:!1}),A()}}},q=()=>{m||(Te=!0,!(!f||oe)&&(oe=!0,Promise.resolve().then(async()=>{try{for(;Te&&!m;)Te=!1,await Ve()}catch{await Y()}finally{oe=!1,Te&&q()}})))},ke=()=>{c?.invalidate(),q()},Le=new ResizeObserver(ke);window.addEventListener("resize",ke);let se=new Set,ze=new WeakSet,ie=s=>{let u=s.composedPath()[0]??s.target;if(!(m||p!=="native"||!l||ze.has(s)||u!==document&&![...Ne(l)].includes(u))){if(ze.add(s),g&&u===d){c?.changed(u);return}j?t.captureMode==="view"?c?.moved():c?.changed():((D||c?.restoring)&&H++,j=!0,R(),k=t.failedFallback!=="native",c?.token?l.removeAttribute(I):He()),Re()}};function Re(){clearTimeout(w),w=setTimeout(()=>{B||(j=!1,!(m||p!=="native")&&(c?.end(),c?.restoring||q()))},100)}function Je(s){if(m||p!=="native"||!l?.isConnected||!c||(B=!0,c.token))return;(D||c.restoring)&&H++,K=s;let u=Ue(l);if(u&&(u.owner=J,u.projected=!0),j=!0,R(),!c.token){j=!1,Y();return}Re()}let Me=()=>{let s=new Set(l?Ne(l):[]),u=s.size!==se.size||[...s].some(h=>!se.has(h));for(let h of se)s.has(h)||(h.removeEventListener("scroll",ie),h.removeEventListener("slotchange",Ce),se.delete(h));for(let h of s)se.has(h)||(h.addEventListener("scroll",ie,{passive:!0}),h.addEventListener("slotchange",Ce),se.add(h));u&&(g=!1,d=void 0,c?.invalidate(),q())},Ce=()=>{Me(),q()};document.addEventListener("scroll",ie,!0);let Oe=new MutationObserver(s=>{Me(),fe()!==l?q():l&&s.some(u=>u.type==="childList")&&we(x())});Oe.observe(document.documentElement,{childList:!0,attributes:!0,subtree:!0});let we=(s,u=!1)=>{if(m||!u&&s===p)return;j=!1,B=!1,clearTimeout(w),p=s,H++;let h=H;p==="none"?L.send({action:"visibility",visible:!1}).then(()=>{p!=="none"||h!==H||m||(c?.invalidate(!0),A())}).catch(()=>{p!=="none"||h!==H||m||(c?.invalidate(!0),A())}):p==="web"?(R(),c?.token||L.send({action:"visibility",visible:!1}).catch(()=>{}),A()):p==="native"&&(c?.end(),c?.restoring||q())},Ae=M.subscribe(()=>queueMicrotask(()=>we(x()))),ne,$e=async()=>{if(!m){m=!0,te(),G(),de(),H++,Ae(),Oe.disconnect(),Le.disconnect(),window.removeEventListener("resize",ke),document.removeEventListener("scroll",ie,!0);for(let s of se)s.removeEventListener("scroll",ie),s.removeEventListener("slotchange",Ce);se.clear(),clearTimeout(w),l?.removeAttribute(I),l?.removeAttribute("data-native-ui-destination"),ue.remove(),await Promise.allSettled([Promise.resolve(ne?.remove()),c?.destroy()])}};try{let s=H;ne=await Ye({addListener:(h,N)=>o.addListener(h,N),update:y?.update,projection:y?.projection,accepts:h=>!y?.match||Object.entries(y?.match).every(([N,F])=>typeof h=="object"&&h!==null&&h[N]===F)},(h,N)=>{m||(h&&(h==="native"&&(k=!1),P=h,L.send({action:"destination",destination:h}).catch(()=>{}),we(x(),!0)),N&&(ye(),b++,c?.changed(),p==="native"&&q()))});let u=await L.send({action:"connect"});return H===s&&["native","web","none"].includes(u?.destination)&&(P=u.destination),p=x(),f=!0,q(),{destroy:$e,show:()=>{k=!1,P="native",we(x(),!0)},hide:()=>{P=p="none",H++,te(),j=!1,B=!1,clearTimeout(w),c?.invalidate(!0),A()}}}catch(s){throw await $e(),s}};var be=new Map,Ee=new Map,Be=new Map,Lt=Array.from(crypto.getRandomValues(new Uint32Array(4)),t=>t.toString(16)).join("-"),qe=new Map,Mt=(t,r,o)=>qe.get(t)?.get(r)?.(o),jt=0,St=(t,r)=>new Promise((o,v)=>{let y=`${t}:${Lt}:${++jt}`,m=setTimeout(()=>{Ee.delete(y),v(new Error("Native UI session timed out"))},5e3);Ee.set(y,{resolve:o,reject:v,timer:m});let f=JSON.stringify({key:y,request:r}),P=globalThis,p=P.webkit?.messageHandlers?.[t]??P[t]??P.nativeUIEngine;if(!p){clearTimeout(m),Ee.delete(y),v(new Error("Native UI session unavailable"));return}try{p.postMessage(f)}catch(M){clearTimeout(m),Ee.delete(y),v(M)}}),xt=(t,r,o,v)=>{if(r===null){Be.get(t)?.(o);return}let y=Ee.get(r);!y||!r.startsWith(`${t}:`)||(Ee.delete(r),clearTimeout(y.timer),v?y.reject(new Error(v)):y.resolve(o))},Ht=t=>{be.get(t)?.show()},kt=t=>(be.get(t)?.hide(),!0),Rt=async t=>{let r=be.get(t);be.delete(t),qe.delete(t),Be.delete(t);for(let[o,v]of Ee)o.startsWith(`${t}:`)&&(Ee.delete(o),clearTimeout(v.timer),v.reject(new Error("Native UI session closed")));await r?.destroy()},Ct=async t=>{if(be.has(t.id))return;let r,o=!1,v,y={show:()=>{v="native",r?.show()},hide:()=>{v="none",r?.hide()},destroy:async()=>{o=!0,await r?.destroy()}};be.set(t.id,y);try{if(document.readyState==="loading"&&await new Promise(f=>document.addEventListener("DOMContentLoaded",()=>f(),{once:!0})),o)return;let m=new Map;t.runtime==="cordova"&&qe.set(t.id,m),r=await lt(t,{send:f=>St(t.id,f),listen:async f=>(Be.set(t.id,f),{remove:()=>{Be.get(t.id)===f&&Be.delete(t.id)}})},t.runtime==="cordova"?{addListener:async(f,P)=>(m.set(f,P),{remove:()=>{m.delete(f)}})}:void 0),v==="none"?r.hide():v==="native"&&r.show(),o&&await r.destroy()}catch(m){throw be.get(t.id)===y&&(be.delete(t.id),qe.delete(t.id)),m}};return ht(Ot);})();
22
+ `;var Lt=({control:t},i=!1)=>[t.id,t.kind,t.placement,i?0:t.x,i?0:t.y,t.width,t.height,t.dark,t.rtl],Mt=async(t,i,s)=>{let v=s.captureDelayMs??50;if(!Number.isFinite(v)||v<0||v>2147483647)throw new RangeError("captureDelayMs must be a finite, nonnegative timer interval");let y="native",m=t.element.ownerDocument,f=m.defaultView,P=Array.from(f.crypto.getRandomValues(new Uint32Array(4)),e=>e.toString(16)).join("-"),p=e=>e.isConnected&&e.ownerDocument===m,M=e=>!!e?.isConnected&&e.ownerDocument===m,S=new Map,j=new Set,B=e=>{j.delete(e),e.style.setProperty("display","none","important"),e.removeAttribute("data-native-ui-projection-image"),e.removeAttribute("src"),He()},H=i.send.bind(i),g="data-native-ui-snapshot",d=`${t.namespace}:sources-change`,w=new Set,c=new Set,W=new Set,k=new WeakMap,l=0,V=e=>JSON.stringify(e.map(o=>{k.has(o.scope)||k.set(o.scope,++l);let n=s.transient&&!s.opaqueUnderlay?o.scope.element.getBoundingClientRect():void 0,a=n?{...o,control:{...o.control,x:o.control.x-n.x,y:o.control.y-n.y}}:o;return[k.get(o.scope),Lt(a,!!s.opaqueUnderlay)]})),$=new Map,J=e=>{let o=e.getRootNode();return o.nodeType===11&&"host"in o?o:m},ge=e=>{if($.has(e))return;let o=m.createElement("style");o.setAttribute("data-native-ui-snapshot-base",""),o.textContent=rt+(e.nodeType===11?st:""),(e.nodeType===11?e:e.head).append(o),$.set(e,o)},_=e=>{let o=[...e.getAnimations({subtree:!0}),...[...G.get(e)??[]].flatMap(n=>n.shadowRoot?.getAnimations()??[])];for(let n of o)"transitionProperty"in n&&n.transitionProperty==="visibility"&&n.cancel()},G=new Map,Q=e=>{let o=G.get(e)??new Set;G.set(e,o);let n=e.hasAttribute("data-native-ui-snapshot-source")||e.hasAttribute("data-native-ui-snapshot-prepared"),a=T=>{if(!T.hasAttribute("data-native-ui-projection-node")){if(T.shadowRoot){o.add(T),ge(T.shadowRoot);for(let C of T.shadowRoot.children)a(C)}for(let C of T.children)a(C)}};a(e);for(let T of o)T.toggleAttribute("data-native-ui-snapshot-content",n);_(e)},pe=()=>`${f.innerWidth}:${f.innerHeight}:${f.devicePixelRatio}`,ie=!1,ce=!1,ee=0,le=0,L,me,E,x=!1,D=!1,Te=0,Le=0,ne=0,de=0,I=0,ue=-1,A=!1,Ne=!1,Y,He=()=>{let e=new Set(Y?.candidates.map(n=>n.scope.element));for(let[n,a]of S){let T=!A&&n.isConnected&&e.has(n);if(T&&(E||D||ie))continue;let C=T?2*Math.max(1,L?.scopes.filter(R=>R.element===n).length??0):0,O=Math.max(0,C-a.filter(R=>j.has(R)).length),U=a.filter(R=>j.has(R)||O-- >0?!0:(R.remove(),!1));U.length?S.set(n,U):S.delete(n)}let o=new Set([...Y?.candidates.map(n=>n.scope.element.getRootNode())??[],...E?.scopes.map(n=>n.element.getRootNode())??[],...L?.scopes.map(n=>n.element.getRootNode())??[],...[...j].map(n=>n.getRootNode()),...[...G.values()].flatMap(n=>[...n].map(a=>a.shadowRoot))]);for(let[n,a]of $)n.nodeType===11&&!o.has(n)&&(a.remove(),$.delete(n))},we=new Set,Ve=(e,o,n)=>{let a=()=>{we.delete(a)&&(H({action:e,token:o}).catch(()=>{}),e==="restored"&&He())};we.add(a),n&&!A?f.requestAnimationFrame(()=>f.requestAnimationFrame(a)):a()},q=new Set,ke=()=>{q.forEach(e=>W.delete(e)),q.clear()},Me=(e,o=!1)=>{!e||e===L||e===E||(e.images.forEach(n=>{B(n)}),Ve("release",e.data.token,o))},re=e=>e.filter(o=>{if(!M(o.scope.element)||![o.element,...o.sources??[]].every(p))return!1;if(o.control.scroll){let n=o.element.getBoundingClientRect(),a=o.control.scroll.clip;if(n.right<=a.x||n.bottom<=a.y||n.left>=a.x+a.width||n.top>=a.y+a.height)return!1}return s.eligible?.(o)??!0}),ze=async()=>{if(de=0,A||y!=="native"||Ne||x||E||s.blocked()||!Y||L?.revision===Y.revision&&ue===I)return;let{revision:e,candidates:o}=Y,n=ne,a=I,T=pe(),C=re(o);if(!C.length)return;Ne=!0;let O,U=!1,R=[];try{let se=!1,ae=H({action:"prepare",revision:s.captureRevision?.()??e,viewportWidth:f.innerWidth,ids:C.map(b=>b.control.id)});ae.then(b=>{se&&b.token&&H({action:"release",token:b.token}).catch(()=>{})}).catch(()=>{});try{O=await ye(ae)}finally{se=!0}if(!O.token||!O.parts?.length)return;let We=new Set(C.map(b=>b.control.id));if(O.parts.some(b=>!b.url.startsWith("file:")&&!/^\/__native_ui_engine__\/nativeUI[a-fA-F0-9]+\/[a-fA-F0-9-]+-[0-9]+\.png$/.test(b.url)||!b.ids.length||b.ids.some(fe=>!We.has(fe))||![b.x,b.y,b.width,b.height].every(Number.isFinite)||b.width<=0||b.height<=0)||new Set(O.parts.flatMap(b=>b.ids)).size!==We.size||O.parts.some(b=>new Set(b.ids.map(fe=>C.find(Xe=>Xe.control.id===fe).scope)).size!==1))return;let ve=O.parts.map(b=>C.find(fe=>b.ids.includes(fe.control.id)));if(await ye(Promise.all(O.parts.map(async(b,fe)=>{let Xe=ve[fe].scope.element,Ie=S.get(Xe);Ie||(Ie=[],S.set(Xe,Ie));let he=Ie.find(ft=>!j.has(ft));return he||(he=m.createElement("img"),he.setAttribute("data-native-ui-projection-node",""),Ie.push(he)),j.add(he),R.push(he),he.src=it(b.url),await he.decode(),he}))),A||y!=="native"||x||E||s.blocked()||ne!==n||pe()!==T||Y?.revision!==e||C.some(b=>!M(b.scope.element)||![b.element,...b.sources??[]].every(p)))return;let ut=L,et={data:O,images:R,sources:Array.from(new Set(C.flatMap(b=>[b.element,...b.sources??[]]))),targets:ve.map(b=>b.element),scopes:ve.map(b=>b.scope),parents:ve.map(b=>b.scope.element.parentNode),frames:[],viewport:T,revision:e,layout:V(C)};try{if(!Z(et))throw new Error("Projection destination is unavailable")}catch(b){Ae();try{Z(),L?.sources.forEach(fe=>{W.add(fe),q.add(fe)})}catch{te()}throw b}L=et,ke(),L.sources.forEach(b=>{W.add(b),q.add(b)}),s.opaqueUnderlay&&H({action:"prepared",token:O.token}).catch(()=>{}),f.dispatchEvent(new Event(d)),ue=a,U=!0,Me(ut)}catch{}finally{U||R.forEach(se=>{B(se)}),O?.token&&!U&&H({action:"release",token:O.token}).catch(()=>{}),Ne=!1,Y&&(Y.revision!==e||I!==a)&&oe()}},oe=()=>{f.clearTimeout(de),!A&&y==="native"&&!E&&!x&&!s.blocked()&&(de=f.setTimeout(()=>{ze()},v))},Re,Je=(e,o)=>{if(o.some(a=>a.scope.element.ownerDocument!==m||[a.element,...a.sources??[]].some(T=>T.ownerDocument!==m)))throw new TypeError("All projection sources and fallback scopes must share one Document");if(s.blocked())return;!E&&L&&L.layout!==V(re(o))&&Se();let n=V(o);Re!==n&&(Re=n,I++,ne++),Y?.revision!==e&&ne++,Y={revision:e,candidates:o},He(),(L?.revision!==e||ue!==I)&&oe()},je=e=>{!E&&L&&e&&Y?.candidates.some(o=>o.scrollElement===e&&L.sources.includes(o.element))&&Se(),E&&z(),I++,ne++,oe()},Ce=await Ye(i.listen(({active:e})=>{x=e,ne++,f.clearTimeout(de),e||oe()})),Oe=new Set,be=e=>{G.get(e)?.forEach(o=>o.removeAttribute("data-native-ui-snapshot-content")),Oe.delete(e)&&e.removeAttribute("data-native-ui-snapshot-prepared"),e.removeAttribute("data-native-ui-snapshot-visible"),e.removeAttribute(g),e.removeAttribute("data-native-ui-snapshot-positioned"),e.removeAttribute("data-native-ui-snapshot-source"),_(e),G.delete(e)},Ae=()=>{le++,f.cancelAnimationFrame(ee);for(let e of K.keys())e.style.setProperty("opacity","0","important");K.clear(),ee=0,ie=!1,ce=!1,ke(),E?.sources.forEach(e=>{w.delete(e),c.delete(e)}),h.forEach(be),h.clear(),$.forEach((e,o)=>{(A||o.nodeType===11&&!o.host.isConnected)&&(e.remove(),$.delete(o))})},te=(e=E,o=D)=>{if(E!==e)return;let n=new Set([E,L]);Ae(),E=void 0,L=void 0,D=!1,f.clearTimeout(Te),n.forEach(a=>Me(a,o)),s.wake()},Fe=()=>{if(!E)return;if(L!==E){te();return}let e=E;le++,f.cancelAnimationFrame(ee),ee=0,ie=ce=!1,e.sources.forEach(o=>{w.delete(o),c.delete(o)}),h.forEach(o=>o.removeAttribute("data-native-ui-snapshot-source")),N(e,!!s.opaqueUnderlay),E=void 0,D=!1,f.clearTimeout(Te),Ve("restored",e.data.token,!0),e.sources.forEach(o=>{W.add(o),q.add(o)}),s.wake()},r=()=>{D&&Fe(),we.forEach(e=>e())},u=e=>e.scopes.map(o=>M(o.element)?o.element:void 0),h=new Set,N=(e,o)=>{for(let n of h)n.toggleAttribute("data-native-ui-snapshot-visible",o),Q(n);e.images.forEach(n=>n.style.setProperty("opacity",o?"1":"0","important"))},F=new Map,K=new Map,z=()=>{let e=[];for(let[o,n]of K){let{target:a,frame:T}=n;if(!a.isConnected){o.remove();continue}let C=a.shadowRoot??a;o.parentNode!==C&&C.insertBefore(o,null);let O=a.getBoundingClientRect(),U=o.getBoundingClientRect(),R=parseFloat(o.style.width)||T.width,se=parseFloat(o.style.height)||T.height,ae=U.width/R||1,We=U.height/se||1,ve=n.scale??(n.scale={x:ae,y:We});e.push({element:o,left:(parseFloat(o.style.left)||0)+(O.x+T.x*ae/ve.x-U.x)/ae,top:(parseFloat(o.style.top)||0)+(O.y+T.y*We/ve.y-U.y)/We,width:T.width/ve.x,height:T.height/ve.y})}for(let{element:o,...n}of e)for(let[a,T]of Object.entries(n)){let C=`${T}px`;o.style.getPropertyValue(a)!==C&&o.style.setProperty(a,C,"important")}},Z=(e=E??L)=>{if(!e||D)return!1;if(!e.sources.every(p)||u(e).some(n=>!n))return Se(),!1;let o=u(e);if(E){if(o.some(a=>!a?.isConnected||!$.has(J(a))))return te(E,!1),!1;let n=new Set;for(let[a,T]of e.targets.entries()){let C=F.get(T),O=o[a];!C||!O?.isConnected||(O.setAttribute(g,C),O.setAttribute("data-native-ui-snapshot-source",""),n.add(O))}for(let a of h)n.has(a)||be(a);return h.clear(),n.forEach(a=>h.add(a)),N(e,ce),!0}S.forEach(n=>n.forEach(a=>{e.images.includes(a)||(a.style.setProperty("display","none","important"),a.removeAttribute("data-native-ui-projection-image"))})),F.clear(),K.clear(),h.forEach(be),h.clear();for(let n of new Set(o)){if(!n)continue;h.add(n),ge(J(n));let a=o.flatMap((U,R)=>U===n?[R]:[]),T=`${P}-${a[0]}`;n.setAttribute(g,T),n!==e.targets[a[0]]&&(n.setAttribute("data-native-ui-snapshot-prepared",""),Oe.add(n)),n.toggleAttribute("data-native-ui-snapshot-visible",!!s.opaqueUnderlay),Q(n);let C=n.getBoundingClientRect();n.toggleAttribute("data-native-ui-snapshot-positioned",f.getComputedStyle(n).position==="static");let O=n.shadowRoot??n;for(let U of[...a].reverse()){let R=e.images[U],se=e.data.parts[U],ae=e.frames[U]??{x:s.opaqueUnderlay?0:se.x-C.x,y:s.opaqueUnderlay?0:se.y-C.y,width:se.width,height:se.height};e.frames[U]=ae,F.set(e.targets[U],T),R.inert=!0,R.setAttribute("aria-hidden","true"),R.setAttribute("data-native-ui-projection-image",T),R.style.cssText="all:initial!important;position:absolute!important;display:block!important;visibility:visible!important;pointer-events:none!important;image-rendering:pixelated!important;will-change:opacity!important;opacity:0!important;",R.style.setProperty("width",`${ae.width}px`,"important"),R.style.setProperty("height",`${ae.height}px`,"important"),R.style.setProperty("left","0px","important"),R.style.setProperty("top","0px","important"),R.style.setProperty("opacity",s.opaqueUnderlay?"1":"0","important"),R.parentNode!==O&&O.insertBefore(R,null),K.set(R,{target:n,frame:ae})}}return e.parents=e.scopes.map(n=>n.element.parentNode),z(),!0},X=e=>e.images.map((o,n)=>{let a=o.getBoundingClientRect();return{...e.data.parts[n],x:a.x,y:a.y,width:a.width,height:a.height}}),$e=()=>{if(r(),A||y==="none"||E)return;let e=L;if(!e||e.viewport!==pe()||!e.sources.every(p)||s.current&&!s.current(e.targets)||e.targets.some(a=>!F.has(a))||u(e).some(a=>!a?.isConnected)||[...h].some(a=>!a.isConnected))return;e.scopes.some((a,T)=>a.element.parentNode!==e.parents[T])&&Z(),z();let o=++le,n=()=>le===o;ie=!0,ce=!!s.opaqueUnderlay,E=e,ne++,f.clearTimeout(de),e.sources.forEach(a=>{w.add(a)}),h.forEach(a=>a.setAttribute("data-native-ui-snapshot-source","")),N(e,ce),ee=f.requestAnimationFrame(()=>{ee=0,n()&&(ie=!1,s.wake())}),ye(H({action:"cover",token:e.data.token,...s.transient?{transient:!0,viewportWidth:f.innerWidth,parts:X(e)}:{}})).then(a=>{if(n()){if(ie=!1,a?.shown!==!0){te(e,!1);return}D||(ce=!0,e.sources.forEach(T=>c.add(T)),Z(),z()),f.cancelAnimationFrame(ee),ee=f.requestAnimationFrame(()=>{ee=0,n()&&H({action:"painted",token:e.data.token}).catch(()=>{n()&&te(e,!1)})}),s.wake()}}).catch(()=>{n()&&te(e,!1)})},Ze=()=>{if(!(!E||A||D)){if(!E.sources.every(p)||u(E).some(e=>!e)||s.current&&!s.current(E.targets)){te(E,!1);return}if(!s.blocked()){if(z(),s.transient){let e=E;if(u(e).some(a=>!a?.isConnected)){te(e,!1);return}let n=X(e);s.opaqueUnderlay||n.every((a,T)=>a.width===e.data.parts[T].width&&a.height===e.data.parts[T].height)?e.data={...e.data,parts:n}:L=void 0,H({action:"restore",token:e.data.token,viewportWidth:f.innerWidth,parts:n}).catch(()=>{})}Le=s.revision?.()??Y?.revision??0,D=!0,E.sources.forEach(e=>w.delete(e)),Te=f.setTimeout(()=>{D&&te(E,!1)},250),s.wake()}}},dt=(e,o,n)=>{!D||!E||n===void 0||n<=Le||!s.transient&&e||E.data.parts.every(a=>a.ids.every(T=>o?.includes(T)))&&(Fe(),oe())},Se=(e=!1)=>{we.forEach(n=>n()),ne++,f.clearTimeout(de);let o=L;if(L=void 0,(e||A)&&(me=void 0),s.retainPreparedUntilRelease&&!E&&!e&&!A){if(o){let n={};me=n,ye(H({action:"release",token:o.data.token})).catch(()=>{}).finally(()=>{if(o.images.forEach(a=>{B(a)}),me===n&&(me=void 0,!(L||E))){for(let a of h)a.getAttribute(g)!==F.get(a)&&(h.delete(a),Oe.delete(a));Ae(),F.clear(),f.dispatchEvent(new Event(d))}})}if(me)return}Me(o),E?te(E,!1):(Ae(),F.clear(),f.dispatchEvent(new Event(d)))},Qe;try{s.events&&(Qe=await qe(s.events,(e,o)=>{if(!A){if(e&&(y=e),y==="none"){Se();return}e==="web"&&$e(),e==="native"&&(Ze(),oe()),o&&je()}}))}catch(e){throw A=!0,Se(),await ye(Promise.resolve(Ce.remove())).catch(()=>{}),e}return{get owners(){return[...w]},get visibleOwners(){return[...c]},get preparedOwners(){return[...W]},consider:Je,changed:je,moved:z,get token(){return D?void 0:E?.data.token},begin:$e,get restoring(){return D},get handoffPending(){return ie},retireRestoration:r,end:Ze,updated:dt,invalidate:Se,async destroy(){A=!0,Se(),S.forEach(e=>e.forEach(o=>o.remove())),S.clear(),await Promise.allSettled([ye(Promise.resolve().then(()=>Ce?.remove())),ye(Promise.resolve().then(()=>Qe?.remove()))])}}},at=async(t,i,s)=>{let v=await Mt(t,i,s),y=v.consider;return Object.assign(v,{consider:(m,f)=>y(m,f.map(P=>({...P,scope:t})))})};var ct=(t,i)=>{if(t.nodeType!==1||t.namespaceURI!=="http://www.w3.org/1999/xhtml"||!t.ownerDocument||!("style"in t))throw new TypeError("A Web projection scope requires an HTMLElement fallback");if(t.matches("img, input, textarea, select, video, audio, canvas, iframe, embed, object, br, hr, area, base, link, meta, source, track, wbr"))throw new TypeError("A Web projection fallback must be a container element; use the existing fallback container");return Object.freeze({element:t,namespace:i})};var lt=async(t,i,s={addListener:async(v,y)=>globalThis.Capacitor.addListener(t.plugin,v,y)})=>{let v="view",y=t.receiveListeners,m=!1,f=!1,P="native",p="native",M=nt(document),S=()=>P==="native"&&l&&M.isCovered(l)?"web":P,j=!1,B=!1,H=!1,g=!1,d,w,c,W=!1,k=()=>{let r=W;W=!0;try{c?.begin()}finally{W=r}},l,V=0,$,J=()=>{V++,$?.(),$=void 0},ge=async()=>{if(!t.values||!l||m)return;let r=l;J();let u=V,h=()=>!m&&u===V&&l===r&&ue()===r&&r.isConnected;if(r.localName.includes("-")&&!r.matches(":defined")&&Object.values(t.values).some(F=>F.property)){if(await Promise.race([r.ownerDocument.defaultView.customElements.whenDefined(r.localName),new Promise(F=>{$=F})]),!h())return;$=void 0}let N=await i.send({action:"values"}).catch(()=>{});if(h()&&N?.values&&typeof N.values=="object"&&!Array.isArray(N.values))try{tt(r,N.values,t.values)}catch(F){console.warn("[Native UI Engine] Cannot reflect values",F)}},_={},G,Q=0,pe=new Set,ie=()=>{},ce=new Promise(r=>{ie=r}),ee=()=>{pe.clear(),ie(),ce=new Promise(r=>{ie=r})},le=()=>{Q++,G=void 0;let r=Ue(l);r?.owner===_&&(r.projected=!1,r.owner=void 0)},L={listen:r=>i.listen(u=>{r(u),!u.active&&B&&(B=!1,Re()),u.precovered&&Je(u.precovered)}),send:r=>{if(!t.placementHandoff)return i.send(r);if(r.action==="visibility"&&!r.visible&&ee(),r.action==="visibility"&&r.visible&&pe.size){let z=x,Z=Q,X=l,$e=ce;return Promise.race([Promise.allSettled([...pe]),$e]).then(()=>m||$e!==ce||z!==x||Z!==Q||X!==l||!X?.isConnected||ue()!==l||S()!=="native"?{}:L.send(r))}let u=Ue(l);if(r.action==="cover"){let z=G===r.token&&u?.owner===_&&u.projected,Z=++Q;return G=r.token,u&&(u.owner=_,u.projected=!!z),i.send(r).then(X=>(Z===Q&&u===Ue(l)&&u?.owner===_&&(u.projected=X?.shown===!0),X)).catch(X=>{throw Z===Q&&u?.owner===_&&(le(),u.invalidate()),X})}let h=r.action==="release"&&r.token===G,N=G&&(r.action==="restore"||h||r.action==="reset"||r.action==="visibility"&&r.visible);N&&Q++;let F=Q,K=()=>{F!==Q||u?.owner!==_||(le(),u.invalidate())};try{let z=N&&u?.owner===_?{...r,token:"token"in r?r.token:G,placement:u.measure()}:r,Z=i.send(z).then(async X=>(r.action==="release"&&X?.requiresPlacement&&(u&&(!u.owner||u.owner===_)?X=await i.send({...r,placement:u.measure()}):(await i.send({action:"visibility",visible:!1}),X=await i.send(r))),F===Q&&(r.action==="resume"||h||r.action==="reset"||r.action==="visibility")&&le(),X)).catch(X=>{throw K(),X});return r.action!=="release"||!t.nativeDragHandoff?Z:(pe.add(Z),Z.finally(()=>pe.delete(Z)))}catch(z){return K(),Promise.reject(z)}}},me=0,E=0,x=0,D,Te=!1,Le=!1,ne=!1,de=document.createElement("style"),I=`data-native-ui-session-${t.id.replace(/[^a-zA-Z0-9-]/g,"")}`;de.textContent=`[${I}="native"], [${I}="native"] *, [${I}="none"], [${I}="none"] * {visibility:hidden!important}`,document.documentElement.append(de);let ue=()=>{let r=document.querySelectorAll(t.projectionSelector);return r.length===1&&r[0]instanceof HTMLElement?r[0]:void 0},A=()=>{let r=S();l&&l.getAttribute("data-native-ui-destination")!==r&&l.setAttribute("data-native-ui-destination",r),l&&(p==="web"||p==="native"&&H?l.removeAttribute(I):l.setAttribute(I,p))},Ne=()=>{if(!l?.isConnected)return[];let r=l.getBoundingClientRect(),u={x:r.x,y:r.y,width:r.width,height:r.height},h=d&&Ke(l,d,()=>t.id);return[{element:l,scrollElement:d,control:{id:v,...u,...h?{scroll:h}:{}}}]},Y=async()=>{if(m)return;let r=x,u=t.failedFallback==="native"&&S()==="native"&&!!l?.isConnected;await L.send({action:"visibility",visible:u}).catch(()=>{}),!(m||r!==x)&&(u||p==="none"?A():l?.removeAttribute(I))},He=()=>{if(!j||p!=="native"||!l?.isConnected||c?.token||c?.restoring)return;let r=t.failedFallback==="native";L.send({action:"visibility",visible:r}).catch(()=>{}),r?A():l.removeAttribute(I)},we=()=>{if(m||W)return;if(He(),p==="web"&&c&&!c.token&&!c.restoring){L.send({action:"visibility",visible:!1}).catch(()=>{}),l?.removeAttribute(I);return}if(!c?.restoring||D===c||p!=="native")return;D=c;let r=x,u=c;L.send({action:"resume"}).then(()=>{m||r!==x||u!==c||ue()!==l||(H=!1,u.updated(void 0,[v],++me),A(),q())}).catch(()=>{r!==x||m||(u.invalidate(),Y())}).finally(()=>{D===u&&(D=void 0),we()})},Ve=async()=>{if(m||Te)return;if(ue()!==l){x++,j=!1,B=!1,clearTimeout(w),le(),l?.removeAttribute(I),l?.removeAttribute("data-native-ui-destination"),J();let u=c,h=l;c=void 0,l=void 0,Me.disconnect(),je();try{h&&await L.send({action:"visibility",visible:!1})}finally{await u?.destroy()}if(m)return;l=ue(),H=!1,l&&(Me.observe(l),ge())}if(je(),!j){if(c?.restoring){we();return}if(p=S(),!l){await L.send({action:"visibility",visible:!1});return}if(!c){Te=!0;let u=x;try{let h=await at(ct(l,t.id),L,{wake:we,captureDelayMs:t.captureDelayMs,retainPreparedUntilRelease:t.nativeDragHandoff,blocked:()=>p!=="native"||j||H&&!c?.token,revision:()=>me,captureRevision:t.captureMode==="view"?()=>E:void 0,transient:!0,opaqueUnderlay:t.captureMode==="view"});if(m||u!==x){await h.destroy();return}c=h}finally{Te=!1}}if(!m){if(p==="native"&&H){await L.send({action:"visibility",visible:!1}),A();return}if(p==="native"){let u=x;if(await L.send({action:"visibility",visible:!0}),m||u!==x||p!=="native"||j)return;A(),d=[...De(l)].find(K=>{let z=getComputedStyle(K);return/auto|scroll/.test(z.overflowX+z.overflowY)&&(K.scrollHeight>K.clientHeight||K.scrollWidth>K.clientWidth)});let h=d&&Ke(l,d,()=>t.id),N=t.nativeDragHandoff&&t.captureMode==="view"&&d?{viewport:d.getBoundingClientRect(),offsetX:d.scrollLeft,offsetY:d.scrollTop,rangeX:Math.max(0,d.scrollWidth-d.clientWidth),rangeY:Math.max(0,d.scrollHeight-d.clientHeight)}:void 0,F=await L.send({action:"track",viewportWidth:innerWidth,...h?{scroll:h}:{},...N?{gesture:N}:{}});if(m||u!==x)return;g=F?.tracked===!0,c.consider(t.captureMode==="view"?E:++me,Ne())}else await L.send({action:"visibility",visible:!1}),A()}}},q=()=>{m||(Le=!0,!(!f||ne)&&(ne=!0,Promise.resolve().then(async()=>{try{for(;Le&&!m;)Le=!1,await Ve()}catch{await Y()}finally{ne=!1,Le&&q()}})))},ke=()=>{c?.invalidate(),q()},Me=new ResizeObserver(ke);window.addEventListener("resize",ke);let re=new Set,ze=new WeakSet,oe=r=>{let u=r.composedPath()[0]??r.target;if(!(m||p!=="native"||!l||ze.has(r)||u!==document&&![...De(l)].includes(u))){if(ze.add(r),g&&u===d){c?.changed(u);return}j?t.captureMode==="view"?c?.moved():c?.changed():((D||c?.restoring)&&x++,j=!0,k(),H=t.failedFallback!=="native",c?.token?l.removeAttribute(I):He()),Re()}};function Re(){clearTimeout(w),w=setTimeout(()=>{B||(j=!1,!(m||p!=="native")&&(c?.end(),c?.restoring||q()))},100)}function Je(r){if(m||p!=="native"||!l?.isConnected||!c||(B=!0,c.token))return;(D||c.restoring)&&x++,G=r;let u=Ue(l);if(u&&(u.owner=_,u.projected=!0),j=!0,k(),!c.token){j=!1,Y();return}Re()}let je=()=>{let r=new Set(l?De(l):[]),u=r.size!==re.size||[...r].some(h=>!re.has(h));for(let h of re)r.has(h)||(h.removeEventListener("scroll",oe),h.removeEventListener("slotchange",Ce),re.delete(h));for(let h of r)re.has(h)||(h.addEventListener("scroll",oe,{passive:!0}),h.addEventListener("slotchange",Ce),re.add(h));u&&(g=!1,d=void 0,c?.invalidate(),q())},Ce=()=>{je(),q()};document.addEventListener("scroll",oe,!0);let Oe=new MutationObserver(r=>{je(),ue()!==l?q():l&&r.some(u=>u.type==="childList")&&be(S())});Oe.observe(document.documentElement,{childList:!0,attributes:!0,subtree:!0});let be=(r,u=!1)=>{if(m||!u&&r===p)return;j=!1,B=!1,clearTimeout(w),p=r,x++;let h=x;p==="none"?L.send({action:"visibility",visible:!1}).then(()=>{p!=="none"||h!==x||m||(c?.invalidate(!0),A())}).catch(()=>{p!=="none"||h!==x||m||(c?.invalidate(!0),A())}):p==="web"?(k(),c?.token||L.send({action:"visibility",visible:!1}).catch(()=>{}),A()):p==="native"&&(c?.end(),c?.restoring||q())},Ae=M.subscribe(()=>queueMicrotask(()=>be(S()))),te,Fe=async()=>{if(!m){m=!0,ee(),J(),le(),x++,Ae(),Oe.disconnect(),Me.disconnect(),window.removeEventListener("resize",ke),document.removeEventListener("scroll",oe,!0);for(let r of re)r.removeEventListener("scroll",oe),r.removeEventListener("slotchange",Ce);re.clear(),clearTimeout(w),l?.removeAttribute(I),l?.removeAttribute("data-native-ui-destination"),de.remove(),await Promise.allSettled([Promise.resolve(te?.remove()),c?.destroy()])}};try{let r=x;te=await qe({addListener:(h,N)=>s.addListener(h,N),update:y?.update,projection:y?.projection,accepts:h=>!y?.match||Object.entries(y?.match).every(([N,F])=>typeof h=="object"&&h!==null&&h[N]===F)},(h,N)=>{m||(h&&(h==="native"&&(H=!1),P=h,L.send({action:"destination",destination:h}).catch(()=>{}),be(S(),!0)),N&&(ge(),E++,c?.changed(),p==="native"&&q()))});let u=await L.send({action:"connect"});return x===r&&["native","web","none"].includes(u?.destination)&&(P=u.destination),p=S(),f=!0,q(),{destroy:Fe,show:()=>{H=!1,P="native",be(S(),!0)},hide:()=>{P=p="none",x++,ee(),j=!1,B=!1,clearTimeout(w),c?.invalidate(!0),A()}}}catch(r){throw await Fe(),r}};var Ee=new Map,Pe=new Map,Be=new Map,jt=Array.from(crypto.getRandomValues(new Uint32Array(4)),t=>t.toString(16)).join("-"),_e=new Map,St=(t,i,s)=>_e.get(t)?.get(i)?.(s),xt=0,Ht=(t,i)=>new Promise((s,v)=>{let y=`${t}:${jt}:${++xt}`,m=setTimeout(()=>{Pe.delete(y),v(new Error("Native UI session timed out"))},5e3);Pe.set(y,{resolve:s,reject:v,timer:m});let f=JSON.stringify({key:y,request:i}),P=globalThis,p=P.webkit?.messageHandlers?.[t]??P.nativeUIEngine;if(!p){clearTimeout(m),Pe.delete(y),v(new Error("Native UI session unavailable"));return}try{p.postMessage(f)}catch(M){clearTimeout(m),Pe.delete(y),v(M)}}),kt=(t,i,s,v)=>{if(i===null){Be.get(t)?.(s);return}let y=Pe.get(i);!y||!i.startsWith(`${t}:`)||(Pe.delete(i),clearTimeout(y.timer),v?y.reject(new Error(v)):y.resolve(s))},Rt=t=>{Ee.get(t)?.show()},Ct=t=>(Ee.get(t)?.hide(),!0),Ot=async t=>{let i=Ee.get(t);Ee.delete(t),_e.delete(t),Be.delete(t);for(let[s,v]of Pe)s.startsWith(`${t}:`)&&(Pe.delete(s),clearTimeout(v.timer),v.reject(new Error("Native UI session closed")));await i?.destroy()},At=async t=>{if(Ee.has(t.id))return;let i,s=!1,v,y={show:()=>{v="native",i?.show()},hide:()=>{v="none",i?.hide()},destroy:async()=>{s=!0,await i?.destroy()}};Ee.set(t.id,y);try{if(document.readyState==="loading"&&await new Promise(f=>document.addEventListener("DOMContentLoaded",()=>f(),{once:!0})),s)return;let m=new Map;t.runtime==="cordova"&&_e.set(t.id,m),i=await lt(t,{send:f=>Ht(t.id,f),listen:async f=>(Be.set(t.id,f),{remove:()=>{Be.get(t.id)===f&&Be.delete(t.id)}})},t.runtime==="cordova"?{addListener:async(f,P)=>(m.set(f,P),{remove:()=>{m.delete(f)}})}:void 0),v==="none"?i.hide():v==="native"&&i.show(),s&&await i.destroy()}catch(m){throw Ee.get(t.id)===y&&(Ee.delete(t.id),_e.delete(t.id)),m}};return gt(Wt);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webview-native-ui/engine",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Native UI placement, scroll tracking, and Web projection for Capacitor and Cordova plugin maintainers.",
5
5
  "type": "module",
6
6
  "license": "MIT",