@webview-native-ui/engine 0.0.5 → 0.0.7

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.
Files changed (47) hide show
  1. package/README.md +19 -12
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +3 -0
  4. package/dist/native/session.d.ts +5 -0
  5. package/dist/native/session.js +83 -14
  6. package/dist/native/updates.d.ts +16 -0
  7. package/dist/native/updates.js +108 -0
  8. package/dist/native/values.d.ts +5 -0
  9. package/dist/native/values.js +27 -0
  10. package/dist/projection/surface.d.ts +35 -0
  11. package/dist/projection/surface.js +81 -0
  12. package/dist/projection/surfaces.d.ts +20 -0
  13. package/dist/projection/surfaces.js +116 -0
  14. package/dist/projection/web.js +22 -12
  15. package/dist/types.d.ts +4 -0
  16. package/docs/README.md +70 -0
  17. package/docs/advanced/README.md +20 -0
  18. package/docs/advanced/android-placement.md +48 -0
  19. package/docs/advanced/native-handoff.md +89 -0
  20. package/docs/advanced/renderer.md +148 -0
  21. package/docs/advanced/update-queue.md +58 -0
  22. package/docs/advanced/web-projection.md +88 -0
  23. package/docs/assets/ios-native-capture-comparison.png +0 -0
  24. package/docs/concepts.md +97 -0
  25. package/docs/configuration/README.md +29 -0
  26. package/docs/configuration/capture.md +27 -0
  27. package/docs/configuration/events.md +35 -0
  28. package/docs/configuration/ionic-overlays.md +65 -0
  29. package/docs/configuration/values.md +125 -0
  30. package/docs/cordova.md +1 -1
  31. package/docs/{capacitor-plugin.md → getting-started/capacitor.md} +45 -47
  32. package/docs/{cordova-plugin.md → getting-started/cordova.md} +32 -15
  33. package/docs/getting-started/initialization.md +79 -0
  34. package/docs/maintenance.md +14 -2
  35. package/docs/{projection-containers.md → reference/containers.md} +6 -2
  36. package/docs/reference/platforms.md +137 -0
  37. package/docs/reference/projection.md +134 -0
  38. package/docs/reference/state.md +39 -0
  39. package/ios/Sources/WebViewNativeUIEngine/ClippedView.swift +12 -12
  40. package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +70 -9
  41. package/ios/Sources/WebViewNativeUIEngine/Frame.swift +10 -10
  42. package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +92 -44
  43. package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -3
  44. package/ios/Sources/WebViewNativeUIEngine/ScrollBindings.swift +3 -0
  45. package/ios/Sources/WebViewNativeUIEngine/ScrollTracker.swift +10 -8
  46. package/package.json +10 -3
  47. package/docs/projection.md +0 -364
@@ -2,16 +2,32 @@
2
2
 
3
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.
4
4
 
5
- This guide covers dependencies, registration, notifications and teardown. See [projection behavior](projection.md) for shared DOM requirements, capture policies and advanced integration.
5
+ This guide covers dependencies, registration, notifications and teardown. Start with
6
+ [native initialization](initialization.md) for the minimal call, required inputs
7
+ and ownership; the plugin diffs below add event wiring and lifetime handling. Use the [projection reference](../reference/projection.md) for exact contracts and platform limits.
8
+
9
+ For a Capacitor tab-bar plugin, the application should keep calling the same
10
+ show and selection methods. Add the registration in the native plugin, then
11
+ forward its successful updates so a Web modal can cover the bar without requiring
12
+ every application to implement image handoff.
6
13
 
7
14
  ## Requirements
8
15
 
9
- This package declares support for Capacitor 8 (`>=8.0.0 <9`). The Engine library requires iOS 15+ and Android API 23+; the consuming Capacitor version or SDK may require a higher minimum. Composited Android capture requires API 26+ and the WebView features described in the [projection reference](projection.md#dom-and-native-surface-requirements).
16
+ This package declares support for Capacitor 8 (`>=8.0.0 <9`). The Engine library requires iOS 15+ and Android API 23+; the consuming Capacitor version or SDK may require a higher minimum. Composited Android capture requires API 26+ and the WebView features described in the [projection reference](../reference/projection.md#dom-and-native-surface-requirements).
17
+
18
+ ## Choose the Web destination
19
+
20
+ Provide one laid-out DOM container for each registration, in the layer where its Web projection belongs. It may be inserted after registration. The plugin still owns native view placement and sizing; the Engine does not create or size either view for you.
21
+
22
+ Choose a [supported container](../reference/containers.md) before wiring its selector. Read the [DOM and native surface requirements](../reference/projection.md#dom-and-native-surface-requirements) before integrating custom elements, SDK content or multiple surfaces. Scroll tracking and image handoff are internal to the registration. For Ionic applications, optionally enable [Ionic overlay support](../configuration/ionic-overlays.md) once during application startup. Other frameworks emit the configured projection notifications from their own transition and overlay lifecycle.
10
23
 
11
24
  ## Install inside a plugin
12
25
 
26
+ Replace `<engine-version>` in every dependency below with the same published
27
+ release. Read that release tag’s documentation so the examples match its APIs.
28
+
13
29
  ```sh
14
- npm install --save-exact @webview-native-ui/engine@0.0.1
30
+ npm install --save-exact '@webview-native-ui/engine@<engine-version>'
15
31
  ```
16
32
 
17
33
  The npm archive contains TypeScript declarations, ESM, Swift sources and a podspec. Native dependencies must also be declared by the consuming plugin; an npm dependency alone links neither the Swift library nor the Android AAR.
@@ -19,12 +35,12 @@ The npm archive contains TypeScript declarations, ESM, Swift sources and a podsp
19
35
  The primary native distribution is Swift Package Manager. Depend on the repository-root library from your plugin:
20
36
 
21
37
  ```swift
22
- .package(url: "https://github.com/webview-native-ui/engine.git", exact: "0.0.1")
38
+ .package(url: "https://github.com/webview-native-ui/engine.git", exact: "<engine-version>")
23
39
  // In the plugin target's dependencies:
24
40
  .product(name: "WebViewNativeUIEngine", package: "engine")
25
41
  ```
26
42
 
27
- The new coordinates start at `0.0.1`. 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
+ 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.
28
44
 
29
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.
30
46
 
@@ -36,7 +52,7 @@ In the consuming plugin's `android/build.gradle`, add the Engine version matchin
36
52
 
37
53
  ```groovy
38
54
  dependencies {
39
- implementation 'io.github.webview-native-ui:engine:0.0.1'
55
+ implementation 'io.github.webview-native-ui:engine:<engine-version>'
40
56
  }
41
57
  ```
42
58
 
@@ -58,13 +74,13 @@ If the build uses project-level repositories instead, add them to its existing `
58
74
  CocoaPods uses the Swift sources and podspec shipped in npm; no CocoaPods Trunk publication is required. The plugin maintainer declares the matching dependency in the plugin's podspec:
59
75
 
60
76
  ```ruby
61
- s.dependency 'WebViewNativeUIEngine', '0.0.1'
77
+ s.dependency 'WebViewNativeUIEngine', '<engine-version>'
62
78
  ```
63
79
 
64
80
  The maintainer must also document this one-time application setup. Install the matching engine version as a direct npm dependency, then add the local pod **inside the application's target, outside `def capacitor_pods`**, which Capacitor regenerates:
65
81
 
66
82
  ```sh
67
- npm install --save-exact @webview-native-ui/engine@0.0.1
83
+ npm install --save-exact '@webview-native-ui/engine@<engine-version>'
68
84
  ```
69
85
 
70
86
  ```ruby
@@ -81,6 +97,9 @@ Run `npx cap sync ios` and rebuild. Keep the direct engine version aligned with
81
97
 
82
98
  Keep the plugin's normal `registerPlugin()` export and public methods unchanged. Create and retain a registration after creating the native renderer. The engine ships its Web runtime with the Swift/Android library, starts it in the current document and future documents, finds the existing fallback DOM, and subscribes to the plugin's notifications. Applications do not call a separate JS initializer.
83
99
 
100
+ The [notification contract](../configuration/events.md) defines which successful
101
+ changes to report and how startup routing works.
102
+
84
103
  The integration has three responsibilities:
85
104
 
86
105
  1. The consumer provides an existing, laid-out Web fallback container in the correct layer. Pass this projection destination as the required `projectionSelector: string`; there is no `null` / automatic DOM creation mode.
@@ -228,12 +247,12 @@ before the first navigation, even when SDK views arrive later. Include installat
228
247
  in the same error boundary as registration; if it fails, keep the existing Web
229
248
  fallback and do not retry installation after navigation. Registrations then
230
249
  share that main-frame, origin-restricted channel; they do not install new message
231
- listeners into a loaded WebView. This avoids a WebView native crash reproduced when
232
- registering an asynchronously loaded AdMob view. The registration constructor can
250
+ listeners into a loaded WebView. Installing the channel before navigation avoids a WebView native crash when
251
+ registering a view that loads asynchronously. The registration constructor can
233
252
  install the channel only on an un-navigated WebView; late installation fails with
234
253
  an actionable error instead of attempting unsafe injection. All registrations on
235
254
  the same WebView must use the same allowed origins. Closing a registration removes
236
- only its owner from the channel, so other slots continue working. The shared channel
255
+ only its owner from the channel, so other registered views continue working. The shared channel
237
256
  remains installed for the WebView lifetime; registrations must still be retained
238
257
  and closed by their consuming plugin.
239
258
 
@@ -241,43 +260,22 @@ Both diffs add a private property, native registration, a notification override,
241
260
 
242
261
  Keep your plugin's existing `registerPlugin()` export. Use the same event names in the registration and `notifyListeners`; if the plugin already emits a suitable event (such as `tabSelected`), list that event instead of emitting it twice.
243
262
 
244
- ### Notifications during startup
245
-
246
- The notification override above lets the Engine retain the latest matching projection destination before the Web runtime subscribes. No separate state variable or initial-state callback is needed. Forward every notification to `super` as usual: the Engine records state without replaying notifications, updates, or user actions. Without a destination notification, it infers the initial destination from native view visibility. Registration must exist before the notifications it should observe.
247
-
248
- If your plugin owns multiple registrations, pass the notification to each registration; `receiveListeners.match` selects the owning surface. Appearance updates do not change the destination. The renderer still owns native placement and visibility before connection.
249
-
250
- With the default Web fallback, scrolling without a prepared image or native scroll binding keeps the Web fallback visible until the renderer confirms current native placement with its native-destination notification. An appearance update alone is not that confirmation. Consumers choosing native failure recovery must keep their own native geometry current.
251
-
252
- ### Which events to emit
253
-
254
- All incoming event declarations belong to `receiveListeners`. Its `update` list refreshes the image, its `projection` lists select the destination, and its optional `match` filters notifications for the owning surface.
255
-
256
- | Notification | Where to emit it in the existing plugin |
257
- | --------------------------------------- | ------------------------------------------------------------------------------------- |
258
- | `appearanceChanged` (an `update` event) | After a successful native appearance update, such as `setColors` |
259
- | `transitionStarted` (`web`) | Before the fallback DOM starts moving or a Web layer covers the native view |
260
- | `shown` (`native`) | After the plugin's existing show operation, or when returning from the Web projection |
261
- | `hidden` (`none`) | After the plugin's existing hide operation |
262
-
263
- The names in the examples are conventions you choose, not reserved Engine methods. An `update` event refreshes the prepared image without changing the requested destination. It does not call `setColors` again.
264
-
265
- Emit `transitionStarted` before the DOM starts moving, `shown` when native projection is requested again, and `hidden` after the plugin's own hide operation. Native capture, URL conversion, image preparation, draw acknowledgement, image retirement and listener cleanup stay inside the engine. The engine never invokes the plugin's `show`, `hide`, selection or ad-loading methods. End the registration with `projection.destroy()` on iOS or `projection.close()` on Android when the renderer is destroyed. Retain the registration for its full lifetime and call native APIs on the UI thread.
266
-
267
- ## DOM, scrolling and overlays
268
-
269
- Provide one laid-out DOM container for each registration, in the layer where its Web projection belongs. It may be inserted after registration. The plugin still owns native view placement and sizing; the Engine does not create or size either view for you.
270
-
271
- Read the [DOM and native surface requirements](projection.md#dom-and-native-surface-requirements) before integrating custom elements, SDK content or multiple surfaces. Scroll tracking and image handoff are internal to the registration. For Ionic applications, optionally enable [Ionic overlay support](projection.md#ionic-overlay-support-optional) once during application startup. Other frameworks emit the configured projection notifications from their own transition and overlay lifecycle.
263
+ ## Verify the integration
272
264
 
273
- ## Animated native controls
265
+ Show the control, change its appearance, then open and dismiss a covering Web
266
+ overlay. Check that the prepared appearance belongs to the current control and
267
+ that native interaction returns at the current position. Also check hiding,
268
+ container replacement and plugin teardown: late replies must not reveal retired
269
+ content. Exercise a missing image to verify the chosen Web fallback.
274
270
 
275
- Native appearance updates prepare replacement images after a 50 ms debounce by default.
276
- If your control continues animating after its update notification, set `captureDelayMs`
277
- on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
278
- This changes background image preparation, not overlay presentation timing.
279
- See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
271
+ Use the actual control and backgrounds on supported devices; bridge success alone
272
+ cannot verify visual continuity. For a renderer that already batches controls by
273
+ page, continue with the [renderer guide](../advanced/renderer.md) rather than duplicating
274
+ registration machinery for each handoff.
280
275
 
281
- ### Projection DOM
276
+ ## Next: choose optional behavior
282
277
 
283
- The engine keeps a reusable image inside the fallback container, using its open ShadowRoot when available. No placement option is needed. If the renderer inspects child elements, exclude nodes marked `data-native-ui-projection-node`. See [DOM requirements](projection.md#dom-and-native-surface-requirements).
278
+ Use the [configuration guide](../configuration/README.md) for notification routing,
279
+ value reflection, capture timing and fallback policy. These shared settings are
280
+ not additional platform setup. For a renderer that already batches controls, see
281
+ [advanced APIs](../advanced/README.md).
@@ -4,16 +4,38 @@ The same npm package, Swift library and Android AAR support Capacitor and Cordov
4
4
 
5
5
  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.
6
6
 
7
+ See [native initialization](initialization.md) for required inputs and ownership. The platform examples below add Cordova-specific
8
+ startup, notification forwarding and teardown. Set `runtime` to Cordova even
9
+ though it is optional in the constructor.
10
+
11
+ For a Cordova plugin showing a native advertisement in a feed, the application
12
+ already owns a DOM slot and the plugin owns SDK interaction. This route connects
13
+ that slot to projection without adding a Capacitor dependency or replacing the
14
+ SDK's click handling; capture must still be allowed for the loaded content.
15
+
7
16
  ## Requirements
8
17
 
9
18
  The integration is verified with cordova-ios 8.1.1 and cordova-android 15.1.0 using their standard WebViews. The Engine itself requires iOS 15+ and Android API 23+; the Cordova host or SDK may require a higher minimum. Composited Android capture requires API 26+.
10
19
 
20
+ ## Choose the Web destination
21
+
22
+ Provide a laid-out container such as `native-ad` or `#player` in the layer where
23
+ the projected appearance should live. It may be inserted after registration, but
24
+ must exist when projection starts. The native view's rectangle cannot identify
25
+ its intended place in the DOM stacking tree. Check the
26
+ [container support table](../reference/containers.md) before choosing the selector.
27
+ For Ionic applications, add [overlay support](../configuration/ionic-overlays.md) once; other
28
+ frameworks report their own transition lifecycle through notifications.
29
+
11
30
  ## Dependencies
12
31
 
32
+ Replace `<engine-version>` in every dependency below with the same published
33
+ release. Read that release tag’s documentation so the examples match its APIs.
34
+
13
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.
14
36
 
15
37
  ```sh
16
- npm install --save-exact @webview-native-ui/engine@0.0.1
38
+ npm install --save-exact '@webview-native-ui/engine@<engine-version>'
17
39
  ```
18
40
 
19
41
  Declare the native dependencies in the consuming plugin as well. npm installation alone does not link either native library.
@@ -21,7 +43,7 @@ Declare the native dependencies in the consuming plugin as well. npm installatio
21
43
  For Swift Package Manager:
22
44
 
23
45
  ```swift
24
- .package(url: "https://github.com/webview-native-ui/engine.git", exact: "0.0.1")
46
+ .package(url: "https://github.com/webview-native-ui/engine.git", exact: "<engine-version>")
25
47
  // In the plugin target's dependencies:
26
48
  .product(name: "WebViewNativeUIEngine", package: "engine")
27
49
  ```
@@ -30,7 +52,7 @@ For the plugin's Android Gradle configuration:
30
52
 
31
53
  ```groovy
32
54
  dependencies {
33
- implementation 'io.github.webview-native-ui:engine:0.0.1'
55
+ implementation 'io.github.webview-native-ui:engine:<engine-version>'
34
56
  }
35
57
  ```
36
58
 
@@ -166,7 +188,7 @@ class ExamplePlugin : CordovaPlugin() {
166
188
  }
167
189
  ```
168
190
 
169
- Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. Native view creation and placement remain on the UI thread. If the renderer loads asynchronously, keep `install(web)` in `pluginInitialize()` and create the registration after the view is ready. Close the old registration before replacing that renderer.
191
+ Use `projection?.receive(eventName, data)` at the existing notification point, as on iOS. See the shared [notification contract](../configuration/events.md) for update and destination semantics. Native view creation and placement remain on the UI thread. If the renderer loads asynchronously, keep `install(web)` in `pluginInitialize()` and create the registration after the view is ready. Close the old registration before replacing that renderer.
170
192
 
171
193
  The `PathHandler` delegates same-origin image requests to Engine. It serves only the registration's PNG files in app-private cache storage; unrelated paths return `null`. If the plugin already has a handler, combine them with `projection?.handleResource(path) ?: existingHandler.handle(path)`. For multiple registrations, delegate to the matching owner. Keep the handler installed for the WebView lifetime. No WebViewClient replacement or storage permission is needed.
172
194
 
@@ -176,7 +198,7 @@ The defaults support Cordova's `https://localhost` and `http://localhost`. If th
176
198
 
177
199
  Keep the registration for the WebView lifetime. Engine reconnects after a document reload and clears the old document's image state. If the plugin destroys its native UI in `onReset`, update its view provider and emit the appropriate destination; do not leave a registration pointing at a destroyed view. Dispose registrations when the plugin/WebView is destroyed.
178
200
 
179
- Capacitor registrations retain their existing default. The Cordova integration requires the standard Cordova WebView resource handler. Running a Cordova plugin inside a Capacitor app is not covered by this integration; the host must use the Capacitor integration instead. See the shared [DOM and native surface requirements](projection.md#dom-and-native-surface-requirements) for selector, layout and capture rules. [Ionic overlay support](projection.md#ionic-overlay-support-optional) remains optional; plain Cordova apps need not enable it.
201
+ The Cordova integration requires the standard Cordova WebView resource handler. Running a Cordova plugin inside a Capacitor app is not covered by this integration; the host must use the Capacitor integration instead. See the shared [DOM and native surface requirements](../reference/projection.md#dom-and-native-surface-requirements) for selector, layout and capture rules. [Ionic overlay support](../configuration/ionic-overlays.md) remains optional; plain Cordova apps need not enable it.
180
202
 
181
203
  The integration fixture uses Cordova CLI 13, cordova-ios 8.1.1 and cordova-android 15.1.0 with their standard WebViews. It checks actual PNG handoff, native restoration and hidden-state retention across document reload, not merely JS bridge mocks. Older Cordova releases and custom WebView engines require separate verification.
182
204
 
@@ -189,14 +211,9 @@ npm run test:cordova -- android emulator-5554
189
211
 
190
212
  The script creates a disposable app, uses local SwiftPM/Maven Engine artifacts, and records the application result in the printed temporary directory. It never publishes to npm or Maven. Android requires JDK 21+, SDK 36 and `ANDROID_HOME`; iOS requires Xcode.
191
213
 
192
- ## Animated native controls
193
-
194
- Native appearance updates prepare replacement images after a 50 ms debounce by default.
195
- If your control continues animating after its update notification, set `captureDelayMs`
196
- on the native registration (Swift: `captureDelayMs: 600`; Kotlin: `captureDelayMs = 600`).
197
- This changes background image preparation, not overlay presentation timing.
198
- See [capture timing](projection.md#capture-timing) for the cache behavior and measured guidance.
199
-
200
- ### Projection DOM
214
+ ## Next: choose optional behavior
201
215
 
202
- The engine keeps a reusable image inside the fallback container, using its open ShadowRoot when available. No placement option is needed. If the renderer inspects child elements, exclude nodes marked `data-native-ui-projection-node`. See [DOM requirements](projection.md#dom-and-native-surface-requirements).
216
+ Use the [configuration guide](../configuration/README.md) for notification routing,
217
+ value reflection, capture timing and fallback policy. These shared settings are
218
+ not additional platform setup. For a renderer that already batches controls, see
219
+ [advanced APIs](../advanced/README.md).
@@ -0,0 +1,79 @@
1
+ # Initialize a native view registration
2
+
3
+ Start with `NativeUIProjection` when a plugin owns a native view and a corresponding
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.
6
+
7
+ Use the [Capacitor](capacitor.md) or [Cordova](cordova.md) guide for
8
+ dependency installation, plugin lifecycle hooks and notification forwarding.
9
+ The examples below show the smallest registration within those hooks, followed by
10
+ required inputs. After initialization, the configuration guide lists optional
11
+ behavior; advanced APIs have their own entry point.
12
+
13
+ For example, a plugin already draws a native tab bar, but an application opens a
14
+ Web settings sheet above it. Register the bar and its fallback navigation host so
15
+ the Engine can put the captured bar beneath that sheet and restore native input
16
+ when it closes. The plugin keeps its existing tab selection API.
17
+
18
+ ## Minimal initialization
19
+
20
+ Create and retain one registration after your renderer has attached its native
21
+ view. The selector may match a DOM container added later, but native presentation
22
+ waits until that container exists. Give it a [supported layout box](../reference/containers.md).
23
+
24
+ In a Capacitor Swift plugin, inside `load()` or the existing renderer initializer:
25
+
26
+ ```swift
27
+ projection = try NativeUIProjection(
28
+ webView: bridge?.webView,
29
+ plugin: jsName,
30
+ projectionSelector: "#native-ui-fallback",
31
+ view: { [weak self] in self?.nativeView }
32
+ )
33
+ ```
34
+
35
+ In a Capacitor Kotlin plugin, install the shared channel in `load()` **before the
36
+ first WebView navigation**. The registration may be created later when its view is ready:
37
+
38
+ ```kotlin
39
+ NativeUIProjection.install(bridge.webView)
40
+ projection = NativeUIProjection(
41
+ window = activity.window,
42
+ webView = bridge.webView,
43
+ plugin = pluginHandle.id,
44
+ projectionSelector = "#native-ui-fallback",
45
+ view = { nativeView }
46
+ )
47
+ ```
48
+
49
+ Both snippets belong inside the integration guide's error boundary; retain the
50
+ existing Web fallback if initialization fails. `projection` and `nativeView` are
51
+ properties of your plugin. Dispose the registration with `destroy()` on iOS or
52
+ `close()` on Android when that renderer is retired. All native lifecycle calls
53
+ run on the UI thread.
54
+
55
+ Cordova uses the same registration with `runtime: .cordova` / `runtime = Runtime.CORDOVA`
56
+ and its own WebView and plugin identity. Follow the [Cordova guide](cordova.md)
57
+ for those hooks and Android's resource handler.
58
+
59
+ ## Required inputs
60
+
61
+ | Input | Platforms | What it identifies and why |
62
+ | -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
63
+ | `webView` | iOS / Android | The application's WebView, which owns the destination document and bridge. Swift accepts an optional reference so a missing WebView throws through the initialization error boundary. |
64
+ | `plugin` | iOS / Android | The existing plugin identity routes notifications to this plugin rather than another Engine consumer. Capacitor: `jsName` on iOS, `pluginHandle.id` on Android. Cordova: the configured service name. |
65
+ | `projectionSelector` | iOS / Android | A CSS selector matching exactly one existing Web container when presentation begins. It gives the image its correct layout and overlay layer; a native rectangle cannot identify that DOM layer. |
66
+ | `view` | iOS / Android | A callback returning the renderer's current native view, or nil/null when absent. Return the current view within this renderer lifetime. When replacing the renderer, dispose its registration and create a new one; the plugin owns layout and normal visibility. |
67
+ | `window` | Android | The window containing the WebView and native surface, needed for composited capture and interaction tracking. |
68
+
69
+ These inputs establish ownership. They do not tell the Engine when content
70
+ changes or when an application transition should change presentation.
71
+
72
+ ## Next: configure changing UI
73
+
74
+ The minimal registration establishes ownership. Continue with [optional settings](../configuration/README.md)
75
+ for event wiring, value reflection, capture and failure policy. Each setting lists
76
+ its default and when it is needed. See [advanced APIs](../advanced/README.md) only when
77
+ your renderer already manages batches, placement or update reconciliation.
78
+
79
+ [Documentation overview](../README.md)
@@ -1,5 +1,10 @@
1
1
  # Development and releases
2
2
 
3
+ Use this guide when changing or publishing the Engine itself. A native consumer
4
+ may build without Node.js, so source changes must regenerate the bundled runtime.
5
+ A release may reach npm before Maven artifacts are downloadable, so publication
6
+ acceptance and consumer download readiness are checked separately below.
7
+
3
8
  ## Development
4
9
 
5
10
  The public entry points are `src/index.ts` and `src/ionic.ts`, with shared contracts in `src/types.ts`. Implementation is grouped by responsibility:
@@ -23,7 +28,11 @@ npm test
23
28
  xcodebuild -scheme WebViewNativeUIEngine -destination 'platform=iOS Simulator,id=<booted simulator UUID>' test
24
29
  ```
25
30
 
26
- Lint combines strict TypeScript checking, the standard ESLint/TypeScript recommended rules, and typed Promise checks for `src`. Scripts and tests receive syntax-level linting; Prettier owns formatting. `npm run format` applies formatting only. Bridge payloads and test doubles may use explicit `any`; production boundary validation remains necessary. An explicit `void` marks background work, but does not handle a rejected Promise. Android uses `lintRelease` with warnings treated as errors in CI; Swift is compiled and tested on an iOS simulator.
31
+ Lint combines strict TypeScript checking, the standard ESLint/TypeScript recommended rules, and typed Promise checks for `src`. Scripts and tests receive syntax-level linting; Prettier owns formatting. `npm run format` applies formatting only. Bridge payloads and test doubles may use explicit `any`; production boundary validation remains necessary. An explicit `void` marks background work, but does not handle a rejected Promise.
32
+
33
+ Native checks run in their platform CI jobs: `npm run lint:swift` uses node-swiftlint with the Capacitor configuration on macOS (`brew install swiftlint`); `npm run lint:android` runs Kotlin formatting checks and Android Lint with warnings treated as errors (JDK 21 and Android SDK 36). Use `npm run format:swift` and `npm run format:kotlin` to apply native formatting. Kotlin uses ktfmt because the Prettier Kotlin plugin cannot parse the existing native test fixture.
34
+
35
+ These checks include native tests and Cordova fixtures. Swift API documentation uses `///`; implementation notes use `//`. Swift size/complexity heuristics do not force state-machine splits or public API changes; fixture-only force unwraps/casts are permitted, while production retains the checks. Swift is also compiled and tested on an iOS simulator.
27
36
 
28
37
  The engine is versioned independently of its consumers. Publish order is Maven publication acceptance → npm engine; consumers upgrade separately. npm releases require a successful Maven publication workflow for the exact tagged commit. Maven Central distribution may still be pending: Android consumers must wait until the matching POM and AAR are downloadable, while iOS and Web consumers can upgrade immediately. No publication is performed by build or test commands. Tests cover plain DOM, fractional/RTL geometry, stale ACKs, image ownership and native WKWebView scroll tracking; consumer tests remain responsible for Ionic/SDK integration.
29
38
 
@@ -39,7 +48,8 @@ For local packaging verification, run `python3 scripts/native-ui-engine-release/
39
48
 
40
49
  ## Maintainer release setup
41
50
 
42
- This repository starts from the merged Engine implementation at [ionic-theme-ios27 `b218d8b`](https://github.com/rdlabo-dev/ionic-theme-ios27/commit/b218d8bfe0c87c02addc668fee9c7aa333489486). Extraction preserves the merged implementation; Cordova host support is developed in this repository. The renamed package starts at version `0.0.1`. Publish the same version for npm, SwiftPM and Maven; do not reuse the old package coordinates.
51
+ Publish the same version for npm, SwiftPM and Maven; consumers pin matching
52
+ versions across these distributions.
43
53
 
44
54
  The npm organization `webview-native-ui` and Maven Central namespace `io.github.webview-native-ui` are registered. Maintain npm trusted publishing for `webview-native-ui/engine`, workflow `release.yml`, and provision the Maven secrets above in this repository. Credentials are not copied by moving source files.
45
55
 
@@ -50,3 +60,5 @@ The npm organization `webview-native-ui` and Maven Central namespace `io.github.
50
60
  No build or test command publishes a package. Existing npm/Maven versions are never overwritten.
51
61
 
52
62
  To rerun release verification for an existing tag, dispatch `release.yml` with that tag. It checks the original tagged commit and skips npm publication only when the existing package contents match. Keep the tag unchanged. Release tooling uses the Node.js 24 bundled npm, matching CI.
63
+
64
+ [Documentation overview](README.md)
@@ -10,7 +10,7 @@ Choose the existing DOM element that occupies the native surface's intended loca
10
10
  - `projectionSelector` uses document CSS selectors (ID, class, attribute or descendant). It must match exactly one element and does not enter Shadow DOM. A missing element can be created later; zero or multiple matches keep Native hidden.
11
11
  - The retained image is inserted in the element's open ShadowRoot, or as a light-DOM child otherwise. Closed shadow content must inherit the host's visibility and expose a slot that can display the image.
12
12
  - An overlay, collapsed item or inactive page is eligible only while it has a visible layout box. Do not force hidden configuration elements to render just to use them as destinations.
13
- - Host/ancestor clipping, opacity, transforms and filters still affect the image. The image is noninteractive and freezes the captured appearance. See [projection behavior](projection.md#dom-and-native-surface-requirements) for lifecycle, mutation-observer exclusions and native capture limits.
13
+ - Host/ancestor clipping, opacity, transforms and filters still affect the image. The image is noninteractive and freezes the captured appearance. See the [projection reference](projection.md#dom-and-native-surface-requirements) for native capture limits and the [renderer guide](../advanced/web-projection.md) for DOM mutation-observer exclusions.
14
14
 
15
15
  ## HTML containers
16
16
 
@@ -184,7 +184,11 @@ Select `#seat-projection`, not the HTML `select`:
184
184
  </span>
185
185
  ```
186
186
 
187
- The control's DOM, value and event listeners survive handoff. The plugin still owns native behavior and value synchronization; the prepared image does not accept input.
187
+ The control's DOM, value and event listeners survive handoff; the prepared image
188
+ does not accept input. Native behavior and application events remain with the
189
+ plugin. Optional [value bindings](../configuration/values.md) reflect native values on the
190
+ selected host. With the wrapper above, that host is the span, not the inner select;
191
+ the wrapper's component adapter must forward reflected values to its control.
188
192
 
189
193
  ## Verification scope
190
194
 
@@ -0,0 +1,137 @@
1
+ # Capture and platform limits
2
+
3
+ Choose capture and fallback settings for the actual native surface. Start with
4
+ the [projection contract](projection.md) and [configuration guide](../configuration/README.md);
5
+ this reference describes platform behavior and limits, not additional setup steps.
6
+
7
+ An opaque card in a feed and a translucent player control need different capture
8
+ choices. Drawing the card alone can avoid capturing neighboring content; drawing
9
+ the translucent control alone loses the background it blends with. Use the cases
10
+ below to choose capture behavior, and retain a Web fallback for content that
11
+ cannot be captured. These choices preserve available pixels, not live video or
12
+ all effects of the platform compositor.
13
+
14
+ ## Android capture modes
15
+
16
+ For opaque, software-renderable Android SDK views, use
17
+ `captureMode = NativeUIProjection.CaptureMode.VIEW` together with an explicit
18
+ `failedFallback` policy (`WEB` unless the consumer maintains native placement). This draws only the View's
19
+ pixels with no surrounding padding, so clipping a partially visible ad cannot bake
20
+ in a toolbar or adjacent content. Video, SurfaceView, translucent views, and content
21
+ requiring Window composition must keep the default `COMPOSITED` mode. The consumer
22
+ must provide a matching, clipped DOM host for the full opaque View; arbitrary native
23
+ outline masks, transforms, and custom clipping are not reproduced.
24
+
25
+ VIEW pixels are cached by native View identity, dimensions, and content revision.
26
+ Ordinary scrolling retains the decoded Web image and updates placement without
27
+ capture, PNG encoding, or image decode. The image covers the host border box at
28
+ local origin (0, 0), independently of native placement acknowledgment latency.
29
+ Opaque images remain painted beneath the live native View between scrolls.
30
+ Before hiding that View, the engine waits for WebView visual readiness and a draw;
31
+ VIEW creates no native entry or return image copies. Return acknowledgment waits
32
+ across the native draw and the following animation frame. A generation check prevents
33
+ a delayed cover callback from hiding content after restoration or suspension.
34
+
35
+ The native Bitmap is recycled after PNG encoding in VIEW mode; the PNG and decoded
36
+ Web image remain cached. Plugin update notifications advance the content revision:
37
+ notify an update whenever content changes without replacing the View. Replacing the
38
+ registration, WebView navigation, reset, or close retires the cache. Encoding stays
39
+ off the UI thread; View drawing and cache ownership stay on it, with the existing
40
+ 8-million-pixel cap.
41
+
42
+ COMPOSITED retains native handoff copies for translucent content. Entry copies wait
43
+ for WebView visual readiness and a draw before retirement. If the 500 ms entry
44
+ deadline expires, the native View is restored before the copy is removed, and a late
45
+ paint acknowledgment is rejected.
46
+
47
+ ## Android library
48
+
49
+ Android uses a Gradle Android library, distributed as an AAR through Maven rather than Capacitor plugin registration. The publication coordinates are `io.github.webview-native-ui:engine:<version>`. Build and verify the local Maven publication with:
50
+
51
+ ```sh
52
+ android/gradlew -p android testDebugUnitTest assembleRelease connectedDebugAndroidTest publishReleasePublicationToVerificationRepository
53
+ ```
54
+
55
+ `NativeUIClippedView` is a low-level helper that applies caller-supplied position and clipping without resetting unchanged dimensions. `NativeUIProjection` manages image handoff and restoration; it does not use this helper to lay out the renderer. The consuming plugin owns normal native placement and size updates. Android does not claim WKWebView-style native overflow binding. High-level registrations share a non-consuming window touch observer: down cancels in-flight capture adoption, up/cancel/focus loss permits preparation again, and teardown removes only that registration. Existing callback dispatch and return values are preserved.
56
+
57
+ `NativeUISnapshots` prepares composited PixelCopy images and local PNG files, then covers/restores from that cache. Encoding runs off the UI thread; only one preparation runs per owner. Notify it of visual changes and interaction, and release tokens on teardown. All view/lifecycle calls run on the UI thread. The plugin retains revision, eligibility and fallback policy.
58
+
59
+ After prepared Web pixels are shown or retired, `NativeUIWebReadiness.prepare` checks readiness for a subsequent WebView draw. Its `current` predicate must validate the owning session, source generation and image revision. The temporary native copy covers this asynchronous handoff; overlay startup itself does not wait. Neither bridge acknowledgement nor this callback proves physical display presentation.
60
+
61
+ An opaque image can remain beneath an opaque native view between handoffs, but
62
+ that policy must not be applied to translucent controls: drawing a translucent
63
+ view over its own captured appearance would composite the content twice. Choose
64
+ capture behavior for the actual view and background, and validate the complete
65
+ integration. Never synthesize clicks through a projected image.
66
+
67
+ ### Translucent Android surfaces
68
+
69
+ Capture the composited window pixels, including the background, instead of drawing the translucent view onto an empty bitmap. Keep the prepared Web image decoded but hidden. At handoff, `cover(token, window, reference, views)` installs a temporary opaque image copy before hiding those native views in the same UI turn. Reveal the Web image, confirm Web drawing readiness, then retire the entry copy. The overlay starts immediately; image encoding is never on this path.
70
+
71
+ For return, install the prepared return copy with `restore`, restore the native views underneath it, hide the Web image, and retire the return copy after Web drawing readiness. This avoids compositing live translucent native pixels over their own snapshot. Validate the token and current surface generation at every asynchronous callback. The temporary copy is noninteractive and bounded; teardown releases both copies and cached resources.
72
+
73
+ This preserves the captured background, not a continuously changing scene behind the image. Background changes must invalidate the cache through the same visual-change notifications as content updates. Video and unsupported capture surfaces retain the consumer's fallback policy.
74
+
75
+ Consumer integration tests belong to their respective repositories. Engine CI exercises its browser, Swift and Android contracts without requiring a sibling renderer checkout.
76
+
77
+ ## Image color precision on iOS
78
+
79
+ Prepared window snapshots use an extended Display P3 Float16 bitmap during capture
80
+ and are encoded as 16-bit Display P3 PNGs on a background queue. The Web projection
81
+ displays the prepared image directly, without a Canvas or WebGPU conversion. Native
82
+ entry and return copies also use the decoded PNG, so both sides share the same
83
+ prepared pixels after color conversion. Encoding and decoding
84
+ stay outside the overlay handoff; consumers do not need a new setting or dependency.
85
+
86
+ This reduces color loss in wide-gamut content but does not guarantee identical Liquid
87
+ Glass appearance: native compositing and conversion to integer PNG can still change
88
+ pixels. Higher precision also increases capture memory and image size. The existing
89
+ fallback policy applies if preparation fails. Android retains its platform capture path.
90
+
91
+ ### Liquid Glass capture differences
92
+
93
+ A captured Liquid Glass control can look different from the live native control
94
+ when it is projected into the WebView. Reflective edge highlights and background
95
+ refraction or blur are not fully preserved by the capture path. Higher color
96
+ precision alone does not resolve this difference.
97
+
98
+ ![Device screenshots comparing captured and live UIKit tab bars on iPhone and iPad](../assets/ios-native-capture-comparison.png)
99
+
100
+ Left: **iPhone 15 Pro Max, iOS 27.2**. Right: **iPad mini (6th generation),
101
+ iPadOS 27.2**. Each device screenshot shows the captured **16-bit Display P3 PNG
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.
105
+
106
+ The system screenshot preserves the live appearance, but the application-level
107
+ capture does not reproduce it completely. On the tested iPhone, the difference was
108
+ also present when the unencoded captured image was displayed in a native
109
+ `UIImageView`, before PNG encoding or WebView display. Drawing the entire
110
+ `UIWindow` into a full-window bitmap and then cropping it did not improve the
111
+ result compared with the Engine's cropped bitmap.
112
+
113
+ System screenshots are evidence for this comparison, not an alternative Engine
114
+ capture backend. These screenshots were obtained through developer tooling with
115
+ Developer Mode enabled on the devices. Application screen-capture APIs such as
116
+ ReplayKit require user consent; manually taking a screenshot also requires user
117
+ action. Cropping a system screenshot would preserve its captured pixels, but does
118
+ not remove these acquisition requirements. The Engine therefore does not adopt
119
+ this route for automatic UI handoffs: ordinary projection must not depend on
120
+ Developer Mode, screenshot actions, or a screen-recording permission flow. See
121
+ [Apple's ReplayKit security documentation](https://support.apple.com/guide/security/replaykit-security-seca5fc039dd/web).
122
+
123
+ We have reported this limitation to Apple and requested a supported way to capture
124
+ the complete appearance without starting a screen-recording session. See the
125
+ [Apple Developer Forums discussion](https://developer.apple.com/forums/thread/853543).
126
+ Apple suggested capturing an ancestor that includes the necessary background;
127
+ the full-window comparison above tests that approach. This remains an observed
128
+ capture limitation, not a confirmed fix or a commitment from Apple. Validate
129
+ translucent controls on physical devices with the backgrounds your plugin supports.
130
+
131
+ ## Temporary storage and application permissions
132
+
133
+ Prepared images stay in the app's private temporary storage. iOS uses `FileManager.default.temporaryDirectory`. On Android, construct `NativeUISnapshots` with a directory inside `context.cacheDir`; do not use shared or external storage. Neither platform needs a photo-library or storage permission prompt for this path. No Capacitor Filesystem plugin is required. See [Apple's sandbox directories](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) and [Android's app-specific storage](https://developer.android.com/training/data-storage/app-specific).
134
+
135
+ `Capacitor.convertFileSrc` maps the returned file URL to the existing WebView local server. It grants no filesystem permission and does not upload the image. If the application sets a Content Security Policy, its `img-src` must allow the local app origin used by that URL; do not enable broad `file:` access or unrelated WebView file-access settings.
136
+
137
+ `release(token)` and owner teardown remove owned snapshot files. Abrupt process termination can leave temporary images until the operating system clears the cache or the app is uninstalled; immediate deletion after a crash is not guaranteed. Keep this storage private and treat it as disposable, not persistent application data.