@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.
- package/README.md +19 -12
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/native/session.d.ts +5 -0
- package/dist/native/session.js +125 -16
- package/dist/native/updates.d.ts +16 -0
- package/dist/native/updates.js +108 -0
- package/dist/native/values.d.ts +5 -0
- package/dist/native/values.js +27 -0
- package/dist/projection/surface.d.ts +35 -0
- package/dist/projection/surface.js +81 -0
- package/dist/projection/surfaces.d.ts +20 -0
- package/dist/projection/surfaces.js +116 -0
- package/docs/README.md +70 -0
- package/docs/advanced/README.md +20 -0
- package/docs/advanced/android-placement.md +48 -0
- package/docs/advanced/native-handoff.md +89 -0
- package/docs/advanced/renderer.md +148 -0
- package/docs/advanced/update-queue.md +58 -0
- package/docs/advanced/web-projection.md +88 -0
- package/docs/assets/ios-native-capture-comparison.png +0 -0
- package/docs/concepts.md +97 -0
- package/docs/configuration/README.md +29 -0
- package/docs/configuration/capture.md +27 -0
- package/docs/configuration/events.md +35 -0
- package/docs/configuration/ionic-overlays.md +65 -0
- package/docs/configuration/values.md +125 -0
- package/docs/cordova.md +1 -1
- package/docs/{capacitor-plugin.md → getting-started/capacitor.md} +45 -47
- package/docs/{cordova-plugin.md → getting-started/cordova.md} +32 -15
- package/docs/getting-started/initialization.md +79 -0
- package/docs/maintenance.md +38 -8
- package/docs/{projection-containers.md → reference/containers.md} +6 -2
- package/docs/reference/platforms.md +137 -0
- package/docs/reference/projection.md +134 -0
- package/docs/reference/state.md +39 -0
- package/ios/Sources/WebViewNativeUIEngine/ClippedView.swift +12 -12
- package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +68 -7
- package/ios/Sources/WebViewNativeUIEngine/Frame.swift +10 -10
- package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +74 -39
- package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +6 -6
- package/ios/Sources/WebViewNativeUIEngine/ScrollBindings.swift +2 -0
- package/ios/Sources/WebViewNativeUIEngine/ScrollTracker.swift +9 -7
- package/package.json +9 -4
- package/docs/projection.md +0 -368
|
Binary file
|
package/docs/concepts.md
ADDED
|
@@ -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
|
@@ -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.
|
|
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
|
|
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: "
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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
|
|
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.
|
|
232
|
-
registering
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
276
|
+
## Next: choose optional behavior
|
|
282
277
|
|
|
283
|
-
|
|
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).
|