@webview-native-ui/engine 1.0.1 → 1.0.2-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/WebViewNativeUIControls.podspec +17 -0
- package/dist/controls/activation.d.ts +15 -0
- package/dist/controls/activation.js +370 -0
- package/dist/controls/components/index.d.ts +16 -0
- package/dist/controls/components/index.js +38 -0
- package/dist/controls/components/ion-back-button.d.ts +4 -0
- package/dist/controls/components/ion-back-button.js +16 -0
- package/dist/controls/components/ion-button.d.ts +4 -0
- package/dist/controls/components/ion-button.js +26 -0
- package/dist/controls/components/ion-buttons.d.ts +5 -0
- package/dist/controls/components/ion-buttons.js +34 -0
- package/dist/controls/components/ion-fab.d.ts +4 -0
- package/dist/controls/components/ion-fab.js +164 -0
- package/dist/controls/components/ion-menu-button.d.ts +7 -0
- package/dist/controls/components/ion-menu-button.js +41 -0
- package/dist/controls/components/ion-segment.d.ts +5 -0
- package/dist/controls/components/ion-segment.js +24 -0
- package/dist/controls/components/ion-tab-bar.d.ts +5 -0
- package/dist/controls/components/ion-tab-bar.js +49 -0
- package/dist/controls/components/searchable-tabs.d.ts +20 -0
- package/dist/controls/components/searchable-tabs.js +525 -0
- package/dist/controls/definitions.d.ts +360 -0
- package/dist/controls/definitions.js +1 -0
- package/dist/controls/extensions.d.ts +44 -0
- package/dist/controls/extensions.js +164 -0
- package/dist/controls/foldable/fold-state.d.ts +4 -0
- package/dist/controls/foldable/fold-state.js +8 -0
- package/dist/controls/foldable/types.d.ts +9 -0
- package/dist/controls/foldable/types.js +1 -0
- package/dist/controls/html.d.ts +8 -0
- package/dist/controls/html.js +260 -0
- package/dist/controls/index.d.ts +7 -0
- package/dist/controls/index.js +4 -0
- package/dist/controls/ionic.d.ts +1 -0
- package/dist/controls/ionic.js +1 -0
- package/dist/controls/legacy-layout.d.ts +7 -0
- package/dist/controls/legacy-layout.js +23 -0
- package/dist/controls/overlays/content.d.ts +2 -0
- package/dist/controls/overlays/content.js +55 -0
- package/dist/controls/overlays/controller.d.ts +7 -0
- package/dist/controls/overlays/controller.js +369 -0
- package/dist/controls/overlays/covered-overlay.d.ts +4 -0
- package/dist/controls/overlays/covered-overlay.js +9 -0
- package/dist/controls/overlays/dialog-animation.d.ts +7 -0
- package/dist/controls/overlays/dialog-animation.js +49 -0
- package/dist/controls/overlays/dismiss-animation.d.ts +2 -0
- package/dist/controls/overlays/dismiss-animation.js +14 -0
- package/dist/controls/overlays/focus.d.ts +2 -0
- package/dist/controls/overlays/focus.js +67 -0
- package/dist/controls/overlays/popover.d.ts +10 -0
- package/dist/controls/overlays/popover.js +117 -0
- package/dist/controls/overlays/styles.d.ts +9 -0
- package/dist/controls/overlays/styles.js +194 -0
- package/dist/controls/prehide.d.ts +6 -0
- package/dist/controls/prehide.js +280 -0
- package/dist/controls/runtime.d.ts +5 -0
- package/dist/controls/runtime.js +1685 -0
- package/dist/controls/shared/bridge.d.ts +4 -0
- package/dist/controls/shared/bridge.js +15 -0
- package/dist/controls/shared/candidate.d.ts +23 -0
- package/dist/controls/shared/candidate.js +128 -0
- package/dist/controls/shared/clipping.d.ts +1 -0
- package/dist/controls/shared/clipping.js +1 -0
- package/dist/controls/shared/connection.d.ts +21 -0
- package/dist/controls/shared/connection.js +112 -0
- package/dist/controls/shared/control-options.d.ts +4 -0
- package/dist/controls/shared/control-options.js +14 -0
- package/dist/controls/shared/covered-snapshot.d.ts +35 -0
- package/dist/controls/shared/covered-snapshot.js +41 -0
- package/dist/controls/shared/covered-source.d.ts +22 -0
- package/dist/controls/shared/covered-source.js +72 -0
- package/dist/controls/shared/crossfade.d.ts +11 -0
- package/dist/controls/shared/crossfade.js +71 -0
- package/dist/controls/shared/diagnostics.d.ts +2 -0
- package/dist/controls/shared/diagnostics.js +16 -0
- package/dist/controls/shared/dom.d.ts +45 -0
- package/dist/controls/shared/dom.js +258 -0
- package/dist/controls/shared/events.d.ts +12 -0
- package/dist/controls/shared/events.js +13 -0
- package/dist/controls/shared/horizontal-navigation.d.ts +36 -0
- package/dist/controls/shared/horizontal-navigation.js +90 -0
- package/dist/controls/shared/icons.d.ts +5 -0
- package/dist/controls/shared/icons.js +66 -0
- package/dist/controls/shared/modal-animation.d.ts +21 -0
- package/dist/controls/shared/modal-animation.js +97 -0
- package/dist/controls/shared/modal.d.ts +26 -0
- package/dist/controls/shared/modal.js +261 -0
- package/dist/controls/shared/projection-id.d.ts +2 -0
- package/dist/controls/shared/projection-id.js +2 -0
- package/dist/controls/shared/scroll-placement.d.ts +1 -0
- package/dist/controls/shared/scroll-placement.js +1 -0
- package/dist/controls/shared/tab-bar-width.d.ts +7 -0
- package/dist/controls/shared/tab-bar-width.js +72 -0
- package/dist/controls/shared/toolbar-options.d.ts +10 -0
- package/dist/controls/shared/toolbar-options.js +38 -0
- package/dist/controls/shared/transition-identity.d.ts +3 -0
- package/dist/controls/shared/transition-identity.js +64 -0
- package/dist/controls/shared/vertical-bars-geometry.d.ts +8 -0
- package/dist/controls/shared/vertical-bars-geometry.js +82 -0
- package/dist/controls/shared/vertical-navigation.d.ts +5 -0
- package/dist/controls/shared/vertical-navigation.js +39 -0
- package/dist/controls/startup.d.ts +7 -0
- package/dist/controls/startup.js +72 -0
- package/dist/controls/transport.d.ts +10 -0
- package/dist/controls/transport.js +1 -0
- package/dist/controls/vertical-bars-web-search.d.ts +8 -0
- package/dist/controls/vertical-bars-web-search.js +83 -0
- package/dist/controls/vertical-bars-web.d.ts +2 -0
- package/dist/controls/vertical-bars-web.js +609 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/integration/index.d.ts +36 -0
- package/dist/integration/index.js +25 -0
- package/dist/native/session.js +14 -8
- package/dist/projection/pause.d.ts +10 -0
- package/dist/projection/pause.js +58 -0
- package/dist/projection/styles.d.ts +1 -1
- package/dist/projection/styles.js +0 -1
- package/dist/projection/surfaces.js +2 -1
- package/dist/projection/web.js +39 -5
- package/dist/types.d.ts +5 -1
- package/docs/README.md +1 -0
- package/docs/configuration/README.md +3 -1
- package/docs/configuration/ionic-overlays.md +36 -2
- package/docs/configuration/pause-resume.md +51 -0
- package/docs/controls.md +39 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButton.swift +98 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButtons.swift +24 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIComponents.swift +19 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIFab.swift +80 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUISearchController.swift +449 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUISegment.swift +91 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUITabBar.swift +213 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIToolbarTransition.swift +186 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalBars.swift +696 -0
- package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalSearch.swift +117 -0
- package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUI.swift +180 -0
- package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUIRendering.swift +38 -0
- package/ios/Sources/WebViewNativeUIControls/NativeUIController.swift +801 -0
- package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayController.swift +131 -0
- package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayHost.swift +85 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIControlRendering.swift +21 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICoveredSnapshots.swift +14 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICrossfade.swift +35 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIHost.swift +8 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIScrollTracker.swift +2 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUISnapshot.swift +206 -0
- package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIVerticalBarsGeometry.swift +93 -0
- package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +2 -2
- package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +1 -1
- package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -4
- package/package.json +30 -5
- package/src/controls/activation.ts +427 -0
- package/src/controls/components/index.ts +45 -0
- package/src/controls/components/ion-back-button.ts +22 -0
- package/src/controls/components/ion-button.ts +28 -0
- package/src/controls/components/ion-buttons.ts +37 -0
- package/src/controls/components/ion-fab.ts +151 -0
- package/src/controls/components/ion-menu-button.ts +37 -0
- package/src/controls/components/ion-segment.ts +27 -0
- package/src/controls/components/ion-tab-bar.ts +55 -0
- package/src/controls/components/searchable-tabs.ts +534 -0
- package/src/controls/definitions.ts +338 -0
- package/src/controls/extensions.ts +198 -0
- package/src/controls/foldable/fold-state.ts +10 -0
- package/src/controls/foldable/types.ts +4 -0
- package/src/controls/html.ts +239 -0
- package/src/controls/index.ts +7 -0
- package/src/controls/ionic.ts +1 -0
- package/src/controls/legacy-layout.ts +21 -0
- package/src/controls/overlays/content.ts +51 -0
- package/src/controls/overlays/controller.ts +394 -0
- package/src/controls/overlays/covered-overlay.ts +11 -0
- package/src/controls/overlays/dialog-animation.ts +53 -0
- package/src/controls/overlays/dismiss-animation.ts +15 -0
- package/src/controls/overlays/focus.ts +64 -0
- package/src/controls/overlays/popover.ts +116 -0
- package/src/controls/overlays/styles.ts +195 -0
- package/src/controls/prehide.ts +285 -0
- package/src/controls/runtime.ts +1731 -0
- package/src/controls/shared/bridge.ts +18 -0
- package/src/controls/shared/candidate.ts +163 -0
- package/src/controls/shared/clipping.ts +1 -0
- package/src/controls/shared/connection.ts +105 -0
- package/src/controls/shared/control-options.ts +20 -0
- package/src/controls/shared/covered-snapshot.ts +52 -0
- package/src/controls/shared/covered-source.ts +78 -0
- package/src/controls/shared/crossfade.ts +70 -0
- package/src/controls/shared/diagnostics.ts +13 -0
- package/src/controls/shared/dom.ts +310 -0
- package/src/controls/shared/events.ts +14 -0
- package/src/controls/shared/horizontal-navigation.ts +106 -0
- package/src/controls/shared/icons.ts +64 -0
- package/src/controls/shared/modal-animation.ts +109 -0
- package/src/controls/shared/modal.ts +286 -0
- package/src/controls/shared/projection-id.ts +2 -0
- package/src/controls/shared/scroll-placement.ts +1 -0
- package/src/controls/shared/tab-bar-width.ts +66 -0
- package/src/controls/shared/toolbar-options.ts +51 -0
- package/src/controls/shared/transition-identity.ts +67 -0
- package/src/controls/shared/vertical-bars-geometry.ts +92 -0
- package/src/controls/shared/vertical-navigation.ts +34 -0
- package/src/controls/startup.ts +74 -0
- package/src/controls/transport.ts +11 -0
- package/src/controls/vertical-bars-web-search.ts +86 -0
- package/src/controls/vertical-bars-web.ts +674 -0
- package/src/dom/geometry.ts +24 -0
- package/src/dom/overlays.ts +196 -0
- package/src/dom/scroll-bindings.ts +66 -0
- package/src/dom/scroll-placement.ts +92 -0
- package/src/dom/visibility.ts +29 -0
- package/src/index.ts +19 -0
- package/src/integration/index.ts +64 -0
- package/src/ionic.ts +34 -0
- package/src/native/bootstrap.ts +132 -0
- package/src/native/bridge.ts +18 -0
- package/src/native/resource-url.ts +11 -0
- package/src/native/session.ts +708 -0
- package/src/native/updates.ts +103 -0
- package/src/native/values.ts +29 -0
- package/src/projection/events.ts +58 -0
- package/src/projection/pause.ts +58 -0
- package/src/projection/placement.ts +34 -0
- package/src/projection/scope.ts +13 -0
- package/src/projection/styles.ts +26 -0
- package/src/projection/surface.ts +88 -0
- package/src/projection/surfaces.ts +119 -0
- package/src/projection/web.ts +1013 -0
- package/src/types.ts +58 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
import { applyNativeValues, type NativeValueBinding } from './values.js';
|
|
2
|
+
import { projectionPlacement } from '../projection/placement.js';
|
|
3
|
+
import { getProjectionPause } from '../projection/pause.js';
|
|
4
|
+
import { readScrollPlacement } from '../dom/scroll-placement.js';
|
|
5
|
+
import { projectionAncestors } from '../dom/geometry.js';
|
|
6
|
+
import { createWebProjection, type WebProjection } from '../projection/web.js';
|
|
7
|
+
import { createWebProjectionScope } from '../projection/scope.js';
|
|
8
|
+
import { bindProjectionEvents, type ProjectionDestination } from '../projection/events.js';
|
|
9
|
+
import type { Frame, ScrollPlacement, ListenerHandle, WebProjectionRequest } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/** Serialized by the native library. One registration owns one fixed fallback container. */
|
|
12
|
+
export interface NativeSessionConfiguration {
|
|
13
|
+
id: string;
|
|
14
|
+
plugin: string;
|
|
15
|
+
runtime?: 'capacitor' | 'cordova';
|
|
16
|
+
projectionSelector: string;
|
|
17
|
+
captureDelayMs?: number;
|
|
18
|
+
/** Value reflection is opt-in; native supplies the current values. */
|
|
19
|
+
values?: Record<string, NativeValueBinding>;
|
|
20
|
+
/** Recovery while native is requested. Explicit web/none destinations take precedence. */
|
|
21
|
+
failedFallback?: 'web' | 'native';
|
|
22
|
+
captureMode?: 'composited' | 'view';
|
|
23
|
+
/** Native registration accepts a final placement in its reveal request. */
|
|
24
|
+
placementHandoff?: boolean;
|
|
25
|
+
/** Android can cover prepared pixels before a Web scroll event arrives. */
|
|
26
|
+
nativeDragHandoff?: boolean;
|
|
27
|
+
receiveListeners?: {
|
|
28
|
+
update?: string[];
|
|
29
|
+
projection?: Partial<Record<ProjectionDestination, string[]>>;
|
|
30
|
+
match?: Record<string, string | number | boolean>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface NativeSessionBridge {
|
|
34
|
+
send(
|
|
35
|
+
request:
|
|
36
|
+
| WebProjectionRequest
|
|
37
|
+
| { action: 'resume'; token?: string }
|
|
38
|
+
| { action: 'visibility'; visible: boolean }
|
|
39
|
+
| {
|
|
40
|
+
action: 'track';
|
|
41
|
+
viewportWidth: number;
|
|
42
|
+
scroll?: ScrollPlacement;
|
|
43
|
+
gesture?: { viewport: Frame; offsetX: number; offsetY: number; rangeX: number; rangeY: number };
|
|
44
|
+
}
|
|
45
|
+
| { action: 'connect' }
|
|
46
|
+
| { action: 'values' }
|
|
47
|
+
| { action: 'destination'; destination: ProjectionDestination },
|
|
48
|
+
): Promise<any>;
|
|
49
|
+
listen(listener: (event: { active: boolean; precovered?: string }) => void): Promise<ListenerHandle>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Native startup calls this; application code does not initialize or wrap the plugin. */
|
|
53
|
+
export const startNativeSession = async (
|
|
54
|
+
config: NativeSessionConfiguration,
|
|
55
|
+
nativeBridge: NativeSessionBridge,
|
|
56
|
+
plugin: { addListener(name: string, listener: (event: unknown) => void): Promise<ListenerHandle> } = {
|
|
57
|
+
addListener: async (name, listener) => {
|
|
58
|
+
const capacitor = (
|
|
59
|
+
globalThis as unknown as {
|
|
60
|
+
Capacitor: {
|
|
61
|
+
addListener(plugin: string, event: string, listener: (event: unknown) => void): ListenerHandle;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
).Capacitor;
|
|
65
|
+
return capacitor.addListener(config.plugin, name, listener);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
) => {
|
|
69
|
+
const sourceID = 'view';
|
|
70
|
+
const listeners = config.receiveListeners;
|
|
71
|
+
let stopped = false;
|
|
72
|
+
let ready = false;
|
|
73
|
+
let requestedDestination: ProjectionDestination = 'native';
|
|
74
|
+
let destination: ProjectionDestination = 'native';
|
|
75
|
+
const projectionRequest = getProjectionPause(document);
|
|
76
|
+
const effectiveDestination = (): ProjectionDestination =>
|
|
77
|
+
requestedDestination === 'native' && projectionRequest.isPaused(target) ? 'web' : requestedDestination;
|
|
78
|
+
let automaticMotion = false;
|
|
79
|
+
let nativeDragging = false;
|
|
80
|
+
let placementPending = false;
|
|
81
|
+
let nativeScroll = false;
|
|
82
|
+
let scrollOwner: HTMLElement | undefined;
|
|
83
|
+
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
|
84
|
+
let manager: WebProjection | undefined;
|
|
85
|
+
let startingProjection = false;
|
|
86
|
+
const beginProjection = () => {
|
|
87
|
+
const previous = startingProjection;
|
|
88
|
+
startingProjection = true;
|
|
89
|
+
try {
|
|
90
|
+
manager?.begin();
|
|
91
|
+
} finally {
|
|
92
|
+
startingProjection = previous;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
let target: HTMLElement | undefined;
|
|
96
|
+
let valueRequest = 0;
|
|
97
|
+
let cancelValueWait: (() => void) | undefined;
|
|
98
|
+
const retireValueRead = () => {
|
|
99
|
+
valueRequest++;
|
|
100
|
+
cancelValueWait?.();
|
|
101
|
+
cancelValueWait = undefined;
|
|
102
|
+
};
|
|
103
|
+
const refreshValues = async () => {
|
|
104
|
+
if (!config.values || !target || stopped) return;
|
|
105
|
+
const element = target;
|
|
106
|
+
retireValueRead();
|
|
107
|
+
const request = valueRequest;
|
|
108
|
+
const current = () => !stopped && request === valueRequest && target === element && findTarget() === element && element.isConnected;
|
|
109
|
+
// Defining a custom element installs its setters. Earlier assignment would shadow them.
|
|
110
|
+
if (
|
|
111
|
+
element.localName.includes('-') &&
|
|
112
|
+
!element.matches(':defined') &&
|
|
113
|
+
Object.values(config.values).some((binding) => binding.property)
|
|
114
|
+
) {
|
|
115
|
+
await Promise.race([
|
|
116
|
+
element.ownerDocument.defaultView!.customElements.whenDefined(element.localName),
|
|
117
|
+
new Promise<void>((resolve) => {
|
|
118
|
+
cancelValueWait = resolve;
|
|
119
|
+
}),
|
|
120
|
+
]);
|
|
121
|
+
if (!current()) return;
|
|
122
|
+
cancelValueWait = undefined;
|
|
123
|
+
}
|
|
124
|
+
const result = await nativeBridge.send({ action: 'values' }).catch(() => undefined);
|
|
125
|
+
if (!current()) return;
|
|
126
|
+
if (result?.values && typeof result.values === 'object' && !Array.isArray(result.values)) {
|
|
127
|
+
try {
|
|
128
|
+
applyNativeValues(element, result.values, config.values);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn('[Native UI Engine] Cannot reflect values', error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const placementOwner = {};
|
|
135
|
+
let coveredToken: string | undefined;
|
|
136
|
+
let placementEpoch = 0;
|
|
137
|
+
const releases = new Set<Promise<unknown>>();
|
|
138
|
+
let cancelReleaseWait = () => {};
|
|
139
|
+
let releaseCancellation = new Promise<void>((resolve) => {
|
|
140
|
+
cancelReleaseWait = resolve;
|
|
141
|
+
});
|
|
142
|
+
const cancelReleases = () => {
|
|
143
|
+
releases.clear();
|
|
144
|
+
cancelReleaseWait();
|
|
145
|
+
releaseCancellation = new Promise<void>((resolve) => {
|
|
146
|
+
cancelReleaseWait = resolve;
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const clearPlacement = () => {
|
|
150
|
+
placementEpoch++;
|
|
151
|
+
coveredToken = undefined;
|
|
152
|
+
const binding = projectionPlacement(target);
|
|
153
|
+
if (binding?.owner === placementOwner) {
|
|
154
|
+
binding.projected = false;
|
|
155
|
+
binding.owner = undefined;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const bridge: NativeSessionBridge = {
|
|
159
|
+
listen: (listener) =>
|
|
160
|
+
nativeBridge.listen((event) => {
|
|
161
|
+
listener(event);
|
|
162
|
+
if (!event.active && nativeDragging) {
|
|
163
|
+
nativeDragging = false;
|
|
164
|
+
endAutomaticMotionLater();
|
|
165
|
+
}
|
|
166
|
+
if (event.precovered) adoptNativeCover(event.precovered);
|
|
167
|
+
}),
|
|
168
|
+
send: (request) => {
|
|
169
|
+
if (!config.placementHandoff) return nativeBridge.send(request);
|
|
170
|
+
if (request.action === 'visibility' && !request.visible) cancelReleases();
|
|
171
|
+
if (request.action === 'visibility' && request.visible && releases.size) {
|
|
172
|
+
const version = generation;
|
|
173
|
+
const epoch = placementEpoch;
|
|
174
|
+
const host = target;
|
|
175
|
+
const cancellation = releaseCancellation;
|
|
176
|
+
// All reveal paths (including scroll fallback) wait for idle release's placement/draw ACK.
|
|
177
|
+
// Re-enter to include releases added while waiting. Hide/teardown cancels the wait.
|
|
178
|
+
return Promise.race([Promise.allSettled([...releases]), cancellation]).then(() => {
|
|
179
|
+
if (
|
|
180
|
+
stopped ||
|
|
181
|
+
cancellation !== releaseCancellation ||
|
|
182
|
+
version !== generation ||
|
|
183
|
+
epoch !== placementEpoch ||
|
|
184
|
+
host !== target ||
|
|
185
|
+
!host?.isConnected ||
|
|
186
|
+
findTarget() !== target ||
|
|
187
|
+
effectiveDestination() !== 'native'
|
|
188
|
+
)
|
|
189
|
+
return {};
|
|
190
|
+
return bridge.send(request);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const binding = projectionPlacement(target);
|
|
194
|
+
if (request.action === 'cover') {
|
|
195
|
+
const alreadyCovered = coveredToken === request.token && binding?.owner === placementOwner && binding.projected;
|
|
196
|
+
const epoch = ++placementEpoch;
|
|
197
|
+
coveredToken = request.token;
|
|
198
|
+
if (binding) {
|
|
199
|
+
binding.owner = placementOwner;
|
|
200
|
+
binding.projected = !!alreadyCovered;
|
|
201
|
+
}
|
|
202
|
+
return nativeBridge
|
|
203
|
+
.send(request)
|
|
204
|
+
.then((result) => {
|
|
205
|
+
if (epoch === placementEpoch && binding === projectionPlacement(target) && binding?.owner === placementOwner)
|
|
206
|
+
binding.projected = result?.shown === true;
|
|
207
|
+
return result;
|
|
208
|
+
})
|
|
209
|
+
.catch((error) => {
|
|
210
|
+
if (epoch === placementEpoch && binding?.owner === placementOwner) {
|
|
211
|
+
clearPlacement();
|
|
212
|
+
binding.invalidate();
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const releasing = request.action === 'release' && request.token === coveredToken;
|
|
218
|
+
const revealing =
|
|
219
|
+
coveredToken &&
|
|
220
|
+
(request.action === 'restore' || releasing || request.action === 'reset' || (request.action === 'visibility' && request.visible));
|
|
221
|
+
if (revealing) placementEpoch++;
|
|
222
|
+
const epoch = placementEpoch;
|
|
223
|
+
const failed = () => {
|
|
224
|
+
if (epoch !== placementEpoch || binding?.owner !== placementOwner) return;
|
|
225
|
+
clearPlacement();
|
|
226
|
+
binding.invalidate();
|
|
227
|
+
};
|
|
228
|
+
// Measure synchronously and use the existing ordered reveal/draw acknowledgement.
|
|
229
|
+
try {
|
|
230
|
+
const payload =
|
|
231
|
+
revealing && binding?.owner === placementOwner
|
|
232
|
+
? { ...request, token: ('token' in request ? request.token : coveredToken)!, placement: binding.measure() }
|
|
233
|
+
: request;
|
|
234
|
+
const pending = nativeBridge
|
|
235
|
+
.send(payload)
|
|
236
|
+
.then(async (result) => {
|
|
237
|
+
// A native drag can race retirement before its notification reaches JS.
|
|
238
|
+
// Keep the prepared underlay until native has applied a fresh return placement.
|
|
239
|
+
if (request.action === 'release' && result?.requiresPlacement) {
|
|
240
|
+
if (binding && (!binding.owner || binding.owner === placementOwner)) {
|
|
241
|
+
result = await nativeBridge.send({ ...request, placement: binding.measure() } as typeof request);
|
|
242
|
+
} else {
|
|
243
|
+
await nativeBridge.send({ action: 'visibility', visible: false });
|
|
244
|
+
result = await nativeBridge.send(request);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
epoch === placementEpoch &&
|
|
249
|
+
(request.action === 'resume' || releasing || request.action === 'reset' || request.action === 'visibility')
|
|
250
|
+
)
|
|
251
|
+
clearPlacement();
|
|
252
|
+
return result;
|
|
253
|
+
})
|
|
254
|
+
.catch((error) => {
|
|
255
|
+
failed();
|
|
256
|
+
throw error;
|
|
257
|
+
});
|
|
258
|
+
if (request.action !== 'release' || !config.nativeDragHandoff) return pending;
|
|
259
|
+
releases.add(pending);
|
|
260
|
+
return pending.finally(() => releases.delete(pending));
|
|
261
|
+
} catch (error) {
|
|
262
|
+
failed();
|
|
263
|
+
return Promise.reject(error);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
let revision = 0;
|
|
269
|
+
let contentRevision = 0;
|
|
270
|
+
let generation = 0;
|
|
271
|
+
let restoring: WebProjection | undefined;
|
|
272
|
+
let initializing = false;
|
|
273
|
+
let scheduled = false;
|
|
274
|
+
let reconciling = false;
|
|
275
|
+
const style = document.createElement('style');
|
|
276
|
+
const marker = `data-native-ui-session-${config.id.replace(/[^a-zA-Z0-9-]/g, '')}`;
|
|
277
|
+
style.textContent = `[${marker}="native"], [${marker}="native"] *, [${marker}="none"], [${marker}="none"] * {visibility:hidden!important}`;
|
|
278
|
+
document.documentElement.append(style);
|
|
279
|
+
const findTarget = () => {
|
|
280
|
+
const matches = document.querySelectorAll(config.projectionSelector);
|
|
281
|
+
return matches.length === 1 && matches[0] instanceof HTMLElement ? matches[0] : undefined;
|
|
282
|
+
};
|
|
283
|
+
const setVisibility = () => {
|
|
284
|
+
const intent = effectiveDestination();
|
|
285
|
+
if (target && target.getAttribute('data-native-ui-destination') !== intent) target.setAttribute('data-native-ui-destination', intent);
|
|
286
|
+
if (!target) return;
|
|
287
|
+
if (destination === 'web' || (destination === 'native' && placementPending)) target.removeAttribute(marker);
|
|
288
|
+
else target.setAttribute(marker, destination);
|
|
289
|
+
};
|
|
290
|
+
const sources = () => {
|
|
291
|
+
if (!target?.isConnected) return [];
|
|
292
|
+
const r = target.getBoundingClientRect();
|
|
293
|
+
const frame: Frame = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
294
|
+
const scroll = scrollOwner && readScrollPlacement(target, scrollOwner, () => config.id);
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
element: target!,
|
|
298
|
+
scrollElement: scrollOwner,
|
|
299
|
+
control: { id: sourceID, ...frame, ...(scroll ? { scroll } : {}) },
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
};
|
|
303
|
+
const recoverFailedProjection = async () => {
|
|
304
|
+
if (stopped) return;
|
|
305
|
+
const version = generation;
|
|
306
|
+
const native = config.failedFallback === 'native' && effectiveDestination() === 'native' && !!target?.isConnected;
|
|
307
|
+
await bridge.send({ action: 'visibility', visible: native }).catch(() => {});
|
|
308
|
+
if (stopped || version !== generation) return;
|
|
309
|
+
if (native || destination === 'none') setVisibility();
|
|
310
|
+
else target?.removeAttribute(marker);
|
|
311
|
+
};
|
|
312
|
+
const applyFailedFallback = () => {
|
|
313
|
+
if (!automaticMotion || destination !== 'native' || !target?.isConnected || manager?.token || manager?.restoring) return;
|
|
314
|
+
const native = config.failedFallback === 'native';
|
|
315
|
+
void bridge.send({ action: 'visibility', visible: native }).catch(() => {});
|
|
316
|
+
if (native) setVisibility();
|
|
317
|
+
else target.removeAttribute(marker);
|
|
318
|
+
};
|
|
319
|
+
const wake = () => {
|
|
320
|
+
// begin() can synchronously retire a previous return before installing its next
|
|
321
|
+
// image. That intermediate state is not a failed projection or a new reveal.
|
|
322
|
+
if (stopped || startingProjection) return;
|
|
323
|
+
applyFailedFallback();
|
|
324
|
+
if (destination === 'web' && manager && !manager.token && !manager.restoring) {
|
|
325
|
+
void bridge.send({ action: 'visibility', visible: false }).catch(() => {});
|
|
326
|
+
target?.removeAttribute(marker);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!manager?.restoring || restoring === manager || destination !== 'native') return;
|
|
330
|
+
restoring = manager;
|
|
331
|
+
const version = generation;
|
|
332
|
+
const current = manager;
|
|
333
|
+
// The native library acknowledges after the live views have entered a draw pass.
|
|
334
|
+
void bridge
|
|
335
|
+
.send({ action: 'resume' })
|
|
336
|
+
.then(() => {
|
|
337
|
+
if (stopped || version !== generation || current !== manager || findTarget() !== target) return;
|
|
338
|
+
placementPending = false;
|
|
339
|
+
current.updated(undefined, [sourceID], ++revision);
|
|
340
|
+
setVisibility();
|
|
341
|
+
schedule();
|
|
342
|
+
})
|
|
343
|
+
.catch(() => {
|
|
344
|
+
if (version !== generation || stopped) return;
|
|
345
|
+
current.invalidate();
|
|
346
|
+
void recoverFailedProjection();
|
|
347
|
+
})
|
|
348
|
+
.finally(() => {
|
|
349
|
+
if (restoring === current) restoring = undefined;
|
|
350
|
+
wake();
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
const reconcile = async () => {
|
|
354
|
+
if (stopped || initializing) return;
|
|
355
|
+
const found = findTarget();
|
|
356
|
+
if (found !== target) {
|
|
357
|
+
generation++;
|
|
358
|
+
automaticMotion = false;
|
|
359
|
+
nativeDragging = false;
|
|
360
|
+
clearTimeout(scrollTimer);
|
|
361
|
+
clearPlacement();
|
|
362
|
+
target?.removeAttribute(marker);
|
|
363
|
+
target?.removeAttribute('data-native-ui-destination');
|
|
364
|
+
retireValueRead();
|
|
365
|
+
// A page/DOM replacement retires its old surface even during a drag or draw ACK.
|
|
366
|
+
// Hide before releasing its image so retirement cannot reveal the departed view.
|
|
367
|
+
const previous = manager;
|
|
368
|
+
const previousTarget = target;
|
|
369
|
+
manager = undefined;
|
|
370
|
+
target = undefined;
|
|
371
|
+
sizeObserver.disconnect();
|
|
372
|
+
refreshScrollListeners();
|
|
373
|
+
try {
|
|
374
|
+
if (previousTarget) await bridge.send({ action: 'visibility', visible: false });
|
|
375
|
+
} finally {
|
|
376
|
+
await previous?.destroy();
|
|
377
|
+
}
|
|
378
|
+
if (stopped) return;
|
|
379
|
+
target = findTarget();
|
|
380
|
+
placementPending = false;
|
|
381
|
+
if (target) {
|
|
382
|
+
sizeObserver.observe(target);
|
|
383
|
+
void refreshValues();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
refreshScrollListeners();
|
|
387
|
+
if (automaticMotion) return;
|
|
388
|
+
if (manager?.restoring) {
|
|
389
|
+
wake();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
destination = effectiveDestination();
|
|
393
|
+
if (!target) {
|
|
394
|
+
await bridge.send({ action: 'visibility', visible: false });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!manager) {
|
|
398
|
+
initializing = true;
|
|
399
|
+
const version = generation;
|
|
400
|
+
try {
|
|
401
|
+
const created = await createWebProjection(createWebProjectionScope(target, config.id), bridge, {
|
|
402
|
+
wake,
|
|
403
|
+
captureDelayMs: config.captureDelayMs,
|
|
404
|
+
retainPreparedUntilRelease: config.nativeDragHandoff,
|
|
405
|
+
blocked: () => destination !== 'native' || automaticMotion || (placementPending && !manager?.token),
|
|
406
|
+
revision: () => revision,
|
|
407
|
+
captureRevision: config.captureMode === 'view' ? () => contentRevision : undefined,
|
|
408
|
+
transient: true,
|
|
409
|
+
opaqueUnderlay: config.captureMode === 'view',
|
|
410
|
+
});
|
|
411
|
+
if (stopped || version !== generation) {
|
|
412
|
+
await created.destroy();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
manager = created;
|
|
416
|
+
} finally {
|
|
417
|
+
initializing = false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (stopped) return;
|
|
421
|
+
if (destination === 'native' && placementPending) {
|
|
422
|
+
await bridge.send({ action: 'visibility', visible: false });
|
|
423
|
+
setVisibility();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (destination === 'native') {
|
|
427
|
+
const version = generation;
|
|
428
|
+
await bridge.send({ action: 'visibility', visible: true });
|
|
429
|
+
if (stopped || version !== generation || destination !== 'native' || automaticMotion) return;
|
|
430
|
+
setVisibility();
|
|
431
|
+
scrollOwner = [...projectionAncestors(target!)].find((element) => {
|
|
432
|
+
const style = getComputedStyle(element);
|
|
433
|
+
return (
|
|
434
|
+
/auto|scroll/.test(style.overflowX + style.overflowY) &&
|
|
435
|
+
(element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth)
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
const scroll = scrollOwner && readScrollPlacement(target!, scrollOwner, () => config.id);
|
|
439
|
+
// Gesture gating needs a hit region, not native compositor scroll eligibility.
|
|
440
|
+
// Rounded/clipped Ionic hosts can reject readScrollPlacement while still scrolling in Web.
|
|
441
|
+
const gesture =
|
|
442
|
+
config.nativeDragHandoff && config.captureMode === 'view' && scrollOwner
|
|
443
|
+
? {
|
|
444
|
+
viewport: scrollOwner.getBoundingClientRect(),
|
|
445
|
+
offsetX: scrollOwner.scrollLeft,
|
|
446
|
+
offsetY: scrollOwner.scrollTop,
|
|
447
|
+
rangeX: Math.max(0, scrollOwner.scrollWidth - scrollOwner.clientWidth),
|
|
448
|
+
rangeY: Math.max(0, scrollOwner.scrollHeight - scrollOwner.clientHeight),
|
|
449
|
+
}
|
|
450
|
+
: undefined;
|
|
451
|
+
const tracking = await bridge.send({
|
|
452
|
+
action: 'track',
|
|
453
|
+
viewportWidth: innerWidth,
|
|
454
|
+
...(scroll ? { scroll } : {}),
|
|
455
|
+
...(gesture ? { gesture } : {}),
|
|
456
|
+
});
|
|
457
|
+
if (stopped || version !== generation) return;
|
|
458
|
+
nativeScroll = tracking?.tracked === true;
|
|
459
|
+
manager.consider(config.captureMode === 'view' ? contentRevision : ++revision, sources());
|
|
460
|
+
} else {
|
|
461
|
+
await bridge.send({ action: 'visibility', visible: false });
|
|
462
|
+
setVisibility();
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
const schedule = () => {
|
|
466
|
+
if (stopped) return;
|
|
467
|
+
scheduled = true;
|
|
468
|
+
if (!ready || reconciling) return;
|
|
469
|
+
reconciling = true;
|
|
470
|
+
void Promise.resolve().then(async () => {
|
|
471
|
+
try {
|
|
472
|
+
while (scheduled && !stopped) {
|
|
473
|
+
scheduled = false;
|
|
474
|
+
await reconcile();
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
// Preserve the selected recovery policy without overriding a newer destination.
|
|
478
|
+
await recoverFailedProjection();
|
|
479
|
+
} finally {
|
|
480
|
+
reconciling = false;
|
|
481
|
+
if (scheduled) schedule();
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
const resized = () => {
|
|
486
|
+
manager?.invalidate();
|
|
487
|
+
schedule();
|
|
488
|
+
};
|
|
489
|
+
const sizeObserver = new ResizeObserver(resized);
|
|
490
|
+
window.addEventListener('resize', resized);
|
|
491
|
+
const scrollAncestors = new Set<HTMLElement>();
|
|
492
|
+
const handledScrolls = new WeakSet<Event>();
|
|
493
|
+
const onScroll = (event: Event) => {
|
|
494
|
+
const owner = event.composedPath()[0] ?? event.target;
|
|
495
|
+
if (
|
|
496
|
+
stopped ||
|
|
497
|
+
destination !== 'native' ||
|
|
498
|
+
!target ||
|
|
499
|
+
handledScrolls.has(event) ||
|
|
500
|
+
(owner !== document && ![...projectionAncestors(target)].includes(owner as HTMLElement))
|
|
501
|
+
)
|
|
502
|
+
return;
|
|
503
|
+
handledScrolls.add(event);
|
|
504
|
+
if (nativeScroll && owner === scrollOwner) {
|
|
505
|
+
manager?.changed(owner as EventTarget);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (!automaticMotion) {
|
|
509
|
+
// A fresh gesture supersedes the preceding presentation's pending draw ACK.
|
|
510
|
+
// begin() retires that return image and starts another presentation on the same manager.
|
|
511
|
+
if (restoring || manager?.restoring) generation++;
|
|
512
|
+
automaticMotion = true;
|
|
513
|
+
beginProjection();
|
|
514
|
+
placementPending = config.failedFallback !== 'native';
|
|
515
|
+
if (manager?.token) target.removeAttribute(marker);
|
|
516
|
+
else applyFailedFallback();
|
|
517
|
+
} else if (config.captureMode === 'view') manager?.moved();
|
|
518
|
+
else manager?.changed();
|
|
519
|
+
endAutomaticMotionLater();
|
|
520
|
+
};
|
|
521
|
+
function endAutomaticMotionLater() {
|
|
522
|
+
clearTimeout(scrollTimer);
|
|
523
|
+
scrollTimer = setTimeout(() => {
|
|
524
|
+
if (nativeDragging) return;
|
|
525
|
+
automaticMotion = false;
|
|
526
|
+
if (stopped || destination !== 'native') return;
|
|
527
|
+
manager?.end();
|
|
528
|
+
if (!manager?.restoring) schedule();
|
|
529
|
+
}, 100);
|
|
530
|
+
}
|
|
531
|
+
function adoptNativeCover(token: string) {
|
|
532
|
+
if (stopped || destination !== 'native' || !target?.isConnected || !manager) return;
|
|
533
|
+
nativeDragging = true;
|
|
534
|
+
// A scroll handler may have adopted the same native cover before its notification arrives.
|
|
535
|
+
if (manager.token) return;
|
|
536
|
+
if (restoring || manager.restoring) generation++;
|
|
537
|
+
coveredToken = token;
|
|
538
|
+
const binding = projectionPlacement(target);
|
|
539
|
+
if (binding) {
|
|
540
|
+
binding.owner = placementOwner;
|
|
541
|
+
binding.projected = true;
|
|
542
|
+
}
|
|
543
|
+
automaticMotion = true;
|
|
544
|
+
beginProjection();
|
|
545
|
+
if (!manager.token) {
|
|
546
|
+
automaticMotion = false;
|
|
547
|
+
void recoverFailedProjection();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
endAutomaticMotionLater();
|
|
551
|
+
}
|
|
552
|
+
// Listen on composed-tree ancestors themselves: non-composed scroll events inside
|
|
553
|
+
// Ionic's shadow tree never reach document, and Node.contains excludes slotted children.
|
|
554
|
+
const refreshScrollListeners = () => {
|
|
555
|
+
const next = new Set(target ? projectionAncestors(target) : []);
|
|
556
|
+
const changed = next.size !== scrollAncestors.size || [...next].some((element) => !scrollAncestors.has(element));
|
|
557
|
+
for (const element of scrollAncestors)
|
|
558
|
+
if (!next.has(element)) {
|
|
559
|
+
element.removeEventListener('scroll', onScroll);
|
|
560
|
+
element.removeEventListener('slotchange', slotChanged);
|
|
561
|
+
scrollAncestors.delete(element);
|
|
562
|
+
}
|
|
563
|
+
for (const element of next)
|
|
564
|
+
if (!scrollAncestors.has(element)) {
|
|
565
|
+
element.addEventListener('scroll', onScroll, { passive: true });
|
|
566
|
+
element.addEventListener('slotchange', slotChanged);
|
|
567
|
+
scrollAncestors.add(element);
|
|
568
|
+
}
|
|
569
|
+
if (changed) {
|
|
570
|
+
nativeScroll = false;
|
|
571
|
+
scrollOwner = undefined;
|
|
572
|
+
manager?.invalidate();
|
|
573
|
+
schedule();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const slotChanged = () => {
|
|
577
|
+
refreshScrollListeners();
|
|
578
|
+
schedule();
|
|
579
|
+
};
|
|
580
|
+
document.addEventListener('scroll', onScroll, true);
|
|
581
|
+
const observer = new MutationObserver((records) => {
|
|
582
|
+
refreshScrollListeners();
|
|
583
|
+
if (findTarget() !== target) schedule();
|
|
584
|
+
// Moving the same host into or out of an overlay changes coverage, not registration.
|
|
585
|
+
else if (target && records.some((record) => record.type === 'childList')) applyDestination(effectiveDestination());
|
|
586
|
+
});
|
|
587
|
+
observer.observe(document.documentElement, { childList: true, attributes: true, subtree: true });
|
|
588
|
+
const applyDestination = (next: ProjectionDestination, force?: ProjectionDestination) => {
|
|
589
|
+
if (stopped || (!force && next === destination)) return;
|
|
590
|
+
automaticMotion = false;
|
|
591
|
+
nativeDragging = false;
|
|
592
|
+
clearTimeout(scrollTimer);
|
|
593
|
+
destination = next;
|
|
594
|
+
generation++;
|
|
595
|
+
const version = generation;
|
|
596
|
+
if (destination === 'none') {
|
|
597
|
+
// Retire native visibility before releasing images; release must not resurrect hidden views.
|
|
598
|
+
void bridge
|
|
599
|
+
.send({ action: 'visibility', visible: false })
|
|
600
|
+
.then(() => {
|
|
601
|
+
if (destination !== 'none' || version !== generation || stopped) return;
|
|
602
|
+
manager?.invalidate(true);
|
|
603
|
+
setVisibility();
|
|
604
|
+
})
|
|
605
|
+
.catch(() => {
|
|
606
|
+
if (destination !== 'none' || version !== generation || stopped) return;
|
|
607
|
+
manager?.invalidate(true);
|
|
608
|
+
setVisibility();
|
|
609
|
+
});
|
|
610
|
+
} else if (destination === 'web') {
|
|
611
|
+
// A native show starts a new local request; the screen-wide Web request prevents restoration.
|
|
612
|
+
if (force === 'native' && projectionRequest.isPaused()) manager?.end();
|
|
613
|
+
beginProjection();
|
|
614
|
+
if (!manager?.token) void bridge.send({ action: 'visibility', visible: false }).catch(() => {});
|
|
615
|
+
setVisibility();
|
|
616
|
+
} else if (destination === 'native') {
|
|
617
|
+
manager?.end();
|
|
618
|
+
if (!manager?.restoring) schedule();
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
// Let a renderer claim a native child window during the same Ionic event dispatch.
|
|
622
|
+
const stopPause = projectionRequest.subscribe(() =>
|
|
623
|
+
queueMicrotask(() => {
|
|
624
|
+
if (ready) applyDestination(effectiveDestination());
|
|
625
|
+
}),
|
|
626
|
+
);
|
|
627
|
+
let events: ListenerHandle | undefined;
|
|
628
|
+
const destroy = async () => {
|
|
629
|
+
if (stopped) return;
|
|
630
|
+
stopped = true;
|
|
631
|
+
cancelReleases();
|
|
632
|
+
retireValueRead();
|
|
633
|
+
clearPlacement();
|
|
634
|
+
generation++;
|
|
635
|
+
stopPause();
|
|
636
|
+
observer.disconnect();
|
|
637
|
+
sizeObserver.disconnect();
|
|
638
|
+
window.removeEventListener('resize', resized);
|
|
639
|
+
document.removeEventListener('scroll', onScroll, true);
|
|
640
|
+
for (const element of scrollAncestors) {
|
|
641
|
+
element.removeEventListener('scroll', onScroll);
|
|
642
|
+
element.removeEventListener('slotchange', slotChanged);
|
|
643
|
+
}
|
|
644
|
+
scrollAncestors.clear();
|
|
645
|
+
clearTimeout(scrollTimer);
|
|
646
|
+
target?.removeAttribute(marker);
|
|
647
|
+
target?.removeAttribute('data-native-ui-destination');
|
|
648
|
+
style.remove();
|
|
649
|
+
await Promise.allSettled([Promise.resolve(events?.remove()), manager?.destroy()]);
|
|
650
|
+
};
|
|
651
|
+
try {
|
|
652
|
+
const initialGeneration = generation;
|
|
653
|
+
events = await bindProjectionEvents(
|
|
654
|
+
{
|
|
655
|
+
addListener: (name, listener) => plugin.addListener(name, listener),
|
|
656
|
+
update: listeners?.update,
|
|
657
|
+
projection: listeners?.projection,
|
|
658
|
+
accepts: (event) =>
|
|
659
|
+
!listeners?.match ||
|
|
660
|
+
Object.entries(listeners?.match).every(
|
|
661
|
+
([key, value]) => typeof event === 'object' && event !== null && (event as Record<string, unknown>)[key] === value,
|
|
662
|
+
),
|
|
663
|
+
},
|
|
664
|
+
(next, updated) => {
|
|
665
|
+
if (stopped) return;
|
|
666
|
+
if (next) {
|
|
667
|
+
if (next === 'native') placementPending = false;
|
|
668
|
+
requestedDestination = next;
|
|
669
|
+
void bridge.send({ action: 'destination', destination: next }).catch(() => {});
|
|
670
|
+
applyDestination(effectiveDestination(), next);
|
|
671
|
+
}
|
|
672
|
+
if (updated) {
|
|
673
|
+
void refreshValues();
|
|
674
|
+
contentRevision++;
|
|
675
|
+
manager?.changed();
|
|
676
|
+
if (destination === 'native') schedule();
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
);
|
|
680
|
+
const initial = await bridge.send({ action: 'connect' });
|
|
681
|
+
if (generation === initialGeneration && ['native', 'web', 'none'].includes(initial?.destination))
|
|
682
|
+
requestedDestination = initial.destination;
|
|
683
|
+
destination = effectiveDestination();
|
|
684
|
+
ready = true;
|
|
685
|
+
schedule();
|
|
686
|
+
return {
|
|
687
|
+
destroy,
|
|
688
|
+
show: () => {
|
|
689
|
+
placementPending = false;
|
|
690
|
+
requestedDestination = 'native';
|
|
691
|
+
applyDestination(effectiveDestination(), 'native');
|
|
692
|
+
},
|
|
693
|
+
hide: () => {
|
|
694
|
+
requestedDestination = destination = 'none';
|
|
695
|
+
generation++;
|
|
696
|
+
cancelReleases();
|
|
697
|
+
automaticMotion = false;
|
|
698
|
+
nativeDragging = false;
|
|
699
|
+
clearTimeout(scrollTimer);
|
|
700
|
+
manager?.invalidate(true);
|
|
701
|
+
setVisibility();
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
} catch (error) {
|
|
705
|
+
await destroy();
|
|
706
|
+
throw error;
|
|
707
|
+
}
|
|
708
|
+
};
|