@webview-native-ui/engine 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +83 -14
- 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/dist/projection/web.js +22 -12
- package/dist/types.d.ts +4 -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 +14 -2
- 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 +70 -9
- package/ios/Sources/WebViewNativeUIEngine/Frame.swift +10 -10
- package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +92 -44
- package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -3
- package/ios/Sources/WebViewNativeUIEngine/ScrollBindings.swift +3 -0
- package/ios/Sources/WebViewNativeUIEngine/ScrollTracker.swift +10 -8
- package/package.json +10 -3
- package/docs/projection.md +0 -364
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Low-level Web projection
|
|
2
|
+
|
|
3
|
+
Use the primitives below only if the surface helpers do not fit your renderer's
|
|
4
|
+
existing ownership model. `createWebProjectionSurface` already manages scopes and
|
|
5
|
+
batch handoffs; manually creating another manager for the same fallback would
|
|
6
|
+
introduce competing ownership.
|
|
7
|
+
|
|
8
|
+
For example, a renderer already owns one fallback container per native panel and
|
|
9
|
+
accepts native updates through its own revision protocol. A projection scope lets
|
|
10
|
+
it retain those ownership decisions while delegating image preparation and return
|
|
11
|
+
coordination. Prefer the surface helpers if page or modal grouping already fits;
|
|
12
|
+
a second manager on the same container would compete for its pixels.
|
|
13
|
+
|
|
14
|
+
A scope binds one existing Web fallback element for its lifetime. Obtain that element from your plugin's DOM; the engine does not create a replacement host or choose a page, stacking order or z-index. A different fallback element requires a new scope. Its Document is derived from `element.ownerDocument`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createWebProjectionScope, createWebProjection } from '@webview-native-ui/engine';
|
|
18
|
+
|
|
19
|
+
const fallback = container.querySelector<HTMLElement>('[data-web-fallback]');
|
|
20
|
+
if (!fallback) throw new Error('Web fallback is missing');
|
|
21
|
+
const scope = createWebProjectionScope(fallback, 'my-plugin');
|
|
22
|
+
const projection = await createWebProjection(
|
|
23
|
+
scope,
|
|
24
|
+
{
|
|
25
|
+
send: (request) => plugin.snapshot(request),
|
|
26
|
+
listen: (listener) => plugin.addListener('snapshotInteraction', listener),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
wake: () => schedulePluginUpdate(),
|
|
30
|
+
blocked: () => overlayIsActive(),
|
|
31
|
+
revision: () => lastSentRevision,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The container, bridge and update callbacks above belong to the consuming plugin. No `document`, `target`, `layer`, CSS selector or layer resolver is passed to the projection manager.
|
|
37
|
+
|
|
38
|
+
1. After an accepted native update, call `projection.consider(revision, sources)`. Sources associate the native IDs and viewport frames with their source DOM; they need not be inside the fallback element. Images are captured and decoded in advance.
|
|
39
|
+
2. Call `projection.changed()` for appearance changes, or `projection.changed(scrollElement)` to invalidate scroll geometry. Layout updates go through `consider`; use `invalidate()` for other geometry changes.
|
|
40
|
+
3. Before covering Native with a Web overlay, call `projection.begin()`. It displays already prepared pixels at the fixed fallback. No new capture or decode starts here. If capture is unavailable, the plugin keeps its ordinary Web fallback.
|
|
41
|
+
4. Exclude `projection.owners` from normal Native updates while the image owns those sources. `visibleOwners` keeps their return pixels while Native is being restored; `preparedOwners` identifies sources with prepared images. These arrays are read-only snapshots.
|
|
42
|
+
5. Call `projection.end()`, send the normal native update, then call `projection.updated(snapshotToken, acceptedIDs, revision)`. A newer acknowledgement containing all captured IDs retires the image.
|
|
43
|
+
6. Call `projection.destroy()` when disposing the surface. It releases only its own images and listener; the plugin owns final Native reset and its DOM.
|
|
44
|
+
|
|
45
|
+
The engine prepares a noninteractive `img` inside the fallback container (directly inside its open ShadowRoot when present) while Native is visible. It reuses the decoded image and DOM node across handoffs, and releases them when the scope is retired or destroyed. The image is `inert`, hidden from accessibility and excluded from hit testing. The existing fallback and its children are never cloned, replaced, removed or assigned `inert`/`aria-hidden`; their listeners, values and application changes survive projection. Both `::before` and `::after` remain available to the application.
|
|
46
|
+
|
|
47
|
+
Supply an existing container such as `div`, `button` or a custom element. Replaced/void elements such as `img`, `input` and `video` remain unsupported as scopes; use their existing fallback container. The renderer may move the same container between parents, but must not replace a low-level scope's element or let two managers control it simultaneously. Notify the manager through `moved()` when changing its placement during projection.
|
|
48
|
+
|
|
49
|
+
The image is absolutely positioned within the fallback, which retains its layout box. Static hosts become positioned while the image is visible. Host and ancestor clipping, transforms and filters still apply. Open ShadowRoot insertion bypasses internal slot wrappers, not the host's own clipping. Closed shadow roots cannot be inspected; provide a fallback container that can render the image.
|
|
50
|
+
|
|
51
|
+
Engine-owned nodes carry `data-native-ui-projection-node` for their entire pooled lifetime. Renderers that inspect fallback children must exclude these nodes from application content discovery. A renderer that uses a MutationObserver to invalidate captures must also ignore insertion/removal and attribute changes of these nodes, changes inside them, and Engine-owned `data-native-ui-snapshot-*` attributes. Continue observing authored content, dimensions and disabled-state changes; ignoring all mutations on a projection host would hide real updates. Otherwise image preparation can invalidate itself and cause repeated capture/release cycles. The engine removes only its own nodes on disposal. Child insertion can affect structural selectors within the fallback, but does not add items alongside it in flex, grid or virtual lists.
|
|
52
|
+
|
|
53
|
+
### Notifications from the existing plugin
|
|
54
|
+
|
|
55
|
+
The low-level manager can subscribe to the existing plugin's events through `options.events`:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
events: {
|
|
59
|
+
addListener: (name, listener) => plugin.addListener(name, listener),
|
|
60
|
+
update: ['setColors', 'setTabs', 'tabSelected'],
|
|
61
|
+
projection: {
|
|
62
|
+
native: ['shown'],
|
|
63
|
+
web: ['transitionStarted'],
|
|
64
|
+
none: ['hidden'],
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
These are **notifications**, not intercepted method names. The plugin emits update events after its native changes succeed. A Web projection event must arrive before movement starts; it uses the previously prepared image. A native event requests restoration, but the image remains until the normal renderer acknowledgement confirms that Native is ready. A hidden event follows the plugin's native hide operation and retires the image; it does not invoke `hide()` or change application-owned fallback DOM. Appearance updates preserve the requested destination, including `none`.
|
|
70
|
+
|
|
71
|
+
For plugins with several surfaces, provide `accepts(event)` to select this scope's stable surface identity. Events shared by `update` and a destination are subscribed once and processed together. Conflicting destinations for the same event are rejected before subscription. Destroying the manager removes all its subscriptions, including registrations that resolve late after initialization fails. No public plugin methods are wrapped or replayed.
|
|
72
|
+
|
|
73
|
+
The manager uses the host runtime's resource URL conversion internally (Capacitor or Cordova). Plugin authors do not supply a URL resolver.
|
|
74
|
+
|
|
75
|
+
This section describes the lower-level manager for renderers that already own batch reconciliation, rather than a single native-view registration. Prefer the native registration in the plugin guides for a new integration; it owns startup, measurements and draw acknowledgements.
|
|
76
|
+
|
|
77
|
+
### Multiple fallbacks in one Native transaction
|
|
78
|
+
|
|
79
|
+
For example, a toolbar contains two button groups with separate fallback hosts,
|
|
80
|
+
but the renderer hides and restores them in one native transaction. Independent
|
|
81
|
+
managers could expose one group before the other. A batch uses one handoff token
|
|
82
|
+
for the groups so they can cross the rendering boundary together.
|
|
83
|
+
|
|
84
|
+
Use `createWebProjectionBatch(transport, options)` when several fallback elements must switch together. It uses the same image engine, one listener and one capture/cover/restore token. Pass `{ scope, element, control, ... }` for each entry to `batch.consider(revision, sources)`. Several native IDs may share one scope, such as a button group.
|
|
85
|
+
|
|
86
|
+
The batch derives its Document from the first nonempty `consider`; all subsequent scopes and sources must belong to that Document. Before this first update, `begin()` does nothing and `destroy()` is safe. A changed fallback requires a new scope. Scope identity participates in cache invalidation; an active presentation retains its captured scopes until restoration finishes.
|
|
87
|
+
|
|
88
|
+
`WebProjectionRequest` is an action-discriminated union. `prepare` requires `revision`, `viewportWidth` and `ids`; `cover` requires `token` and includes `viewportWidth` and destination `parts` for transient movement; `restore` requires `token`, `viewportWidth` and destination `parts`; `painted`, `restored` and `release` require `token`; `reset` takes no payload. Empty preparation results and `{ shown: false }` mean the plugin should use its normal Web fallback. Use the plugin's existing ordered bridge; the engine does not retry user actions.
|
|
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