@webview-native-ui/engine 1.0.0 → 1.0.2-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -35
- 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/bootstrap.js +1 -1
- 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 +11 -8
- package/dist/projection/web.js +48 -13
- package/dist/types.d.ts +5 -1
- package/docs/README.md +17 -10
- package/docs/concepts.md +39 -14
- package/docs/configuration/README.md +4 -2
- package/docs/configuration/ionic-overlays.md +36 -2
- package/docs/configuration/pause-resume.md +51 -0
- package/docs/controls.md +39 -0
- package/docs/getting-started/capacitor.md +3 -3
- package/docs/getting-started/cordova.md +3 -1
- package/docs/getting-started/initialization.md +18 -2
- package/docs/reference/platforms.md +2 -2
- package/docs/reference/projection.md +1 -1
- 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 +6 -4
- package/ios/Sources/WebViewNativeUIEngine/ProjectionOrigins.swift +31 -0
- 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
package/README.md
CHANGED
|
@@ -1,60 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Beyond the WebView.
|
|
2
2
|
|
|
3
|
-
**Bring
|
|
3
|
+
**Bring Native UI seamlessly into Web apps.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Build your screens with the flexibility of the Web and the controls and capabilities of native platforms. WebView Native UI Engine helps them work together as one experience, while keeping your Web layouts and navigation.
|
|
6
|
+
|
|
7
|
+
## Web UI. Native UI. One experience.
|
|
8
|
+
|
|
9
|
+
A native component should feel like part of the screen around it. For example:
|
|
10
|
+
|
|
11
|
+
- **Scroll together.** A native ad moves with the surrounding Web content as the reader scrolls through a feed.
|
|
12
|
+
- **Layer together.** A Web alert appears over native navigation controls. Close it, and those controls are ready for native interaction again.
|
|
13
|
+
|
|
14
|
+
Scrolling and overlays are examples of a broader goal: letting Web UI and Native UI coexist throughout the screen's lifecycle.
|
|
6
15
|
|
|
7
16
|

|
|
8
17
|
|
|
9
|
-
_Conceptual view.
|
|
18
|
+
_Conceptual view of overlay presentation. The Engine projects native appearance into the Web layer; it does not reorder the actual native view within the DOM._
|
|
19
|
+
|
|
20
|
+
https://github.com/user-attachments/assets/2768f014-7325-49c7-b848-25052756c36d
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
native controls beneath a Web alert, followed by a native ad scrolling with Web content. Recorded on iOS Simulator.
|
|
12
23
|
|
|
13
|
-
##
|
|
24
|
+
## Built for plugin developers
|
|
14
25
|
|
|
15
|
-
WebView
|
|
26
|
+
WebView Native UI Engine is a library for **Capacitor and Cordova plugin developers**. Integrate it into a plugin to bring native components into Web-driven screens, without making each application build its own coordination between rendering layers.
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
A native view above a WebView does not automatically participate in DOM scrolling, clipping or stacking order. A Web modal cannot simply cover it with a higher `z-index`. The Engine coordinates presentation across that boundary, so you can focus on the controls and native capabilities your plugin provides.
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
Use it for toolbars, tab bars, controls and capturable SDK views. Keep your existing plugin API and business logic; applications gain these capabilities through the plugins they use.
|
|
20
31
|
|
|
21
|
-
##
|
|
32
|
+
## How it works
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
- **Web overlays that coexist with native content.** Present modals and other overlays over native surfaces, then return to the interactive native UI when they close.
|
|
25
|
-
- **Scrolling and transitions that retain native appearance.** Carry the native surface's captured appearance through Web-layer movement, without recreating its styling in CSS.
|
|
26
|
-
- **Shared infrastructure across components and platforms.** Reuse the same projection and restoration lifecycle on iOS and Android instead of implementing it independently in every integration.
|
|
34
|
+
The plugin supplies a native view and a DOM container that defines its place in the Web layout. The Engine coordinates DOM measurement, supported native scroll tracking and the handoff between native and Web presentation.
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
When the Web layer needs to own presentation, the Engine displays a prepared image of the native component in that container. When native presentation can resume, it coordinates placement and restoration of the interactive view.
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
The image is a temporary visual projection, not a second interactive control. It does not forward clicks or play live video. Capture eligibility and platform rendering limits still apply, including differences in Liquid Glass highlights. When a suitable image is unavailable, the integration uses its configured Web fallback. See [concepts and limits](docs/concepts.md) and [platform support](docs/reference/platforms.md) before choosing a component.
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
## Start building
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
The Engine supports **iOS and Android**. Ionic and Angular are optional. Start with a native view and its DOM container, then connect the lifecycle notifications your UI needs. The integration guides cover runtime startup and optional framework support.
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
| Start here | Why |
|
|
45
|
+
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
46
|
+
| [Basic initialization](docs/getting-started/initialization.md) | Register a native view and its DOM container with the minimum required setup. |
|
|
47
|
+
| [Capacitor plugin guide](docs/getting-started/capacitor.md) | Connect the Engine to an existing Capacitor plugin. |
|
|
48
|
+
| [Cordova plugin guide](docs/getting-started/cordova.md) | Use the same lifecycle through Cordova's native hooks. |
|
|
49
|
+
| [Documentation](docs/README.md) | Find optional settings, renderer APIs and reference by the problem you need to solve. |
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
43
|
-
| [Documentation](docs/README.md) | Reading order: basic initialization, optional behavior, advanced APIs and reference. |
|
|
44
|
-
| [Concepts and limits](docs/concepts.md) | Decide whether projection fits your component. |
|
|
45
|
-
| [Basic initialization](docs/getting-started/initialization.md) | Minimum setup and required inputs, before framework-specific integration. |
|
|
51
|
+
Use matching Engine versions across npm and native dependencies, and retain dependency lockfiles for reproducible builds. Android placement requires API 23+, with composited snapshots available from API 26+.
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
application-side Engine initializer. The API is pre-1.0: pin matching Engine
|
|
49
|
-
versions across npm and native dependencies. Android placement requires API 23+,
|
|
50
|
-
with composited snapshots available from API 26+.
|
|
53
|
+
## Optional Ionic integration
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
For Ionic applications using an Engine-enabled plugin, the `@webview-native-ui/engine/ionic` entry point connects Ionic overlay lifecycle events to native projection:
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
```ts
|
|
58
|
+
import { enableSupportIonicOverlays } from '@webview-native-ui/engine/ionic';
|
|
59
|
+
|
|
60
|
+
enableSupportIonicOverlays();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Call it once during application startup unless your plugin already enables it. Applications importing it directly need `@webview-native-ui/engine` as a direct dependency. This shared framework integration coordinates coverage across plugins; each plugin retains control of its own presentation API. See [Ionic overlay support](docs/configuration/ionic-overlays.md) for setup, supported overlays and renderer helpers.
|
|
64
|
+
|
|
65
|
+
## Contributing
|
|
66
|
+
|
|
67
|
+
For repository development, verification and publishing, see [development and releases](docs/maintenance.md).
|
|
57
68
|
|
|
58
69
|
## License
|
|
59
70
|
|
|
60
71
|
[MIT](LICENSE). Commercial use, modification and redistribution are permitted with the license's notice and attribution requirements. The Engine's license does not change the licenses of applications or plugins that depend on it.
|
|
72
|
+
|
|
73
|
+
For the optional shared control runtime and adapter boundaries, see [shared controls](docs/controls.md).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
3
|
+
Pod::Spec.new do |s|
|
|
4
|
+
s.name = 'WebViewNativeUIControls'
|
|
5
|
+
s.version = package['version']
|
|
6
|
+
s.summary = 'Native UI infrastructure for Capacitor and Cordova plugin maintainers.'
|
|
7
|
+
s.homepage = 'https://github.com/webview-native-ui/engine'
|
|
8
|
+
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
|
9
|
+
s.preserve_paths = 'LICENSE'
|
|
10
|
+
s.author = { 'rdlabo' => 'sakakibara@rdlabo.jp' }
|
|
11
|
+
s.source = { :git => 'https://github.com/webview-native-ui/engine.git', :tag => "v#{s.version}" }
|
|
12
|
+
s.source_files = 'ios/Sources/WebViewNativeUIControls/**/*.swift'
|
|
13
|
+
s.ios.deployment_target = '15.0'
|
|
14
|
+
s.swift_version = '5.9'
|
|
15
|
+
s.frameworks = 'UIKit', 'WebKit', 'SwiftUI'
|
|
16
|
+
s.dependency 'WebViewNativeUIEngine', s.version.to_s
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NativeUITransport } from './transport.js';
|
|
2
|
+
import type { NativeUIRuntimeHandle, NativeUIRuntimeOptions, NativeUIPlugin, WebViewMetrics } from './definitions.js';
|
|
3
|
+
import { type NativeUIConnection } from './shared/connection.js';
|
|
4
|
+
export interface OverlayController {
|
|
5
|
+
suspend(): Promise<() => void>;
|
|
6
|
+
destroy(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
type OverlayFactory = (doc: Document, plugin: NativeUIPlugin, options: NativeUIRuntimeOptions, retain: (deferActivation?: (id: string) => boolean) => () => void, nativeRail: () => boolean, connection?: NativeUIConnection) => Promise<OverlayController>;
|
|
9
|
+
export { defineNativeUI } from './extensions.js';
|
|
10
|
+
export type { NativeUIExtension, NativeUIValue } from './extensions.js';
|
|
11
|
+
export type { NativeUIComponent, NativeUIControls, NativeUIRuntimeHandle, NativeUIRuntimeOptions, NativeUIStatus, NativeUISuspension, VerticalBarEdge, WebViewMetrics, } from './definitions.js';
|
|
12
|
+
export declare const createNativeUIActivation: (transport: NativeUITransport) => {
|
|
13
|
+
enableNativeUI: (options?: NativeUIRuntimeOptions, overlayFactory?: OverlayFactory) => Promise<NativeUIRuntimeHandle>;
|
|
14
|
+
configureNativeTransition: () => Promise<WebViewMetrics>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { controlDefault } from './shared/control-options.js';
|
|
2
|
+
import { toolbarOptions } from './shared/toolbar-options.js';
|
|
3
|
+
import { createDiagnostics } from './shared/diagnostics.js';
|
|
4
|
+
import { createHTMLWebProjection } from './html.js';
|
|
5
|
+
import { validateExtensions } from './extensions.js';
|
|
6
|
+
import { nativeWebViewMetrics } from '../integration/index.js';
|
|
7
|
+
import { createRuntime } from './runtime.js';
|
|
8
|
+
import { bounded, boundedListener } from './shared/bridge.js';
|
|
9
|
+
import { createNativeConnection } from './shared/connection.js';
|
|
10
|
+
import { createVerticalBarsWebProjection } from './vertical-bars-web.js';
|
|
11
|
+
import { prehideVerticalBarsToolbarSources } from './prehide.js';
|
|
12
|
+
import { observeVerticalBarsModals, topModal, modalPreparing, modalPresentation } from './shared/modal.js';
|
|
13
|
+
export { defineNativeUI } from './extensions.js';
|
|
14
|
+
export const createNativeUIActivation = (transport) => {
|
|
15
|
+
const plugin = transport.plugin;
|
|
16
|
+
let active;
|
|
17
|
+
let activeFailed = false;
|
|
18
|
+
let activeRoot;
|
|
19
|
+
let activeRailHost;
|
|
20
|
+
let activeConfiguration;
|
|
21
|
+
let activeExtensions = [];
|
|
22
|
+
let stopping;
|
|
23
|
+
// Every stop entry point releases the activation immediately but reserves the
|
|
24
|
+
// shared native/DOM resources until its teardown has settled.
|
|
25
|
+
const stopActivation = (owner, destruction) => {
|
|
26
|
+
if (active !== owner)
|
|
27
|
+
return;
|
|
28
|
+
active = undefined;
|
|
29
|
+
activeFailed = false;
|
|
30
|
+
activeConfiguration = undefined;
|
|
31
|
+
activeRoot = undefined;
|
|
32
|
+
activeRailHost = undefined;
|
|
33
|
+
activeExtensions = [];
|
|
34
|
+
stopping = destruction;
|
|
35
|
+
const release = () => {
|
|
36
|
+
if (stopping === destruction)
|
|
37
|
+
stopping = undefined;
|
|
38
|
+
};
|
|
39
|
+
void destruction.then(release, release);
|
|
40
|
+
};
|
|
41
|
+
const web = (reason) => ({
|
|
42
|
+
markForCheck: () => { },
|
|
43
|
+
getStatus: () => ({ state: 'web', projected: 0, updates: 0, reason }),
|
|
44
|
+
suspend: async () => ({ resume: async () => { } }),
|
|
45
|
+
destroy: async () => { },
|
|
46
|
+
});
|
|
47
|
+
const combine = (native, fallback) => ({
|
|
48
|
+
markForCheck() {
|
|
49
|
+
native.markForCheck();
|
|
50
|
+
fallback.markForCheck();
|
|
51
|
+
},
|
|
52
|
+
getStatus() {
|
|
53
|
+
const nativeStatus = native.getStatus();
|
|
54
|
+
const fallbackStatus = fallback.getStatus();
|
|
55
|
+
return {
|
|
56
|
+
...nativeStatus,
|
|
57
|
+
state: nativeStatus.state === 'stopped' && fallbackStatus.state === 'stopped'
|
|
58
|
+
? 'stopped'
|
|
59
|
+
: nativeStatus.projected > 0
|
|
60
|
+
? 'native'
|
|
61
|
+
: 'web',
|
|
62
|
+
projected: nativeStatus.projected + fallbackStatus.projected,
|
|
63
|
+
updates: nativeStatus.updates + fallbackStatus.updates,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
async suspend(options) {
|
|
67
|
+
const [nativeLease, fallbackLease] = await Promise.all([native.suspend(options), fallback.suspend(options)]);
|
|
68
|
+
return { resume: async () => void (await Promise.all([nativeLease.resume(), fallbackLease.resume()])) };
|
|
69
|
+
},
|
|
70
|
+
async destroy() {
|
|
71
|
+
await Promise.all([native.destroy(), fallback.destroy()]);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
/** Wraps a runtime handle with the shared enable-lifecycle: reason, suspend hooks and idempotent destroy. */
|
|
75
|
+
const manage = (handle, lifecycle = {}) => {
|
|
76
|
+
let destruction;
|
|
77
|
+
return {
|
|
78
|
+
markForCheck: () => {
|
|
79
|
+
if (!destruction)
|
|
80
|
+
handle.markForCheck();
|
|
81
|
+
},
|
|
82
|
+
getStatus: () => (lifecycle.reason ? { ...handle.getStatus(), reason: lifecycle.reason } : handle.getStatus()),
|
|
83
|
+
async suspend(options) {
|
|
84
|
+
const lease = await handle.suspend(options);
|
|
85
|
+
const resume = await lifecycle.suspend?.();
|
|
86
|
+
return {
|
|
87
|
+
async resume() {
|
|
88
|
+
await lease.resume();
|
|
89
|
+
resume?.();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
destroy() {
|
|
94
|
+
if (!destruction) {
|
|
95
|
+
destruction = Promise.resolve().then(async () => {
|
|
96
|
+
try {
|
|
97
|
+
await handle.destroy();
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await lifecycle.destroy?.();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
lifecycle.stopping?.(destruction);
|
|
104
|
+
}
|
|
105
|
+
return destruction;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
/** Reads the current native WebView geometry and applies it to page transitions. */
|
|
110
|
+
const configureNativeTransition = async () => {
|
|
111
|
+
const metrics = typeof document !== 'undefined' && transport.getPlatform() === 'ios' && transport.isAvailable()
|
|
112
|
+
? await bounded(plugin.getWebViewMetrics())
|
|
113
|
+
: { radius: 0 };
|
|
114
|
+
nativeWebViewMetrics.radius = metrics.radius;
|
|
115
|
+
return metrics;
|
|
116
|
+
};
|
|
117
|
+
/** Resolves a logical vertical-bar edge to the physical side for the given direction. */
|
|
118
|
+
const physicalVerticalBarEdge = (edge, rtl) => (edge === 'leading') !== rtl ? 'left' : 'right';
|
|
119
|
+
const elementRtl = (element) => element.closest('[dir]')?.getAttribute('dir') === 'rtl';
|
|
120
|
+
const webProjection = (doc, options, enabled = () => true) => {
|
|
121
|
+
const ionic = () => createVerticalBarsWebProjection(doc, options, () => options.verticalBars?.enabled !== false && enabled());
|
|
122
|
+
if (options.source === 'html')
|
|
123
|
+
return createHTMLWebProjection(doc, options);
|
|
124
|
+
if (options.source === 'auto')
|
|
125
|
+
return combine(ionic(), createHTMLWebProjection(doc, options));
|
|
126
|
+
return ionic();
|
|
127
|
+
};
|
|
128
|
+
/** Call once at application startup. Ionic markup remains the source of truth. */
|
|
129
|
+
const enableNativeUI = (options = {}, overlayFactory) => {
|
|
130
|
+
if (typeof document === 'undefined')
|
|
131
|
+
return Promise.resolve(web('Requires a document'));
|
|
132
|
+
const extensions = [...(options.extensions ?? [])];
|
|
133
|
+
const nativeState = (options.nativeState = {});
|
|
134
|
+
try {
|
|
135
|
+
validateExtensions(extensions, document);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
return Promise.reject(error);
|
|
139
|
+
}
|
|
140
|
+
const configuration = JSON.stringify([
|
|
141
|
+
options.source ?? 'ionic',
|
|
142
|
+
options.debug === true,
|
|
143
|
+
options.ionic?.auto !== false,
|
|
144
|
+
options.verticalBars?.enabled !== false,
|
|
145
|
+
options.verticalBars?.placement?.edge,
|
|
146
|
+
options.rail?.edge,
|
|
147
|
+
toolbarOptions(options, false),
|
|
148
|
+
toolbarOptions(options, true),
|
|
149
|
+
options.ionic?.verticalBars?.buttonProjection ?? 'system',
|
|
150
|
+
// Local source overrides can use this even when the startup projection is system.
|
|
151
|
+
options.ionic?.verticalBars?.buttonDefaultFill ?? 'glass',
|
|
152
|
+
...['tabs', 'segment', 'fab'].flatMap((kind) => [
|
|
153
|
+
controlDefault(options, kind, false),
|
|
154
|
+
controlDefault(options, kind, true),
|
|
155
|
+
]),
|
|
156
|
+
options.ionic?.verticalBars?.popover === true,
|
|
157
|
+
]);
|
|
158
|
+
if (active && activeFailed) {
|
|
159
|
+
const previous = active;
|
|
160
|
+
stopActivation(previous, previous.then((handle) => handle.destroy()));
|
|
161
|
+
}
|
|
162
|
+
if (active &&
|
|
163
|
+
(activeConfiguration !== configuration ||
|
|
164
|
+
activeRoot !== options.root ||
|
|
165
|
+
activeRailHost !== options.rail?.host ||
|
|
166
|
+
extensions.length !== activeExtensions.length ||
|
|
167
|
+
extensions.some((extension, index) => extension !== activeExtensions[index])))
|
|
168
|
+
return Promise.reject(new Error('Native UI is already running with different controls or extensions; destroy it before changing configuration.'));
|
|
169
|
+
if (active)
|
|
170
|
+
return active;
|
|
171
|
+
activeConfiguration = configuration;
|
|
172
|
+
activeRoot = options.root;
|
|
173
|
+
activeRailHost = options.rail?.host;
|
|
174
|
+
activeExtensions = extensions;
|
|
175
|
+
const previousStop = stopping;
|
|
176
|
+
let start;
|
|
177
|
+
const stop = (destruction) => stopActivation(start, destruction);
|
|
178
|
+
let releaseNative = async () => { };
|
|
179
|
+
let retired = false;
|
|
180
|
+
const pagehide = () => runtimeStopped('pagehide');
|
|
181
|
+
const runtimeStopped = (cause) => {
|
|
182
|
+
if (cause === 'failure') {
|
|
183
|
+
// Keep the Web rail usable, but replace this degraded activation on the next enable.
|
|
184
|
+
if (active === start)
|
|
185
|
+
activeFailed = true;
|
|
186
|
+
void start
|
|
187
|
+
.then(async () => {
|
|
188
|
+
if (!retired)
|
|
189
|
+
document.defaultView?.addEventListener('pagehide', pagehide, { once: true });
|
|
190
|
+
await releaseNative();
|
|
191
|
+
})
|
|
192
|
+
.catch(() => { });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const destruction = start.then((handle) => handle.destroy());
|
|
196
|
+
stop(destruction);
|
|
197
|
+
void destruction.catch(() => { });
|
|
198
|
+
};
|
|
199
|
+
return (start = active =
|
|
200
|
+
(async () => {
|
|
201
|
+
// Native controls and DOM ownership are shared. A replacement activation
|
|
202
|
+
// must wait until the old handle has finished clearing both surfaces.
|
|
203
|
+
if (previousStop)
|
|
204
|
+
await previousStop.catch(() => { });
|
|
205
|
+
const platform = transport.getPlatform();
|
|
206
|
+
const diagnose = createDiagnostics(options.debug);
|
|
207
|
+
let nativeModalAnimations = () => false;
|
|
208
|
+
const stopModals = options.source === 'html' ? () => { } : observeVerticalBarsModals(document, () => nativeModalAnimations());
|
|
209
|
+
const prehide = platform !== 'android' && options.source !== 'html' && options.ionic?.auto !== false
|
|
210
|
+
? prehideVerticalBarsToolbarSources(document, () => options.verticalBars?.enabled !== false, options.root, options)
|
|
211
|
+
: undefined;
|
|
212
|
+
const fallback = (reason) => {
|
|
213
|
+
diagnose(`web-fallback: ${reason}`);
|
|
214
|
+
return manage(webProjection(document, options), {
|
|
215
|
+
reason,
|
|
216
|
+
suspend: () => prehide?.suspend(),
|
|
217
|
+
destroy: () => {
|
|
218
|
+
retired = true;
|
|
219
|
+
document.defaultView?.removeEventListener('pagehide', pagehide);
|
|
220
|
+
prehide?.stop();
|
|
221
|
+
stopModals();
|
|
222
|
+
},
|
|
223
|
+
stopping: stop,
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
if (platform !== 'ios' && platform !== 'android')
|
|
227
|
+
return fallback('Requires an iOS or Android native host');
|
|
228
|
+
if (!transport.isAvailable())
|
|
229
|
+
return fallback('Native UI plugin is not registered');
|
|
230
|
+
let runtime;
|
|
231
|
+
let overlays;
|
|
232
|
+
let connection;
|
|
233
|
+
let metricsListener;
|
|
234
|
+
let nativeRelease;
|
|
235
|
+
releaseNative = () => (nativeRelease ??= (async () => {
|
|
236
|
+
try {
|
|
237
|
+
connection?.stop();
|
|
238
|
+
await overlays?.destroy();
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
if (metricsListener)
|
|
242
|
+
await bounded(metricsListener.remove()).catch(() => { });
|
|
243
|
+
}
|
|
244
|
+
})());
|
|
245
|
+
const cleanup = async () => {
|
|
246
|
+
retired = true;
|
|
247
|
+
try {
|
|
248
|
+
await releaseNative();
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
document.defaultView?.removeEventListener('pagehide', pagehide);
|
|
252
|
+
prehide?.stop();
|
|
253
|
+
stopModals();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
try {
|
|
257
|
+
const ordinaryControls = ['button', 'backButton', 'tabs', 'segment', 'fab'].some((kind) => controlDefault(options, kind, false));
|
|
258
|
+
const capabilities = await bounded(plugin.configure({ suppressScrollEdgeEffect: ordinaryControls }));
|
|
259
|
+
if (!capabilities.supported)
|
|
260
|
+
return fallback('Native UI is unavailable on this device');
|
|
261
|
+
if (capabilities.connectionState)
|
|
262
|
+
connection = createNativeConnection(document, plugin);
|
|
263
|
+
const listen = connection?.listen ?? plugin.addListener.bind(plugin);
|
|
264
|
+
const unavailableExtensions = new Map();
|
|
265
|
+
for (const extension of extensions) {
|
|
266
|
+
if (platform === 'android' && extension.placement === 'tab-accessory')
|
|
267
|
+
unavailableExtensions.set(extension.name, 'unsupported-placement: tab-accessory requires a supported iOS tab host.');
|
|
268
|
+
else if (!capabilities.extensions?.includes(extension.name))
|
|
269
|
+
unavailableExtensions.set(extension.name, 'unregistered-extension: register this name in the native app before enabling the shell, then rebuild.');
|
|
270
|
+
}
|
|
271
|
+
if (platform === 'android') {
|
|
272
|
+
runtime = await createRuntime(document, plugin, {
|
|
273
|
+
...options,
|
|
274
|
+
source: 'extensions',
|
|
275
|
+
coveredSnapshots: capabilities.coveredSnapshots === true,
|
|
276
|
+
extensions,
|
|
277
|
+
unavailableExtensions,
|
|
278
|
+
}, () => false, runtimeStopped, connection);
|
|
279
|
+
runtime = combine(runtime, webProjection(document, options));
|
|
280
|
+
metricsListener = await boundedListener(listen('webViewMetricsChange', () => {
|
|
281
|
+
document.defaultView?.dispatchEvent(new Event('nativeUIRefresh'));
|
|
282
|
+
}));
|
|
283
|
+
connection?.start();
|
|
284
|
+
return manage(runtime, {
|
|
285
|
+
stopping: stop,
|
|
286
|
+
destroy: cleanup,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
let deviceEdge = null;
|
|
290
|
+
const ionicRoot = () => {
|
|
291
|
+
if (options.source === 'html')
|
|
292
|
+
return null;
|
|
293
|
+
const root = options.root ?? document.body;
|
|
294
|
+
return root.closest('ion-app') ?? root.querySelector('ion-app');
|
|
295
|
+
};
|
|
296
|
+
const updateNativeEdge = () => {
|
|
297
|
+
const requested = options.verticalBars?.placement?.edge;
|
|
298
|
+
const root = ionicRoot() ?? options.root ?? document.body;
|
|
299
|
+
nativeState.edge =
|
|
300
|
+
deviceEdge && (!requested || requested === deviceEdge) ? physicalVerticalBarEdge(deviceEdge, elementRtl(root)) : undefined;
|
|
301
|
+
};
|
|
302
|
+
// Ionic layout is read-only input; device metrics determine native availability.
|
|
303
|
+
const nativeVerticalBars = () => {
|
|
304
|
+
updateNativeEdge();
|
|
305
|
+
if (options.verticalBars?.enabled === false)
|
|
306
|
+
return false;
|
|
307
|
+
const app = ionicRoot();
|
|
308
|
+
if (!app?.classList.contains('ios-theme-vertical-bars'))
|
|
309
|
+
return !!nativeState.edge;
|
|
310
|
+
const physicalEdge = app.classList.contains('ios-theme-vertical-bars-left') ? 'left' : 'right';
|
|
311
|
+
return physicalEdge === nativeState.edge;
|
|
312
|
+
};
|
|
313
|
+
nativeModalAnimations = () => capabilities.modalAnimation === true && nativeVerticalBars();
|
|
314
|
+
const applyMetrics = (metrics) => {
|
|
315
|
+
if (ordinaryControls)
|
|
316
|
+
nativeWebViewMetrics.radius = metrics.radius;
|
|
317
|
+
deviceEdge = metrics.verticalBarEdge ?? null;
|
|
318
|
+
updateNativeEdge();
|
|
319
|
+
document.defaultView?.dispatchEvent(new Event('nativeUIRefresh'));
|
|
320
|
+
};
|
|
321
|
+
metricsListener = await boundedListener(listen('webViewMetricsChange', applyMetrics));
|
|
322
|
+
await bounded(Promise.resolve().then(() => plugin.getWebViewMetrics()))
|
|
323
|
+
.then(applyMetrics)
|
|
324
|
+
.catch(() => undefined);
|
|
325
|
+
connection?.observe(async (_, restored) => {
|
|
326
|
+
if (restored)
|
|
327
|
+
await bounded(plugin.getWebViewMetrics())
|
|
328
|
+
.then(applyMetrics)
|
|
329
|
+
.catch(() => undefined);
|
|
330
|
+
});
|
|
331
|
+
const native = await createRuntime(document, plugin, {
|
|
332
|
+
...options,
|
|
333
|
+
extensions,
|
|
334
|
+
unavailableExtensions,
|
|
335
|
+
coveredSnapshots: capabilities.coveredSnapshots === true,
|
|
336
|
+
nativeScroll: capabilities.nativeScroll === true,
|
|
337
|
+
}, nativeVerticalBars, runtimeStopped, connection);
|
|
338
|
+
runtime = combine(native, webProjection(document, options, () => {
|
|
339
|
+
const modal = topModal(document);
|
|
340
|
+
return (!(modal && (modalPreparing(modal) || modalPresentation(modal))) &&
|
|
341
|
+
(!nativeVerticalBars() || native.getStatus().projected === 0));
|
|
342
|
+
}));
|
|
343
|
+
if (options.ionic?.verticalBars?.popover === true && overlayFactory)
|
|
344
|
+
overlays = await overlayFactory(document, plugin, options, native.retain, nativeVerticalBars, connection);
|
|
345
|
+
connection?.start();
|
|
346
|
+
return manage(runtime, {
|
|
347
|
+
suspend: async () => {
|
|
348
|
+
const restorePrehide = prehide?.suspend();
|
|
349
|
+
const resumeOverlays = await overlays?.suspend();
|
|
350
|
+
return () => {
|
|
351
|
+
restorePrehide?.();
|
|
352
|
+
resumeOverlays?.();
|
|
353
|
+
};
|
|
354
|
+
},
|
|
355
|
+
stopping: stop,
|
|
356
|
+
destroy: cleanup,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
connection?.stop();
|
|
361
|
+
await overlays?.destroy().catch(() => { });
|
|
362
|
+
await runtime?.destroy().catch(() => { });
|
|
363
|
+
if (metricsListener)
|
|
364
|
+
await bounded(metricsListener.remove()).catch(() => { });
|
|
365
|
+
return fallback(error instanceof Error ? error.message : String(error));
|
|
366
|
+
}
|
|
367
|
+
})());
|
|
368
|
+
};
|
|
369
|
+
return { enableNativeUI, configureNativeTransition };
|
|
370
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions.js';
|
|
2
|
+
import * as button from './ion-button.js';
|
|
3
|
+
import * as buttons from './ion-buttons.js';
|
|
4
|
+
import * as backButton from './ion-back-button.js';
|
|
5
|
+
import * as menuButton from './ion-menu-button.js';
|
|
6
|
+
import * as tabBar from './ion-tab-bar.js';
|
|
7
|
+
import * as segment from './ion-segment.js';
|
|
8
|
+
import * as fab from './ion-fab.js';
|
|
9
|
+
import type { Candidate, Identify } from '../shared/candidate.js';
|
|
10
|
+
export declare const components: readonly [typeof button, typeof buttons, typeof backButton, typeof menuButton, typeof tabBar, typeof segment, typeof fab];
|
|
11
|
+
export type NativeUIComponent = (typeof components)[number]['tag'];
|
|
12
|
+
export declare const selector: string;
|
|
13
|
+
export declare const shadowSelector: string;
|
|
14
|
+
export declare const motionSelector: string;
|
|
15
|
+
export declare const isVerticalBarsCandidate: (element: HTMLElement) => boolean;
|
|
16
|
+
export declare const readCandidate: (element: HTMLElement, id: Identify, options?: VerticalControlAreaOptions) => Candidate | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as button from './ion-button.js';
|
|
2
|
+
import * as buttons from './ion-buttons.js';
|
|
3
|
+
import * as backButton from './ion-back-button.js';
|
|
4
|
+
import * as menuButton from './ion-menu-button.js';
|
|
5
|
+
import * as tabBar from './ion-tab-bar.js';
|
|
6
|
+
import * as segment from './ion-segment.js';
|
|
7
|
+
import * as fab from './ion-fab.js';
|
|
8
|
+
import { isVerticalBarsSource, visible, withoutPrehide } from '../shared/dom.js';
|
|
9
|
+
// Static composition only. Each component declares its own tag, discovery and reader.
|
|
10
|
+
export const components = [button, buttons, backButton, menuButton, tabBar, segment, fab];
|
|
11
|
+
export const selector = components
|
|
12
|
+
.map((component) => ('selector' in component ? component.selector : component.tag))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.join(', ');
|
|
15
|
+
export const shadowSelector = [
|
|
16
|
+
'ion-icon',
|
|
17
|
+
...components.flatMap((component) => ('shadowSelector' in component ? [component.shadowSelector] : [])),
|
|
18
|
+
].join(', ');
|
|
19
|
+
export const motionSelector = [
|
|
20
|
+
'.ion-page',
|
|
21
|
+
'ion-header',
|
|
22
|
+
'ion-footer',
|
|
23
|
+
'ion-toolbar',
|
|
24
|
+
...components.filter((component) => 'tracksMotion' in component && component.tracksMotion).map((component) => component.tag),
|
|
25
|
+
].join(', ');
|
|
26
|
+
export const isVerticalBarsCandidate = isVerticalBarsSource;
|
|
27
|
+
export const readCandidate = (element, id, options = {}) => {
|
|
28
|
+
const verticalBars = isVerticalBarsCandidate(element);
|
|
29
|
+
if ((!element.classList.contains('ios') && !verticalBars) ||
|
|
30
|
+
!visible(element, verticalBars) ||
|
|
31
|
+
element.closest('ion-popover') ||
|
|
32
|
+
(element.closest('ion-modal') && !verticalBars))
|
|
33
|
+
return;
|
|
34
|
+
if (element.contains(element.ownerDocument.activeElement))
|
|
35
|
+
return;
|
|
36
|
+
// Read icon and control geometry before restoring prehide, which can collapse the toolbar.
|
|
37
|
+
return withoutPrehide(element, () => components.find((component) => component.tag === element.localName)?.read(element, id, options));
|
|
38
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions.js';
|
|
2
|
+
import type { Candidate, Identify } from '../shared/candidate.js';
|
|
3
|
+
export declare const tag = "ion-back-button";
|
|
4
|
+
export declare const read: (element: HTMLElement, id: Identify, options?: VerticalControlAreaOptions) => Candidate | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { horizontalSelected, inVerticalToolbarLayout } from '../shared/toolbar-options.js';
|
|
2
|
+
import { createCandidate, appendItem } from '../shared/candidate.js';
|
|
3
|
+
import { inFixedToolbar, isVerticalBarsSource } from '../shared/dom.js';
|
|
4
|
+
export const tag = 'ion-back-button';
|
|
5
|
+
export const read = (element, id, options = {}) => {
|
|
6
|
+
const button = element;
|
|
7
|
+
if ((inVerticalToolbarLayout(element) ? !isVerticalBarsSource(element) : !horizontalSelected(element, options)) ||
|
|
8
|
+
!button.shadowRoot ||
|
|
9
|
+
button.icon !== undefined ||
|
|
10
|
+
button.color !== undefined ||
|
|
11
|
+
(!isVerticalBarsSource(element) && !inFixedToolbar(element)))
|
|
12
|
+
return;
|
|
13
|
+
const candidate = createCandidate(element, tag, id);
|
|
14
|
+
const label = button.shadowRoot.querySelector('[part="text"]')?.textContent?.trim() ?? '';
|
|
15
|
+
return appendItem(candidate, element, id, button.shadowRoot, label) ? candidate : undefined;
|
|
16
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VerticalControlAreaOptions } from '../definitions.js';
|
|
2
|
+
import type { Candidate, Identify } from '../shared/candidate.js';
|
|
3
|
+
export declare const tag = "ion-button";
|
|
4
|
+
export declare const read: (element: HTMLElement, id: Identify, options?: VerticalControlAreaOptions) => Candidate | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { horizontalSelected, horizontalToolbarGroup, inVerticalToolbarLayout } from '../shared/toolbar-options.js';
|
|
2
|
+
import { createCandidate, appendItem } from '../shared/candidate.js';
|
|
3
|
+
import { inFixedToolbar, isVerticalBarsToolbarAction, isVerticalBarsToolbarGroup } from '../shared/dom.js';
|
|
4
|
+
export const tag = 'ion-button';
|
|
5
|
+
export const read = (element, id, options = {}) => {
|
|
6
|
+
const button = element;
|
|
7
|
+
const verticalBars = inVerticalToolbarLayout(element);
|
|
8
|
+
if (verticalBars && element.parentElement && isVerticalBarsToolbarGroup(element.parentElement))
|
|
9
|
+
return;
|
|
10
|
+
const fill = button.fill ?? 'default';
|
|
11
|
+
const group = element.parentElement?.matches('ion-buttons') ? element.parentElement : undefined;
|
|
12
|
+
if (!verticalBars && (!horizontalSelected(element, options) || (group && horizontalToolbarGroup(group, options))))
|
|
13
|
+
return;
|
|
14
|
+
if (!inFixedToolbar(element) ||
|
|
15
|
+
(verticalBars && !isVerticalBarsToolbarAction(element)) ||
|
|
16
|
+
(!verticalBars && fill !== 'default' && !(group && fill === 'clear')) ||
|
|
17
|
+
(!verticalBars && button.classList.contains('ion-color')))
|
|
18
|
+
return;
|
|
19
|
+
const candidate = createCandidate(element, tag, id, options);
|
|
20
|
+
const item = appendItem(candidate, element, id);
|
|
21
|
+
if (!item)
|
|
22
|
+
return;
|
|
23
|
+
if (!verticalBars && (fill === 'clear' || (group && button.fill === undefined)))
|
|
24
|
+
item.buttonFill = 'clear';
|
|
25
|
+
return candidate;
|
|
26
|
+
};
|