@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,609 @@
1
+ import { getUnprojectedVisibility } from '../index.js';
2
+ import { enableSupportIonicOverlays } from '../ionic.js';
3
+ import { getProjectionState } from './shared/covered-source.js';
4
+ import { controlSelected } from './shared/control-options.js';
5
+ import { createCrossfade, fadeMarker } from './shared/crossfade.js';
6
+ import { alignVerticalBarClone, verticalBarFrame } from './shared/vertical-bars-geometry.js';
7
+ import { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search.js';
8
+ import { inVerticalBarsSurface, topModal, movingModal, modalPreparing, modalPresentation } from './shared/modal.js';
9
+ import { VERTICAL_BARS_TRANSITION_CANCELED } from '../integration/index.js';
10
+ import { activateProjectedElement, createVerticalBarsPageState, verticalBarsActionCandidate, verticalBarsBackCandidate, verticalBarsEnteringPage, verticalBarsToolbarActions, isExcluded, isNativeUIDisabled, isVerticalBarsToolbarGroup, preferredVerticalBarsBack, verticalBarsOwned, marker, syncToolbarText, toolbarTextMarker, prehideOnlyMutation, prehiddenClass, unprojected, withoutPrehide, } from './shared/dom.js';
11
+ const backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';
12
+ const toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';
13
+ const readyClass = 'ios-theme-vertical-bars-toolbar-ready';
14
+ const toolbarControlSize = 48;
15
+ const toolbarItemGap = 4;
16
+ const toolbarControlGap = 12;
17
+ const toolbarGroupHeight = (count) => count * toolbarControlSize + Math.max(0, count - 1) * toolbarItemGap;
18
+ export const createVerticalBarsWebProjection = (doc, options, enabled = () => true) => {
19
+ const projectionState = getProjectionState(doc);
20
+ const { isProjectionMutation, sourcesChangedEvent, targetsChangedEvent } = projectionState;
21
+ const win = doc.defaultView;
22
+ const crossfade = createCrossfade(win);
23
+ let incomingInstant;
24
+ const toolbarEnabled = options.ionic?.auto !== false;
25
+ let root;
26
+ let backProjections = [];
27
+ let toolbarProjections = [];
28
+ let suspended = 0;
29
+ let stopped = false;
30
+ let frame = 0;
31
+ let updates = 0;
32
+ let observingVerticalBars = false;
33
+ const verticalBarsPages = createVerticalBarsPageState();
34
+ let sourceObserver;
35
+ let waiters = [];
36
+ const listeners = new AbortController();
37
+ const verticalBarsRoot = () => doc.querySelector('ion-app.ios-theme-vertical-bars');
38
+ const surfaceOf = (element) => element.closest('ion-modal') ?? verticalBarsRoot();
39
+ const hasNativeSources = () => projectionState.snapshotsEnabled && !!doc.querySelector('[data-native-ui-vertical-bars], [data-native-ui-vertical-bars-member]');
40
+ const isPrepared = (element) => {
41
+ const modal = element.closest('ion-modal');
42
+ // Preparing hidden proxy DOM inside an animating Ionic sheet can rewind its
43
+ // compositor animation. Native owns entry; prepare the image target at DidPresent.
44
+ if (modal && (modalPreparing(modal) || modalPresentation(modal)))
45
+ return false;
46
+ return ((projectionState.snapshotsEnabled &&
47
+ (element.matches('[data-native-ui-vertical-bars], [data-native-ui-vertical-bars-member]') ||
48
+ !!element.querySelector('[data-native-ui-vertical-bars], [data-native-ui-vertical-bars-member]'))) ||
49
+ [...projectionState.preparedOwners].some((owner) => owner === element || owner.contains(element) || element.contains(owner)));
50
+ };
51
+ // Covered toolbar controls keep a Web visual on their own surface, below the modal.
52
+ const isCovered = (element) => (!!movingModal(doc) && element.closest('ion-modal') === movingModal(doc)) ||
53
+ projectionState.owns(element) ||
54
+ projectionState.isVisible(element) ||
55
+ isPrepared(element) ||
56
+ (!!topModal(doc) && surfaceOf(element) !== topModal(doc));
57
+ const projectedSources = () => [
58
+ ...Array.from(doc.querySelectorAll('[data-native-ui-vertical-bars], [data-native-ui-vertical-bars-member]')),
59
+ ...projectionState.owners,
60
+ ...projectionState.preparedOwners,
61
+ ...backProjections.map(({ source }) => source),
62
+ search.source,
63
+ ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source)),
64
+ ].filter((source) => !!source);
65
+ const isRendered = (element) => withoutPrehide(element, () => {
66
+ const style = getComputedStyle(element);
67
+ const rect = element.getBoundingClientRect();
68
+ return (element.isConnected &&
69
+ style.display !== 'none' &&
70
+ (style.visibility === 'visible' || getUnprojectedVisibility(element) === 'visible') &&
71
+ rect.width > 0 &&
72
+ rect.height > 0);
73
+ });
74
+ const search = createVerticalBarsWebSearch(doc, (element) => options.ionic?.auto !== false &&
75
+ controlSelected(element, options, 'tabs', true) &&
76
+ !!verticalBarsRoot()?.contains(element) &&
77
+ (!options.root || options.root.contains(element)) &&
78
+ !element.closest('[data-native-ui-placement="element"]') &&
79
+ inVerticalBarsSurface(element) &&
80
+ !isExcluded(element, verticalBarsEnteringPage(element)) &&
81
+ !isNativeUIDisabled(element) &&
82
+ !verticalBarsPages.isDeparted(element) &&
83
+ isRendered(element), () => {
84
+ void schedule();
85
+ });
86
+ const inEligibleToolbar = (element) => !!verticalBarsRoot()?.contains(element) &&
87
+ (!options.root || options.root.contains(element)) &&
88
+ !element.closest('[data-native-ui-placement="element"]') &&
89
+ (inVerticalBarsSurface(element) || isCovered(element)) &&
90
+ verticalBarsActionCandidate(element) &&
91
+ !isExcluded(element, verticalBarsEnteringPage(element)) &&
92
+ (!verticalBarsPages.isDeparted(element) || projectionState.isVisible(element));
93
+ const isEligibleBack = (element) => !!verticalBarsRoot()?.contains(element) &&
94
+ (!options.root || options.root.contains(element)) &&
95
+ !element.closest('[data-native-ui-placement="element"]') &&
96
+ verticalBarsOwned(element) &&
97
+ verticalBarsBackCandidate(element) &&
98
+ !isExcluded(element, verticalBarsEnteringPage(element)) &&
99
+ (!verticalBarsPages.isDeparted(element) || projectionState.isVisible(element)) &&
100
+ unprojected(projectedSources(), () => isRendered(element));
101
+ const pageOrder = (element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page'));
102
+ const findBack = (surface = topModal(doc) ?? verticalBarsRoot()) => {
103
+ const candidates = Array.from(doc.querySelectorAll(`ion-back-button:not(.${backProjectionClass})`)).filter((element) => surfaceOf(element) === surface && isEligibleBack(element));
104
+ return preferredVerticalBarsBack(candidates, doc);
105
+ };
106
+ const findToolbarGroups = () => {
107
+ const candidates = Array.from(doc.querySelectorAll(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap((group) => {
108
+ const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));
109
+ if (!actions.length)
110
+ return [];
111
+ if (!isVerticalBarsToolbarGroup(group))
112
+ return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));
113
+ return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];
114
+ });
115
+ for (const action of Array.from(doc.querySelectorAll('ion-button, ion-menu-button'))) {
116
+ if (action.closest('ion-buttons') || !verticalBarsOwned(action) || !inEligibleToolbar(action))
117
+ continue;
118
+ if (unprojected(projectedSources(), () => isRendered(action)))
119
+ candidates.push({ group: action, actions: [action] });
120
+ }
121
+ const highestPages = new Map();
122
+ for (const { group } of candidates) {
123
+ const surface = surfaceOf(group);
124
+ highestPages.set(surface, Math.max(highestPages.get(surface) ?? -1, pageOrder(group)));
125
+ }
126
+ return candidates.filter(({ group }) => (enabled() || isCovered(group)) && pageOrder(group) === highestPages.get(surfaceOf(group)));
127
+ };
128
+ const isCurrentToolbarAction = (source) => inVerticalBarsSurface(source) && findToolbarGroups().some(({ actions }) => actions.includes(source));
129
+ const nativeOwned = projectionState.nativeOwns;
130
+ const restore = (retire, retained = new Set()) => {
131
+ search.restore(retire);
132
+ for (const current of backProjections) {
133
+ if (retained.has(current))
134
+ continue;
135
+ const { source, projection, root: surface, previousAriaHidden } = current;
136
+ surface.classList.remove(readyClass);
137
+ if (retire)
138
+ retire(projection);
139
+ else
140
+ projection.remove();
141
+ if (!nativeOwned(source)) {
142
+ source.removeAttribute(marker);
143
+ if (previousAriaHidden === null)
144
+ source.removeAttribute('aria-hidden');
145
+ else if (previousAriaHidden !== undefined)
146
+ source.setAttribute('aria-hidden', previousAriaHidden);
147
+ }
148
+ source.dispatchEvent(new CustomEvent('nativeUIChange'));
149
+ }
150
+ backProjections = backProjections.filter((current) => retained.has(current));
151
+ for (const current of toolbarProjections) {
152
+ if (retained.has(current))
153
+ continue;
154
+ const { projection, actions, root: surface } = current;
155
+ surface.classList.remove(readyClass);
156
+ if (retire)
157
+ retire(projection);
158
+ else
159
+ projection.remove();
160
+ for (const { source, previousAriaHidden } of actions) {
161
+ if (!nativeOwned(source)) {
162
+ source.removeAttribute(marker);
163
+ if (previousAriaHidden === null)
164
+ source.removeAttribute('aria-hidden');
165
+ else if (previousAriaHidden !== undefined)
166
+ source.setAttribute('aria-hidden', previousAriaHidden);
167
+ }
168
+ source.dispatchEvent(new CustomEvent('nativeUIChange'));
169
+ }
170
+ }
171
+ toolbarProjections = toolbarProjections.filter((current) => retained.has(current));
172
+ sourceObserver?.disconnect();
173
+ sourceObserver = undefined;
174
+ root?.classList.remove(readyClass);
175
+ root = undefined;
176
+ };
177
+ const copyAttributes = (target, original) => {
178
+ for (const name of ['id', 'slot', marker, 'aria-hidden'])
179
+ target.removeAttribute(name);
180
+ for (const attribute of Array.from(target.attributes))
181
+ if (!['class', marker, fadeMarker, 'data-native-ui-proxy-prepared'].includes(attribute.name) &&
182
+ !attribute.name.startsWith('data-native-ui-snapshot') &&
183
+ !original.hasAttribute(attribute.name))
184
+ target.removeAttribute(attribute.name);
185
+ for (const attribute of Array.from(original.attributes))
186
+ if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name) && !attribute.name.startsWith('data-native-ui-snapshot'))
187
+ target.setAttribute(attribute.name, attribute.value);
188
+ target.classList.remove(prehiddenClass);
189
+ // Establish ownership before insertion/layout: glass can sample even a single Web paint.
190
+ target.toggleAttribute('data-native-ui-proxy-prepared', isPrepared(original));
191
+ };
192
+ const syncBack = (target, original, surface) => {
193
+ copyAttributes(target, original);
194
+ target.classList.add(backProjectionClass, 'ion-cloned-element');
195
+ target.disabled = original.disabled;
196
+ target.defaultHref = original.defaultHref;
197
+ target.icon = original.icon;
198
+ target.color = original.color;
199
+ target.mode = original.mode;
200
+ target.text = '';
201
+ alignVerticalBarClone(target, original, surface);
202
+ };
203
+ const syncAction = (target, original) => {
204
+ copyAttributes(target, original);
205
+ target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');
206
+ const label = original.getAttribute('aria-label') ??
207
+ original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??
208
+ original.textContent?.trim();
209
+ if (label)
210
+ target.setAttribute('aria-label', label);
211
+ if ('disabled' in original)
212
+ target.disabled = original.disabled;
213
+ if (original.matches('ion-button')) {
214
+ const icon = original.querySelector('ion-icon, svg');
215
+ const clonedIcon = icon?.cloneNode(true);
216
+ clonedIcon?.setAttribute('slot', 'icon-only');
217
+ target.replaceChildren(...(clonedIcon ? [clonedIcon] : [document.createTextNode(original.textContent?.trim() || label || '')]));
218
+ }
219
+ };
220
+ const sameToolbar = (current, { group, actions }) => group === current.source &&
221
+ surfaceOf(group) === current.root &&
222
+ actions.length === current.actions.length &&
223
+ actions.every((action, index) => action === current.actions[index].source);
224
+ const sameSources = (backs, groups) => backs.length === backProjections.length &&
225
+ backs.every((source, index) => source === backProjections[index].source && surfaceOf(source) === backProjections[index].root) &&
226
+ groups.length === toolbarProjections.length &&
227
+ groups.every((group, index) => sameToolbar(toolbarProjections[index], group));
228
+ const syncExisting = () => {
229
+ for (const { source, projection, root: surface } of backProjections) {
230
+ if (!surface.classList.contains(readyClass))
231
+ surface.classList.add(readyClass);
232
+ if (projectionState.isVisible(source)) {
233
+ projection.inert = true;
234
+ continue;
235
+ }
236
+ syncBack(projection, source, surface);
237
+ projection.inert = isCovered(source);
238
+ if (projection.inert)
239
+ projection.setAttribute('aria-hidden', 'true');
240
+ }
241
+ const offsets = new Map();
242
+ for (const { projection, source, actions, root: surface } of toolbarProjections) {
243
+ if (!surface.classList.contains(readyClass))
244
+ surface.classList.add(readyClass);
245
+ const topOffset = offsets.get(surface) ?? (findBack(surface) ? toolbarControlSize + toolbarControlGap : 0);
246
+ if (projectionState.isVisible(source) || actions.some((action) => projectionState.isVisible(action.source))) {
247
+ projection.inert = true;
248
+ offsets.set(surface, topOffset + toolbarGroupHeight(actions.length) + toolbarControlGap);
249
+ continue;
250
+ }
251
+ if (actions.length === 1 && !projection.matches('ion-buttons'))
252
+ syncAction(projection, actions[0].source);
253
+ else
254
+ copyAttributes(projection, source);
255
+ projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
256
+ projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
257
+ // Preserve appearance without bypassing the modal's input/focus boundary.
258
+ projection.inert = isCovered(source);
259
+ if (projection.inert)
260
+ projection.setAttribute('aria-hidden', 'true');
261
+ if (projection.matches('ion-buttons'))
262
+ actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));
263
+ const frames = actions.map(({ source }) => verticalBarFrame(source));
264
+ if (frames.every((frame) => !!frame)) {
265
+ if (actions.length === 1)
266
+ alignVerticalBarClone(projection, actions[0].source, surface);
267
+ else {
268
+ const left = Math.min(...frames.map((frame) => frame.x));
269
+ const top = Math.min(...frames.map((frame) => frame.y));
270
+ const right = Math.max(...frames.map((frame) => frame.x + frame.width));
271
+ const bottom = Math.max(...frames.map((frame) => frame.y + frame.height));
272
+ const origin = surface.getBoundingClientRect();
273
+ Object.assign(projection.style, {
274
+ left: `${left - origin.left}px`,
275
+ top: `${top - origin.top}px`,
276
+ right: 'auto',
277
+ bottom: 'auto',
278
+ width: `${right - left}px`,
279
+ height: `${bottom - top}px`,
280
+ padding: '0',
281
+ boxSizing: 'border-box',
282
+ });
283
+ actions.forEach(({ source, projection: clone }) => alignVerticalBarClone(clone, source, projection));
284
+ }
285
+ }
286
+ offsets.set(surface, topOffset + toolbarGroupHeight(actions.length) + toolbarControlGap);
287
+ }
288
+ };
289
+ const project = (nextBacks, groups) => {
290
+ root = topModal(doc) ?? verticalBarsRoot();
291
+ let topOffset = 0;
292
+ for (const source of nextBacks) {
293
+ const surface = surfaceOf(source);
294
+ if (surface === root)
295
+ topOffset = toolbarControlSize + toolbarControlGap;
296
+ if (backProjections.some((current) => current.source === source))
297
+ continue;
298
+ const previousAriaHidden = nativeOwned(source) && source.hasAttribute(marker) ? undefined : source.getAttribute('aria-hidden');
299
+ const projection = source.cloneNode(false);
300
+ syncBack(projection, source, surface);
301
+ projection.addEventListener('click', (event) => {
302
+ event.preventDefault();
303
+ event.stopImmediatePropagation();
304
+ if (inVerticalBarsSurface(source) && source === findBack())
305
+ activateProjectedElement(source);
306
+ }, { capture: true });
307
+ projectionState.setTarget(source, projection);
308
+ surface.append(projection);
309
+ backProjections.push({ source, projection, root: surface, previousAriaHidden });
310
+ source.setAttribute(marker, '');
311
+ source.setAttribute('aria-hidden', 'true');
312
+ source.dispatchEvent(new CustomEvent('nativeUIChange'));
313
+ if (surface === root)
314
+ topOffset = toolbarControlSize + toolbarControlGap;
315
+ }
316
+ for (const { group, actions: sources } of groups) {
317
+ if (toolbarProjections.some((current) => sameToolbar(current, { group, actions: sources }))) {
318
+ topOffset += toolbarGroupHeight(sources.length) + toolbarControlGap;
319
+ continue;
320
+ }
321
+ const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false);
322
+ if (sources.length === 1)
323
+ syncAction(projection, sources[0]);
324
+ else
325
+ copyAttributes(projection, group);
326
+ projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
327
+ projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
328
+ const actions = sources.map((source) => {
329
+ const clone = sources.length === 1 ? projection : source.cloneNode(false);
330
+ if (sources.length > 1)
331
+ syncAction(clone, source);
332
+ clone.addEventListener('click', (event) => {
333
+ event.preventDefault();
334
+ event.stopImmediatePropagation();
335
+ if (isCurrentToolbarAction(source))
336
+ activateProjectedElement(source);
337
+ }, { capture: true });
338
+ if (sources.length > 1)
339
+ projection.append(clone);
340
+ projectionState.setTarget(source, clone);
341
+ const previousAriaHidden = nativeOwned(source) && source.hasAttribute(marker) ? undefined : source.getAttribute('aria-hidden');
342
+ source.setAttribute(marker, '');
343
+ source.setAttribute('aria-hidden', 'true');
344
+ source.dispatchEvent(new CustomEvent('nativeUIChange'));
345
+ return { source, projection: clone, previousAriaHidden };
346
+ });
347
+ const surface = surfaceOf(group);
348
+ projectionState.setTarget(group, projection);
349
+ surface.append(projection);
350
+ toolbarProjections.push({ source: group, root: surface, projection, actions });
351
+ topOffset += toolbarGroupHeight(actions.length) + toolbarControlGap;
352
+ }
353
+ backProjections.sort((a, b) => nextBacks.indexOf(a.source) - nextBacks.indexOf(b.source));
354
+ toolbarProjections.sort((a, b) => groups.findIndex((group) => sameToolbar(a, group)) - groups.findIndex((group) => sameToolbar(b, group)));
355
+ syncExisting();
356
+ if (nextBacks.length || toolbarProjections.length)
357
+ root.classList.add(readyClass);
358
+ sourceObserver = new MutationObserver((records) => {
359
+ if (records.some((record) => record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record))))
360
+ void schedule();
361
+ });
362
+ for (const back of nextBacks)
363
+ if (back.shadowRoot)
364
+ sourceObserver.observe(back.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
365
+ for (const { group } of groups)
366
+ sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
367
+ };
368
+ const reveal = () => {
369
+ if (incomingInstant === undefined)
370
+ return;
371
+ for (const surface of [
372
+ ...backProjections.map(({ projection }) => projection),
373
+ ...toolbarProjections.map(({ projection }) => projection),
374
+ search.surface,
375
+ ])
376
+ if (surface)
377
+ crossfade.play(surface, false, incomingInstant);
378
+ incomingInstant = undefined;
379
+ };
380
+ const performUpdate = () => {
381
+ frame = 0;
382
+ syncToolbarText(doc);
383
+ const currentRoot = verticalBarsRoot();
384
+ if (observingVerticalBars !== !!currentRoot) {
385
+ observingVerticalBars = !!currentRoot;
386
+ observer.disconnect();
387
+ observer.observe(doc.documentElement, {
388
+ subtree: true,
389
+ childList: true,
390
+ characterData: observingVerticalBars,
391
+ attributes: true,
392
+ attributeOldValue: true,
393
+ attributeFilter: observingVerticalBars ? undefined : ['class'],
394
+ });
395
+ }
396
+ if (stopped ||
397
+ suspended ||
398
+ options.verticalBars?.enabled === false ||
399
+ !currentRoot ||
400
+ (!enabled() && !topModal(doc) && !projectionState.owners.length && !projectionState.preparedOwners.length && !hasNativeSources()))
401
+ return restore();
402
+ const surfaces = new Set(Array.from(doc.querySelectorAll(`ion-back-button:not(.${backProjectionClass})`)).map(surfaceOf));
403
+ const nextBacks = toolbarEnabled
404
+ ? [...surfaces]
405
+ .map((surface) => findBack(surface))
406
+ .filter((back) => !!back && (enabled() || isCovered(back)))
407
+ : [];
408
+ const groups = toolbarEnabled ? findToolbarGroups() : [];
409
+ const projectionRoot = topModal(doc) ?? currentRoot;
410
+ if (enabled())
411
+ search.update(projectionRoot);
412
+ else
413
+ search.restore();
414
+ if (!nextBacks.length && !groups.length && !search.source)
415
+ return restore();
416
+ if (root && sameSources(nextBacks, groups)) {
417
+ syncExisting();
418
+ reveal();
419
+ return;
420
+ }
421
+ // Image scopes retain their DOM identity while another surface adds/removes controls.
422
+ const retained = new Set([
423
+ ...backProjections.filter((current) => nextBacks.includes(current.source) && current.root === surfaceOf(current.source)),
424
+ ...toolbarProjections.filter((current) => groups.some((group) => sameToolbar(current, group))),
425
+ ]);
426
+ restore(undefined, retained);
427
+ project(nextBacks, groups);
428
+ if (enabled())
429
+ search.update(projectionRoot);
430
+ else
431
+ search.restore();
432
+ reveal();
433
+ updates++;
434
+ };
435
+ const update = () => {
436
+ try {
437
+ performUpdate();
438
+ }
439
+ catch {
440
+ restore();
441
+ }
442
+ finally {
443
+ win.dispatchEvent(new Event(targetsChangedEvent));
444
+ const pending = waiters;
445
+ waiters = [];
446
+ pending.forEach((resolve) => resolve());
447
+ }
448
+ };
449
+ const schedule = () => {
450
+ if (stopped)
451
+ return Promise.resolve();
452
+ // Release Web ownership before the native runtime can acknowledge its next frame.
453
+ if (!enabled() && !topModal(doc) && !projectionState.owners.length && !projectionState.preparedOwners.length && !hasNativeSources())
454
+ restore();
455
+ const done = new Promise((resolve) => waiters.push(resolve));
456
+ if (!frame)
457
+ frame = win.requestAnimationFrame(update);
458
+ return done;
459
+ };
460
+ const observer = new MutationObserver((records) => {
461
+ const insideProjection = (target) => {
462
+ const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;
463
+ const rootNode = target.getRootNode();
464
+ const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;
465
+ return !![element, shadowHost].some((candidate) => candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}, .${searchProjectionClass}`));
466
+ };
467
+ const verticalBarsChanged = records.some((record) => (record.type === 'attributes' && record.target.matches('ion-app')) ||
468
+ (record.type === 'childList' &&
469
+ Array.from(record.addedNodes).some((node) => node instanceof Element &&
470
+ (node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')))));
471
+ if ((observingVerticalBars &&
472
+ records.some((record) => !isProjectionMutation(record) &&
473
+ record.attributeName !== marker &&
474
+ record.attributeName !== toolbarTextMarker &&
475
+ !prehideOnlyMutation(record) &&
476
+ !insideProjection(record.target))) ||
477
+ (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record))))
478
+ void schedule();
479
+ });
480
+ observer.observe(doc.documentElement, {
481
+ subtree: true,
482
+ childList: true,
483
+ attributes: true,
484
+ attributeOldValue: true,
485
+ attributeFilter: ['class'],
486
+ });
487
+ const pageLifecycle = (event) => {
488
+ const page = event.target;
489
+ if (!(page instanceof HTMLElement) || !page.matches('.ion-page'))
490
+ return;
491
+ if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {
492
+ const entering = event.detail?.entering;
493
+ verticalBarsPages.cancel(entering, page);
494
+ }
495
+ else
496
+ verticalBarsPages.lifecycle(event);
497
+ if (verticalBarsRoot())
498
+ void schedule();
499
+ };
500
+ for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])
501
+ doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });
502
+ doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });
503
+ const stopOverlays = enableSupportIonicOverlays(undefined, doc).subscribe(({ element, phase }) => {
504
+ if (!element?.matches('ion-modal'))
505
+ return;
506
+ if (phase === 'willPresent' || phase === 'didPresent')
507
+ void schedule();
508
+ // Retire modal clones before they can paint at the background rail offset.
509
+ if (phase === 'didDismiss')
510
+ update();
511
+ });
512
+ listeners.signal.addEventListener('abort', stopOverlays, { once: true });
513
+ win.addEventListener('nativeUIVerticalBarsAcquire', (event) => {
514
+ const instant = event.detail?.instant ?? true;
515
+ incomingInstant = undefined;
516
+ // Acquisition is dispatched before Native records the source's original ARIA.
517
+ // Transfer Web-owned visibility without destroying the prepared image target.
518
+ for (const current of [...backProjections, ...toolbarProjections.flatMap(({ actions }) => actions)]) {
519
+ if (!nativeOwned(current.source) || current.previousAriaHidden === undefined)
520
+ continue;
521
+ if (current.previousAriaHidden === null)
522
+ current.source.removeAttribute('aria-hidden');
523
+ else
524
+ current.source.setAttribute('aria-hidden', current.previousAriaHidden);
525
+ current.previousAriaHidden = undefined;
526
+ }
527
+ if ([...backProjections, ...toolbarProjections].some(({ source }) => isCovered(source))) {
528
+ // Conceal existing Web surfaces immediately; leave discovery and geometry
529
+ // to the scheduled update so acquisition does not create new controls.
530
+ const retained = [
531
+ ...backProjections,
532
+ ...toolbarProjections.filter(({ actions }) => actions.length > 1),
533
+ ...toolbarProjections.flatMap(({ actions }) => actions),
534
+ ];
535
+ for (const { source, projection } of retained)
536
+ for (const attribute of ['data-native-ui-vertical-bars', 'data-native-ui-vertical-bars-member'])
537
+ projection.toggleAttribute(attribute, source.hasAttribute(attribute));
538
+ void schedule();
539
+ return;
540
+ }
541
+ restore((surface) => {
542
+ // Keep only the outgoing visual at its last rectangle after Web ownership
543
+ // is released. The native acknowledgement transfers input exactly once.
544
+ const box = surface.getBoundingClientRect();
545
+ surface.style.setProperty('position', 'fixed', 'important');
546
+ surface.style.setProperty('display', 'block', 'important');
547
+ surface.style.setProperty('left', `${box.left}px`, 'important');
548
+ surface.style.setProperty('top', `${box.top}px`, 'important');
549
+ surface.style.setProperty('right', 'auto', 'important');
550
+ surface.style.setProperty('bottom', 'auto', 'important');
551
+ surface.style.setProperty('width', `${box.width}px`, 'important');
552
+ surface.style.setProperty('height', `${box.height}px`, 'important');
553
+ crossfade.retire(surface, instant);
554
+ });
555
+ }, { signal: listeners.signal });
556
+ win.addEventListener('nativeUIVerticalBarsRelease', (event) => {
557
+ incomingInstant = event.detail.instant;
558
+ void schedule();
559
+ }, { signal: listeners.signal });
560
+ win.addEventListener('resize', () => {
561
+ void schedule();
562
+ }, { signal: listeners.signal });
563
+ win.addEventListener('nativeUIRefresh', () => {
564
+ void schedule();
565
+ }, { signal: listeners.signal });
566
+ win.addEventListener(sourcesChangedEvent, () => {
567
+ void schedule();
568
+ }, { signal: listeners.signal });
569
+ void schedule();
570
+ return {
571
+ markForCheck: () => {
572
+ void schedule();
573
+ },
574
+ getStatus: () => ({
575
+ state: stopped ? 'stopped' : 'web',
576
+ projected: backProjections.length + Number(!!search.source) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),
577
+ updates,
578
+ }),
579
+ async suspend() {
580
+ suspended++;
581
+ restore();
582
+ let resumed = false;
583
+ return {
584
+ async resume() {
585
+ if (resumed)
586
+ return;
587
+ resumed = true;
588
+ suspended = Math.max(0, suspended - 1);
589
+ if (!suspended)
590
+ await void schedule();
591
+ },
592
+ };
593
+ },
594
+ async destroy() {
595
+ if (stopped)
596
+ return;
597
+ stopped = true;
598
+ win.cancelAnimationFrame(frame);
599
+ frame = 0;
600
+ waiters.forEach((resolve) => resolve());
601
+ waiters = [];
602
+ observer.disconnect();
603
+ sourceObserver?.disconnect();
604
+ listeners.abort();
605
+ crossfade.destroy();
606
+ restore();
607
+ },
608
+ };
609
+ };
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export { bindProjectionPlacement, type ProjectionPlacementBinding } from './proj
11
11
  export { createWebProjectionSurfaces } from './projection/surfaces.js';
12
12
  export * from './projection/surface.js';
13
13
  export { createNativeUpdateQueue, type NativeUpdateSender } from './native/updates.js';
14
+ export { pause, resume } from './projection/pause.js';
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export { bindProjectionPlacement } from './projection/placement.js';
10
10
  export { createWebProjectionSurfaces } from './projection/surfaces.js';
11
11
  export * from './projection/surface.js';
12
12
  export { createNativeUpdateQueue } from './native/updates.js';
13
+ export { pause, resume } from './projection/pause.js';
@@ -0,0 +1,36 @@
1
+ export interface NativeUISuspensionOptions {
2
+ /** Navigation-only toolbar handoff; omitted for explicit Web ownership requests. */
3
+ navigation?: boolean;
4
+ /** A swipe whose navigation result has not yet been committed. */
5
+ interactive?: boolean;
6
+ /** Direction supplied by Ionic's navigation animation builder. */
7
+ direction?: 'forward' | 'back' | 'root';
8
+ }
9
+ export type NativeUITransitionRelease = ((canceled?: boolean) => void) & {
10
+ progress?: (value: number) => void;
11
+ /** The finger lifted; Web will now settle toward its chosen endpoint. */
12
+ finishGesture?: (complete: boolean) => void;
13
+ };
14
+ export interface NativeUIIntegration {
15
+ suspend(scopes: HTMLElement[], options?: NativeUISuspensionOptions): Promise<NativeUITransitionRelease>;
16
+ search?(binding: NativeSearchBinding, active: boolean, focus?: boolean): Promise<boolean>;
17
+ }
18
+ export interface NativeSearchBinding {
19
+ tabBar: HTMLElement;
20
+ trigger: HTMLElement;
21
+ footer: HTMLElement;
22
+ active: boolean;
23
+ focused: boolean;
24
+ }
25
+ export declare const registerNativeSearch: (tabBar: HTMLElement, trigger: HTMLElement, footer: HTMLElement) => NativeSearchBinding;
26
+ export declare const getNativeSearchBindings: (doc: Document) => NativeSearchBinding[];
27
+ export declare const requestNativeSearch: (binding: NativeSearchBinding, active: boolean, focus?: boolean) => Promise<boolean>;
28
+ export declare const VERTICAL_BARS_TRANSITION_CANCELED = "iosThemeVerticalBarsTransitionCanceled";
29
+ export declare const setNativeUIIntegration: (doc: Document, runtime?: NativeUIIntegration) => void;
30
+ export declare const hasNativeUIIntegration: (doc: Document) => boolean;
31
+ export declare const isNativeUI: (element: HTMLElement) => boolean;
32
+ export declare const suspendNativeUI: (scopes: HTMLElement[], options?: NativeUISuspensionOptions) => Promise<NativeUITransitionRelease>;
33
+ /** Shared geometry used by the theme's transition adapter; no theme import in the plugin. */
34
+ export declare const nativeWebViewMetrics: {
35
+ radius: number;
36
+ };