@webview-native-ui/engine 0.0.6 → 0.0.8

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 (45) 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 +125 -16
  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/docs/README.md +70 -0
  15. package/docs/advanced/README.md +20 -0
  16. package/docs/advanced/android-placement.md +48 -0
  17. package/docs/advanced/native-handoff.md +89 -0
  18. package/docs/advanced/renderer.md +148 -0
  19. package/docs/advanced/update-queue.md +58 -0
  20. package/docs/advanced/web-projection.md +88 -0
  21. package/docs/assets/ios-native-capture-comparison.png +0 -0
  22. package/docs/concepts.md +97 -0
  23. package/docs/configuration/README.md +29 -0
  24. package/docs/configuration/capture.md +27 -0
  25. package/docs/configuration/events.md +35 -0
  26. package/docs/configuration/ionic-overlays.md +65 -0
  27. package/docs/configuration/values.md +125 -0
  28. package/docs/cordova.md +1 -1
  29. package/docs/{capacitor-plugin.md → getting-started/capacitor.md} +45 -47
  30. package/docs/{cordova-plugin.md → getting-started/cordova.md} +32 -15
  31. package/docs/getting-started/initialization.md +79 -0
  32. package/docs/maintenance.md +38 -8
  33. package/docs/{projection-containers.md → reference/containers.md} +6 -2
  34. package/docs/reference/platforms.md +137 -0
  35. package/docs/reference/projection.md +134 -0
  36. package/docs/reference/state.md +39 -0
  37. package/ios/Sources/WebViewNativeUIEngine/ClippedView.swift +12 -12
  38. package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +68 -7
  39. package/ios/Sources/WebViewNativeUIEngine/Frame.swift +10 -10
  40. package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +74 -39
  41. package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +6 -6
  42. package/ios/Sources/WebViewNativeUIEngine/ScrollBindings.swift +2 -0
  43. package/ios/Sources/WebViewNativeUIEngine/ScrollTracker.swift +9 -7
  44. package/package.json +9 -4
  45. package/docs/projection.md +0 -368
@@ -0,0 +1,97 @@
1
+ # Native UI in Web-driven screens
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.
7
+
8
+ ## Why a bridge call is not enough
9
+
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.
14
+
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.
19
+
20
+ ## Three presentations, with different purposes
21
+
22
+ | Presentation | What the user sees | Why it is used |
23
+ | ---------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------- |
24
+ | Live native view | The actual interactive control or eligible SDK view | Normal native interaction and rendering. |
25
+ | Web projection | A prepared, noninteractive image in a DOM container | The native appearance can move with Web content or sit beneath a Web overlay. |
26
+ | Web fallback | The application's own HTML, or its chosen empty state | The integration remains usable when a suitable image cannot be prepared. |
27
+
28
+ A projection is not a second live control. It does not forward clicks, animate
29
+ video, or reorder the actual native view beneath individual DOM elements. The
30
+ configured destinations `native`, `web` and `none` express where content should
31
+ belong; the Engine chooses the handoff mechanism. See the
32
+ [projection contract](reference/projection.md#projection-contract) for exact precedence and
33
+ restoration rules.
34
+
35
+ ## Why the integration supplies a DOM container
36
+
37
+ The container gives the image its place in the application's layout and layer
38
+ structure. A native rectangle alone cannot tell the Engine which modal, clipping
39
+ ancestor or scrolling region should contain the image. The container can already
40
+ hold a Web fallback; its content and listeners survive projection.
41
+
42
+ For an ad, this might be a `native-ad` element in a scrolling feed. For a tab bar,
43
+ it might be a fallback navigation container below the page's overlay layer.
44
+ Choose a [supported container](reference/containers.md) with the intended layout
45
+ box. The Engine neither creates the application layout nor supplies an ad or tab
46
+ bar implementation.
47
+
48
+ ## Choose the smallest integration that fits
49
+
50
+ | What you are building | Start here | Why this route |
51
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
52
+ | A Capacitor plugin exposing a native view, such as a tab bar or a capturable advertisement view | [Capacitor plugin guide](getting-started/capacitor.md) | Native registration owns runtime startup, measurements and handoff acknowledgements; keep the existing plugin API. |
53
+ | A Cordova plugin with the same need | [Cordova plugin guide](getting-started/cordova.md) | The same lifecycle uses Cordova's native hooks and resource handler. |
54
+ | A renderer already batching many controls across pages and modals | [Renderer guide](advanced/renderer.md) | Group controls by lifecycle while retaining the renderer's existing placement and update protocol. |
55
+ | An Ionic application using an Engine-enabled plugin | [Ionic overlay guide](configuration/ionic-overlays.md) | One shared monitor connects Ionic overlay lifecycle to projection coverage. |
56
+
57
+ See [native initialization](getting-started/initialization.md) for the minimal registration
58
+ and a guide to optional settings versus advanced APIs.
59
+
60
+ A Web modal alone does not require the renderer APIs. Start with native
61
+ `NativeUIProjection` registration unless you already own multi-control
62
+ reconciliation. Applications normally gain Engine support through an integrating
63
+ plugin rather than creating registrations themselves. Other Web frameworks can
64
+ use their own transition notifications; Ionic is optional.
65
+
66
+ ## Where responsibilities meet
67
+
68
+ | Engine | Consuming plugin |
69
+ | ------------------------------------------------------------------- | -------------------------------------------------------------------- |
70
+ | DOM measurement, image geometry and supported native scroll binding | Creating, attaching, positioning and sizing native views |
71
+ | Startup, event subscriptions and native draw acknowledgements | Providing the fallback DOM at the correct layer |
72
+ | Prepared image cache, decoding, display and retirement | Reporting appearance changes and the intended projection destination |
73
+ | Native capture and temporary return images | Providing native views and SDK capture eligibility |
74
+ | Image invalidation, token cleanup and observer teardown | Owning business actions and the interactive Web fallback |
75
+
76
+ A DOM container does not necessarily contain an interactive Web replacement: a button plugin may provide an HTML button as its fallback, while an advertising plugin may leave the container empty when native content is unavailable. Image handoff is explicit and noninteractive. The engine never forwards clicks through an image or replaces SDK click/impression handling.
77
+
78
+ Native registrations can also [reflect current component values](configuration/values.md)
79
+ into their fallback host. This is independent of [presentation intent](reference/state.md):
80
+ a selected tab is component data, while `native`, `web` and `none` describe where
81
+ it belongs. Application events and framework form integration remain with the
82
+ component adapter.
83
+
84
+ ## Decide whether the limits fit your UI
85
+
86
+ Images freeze both the control and any captured background. Content that must stay
87
+ live or interactive while covered needs a different presentation policy. SDK
88
+ capture restrictions still apply, and some platform compositor effects, including
89
+ Liquid Glass highlights, can differ from their captured appearance. The Engine
90
+ does not promise pixel-identical capture of every native view.
91
+
92
+ Validate your actual backgrounds, overlay transitions and restoration on supported
93
+ 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.
96
+
97
+ [Documentation overview](README.md)
@@ -0,0 +1,29 @@
1
+ # Configure a native registration
2
+
3
+ Start with [basic initialization](../getting-started/initialization.md). The settings below belong
4
+ to that same registration; add only those required by your component.
5
+
6
+ ## Optional behavior
7
+
8
+ Optional means there is a default, not that a changing UI can omit its notifications.
9
+ For example, a tab selection changes appearance even if the tab bar stays at the
10
+ same position: include that successful selection notification in `receiveListeners.update`.
11
+
12
+ | Input | When omitted | Case and reason to configure it |
13
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14
+ | `receiveListeners` | No plugin events are subscribed. Initial intent comes from native view visibility; internal DOM and scroll handling still run. | A tab selection changes the highlighted item: connect `update` to refresh pixels and bound values, `projection` for presentation changes, and `match` for per-surface routing. See [event wiring](events.md#choose-successful-update-and-presentation-events) and the Cordova guide's `receive` forwarding. |
15
+ | `readValues` | No native-to-DOM value reads. | A native toggle changes while its fallback is hidden: read its current state so the fallback does not return with the old value. See [value bindings](values.md). |
16
+ | `values` | With `readValues`, all returned keys become `data-native-ui-value-*` attributes. Without `readValues`, no reflection occurs. | A custom element renders from `checked` instead of attributes: map that key to its host property. This does not synthesize application events. |
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
+ | `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
+ | `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. |
21
+ | `runtime` | Capacitor. | **Required for Cordova integrations**: select Cordova so notifications and resource URLs use its transport. |
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
+
24
+ The Engine exposes [presentation intent](../reference/state.md) separately from
25
+ component values. Neither requires an application-side Engine wrapper. Ionic
26
+ overlay detection is a separate, opt-in [shared initializer](ionic-overlays.md),
27
+ not a registration argument; other frameworks report their lifecycle explicitly.
28
+
29
+ [Documentation overview](../README.md)
@@ -0,0 +1,27 @@
1
+ # Configure capture timing
2
+
3
+ Use this when a successful state change is reported before its visual animation
4
+ ends. For example, a tab-selection event can arrive while the highlight is still
5
+ enlarged; capturing immediately would preserve that intermediate shape beneath
6
+ the next Web overlay. Delay background preparation until the control settles.
7
+ Keep the default when the measured appearance is already settled; a longer delay
8
+ also keeps the previous image available for longer.
9
+
10
+ `captureDelayMs` is an optional registration setting shared by Capacitor and Cordova.
11
+ It defaults to **50 milliseconds** on iOS, Android and the lower-level `createWebProjection`
12
+ options. Values must be finite and nonnegative. It uses the existing debounce: a new
13
+ update or interaction restarts preparation after activity settles. It does not add a
14
+ second timer or delay the overlay's presentation.
15
+
16
+ Until replacement pixels are ready, the last completed image remains available. If
17
+ there is no completed image, normal Web fallback applies. Opening an overlay during
18
+ the debounce does not wait for a new capture. Consequently, increasing the delay also
19
+ increases the period in which the previous appearance may be used after an update.
20
+
21
+ A starting recommendation for **UITabBar Liquid Glass selection animations is 600 ms**.
22
+ In iOS 27.0 iPhone and iOS 27.1 Duo simulator comparisons, 50 ms captured an enlarged
23
+ selection, 300 ms still retained a small residual change, and 600 ms matched the settled
24
+ appearance observed at 1000 ms. This is a recommendation for that interaction, not an
25
+ Apple timing contract or a universal Liquid Glass duration. Measure your control's
26
+ selection, reselection and successive updates before choosing a value. Continuously
27
+ changing content has no guaranteed settled snapshot.
@@ -0,0 +1,35 @@
1
+ # Connect plugin notifications
2
+
3
+ Use `receiveListeners` when native content or presentation changes after initialization.
4
+ These are successful-change notifications, not method interception. The
5
+ [Capacitor](../getting-started/capacitor.md) and [Cordova](../getting-started/cordova.md) guides show where
6
+ to forward them; this page defines the shared event contract.
7
+
8
+ For example, selecting another native tab changes the highlighted item but not
9
+ the bar's position. Without an update notification, opening a Web sheet can reuse
10
+ an image of the old selection. Emit the plugin's successful selection event and
11
+ include it in `update`. A hide operation is different: route its event to `none`
12
+ so a later appearance update cannot make the hidden bar reappear.
13
+
14
+ ## Startup and routing
15
+
16
+ Forwarding notifications from the plugin lets the Engine retain the latest matching projection destination before the Web runtime subscribes. No separate state variable or initial-state callback is needed. Keep the plugin's application event delivery unchanged: 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.
17
+
18
+ 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.
19
+
20
+ 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.
21
+
22
+ ## Choose successful update and presentation events
23
+
24
+ All incoming event declarations belong to `receiveListeners`. Its `update` list refreshes the image and any configured value bindings, its `projection` lists select the destination, and its optional `match` filters notifications for the owning surface.
25
+
26
+ | Notification | Where to emit it in the existing plugin |
27
+ | --------------------------------------- | ------------------------------------------------------------------------------------- |
28
+ | `appearanceChanged` (an `update` event) | After a successful native appearance update, such as `setColors` |
29
+ | `transitionStarted` (`web`) | Before the fallback DOM starts moving or a Web layer covers the native view |
30
+ | `shown` (`native`) | After the plugin's existing show operation, or when returning from the Web projection |
31
+ | `hidden` (`none`) | After the plugin's existing hide operation |
32
+
33
+ The names in the examples are conventions you choose, not reserved Engine methods. An `update` event refreshes the prepared image and configured values without changing the requested destination. It does not call `setColors` again.
34
+
35
+ 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.
@@ -0,0 +1,65 @@
1
+ # Cover native UI with Ionic overlays
2
+
3
+ Use this adapter when an Ionic modal or another covering Web overlay should appear
4
+ above Engine-managed native content. Native registration handles capture and
5
+ restoration, but it cannot infer Ionic's presentation lifecycle from a native
6
+ view. This shared monitor supplies that coverage decision for all registrations.
7
+
8
+ Integrate the plugin through the [Capacitor](../getting-started/capacitor.md) or
9
+ [Cordova](../getting-started/cordova.md) guide first. Non-Ionic applications use their own
10
+ lifecycle notifications and do not install this adapter.
11
+
12
+ For example, opening an Ionic settings modal should cover the background native
13
+ tab bar while leaving the modal's own native close button usable. A blanket hide
14
+ notification cannot express that relationship. The shared monitor evaluates each
15
+ host against the overlay stack, so independent plugins use the same coverage rule.
16
+
17
+ ## Enable once
18
+
19
+ For an Ionic application, call this once during application startup. When calling it directly from the application, install `@webview-native-ui/engine` as a direct application dependency:
20
+
21
+ ```ts
22
+ import { enableSupportIonicOverlays } from '@webview-native-ui/engine/ionic';
23
+
24
+ enableSupportIonicOverlays();
25
+ ```
26
+
27
+ Plugin maintainers can include this snippet in their application setup instructions, or call it inside an existing JS initializer. `@rdlabo/capacitor-native-ui` already calls the same initializer internally; its users need no additional call. Non-Ionic applications omit it. Native registration alone does not install Ionic event listeners.
28
+
29
+ ## Coverage and lifetime
30
+
31
+ One monitor is shared per document, including between the npm module and the runtime injected by Swift/Android. Repeated initialization does not add listeners or erase explicit configuration. Destroying one projection removes only its subscription; it does not stop the monitor used by other plugins. The monitor lives with the document.
32
+
33
+ The default covers Modal, Popover, Alert, Action Sheet, Loading, Picker, Toast and Menu lifecycle events. It compares each projection's DOM with the active overlay stack: content inside the front modal stays native, while covered content temporarily uses its prepared image. Toasts and inactive sheet backdrops do not cover a surface. Nested overlays keep it covered until the remaining stack permits restoration. Missing images retain the normal Web fallback. This temporary coverage never overwrites a plugin's explicit `web` or `none` destination. Projections registered later read the current state.
34
+
35
+ A popover displayed in a native window is different from a popover rendered in
36
+ the WebView: its DOM placeholder must not be treated as a covering Web overlay.
37
+ The `@rdlabo/capacitor-native-ui` plugin reports this native presentation ownership
38
+ to the monitor. If its native presentation fails, it releases that ownership and
39
+ the monitor resumes ordinary Web overlay coverage. This exception depends on an
40
+ explicit integration; it is not inferred from the popover's DOM alone.
41
+
42
+ ## Customize unusual layouts
43
+
44
+ Optional settings are document-wide. Keep the defaults for ordinary Ionic overlays.
45
+ Use `exclude` for an application overlay that should not cover native content, and
46
+ `additional` when the same app also uses a custom dialog with its own lifecycle
47
+ events. Use `covers` only when DOM containment and backdrop state do not describe
48
+ which hosts that custom layout actually covers:
49
+
50
+ ```ts
51
+ enableSupportIonicOverlays({
52
+ exclude: '[data-nonblocking-overlay]',
53
+ additional: [
54
+ {
55
+ selector: 'app-dialog',
56
+ events: {
57
+ willPresent: 'appDialogWillPresent',
58
+ didDismiss: 'appDialogDidDismiss',
59
+ },
60
+ },
61
+ ],
62
+ });
63
+ ```
64
+
65
+ Use `covers(overlay, target)` only when a registered DOM projection needs a custom coverage rule; return `undefined` for the default, or a boolean to override it. Exclusions and explicit native-window ownership take precedence. The standard relationship uses overlay order, DOM containment and backdrop state, rather than attempting to interpret arbitrary CSS stacking contexts. Configure unusual layouts explicitly.
@@ -0,0 +1,125 @@
1
+ # Reflect component values into the DOM
2
+
3
+ A native toggle, tab selection or text control can change while its Web fallback
4
+ is hidden. Reflect its current values so that the fallback and application can
5
+ observe the same selection when they become visible. This is separate from
6
+ [projection state](../reference/state.md): a selected tab is component data, not a
7
+ request to move its rendering between native and Web.
8
+
9
+ For example, a user turns a native toggle on while its Web fallback is hidden.
10
+ If capture later becomes unavailable, an unchanged fallback would show it off.
11
+ Return the current boolean through `readValues` and map it to the supported host's
12
+ `checked` property. Application form notifications still use the component's
13
+ existing adapter; the Engine only keeps the displayed data aligned.
14
+
15
+ Configure this connection in the plugin's existing `NativeUIProjection`
16
+ initialization. No application-side wrapper is required. These options work with
17
+ both Capacitor and Cordova registrations. Values are applied to the same element
18
+ matched by `projectionSelector`, not a child control or a separate binding target.
19
+ Choose a [supported projection container](../reference/containers.md): for example,
20
+ an `ion-toggle` or an application custom element exposing `checked` or `value`.
21
+ HTML `input`, `select` and `textarea` are not direct projection containers. When
22
+ using a wrapper around one, its own attribute observer or property setter must
23
+ forward the value to the inner control. The Engine does not discover that control.
24
+ These bindings belong to native registrations; the lower-level renderer APIs keep
25
+ the renderer's existing value synchronization.
26
+
27
+ ## Start with data attributes
28
+
29
+ Supply `readValues` to enable reflection. It reads the plugin's **current** values,
30
+ including programmatic changes; event payloads are not used as the value store.
31
+ Every returned key defaults to `data-native-ui-value-<key>`. Omit `readValues` to
32
+ keep the existing behavior without value requests.
33
+
34
+ For example, these arguments in a Swift registration expose the selected tab as
35
+ `data-native-ui-value-selected`:
36
+
37
+ ```swift
38
+ receiveListeners: .init(update: ["updated", "tabSelected"]),
39
+ readValues: { [weak self] in
40
+ ["selected": self?.selectedTabID ?? ""]
41
+ },
42
+ ```
43
+
44
+ Use lowercase keys starting with a letter, followed by letters, digits or hyphens.
45
+ Return JSON-compatible values. Strings are written literally; numbers, booleans,
46
+ arrays, objects and null use JSON text. Null becomes the attribute text `null`;
47
+ an empty string produces an empty attribute. Neither removes the attribute, and
48
+ an omitted key leaves the previous value unchanged. Use `NSNull()` in Swift or
49
+ `JSONObject.NULL` in Kotlin to return JSON null (`JSONObject.put(key, null)`
50
+ removes the key instead).
51
+
52
+ The Engine reads values when the matching DOM appears, after page reconnection,
53
+ and on configured `update` events. It ignores replies for a departed
54
+ DOM or superseded request. The callback runs on the native UI thread: read stored
55
+ state rather than performing blocking work.
56
+
57
+ ## Choose a DOM property when the component needs one
58
+
59
+ A supported Web component host may expose `value`, `checked` or another data
60
+ property instead of reading attributes. Override only those keys with `values`:
61
+
62
+ ```swift
63
+ receiveListeners: .init(update: ["updated", "selectionChanged"]),
64
+ values: ["selected": .init(property: "value")],
65
+ readValues: { [weak self] in
66
+ ["selected": self?.selectedTabID ?? ""]
67
+ },
68
+ ```
69
+
70
+ The equivalent Kotlin registration arguments are:
71
+
72
+ ```kotlin
73
+ receiveListeners = NativeUIProjection.ReceiveListeners(
74
+ update = listOf("updated", "selectionChanged")
75
+ ),
76
+ values = mapOf(
77
+ "selected" to NativeUIProjection.ValueBinding(property = "value")
78
+ ),
79
+ readValues = { JSONObject().put("selected", selectedTabID) },
80
+ ```
81
+
82
+ Property assignment preserves JSON types: a boolean stays a boolean instead of
83
+ becoming an attribute string. Choose a writable data property supported by the
84
+ component. HTML, style and event-handler properties are not binding destinations.
85
+ A property override replaces attribute reflection for that key. Other returned
86
+ keys still use the default data attributes.
87
+
88
+ When property bindings target an autonomous custom element (such as `<my-toggle>`)
89
+ that is not defined yet, the
90
+ Engine waits for its definition before reading and assigning values. This keeps
91
+ an early assignment from hiding the component's eventual setter. It rechecks DOM
92
+ ownership and pending updates after the wait. Attribute-only bindings can reflect
93
+ values before the custom element is defined. Define customized built-ins (such as
94
+ `<input is="my-input">`) before enabling property bindings; their custom definition
95
+ name is not reliably available from the DOM.
96
+
97
+ ## Keep application events in the component adapter
98
+
99
+ The Engine connects native state to the DOM. It does not implement framework
100
+ two-way binding or dispatch `input`, `change`, `ionChange` or other component
101
+ events. Initial reads, user selections and programmatic updates all follow the
102
+ same reflection path. Concurrent reads use the newest response, and responses
103
+ for a departed container are ignored.
104
+
105
+ Keep application notifications on the plugin's existing event API. The adapter
106
+ for a form control, custom element or framework decides how that native event
107
+ updates the application model. For example, a tab plugin's `tabSelected` event
108
+ can update the application's selected-tab state while the Engine separately
109
+ reflects the current selection into the fallback's `value` property. Do not
110
+ read the DOM from that event handler expecting the asynchronous Engine refresh
111
+ to have completed; use the plugin event's documented payload instead.
112
+
113
+ A custom element may react to a reflected attribute through its
114
+ `attributeChangedCallback`, or accept a property through its setter. Use that
115
+ hook to render the current value. Setters that emit application events retain
116
+ their own behavior; the Engine neither suppresses those events nor invents a
117
+ universal event shape. Reflecting a property does not by itself connect a
118
+ framework form.
119
+
120
+ The Engine does not invoke native setters, observe DOM properties to send values
121
+ back to native, or replay actions. Retain the component's existing Web-to-native
122
+ handler and avoid unconditional setter/event loops.
123
+
124
+ On teardown, reflected values remain available to the Web fallback. The Engine
125
+ removes its projection-state marker, but does not reset component data.
package/docs/cordova.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Cordova plugin integration
2
2
 
3
- This guide has moved to [Cordova plugin integration](cordova-plugin.md).
3
+ This guide has moved to [Cordova plugin integration](getting-started/cordova.md).
@@ -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).