@webview-native-ui/engine 1.0.0 → 1.0.2-0
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 +48 -35
- package/WebViewNativeUIControls.podspec +17 -0
- package/dist/controls/activation.d.ts +15 -0
- package/dist/controls/activation.js +370 -0
- package/dist/controls/components/index.d.ts +16 -0
- package/dist/controls/components/index.js +38 -0
- package/dist/controls/components/ion-back-button.d.ts +4 -0
- package/dist/controls/components/ion-back-button.js +16 -0
- package/dist/controls/components/ion-button.d.ts +4 -0
- package/dist/controls/components/ion-button.js +26 -0
- package/dist/controls/components/ion-buttons.d.ts +5 -0
- package/dist/controls/components/ion-buttons.js +34 -0
- package/dist/controls/components/ion-fab.d.ts +4 -0
- package/dist/controls/components/ion-fab.js +164 -0
- package/dist/controls/components/ion-menu-button.d.ts +7 -0
- package/dist/controls/components/ion-menu-button.js +41 -0
- package/dist/controls/components/ion-segment.d.ts +5 -0
- package/dist/controls/components/ion-segment.js +24 -0
- package/dist/controls/components/ion-tab-bar.d.ts +5 -0
- package/dist/controls/components/ion-tab-bar.js +49 -0
- package/dist/controls/components/searchable-tabs.d.ts +20 -0
- package/dist/controls/components/searchable-tabs.js +525 -0
- package/dist/controls/definitions.d.ts +360 -0
- package/dist/controls/definitions.js +1 -0
- package/dist/controls/extensions.d.ts +44 -0
- package/dist/controls/extensions.js +164 -0
- package/dist/controls/foldable/fold-state.d.ts +4 -0
- package/dist/controls/foldable/fold-state.js +8 -0
- package/dist/controls/foldable/types.d.ts +9 -0
- package/dist/controls/foldable/types.js +1 -0
- package/dist/controls/html.d.ts +8 -0
- package/dist/controls/html.js +260 -0
- package/dist/controls/index.d.ts +7 -0
- package/dist/controls/index.js +4 -0
- package/dist/controls/ionic.d.ts +1 -0
- package/dist/controls/ionic.js +1 -0
- package/dist/controls/legacy-layout.d.ts +7 -0
- package/dist/controls/legacy-layout.js +23 -0
- package/dist/controls/overlays/content.d.ts +2 -0
- package/dist/controls/overlays/content.js +55 -0
- package/dist/controls/overlays/controller.d.ts +7 -0
- package/dist/controls/overlays/controller.js +369 -0
- package/dist/controls/overlays/covered-overlay.d.ts +4 -0
- package/dist/controls/overlays/covered-overlay.js +9 -0
- package/dist/controls/overlays/dialog-animation.d.ts +7 -0
- package/dist/controls/overlays/dialog-animation.js +49 -0
- package/dist/controls/overlays/dismiss-animation.d.ts +2 -0
- package/dist/controls/overlays/dismiss-animation.js +14 -0
- package/dist/controls/overlays/focus.d.ts +2 -0
- package/dist/controls/overlays/focus.js +67 -0
- package/dist/controls/overlays/popover.d.ts +10 -0
- package/dist/controls/overlays/popover.js +117 -0
- package/dist/controls/overlays/styles.d.ts +9 -0
- package/dist/controls/overlays/styles.js +194 -0
- package/dist/controls/prehide.d.ts +6 -0
- package/dist/controls/prehide.js +280 -0
- package/dist/controls/runtime.d.ts +5 -0
- package/dist/controls/runtime.js +1685 -0
- package/dist/controls/shared/bridge.d.ts +4 -0
- package/dist/controls/shared/bridge.js +15 -0
- package/dist/controls/shared/candidate.d.ts +23 -0
- package/dist/controls/shared/candidate.js +128 -0
- package/dist/controls/shared/clipping.d.ts +1 -0
- package/dist/controls/shared/clipping.js +1 -0
- package/dist/controls/shared/connection.d.ts +21 -0
- package/dist/controls/shared/connection.js +112 -0
- package/dist/controls/shared/control-options.d.ts +4 -0
- package/dist/controls/shared/control-options.js +14 -0
- package/dist/controls/shared/covered-snapshot.d.ts +35 -0
- package/dist/controls/shared/covered-snapshot.js +41 -0
- package/dist/controls/shared/covered-source.d.ts +22 -0
- package/dist/controls/shared/covered-source.js +72 -0
- package/dist/controls/shared/crossfade.d.ts +11 -0
- package/dist/controls/shared/crossfade.js +71 -0
- package/dist/controls/shared/diagnostics.d.ts +2 -0
- package/dist/controls/shared/diagnostics.js +16 -0
- package/dist/controls/shared/dom.d.ts +45 -0
- package/dist/controls/shared/dom.js +258 -0
- package/dist/controls/shared/events.d.ts +12 -0
- package/dist/controls/shared/events.js +13 -0
- package/dist/controls/shared/horizontal-navigation.d.ts +36 -0
- package/dist/controls/shared/horizontal-navigation.js +90 -0
- package/dist/controls/shared/icons.d.ts +5 -0
- package/dist/controls/shared/icons.js +66 -0
- package/dist/controls/shared/modal-animation.d.ts +21 -0
- package/dist/controls/shared/modal-animation.js +97 -0
- package/dist/controls/shared/modal.d.ts +26 -0
- package/dist/controls/shared/modal.js +261 -0
- package/dist/controls/shared/projection-id.d.ts +2 -0
- package/dist/controls/shared/projection-id.js +2 -0
- package/dist/controls/shared/scroll-placement.d.ts +1 -0
- package/dist/controls/shared/scroll-placement.js +1 -0
- package/dist/controls/shared/tab-bar-width.d.ts +7 -0
- package/dist/controls/shared/tab-bar-width.js +72 -0
- package/dist/controls/shared/toolbar-options.d.ts +10 -0
- package/dist/controls/shared/toolbar-options.js +38 -0
- package/dist/controls/shared/transition-identity.d.ts +3 -0
- package/dist/controls/shared/transition-identity.js +64 -0
- package/dist/controls/shared/vertical-bars-geometry.d.ts +8 -0
- package/dist/controls/shared/vertical-bars-geometry.js +82 -0
- package/dist/controls/shared/vertical-navigation.d.ts +5 -0
- package/dist/controls/shared/vertical-navigation.js +39 -0
- package/dist/controls/startup.d.ts +7 -0
- package/dist/controls/startup.js +72 -0
- package/dist/controls/transport.d.ts +10 -0
- package/dist/controls/transport.js +1 -0
- package/dist/controls/vertical-bars-web-search.d.ts +8 -0
- package/dist/controls/vertical-bars-web-search.js +83 -0
- package/dist/controls/vertical-bars-web.d.ts +2 -0
- package/dist/controls/vertical-bars-web.js +609 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/integration/index.d.ts +36 -0
- package/dist/integration/index.js +25 -0
- package/dist/native/bootstrap.js +1 -1
- package/dist/native/session.js +14 -8
- package/dist/projection/pause.d.ts +10 -0
- package/dist/projection/pause.js +58 -0
- package/dist/projection/styles.d.ts +1 -1
- package/dist/projection/styles.js +0 -1
- package/dist/projection/surfaces.js +11 -8
- package/dist/projection/web.js +48 -13
- package/dist/types.d.ts +5 -1
- package/docs/README.md +17 -10
- package/docs/concepts.md +39 -14
- package/docs/configuration/README.md +4 -2
- package/docs/configuration/ionic-overlays.md +36 -2
- package/docs/configuration/pause-resume.md +51 -0
- package/docs/controls.md +39 -0
- package/docs/getting-started/capacitor.md +3 -3
- package/docs/getting-started/cordova.md +3 -1
- package/docs/getting-started/initialization.md +18 -2
- package/docs/reference/platforms.md +2 -2
- package/docs/reference/projection.md +1 -1
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButton.swift +98 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButtons.swift +24 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIComponents.swift +19 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIFab.swift +80 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUISearchController.swift +449 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUISegment.swift +91 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUITabBar.swift +213 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIToolbarTransition.swift +186 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalBars.swift +696 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalSearch.swift +117 -0
- package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUI.swift +180 -0
- package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUIRendering.swift +38 -0
- package/ios/Sources/WebViewNativeUIControls/NativeUIController.swift +801 -0
- package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayController.swift +131 -0
- package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayHost.swift +85 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIControlRendering.swift +21 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICoveredSnapshots.swift +14 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICrossfade.swift +35 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIHost.swift +8 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIScrollTracker.swift +2 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUISnapshot.swift +206 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIVerticalBarsGeometry.swift +93 -0
- package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +2 -2
- package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +6 -4
- package/ios/Sources/WebViewNativeUIEngine/ProjectionOrigins.swift +31 -0
- package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -4
- package/package.json +30 -5
- package/src/controls/activation.ts +427 -0
- package/src/controls/components/index.ts +45 -0
- package/src/controls/components/ion-back-button.ts +22 -0
- package/src/controls/components/ion-button.ts +28 -0
- package/src/controls/components/ion-buttons.ts +37 -0
- package/src/controls/components/ion-fab.ts +151 -0
- package/src/controls/components/ion-menu-button.ts +37 -0
- package/src/controls/components/ion-segment.ts +27 -0
- package/src/controls/components/ion-tab-bar.ts +55 -0
- package/src/controls/components/searchable-tabs.ts +534 -0
- package/src/controls/definitions.ts +338 -0
- package/src/controls/extensions.ts +198 -0
- package/src/controls/foldable/fold-state.ts +10 -0
- package/src/controls/foldable/types.ts +4 -0
- package/src/controls/html.ts +239 -0
- package/src/controls/index.ts +7 -0
- package/src/controls/ionic.ts +1 -0
- package/src/controls/legacy-layout.ts +21 -0
- package/src/controls/overlays/content.ts +51 -0
- package/src/controls/overlays/controller.ts +394 -0
- package/src/controls/overlays/covered-overlay.ts +11 -0
- package/src/controls/overlays/dialog-animation.ts +53 -0
- package/src/controls/overlays/dismiss-animation.ts +15 -0
- package/src/controls/overlays/focus.ts +64 -0
- package/src/controls/overlays/popover.ts +116 -0
- package/src/controls/overlays/styles.ts +195 -0
- package/src/controls/prehide.ts +285 -0
- package/src/controls/runtime.ts +1731 -0
- package/src/controls/shared/bridge.ts +18 -0
- package/src/controls/shared/candidate.ts +163 -0
- package/src/controls/shared/clipping.ts +1 -0
- package/src/controls/shared/connection.ts +105 -0
- package/src/controls/shared/control-options.ts +20 -0
- package/src/controls/shared/covered-snapshot.ts +52 -0
- package/src/controls/shared/covered-source.ts +78 -0
- package/src/controls/shared/crossfade.ts +70 -0
- package/src/controls/shared/diagnostics.ts +13 -0
- package/src/controls/shared/dom.ts +310 -0
- package/src/controls/shared/events.ts +14 -0
- package/src/controls/shared/horizontal-navigation.ts +106 -0
- package/src/controls/shared/icons.ts +64 -0
- package/src/controls/shared/modal-animation.ts +109 -0
- package/src/controls/shared/modal.ts +286 -0
- package/src/controls/shared/projection-id.ts +2 -0
- package/src/controls/shared/scroll-placement.ts +1 -0
- package/src/controls/shared/tab-bar-width.ts +66 -0
- package/src/controls/shared/toolbar-options.ts +51 -0
- package/src/controls/shared/transition-identity.ts +67 -0
- package/src/controls/shared/vertical-bars-geometry.ts +92 -0
- package/src/controls/shared/vertical-navigation.ts +34 -0
- package/src/controls/startup.ts +74 -0
- package/src/controls/transport.ts +11 -0
- package/src/controls/vertical-bars-web-search.ts +86 -0
- package/src/controls/vertical-bars-web.ts +674 -0
- package/src/dom/geometry.ts +24 -0
- package/src/dom/overlays.ts +196 -0
- package/src/dom/scroll-bindings.ts +66 -0
- package/src/dom/scroll-placement.ts +92 -0
- package/src/dom/visibility.ts +29 -0
- package/src/index.ts +19 -0
- package/src/integration/index.ts +64 -0
- package/src/ionic.ts +34 -0
- package/src/native/bootstrap.ts +132 -0
- package/src/native/bridge.ts +18 -0
- package/src/native/resource-url.ts +11 -0
- package/src/native/session.ts +708 -0
- package/src/native/updates.ts +103 -0
- package/src/native/values.ts +29 -0
- package/src/projection/events.ts +58 -0
- package/src/projection/pause.ts +58 -0
- package/src/projection/placement.ts +34 -0
- package/src/projection/scope.ts +13 -0
- package/src/projection/styles.ts +26 -0
- package/src/projection/surface.ts +88 -0
- package/src/projection/surfaces.ts +119 -0
- package/src/projection/web.ts +1013 -0
- package/src/types.ts +58 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference lib="es2021.weakref" />
|
|
2
|
+
const searches = new WeakMap();
|
|
3
|
+
export const registerNativeSearch = (tabBar, trigger, footer) => {
|
|
4
|
+
const binding = { tabBar, trigger, footer, active: false, focused: false };
|
|
5
|
+
const entries = (searches.get(tabBar.ownerDocument) ?? []).filter((entry) => entry.deref() && entry.deref()?.footer !== footer);
|
|
6
|
+
entries.push(new WeakRef(binding));
|
|
7
|
+
searches.set(tabBar.ownerDocument, entries);
|
|
8
|
+
tabBar.ownerDocument.defaultView?.dispatchEvent(new Event('nativeUIRefresh'));
|
|
9
|
+
return binding;
|
|
10
|
+
};
|
|
11
|
+
export const getNativeSearchBindings = (doc) => (searches.get(doc) ?? []).flatMap((entry) => entry.deref() ?? []);
|
|
12
|
+
export const requestNativeSearch = async (binding, active, focus) => (await runtimes.get(binding.tabBar.ownerDocument)?.search?.(binding, active, focus)) ?? false;
|
|
13
|
+
const runtimes = new WeakMap();
|
|
14
|
+
export const VERTICAL_BARS_TRANSITION_CANCELED = 'iosThemeVerticalBarsTransitionCanceled';
|
|
15
|
+
export const setNativeUIIntegration = (doc, runtime) => {
|
|
16
|
+
if (runtime)
|
|
17
|
+
runtimes.set(doc, runtime);
|
|
18
|
+
else
|
|
19
|
+
runtimes.delete(doc);
|
|
20
|
+
};
|
|
21
|
+
export const hasNativeUIIntegration = (doc) => runtimes.has(doc);
|
|
22
|
+
export const isNativeUI = (element) => element.hasAttribute('data-native-ui-projected');
|
|
23
|
+
export const suspendNativeUI = async (scopes, options) => (await runtimes.get(scopes[0]?.ownerDocument)?.suspend(scopes, options)) ?? (() => { });
|
|
24
|
+
/** Shared geometry used by the theme's transition adapter; no theme import in the plugin. */
|
|
25
|
+
export const nativeWebViewMetrics = { radius: 0 };
|
package/dist/native/bootstrap.js
CHANGED
|
@@ -16,7 +16,7 @@ const send = (id, request) => new Promise((resolve, reject) => {
|
|
|
16
16
|
pending.set(key, { resolve, reject, timer });
|
|
17
17
|
const message = JSON.stringify({ key, request });
|
|
18
18
|
const host = globalThis;
|
|
19
|
-
const channel = host.webkit?.messageHandlers?.[id] ?? host
|
|
19
|
+
const channel = host.webkit?.messageHandlers?.[id] ?? host.nativeUIEngine;
|
|
20
20
|
if (!channel) {
|
|
21
21
|
clearTimeout(timer);
|
|
22
22
|
pending.delete(key);
|
package/dist/native/session.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyNativeValues } from './values.js';
|
|
2
2
|
import { projectionPlacement } from '../projection/placement.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getProjectionPause } from '../projection/pause.js';
|
|
4
4
|
import { readScrollPlacement } from '../dom/scroll-placement.js';
|
|
5
5
|
import { projectionAncestors } from '../dom/geometry.js';
|
|
6
6
|
import { createWebProjection } from '../projection/web.js';
|
|
@@ -19,8 +19,8 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
19
19
|
let ready = false;
|
|
20
20
|
let requestedDestination = 'native';
|
|
21
21
|
let destination = 'native';
|
|
22
|
-
const
|
|
23
|
-
const effectiveDestination = () => requestedDestination === 'native' &&
|
|
22
|
+
const projectionRequest = getProjectionPause(document);
|
|
23
|
+
const effectiveDestination = () => requestedDestination === 'native' && projectionRequest.isPaused(target) ? 'web' : requestedDestination;
|
|
24
24
|
let automaticMotion = false;
|
|
25
25
|
let nativeDragging = false;
|
|
26
26
|
let placementPending = false;
|
|
@@ -575,7 +575,7 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
575
575
|
applyDestination(effectiveDestination());
|
|
576
576
|
});
|
|
577
577
|
observer.observe(document.documentElement, { childList: true, attributes: true, subtree: true });
|
|
578
|
-
const applyDestination = (next, force
|
|
578
|
+
const applyDestination = (next, force) => {
|
|
579
579
|
if (stopped || (!force && next === destination))
|
|
580
580
|
return;
|
|
581
581
|
automaticMotion = false;
|
|
@@ -602,6 +602,9 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
602
602
|
});
|
|
603
603
|
}
|
|
604
604
|
else if (destination === 'web') {
|
|
605
|
+
// A native show starts a new local request; the screen-wide Web request prevents restoration.
|
|
606
|
+
if (force === 'native' && projectionRequest.isPaused())
|
|
607
|
+
manager?.end();
|
|
605
608
|
beginProjection();
|
|
606
609
|
if (!manager?.token)
|
|
607
610
|
void bridge.send({ action: 'visibility', visible: false }).catch(() => { });
|
|
@@ -614,7 +617,10 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
614
617
|
}
|
|
615
618
|
};
|
|
616
619
|
// Let a renderer claim a native child window during the same Ionic event dispatch.
|
|
617
|
-
const
|
|
620
|
+
const stopPause = projectionRequest.subscribe(() => queueMicrotask(() => {
|
|
621
|
+
if (ready)
|
|
622
|
+
applyDestination(effectiveDestination());
|
|
623
|
+
}));
|
|
618
624
|
let events;
|
|
619
625
|
const destroy = async () => {
|
|
620
626
|
if (stopped)
|
|
@@ -624,7 +630,7 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
624
630
|
retireValueRead();
|
|
625
631
|
clearPlacement();
|
|
626
632
|
generation++;
|
|
627
|
-
|
|
633
|
+
stopPause();
|
|
628
634
|
observer.disconnect();
|
|
629
635
|
sizeObserver.disconnect();
|
|
630
636
|
window.removeEventListener('resize', resized);
|
|
@@ -656,7 +662,7 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
656
662
|
placementPending = false;
|
|
657
663
|
requestedDestination = next;
|
|
658
664
|
void bridge.send({ action: 'destination', destination: next }).catch(() => { });
|
|
659
|
-
applyDestination(effectiveDestination(),
|
|
665
|
+
applyDestination(effectiveDestination(), next);
|
|
660
666
|
}
|
|
661
667
|
if (updated) {
|
|
662
668
|
void refreshValues();
|
|
@@ -677,7 +683,7 @@ export const startNativeSession = async (config, nativeBridge, plugin = {
|
|
|
677
683
|
show: () => {
|
|
678
684
|
placementPending = false;
|
|
679
685
|
requestedDestination = 'native';
|
|
680
|
-
applyDestination(effectiveDestination(),
|
|
686
|
+
applyDestination(effectiveDestination(), 'native');
|
|
681
687
|
},
|
|
682
688
|
hide: () => {
|
|
683
689
|
requestedDestination = destination = 'none';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const getProjectionPause: (doc: Document) => {
|
|
2
|
+
isPaused: (target?: HTMLElement) => boolean;
|
|
3
|
+
subscribe(listener: () => void): () => void;
|
|
4
|
+
pause: () => void;
|
|
5
|
+
resume: () => void;
|
|
6
|
+
};
|
|
7
|
+
/** Request temporary Web projection across this document. Pair each call with resume(). */
|
|
8
|
+
export declare const pause: () => void;
|
|
9
|
+
/** Release one shared pause, regardless of caller; resume exactly once per pause(). */
|
|
10
|
+
export declare const resume: () => void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getOverlayCoordinator } from '../dom/overlays.js';
|
|
2
|
+
const key = Symbol.for('@webview-native-ui/engine/projection-pause/v1');
|
|
3
|
+
const createPause = (doc) => {
|
|
4
|
+
const overlays = getOverlayCoordinator(doc);
|
|
5
|
+
const listeners = new Set();
|
|
6
|
+
let depth = 0;
|
|
7
|
+
let releaseFrame = 0;
|
|
8
|
+
const paused = () => depth > 0 || releaseFrame !== 0;
|
|
9
|
+
const notify = () => {
|
|
10
|
+
for (const listener of listeners)
|
|
11
|
+
listener();
|
|
12
|
+
};
|
|
13
|
+
const change = (delta) => {
|
|
14
|
+
if (delta < 0 && depth === 0)
|
|
15
|
+
return;
|
|
16
|
+
const wasPaused = paused();
|
|
17
|
+
depth += delta;
|
|
18
|
+
const win = doc.defaultView;
|
|
19
|
+
if (releaseFrame && win) {
|
|
20
|
+
win.cancelAnimationFrame(releaseFrame);
|
|
21
|
+
releaseFrame = 0;
|
|
22
|
+
}
|
|
23
|
+
if (depth === 0 && win) {
|
|
24
|
+
// Give DOM dismissal a paint opportunity before requesting Native return.
|
|
25
|
+
// Keep all consumers paused meanwhile, including Ionic change callbacks.
|
|
26
|
+
releaseFrame = win.requestAnimationFrame(() => {
|
|
27
|
+
releaseFrame = win.requestAnimationFrame(() => {
|
|
28
|
+
releaseFrame = 0;
|
|
29
|
+
notify();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (wasPaused !== paused())
|
|
34
|
+
notify();
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
// Without a target, only the document-wide request applies; renderers own local coverage.
|
|
38
|
+
isPaused: (target) => paused() || (!!target && overlays.isCovered(target)),
|
|
39
|
+
subscribe(listener) {
|
|
40
|
+
listeners.add(listener);
|
|
41
|
+
const stopOverlays = overlays.subscribe(listener);
|
|
42
|
+
return () => {
|
|
43
|
+
listeners.delete(listener);
|
|
44
|
+
stopOverlays();
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
pause: () => change(1),
|
|
48
|
+
resume: () => change(-1),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export const getProjectionPause = (doc) => {
|
|
52
|
+
const shared = doc;
|
|
53
|
+
return (shared[key] ??= createPause(doc));
|
|
54
|
+
};
|
|
55
|
+
/** Request temporary Web projection across this document. Pair each call with resume(). */
|
|
56
|
+
export const pause = () => getProjectionPause(document).pause();
|
|
57
|
+
/** Release one shared pause, regardless of caller; resume exactly once per pause(). */
|
|
58
|
+
export const resume = () => getProjectionPause(document).resume();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const snapshotStyles = "\n@layer native-ui-projection {\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]), :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::part(native) { visibility: hidden !important; }\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::after,\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::after {\n visibility: hidden !important;\n}\n}\n[data-native-ui-snapshot-visible]
|
|
1
|
+
export declare const snapshotStyles = "\n@layer native-ui-projection {\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]), :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::part(native) { visibility: hidden !important; }\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content])::after,\n:is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::before, :is([data-native-ui-snapshot-source], [data-native-ui-snapshot-prepared], [data-native-ui-snapshot-content]) *::after {\n visibility: hidden !important;\n}\n}\n[data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }\n";
|
|
2
2
|
export declare const snapshotShadowStyles = "\n@layer native-ui-projection {\n:host([data-native-ui-snapshot-content]),\n:host([data-native-ui-snapshot-content])::before,\n:host([data-native-ui-snapshot-content])::after,\n:host([data-native-ui-snapshot-content]) *,\n:host([data-native-ui-snapshot-content]) *::before,\n:host([data-native-ui-snapshot-content]) *::after {\n visibility: hidden !important;\n}\n}\n";
|
|
@@ -8,7 +8,6 @@ ${hidden} *::before, ${hidden} *::after {
|
|
|
8
8
|
visibility: hidden !important;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
[data-native-ui-snapshot-visible] { opacity:1!important;overflow:visible!important; }
|
|
12
11
|
[data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }
|
|
13
12
|
`;
|
|
14
13
|
export const snapshotShadowStyles = `
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** Owns asynchronous projection creation and disposal for DOM-backed surfaces. */
|
|
2
2
|
export const createWebProjectionSurfaces = (options = {}) => {
|
|
3
3
|
const entries = new Map();
|
|
4
|
+
const retiring = new Set();
|
|
5
|
+
const retire = (disposal) => {
|
|
6
|
+
const pending = disposal.finally(() => retiring.delete(pending));
|
|
7
|
+
retiring.add(pending);
|
|
8
|
+
return pending;
|
|
9
|
+
};
|
|
4
10
|
let stopped = false;
|
|
5
11
|
const values = () => [...entries.values()].flatMap((entry) => (entry.surface ? [entry.surface] : []));
|
|
6
12
|
let doc;
|
|
@@ -39,7 +45,8 @@ export const createWebProjectionSurfaces = (options = {}) => {
|
|
|
39
45
|
if (motionFrame || stopped || !moving.size || !doc?.defaultView)
|
|
40
46
|
return;
|
|
41
47
|
// Ancestor transforms already carry the image. Only the host's own motion needs rebasing.
|
|
42
|
-
const surfaces = values().filter((surface) => (surface.token || surface.restoring) &&
|
|
48
|
+
const surfaces = values().filter((surface) => (surface.token || surface.restoring) &&
|
|
49
|
+
(surface.restoring ? surface.visibleOwners : surface.owners).some((owner) => moving.has(target(owner))));
|
|
43
50
|
if (!surfaces.length)
|
|
44
51
|
return;
|
|
45
52
|
motionFrame = doc.defaultView.requestAnimationFrame(() => {
|
|
@@ -53,11 +60,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
|
|
|
53
60
|
if (!entry)
|
|
54
61
|
return;
|
|
55
62
|
entries.delete(owner);
|
|
56
|
-
|
|
57
|
-
await entry.surface.destroy();
|
|
58
|
-
// A failed creation has nothing to dispose; ensure() reports that error.
|
|
59
|
-
else
|
|
60
|
-
await entry.ready.catch(() => { });
|
|
63
|
+
await retire(entry.surface ? entry.surface.destroy() : entry.ready.catch(() => { }));
|
|
61
64
|
};
|
|
62
65
|
return {
|
|
63
66
|
get: (owner) => entries.get(owner)?.surface,
|
|
@@ -89,7 +92,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
|
|
|
89
92
|
if (stopped || entries.get(owner) !== entry || !owner.isConnected) {
|
|
90
93
|
if (entries.get(owner) === entry)
|
|
91
94
|
entries.delete(owner);
|
|
92
|
-
await surface.destroy();
|
|
95
|
+
await retire(surface.destroy());
|
|
93
96
|
return undefined;
|
|
94
97
|
}
|
|
95
98
|
entry.surface = surface;
|
|
@@ -110,7 +113,7 @@ export const createWebProjectionSurfaces = (options = {}) => {
|
|
|
110
113
|
listeners?.abort();
|
|
111
114
|
if (motionFrame)
|
|
112
115
|
doc?.defaultView?.cancelAnimationFrame(motionFrame);
|
|
113
|
-
await Promise.all([...entries.keys()].map(remove));
|
|
116
|
+
await Promise.all([...retiring, ...[...entries.keys()].map(remove)]);
|
|
114
117
|
},
|
|
115
118
|
};
|
|
116
119
|
};
|
package/dist/projection/web.js
CHANGED
|
@@ -2,7 +2,8 @@ import { projectionURL } from '../native/resource-url.js';
|
|
|
2
2
|
import { bounded, boundedListener } from '../native/bridge.js';
|
|
3
3
|
import { bindProjectionEvents } from './events.js';
|
|
4
4
|
import { snapshotStyles, snapshotShadowStyles } from './styles.js';
|
|
5
|
-
|
|
5
|
+
import { getProjectionPause } from './pause.js';
|
|
6
|
+
const layout = ({ control: c }, positionIndependent = false) => [
|
|
6
7
|
c.id,
|
|
7
8
|
c.kind,
|
|
8
9
|
c.placement,
|
|
@@ -12,7 +13,7 @@ const layout = (candidates, positionIndependent = false) => JSON.stringify(candi
|
|
|
12
13
|
c.height,
|
|
13
14
|
c.dark,
|
|
14
15
|
c.rtl,
|
|
15
|
-
]
|
|
16
|
+
];
|
|
16
17
|
/** Images are prepared after accepted native updates, never on the overlay's critical path. */
|
|
17
18
|
const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
18
19
|
const captureDelayMs = callbacks.captureDelayMs ?? 50;
|
|
@@ -20,6 +21,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
20
21
|
throw new RangeError('captureDelayMs must be a finite, nonnegative timer interval');
|
|
21
22
|
let destination = 'native';
|
|
22
23
|
const doc = scope.element.ownerDocument;
|
|
24
|
+
const projectionRequest = getProjectionPause(doc);
|
|
25
|
+
let locallyRequested = false;
|
|
23
26
|
const win = doc.defaultView;
|
|
24
27
|
// Native tokens are local to a transport; never use them as global CSS identities.
|
|
25
28
|
const imageIdentity = Array.from(win.crypto.getRandomValues(new Uint32Array(4)), (value) => value.toString(16)).join('-');
|
|
@@ -48,7 +51,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
48
51
|
const relative = origin
|
|
49
52
|
? { ...candidate, control: { ...candidate.control, x: candidate.control.x - origin.x, y: candidate.control.y - origin.y } }
|
|
50
53
|
: candidate;
|
|
51
|
-
return [scopeIDs.get(candidate.scope), layout(
|
|
54
|
+
return [scopeIDs.get(candidate.scope), layout(relative, !!callbacks.opaqueUnderlay)];
|
|
52
55
|
}));
|
|
53
56
|
const sheets = new Map();
|
|
54
57
|
const rootFor = (element) => {
|
|
@@ -249,8 +252,9 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
249
252
|
// One crop may cover a button group, but must have one fixed fallback destination.
|
|
250
253
|
if (result.parts.some((part) => new Set(part.ids.map((id) => selected.find((candidate) => candidate.control.id === id).scope)).size !== 1))
|
|
251
254
|
return;
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
const partSources = result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)));
|
|
256
|
+
await bounded(Promise.all(result.parts.map(async (part, index) => {
|
|
257
|
+
const target = partSources[index].scope.element;
|
|
254
258
|
let pool = imagePool.get(target);
|
|
255
259
|
if (!pool) {
|
|
256
260
|
pool = [];
|
|
@@ -283,9 +287,9 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
283
287
|
data: result,
|
|
284
288
|
images,
|
|
285
289
|
sources: Array.from(new Set(selected.flatMap((item) => [item.element, ...(item.sources ?? [])]))),
|
|
286
|
-
targets:
|
|
287
|
-
scopes:
|
|
288
|
-
parents:
|
|
290
|
+
targets: partSources.map((source) => source.element),
|
|
291
|
+
scopes: partSources.map((source) => source.scope),
|
|
292
|
+
parents: partSources.map((source) => source.scope.element.parentNode),
|
|
289
293
|
frames: [],
|
|
290
294
|
viewport: size,
|
|
291
295
|
revision,
|
|
@@ -323,6 +327,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
323
327
|
preparedVisualRevision = visual;
|
|
324
328
|
adopted = true;
|
|
325
329
|
dispose(previous);
|
|
330
|
+
if (projectionRequest.isPaused())
|
|
331
|
+
present();
|
|
326
332
|
}
|
|
327
333
|
catch {
|
|
328
334
|
// Preparing a visual is optional. Preserve the previous image or the Web fallback.
|
|
@@ -617,11 +623,11 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
617
623
|
const rect = element.getBoundingClientRect();
|
|
618
624
|
return { ...image.data.parts[index], x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
619
625
|
});
|
|
620
|
-
const
|
|
626
|
+
const present = (force = false) => {
|
|
621
627
|
retireRestoration();
|
|
622
|
-
if (stopped || destination === 'none' || active)
|
|
628
|
+
if (stopped || destination === 'none' || (active && !force))
|
|
623
629
|
return;
|
|
624
|
-
const image = cached;
|
|
630
|
+
const image = active ?? cached;
|
|
625
631
|
if (!image ||
|
|
626
632
|
image.viewport !== viewport() ||
|
|
627
633
|
!image.sources.every(validSource) ||
|
|
@@ -684,7 +690,12 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
684
690
|
paintFrame = 0;
|
|
685
691
|
if (!isCurrent())
|
|
686
692
|
return;
|
|
687
|
-
void call({ action: 'painted', token: image.data.token })
|
|
693
|
+
void call({ action: 'painted', token: image.data.token })
|
|
694
|
+
.then((result) => {
|
|
695
|
+
if (isCurrent() && result?.shown === false)
|
|
696
|
+
abandon(image, false);
|
|
697
|
+
})
|
|
698
|
+
.catch(() => {
|
|
688
699
|
if (isCurrent())
|
|
689
700
|
abandon(image, false);
|
|
690
701
|
});
|
|
@@ -696,7 +707,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
696
707
|
abandon(image, false);
|
|
697
708
|
});
|
|
698
709
|
};
|
|
699
|
-
const
|
|
710
|
+
const restore = () => {
|
|
700
711
|
if (!active || stopped || ending)
|
|
701
712
|
return;
|
|
702
713
|
// A return belongs to the captured surface, never to the modal exposed beneath it.
|
|
@@ -795,6 +806,25 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
795
806
|
win.dispatchEvent(new Event(sourcesChangedEvent));
|
|
796
807
|
}
|
|
797
808
|
};
|
|
809
|
+
const begin = () => {
|
|
810
|
+
const starting = !locallyRequested;
|
|
811
|
+
locallyRequested = true;
|
|
812
|
+
present(starting && projectionRequest.isPaused());
|
|
813
|
+
};
|
|
814
|
+
const end = () => {
|
|
815
|
+
locallyRequested = false;
|
|
816
|
+
if (!projectionRequest.isPaused())
|
|
817
|
+
restore();
|
|
818
|
+
};
|
|
819
|
+
const projectionChanged = () => queueMicrotask(() => {
|
|
820
|
+
if (stopped)
|
|
821
|
+
return;
|
|
822
|
+
if (projectionRequest.isPaused())
|
|
823
|
+
present();
|
|
824
|
+
else if (!locallyRequested)
|
|
825
|
+
restore();
|
|
826
|
+
callbacks.wake();
|
|
827
|
+
});
|
|
798
828
|
let eventListener;
|
|
799
829
|
try {
|
|
800
830
|
if (callbacks.events)
|
|
@@ -806,6 +836,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
806
836
|
// Native hide has already happened. Retire images and invalidate pending captures,
|
|
807
837
|
// without calling the plugin's public hide method or disturbing its fallback DOM.
|
|
808
838
|
if (destination === 'none') {
|
|
839
|
+
locallyRequested = false;
|
|
809
840
|
invalidate();
|
|
810
841
|
return;
|
|
811
842
|
}
|
|
@@ -813,6 +844,8 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
813
844
|
begin();
|
|
814
845
|
if (next === 'native') {
|
|
815
846
|
end();
|
|
847
|
+
if (projectionRequest.isPaused())
|
|
848
|
+
present(true);
|
|
816
849
|
schedule();
|
|
817
850
|
}
|
|
818
851
|
if (updated)
|
|
@@ -825,6 +858,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
825
858
|
await bounded(Promise.resolve(listener.remove())).catch(() => { });
|
|
826
859
|
throw error;
|
|
827
860
|
}
|
|
861
|
+
const stopPause = projectionRequest.subscribe(projectionChanged);
|
|
828
862
|
return {
|
|
829
863
|
get owners() {
|
|
830
864
|
return [...owners];
|
|
@@ -854,6 +888,7 @@ const createProjectionEngine = async (scope, transport, callbacks) => {
|
|
|
854
888
|
invalidate,
|
|
855
889
|
async destroy() {
|
|
856
890
|
stopped = true;
|
|
891
|
+
stopPause();
|
|
857
892
|
invalidate();
|
|
858
893
|
imagePool.forEach((pool) => pool.forEach((image) => image.remove()));
|
|
859
894
|
imagePool.clear();
|
package/dist/types.d.ts
CHANGED
|
@@ -79,7 +79,11 @@ export type WebProjectionResult<Request extends WebProjectionRequest> = Request
|
|
|
79
79
|
action: 'cover' | 'restore';
|
|
80
80
|
} ? {
|
|
81
81
|
shown: boolean;
|
|
82
|
-
} :
|
|
82
|
+
} : Request extends {
|
|
83
|
+
action: 'painted';
|
|
84
|
+
} ? {
|
|
85
|
+
shown: boolean;
|
|
86
|
+
} | void : void;
|
|
83
87
|
export interface ListenerHandle {
|
|
84
88
|
remove(): Promise<void> | void;
|
|
85
89
|
}
|
package/docs/README.md
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
# Documentation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Build Web-driven screens with native controls and capabilities. These guides
|
|
4
|
+
explain how to integrate the Engine into a plugin so Web UI and Native UI can
|
|
5
|
+
coexist. Start with [the shared model](concepts.md): how a native component belongs in a Web layout,
|
|
6
|
+
which layer presents it, and how interaction returns to the live view. Scrolling
|
|
7
|
+
and overlays are examples of that coordination, not separate integration models.
|
|
8
|
+
|
|
9
|
+
Then choose the smallest integration that fits your ownership model. Register a
|
|
10
|
+
native view and its DOM container, connect the lifecycle notifications your UI
|
|
11
|
+
needs, and add advanced APIs only if you already own a renderer's batching or placement.
|
|
7
12
|
|
|
8
13
|
## Choose by the problem you need to solve
|
|
9
14
|
|
|
10
|
-
| Case | Start here
|
|
11
|
-
| ---------------------------------------------------------------------------------------- |
|
|
12
|
-
|
|
|
13
|
-
| A
|
|
14
|
-
|
|
|
15
|
-
|
|
|
15
|
+
| Case | Start here | Why |
|
|
16
|
+
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| An eligible native SDK view belongs in a scrolling Web feed | [Basic registration](getting-started/initialization.md) and [platform limits](reference/platforms.md) | Bind it to its DOM container; native tracking or temporary image presentation depends on the platform and layout. |
|
|
18
|
+
| A Web modal must cover a plugin's native tab bar | [Basic registration](getting-started/initialization.md), then the framework integration guide | Register the view and its DOM host, then connect overlay lifecycle; the Engine coordinates handoff and restoration. |
|
|
19
|
+
| A hidden fallback returns with the old toggle selection | [Value bindings](configuration/values.md) | Reflect current native data on the fallback host, independently of where it is displayed. |
|
|
20
|
+
| Background toolbar buttons and foreground modal buttons need different handoff lifetimes | [Renderer surfaces](advanced/renderer.md) | Separate owners so closing one surface cannot restore another's controls. |
|
|
21
|
+
| An existing renderer overlaps updates or loses a native acknowledgement | [Update queue](advanced/update-queue.md) | Serialize fresh-state passes and reconcile the connection without replaying user actions. |
|
|
16
22
|
|
|
17
23
|
The full reading order follows. Skip advanced APIs unless your renderer owns the
|
|
18
24
|
work described in their cases.
|
|
@@ -43,6 +49,7 @@ that require each option. Read only the relevant detail:
|
|
|
43
49
|
- [Value bindings](configuration/values.md): keep the fallback host's component data aligned with native state.
|
|
44
50
|
- [Capture timing](configuration/capture.md): prepare pixels after native appearance settles.
|
|
45
51
|
- [Ionic overlays](configuration/ionic-overlays.md): connect Ionic coverage with one shared application initializer.
|
|
52
|
+
- [Custom Web overlays](configuration/pause-resume.md): re-export pause/resume from a plugin so applications can bracket their own overlays.
|
|
46
53
|
|
|
47
54
|
## 4. Use advanced APIs when you own a renderer
|
|
48
55
|
|
package/docs/concepts.md
CHANGED
|
@@ -1,21 +1,46 @@
|
|
|
1
1
|
# Native UI in Web-driven screens
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The goal is to combine the flexibility of Web layouts and navigation with native
|
|
4
|
+
controls and capabilities in one screen. Applications keep their Web-driven UI;
|
|
5
|
+
plugins supply native components. The Engine coordinates presentation between
|
|
6
|
+
them so each component can coexist with the surrounding screen throughout layout
|
|
7
|
+
changes, interaction and navigation.
|
|
7
8
|
|
|
8
9
|
## Why a bridge call is not enough
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Creating a native view does not give it a place in the DOM. A view above the
|
|
12
|
+
WebView does not automatically follow a scrolling container or participate in
|
|
13
|
+
its clipping and stacking order. A higher Web `z-index` cannot cover it. Simply
|
|
14
|
+
hiding and showing it can reveal a different appearance or restore it at an
|
|
15
|
+
outdated position.
|
|
14
16
|
|
|
15
|
-
The
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
The shared integration model is a native view paired with a DOM container. The
|
|
18
|
+
container identifies where the component belongs in the Web screen. The plugin
|
|
19
|
+
provides native content and reports changes; the Engine coordinates measurement,
|
|
20
|
+
prepared appearance and the handoff between presentation layers.
|
|
21
|
+
|
|
22
|
+
## What coexistence looks like
|
|
23
|
+
|
|
24
|
+
These situations share the same presentation model. Each integration connects the lifecycle and value notifications it needs:
|
|
25
|
+
|
|
26
|
+
| Situation | Desired experience | How the shared model helps |
|
|
27
|
+
| -------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| An eligible native advertisement sits in a Web feed | It stays aligned with the content as the reader scrolls. | The DOM container supplies geometry; supported native tracking or temporary Web projection follows the movement. |
|
|
29
|
+
| A Web alert opens over native navigation controls | The alert covers their appearance, then native interaction returns when it closes. | Lifecycle notifications or the Ionic adapter request coverage; the Engine coordinates image handoff and restoration. |
|
|
30
|
+
| A page or modal changes layout | Its native controls return at the current location rather than an old rectangle. | Geometry and restoration are coordinated with the owning container's lifetime. |
|
|
31
|
+
| A native toggle changes while its Web fallback is hidden | Returning to HTML does not reveal the old value. | Optional value bindings reflect component data independently of presentation. |
|
|
32
|
+
|
|
33
|
+
Scrolling does not always mean moving a live native view. Supported iOS overflow
|
|
34
|
+
layouts can use native scroll bindings; Android uses temporary image presentation,
|
|
35
|
+
and unsupported layouts follow the configured fallback policy. Images do not
|
|
36
|
+
provide live SDK interaction. See [platform limits](reference/platforms.md) and
|
|
37
|
+
[scroll binding constraints](advanced/native-handoff.md#native-scroll-tracking).
|
|
38
|
+
|
|
39
|
+
The Engine prepares images while native content is available and retains them
|
|
40
|
+
through the handoff until native restoration is acknowledged. Preparation is
|
|
41
|
+
separate from presentation so opening an overlay does not wait for capture.
|
|
42
|
+
Application-specific lifecycle decisions still need notifications; registering a
|
|
43
|
+
view does not automatically detect every framework's overlays.
|
|
19
44
|
|
|
20
45
|
## Three presentations, with different purposes
|
|
21
46
|
|
|
@@ -91,7 +116,7 @@ does not promise pixel-identical capture of every native view.
|
|
|
91
116
|
|
|
92
117
|
Validate your actual backgrounds, overlay transitions and restoration on supported
|
|
93
118
|
devices. If capture is unavailable, decide what the user should see in the Web
|
|
94
|
-
fallback before integrating. Read the [capture and platform reference](reference/
|
|
95
|
-
for eligibility
|
|
119
|
+
fallback before integrating. Read the [capture and platform reference](reference/platforms.md)
|
|
120
|
+
and [projection contract](reference/projection.md) for eligibility and failure policies.
|
|
96
121
|
|
|
97
122
|
[Documentation overview](README.md)
|
|
@@ -17,13 +17,15 @@ same position: include that successful selection notification in `receiveListene
|
|
|
17
17
|
| `captureDelayMs` | Prepare images after a 50 ms debounce. | A tab highlight is still animating when its selection event arrives: wait for settled pixels. See [capture timing](capture.md); this does not delay overlay opening. |
|
|
18
18
|
| `canCapture` | Returns true; Engine geometry and platform checks still apply. | An SDK switches from capturable static content to restricted video: prevent an ineligible capture. Return eligibility for the current content. |
|
|
19
19
|
| `failedFallback` | Use the ordinary Web fallback when no suitable image is available. | An SDK view has no usable Web replacement: keep it native during automatic motion only if the renderer maintains its placement. Explicit Web coverage and `none` still take precedence. See [failure policy](../reference/projection.md#native-surface-requirements). |
|
|
20
|
-
| `allowedOrigins` |
|
|
20
|
+
| `allowedOrigins` | Capacitor: bundled `server.url` or local hostname/scheme; localhost defaults when no app server config exists. Cordova: localhost defaults. | Override for programmatic configuration or a narrower trusted origin. On Android set `install` before navigation; registrations inherit it. See [origin matching](../reference/projection.md). |
|
|
21
21
|
| `runtime` | Capacitor. | **Required for Cordova integrations**: select Cordova so notifications and resource URLs use its transport. |
|
|
22
22
|
| `captureMode` (Android) | `COMPOSITED`, including the visible background. | An opaque card scrolls past adjacent content: `VIEW` captures the card alone instead of surrounding window pixels. Do not use it for translucent content, video or SurfaceView. See [capture eligibility](../reference/platforms.md#android-capture-modes). |
|
|
23
23
|
|
|
24
24
|
The Engine exposes [presentation intent](../reference/state.md) separately from
|
|
25
25
|
component values. Neither requires an application-side Engine wrapper. Ionic
|
|
26
26
|
overlay detection is a separate, opt-in [shared initializer](ionic-overlays.md),
|
|
27
|
-
not a registration argument
|
|
27
|
+
not a registration argument. For application-owned overlays, plugins can
|
|
28
|
+
[re-export pause/resume](pause-resume.md) so applications explicitly bracket their
|
|
29
|
+
overlays.
|
|
28
30
|
|
|
29
31
|
[Documentation overview](../README.md)
|