@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.
Files changed (238) hide show
  1. package/README.md +48 -35
  2. package/WebViewNativeUIControls.podspec +17 -0
  3. package/dist/controls/activation.d.ts +15 -0
  4. package/dist/controls/activation.js +370 -0
  5. package/dist/controls/components/index.d.ts +16 -0
  6. package/dist/controls/components/index.js +38 -0
  7. package/dist/controls/components/ion-back-button.d.ts +4 -0
  8. package/dist/controls/components/ion-back-button.js +16 -0
  9. package/dist/controls/components/ion-button.d.ts +4 -0
  10. package/dist/controls/components/ion-button.js +26 -0
  11. package/dist/controls/components/ion-buttons.d.ts +5 -0
  12. package/dist/controls/components/ion-buttons.js +34 -0
  13. package/dist/controls/components/ion-fab.d.ts +4 -0
  14. package/dist/controls/components/ion-fab.js +164 -0
  15. package/dist/controls/components/ion-menu-button.d.ts +7 -0
  16. package/dist/controls/components/ion-menu-button.js +41 -0
  17. package/dist/controls/components/ion-segment.d.ts +5 -0
  18. package/dist/controls/components/ion-segment.js +24 -0
  19. package/dist/controls/components/ion-tab-bar.d.ts +5 -0
  20. package/dist/controls/components/ion-tab-bar.js +49 -0
  21. package/dist/controls/components/searchable-tabs.d.ts +20 -0
  22. package/dist/controls/components/searchable-tabs.js +525 -0
  23. package/dist/controls/definitions.d.ts +360 -0
  24. package/dist/controls/definitions.js +1 -0
  25. package/dist/controls/extensions.d.ts +44 -0
  26. package/dist/controls/extensions.js +164 -0
  27. package/dist/controls/foldable/fold-state.d.ts +4 -0
  28. package/dist/controls/foldable/fold-state.js +8 -0
  29. package/dist/controls/foldable/types.d.ts +9 -0
  30. package/dist/controls/foldable/types.js +1 -0
  31. package/dist/controls/html.d.ts +8 -0
  32. package/dist/controls/html.js +260 -0
  33. package/dist/controls/index.d.ts +7 -0
  34. package/dist/controls/index.js +4 -0
  35. package/dist/controls/ionic.d.ts +1 -0
  36. package/dist/controls/ionic.js +1 -0
  37. package/dist/controls/legacy-layout.d.ts +7 -0
  38. package/dist/controls/legacy-layout.js +23 -0
  39. package/dist/controls/overlays/content.d.ts +2 -0
  40. package/dist/controls/overlays/content.js +55 -0
  41. package/dist/controls/overlays/controller.d.ts +7 -0
  42. package/dist/controls/overlays/controller.js +369 -0
  43. package/dist/controls/overlays/covered-overlay.d.ts +4 -0
  44. package/dist/controls/overlays/covered-overlay.js +9 -0
  45. package/dist/controls/overlays/dialog-animation.d.ts +7 -0
  46. package/dist/controls/overlays/dialog-animation.js +49 -0
  47. package/dist/controls/overlays/dismiss-animation.d.ts +2 -0
  48. package/dist/controls/overlays/dismiss-animation.js +14 -0
  49. package/dist/controls/overlays/focus.d.ts +2 -0
  50. package/dist/controls/overlays/focus.js +67 -0
  51. package/dist/controls/overlays/popover.d.ts +10 -0
  52. package/dist/controls/overlays/popover.js +117 -0
  53. package/dist/controls/overlays/styles.d.ts +9 -0
  54. package/dist/controls/overlays/styles.js +194 -0
  55. package/dist/controls/prehide.d.ts +6 -0
  56. package/dist/controls/prehide.js +280 -0
  57. package/dist/controls/runtime.d.ts +5 -0
  58. package/dist/controls/runtime.js +1685 -0
  59. package/dist/controls/shared/bridge.d.ts +4 -0
  60. package/dist/controls/shared/bridge.js +15 -0
  61. package/dist/controls/shared/candidate.d.ts +23 -0
  62. package/dist/controls/shared/candidate.js +128 -0
  63. package/dist/controls/shared/clipping.d.ts +1 -0
  64. package/dist/controls/shared/clipping.js +1 -0
  65. package/dist/controls/shared/connection.d.ts +21 -0
  66. package/dist/controls/shared/connection.js +112 -0
  67. package/dist/controls/shared/control-options.d.ts +4 -0
  68. package/dist/controls/shared/control-options.js +14 -0
  69. package/dist/controls/shared/covered-snapshot.d.ts +35 -0
  70. package/dist/controls/shared/covered-snapshot.js +41 -0
  71. package/dist/controls/shared/covered-source.d.ts +22 -0
  72. package/dist/controls/shared/covered-source.js +72 -0
  73. package/dist/controls/shared/crossfade.d.ts +11 -0
  74. package/dist/controls/shared/crossfade.js +71 -0
  75. package/dist/controls/shared/diagnostics.d.ts +2 -0
  76. package/dist/controls/shared/diagnostics.js +16 -0
  77. package/dist/controls/shared/dom.d.ts +45 -0
  78. package/dist/controls/shared/dom.js +258 -0
  79. package/dist/controls/shared/events.d.ts +12 -0
  80. package/dist/controls/shared/events.js +13 -0
  81. package/dist/controls/shared/horizontal-navigation.d.ts +36 -0
  82. package/dist/controls/shared/horizontal-navigation.js +90 -0
  83. package/dist/controls/shared/icons.d.ts +5 -0
  84. package/dist/controls/shared/icons.js +66 -0
  85. package/dist/controls/shared/modal-animation.d.ts +21 -0
  86. package/dist/controls/shared/modal-animation.js +97 -0
  87. package/dist/controls/shared/modal.d.ts +26 -0
  88. package/dist/controls/shared/modal.js +261 -0
  89. package/dist/controls/shared/projection-id.d.ts +2 -0
  90. package/dist/controls/shared/projection-id.js +2 -0
  91. package/dist/controls/shared/scroll-placement.d.ts +1 -0
  92. package/dist/controls/shared/scroll-placement.js +1 -0
  93. package/dist/controls/shared/tab-bar-width.d.ts +7 -0
  94. package/dist/controls/shared/tab-bar-width.js +72 -0
  95. package/dist/controls/shared/toolbar-options.d.ts +10 -0
  96. package/dist/controls/shared/toolbar-options.js +38 -0
  97. package/dist/controls/shared/transition-identity.d.ts +3 -0
  98. package/dist/controls/shared/transition-identity.js +64 -0
  99. package/dist/controls/shared/vertical-bars-geometry.d.ts +8 -0
  100. package/dist/controls/shared/vertical-bars-geometry.js +82 -0
  101. package/dist/controls/shared/vertical-navigation.d.ts +5 -0
  102. package/dist/controls/shared/vertical-navigation.js +39 -0
  103. package/dist/controls/startup.d.ts +7 -0
  104. package/dist/controls/startup.js +72 -0
  105. package/dist/controls/transport.d.ts +10 -0
  106. package/dist/controls/transport.js +1 -0
  107. package/dist/controls/vertical-bars-web-search.d.ts +8 -0
  108. package/dist/controls/vertical-bars-web-search.js +83 -0
  109. package/dist/controls/vertical-bars-web.d.ts +2 -0
  110. package/dist/controls/vertical-bars-web.js +609 -0
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +1 -0
  113. package/dist/integration/index.d.ts +36 -0
  114. package/dist/integration/index.js +25 -0
  115. package/dist/native/bootstrap.js +1 -1
  116. package/dist/native/session.js +14 -8
  117. package/dist/projection/pause.d.ts +10 -0
  118. package/dist/projection/pause.js +58 -0
  119. package/dist/projection/styles.d.ts +1 -1
  120. package/dist/projection/styles.js +0 -1
  121. package/dist/projection/surfaces.js +11 -8
  122. package/dist/projection/web.js +48 -13
  123. package/dist/types.d.ts +5 -1
  124. package/docs/README.md +17 -10
  125. package/docs/concepts.md +39 -14
  126. package/docs/configuration/README.md +4 -2
  127. package/docs/configuration/ionic-overlays.md +36 -2
  128. package/docs/configuration/pause-resume.md +51 -0
  129. package/docs/controls.md +39 -0
  130. package/docs/getting-started/capacitor.md +3 -3
  131. package/docs/getting-started/cordova.md +3 -1
  132. package/docs/getting-started/initialization.md +18 -2
  133. package/docs/reference/platforms.md +2 -2
  134. package/docs/reference/projection.md +1 -1
  135. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButton.swift +98 -0
  136. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButtons.swift +24 -0
  137. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIComponents.swift +19 -0
  138. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIFab.swift +80 -0
  139. package/ios/Sources/WebViewNativeUIControls/Components/NativeUISearchController.swift +449 -0
  140. package/ios/Sources/WebViewNativeUIControls/Components/NativeUISegment.swift +91 -0
  141. package/ios/Sources/WebViewNativeUIControls/Components/NativeUITabBar.swift +213 -0
  142. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIToolbarTransition.swift +186 -0
  143. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalBars.swift +696 -0
  144. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalSearch.swift +117 -0
  145. package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUI.swift +180 -0
  146. package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUIRendering.swift +38 -0
  147. package/ios/Sources/WebViewNativeUIControls/NativeUIController.swift +801 -0
  148. package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayController.swift +131 -0
  149. package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayHost.swift +85 -0
  150. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIControlRendering.swift +21 -0
  151. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICoveredSnapshots.swift +14 -0
  152. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICrossfade.swift +35 -0
  153. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIHost.swift +8 -0
  154. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIScrollTracker.swift +2 -0
  155. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUISnapshot.swift +206 -0
  156. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIVerticalBarsGeometry.swift +93 -0
  157. package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +2 -2
  158. package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +6 -4
  159. package/ios/Sources/WebViewNativeUIEngine/ProjectionOrigins.swift +31 -0
  160. package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -4
  161. package/package.json +30 -5
  162. package/src/controls/activation.ts +427 -0
  163. package/src/controls/components/index.ts +45 -0
  164. package/src/controls/components/ion-back-button.ts +22 -0
  165. package/src/controls/components/ion-button.ts +28 -0
  166. package/src/controls/components/ion-buttons.ts +37 -0
  167. package/src/controls/components/ion-fab.ts +151 -0
  168. package/src/controls/components/ion-menu-button.ts +37 -0
  169. package/src/controls/components/ion-segment.ts +27 -0
  170. package/src/controls/components/ion-tab-bar.ts +55 -0
  171. package/src/controls/components/searchable-tabs.ts +534 -0
  172. package/src/controls/definitions.ts +338 -0
  173. package/src/controls/extensions.ts +198 -0
  174. package/src/controls/foldable/fold-state.ts +10 -0
  175. package/src/controls/foldable/types.ts +4 -0
  176. package/src/controls/html.ts +239 -0
  177. package/src/controls/index.ts +7 -0
  178. package/src/controls/ionic.ts +1 -0
  179. package/src/controls/legacy-layout.ts +21 -0
  180. package/src/controls/overlays/content.ts +51 -0
  181. package/src/controls/overlays/controller.ts +394 -0
  182. package/src/controls/overlays/covered-overlay.ts +11 -0
  183. package/src/controls/overlays/dialog-animation.ts +53 -0
  184. package/src/controls/overlays/dismiss-animation.ts +15 -0
  185. package/src/controls/overlays/focus.ts +64 -0
  186. package/src/controls/overlays/popover.ts +116 -0
  187. package/src/controls/overlays/styles.ts +195 -0
  188. package/src/controls/prehide.ts +285 -0
  189. package/src/controls/runtime.ts +1731 -0
  190. package/src/controls/shared/bridge.ts +18 -0
  191. package/src/controls/shared/candidate.ts +163 -0
  192. package/src/controls/shared/clipping.ts +1 -0
  193. package/src/controls/shared/connection.ts +105 -0
  194. package/src/controls/shared/control-options.ts +20 -0
  195. package/src/controls/shared/covered-snapshot.ts +52 -0
  196. package/src/controls/shared/covered-source.ts +78 -0
  197. package/src/controls/shared/crossfade.ts +70 -0
  198. package/src/controls/shared/diagnostics.ts +13 -0
  199. package/src/controls/shared/dom.ts +310 -0
  200. package/src/controls/shared/events.ts +14 -0
  201. package/src/controls/shared/horizontal-navigation.ts +106 -0
  202. package/src/controls/shared/icons.ts +64 -0
  203. package/src/controls/shared/modal-animation.ts +109 -0
  204. package/src/controls/shared/modal.ts +286 -0
  205. package/src/controls/shared/projection-id.ts +2 -0
  206. package/src/controls/shared/scroll-placement.ts +1 -0
  207. package/src/controls/shared/tab-bar-width.ts +66 -0
  208. package/src/controls/shared/toolbar-options.ts +51 -0
  209. package/src/controls/shared/transition-identity.ts +67 -0
  210. package/src/controls/shared/vertical-bars-geometry.ts +92 -0
  211. package/src/controls/shared/vertical-navigation.ts +34 -0
  212. package/src/controls/startup.ts +74 -0
  213. package/src/controls/transport.ts +11 -0
  214. package/src/controls/vertical-bars-web-search.ts +86 -0
  215. package/src/controls/vertical-bars-web.ts +674 -0
  216. package/src/dom/geometry.ts +24 -0
  217. package/src/dom/overlays.ts +196 -0
  218. package/src/dom/scroll-bindings.ts +66 -0
  219. package/src/dom/scroll-placement.ts +92 -0
  220. package/src/dom/visibility.ts +29 -0
  221. package/src/index.ts +19 -0
  222. package/src/integration/index.ts +64 -0
  223. package/src/ionic.ts +34 -0
  224. package/src/native/bootstrap.ts +132 -0
  225. package/src/native/bridge.ts +18 -0
  226. package/src/native/resource-url.ts +11 -0
  227. package/src/native/session.ts +708 -0
  228. package/src/native/updates.ts +103 -0
  229. package/src/native/values.ts +29 -0
  230. package/src/projection/events.ts +58 -0
  231. package/src/projection/pause.ts +58 -0
  232. package/src/projection/placement.ts +34 -0
  233. package/src/projection/scope.ts +13 -0
  234. package/src/projection/styles.ts +26 -0
  235. package/src/projection/surface.ts +88 -0
  236. package/src/projection/surfaces.ts +119 -0
  237. package/src/projection/web.ts +1013 -0
  238. 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 };
@@ -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[id] ?? host.nativeUIEngine;
19
+ const channel = host.webkit?.messageHandlers?.[id] ?? host.nativeUIEngine;
20
20
  if (!channel) {
21
21
  clearTimeout(timer);
22
22
  pending.delete(key);
@@ -1,6 +1,6 @@
1
1
  import { applyNativeValues } from './values.js';
2
2
  import { projectionPlacement } from '../projection/placement.js';
3
- import { getOverlayCoordinator } from '../dom/overlays.js';
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 overlays = getOverlayCoordinator(document);
23
- const effectiveDestination = () => requestedDestination === 'native' && target && overlays.isCovered(target) ? 'web' : requestedDestination;
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 = false) => {
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 stopOverlays = overlays.subscribe(() => queueMicrotask(() => applyDestination(effectiveDestination())));
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
- stopOverlays();
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(), true);
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(), true);
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] { opacity:1!important;overflow:visible!important; }\n[data-native-ui-snapshot-visible][data-native-ui-snapshot-positioned] { position:relative!important; }\n";
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) && surface.owners.some((owner) => moving.has(target(owner))));
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
- if (entry.surface)
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
  };
@@ -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
- const layout = (candidates, positionIndependent = false) => JSON.stringify(candidates.map(({ control: c }) => [
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([relative], !!callbacks.opaqueUnderlay)];
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
- await bounded(Promise.all(result.parts.map(async (part) => {
253
- const target = selected.find((candidate) => part.ids.includes(candidate.control.id)).scope.element;
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: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).element),
287
- scopes: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).scope),
288
- parents: result.parts.map((part) => selected.find((candidate) => part.ids.includes(candidate.control.id)).scope.element.parentNode),
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 begin = () => {
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 }).catch(() => {
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 end = () => {
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
- } : void;
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
- Start with the smallest integration that meets your component's needs. Basic
4
- registration is enough for a single native view; optional settings adapt it to
5
- changing content and application lifecycle. Advanced APIs are a separate route for
6
- renderers that already own batching or placement.
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 | Why |
11
- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
12
- | 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 once; the Engine owns capture and restoration. |
13
- | 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. |
14
- | 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. |
15
- | 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. |
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
- 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.
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
- 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.
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 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.
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/projection.md)
95
- for eligibility, timing, storage and failure policies.
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` | 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. |
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; other frameworks report their lifecycle explicitly.
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)