@webview-native-ui/engine 1.0.0 → 1.0.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +48 -35
  2. package/WebViewNativeUIControls.podspec +17 -0
  3. package/dist/controls/activation.d.ts +15 -0
  4. package/dist/controls/activation.js +370 -0
  5. package/dist/controls/components/index.d.ts +16 -0
  6. package/dist/controls/components/index.js +38 -0
  7. package/dist/controls/components/ion-back-button.d.ts +4 -0
  8. package/dist/controls/components/ion-back-button.js +16 -0
  9. package/dist/controls/components/ion-button.d.ts +4 -0
  10. package/dist/controls/components/ion-button.js +26 -0
  11. package/dist/controls/components/ion-buttons.d.ts +5 -0
  12. package/dist/controls/components/ion-buttons.js +34 -0
  13. package/dist/controls/components/ion-fab.d.ts +4 -0
  14. package/dist/controls/components/ion-fab.js +164 -0
  15. package/dist/controls/components/ion-menu-button.d.ts +7 -0
  16. package/dist/controls/components/ion-menu-button.js +41 -0
  17. package/dist/controls/components/ion-segment.d.ts +5 -0
  18. package/dist/controls/components/ion-segment.js +24 -0
  19. package/dist/controls/components/ion-tab-bar.d.ts +5 -0
  20. package/dist/controls/components/ion-tab-bar.js +49 -0
  21. package/dist/controls/components/searchable-tabs.d.ts +20 -0
  22. package/dist/controls/components/searchable-tabs.js +525 -0
  23. package/dist/controls/definitions.d.ts +360 -0
  24. package/dist/controls/definitions.js +1 -0
  25. package/dist/controls/extensions.d.ts +44 -0
  26. package/dist/controls/extensions.js +164 -0
  27. package/dist/controls/foldable/fold-state.d.ts +4 -0
  28. package/dist/controls/foldable/fold-state.js +8 -0
  29. package/dist/controls/foldable/types.d.ts +9 -0
  30. package/dist/controls/foldable/types.js +1 -0
  31. package/dist/controls/html.d.ts +8 -0
  32. package/dist/controls/html.js +260 -0
  33. package/dist/controls/index.d.ts +7 -0
  34. package/dist/controls/index.js +4 -0
  35. package/dist/controls/ionic.d.ts +1 -0
  36. package/dist/controls/ionic.js +1 -0
  37. package/dist/controls/legacy-layout.d.ts +7 -0
  38. package/dist/controls/legacy-layout.js +23 -0
  39. package/dist/controls/overlays/content.d.ts +2 -0
  40. package/dist/controls/overlays/content.js +55 -0
  41. package/dist/controls/overlays/controller.d.ts +7 -0
  42. package/dist/controls/overlays/controller.js +369 -0
  43. package/dist/controls/overlays/covered-overlay.d.ts +4 -0
  44. package/dist/controls/overlays/covered-overlay.js +9 -0
  45. package/dist/controls/overlays/dialog-animation.d.ts +7 -0
  46. package/dist/controls/overlays/dialog-animation.js +49 -0
  47. package/dist/controls/overlays/dismiss-animation.d.ts +2 -0
  48. package/dist/controls/overlays/dismiss-animation.js +14 -0
  49. package/dist/controls/overlays/focus.d.ts +2 -0
  50. package/dist/controls/overlays/focus.js +67 -0
  51. package/dist/controls/overlays/popover.d.ts +10 -0
  52. package/dist/controls/overlays/popover.js +117 -0
  53. package/dist/controls/overlays/styles.d.ts +9 -0
  54. package/dist/controls/overlays/styles.js +194 -0
  55. package/dist/controls/prehide.d.ts +6 -0
  56. package/dist/controls/prehide.js +280 -0
  57. package/dist/controls/runtime.d.ts +5 -0
  58. package/dist/controls/runtime.js +1685 -0
  59. package/dist/controls/shared/bridge.d.ts +4 -0
  60. package/dist/controls/shared/bridge.js +15 -0
  61. package/dist/controls/shared/candidate.d.ts +23 -0
  62. package/dist/controls/shared/candidate.js +128 -0
  63. package/dist/controls/shared/clipping.d.ts +1 -0
  64. package/dist/controls/shared/clipping.js +1 -0
  65. package/dist/controls/shared/connection.d.ts +21 -0
  66. package/dist/controls/shared/connection.js +112 -0
  67. package/dist/controls/shared/control-options.d.ts +4 -0
  68. package/dist/controls/shared/control-options.js +14 -0
  69. package/dist/controls/shared/covered-snapshot.d.ts +35 -0
  70. package/dist/controls/shared/covered-snapshot.js +41 -0
  71. package/dist/controls/shared/covered-source.d.ts +22 -0
  72. package/dist/controls/shared/covered-source.js +72 -0
  73. package/dist/controls/shared/crossfade.d.ts +11 -0
  74. package/dist/controls/shared/crossfade.js +71 -0
  75. package/dist/controls/shared/diagnostics.d.ts +2 -0
  76. package/dist/controls/shared/diagnostics.js +16 -0
  77. package/dist/controls/shared/dom.d.ts +45 -0
  78. package/dist/controls/shared/dom.js +258 -0
  79. package/dist/controls/shared/events.d.ts +12 -0
  80. package/dist/controls/shared/events.js +13 -0
  81. package/dist/controls/shared/horizontal-navigation.d.ts +36 -0
  82. package/dist/controls/shared/horizontal-navigation.js +90 -0
  83. package/dist/controls/shared/icons.d.ts +5 -0
  84. package/dist/controls/shared/icons.js +66 -0
  85. package/dist/controls/shared/modal-animation.d.ts +21 -0
  86. package/dist/controls/shared/modal-animation.js +97 -0
  87. package/dist/controls/shared/modal.d.ts +26 -0
  88. package/dist/controls/shared/modal.js +261 -0
  89. package/dist/controls/shared/projection-id.d.ts +2 -0
  90. package/dist/controls/shared/projection-id.js +2 -0
  91. package/dist/controls/shared/scroll-placement.d.ts +1 -0
  92. package/dist/controls/shared/scroll-placement.js +1 -0
  93. package/dist/controls/shared/tab-bar-width.d.ts +7 -0
  94. package/dist/controls/shared/tab-bar-width.js +72 -0
  95. package/dist/controls/shared/toolbar-options.d.ts +10 -0
  96. package/dist/controls/shared/toolbar-options.js +38 -0
  97. package/dist/controls/shared/transition-identity.d.ts +3 -0
  98. package/dist/controls/shared/transition-identity.js +64 -0
  99. package/dist/controls/shared/vertical-bars-geometry.d.ts +8 -0
  100. package/dist/controls/shared/vertical-bars-geometry.js +82 -0
  101. package/dist/controls/shared/vertical-navigation.d.ts +5 -0
  102. package/dist/controls/shared/vertical-navigation.js +39 -0
  103. package/dist/controls/startup.d.ts +7 -0
  104. package/dist/controls/startup.js +72 -0
  105. package/dist/controls/transport.d.ts +10 -0
  106. package/dist/controls/transport.js +1 -0
  107. package/dist/controls/vertical-bars-web-search.d.ts +8 -0
  108. package/dist/controls/vertical-bars-web-search.js +83 -0
  109. package/dist/controls/vertical-bars-web.d.ts +2 -0
  110. package/dist/controls/vertical-bars-web.js +609 -0
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +1 -0
  113. package/dist/integration/index.d.ts +36 -0
  114. package/dist/integration/index.js +25 -0
  115. package/dist/native/bootstrap.js +1 -1
  116. package/dist/native/session.js +14 -8
  117. package/dist/projection/pause.d.ts +10 -0
  118. package/dist/projection/pause.js +58 -0
  119. package/dist/projection/styles.d.ts +1 -1
  120. package/dist/projection/styles.js +0 -1
  121. package/dist/projection/surfaces.js +11 -8
  122. package/dist/projection/web.js +48 -13
  123. package/dist/types.d.ts +5 -1
  124. package/docs/README.md +17 -10
  125. package/docs/concepts.md +39 -14
  126. package/docs/configuration/README.md +4 -2
  127. package/docs/configuration/ionic-overlays.md +36 -2
  128. package/docs/configuration/pause-resume.md +51 -0
  129. package/docs/controls.md +39 -0
  130. package/docs/getting-started/capacitor.md +3 -3
  131. package/docs/getting-started/cordova.md +3 -1
  132. package/docs/getting-started/initialization.md +18 -2
  133. package/docs/reference/platforms.md +2 -2
  134. package/docs/reference/projection.md +1 -1
  135. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButton.swift +98 -0
  136. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIButtons.swift +24 -0
  137. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIComponents.swift +19 -0
  138. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIFab.swift +80 -0
  139. package/ios/Sources/WebViewNativeUIControls/Components/NativeUISearchController.swift +449 -0
  140. package/ios/Sources/WebViewNativeUIControls/Components/NativeUISegment.swift +91 -0
  141. package/ios/Sources/WebViewNativeUIControls/Components/NativeUITabBar.swift +213 -0
  142. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIToolbarTransition.swift +186 -0
  143. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalBars.swift +696 -0
  144. package/ios/Sources/WebViewNativeUIControls/Components/NativeUIVerticalSearch.swift +117 -0
  145. package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUI.swift +180 -0
  146. package/ios/Sources/WebViewNativeUIControls/Extensions/NativeUIRendering.swift +38 -0
  147. package/ios/Sources/WebViewNativeUIControls/NativeUIController.swift +801 -0
  148. package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayController.swift +131 -0
  149. package/ios/Sources/WebViewNativeUIControls/Overlays/NativeUIOverlayHost.swift +85 -0
  150. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIControlRendering.swift +21 -0
  151. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICoveredSnapshots.swift +14 -0
  152. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUICrossfade.swift +35 -0
  153. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIHost.swift +8 -0
  154. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIScrollTracker.swift +2 -0
  155. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUISnapshot.swift +206 -0
  156. package/ios/Sources/WebViewNativeUIControls/Shared/NativeUIVerticalBarsGeometry.swift +93 -0
  157. package/ios/Sources/WebViewNativeUIEngine/CoveredSnapshots.swift +2 -2
  158. package/ios/Sources/WebViewNativeUIEngine/NativeUIProjection.swift +6 -4
  159. package/ios/Sources/WebViewNativeUIEngine/ProjectionOrigins.swift +31 -0
  160. package/ios/Sources/WebViewNativeUIEngine/Resources/native-ui-engine.js +3 -4
  161. package/package.json +30 -5
  162. package/src/controls/activation.ts +427 -0
  163. package/src/controls/components/index.ts +45 -0
  164. package/src/controls/components/ion-back-button.ts +22 -0
  165. package/src/controls/components/ion-button.ts +28 -0
  166. package/src/controls/components/ion-buttons.ts +37 -0
  167. package/src/controls/components/ion-fab.ts +151 -0
  168. package/src/controls/components/ion-menu-button.ts +37 -0
  169. package/src/controls/components/ion-segment.ts +27 -0
  170. package/src/controls/components/ion-tab-bar.ts +55 -0
  171. package/src/controls/components/searchable-tabs.ts +534 -0
  172. package/src/controls/definitions.ts +338 -0
  173. package/src/controls/extensions.ts +198 -0
  174. package/src/controls/foldable/fold-state.ts +10 -0
  175. package/src/controls/foldable/types.ts +4 -0
  176. package/src/controls/html.ts +239 -0
  177. package/src/controls/index.ts +7 -0
  178. package/src/controls/ionic.ts +1 -0
  179. package/src/controls/legacy-layout.ts +21 -0
  180. package/src/controls/overlays/content.ts +51 -0
  181. package/src/controls/overlays/controller.ts +394 -0
  182. package/src/controls/overlays/covered-overlay.ts +11 -0
  183. package/src/controls/overlays/dialog-animation.ts +53 -0
  184. package/src/controls/overlays/dismiss-animation.ts +15 -0
  185. package/src/controls/overlays/focus.ts +64 -0
  186. package/src/controls/overlays/popover.ts +116 -0
  187. package/src/controls/overlays/styles.ts +195 -0
  188. package/src/controls/prehide.ts +285 -0
  189. package/src/controls/runtime.ts +1731 -0
  190. package/src/controls/shared/bridge.ts +18 -0
  191. package/src/controls/shared/candidate.ts +163 -0
  192. package/src/controls/shared/clipping.ts +1 -0
  193. package/src/controls/shared/connection.ts +105 -0
  194. package/src/controls/shared/control-options.ts +20 -0
  195. package/src/controls/shared/covered-snapshot.ts +52 -0
  196. package/src/controls/shared/covered-source.ts +78 -0
  197. package/src/controls/shared/crossfade.ts +70 -0
  198. package/src/controls/shared/diagnostics.ts +13 -0
  199. package/src/controls/shared/dom.ts +310 -0
  200. package/src/controls/shared/events.ts +14 -0
  201. package/src/controls/shared/horizontal-navigation.ts +106 -0
  202. package/src/controls/shared/icons.ts +64 -0
  203. package/src/controls/shared/modal-animation.ts +109 -0
  204. package/src/controls/shared/modal.ts +286 -0
  205. package/src/controls/shared/projection-id.ts +2 -0
  206. package/src/controls/shared/scroll-placement.ts +1 -0
  207. package/src/controls/shared/tab-bar-width.ts +66 -0
  208. package/src/controls/shared/toolbar-options.ts +51 -0
  209. package/src/controls/shared/transition-identity.ts +67 -0
  210. package/src/controls/shared/vertical-bars-geometry.ts +92 -0
  211. package/src/controls/shared/vertical-navigation.ts +34 -0
  212. package/src/controls/startup.ts +74 -0
  213. package/src/controls/transport.ts +11 -0
  214. package/src/controls/vertical-bars-web-search.ts +86 -0
  215. package/src/controls/vertical-bars-web.ts +674 -0
  216. package/src/dom/geometry.ts +24 -0
  217. package/src/dom/overlays.ts +196 -0
  218. package/src/dom/scroll-bindings.ts +66 -0
  219. package/src/dom/scroll-placement.ts +92 -0
  220. package/src/dom/visibility.ts +29 -0
  221. package/src/index.ts +19 -0
  222. package/src/integration/index.ts +64 -0
  223. package/src/ionic.ts +34 -0
  224. package/src/native/bootstrap.ts +132 -0
  225. package/src/native/bridge.ts +18 -0
  226. package/src/native/resource-url.ts +11 -0
  227. package/src/native/session.ts +708 -0
  228. package/src/native/updates.ts +103 -0
  229. package/src/native/values.ts +29 -0
  230. package/src/projection/events.ts +58 -0
  231. package/src/projection/pause.ts +58 -0
  232. package/src/projection/placement.ts +34 -0
  233. package/src/projection/scope.ts +13 -0
  234. package/src/projection/styles.ts +26 -0
  235. package/src/projection/surface.ts +88 -0
  236. package/src/projection/surfaces.ts +119 -0
  237. package/src/projection/web.ts +1013 -0
  238. package/src/types.ts +58 -0
package/README.md CHANGED
@@ -1,60 +1,73 @@
1
- # WebView Native UI Engine
1
+ # Beyond the WebView.
2
2
 
3
- **Bring Web UI above the native layer.**
3
+ **Bring Native UI seamlessly into Web apps.**
4
4
 
5
- _Visually, through seamless native-to-Web projection._
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
  ![Conceptual view of a Web overlay above a native surface and WebView content](docs/assets/hero.png)
8
17
 
9
- _Conceptual view. Native appearance is projected into the Web layer during overlay presentation._
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
- Build WebView apps where native controls and Web content share the same scrolling screens, transitions and overlays.
22
+ native controls beneath a Web alert, followed by a native ad scrolling with Web content. Recorded on iOS Simulator.
12
23
 
13
- ## Beyond the WebView boundary
24
+ ## Built for plugin developers
14
25
 
15
- WebView apps combine the Web's flexible layouts with access to native platform capabilities. But native UI and the Web page live in separate rendering layers. A native control placed above the WebView does not automatically participate in DOM scrolling, clipping or stacking order. A Web modal cannot simply cover it with a higher `z-index`.
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
- Crossing that boundary takes more than a bridge call. It requires coordinating placement, appearance, input, rendering and restoration across two rendering systems on each platform. Rebuilding that coordination for every component makes otherwise useful designs expensive to deliver and maintain.
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
- WebView Native UI Engine makes that work reusable. It lets applications combine native UI with Web-driven screens and interactions, while sharing the machinery that moves presentation between the two layers. The goal is to expand what a WebView app can be built to do—and make that practical to ship.
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
- ## What this makes possible
32
+ ## How it works
22
33
 
23
- - **Native UI within Web-designed screens.** Combine native toolbars, tab bars, player controls and eligible SDK views with the application's DOM layout and navigation.
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
- ## The approach
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
- Use the live native view where it belongs, and a prepared visual projection in the DOM when the Web layer needs to own presentation. Restore the interactive native view when it can take over again. An existing DOM container gives the Web projection its place in the application's layout and layer structure.
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
- The Engine coordinates this handoff; the integrating renderer still creates and positions its views. A projected image preserves appearance, not live interaction or video. Capture availability and layout support determine when a projection can be used, with an explicit fallback when it cannot. See [concepts and limits](docs/concepts.md) for the detailed contract and limits.
40
+ ## Start building
33
41
 
34
- ## Choose your starting point
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
- Applications gain these capabilities through plugins that integrate the Engine.
37
- Plugin authors retain their existing native controls and public APIs; the Engine
38
- supplies the coordination between native and Web presentation. It supports iOS
39
- and Android with Capacitor or Cordova. Ionic and Angular are not required.
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
- | Start here | Purpose |
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
- Native registration starts the Web runtime; the base integration needs no separate
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
- ## Reference and contributing
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
- The [documentation index](docs/README.md) links to configuration, advanced APIs,
55
- container support and platform contracts. For repository development and publishing,
56
- see [development and releases](docs/maintenance.md).
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
+ };