@rdlabo/capacitor-native-ui 0.0.0-stage → 1.2.1
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/LICENSE +21 -0
- package/Package.swift +23 -0
- package/README.md +53 -2
- package/RdlaboCapacitorNativeUi.podspec +17 -0
- package/android/build.gradle +69 -0
- package/android/proguard-rules.pro +0 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUI.kt +51 -0
- package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUIPlugin.kt +192 -0
- package/android/src/main/java/dev/rdlabo/capacitor/nativeui/NativeUISnapshot.kt +57 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/core.d.ts +4 -0
- package/dist/core.js +3 -0
- package/dist/native/activation.d.ts +14 -0
- package/dist/native/activation.d.ts.map +1 -0
- package/dist/native/activation.js +305 -0
- package/dist/native/activation.js.map +1 -0
- package/dist/native/components/index.d.ts +19 -0
- package/dist/native/components/index.d.ts.map +1 -0
- package/dist/native/components/index.js +38 -0
- package/dist/native/components/index.js.map +1 -0
- package/dist/native/components/ion-back-button.d.ts +10 -0
- package/dist/native/components/ion-back-button.d.ts.map +1 -0
- package/dist/native/components/ion-back-button.js +21 -0
- package/dist/native/components/ion-back-button.js.map +1 -0
- package/dist/native/components/ion-button.d.ts +10 -0
- package/dist/native/components/ion-button.d.ts.map +1 -0
- package/dist/native/components/ion-button.js +25 -0
- package/dist/native/components/ion-button.js.map +1 -0
- package/dist/native/components/ion-buttons.d.ts +11 -0
- package/dist/native/components/ion-buttons.d.ts.map +1 -0
- package/dist/native/components/ion-buttons.js +30 -0
- package/dist/native/components/ion-buttons.js.map +1 -0
- package/dist/native/components/ion-fab.d.ts +10 -0
- package/dist/native/components/ion-fab.d.ts.map +1 -0
- package/dist/native/components/ion-fab.js +132 -0
- package/dist/native/components/ion-fab.js.map +1 -0
- package/dist/native/components/ion-menu-button.d.ts +13 -0
- package/dist/native/components/ion-menu-button.d.ts.map +1 -0
- package/dist/native/components/ion-menu-button.js +44 -0
- package/dist/native/components/ion-menu-button.js.map +1 -0
- package/dist/native/components/ion-segment.d.ts +11 -0
- package/dist/native/components/ion-segment.d.ts.map +1 -0
- package/dist/native/components/ion-segment.js +30 -0
- package/dist/native/components/ion-segment.js.map +1 -0
- package/dist/native/components/ion-tab-bar.d.ts +11 -0
- package/dist/native/components/ion-tab-bar.d.ts.map +1 -0
- package/dist/native/components/ion-tab-bar.js +50 -0
- package/dist/native/components/ion-tab-bar.js.map +1 -0
- package/dist/native/components/searchable-tabs.d.ts +20 -0
- package/dist/native/components/searchable-tabs.d.ts.map +1 -0
- package/dist/native/components/searchable-tabs.js +413 -0
- package/dist/native/components/searchable-tabs.js.map +1 -0
- package/dist/native/definitions.d.ts +255 -0
- package/dist/native/definitions.d.ts.map +1 -0
- package/dist/native/definitions.js +0 -0
- package/dist/native/extensions.d.ts +31 -0
- package/dist/native/extensions.d.ts.map +1 -0
- package/dist/native/extensions.js +122 -0
- package/dist/native/extensions.js.map +1 -0
- package/dist/native/foldable/fold-state.d.ts +5 -0
- package/dist/native/foldable/fold-state.d.ts.map +1 -0
- package/dist/native/foldable/fold-state.js +15 -0
- package/dist/native/foldable/fold-state.js.map +1 -0
- package/dist/native/foldable/types.d.ts +12 -0
- package/dist/native/foldable/types.d.ts.map +1 -0
- package/dist/native/foldable/types.js +0 -0
- package/dist/native/html.d.ts +9 -0
- package/dist/native/html.d.ts.map +1 -0
- package/dist/native/html.js +188 -0
- package/dist/native/html.js.map +1 -0
- package/dist/native/index.d.ts +10 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +28 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/legacy-layout.d.ts +5 -0
- package/dist/native/legacy-layout.d.ts.map +1 -0
- package/dist/native/legacy-layout.js +27 -0
- package/dist/native/legacy-layout.js.map +1 -0
- package/dist/native/overlays/content.d.ts +4 -0
- package/dist/native/overlays/content.d.ts.map +1 -0
- package/dist/native/overlays/content.js +51 -0
- package/dist/native/overlays/content.js.map +1 -0
- package/dist/native/overlays/controller.d.ts +8 -0
- package/dist/native/overlays/controller.d.ts.map +1 -0
- package/dist/native/overlays/controller.js +285 -0
- package/dist/native/overlays/controller.js.map +1 -0
- package/dist/native/overlays/dialog-animation.d.ts +9 -0
- package/dist/native/overlays/dialog-animation.d.ts.map +1 -0
- package/dist/native/overlays/dialog-animation.js +53 -0
- package/dist/native/overlays/dialog-animation.js.map +1 -0
- package/dist/native/overlays/dismiss-animation.d.ts +4 -0
- package/dist/native/overlays/dismiss-animation.d.ts.map +1 -0
- package/dist/native/overlays/dismiss-animation.js +18 -0
- package/dist/native/overlays/dismiss-animation.js.map +1 -0
- package/dist/native/overlays/focus.d.ts +5 -0
- package/dist/native/overlays/focus.d.ts.map +1 -0
- package/dist/native/overlays/focus.js +59 -0
- package/dist/native/overlays/focus.js.map +1 -0
- package/dist/native/overlays/popover.d.ts +12 -0
- package/dist/native/overlays/popover.d.ts.map +1 -0
- package/dist/native/overlays/popover.js +110 -0
- package/dist/native/overlays/popover.js.map +1 -0
- package/dist/native/overlays/styles.d.ts +11 -0
- package/dist/native/overlays/styles.d.ts.map +1 -0
- package/dist/native/overlays/styles.js +167 -0
- package/dist/native/overlays/styles.js.map +1 -0
- package/dist/native/prehide.d.ts +8 -0
- package/dist/native/prehide.d.ts.map +1 -0
- package/dist/native/prehide.js +238 -0
- package/dist/native/prehide.js.map +1 -0
- package/dist/native/runtime.d.ts +7 -0
- package/dist/native/runtime.d.ts.map +1 -0
- package/dist/native/runtime.js +788 -0
- package/dist/native/runtime.js.map +1 -0
- package/dist/native/shared/bridge.d.ts +6 -0
- package/dist/native/shared/bridge.d.ts.map +1 -0
- package/dist/native/shared/bridge.js +18 -0
- package/dist/native/shared/bridge.js.map +1 -0
- package/dist/native/shared/candidate.d.ts +25 -0
- package/dist/native/shared/candidate.d.ts.map +1 -0
- package/dist/native/shared/candidate.js +106 -0
- package/dist/native/shared/candidate.js.map +1 -0
- package/dist/native/shared/clipping.d.ts +5 -0
- package/dist/native/shared/clipping.d.ts.map +1 -0
- package/dist/native/shared/clipping.js +26 -0
- package/dist/native/shared/clipping.js.map +1 -0
- package/dist/native/shared/crossfade.d.ts +12 -0
- package/dist/native/shared/crossfade.d.ts.map +1 -0
- package/dist/native/shared/crossfade.js +67 -0
- package/dist/native/shared/crossfade.js.map +1 -0
- package/dist/native/shared/diagnostics.d.ts +4 -0
- package/dist/native/shared/diagnostics.d.ts.map +1 -0
- package/dist/native/shared/diagnostics.js +18 -0
- package/dist/native/shared/diagnostics.js.map +1 -0
- package/dist/native/shared/dom.d.ts +46 -0
- package/dist/native/shared/dom.d.ts.map +1 -0
- package/dist/native/shared/dom.js +180 -0
- package/dist/native/shared/dom.js.map +1 -0
- package/dist/native/shared/events.d.ts +15 -0
- package/dist/native/shared/events.d.ts.map +1 -0
- package/dist/native/shared/events.js +17 -0
- package/dist/native/shared/events.js.map +1 -0
- package/dist/native/shared/icons.d.ts +8 -0
- package/dist/native/shared/icons.d.ts.map +1 -0
- package/dist/native/shared/icons.js +61 -0
- package/dist/native/shared/icons.js.map +1 -0
- package/dist/native/shared/modal.d.ts +10 -0
- package/dist/native/shared/modal.d.ts.map +1 -0
- package/dist/native/shared/modal.js +160 -0
- package/dist/native/shared/modal.js.map +1 -0
- package/dist/native/shared/projection-id.d.ts +4 -0
- package/dist/native/shared/projection-id.d.ts.map +1 -0
- package/dist/native/shared/projection-id.js +7 -0
- package/dist/native/shared/projection-id.js.map +1 -0
- package/dist/native/shared/toolbar-options.d.ts +9 -0
- package/dist/native/shared/toolbar-options.d.ts.map +1 -0
- package/dist/native/shared/toolbar-options.js +36 -0
- package/dist/native/shared/toolbar-options.js.map +1 -0
- package/dist/native/shared/vertical-bars-geometry.d.ts +10 -0
- package/dist/native/shared/vertical-bars-geometry.d.ts.map +1 -0
- package/dist/native/shared/vertical-bars-geometry.js +85 -0
- package/dist/native/shared/vertical-bars-geometry.js.map +1 -0
- package/dist/native/startup.d.ts +8 -0
- package/dist/native/startup.d.ts.map +1 -0
- package/dist/native/startup.js +86 -0
- package/dist/native/startup.js.map +1 -0
- package/dist/native/vertical-bars-web-search.d.ts +10 -0
- package/dist/native/vertical-bars-web-search.d.ts.map +1 -0
- package/dist/native/vertical-bars-web-search.js +90 -0
- package/dist/native/vertical-bars-web-search.js.map +1 -0
- package/dist/native/vertical-bars-web.d.ts +5 -0
- package/dist/native/vertical-bars-web.d.ts.map +1 -0
- package/dist/native/vertical-bars-web.js +421 -0
- package/dist/native/vertical-bars-web.js.map +1 -0
- package/dist/native-integration/index.d.ts +25 -0
- package/dist/native-integration/index.d.ts.map +1 -0
- package/dist/native-integration/index.js +33 -0
- package/dist/native-integration/index.js.map +1 -0
- package/dist/vertical-bars.d.ts +6 -0
- package/dist/vertical-bars.js +5 -0
- package/docs/configuration.md +187 -0
- package/docs/framework-independent.md +82 -0
- package/docs/migration.md +206 -0
- package/docs/native-mini-player.md +47 -0
- package/docs/native-ui-extensions.md +365 -0
- package/docs/native-ui.md +409 -0
- package/docs/setup.md +205 -0
- package/docs/vertical-bars.md +379 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIButton.swift +71 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIButtons.swift +24 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIComponents.swift +19 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIFab.swift +80 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUISearchController.swift +439 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUISegment.swift +91 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUITabBar.swift +218 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIVerticalBars.swift +590 -0
- package/ios/Sources/NativeUIPlugin/Components/NativeUIVerticalSearch.swift +118 -0
- package/ios/Sources/NativeUIPlugin/Extensions/NativeUI.swift +178 -0
- package/ios/Sources/NativeUIPlugin/Extensions/NativeUIRendering.swift +38 -0
- package/ios/Sources/NativeUIPlugin/NativeUIPlugin.swift +615 -0
- package/ios/Sources/NativeUIPlugin/Overlays/NativeUIOverlayController.swift +119 -0
- package/ios/Sources/NativeUIPlugin/Overlays/NativeUIOverlayHost.swift +75 -0
- package/ios/Sources/NativeUIPlugin/Shared/NativeUIControlRendering.swift +21 -0
- package/ios/Sources/NativeUIPlugin/Shared/NativeUICrossfade.swift +35 -0
- package/ios/Sources/NativeUIPlugin/Shared/NativeUIHost.swift +8 -0
- package/ios/Sources/NativeUIPlugin/Shared/NativeUISnapshot.swift +161 -0
- package/ios/Sources/NativeUIPlugin/Shared/NativeUIVerticalBarsGeometry.swift +52 -0
- package/ios/Tests/NativeUIPluginTests/NativeUIExtensionTests.swift +165 -0
- package/ios/Tests/NativeUIPluginTests/NativeUISnapshotTests.swift +805 -0
- package/package.json +89 -4
- package/src/core.ts +15 -0
- package/src/native/activation.ts +394 -0
- package/src/native/components/index.ts +45 -0
- package/src/native/components/ion-back-button.ts +22 -0
- package/src/native/components/ion-button.ts +25 -0
- package/src/native/components/ion-buttons.ts +37 -0
- package/src/native/components/ion-fab.ts +151 -0
- package/src/native/components/ion-menu-button.ts +37 -0
- package/src/native/components/ion-segment.ts +27 -0
- package/src/native/components/ion-tab-bar.ts +50 -0
- package/src/native/components/searchable-tabs.ts +499 -0
- package/src/native/definitions.ts +324 -0
- package/src/native/extensions.ts +176 -0
- package/src/native/foldable/fold-state.ts +10 -0
- package/src/native/foldable/types.ts +4 -0
- package/src/native/html.ts +193 -0
- package/src/native/index.ts +41 -0
- package/src/native/legacy-layout.ts +21 -0
- package/src/native/overlays/content.ts +51 -0
- package/src/native/overlays/controller.ts +349 -0
- package/src/native/overlays/dialog-animation.ts +53 -0
- package/src/native/overlays/dismiss-animation.ts +15 -0
- package/src/native/overlays/focus.ts +64 -0
- package/src/native/overlays/popover.ts +120 -0
- package/src/native/overlays/styles.ts +181 -0
- package/src/native/prehide.ts +285 -0
- package/src/native/runtime.ts +988 -0
- package/src/native/shared/bridge.ts +18 -0
- package/src/native/shared/candidate.ts +162 -0
- package/src/native/shared/clipping.ts +24 -0
- package/src/native/shared/crossfade.ts +67 -0
- package/src/native/shared/diagnostics.ts +13 -0
- package/src/native/shared/dom.ts +304 -0
- package/src/native/shared/events.ts +14 -0
- package/src/native/shared/icons.ts +57 -0
- package/src/native/shared/modal.ts +157 -0
- package/src/native/shared/projection-id.ts +2 -0
- package/src/native/shared/toolbar-options.ts +53 -0
- package/src/native/shared/vertical-bars-geometry.ts +80 -0
- package/src/native/startup.ts +84 -0
- package/src/native/vertical-bars-web-search.ts +86 -0
- package/src/native/vertical-bars-web.ts +498 -0
- package/src/native-integration/index.ts +49 -0
- package/src/vertical-bars.ts +11 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type { NativeUIExtension } from './extensions';
|
|
2
|
+
import type { PluginListenerHandle } from '@capacitor/core';
|
|
3
|
+
|
|
4
|
+
export type NativeUIComponent =
|
|
5
|
+
| 'ion-button'
|
|
6
|
+
| 'ion-buttons'
|
|
7
|
+
| 'ion-back-button'
|
|
8
|
+
| 'ion-menu-button'
|
|
9
|
+
| 'ion-tab-bar'
|
|
10
|
+
| 'ion-segment'
|
|
11
|
+
| 'ion-fab';
|
|
12
|
+
|
|
13
|
+
export interface NativeUIStatus {
|
|
14
|
+
state: 'web' | 'native' | 'stopped';
|
|
15
|
+
projected: number;
|
|
16
|
+
updates: number;
|
|
17
|
+
reason?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ToolbarProjectionOptions {
|
|
21
|
+
/** Group selected actions; never selects children or back buttons. Defaults to true. */
|
|
22
|
+
buttons?: boolean;
|
|
23
|
+
/** Automatically select ordinary actions, inside or outside groups. Defaults to true. */
|
|
24
|
+
button?: boolean;
|
|
25
|
+
/** Automatically select back navigation independently of groups. Defaults to true. */
|
|
26
|
+
backButton?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface VerticalControlAreaOptions {
|
|
30
|
+
/** App-local DOM readers with matching SwiftUI or Compose registrations.
|
|
31
|
+
* @experimental API and behavior may change in future releases.
|
|
32
|
+
*/
|
|
33
|
+
extensions?: readonly NativeUIExtension[];
|
|
34
|
+
/** Opt-in popovers anchored to native Vertical Bars buttons. */
|
|
35
|
+
controls?: Pick<NativeUIControls, 'popover'>;
|
|
36
|
+
/** Ordinary horizontal toolbar selection/grouping. true enables all three options; false disables them.
|
|
37
|
+
* Object fields default to true. Local data-horizontal-bars overrides still apply.
|
|
38
|
+
* Only enableNativeUI projects horizontal controls.
|
|
39
|
+
*/
|
|
40
|
+
horizontal?: boolean | ToolbarProjectionOptions;
|
|
41
|
+
/** Vertical toolbar selection/grouping. true enables all three options; false disables them.
|
|
42
|
+
* Object fields default to true. Local data-vertical-bars overrides still apply.
|
|
43
|
+
* Neither layout setting controls tabs, segments or FABs.
|
|
44
|
+
*/
|
|
45
|
+
vertical?: boolean | ToolbarProjectionOptions;
|
|
46
|
+
/** Appearance of native vertical ion-button and ion-menu-button actions.
|
|
47
|
+
* `system` (default) uses SwiftUI styling and template icons; `source` projects supported Ionic fill and colors.
|
|
48
|
+
* Local data-projection="source|system" or ios-theme-projection-source / ios-theme-projection-system take precedence:
|
|
49
|
+
* the button itself, then its nearest ion-buttons, then this option. On the same element, a valid attribute wins;
|
|
50
|
+
* otherwise system wins if both classes are present. Invalid attribute values are ignored.
|
|
51
|
+
* Local settings update live; removing them restores inheritance. Actions, disabled state and grouping are preserved.
|
|
52
|
+
* Does not affect back buttons, tabs, FABs, horizontal controls, source elements or Web clones.
|
|
53
|
+
*/
|
|
54
|
+
buttonProjection?: 'source' | 'system';
|
|
55
|
+
/** Default fill for native vertical ion-button actions resolved to `source`, including local overrides.
|
|
56
|
+
* Use `solid` for Ionic's default design, or `null` (also the omitted default) for the iOS theme's glass design.
|
|
57
|
+
* Applies when fill is omitted or `default`, outside ion-buttons. Inside ion-buttons the default is clear.
|
|
58
|
+
* Explicit clear, solid and outline take precedence; clear and outline cannot be configured as defaults.
|
|
59
|
+
* Source elements and Web clones are unchanged.
|
|
60
|
+
*/
|
|
61
|
+
buttonDefaultFill?: 'solid' | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface NativeUIOptions extends VerticalControlAreaOptions {
|
|
65
|
+
/** Log Web fallback reasons and their source elements once per reason during this activation. Defaults to false. */
|
|
66
|
+
debug?: boolean;
|
|
67
|
+
verticalBars?: NativeUIVerticalBarsOptions;
|
|
68
|
+
/** Ionic adapter settings. Explicit data-native-ui sources and extensions remain independent. */
|
|
69
|
+
ionic?: IonicProjectionOptions;
|
|
70
|
+
/** Source subtree. Defaults to document.body. */
|
|
71
|
+
root?: HTMLElement;
|
|
72
|
+
/** @deprecated Call destroy() on the running handle to stop the shell. */
|
|
73
|
+
enabled?: boolean;
|
|
74
|
+
/** @deprecated Use ionic.auto for automatic Ionic projection and verticalBars.popover for popovers. */
|
|
75
|
+
controls?: NativeUIControls;
|
|
76
|
+
/** @deprecated Use verticalBars.only. */
|
|
77
|
+
verticalBarsOnly?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @internal Resolved adapter state; not an application configuration API. */
|
|
81
|
+
export interface NativeUIRuntimeOptions extends NativeUIOptions {
|
|
82
|
+
source?: 'ionic' | 'html' | 'auto' | 'extensions';
|
|
83
|
+
/** @internal Preserves legacy controls.buttons behavior for explicit sources. */
|
|
84
|
+
explicitControls?: boolean;
|
|
85
|
+
nativeState?: { edge?: 'left' | 'right' };
|
|
86
|
+
/** @internal Keep unavailable definitions discoverable for diagnostics without calling their readers. */
|
|
87
|
+
unavailableExtensions?: ReadonlyMap<string, string>;
|
|
88
|
+
rail?: { host: HTMLElement; edge: 'left' | 'right' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface IonicProjectionOptions {
|
|
92
|
+
/** Automatic Ionic projection. Defaults to true; partial objects keep omitted kinds enabled. */
|
|
93
|
+
auto?: boolean | AutoIonicControls;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface AutoIonicControls {
|
|
97
|
+
buttons?: boolean;
|
|
98
|
+
tabs?: boolean;
|
|
99
|
+
segment?: boolean;
|
|
100
|
+
fab?: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface NativeUIControls {
|
|
104
|
+
/** Toolbar actions and explicitly marked HTML buttons. Defaults to true. */
|
|
105
|
+
buttons?: boolean;
|
|
106
|
+
/** Projects tab bars and their native search presentation. */
|
|
107
|
+
tabs?: boolean;
|
|
108
|
+
/** @deprecated Use buttons. Projects toolbar buttons, including back and menu buttons. */
|
|
109
|
+
toolbar?: boolean;
|
|
110
|
+
/** Projects segments. */
|
|
111
|
+
segment?: boolean;
|
|
112
|
+
/** Projects floating action buttons. */
|
|
113
|
+
fab?: boolean;
|
|
114
|
+
/** Relays popovers anchored to native Vertical Bars buttons. Defaults to false. */
|
|
115
|
+
popover?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A running shell. Placement changes preserve all other startup settings. */
|
|
119
|
+
export interface NativeUIHandle extends NativeUIRuntimeHandle {
|
|
120
|
+
/** Enable rail projection when available, preserving other settings. Rejects after destroy. */
|
|
121
|
+
enableVerticalControlArea(): Promise<void>;
|
|
122
|
+
/** Restore rail sources to the Web without stopping ordinary native controls. Rejects after destroy. */
|
|
123
|
+
disableVerticalControlArea(): Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface NativeUIVerticalBarsOptions {
|
|
127
|
+
/** Defaults to true. Availability is always determined separately. */
|
|
128
|
+
enabled?: boolean;
|
|
129
|
+
/** Project only rail controls; unavailable or disabled rails leave controls on the Web. */
|
|
130
|
+
only?: boolean;
|
|
131
|
+
buttonProjection?: 'system' | 'source';
|
|
132
|
+
/** Source-style standalone buttons with no explicit fill. Defaults to glass. */
|
|
133
|
+
buttonDefaultFill?: 'glass' | 'solid';
|
|
134
|
+
/** Ionic popover relay. Defaults to false. */
|
|
135
|
+
popover?: boolean;
|
|
136
|
+
/** Placement preference, not a declaration of device capability. */
|
|
137
|
+
placement?: { edge: Exclude<VerticalBarEdge, null>; webHost?: HTMLElement };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface NativeUIRuntimeHandle {
|
|
141
|
+
/** Schedule a source recheck. Calls coalesce; unchanged snapshots are not sent. No-op after destroy or without a runtime. */
|
|
142
|
+
markForCheck(): void;
|
|
143
|
+
/** Returns the current Web/native projection state. */
|
|
144
|
+
getStatus(): NativeUIStatus;
|
|
145
|
+
/** Restores projected controls to the Web until the returned lease is resumed. */
|
|
146
|
+
suspend(): Promise<NativeUISuspension>;
|
|
147
|
+
/** Stops synchronization, restores Web controls and releases native resources. */
|
|
148
|
+
destroy(): Promise<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Logical edge in the reading direction, matching UIVerticalBarEdge and capacitor-foldable. */
|
|
152
|
+
export type VerticalBarEdge = 'leading' | 'trailing' | null;
|
|
153
|
+
|
|
154
|
+
export interface VerticalBarPlacement {
|
|
155
|
+
edge: VerticalBarEdge;
|
|
156
|
+
/** Explicit rail width in CSS pixels; omitted to use the stylesheet's safe-area rules. */
|
|
157
|
+
inset?: number;
|
|
158
|
+
/** Ignored. Native availability is read from the plugin’s own device metrics.
|
|
159
|
+
* @deprecated This input no longer reports native availability.
|
|
160
|
+
*/
|
|
161
|
+
nativeEdge?: VerticalBarEdge;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface VerticalControlAreaHandle extends NativeUIHandle {
|
|
165
|
+
/** Legacy Web CSS adapter. Use the theme setVerticalBarsLayout helper instead.
|
|
166
|
+
* @deprecated Web layout belongs to the theme or application.
|
|
167
|
+
*/
|
|
168
|
+
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean): void;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface NativeUISuspension {
|
|
172
|
+
/** Releases this suspension. Native projection resumes after all active suspensions are released. */
|
|
173
|
+
resume(): Promise<void>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface Frame {
|
|
177
|
+
x: number;
|
|
178
|
+
y: number;
|
|
179
|
+
width: number;
|
|
180
|
+
height: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface NativeUIBadge {
|
|
184
|
+
value: string;
|
|
185
|
+
color: string;
|
|
186
|
+
textColor: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface NativeUIItem extends Frame {
|
|
190
|
+
id: string;
|
|
191
|
+
label: string;
|
|
192
|
+
accessibilityLabel: string;
|
|
193
|
+
disabled: boolean;
|
|
194
|
+
selected: boolean;
|
|
195
|
+
fontSize: number;
|
|
196
|
+
fontWeight: number;
|
|
197
|
+
color: string;
|
|
198
|
+
/** Resolved ion-button fill for vertical projection; clear omits the native glass background. */
|
|
199
|
+
buttonFill?: 'clear' | 'solid' | 'outline';
|
|
200
|
+
backgroundColor?: string;
|
|
201
|
+
borderColor?: string;
|
|
202
|
+
borderWidth?: number;
|
|
203
|
+
badge?: NativeUIBadge;
|
|
204
|
+
icon?: string;
|
|
205
|
+
iconWidth?: number;
|
|
206
|
+
iconHeight?: number;
|
|
207
|
+
iconPosition?: 'leading' | 'trailing' | 'top';
|
|
208
|
+
imagePadding?: number;
|
|
209
|
+
contentInsetLeading?: number;
|
|
210
|
+
contentInsetTrailing?: number;
|
|
211
|
+
iconTemplate?: boolean;
|
|
212
|
+
visible?: boolean;
|
|
213
|
+
closeIcon?: string;
|
|
214
|
+
closeIconWidth?: number;
|
|
215
|
+
closeIconHeight?: number;
|
|
216
|
+
iconTransition?: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Standard native renderers, independent of the source Web framework. */
|
|
220
|
+
export type NativeUIControlKind =
|
|
221
|
+
| 'button'
|
|
222
|
+
| 'button-group'
|
|
223
|
+
| 'back-button'
|
|
224
|
+
| 'menu-button'
|
|
225
|
+
| 'tab-bar'
|
|
226
|
+
| 'segment'
|
|
227
|
+
| 'floating-action-button';
|
|
228
|
+
|
|
229
|
+
export interface NativeUIControl extends Frame {
|
|
230
|
+
id: string;
|
|
231
|
+
kind: NativeUIControlKind | 'extension';
|
|
232
|
+
extension?: { name: string; props: string; actions: Record<string, string>; tabBarId?: string };
|
|
233
|
+
/** Lets the native host own adaptive placement instead of mirroring the DOM frame. */
|
|
234
|
+
placement?: 'vertical-bars';
|
|
235
|
+
/** Logical Ionic toolbar slot, preserved when projecting to the vertical rail. */
|
|
236
|
+
toolbarSlot?: 'start' | 'end';
|
|
237
|
+
items: NativeUIItem[];
|
|
238
|
+
dark: boolean;
|
|
239
|
+
rtl: boolean;
|
|
240
|
+
tabBarAnchor?: { x: 0 | 0.5 | 1; y: 0 | 1 };
|
|
241
|
+
search?: NativeUISearch;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface NativeUISearch {
|
|
245
|
+
id: string;
|
|
246
|
+
field: NativeUIItem;
|
|
247
|
+
trigger: NativeUIItem;
|
|
248
|
+
closeId: string;
|
|
249
|
+
active: boolean;
|
|
250
|
+
available: boolean;
|
|
251
|
+
focused: boolean;
|
|
252
|
+
value: string;
|
|
253
|
+
placeholder: string;
|
|
254
|
+
disabled: boolean;
|
|
255
|
+
editSequence: number;
|
|
256
|
+
valueVersion: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface NativeUISearchEvent extends NativeUIActivation {
|
|
260
|
+
valueVersion: number;
|
|
261
|
+
phase: 'input' | 'focus' | 'blur' | 'clear' | 'commit';
|
|
262
|
+
value: string;
|
|
263
|
+
composing: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export interface NativeUISnapshot {
|
|
267
|
+
revision: number;
|
|
268
|
+
transitionDuration?: number;
|
|
269
|
+
viewportWidth: number;
|
|
270
|
+
/** Physical side the native rail is drawn on; the runtime resolves the logical edge through the document direction. */
|
|
271
|
+
verticalBarEdge?: 'left' | 'right';
|
|
272
|
+
/** Visible foreground modal bounds; omitted for the full page. */
|
|
273
|
+
verticalBarFrame?: Frame;
|
|
274
|
+
controls: NativeUIControl[];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface NativeUIActivation {
|
|
278
|
+
revision: number;
|
|
279
|
+
id: string;
|
|
280
|
+
sequence: number;
|
|
281
|
+
/** Native extension action awaiting a Web dispatch acknowledgement. */
|
|
282
|
+
requestId?: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface WebViewMetrics {
|
|
286
|
+
/** The WebView's effective top-left corner radius in points, or `0` when unavailable. */
|
|
287
|
+
radius: number;
|
|
288
|
+
/** Device-reported logical rail edge; null when unavailable. */
|
|
289
|
+
verticalBarEdge?: VerticalBarEdge;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface NativeUIPlugin {
|
|
293
|
+
configure(options?: { verticalBarsOnly?: boolean }): Promise<{ supported: boolean; extensions?: string[] }>;
|
|
294
|
+
completeExtensionAction(options: { requestId: string; accepted: boolean }): Promise<void>;
|
|
295
|
+
getWebViewMetrics(): Promise<WebViewMetrics>;
|
|
296
|
+
getVerticalBarsGeometry(options: { viewportWidth: number; ids: string[] }): Promise<{ revision?: number; items: Record<string, Frame> }>;
|
|
297
|
+
prepareOverlay(options: { id: string }): Promise<void>;
|
|
298
|
+
presentOverlay(options: { id: string; presentation?: NativeUIPopoverPresentation }): Promise<void>;
|
|
299
|
+
dismissOverlay(options: { id: string; animated: boolean }): Promise<void>;
|
|
300
|
+
closeOverlay(options: { id: string }): Promise<void>;
|
|
301
|
+
stopOverlays(): Promise<void>;
|
|
302
|
+
update(snapshot: NativeUISnapshot): Promise<{ revision: number; rejectedSearches?: string[]; rejectedControls?: string[] }>;
|
|
303
|
+
clear(options: { revision: number }): Promise<void>;
|
|
304
|
+
addListener(name: 'activate', listener: (event: NativeUIActivation) => void): Promise<PluginListenerHandle>;
|
|
305
|
+
addListener(name: 'search', listener: (event: NativeUISearchEvent) => void): Promise<PluginListenerHandle>;
|
|
306
|
+
addListener(name: 'webViewMetricsChange', listener: (event: WebViewMetrics) => void): Promise<PluginListenerHandle>;
|
|
307
|
+
addListener(name: 'overlay', listener: (event: NativeUIOverlayEvent) => void): Promise<PluginListenerHandle>;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface NativeUIPopoverPresentation {
|
|
311
|
+
kind: 'popover';
|
|
312
|
+
animated: boolean;
|
|
313
|
+
dark: boolean;
|
|
314
|
+
anchorId: string;
|
|
315
|
+
width: number;
|
|
316
|
+
height: number;
|
|
317
|
+
backgroundColor: string;
|
|
318
|
+
backdropDismiss: boolean;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface NativeUIOverlayEvent {
|
|
322
|
+
id: string;
|
|
323
|
+
action: 'dismiss';
|
|
324
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { fitsClip } from './shared/clipping';
|
|
2
|
+
export { projectionAncestors as extensionAncestors } from './shared/clipping';
|
|
3
|
+
import type { Candidate, Identify } from './shared/candidate';
|
|
4
|
+
import { frame, isDark, visible } from './shared/dom';
|
|
5
|
+
import { iconSource, type createIconRenderer } from './shared/icons';
|
|
6
|
+
|
|
7
|
+
export type NativeUIValue = string | number | boolean | null | NativeUIValue[] | { [key: string]: NativeUIValue };
|
|
8
|
+
|
|
9
|
+
/** Opaque image reference. Return it in props; the shell replaces it with NativeUIImage data before transmission. */
|
|
10
|
+
export type NativeUIImageReference = { readonly $nativeUIImage: string };
|
|
11
|
+
type NativeUIImage = { data: string; width: number; height: number; template: boolean };
|
|
12
|
+
|
|
13
|
+
export interface NativeUIReadContext {
|
|
14
|
+
/** Capture a local SVG or resolved ion-icon using the standard renderer. Unsupported artwork returns null. */
|
|
15
|
+
image(element: SVGElement | HTMLElement | null, options?: { template?: boolean }): NativeUIImageReference | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** An app-local DOM projection. Swift or Kotlin registers its native content under the same name.
|
|
19
|
+
* @experimental API and behavior may change in future releases.
|
|
20
|
+
*/
|
|
21
|
+
export interface NativeUIExtension {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly selector: string;
|
|
24
|
+
/** Defaults to the source element's frame. Unsupported hosts keep the Web source visible. */
|
|
25
|
+
readonly placement?: 'element' | 'tab-accessory';
|
|
26
|
+
/** Return null to keep this instance on the Web. Read current source state without side effects. Notify property-only changes with shell.markForCheck(). */
|
|
27
|
+
readonly read: (
|
|
28
|
+
element: HTMLElement,
|
|
29
|
+
context: NativeUIReadContext,
|
|
30
|
+
) => {
|
|
31
|
+
props: { [key: string]: NativeUIValue };
|
|
32
|
+
/** Click targets must belong to the source element. Null targets are unavailable. */
|
|
33
|
+
actions?: Record<string, HTMLElement | null>;
|
|
34
|
+
} | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Defines a projection locally; it does not start native work or register a Capacitor plugin.
|
|
38
|
+
* @experimental API and behavior may change in future releases.
|
|
39
|
+
*/
|
|
40
|
+
export const defineNativeUI = <T extends NativeUIExtension>(extension: T): Readonly<T> => Object.freeze({ ...extension });
|
|
41
|
+
|
|
42
|
+
export const validateExtensions = (extensions: readonly NativeUIExtension[], doc: Document): void => {
|
|
43
|
+
const names = new Set<string>();
|
|
44
|
+
for (const extension of extensions) {
|
|
45
|
+
if (!extension.name.trim() || names.has(extension.name)) throw new Error('Native UI extension names must be nonempty and unique');
|
|
46
|
+
names.add(extension.name);
|
|
47
|
+
if (!extension.selector.trim()) throw new Error('Native UI extensions require a selector');
|
|
48
|
+
doc.querySelector(extension.selector);
|
|
49
|
+
if (extension.placement && !['element', 'tab-accessory'].includes(extension.placement)) throw new Error('Invalid native UI placement');
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const json = (value: unknown, images: Map<string, NativeUIImage>): string => {
|
|
54
|
+
if (!value || typeof value !== 'object' || Object.getPrototypeOf(value) !== Object.prototype)
|
|
55
|
+
throw new Error('Native UI props must be a JSON object');
|
|
56
|
+
const result = JSON.stringify(value, (_key, item: unknown) => {
|
|
57
|
+
if (
|
|
58
|
+
item === undefined ||
|
|
59
|
+
typeof item === 'function' ||
|
|
60
|
+
typeof item === 'symbol' ||
|
|
61
|
+
(typeof item === 'number' && !Number.isFinite(item)) ||
|
|
62
|
+
(item !== null && typeof item === 'object' && !Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype)
|
|
63
|
+
)
|
|
64
|
+
throw new Error('Native UI props must contain only JSON values');
|
|
65
|
+
if (item && typeof item === 'object' && '$nativeUIImage' in item) {
|
|
66
|
+
const image = images.get(item.$nativeUIImage as string);
|
|
67
|
+
if (!image || Object.keys(item).length !== 1) throw new Error('Invalid native image reference');
|
|
68
|
+
return image;
|
|
69
|
+
}
|
|
70
|
+
return item;
|
|
71
|
+
});
|
|
72
|
+
if (!result || result.length > 65536) throw new Error('Native UI props exceed 64K characters');
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Follow slot assignment too: Ionic scroll containers live inside shadow roots. */
|
|
77
|
+
|
|
78
|
+
export const readExtension = (
|
|
79
|
+
element: HTMLElement,
|
|
80
|
+
extensions: readonly NativeUIExtension[],
|
|
81
|
+
id: Identify,
|
|
82
|
+
diagnose: (reason: string) => void = () => {},
|
|
83
|
+
): Candidate | undefined => {
|
|
84
|
+
const skip = (reason: string): undefined => {
|
|
85
|
+
diagnose(reason);
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
88
|
+
const matches = extensions.filter((extension) => element.matches(extension.selector));
|
|
89
|
+
if (matches.length !== 1) return skip('overlapping-selectors: exactly one extension must own this source.');
|
|
90
|
+
if (!visible(element)) return skip('source-hidden: the source must be visible and inside the viewport.');
|
|
91
|
+
if (element.closest('ion-modal, ion-popover, ion-menu'))
|
|
92
|
+
return skip('unsupported-overlay: extension sources inside Web overlays stay on the Web.');
|
|
93
|
+
if (element.contains(element.ownerDocument.activeElement)) return skip('source-focused: keep the focused Web source until focus leaves.');
|
|
94
|
+
const extension = matches[0];
|
|
95
|
+
if (extension.placement !== 'tab-accessory' && !fitsClip(element))
|
|
96
|
+
return skip('source-clipped: the source must fit its clipping ancestors without transformed ancestors.');
|
|
97
|
+
try {
|
|
98
|
+
const images: { value: NativeUIImage; source: string }[] = [];
|
|
99
|
+
const imageValues = new Map<string, NativeUIImage>();
|
|
100
|
+
let state: ReturnType<NativeUIExtension['read']>;
|
|
101
|
+
try {
|
|
102
|
+
state = extension.read(element, {
|
|
103
|
+
image(owner, options) {
|
|
104
|
+
if (!owner || !(element.contains(owner) || element.shadowRoot?.contains(owner))) return null;
|
|
105
|
+
const svg = owner.matches('svg') ? (owner as SVGElement) : owner.shadowRoot?.querySelector<SVGElement>('svg');
|
|
106
|
+
if (!svg) return null;
|
|
107
|
+
const source = iconSource(svg);
|
|
108
|
+
const { width, height } = owner.getBoundingClientRect();
|
|
109
|
+
if (!source || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
|
|
110
|
+
const value: NativeUIImage = { data: '', width, height, template: options?.template ?? false };
|
|
111
|
+
const key = String(images.length);
|
|
112
|
+
images.push({ value, source });
|
|
113
|
+
imageValues.set(key, value);
|
|
114
|
+
return { $nativeUIImage: key };
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
} catch {
|
|
118
|
+
return skip('reader-error: the extension reader threw; inspect its implementation.');
|
|
119
|
+
}
|
|
120
|
+
if (!state) return skip('reader-opt-out: the reader returned null.');
|
|
121
|
+
const props = json(state.props, imageValues);
|
|
122
|
+
const actions = new Map<string, HTMLElement>();
|
|
123
|
+
const targets: Record<string, string> = {};
|
|
124
|
+
for (const [name, target] of Object.entries(state.actions ?? {})) {
|
|
125
|
+
if (!name || (target && !element.contains(target))) return skip('invalid-actions: use named targets inside the source element.');
|
|
126
|
+
if (
|
|
127
|
+
!target ||
|
|
128
|
+
!visible(target) ||
|
|
129
|
+
(target as HTMLElement & { disabled?: boolean }).disabled ||
|
|
130
|
+
target.matches(':disabled, [disabled]') ||
|
|
131
|
+
target.closest('[aria-disabled="true"]') ||
|
|
132
|
+
getComputedStyle(target).pointerEvents === 'none'
|
|
133
|
+
)
|
|
134
|
+
continue;
|
|
135
|
+
targets[name] = id(target);
|
|
136
|
+
actions.set(id(target), target);
|
|
137
|
+
}
|
|
138
|
+
if (Object.keys(targets).length > 30) return skip('invalid-actions: a source can expose at most 30 actions.');
|
|
139
|
+
const tabBar =
|
|
140
|
+
extension.placement === 'tab-accessory'
|
|
141
|
+
? Array.from(element.closest('ion-tabs')?.querySelectorAll<HTMLElement>('ion-tab-bar') ?? []).find(
|
|
142
|
+
(bar) => bar.closest('ion-tabs') === element.closest('ion-tabs'),
|
|
143
|
+
)
|
|
144
|
+
: undefined;
|
|
145
|
+
if (extension.placement === 'tab-accessory' && !tabBar)
|
|
146
|
+
return skip('missing-tab-owner: a tab accessory needs an owning ion-tabs with ion-tab-bar.');
|
|
147
|
+
const style = getComputedStyle(element);
|
|
148
|
+
const control = {
|
|
149
|
+
id: id(element),
|
|
150
|
+
kind: 'extension' as const,
|
|
151
|
+
...frame(element.getBoundingClientRect()),
|
|
152
|
+
items: [],
|
|
153
|
+
dark: isDark(style),
|
|
154
|
+
rtl: style.direction === 'rtl',
|
|
155
|
+
extension: { name: extension.name, props, actions: targets, tabBarId: tabBar ? id(tabBar) : undefined },
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
element,
|
|
159
|
+
actions,
|
|
160
|
+
icons: [],
|
|
161
|
+
control,
|
|
162
|
+
assets: images.length
|
|
163
|
+
? {
|
|
164
|
+
signature: JSON.stringify(images),
|
|
165
|
+
async render(renderer: ReturnType<typeof createIconRenderer>) {
|
|
166
|
+
for (const { value, source } of images) value.data = await renderer.render(source, value.width, value.height);
|
|
167
|
+
control.extension.props = json(state.props, imageValues);
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
: undefined,
|
|
171
|
+
};
|
|
172
|
+
} catch {
|
|
173
|
+
// Keep the source usable without exposing props or application exception data.
|
|
174
|
+
return skip('invalid-props: return a JSON object with finite values, valid image references, and at most 64K characters.');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FoldState } from './types';
|
|
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
|
+
}
|