@rdlabo/ionic-theme-ios27 1.2.1-beta.pr275.shad339e7688db7 → 2.0.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +24 -280
  2. package/dist/css/components/ion-toolbar.css +1 -1
  3. package/dist/css/ionic-theme-ios27.css +1 -1
  4. package/dist/css/vertical-bars.css +1 -1
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/layout/index.d.ts +18 -0
  10. package/dist/layout/index.d.ts.map +1 -0
  11. package/dist/layout/index.js +26 -0
  12. package/dist/layout/index.js.map +1 -0
  13. package/dist/segment/index.js +3 -3
  14. package/dist/segment/index.js.map +1 -1
  15. package/dist/sheets-of-glass/index.js +7 -7
  16. package/dist/sheets-of-glass/index.js.map +1 -1
  17. package/dist/tab-bar-searchable/dismiss-button.js +53 -3
  18. package/dist/tab-bar-searchable/dismiss-button.js.map +1 -1
  19. package/dist/tab-bar-searchable/index.js +18 -3
  20. package/dist/tab-bar-searchable/index.js.map +1 -1
  21. package/dist/transition/ios.transition.d.ts.map +1 -1
  22. package/dist/transition/ios.transition.js +7 -4
  23. package/dist/transition/ios.transition.js.map +1 -1
  24. package/dist/transition/native-ui.d.ts +6 -0
  25. package/dist/transition/native-ui.d.ts.map +1 -0
  26. package/dist/{native-integration/index.js → transition/native-ui.js} +16 -30
  27. package/dist/transition/native-ui.js.map +1 -0
  28. package/docs/adaptive-themes.md +161 -0
  29. package/docs/add-native-ui.md +53 -0
  30. package/docs/animation.md +19 -3
  31. package/docs/eslint.md +3 -1
  32. package/docs/features.md +9 -5
  33. package/docs/iphone-duo.md +62 -35
  34. package/docs/migration.md +17 -26
  35. package/docs/setup.md +92 -0
  36. package/docs/special-markup.md +41 -10
  37. package/docs/using-ion-item-group.md +2 -2
  38. package/docs/vertical-bars-layout.md +67 -0
  39. package/package.json +12 -42
  40. package/src/index.ts +3 -1
  41. package/src/layout/index.ts +29 -0
  42. package/src/segment/index.ts +3 -3
  43. package/src/sheets-of-glass/index.ts +7 -7
  44. package/src/styles/components/ion-toolbar.scss +1 -1
  45. package/src/styles/vertical-bars.scss +15 -18
  46. package/src/tab-bar-searchable/dismiss-button.ts +73 -4
  47. package/src/tab-bar-searchable/index.ts +20 -3
  48. package/src/transition/ios.transition.ts +7 -4
  49. package/src/{native-integration/index.ts → transition/native-ui.ts} +17 -46
  50. package/Package.swift +0 -21
  51. package/dist/_virtual/_rolldown/runtime.js +0 -13
  52. package/dist/native/components/index.d.ts +0 -19
  53. package/dist/native/components/index.d.ts.map +0 -1
  54. package/dist/native/components/index.js +0 -40
  55. package/dist/native/components/index.js.map +0 -1
  56. package/dist/native/components/ion-back-button.d.ts +0 -9
  57. package/dist/native/components/ion-back-button.d.ts.map +0 -1
  58. package/dist/native/components/ion-back-button.js +0 -20
  59. package/dist/native/components/ion-back-button.js.map +0 -1
  60. package/dist/native/components/ion-button.d.ts +0 -10
  61. package/dist/native/components/ion-button.d.ts.map +0 -1
  62. package/dist/native/components/ion-button.js +0 -23
  63. package/dist/native/components/ion-button.js.map +0 -1
  64. package/dist/native/components/ion-buttons.d.ts +0 -11
  65. package/dist/native/components/ion-buttons.d.ts.map +0 -1
  66. package/dist/native/components/ion-buttons.js +0 -30
  67. package/dist/native/components/ion-buttons.js.map +0 -1
  68. package/dist/native/components/ion-fab.d.ts +0 -10
  69. package/dist/native/components/ion-fab.d.ts.map +0 -1
  70. package/dist/native/components/ion-fab.js +0 -132
  71. package/dist/native/components/ion-fab.js.map +0 -1
  72. package/dist/native/components/ion-menu-button.d.ts +0 -13
  73. package/dist/native/components/ion-menu-button.d.ts.map +0 -1
  74. package/dist/native/components/ion-menu-button.js +0 -31
  75. package/dist/native/components/ion-menu-button.js.map +0 -1
  76. package/dist/native/components/ion-segment.d.ts +0 -11
  77. package/dist/native/components/ion-segment.d.ts.map +0 -1
  78. package/dist/native/components/ion-segment.js +0 -30
  79. package/dist/native/components/ion-segment.js.map +0 -1
  80. package/dist/native/components/ion-tab-bar.d.ts +0 -11
  81. package/dist/native/components/ion-tab-bar.d.ts.map +0 -1
  82. package/dist/native/components/ion-tab-bar.js +0 -50
  83. package/dist/native/components/ion-tab-bar.js.map +0 -1
  84. package/dist/native/components/searchable-tabs.d.ts +0 -20
  85. package/dist/native/components/searchable-tabs.d.ts.map +0 -1
  86. package/dist/native/components/searchable-tabs.js +0 -383
  87. package/dist/native/components/searchable-tabs.js.map +0 -1
  88. package/dist/native/definitions.d.ts +0 -191
  89. package/dist/native/definitions.d.ts.map +0 -1
  90. package/dist/native/definitions.js +0 -0
  91. package/dist/native/foldable/fold-state.d.ts +0 -5
  92. package/dist/native/foldable/fold-state.d.ts.map +0 -1
  93. package/dist/native/foldable/fold-state.js +0 -14
  94. package/dist/native/foldable/fold-state.js.map +0 -1
  95. package/dist/native/foldable/types.d.ts +0 -12
  96. package/dist/native/foldable/types.d.ts.map +0 -1
  97. package/dist/native/foldable/types.js +0 -0
  98. package/dist/native/index.d.ts +0 -11
  99. package/dist/native/index.d.ts.map +0 -1
  100. package/dist/native/index.js +0 -267
  101. package/dist/native/index.js.map +0 -1
  102. package/dist/native/overlays/content.d.ts +0 -4
  103. package/dist/native/overlays/content.d.ts.map +0 -1
  104. package/dist/native/overlays/content.js +0 -51
  105. package/dist/native/overlays/content.js.map +0 -1
  106. package/dist/native/overlays/controller.d.ts +0 -8
  107. package/dist/native/overlays/controller.d.ts.map +0 -1
  108. package/dist/native/overlays/controller.js +0 -285
  109. package/dist/native/overlays/controller.js.map +0 -1
  110. package/dist/native/overlays/dialog-animation.d.ts +0 -9
  111. package/dist/native/overlays/dialog-animation.d.ts.map +0 -1
  112. package/dist/native/overlays/dialog-animation.js +0 -53
  113. package/dist/native/overlays/dialog-animation.js.map +0 -1
  114. package/dist/native/overlays/dismiss-animation.d.ts +0 -4
  115. package/dist/native/overlays/dismiss-animation.d.ts.map +0 -1
  116. package/dist/native/overlays/dismiss-animation.js +0 -18
  117. package/dist/native/overlays/dismiss-animation.js.map +0 -1
  118. package/dist/native/overlays/focus.d.ts +0 -5
  119. package/dist/native/overlays/focus.d.ts.map +0 -1
  120. package/dist/native/overlays/focus.js +0 -59
  121. package/dist/native/overlays/focus.js.map +0 -1
  122. package/dist/native/overlays/popover.d.ts +0 -12
  123. package/dist/native/overlays/popover.d.ts.map +0 -1
  124. package/dist/native/overlays/popover.js +0 -110
  125. package/dist/native/overlays/popover.js.map +0 -1
  126. package/dist/native/overlays/styles.d.ts +0 -11
  127. package/dist/native/overlays/styles.d.ts.map +0 -1
  128. package/dist/native/overlays/styles.js +0 -167
  129. package/dist/native/overlays/styles.js.map +0 -1
  130. package/dist/native/prehide.d.ts +0 -7
  131. package/dist/native/prehide.d.ts.map +0 -1
  132. package/dist/native/prehide.js +0 -230
  133. package/dist/native/prehide.js.map +0 -1
  134. package/dist/native/runtime.d.ts +0 -7
  135. package/dist/native/runtime.d.ts.map +0 -1
  136. package/dist/native/runtime.js +0 -710
  137. package/dist/native/runtime.js.map +0 -1
  138. package/dist/native/shared/bridge.d.ts +0 -6
  139. package/dist/native/shared/bridge.d.ts.map +0 -1
  140. package/dist/native/shared/bridge.js +0 -18
  141. package/dist/native/shared/bridge.js.map +0 -1
  142. package/dist/native/shared/candidate.d.ts +0 -21
  143. package/dist/native/shared/candidate.d.ts.map +0 -1
  144. package/dist/native/shared/candidate.js +0 -98
  145. package/dist/native/shared/candidate.js.map +0 -1
  146. package/dist/native/shared/crossfade.d.ts +0 -12
  147. package/dist/native/shared/crossfade.d.ts.map +0 -1
  148. package/dist/native/shared/crossfade.js +0 -67
  149. package/dist/native/shared/crossfade.js.map +0 -1
  150. package/dist/native/shared/dom.d.ts +0 -46
  151. package/dist/native/shared/dom.d.ts.map +0 -1
  152. package/dist/native/shared/dom.js +0 -172
  153. package/dist/native/shared/dom.js.map +0 -1
  154. package/dist/native/shared/events.d.ts +0 -11
  155. package/dist/native/shared/events.d.ts.map +0 -1
  156. package/dist/native/shared/events.js +0 -13
  157. package/dist/native/shared/events.js.map +0 -1
  158. package/dist/native/shared/icons.d.ts +0 -8
  159. package/dist/native/shared/icons.d.ts.map +0 -1
  160. package/dist/native/shared/icons.js +0 -61
  161. package/dist/native/shared/icons.js.map +0 -1
  162. package/dist/native/shared/modal.d.ts +0 -10
  163. package/dist/native/shared/modal.d.ts.map +0 -1
  164. package/dist/native/shared/modal.js +0 -160
  165. package/dist/native/shared/modal.js.map +0 -1
  166. package/dist/native/shared/projection-id.d.ts +0 -4
  167. package/dist/native/shared/projection-id.d.ts.map +0 -1
  168. package/dist/native/shared/projection-id.js +0 -7
  169. package/dist/native/shared/projection-id.js.map +0 -1
  170. package/dist/native/shared/vertical-bars-geometry.d.ts +0 -10
  171. package/dist/native/shared/vertical-bars-geometry.d.ts.map +0 -1
  172. package/dist/native/shared/vertical-bars-geometry.js +0 -85
  173. package/dist/native/shared/vertical-bars-geometry.js.map +0 -1
  174. package/dist/native/vertical-bars-web-search.d.ts +0 -10
  175. package/dist/native/vertical-bars-web-search.d.ts.map +0 -1
  176. package/dist/native/vertical-bars-web-search.js +0 -90
  177. package/dist/native/vertical-bars-web-search.js.map +0 -1
  178. package/dist/native/vertical-bars-web.d.ts +0 -5
  179. package/dist/native/vertical-bars-web.d.ts.map +0 -1
  180. package/dist/native/vertical-bars-web.js +0 -411
  181. package/dist/native/vertical-bars-web.js.map +0 -1
  182. package/dist/native-integration/index.d.ts +0 -23
  183. package/dist/native-integration/index.d.ts.map +0 -1
  184. package/dist/native-integration/index.js.map +0 -1
  185. package/dist/native-integration/transition.d.ts +0 -5
  186. package/dist/native-integration/transition.d.ts.map +0 -1
  187. package/dist/native-integration/transition.js +0 -16
  188. package/dist/native-integration/transition.js.map +0 -1
  189. package/dist/vertical-bars.d.ts +0 -5
  190. package/dist/vertical-bars.js +0 -4
  191. package/docs/e2e-testing.md +0 -51
  192. package/docs/feedback.md +0 -65
  193. package/docs/iphone-duo-with-original-theme.md +0 -158
  194. package/docs/native-ui-shell.md +0 -325
  195. package/docs/vertical-bars.md +0 -300
  196. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButton.swift +0 -71
  197. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButtons.swift +0 -24
  198. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellComponents.swift +0 -18
  199. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellFab.swift +0 -80
  200. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +0 -432
  201. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSegment.swift +0 -91
  202. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +0 -218
  203. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +0 -565
  204. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +0 -118
  205. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +0 -502
  206. package/ios/Sources/IonicNativeUIShellPlugin/Overlays/ShellOverlayController.swift +0 -119
  207. package/ios/Sources/IonicNativeUIShellPlugin/Overlays/ShellOverlayHost.swift +0 -75
  208. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellCrossfade.swift +0 -35
  209. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellHost.swift +0 -8
  210. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellRendering.swift +0 -27
  211. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +0 -135
  212. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellVerticalBarsGeometry.swift +0 -52
  213. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +0 -788
  214. package/src/native/components/index.ts +0 -53
  215. package/src/native/components/ion-back-button.ts +0 -19
  216. package/src/native/components/ion-button.ts +0 -24
  217. package/src/native/components/ion-buttons.ts +0 -38
  218. package/src/native/components/ion-fab.ts +0 -151
  219. package/src/native/components/ion-menu-button.ts +0 -24
  220. package/src/native/components/ion-segment.ts +0 -27
  221. package/src/native/components/ion-tab-bar.ts +0 -50
  222. package/src/native/components/searchable-tabs.ts +0 -459
  223. package/src/native/definitions.ts +0 -218
  224. package/src/native/foldable/fold-state.ts +0 -9
  225. package/src/native/foldable/types.ts +0 -4
  226. package/src/native/index.ts +0 -315
  227. package/src/native/overlays/content.ts +0 -51
  228. package/src/native/overlays/controller.ts +0 -349
  229. package/src/native/overlays/dialog-animation.ts +0 -53
  230. package/src/native/overlays/dismiss-animation.ts +0 -15
  231. package/src/native/overlays/focus.ts +0 -64
  232. package/src/native/overlays/popover.ts +0 -120
  233. package/src/native/overlays/styles.ts +0 -181
  234. package/src/native/prehide.ts +0 -265
  235. package/src/native/runtime.ts +0 -885
  236. package/src/native/shared/bridge.ts +0 -18
  237. package/src/native/shared/candidate.ts +0 -150
  238. package/src/native/shared/crossfade.ts +0 -67
  239. package/src/native/shared/dom.ts +0 -286
  240. package/src/native/shared/events.ts +0 -8
  241. package/src/native/shared/icons.ts +0 -57
  242. package/src/native/shared/modal.ts +0 -157
  243. package/src/native/shared/projection-id.ts +0 -2
  244. package/src/native/shared/vertical-bars-geometry.ts +0 -80
  245. package/src/native/vertical-bars-web-search.ts +0 -86
  246. package/src/native/vertical-bars-web.ts +0 -485
  247. package/src/native-integration/transition.ts +0 -15
  248. package/src/vertical-bars.ts +0 -11
@@ -1,75 +0,0 @@
1
- import UIKit
2
- import WebKit
3
-
4
- /// Hosts only the live content of a Vertical Bars popover.
5
- final class ShellOverlayHost: UIViewController {
6
- let webView: WKWebView
7
- private(set) var options: [String: Any]?
8
- let event: (String) -> Void
9
- // Settle native presentation before the JS bridge's five-second deadline.
10
- static let completionTimeout: TimeInterval = 4
11
- var dismissAnchored: (() -> Void)?
12
- var cancelAnchored: (() -> Void)?
13
- var anchoredVisible = false
14
- private var dismissalCompletions: [() -> Void]?
15
- private var dismissalTimeout: DispatchWorkItem?
16
-
17
- func completeDismissal() {
18
- dismissalTimeout?.cancel()
19
- dismissalTimeout = nil
20
- anchoredVisible = false
21
- let completions = dismissalCompletions ?? []
22
- dismissalCompletions = nil
23
- completions.forEach { $0() }
24
- }
25
-
26
- // Dismiss, close and stop may overlap while SwiftUI is still disappearing.
27
- // Start dismissal once and deliver its completion to every waiting caller.
28
- func dismissPopover(animated: Bool, completion: @escaping () -> Void) {
29
- if dismissalCompletions != nil {
30
- dismissalCompletions?.append(completion)
31
- return
32
- }
33
- dismissalCompletions = [completion]
34
- let finish = { [self] in completeDismissal() }
35
- let timeout = DispatchWorkItem { [weak self] in
36
- guard let self, self.dismissalCompletions != nil else { return }
37
- // Remove the owning rail if SwiftUI never reports disappearance.
38
- // Its detach path settles waiters before any replacement can attach.
39
- self.cancelAnchored?()
40
- self.completeDismissal()
41
- }
42
- dismissalTimeout = timeout
43
- DispatchQueue.main.asyncAfter(deadline: .now() + Self.completionTimeout, execute: timeout)
44
- if let dismissAnchored {
45
- if anchoredVisible { dismissAnchored() }
46
- else { dismissAnchored(); finish() }
47
- } else if presentingViewController != nil {
48
- dismiss(animated: animated, completion: finish)
49
- } else { finish() }
50
- }
51
-
52
- init(configuration: WKWebViewConfiguration, options: [String: Any]?, event: @escaping (String) -> Void) {
53
- webView = WKWebView(frame: .zero, configuration: configuration)
54
- self.event = event
55
- super.init(nibName: nil, bundle: nil)
56
- apply(options)
57
- }
58
- required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
59
-
60
- func apply(_ options: [String: Any]?) {
61
- guard let options else { return }
62
- self.options = options
63
- preferredContentSize = CGSize(width: options["width"] as? Double ?? 280, height: options["height"] as? Double ?? 200)
64
- overrideUserInterfaceStyle = options["dark"] as? Bool == true ? .dark : .light
65
- }
66
-
67
- override func loadView() {
68
- webView.isOpaque = false
69
- webView.backgroundColor = .clear
70
- webView.scrollView.backgroundColor = .clear
71
- webView.scrollView.contentInsetAdjustmentBehavior = .never
72
- view = webView
73
- view.accessibilityViewIsModal = true
74
- }
75
- }
@@ -1,35 +0,0 @@
1
- import UIKit
2
-
3
- @MainActor enum ShellCrossfade {
4
- static func duration(_ milliseconds: Double?) -> TimeInterval {
5
- UIAccessibility.isReduceMotionEnabled ? 0 : (milliseconds ?? 0) / 1000
6
- }
7
-
8
- static func enter(_ view: UIView, duration: TimeInterval) {
9
- guard duration > 0 else { return }
10
- view.alpha = 0
11
- UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
12
- view.alpha = 1
13
- }
14
- }
15
-
16
- static func retire(_ view: UIView, duration: TimeInterval) {
17
- // The outgoing copy is visual only: it cannot keep accepting actions or
18
- // VoiceOver focus once the Web source has been restored.
19
- if duration > 0, let host = view.superview, let parent = host.superview,
20
- !view.isHidden, !host.isHidden, let copy = view.snapshotView(afterScreenUpdates: false) {
21
- copy.frame = host.convert(view.frame, to: parent)
22
- copy.alpha = CGFloat(view.layer.presentation()?.opacity ?? Float(view.alpha))
23
- copy.isUserInteractionEnabled = false
24
- copy.isAccessibilityElement = false
25
- copy.accessibilityElementsHidden = true
26
- parent.insertSubview(copy, aboveSubview: host)
27
- UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseInOut]) {
28
- copy.alpha = 0
29
- } completion: { _ in
30
- copy.removeFromSuperview()
31
- }
32
- }
33
- view.removeFromSuperview()
34
- }
35
- }
@@ -1,8 +0,0 @@
1
- import UIKit
2
-
3
- final class ShellHost: UIView {
4
- override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
5
- let hit = super.hitTest(point, with: event)
6
- return hit === self ? nil : hit
7
- }
8
- }
@@ -1,27 +0,0 @@
1
- import UIKit
2
-
3
- final class ShellRendering {
4
- private var images: [String: UIImage] = [:]
5
- func clear() { images.removeAll() }
6
-
7
- func image(_ item: ShellItemContent) -> UIImage? {
8
- guard let data = item.icon, let width = item.iconWidth, width > 0 else { return nil }
9
- let template = item.iconTemplate == true
10
- let key = (template ? "template:" : "original:") + data
11
- if let cached = images[key] { return cached }
12
- guard let bytes = Data(base64Encoded: data), let raw = UIImage(data: bytes), let cg = raw.cgImage else { return nil }
13
- let result = UIImage(cgImage: cg, scale: CGFloat(cg.width) / width, orientation: .up)
14
- .withRenderingMode(template ? .alwaysTemplate : .alwaysOriginal)
15
- if images.count >= 128 { images.removeAll() }
16
- images[key] = result
17
- return result
18
- }
19
-
20
- func color(_ css: String?) -> UIColor {
21
- guard let css else { return .label }
22
- let numbers = css.components(separatedBy: CharacterSet(charactersIn: "0123456789.").inverted).compactMap(Double.init)
23
- guard numbers.count >= 3 else { return .label }
24
- return UIColor(red: numbers[0] / 255, green: numbers[1] / 255, blue: numbers[2] / 255,
25
- alpha: numbers.count > 3 ? numbers[3] : 1)
26
- }
27
- }
@@ -1,135 +0,0 @@
1
- import Foundation
2
- import CoreGraphics
3
-
4
- enum ShellComponent: String, Decodable {
5
- case button = "ion-button", buttons = "ion-buttons"
6
- case backButton = "ion-back-button", menuButton = "ion-menu-button"
7
- case tabBar = "ion-tab-bar", segment = "ion-segment", fab = "ion-fab"
8
- }
9
-
10
- struct ShellSnapshot: Decodable {
11
- let revision: Int
12
- let transitionDuration: Double?
13
- let viewportWidth: Double
14
- let verticalBarFrame: ShellFrame?
15
- let verticalBarEdge: String?
16
- let controls: [ShellControl]
17
-
18
- var isValid: Bool {
19
- revision >= 0 && (transitionDuration.map { $0.isFinite && $0 >= 0 && $0 <= 500 } ?? true) && viewportWidth.isFinite && viewportWidth > 0 && controls.count <= 100 &&
20
- (verticalBarFrame.map { $0.isValid } ?? true) &&
21
- (verticalBarEdge == nil || verticalBarEdge == "left" || verticalBarEdge == "right") &&
22
- Set(controls.map(\.id)).count == controls.count && controls.allSatisfy(\.isValid)
23
- }
24
- }
25
-
26
- // Frames and content decode from the same flat wire object. Keeping them separate
27
- // lets FABs compare artwork without treating movement or visibility as new content.
28
- struct ShellFrame: Decodable, Equatable {
29
- let x: Double
30
- let y: Double
31
- let width: Double
32
- let height: Double
33
- var rect: CGRect { CGRect(x: x, y: y, width: width, height: height) }
34
- var isValid: Bool { [x, y, width, height].allSatisfy(\.isFinite) && width > 0 && height > 0 }
35
- }
36
-
37
- struct ShellControl: Decodable, Equatable {
38
- enum Placement: String, Decodable { case verticalBars = "vertical-bars" }
39
- enum ToolbarSlot: String, Decodable { case start, end }
40
- let id: String
41
- let kind: ShellComponent
42
- let placement: Placement?
43
- let toolbarSlot: ToolbarSlot?
44
- let frame: ShellFrame
45
- let items: [ShellItem]
46
- let dark: Bool
47
- let rtl: Bool
48
- let tabBarAnchor: ShellTabBar.Anchor?
49
- let search: ShellSearch?
50
-
51
- private enum CodingKeys: String, CodingKey { case id, kind, placement, toolbarSlot, items, dark, rtl, tabBarAnchor, search }
52
-
53
- init(from decoder: Decoder) throws {
54
- let values = try decoder.container(keyedBy: CodingKeys.self)
55
- id = try values.decode(String.self, forKey: .id)
56
- kind = try values.decode(ShellComponent.self, forKey: .kind)
57
- placement = try values.decodeIfPresent(Placement.self, forKey: .placement)
58
- toolbarSlot = try values.decodeIfPresent(ToolbarSlot.self, forKey: .toolbarSlot)
59
- frame = try ShellFrame(from: decoder)
60
- items = try values.decode([ShellItem].self, forKey: .items)
61
- dark = try values.decode(Bool.self, forKey: .dark)
62
- rtl = try values.decode(Bool.self, forKey: .rtl)
63
- tabBarAnchor = try values.decodeIfPresent(ShellTabBar.Anchor.self, forKey: .tabBarAnchor)
64
- search = try values.decodeIfPresent(ShellSearch.self, forKey: .search)
65
- }
66
-
67
- var isValid: Bool {
68
- ShellComponents.supported.contains(kind) && !id.isEmpty && frame.isValid && !items.isEmpty && items.count <= 30 &&
69
- (!ShellButton.kinds.contains(kind) || items.count == 1) &&
70
- (toolbarSlot == nil || (placement == .verticalBars && [.button, .buttons, .menuButton].contains(kind))) &&
71
- Set(items.map(\.id)).count == items.count && items.allSatisfy(\.isValid) &&
72
- (tabBarAnchor.map { kind == .tabBar && $0.isValid } ?? true) &&
73
- (search.map { kind == .tabBar && $0.isValid } ?? true)
74
- }
75
- }
76
-
77
- struct ShellItem: Decodable, Equatable {
78
- let frame: ShellFrame
79
- let content: ShellItemContent
80
- let visible: Bool?
81
- var id: String { content.id }
82
-
83
- private enum CodingKeys: String, CodingKey { case visible }
84
-
85
- init(from decoder: Decoder) throws {
86
- frame = try ShellFrame(from: decoder)
87
- content = try ShellItemContent(from: decoder)
88
- visible = try decoder.container(keyedBy: CodingKeys.self).decodeIfPresent(Bool.self, forKey: .visible)
89
- }
90
-
91
- var isValid: Bool { frame.isValid && content.isValid }
92
- }
93
-
94
- struct ShellBadge: Decodable, Equatable {
95
- let value: String
96
- let color: String
97
- let textColor: String
98
- }
99
-
100
- struct ShellItemContent: Decodable, Equatable {
101
- enum IconPosition: String, Decodable { case leading, trailing, top }
102
- enum ButtonFill: String, Decodable { case clear, solid, outline }
103
- let id: String
104
- var label: String
105
- let accessibilityLabel: String
106
- let disabled: Bool
107
- let selected: Bool
108
- let fontSize: Double
109
- let fontWeight: Double
110
- let color: String
111
- let buttonFill: ButtonFill?
112
- let backgroundColor: String?
113
- let borderColor: String?
114
- let borderWidth: CGFloat?
115
- let badge: ShellBadge?
116
- var icon: String?
117
- var iconWidth: Double?
118
- var iconHeight: Double?
119
- let iconPosition: IconPosition?
120
- let imagePadding: Double?
121
- let contentInsetLeading: Double?
122
- let contentInsetTrailing: Double?
123
- let iconTemplate: Bool?
124
- let closeIcon: String?
125
- let closeIconWidth: Double?
126
- let closeIconHeight: Double?
127
- let iconTransition: Double?
128
-
129
- var isValid: Bool {
130
- !id.isEmpty && fontSize.isFinite && fontSize >= 0 && fontWeight.isFinite &&
131
- [iconWidth, iconHeight, closeIconWidth, closeIconHeight].compactMap { $0 }.allSatisfy { $0.isFinite && $0 > 0 } &&
132
- [imagePadding, contentInsetLeading, contentInsetTrailing].compactMap { $0 }.allSatisfy { $0.isFinite } &&
133
- (iconTransition.map { $0.isFinite && $0 >= 0 } ?? true)
134
- }
135
- }
@@ -1,52 +0,0 @@
1
- import UIKit
2
-
3
- /// Reads public accessibility geometry after the system has laid out its rail.
4
- /// IDs bind measurements to current DOM controls without private class names.
5
- /// Compact square ancestors approximate the system button surface; this remains
6
- /// a layout heuristic and invalid measurements fall back to Web CSS.
7
- enum ShellVerticalBarsGeometry {
8
- static func measure(in root: UIView, relativeTo webView: UIView, ids: Set<String>) -> [String: CGRect] {
9
- guard let window = root.window, window === webView.window else { return [:] }
10
- root.layoutIfNeeded()
11
- var result: [String: CGRect] = [:]
12
- var visited = Set<ObjectIdentifier>()
13
- func visit(_ object: NSObject, depth: Int) {
14
- guard depth < 40, visited.count < 2000, visited.insert(ObjectIdentifier(object)).inserted else { return }
15
- if let id = (object as? UIView)?.accessibilityIdentifier ?? (object as? UIAccessibilityElement)?.accessibilityIdentifier, ids.contains(id) {
16
- let screenFrame = object.accessibilityFrame
17
- let frame = (object as? UIView).map { view -> CGRect in
18
- var surface = view
19
- var ancestor = view.superview
20
- while let current = ancestor, current !== root,
21
- current.bounds.width < 100, current.bounds.height < 100 {
22
- if current.bounds.width >= max(surface.bounds.width, 1), current.bounds.height >= max(surface.bounds.height, 1),
23
- abs(current.bounds.width - current.bounds.height) < 1 { surface = current }
24
- ancestor = current.superview
25
- }
26
- return surface.convert(surface.bounds, to: webView)
27
- } ?? webView.convert(screenFrame, from: window.screen.coordinateSpace)
28
- if !frame.isNull, !frame.isInfinite, frame.width > 0, frame.height > 0,
29
- webView.bounds.intersects(frame), frame.width < webView.bounds.width * 0.75 {
30
- // UIKit may expose a label and its larger button with the same ID.
31
- if result[id] == nil || frame.width * frame.height > result[id]!.width * result[id]!.height {
32
- result[id] = frame
33
- }
34
- }
35
- }
36
- if let view = object as? UIView { view.subviews.forEach { visit($0, depth: depth + 1) } }
37
- if object === webView { return }
38
- if let elements = object.accessibilityElements {
39
- elements.compactMap { $0 as? NSObject }.forEach { visit($0, depth: depth + 1) }
40
- } else {
41
- let count = object.accessibilityElementCount()
42
- if count > 0 && count < 200 {
43
- for index in 0..<count {
44
- if let element = object.accessibilityElement(at: index) as? NSObject { visit(element, depth: depth + 1) }
45
- }
46
- }
47
- }
48
- }
49
- visit(root, depth: 0)
50
- return result
51
- }
52
- }