@webview-native-ui/engine 1.0.1 → 1.0.2
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,338 @@
|
|
|
1
|
+
import type { WebProjectionTransport } from '../index.js';
|
|
2
|
+
import type { NativeUIExtension } from './extensions.js';
|
|
3
|
+
import type { NativeUIListenerHandle as PluginListenerHandle } from './transport.js';
|
|
4
|
+
|
|
5
|
+
export type NativeUIComponent =
|
|
6
|
+
'ion-button' | 'ion-buttons' | 'ion-back-button' | 'ion-menu-button' | 'ion-tab-bar' | 'ion-segment' | 'ion-fab';
|
|
7
|
+
|
|
8
|
+
export interface NativeUIStatus {
|
|
9
|
+
state: 'web' | 'native' | 'stopped';
|
|
10
|
+
projected: number;
|
|
11
|
+
updates: number;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Default selection in ordinary (horizontal) or rail (vertical) placement.
|
|
16
|
+
* Explicit source settings override these defaults. This does not choose placement.
|
|
17
|
+
*/
|
|
18
|
+
export type ControlProjection = boolean | 'horizontalOnly' | 'verticalOnly';
|
|
19
|
+
|
|
20
|
+
export interface NativeUIControls {
|
|
21
|
+
/** Group selected ordinary buttons. Does not select children or include back buttons. */
|
|
22
|
+
buttonGroup?: ControlProjection;
|
|
23
|
+
/** Select ordinary actions, including menu buttons. */
|
|
24
|
+
button?: ControlProjection;
|
|
25
|
+
backButton?: ControlProjection;
|
|
26
|
+
tabs?: ControlProjection;
|
|
27
|
+
segment?: ControlProjection;
|
|
28
|
+
fab?: ControlProjection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IonicProjectionOptions {
|
|
32
|
+
/** Discover supported Ionic controls automatically. Explicit sources remain independent. Defaults to true. */
|
|
33
|
+
auto?: boolean;
|
|
34
|
+
verticalBars?: {
|
|
35
|
+
/** Native appearance of vertical Ionic buttons. Local source settings take precedence. Defaults to system. */
|
|
36
|
+
buttonProjection?: 'system' | 'source';
|
|
37
|
+
/** Source-style standalone ion-button default; ion-buttons still defaults to clear. Defaults to glass. */
|
|
38
|
+
buttonDefaultFill?: 'glass' | 'solid';
|
|
39
|
+
/** Relay Ionic popovers anchored to native rail buttons. Defaults to false. */
|
|
40
|
+
popover?: boolean;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface NativeUIOptions {
|
|
45
|
+
/** Connected source subtree. Defaults to document.body. */
|
|
46
|
+
root?: HTMLElement;
|
|
47
|
+
/** Default selection and grouping. Scalars apply to every kind; omitted fields default to true. */
|
|
48
|
+
controls?: ControlProjection | NativeUIControls;
|
|
49
|
+
verticalBars?: NativeUIVerticalBarsOptions;
|
|
50
|
+
/** App-local projection definitions, independent of controls.
|
|
51
|
+
* @experimental API and behavior may change.
|
|
52
|
+
*/
|
|
53
|
+
extensions?: readonly NativeUIExtension[];
|
|
54
|
+
ionic?: IonicProjectionOptions;
|
|
55
|
+
/** Log Web fallback reasons. Defaults to false. */
|
|
56
|
+
debug?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Options for the standalone rail entry. */
|
|
60
|
+
export type VerticalControlAreaOptions = NativeUIOptions;
|
|
61
|
+
|
|
62
|
+
/** @internal Resolved adapter state; not an application configuration API. */
|
|
63
|
+
export interface NativeUIRuntimeOptions extends NativeUIOptions {
|
|
64
|
+
source?: 'ionic' | 'html' | 'auto' | 'extensions';
|
|
65
|
+
nativeState?: { edge?: 'left' | 'right' };
|
|
66
|
+
/** @internal Negotiated native support; absent on older binaries. */
|
|
67
|
+
coveredSnapshots?: boolean;
|
|
68
|
+
nativeScroll?: boolean;
|
|
69
|
+
unavailableExtensions?: ReadonlyMap<string, string>;
|
|
70
|
+
rail?: { host: HTMLElement; edge: 'left' | 'right' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A running shell. Placement changes preserve all other startup settings. */
|
|
74
|
+
export interface NativeUIHandle extends NativeUIRuntimeHandle {
|
|
75
|
+
/** Enable rail projection when available, preserving other settings. Rejects after destroy. */
|
|
76
|
+
enableVerticalControlArea(): Promise<void>;
|
|
77
|
+
/** Restore rail sources to the Web without stopping ordinary native controls. Rejects after destroy. */
|
|
78
|
+
disableVerticalControlArea(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface NativeUIVerticalBarsOptions {
|
|
82
|
+
/** Defaults to true. Availability is always determined separately. */
|
|
83
|
+
enabled?: boolean;
|
|
84
|
+
/** Placement preference, not a declaration of device capability. */
|
|
85
|
+
placement?: { edge: Exclude<VerticalBarEdge, null>; webHost?: HTMLElement };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface NativeUIRuntimeHandle {
|
|
89
|
+
/** Schedule a source recheck. Calls coalesce; unchanged snapshots are not sent. No-op after destroy or without a runtime. */
|
|
90
|
+
markForCheck(): void;
|
|
91
|
+
/** Returns the current Web/native projection state. */
|
|
92
|
+
getStatus(): NativeUIStatus;
|
|
93
|
+
/** Restores Web controls until resumed. Use covered only while the background is non-interactive; prepared native images preserve its appearance. */
|
|
94
|
+
suspend(options?: { covered?: boolean }): Promise<NativeUISuspension>;
|
|
95
|
+
/** Stops synchronization, restores Web controls and releases native resources. */
|
|
96
|
+
destroy(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Logical edge in the reading direction, matching UIVerticalBarEdge and capacitor-foldable. */
|
|
100
|
+
export type VerticalBarEdge = 'leading' | 'trailing' | null;
|
|
101
|
+
|
|
102
|
+
export interface VerticalBarPlacement {
|
|
103
|
+
edge: VerticalBarEdge;
|
|
104
|
+
/** Explicit rail width in CSS pixels; omitted to use the stylesheet's safe-area rules. */
|
|
105
|
+
inset?: number;
|
|
106
|
+
/** Ignored. Native availability is read from the plugin’s own device metrics.
|
|
107
|
+
* @deprecated This input no longer reports native availability.
|
|
108
|
+
*/
|
|
109
|
+
nativeEdge?: VerticalBarEdge;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface VerticalControlAreaHandle extends NativeUIHandle {
|
|
113
|
+
/** Legacy Web CSS adapter. Use the theme setVerticalBarsLayout helper instead.
|
|
114
|
+
* @deprecated Web layout belongs to the theme or application.
|
|
115
|
+
*/
|
|
116
|
+
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean): void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface NativeUISuspension {
|
|
120
|
+
/** Releases this suspension. Native projection resumes after all active suspensions are released. */
|
|
121
|
+
resume(): Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface Frame {
|
|
125
|
+
x: number;
|
|
126
|
+
y: number;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Measured native rail surface, in Web viewport pixels. */
|
|
132
|
+
export interface NativeUIVerticalBarFrame extends Frame {
|
|
133
|
+
/** Optional for compatibility with older native binaries. */
|
|
134
|
+
cornerRadius?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface NativeUIBadge {
|
|
138
|
+
value: string;
|
|
139
|
+
color: string;
|
|
140
|
+
textColor: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface NativeUIItem extends Frame {
|
|
144
|
+
id: string;
|
|
145
|
+
/** Toolbar presentation identity; never used to dispatch an action. */
|
|
146
|
+
transitionId?: string;
|
|
147
|
+
label: string;
|
|
148
|
+
accessibilityLabel: string;
|
|
149
|
+
disabled: boolean;
|
|
150
|
+
selected: boolean;
|
|
151
|
+
fontSize: number;
|
|
152
|
+
fontWeight: number;
|
|
153
|
+
color: string;
|
|
154
|
+
/** Resolved ion-button fill for vertical projection; clear omits the native glass background. */
|
|
155
|
+
buttonFill?: 'clear' | 'solid' | 'outline';
|
|
156
|
+
backgroundColor?: string;
|
|
157
|
+
borderColor?: string;
|
|
158
|
+
borderWidth?: number;
|
|
159
|
+
badge?: NativeUIBadge;
|
|
160
|
+
icon?: string;
|
|
161
|
+
iconWidth?: number;
|
|
162
|
+
iconHeight?: number;
|
|
163
|
+
iconPosition?: 'leading' | 'trailing' | 'top';
|
|
164
|
+
imagePadding?: number;
|
|
165
|
+
contentInsetLeading?: number;
|
|
166
|
+
contentInsetTrailing?: number;
|
|
167
|
+
iconTemplate?: boolean;
|
|
168
|
+
visible?: boolean;
|
|
169
|
+
closeIcon?: string;
|
|
170
|
+
closeIconWidth?: number;
|
|
171
|
+
closeIconHeight?: number;
|
|
172
|
+
iconTransition?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Standard native renderers, independent of the source Web framework. */
|
|
176
|
+
export type NativeUIControlKind =
|
|
177
|
+
'button' | 'button-group' | 'back-button' | 'menu-button' | 'tab-bar' | 'segment' | 'floating-action-button';
|
|
178
|
+
|
|
179
|
+
/** @internal Layout sample for one native-owned overflow scroll container. */
|
|
180
|
+
export interface NativeUIScrollPlacement {
|
|
181
|
+
id: string;
|
|
182
|
+
/** @internal Only reuse a mapping whose update was accepted by Web. */
|
|
183
|
+
reuse?: boolean;
|
|
184
|
+
offsetX: number;
|
|
185
|
+
offsetY: number;
|
|
186
|
+
viewport: Frame;
|
|
187
|
+
clip: Frame;
|
|
188
|
+
contentWidth: number;
|
|
189
|
+
contentHeight: number;
|
|
190
|
+
frame: Frame;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface NativeUIControl extends Frame {
|
|
194
|
+
id: string;
|
|
195
|
+
/** Stable toolbar group identity, independent of the source control ID. */
|
|
196
|
+
transitionId?: string;
|
|
197
|
+
kind: NativeUIControlKind | 'extension';
|
|
198
|
+
scroll?: NativeUIScrollPlacement;
|
|
199
|
+
extension?: { name: string; props: string; actions: Record<string, string>; tabBarId?: string };
|
|
200
|
+
/** Lets the native host own adaptive placement instead of mirroring the DOM frame. */
|
|
201
|
+
placement?: 'vertical-bars';
|
|
202
|
+
/** Logical Ionic toolbar slot, preserved when projecting to the vertical rail. */
|
|
203
|
+
toolbarSlot?: 'start' | 'end';
|
|
204
|
+
items: NativeUIItem[];
|
|
205
|
+
dark: boolean;
|
|
206
|
+
rtl: boolean;
|
|
207
|
+
/** Applied selection tint for native tabs; other tab appearance remains system-owned. */
|
|
208
|
+
tabTint?: string;
|
|
209
|
+
tabBarAnchor?: { x: 0 | 0.5 | 1; y: 0 | 1 };
|
|
210
|
+
search?: NativeUISearch;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface NativeUISearch {
|
|
214
|
+
id: string;
|
|
215
|
+
field: NativeUIItem;
|
|
216
|
+
trigger: NativeUIItem;
|
|
217
|
+
closeId: string;
|
|
218
|
+
active: boolean;
|
|
219
|
+
available: boolean;
|
|
220
|
+
focused: boolean;
|
|
221
|
+
value: string;
|
|
222
|
+
placeholder: string;
|
|
223
|
+
disabled: boolean;
|
|
224
|
+
editSequence: number;
|
|
225
|
+
valueVersion: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface NativeUISearchEvent extends NativeUIActivation {
|
|
229
|
+
valueVersion: number;
|
|
230
|
+
phase: 'input' | 'focus' | 'blur' | 'clear' | 'commit';
|
|
231
|
+
value: string;
|
|
232
|
+
composing: boolean;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface NativeUISnapshot {
|
|
236
|
+
/** Prepared Web image currently covering the inactive root surface. */
|
|
237
|
+
coveredSnapshotToken?: string;
|
|
238
|
+
revision: number;
|
|
239
|
+
transitionDuration?: number;
|
|
240
|
+
/** Prepared native toolbar endpoints, controlled by the page animation clock. */
|
|
241
|
+
toolbarTransition?: { id: number; sourceIds: string[]; destination: NativeUIControl[] };
|
|
242
|
+
viewportWidth: number;
|
|
243
|
+
/** Physical side the native rail is drawn on; the runtime resolves the logical edge through the document direction. */
|
|
244
|
+
verticalBarEdge?: 'left' | 'right';
|
|
245
|
+
/** Visible foreground modal bounds; omitted for the full page. */
|
|
246
|
+
verticalBarFrame?: Frame;
|
|
247
|
+
modalAnimation?: import('./shared/modal-animation.js').ModalAnimation;
|
|
248
|
+
/** Web-owned page identities, from root through the active rail destination. */
|
|
249
|
+
verticalBarNavigation?: string[];
|
|
250
|
+
/** Keep the current native rail until an interactive navigation settles. */
|
|
251
|
+
verticalBarNavigationPhase?: 'tracking' | 'settling';
|
|
252
|
+
controls: NativeUIControl[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface NativeUIActivation {
|
|
256
|
+
revision: number;
|
|
257
|
+
id: string;
|
|
258
|
+
sequence: number;
|
|
259
|
+
/** Native extension action awaiting a Web dispatch acknowledgement. */
|
|
260
|
+
requestId?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface WebViewMetrics {
|
|
264
|
+
/** The WebView's effective top-left corner radius in points, or `0` when unavailable. */
|
|
265
|
+
radius: number;
|
|
266
|
+
/** Device-reported logical rail edge; null when unavailable. */
|
|
267
|
+
verticalBarEdge?: VerticalBarEdge;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** @internal Native ownership snapshot; reading it never performs layout. */
|
|
271
|
+
export interface NativeUIConnectionState {
|
|
272
|
+
session: string;
|
|
273
|
+
revision: number;
|
|
274
|
+
sequence: number;
|
|
275
|
+
listeners: string[];
|
|
276
|
+
surfaces: { horizontal: boolean; vertical: boolean };
|
|
277
|
+
overlay?: { id: string; state: 'presented' | 'dismissed' | 'missing' };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** @internal Prepared native pixels, served through Capacitor's local resource handler. */
|
|
281
|
+
export interface CoveredSnapshot {
|
|
282
|
+
token: string;
|
|
283
|
+
parts: (Frame & { ids: string[]; url: string })[];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface NativeUIPlugin {
|
|
287
|
+
configure(options?: { suppressScrollEdgeEffect?: boolean }): Promise<{
|
|
288
|
+
supported: boolean;
|
|
289
|
+
extensions?: string[];
|
|
290
|
+
modalAnimation?: boolean;
|
|
291
|
+
connectionState?: boolean;
|
|
292
|
+
coveredSnapshots?: boolean;
|
|
293
|
+
nativeScroll?: boolean;
|
|
294
|
+
}>;
|
|
295
|
+
coveredSnapshot?: WebProjectionTransport['send'];
|
|
296
|
+
completeExtensionAction(options: { requestId: string; accepted: boolean }): Promise<void>;
|
|
297
|
+
getWebViewMetrics(): Promise<WebViewMetrics>;
|
|
298
|
+
getVerticalBarsGeometry(options: {
|
|
299
|
+
viewportWidth: number;
|
|
300
|
+
ids: string[];
|
|
301
|
+
}): Promise<{ revision?: number; items: Record<string, NativeUIVerticalBarFrame> }>;
|
|
302
|
+
prepareOverlay(options: { id: string }): Promise<void>;
|
|
303
|
+
presentOverlay(options: { id: string; presentation?: NativeUIPopoverPresentation }): Promise<void>;
|
|
304
|
+
getConnectionState(): Promise<NativeUIConnectionState>;
|
|
305
|
+
dismissOverlay(options: { id: string; animated: boolean }): Promise<void>;
|
|
306
|
+
closeOverlay(options: { id: string }): Promise<void>;
|
|
307
|
+
stopOverlays(): Promise<void>;
|
|
308
|
+
update(snapshot: NativeUISnapshot): Promise<{
|
|
309
|
+
revision: number;
|
|
310
|
+
rejectedSearches?: string[];
|
|
311
|
+
rejectedControls?: string[];
|
|
312
|
+
tabBarWidths?: Record<string, number>;
|
|
313
|
+
toolbarTransition?: number;
|
|
314
|
+
}>;
|
|
315
|
+
setToolbarTransitionProgress(options: { id: number; progress: number }): Promise<void>;
|
|
316
|
+
clear(options: { revision: number }): Promise<void>;
|
|
317
|
+
addListener(name: 'snapshotInteraction', listener: (event: { active: boolean }) => void): Promise<PluginListenerHandle>;
|
|
318
|
+
addListener(name: 'activate', listener: (event: NativeUIActivation) => void): Promise<PluginListenerHandle>;
|
|
319
|
+
addListener(name: 'search', listener: (event: NativeUISearchEvent) => void): Promise<PluginListenerHandle>;
|
|
320
|
+
addListener(name: 'webViewMetricsChange', listener: (event: WebViewMetrics) => void): Promise<PluginListenerHandle>;
|
|
321
|
+
addListener(name: 'overlay', listener: (event: NativeUIOverlayEvent) => void): Promise<PluginListenerHandle>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface NativeUIPopoverPresentation {
|
|
325
|
+
kind: 'popover';
|
|
326
|
+
animated: boolean;
|
|
327
|
+
dark: boolean;
|
|
328
|
+
anchorId: string;
|
|
329
|
+
width: number;
|
|
330
|
+
height: number;
|
|
331
|
+
backgroundColor: string;
|
|
332
|
+
backdropDismiss: boolean;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface NativeUIOverlayEvent {
|
|
336
|
+
id: string;
|
|
337
|
+
action: 'dismiss';
|
|
338
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { readScrollPlacement } from './shared/scroll-placement.js';
|
|
2
|
+
import { fitsClip } from './shared/clipping.js';
|
|
3
|
+
export { projectionAncestors as extensionAncestors } from './shared/clipping.js';
|
|
4
|
+
import type { Candidate, Identify } from './shared/candidate.js';
|
|
5
|
+
import { frame, isDark, visible } from './shared/dom.js';
|
|
6
|
+
import { iconSource, type createIconRenderer } from './shared/icons.js';
|
|
7
|
+
|
|
8
|
+
export type NativeUIValue = string | number | boolean | null | NativeUIValue[] | { [key: string]: NativeUIValue };
|
|
9
|
+
|
|
10
|
+
/** Opaque image reference. Return it in props; the shell replaces it with NativeUIImage data before transmission. */
|
|
11
|
+
export type NativeUIImageReference = { readonly $nativeUIImage: string };
|
|
12
|
+
type NativeUIImage = { data: string; width: number; height: number; template: boolean };
|
|
13
|
+
|
|
14
|
+
export interface NativeUIReadContext {
|
|
15
|
+
/** Capture a local SVG or resolved ion-icon using the standard renderer. Unsupported artwork returns null. */
|
|
16
|
+
image(element: SVGElement | HTMLElement | null, options?: { template?: boolean }): NativeUIImageReference | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** An app-local DOM projection. Swift or Kotlin registers its native content under the same name.
|
|
20
|
+
* @experimental API and behavior may change in future releases.
|
|
21
|
+
*/
|
|
22
|
+
export interface NativeUIExtension {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly selector: string;
|
|
25
|
+
/** Defaults to the source element's frame. Unsupported hosts keep the Web source visible. */
|
|
26
|
+
readonly placement?: 'element' | 'tab-accessory';
|
|
27
|
+
/** Experimental iOS native scroll tracking. Return the owning overflow element (Ionic: getScrollElement()).
|
|
28
|
+
* One untransformed scroll container is supported. Unsupported bindings keep the Web source.
|
|
29
|
+
* Return null while the container is not ready. Omit to retain JS placement updates.
|
|
30
|
+
*/
|
|
31
|
+
readonly scrollElement?: (element: HTMLElement) => HTMLElement | null;
|
|
32
|
+
/** Return null to keep this instance on the Web. Read current source state without side effects. Notify property-only changes with shell.markForCheck(). */
|
|
33
|
+
readonly read: (
|
|
34
|
+
element: HTMLElement,
|
|
35
|
+
context: NativeUIReadContext,
|
|
36
|
+
) => {
|
|
37
|
+
props: { [key: string]: NativeUIValue };
|
|
38
|
+
/** Click targets must belong to the source element. Null targets are unavailable. */
|
|
39
|
+
actions?: Record<string, HTMLElement | null>;
|
|
40
|
+
} | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Defines a projection locally; it does not start native work or register a Capacitor plugin.
|
|
44
|
+
* @experimental API and behavior may change in future releases.
|
|
45
|
+
*/
|
|
46
|
+
export const defineNativeUI = <T extends NativeUIExtension>(extension: T): Readonly<T> => Object.freeze({ ...extension });
|
|
47
|
+
|
|
48
|
+
export const validateExtensions = (extensions: readonly NativeUIExtension[], doc: Document): void => {
|
|
49
|
+
const names = new Set<string>();
|
|
50
|
+
for (const extension of extensions) {
|
|
51
|
+
if (!extension.name.trim() || names.has(extension.name)) throw new Error('Native UI extension names must be nonempty and unique');
|
|
52
|
+
names.add(extension.name);
|
|
53
|
+
if (!extension.selector.trim()) throw new Error('Native UI extensions require a selector');
|
|
54
|
+
doc.querySelector(extension.selector);
|
|
55
|
+
if (extension.placement && !['element', 'tab-accessory'].includes(extension.placement)) throw new Error('Invalid native UI placement');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const json = (value: unknown, images: Map<string, NativeUIImage>): string => {
|
|
60
|
+
if (!value || typeof value !== 'object' || Object.getPrototypeOf(value) !== Object.prototype)
|
|
61
|
+
throw new Error('Native UI props must be a JSON object');
|
|
62
|
+
const result = JSON.stringify(value, (_key, item: unknown) => {
|
|
63
|
+
if (
|
|
64
|
+
item === undefined ||
|
|
65
|
+
typeof item === 'function' ||
|
|
66
|
+
typeof item === 'symbol' ||
|
|
67
|
+
(typeof item === 'number' && !Number.isFinite(item)) ||
|
|
68
|
+
(item !== null && typeof item === 'object' && !Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype)
|
|
69
|
+
)
|
|
70
|
+
throw new Error('Native UI props must contain only JSON values');
|
|
71
|
+
if (item && typeof item === 'object' && '$nativeUIImage' in item) {
|
|
72
|
+
const image = images.get(item.$nativeUIImage as string);
|
|
73
|
+
if (!image || Object.keys(item).length !== 1) throw new Error('Invalid native image reference');
|
|
74
|
+
return image;
|
|
75
|
+
}
|
|
76
|
+
return item;
|
|
77
|
+
});
|
|
78
|
+
if (!result || result.length > 65536) throw new Error('Native UI props exceed 64K characters');
|
|
79
|
+
return result;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Follow slot assignment too: Ionic scroll containers live inside shadow roots. */
|
|
83
|
+
|
|
84
|
+
export const readExtension = (
|
|
85
|
+
element: HTMLElement,
|
|
86
|
+
extensions: readonly NativeUIExtension[],
|
|
87
|
+
id: Identify,
|
|
88
|
+
diagnose: (reason: string) => void = () => {},
|
|
89
|
+
nativeScroll = false,
|
|
90
|
+
): Candidate | undefined => {
|
|
91
|
+
const skip = (reason: string): undefined => {
|
|
92
|
+
diagnose(reason);
|
|
93
|
+
return undefined;
|
|
94
|
+
};
|
|
95
|
+
const matches = extensions.filter((extension) => element.matches(extension.selector));
|
|
96
|
+
if (matches.length !== 1) return skip('overlapping-selectors: exactly one extension must own this source.');
|
|
97
|
+
const extension = matches[0];
|
|
98
|
+
let scrollElement: HTMLElement | null | undefined;
|
|
99
|
+
if (extension.scrollElement) {
|
|
100
|
+
if (!nativeScroll || extension.placement === 'tab-accessory') return skip('unsupported-scroll: native scroll tracking is unavailable.');
|
|
101
|
+
try {
|
|
102
|
+
scrollElement = extension.scrollElement(element);
|
|
103
|
+
} catch {
|
|
104
|
+
return skip('scroll-reader-error: cannot resolve scroll owner.');
|
|
105
|
+
}
|
|
106
|
+
if (!scrollElement) return skip('missing-scroll-owner: the scroll element is not ready.');
|
|
107
|
+
}
|
|
108
|
+
const scroll = scrollElement ? readScrollPlacement(element, scrollElement, id) : undefined;
|
|
109
|
+
if (scrollElement && !scroll) return skip('unsupported-scroll: use one untransformed overflow container.');
|
|
110
|
+
if (!visible(element, !!scroll || extension.placement === 'tab-accessory'))
|
|
111
|
+
return skip('source-hidden: the source must be visible and inside the viewport.');
|
|
112
|
+
if (element.closest('ion-modal, ion-popover, ion-menu'))
|
|
113
|
+
return skip('unsupported-overlay: extension sources inside Web overlays stay on the Web.');
|
|
114
|
+
if (element.contains(element.ownerDocument.activeElement)) return skip('source-focused: keep the focused Web source until focus leaves.');
|
|
115
|
+
if (!scroll && extension.placement !== 'tab-accessory' && !fitsClip(element))
|
|
116
|
+
return skip('source-clipped: the source must fit its clipping ancestors without transformed ancestors.');
|
|
117
|
+
try {
|
|
118
|
+
const images: { value: NativeUIImage; source: string }[] = [];
|
|
119
|
+
const imageValues = new Map<string, NativeUIImage>();
|
|
120
|
+
let state: ReturnType<NativeUIExtension['read']>;
|
|
121
|
+
try {
|
|
122
|
+
state = extension.read(element, {
|
|
123
|
+
image(owner, options) {
|
|
124
|
+
if (!owner || !(element.contains(owner) || element.shadowRoot?.contains(owner))) return null;
|
|
125
|
+
const svg = owner.matches('svg') ? (owner as SVGElement) : owner.shadowRoot?.querySelector<SVGElement>('svg');
|
|
126
|
+
if (!svg) return null;
|
|
127
|
+
const source = iconSource(svg);
|
|
128
|
+
const { width, height } = owner.getBoundingClientRect();
|
|
129
|
+
if (!source || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
130
|
+
const value: NativeUIImage = { data: '', width, height, template: options?.template ?? false };
|
|
131
|
+
const key = String(images.length);
|
|
132
|
+
images.push({ value, source });
|
|
133
|
+
imageValues.set(key, value);
|
|
134
|
+
return { $nativeUIImage: key };
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
} catch {
|
|
138
|
+
return skip('reader-error: the extension reader threw; inspect its implementation.');
|
|
139
|
+
}
|
|
140
|
+
if (!state) return skip('reader-opt-out: the reader returned null.');
|
|
141
|
+
const props = json(state.props, imageValues);
|
|
142
|
+
const actions = new Map<string, HTMLElement>();
|
|
143
|
+
const targets: Record<string, string> = {};
|
|
144
|
+
for (const [name, target] of Object.entries(state.actions ?? {})) {
|
|
145
|
+
if (!name || (target && !element.contains(target))) return skip('invalid-actions: use named targets inside the source element.');
|
|
146
|
+
if (
|
|
147
|
+
!target ||
|
|
148
|
+
!visible(target, !!scroll || extension.placement === 'tab-accessory') ||
|
|
149
|
+
(target as HTMLElement & { disabled?: boolean }).disabled ||
|
|
150
|
+
target.matches(':disabled, [disabled]') ||
|
|
151
|
+
target.closest('[aria-disabled="true"]') ||
|
|
152
|
+
getComputedStyle(target).pointerEvents === 'none'
|
|
153
|
+
)
|
|
154
|
+
continue;
|
|
155
|
+
targets[name] = id(target);
|
|
156
|
+
actions.set(id(target), target);
|
|
157
|
+
}
|
|
158
|
+
if (Object.keys(targets).length > 30) return skip('invalid-actions: a source can expose at most 30 actions.');
|
|
159
|
+
const tabBar =
|
|
160
|
+
extension.placement === 'tab-accessory'
|
|
161
|
+
? Array.from(element.closest('ion-tabs')?.querySelectorAll<HTMLElement>('ion-tab-bar') ?? []).find(
|
|
162
|
+
(bar) => bar.closest('ion-tabs') === element.closest('ion-tabs'),
|
|
163
|
+
)
|
|
164
|
+
: undefined;
|
|
165
|
+
if (extension.placement === 'tab-accessory' && !tabBar)
|
|
166
|
+
return skip('missing-tab-owner: a tab accessory needs an owning ion-tabs with ion-tab-bar.');
|
|
167
|
+
const style = getComputedStyle(element);
|
|
168
|
+
const control = {
|
|
169
|
+
id: id(element),
|
|
170
|
+
kind: 'extension' as const,
|
|
171
|
+
scroll,
|
|
172
|
+
...frame(element.getBoundingClientRect()),
|
|
173
|
+
items: [],
|
|
174
|
+
dark: isDark(style),
|
|
175
|
+
rtl: style.direction === 'rtl',
|
|
176
|
+
extension: { name: extension.name, props, actions: targets, tabBarId: tabBar ? id(tabBar) : undefined },
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
element,
|
|
180
|
+
scrollElement: scrollElement ?? undefined,
|
|
181
|
+
actions,
|
|
182
|
+
icons: [],
|
|
183
|
+
control,
|
|
184
|
+
assets: images.length
|
|
185
|
+
? {
|
|
186
|
+
signature: JSON.stringify(images),
|
|
187
|
+
async render(renderer: ReturnType<typeof createIconRenderer>) {
|
|
188
|
+
for (const { value, source } of images) value.data = await renderer.render(source, value.width, value.height);
|
|
189
|
+
control.extension.props = json(state.props, imageValues);
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
: undefined,
|
|
193
|
+
};
|
|
194
|
+
} catch {
|
|
195
|
+
// Keep the source usable without exposing props or application exception data.
|
|
196
|
+
return skip('invalid-props: return a JSON object with finite values, valid image references, and at most 64K characters.');
|
|
197
|
+
}
|
|
198
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FoldState } from './types.js';
|
|
2
|
+
|
|
3
|
+
/** @deprecated Import applyFoldStateClasses from @rdlabo/ionic-theme-ios27 instead.
|
|
4
|
+
* Keep posture on ion-app so descendants can follow it through CSS. */
|
|
5
|
+
export function applyFoldStateClasses(root: HTMLElement, fold: FoldState): void {
|
|
6
|
+
for (const state of ['flat', 'half-opened', 'closed']) {
|
|
7
|
+
root.classList.toggle(`ios-theme-fold-${state}`, fold.state === state);
|
|
8
|
+
}
|
|
9
|
+
root.classList.toggle('ios-theme-fold-expanded', fold.state === 'half-opened' || (fold.state === 'flat' && !!fold.hingeBounds));
|
|
10
|
+
}
|