@swiftbrowser/swift 0.1.0 → 0.3.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 (393) hide show
  1. package/Package.swift +200 -9
  2. package/README.md +7 -9
  3. package/Sources/AVFoundation/AVFoundation.swift +131 -0
  4. package/Sources/AVFoundation/Asset.swift +487 -0
  5. package/Sources/AVFoundation/AudioSession.swift +144 -0
  6. package/Sources/AVFoundation/CMTime.swift +303 -0
  7. package/Sources/AVFoundation/Capture.swift +688 -0
  8. package/Sources/AVFoundation/Player.swift +749 -0
  9. package/Sources/AVFoundation/PlayerView.swift +117 -0
  10. package/Sources/AVKit/AVKit.swift +132 -0
  11. package/Sources/Accelerate/Accelerate.swift +122 -0
  12. package/Sources/AppIntents/AppIntents.swift +144 -0
  13. package/Sources/AppIntents/Parameters.swift +220 -0
  14. package/Sources/AppIntents/Shortcuts.swift +159 -0
  15. package/Sources/AudioToolbox/AudioToolbox.swift +55 -0
  16. package/Sources/AuthenticationServices/AuthenticationServices.swift +257 -0
  17. package/Sources/AuthenticationServices/Authorization.swift +251 -0
  18. package/Sources/Charts/Charts.swift +0 -4
  19. package/Sources/Combine/AnyCancellable.swift +65 -0
  20. package/Sources/Combine/AnyPublisher.swift +148 -0
  21. package/Sources/Combine/AnySubscriber.swift +230 -0
  22. package/Sources/Combine/Cancellable.swift +35 -0
  23. package/Sources/Combine/Codable.swift +29 -0
  24. package/Sources/Combine/CombineIdentifier.swift +45 -0
  25. package/Sources/Combine/Concurrency/ConcurrencyHelpers.swift +24 -0
  26. package/Sources/Combine/Concurrency/Future+Concurrency.swift +136 -0
  27. package/Sources/Combine/Concurrency/GENERATED-Publisher+Concurrency.swift +397 -0
  28. package/Sources/Combine/CurrentValueSubject.swift +252 -0
  29. package/Sources/Combine/CustomCombineIdentifierConvertible.swift +28 -0
  30. package/Sources/Combine/Future.swift +228 -0
  31. package/Sources/Combine/GENERATED-RootProtocols.swift +426 -0
  32. package/Sources/Combine/Helpers/ConduitBase.swift +40 -0
  33. package/Sources/Combine/Helpers/ConduitList.swift +59 -0
  34. package/Sources/Combine/Helpers/DebugHook.swift +177 -0
  35. package/Sources/Combine/Helpers/FilterProducer.swift +174 -0
  36. package/Sources/Combine/Helpers/Locking.swift +30 -0
  37. package/Sources/Combine/Helpers/PartialCompletion.swift +26 -0
  38. package/Sources/Combine/Helpers/ReduceProducer.swift +249 -0
  39. package/Sources/Combine/Helpers/SubjectSubscriber.swift +96 -0
  40. package/Sources/Combine/Helpers/SubscriberTap.swift +69 -0
  41. package/Sources/Combine/Helpers/SubscriptionStatus.swift +33 -0
  42. package/Sources/Combine/Helpers/SubscriptionTap.swift +33 -0
  43. package/Sources/Combine/Helpers/Utils.swift +30 -0
  44. package/Sources/Combine/Helpers/Violations.swift +33 -0
  45. package/Sources/Combine/ImmediateScheduler.swift +178 -0
  46. package/Sources/Combine/LICENSE +21 -0
  47. package/Sources/Combine/NotificationPublisher.swift +48 -0
  48. package/Sources/Combine/ObjectWillChange.swift +46 -0
  49. package/Sources/Combine/PassthroughSubject.swift +222 -0
  50. package/Sources/Combine/Publisher+Subscribe.swift +53 -0
  51. package/Sources/Combine/Publishers/Deferred.swift +48 -0
  52. package/Sources/Combine/Publishers/Empty.swift +54 -0
  53. package/Sources/Combine/Publishers/Fail.swift +42 -0
  54. package/Sources/Combine/Publishers/GENERATED-Publishers.Catch.swift +673 -0
  55. package/Sources/Combine/Publishers/GENERATED-Publishers.Encode.swift +388 -0
  56. package/Sources/Combine/Publishers/GENERATED-Publishers.MapKeyPath.swift +384 -0
  57. package/Sources/Combine/Publishers/Just.swift +313 -0
  58. package/Sources/Combine/Publishers/Optional.Publisher.swift +315 -0
  59. package/Sources/Combine/Publishers/Publishers.AllSatisfy.swift +217 -0
  60. package/Sources/Combine/Publishers/Publishers.AssertNoFailure.swift +158 -0
  61. package/Sources/Combine/Publishers/Publishers.Autoconnect.swift +193 -0
  62. package/Sources/Combine/Publishers/Publishers.Breakpoint.swift +231 -0
  63. package/Sources/Combine/Publishers/Publishers.Buffer.swift +356 -0
  64. package/Sources/Combine/Publishers/Publishers.Collect.swift +102 -0
  65. package/Sources/Combine/Publishers/Publishers.CollectByCount.swift +195 -0
  66. package/Sources/Combine/Publishers/Publishers.CompactMap.swift +241 -0
  67. package/Sources/Combine/Publishers/Publishers.Comparison.swift +371 -0
  68. package/Sources/Combine/Publishers/Publishers.Concatenate.swift +474 -0
  69. package/Sources/Combine/Publishers/Publishers.Contains.swift +293 -0
  70. package/Sources/Combine/Publishers/Publishers.Count.swift +87 -0
  71. package/Sources/Combine/Publishers/Publishers.Debounce.swift +332 -0
  72. package/Sources/Combine/Publishers/Publishers.Delay.swift +269 -0
  73. package/Sources/Combine/Publishers/Publishers.Drop.swift +155 -0
  74. package/Sources/Combine/Publishers/Publishers.DropUntilOutput.swift +283 -0
  75. package/Sources/Combine/Publishers/Publishers.DropWhile.swift +378 -0
  76. package/Sources/Combine/Publishers/Publishers.Filter.swift +248 -0
  77. package/Sources/Combine/Publishers/Publishers.First.swift +261 -0
  78. package/Sources/Combine/Publishers/Publishers.FlatMap.swift +533 -0
  79. package/Sources/Combine/Publishers/Publishers.HandleEvents.swift +260 -0
  80. package/Sources/Combine/Publishers/Publishers.IgnoreOutput.swift +109 -0
  81. package/Sources/Combine/Publishers/Publishers.Last.swift +252 -0
  82. package/Sources/Combine/Publishers/Publishers.MakeConnectable.swift +82 -0
  83. package/Sources/Combine/Publishers/Publishers.Map.swift +351 -0
  84. package/Sources/Combine/Publishers/Publishers.MapError.swift +140 -0
  85. package/Sources/Combine/Publishers/Publishers.MeasureInterval.swift +187 -0
  86. package/Sources/Combine/Publishers/Publishers.Multicast.swift +284 -0
  87. package/Sources/Combine/Publishers/Publishers.Output.swift +241 -0
  88. package/Sources/Combine/Publishers/Publishers.PrefixUntilOutput.swift +197 -0
  89. package/Sources/Combine/Publishers/Publishers.PrefixWhile.swift +176 -0
  90. package/Sources/Combine/Publishers/Publishers.Print.swift +206 -0
  91. package/Sources/Combine/Publishers/Publishers.ReceiveOn.swift +227 -0
  92. package/Sources/Combine/Publishers/Publishers.Reduce.swift +204 -0
  93. package/Sources/Combine/Publishers/Publishers.RemoveDuplicates.swift +264 -0
  94. package/Sources/Combine/Publishers/Publishers.ReplaceEmpty.swift +201 -0
  95. package/Sources/Combine/Publishers/Publishers.ReplaceError.swift +211 -0
  96. package/Sources/Combine/Publishers/Publishers.Retry.swift +274 -0
  97. package/Sources/Combine/Publishers/Publishers.Scan.swift +331 -0
  98. package/Sources/Combine/Publishers/Publishers.Sequence.swift +437 -0
  99. package/Sources/Combine/Publishers/Publishers.SetFailureType.swift +120 -0
  100. package/Sources/Combine/Publishers/Publishers.Share.swift +104 -0
  101. package/Sources/Combine/Publishers/Publishers.SubscribeOn.swift +197 -0
  102. package/Sources/Combine/Publishers/Publishers.SwitchToLatest.swift +444 -0
  103. package/Sources/Combine/Publishers/Publishers.Throttle.swift +359 -0
  104. package/Sources/Combine/Publishers/Publishers.Timeout.swift +301 -0
  105. package/Sources/Combine/Publishers/Publishers.Zip.swift +720 -0
  106. package/Sources/Combine/Publishers/Publishers.swift +13 -0
  107. package/Sources/Combine/Publishers/Record.swift +237 -0
  108. package/Sources/Combine/Publishers/Result.Publisher.swift +337 -0
  109. package/Sources/Combine/Result.swift +46 -0
  110. package/Sources/Combine/Scheduler.swift +89 -0
  111. package/Sources/Combine/Schedulers.swift +211 -0
  112. package/Sources/Combine/Subject+Void.swift +17 -0
  113. package/Sources/Combine/Subscriber+Void.swift +20 -0
  114. package/Sources/Combine/Subscribers/Subscribers.Assign.swift +175 -0
  115. package/Sources/Combine/Subscribers/Subscribers.Completion.swift +90 -0
  116. package/Sources/Combine/Subscribers/Subscribers.Demand.swift +476 -0
  117. package/Sources/Combine/Subscribers/Subscribers.Sink.swift +206 -0
  118. package/Sources/Combine/Subscribers/Subscribers.swift +9 -0
  119. package/Sources/Combine/Subscription.swift +22 -0
  120. package/Sources/Combine/Subscriptions.swift +42 -0
  121. package/Sources/Combine/SwiftUIObservation.swift +10 -0
  122. package/Sources/Combine/_Introspection.swift +107 -0
  123. package/Sources/CommonCrypto/CommonCrypto.swift +80 -0
  124. package/Sources/Compression/Compression.swift +365 -0
  125. package/Sources/Compression/Deflate.swift +407 -0
  126. package/Sources/Compression/Zlib.swift +179 -0
  127. package/Sources/CoreFoundationTypes/CoreFoundationTypes.swift +101 -0
  128. package/Sources/CoreGraphics/CoreGraphics.swift +411 -0
  129. package/Sources/CoreGraphics/ProjectionTransforms.swift +28 -0
  130. package/Sources/CoreHaptics/CoreHaptics.swift +399 -0
  131. package/Sources/CoreImage/CIColor.swift +41 -0
  132. package/Sources/CoreImage/CIContext.swift +31 -0
  133. package/Sources/CoreImage/CIDetector.swift +57 -0
  134. package/Sources/CoreImage/CIFilter.swift +243 -0
  135. package/Sources/CoreImage/CIFilterBuiltins.swift +136 -0
  136. package/Sources/CoreImage/CIImage.swift +162 -0
  137. package/Sources/CoreImage/QRCode.swift +393 -0
  138. package/Sources/CoreLocation/Coordinate.swift +30 -0
  139. package/Sources/CoreLocation/Location.swift +65 -0
  140. package/Sources/CoreML/Evaluation.swift +414 -0
  141. package/Sources/CoreML/FeatureValue.swift +266 -0
  142. package/Sources/CoreML/Model.swift +269 -0
  143. package/Sources/CoreML/MultiArray.swift +92 -0
  144. package/Sources/CoreML/Specification.swift +233 -0
  145. package/Sources/CoreText/CoreText.swift +64 -0
  146. package/Sources/CoreTransferable/CoreTransferable.swift +606 -0
  147. package/Sources/CryptoKit/ChaChaPoly.swift +258 -0
  148. package/Sources/CryptoKit/CryptoKit.swift +375 -0
  149. package/Sources/CryptoKit/Curve25519.swift +317 -0
  150. package/Sources/CryptoKit/P256.swift +321 -0
  151. package/Sources/FoundationBridge/AppStorage.swift +165 -0
  152. package/Sources/FoundationBridge/AssetResources.swift +90 -0
  153. package/Sources/FoundationBridge/AsyncBytes.swift +128 -0
  154. package/Sources/FoundationBridge/AsyncImage.swift +116 -0
  155. package/Sources/FoundationBridge/AttributedStrings.swift +16 -0
  156. package/Sources/{_FoundationCommon → FoundationBridge}/Bundle.swift +41 -4
  157. package/Sources/FoundationBridge/CombineExports.swift +8 -0
  158. package/Sources/FoundationBridge/CoreFoundation.swift +77 -0
  159. package/Sources/FoundationBridge/Dispatch.swift +458 -0
  160. package/Sources/FoundationBridge/EnvironmentFoundation.swift +41 -0
  161. package/Sources/FoundationBridge/FileImporter.swift +128 -0
  162. package/Sources/FoundationBridge/FileSystem.swift +68 -0
  163. package/Sources/FoundationBridge/FoundationBridge.swift +24 -72
  164. package/Sources/FoundationBridge/HostResolution.swift +31 -0
  165. package/Sources/FoundationBridge/ItemProvider.swift +655 -0
  166. package/Sources/FoundationBridge/KeyValueObserving.swift +138 -0
  167. package/Sources/FoundationBridge/Links.swift +127 -0
  168. package/Sources/FoundationBridge/ListFormatter.swift +32 -0
  169. package/Sources/FoundationBridge/Localization.swift +149 -0
  170. package/Sources/FoundationBridge/Locks.swift +50 -0
  171. package/Sources/FoundationBridge/Markdown.swift +527 -0
  172. package/Sources/FoundationBridge/MeasurementFormat.swift +95 -0
  173. package/Sources/FoundationBridge/NotificationCenter.swift +154 -0
  174. package/Sources/FoundationBridge/OpenURLHandlers.swift +60 -0
  175. package/Sources/FoundationBridge/Operation.swift +195 -0
  176. package/Sources/FoundationBridge/ProcessInfo.swift +32 -0
  177. package/Sources/FoundationBridge/RelativeDateTimeFormatter.swift +136 -0
  178. package/Sources/FoundationBridge/SceneStorage.swift +78 -0
  179. package/Sources/FoundationBridge/SecurityExports.swift +8 -0
  180. package/Sources/FoundationBridge/Selector.swift +226 -0
  181. package/Sources/FoundationBridge/StringComparators.swift +17 -0
  182. package/Sources/FoundationBridge/Table.swift +211 -0
  183. package/Sources/FoundationBridge/TextAttributes.swift +42 -0
  184. package/Sources/FoundationBridge/Timer.swift +6 -1
  185. package/Sources/FoundationBridge/URLSession.swift +718 -0
  186. package/Sources/{_FoundationCommon → FoundationBridge}/UserDefaults.swift +17 -4
  187. package/Sources/FoundationBridge/WebAuthRedirects.swift +227 -0
  188. package/Sources/FoundationBridge/WebSocket.swift +187 -0
  189. package/Sources/FoundationModels/FoundationModels.swift +1098 -0
  190. package/Sources/ImageIO/GIF.swift +72 -0
  191. package/Sources/ImageIO/ImageIO.swift +248 -0
  192. package/Sources/ImageIO/ImageTypes.swift +28 -0
  193. package/Sources/LinkPresentation/LinkPresentation.swift +249 -0
  194. package/Sources/LocalAuthentication/LAContext.swift +86 -0
  195. package/Sources/MapKit/Geometry.swift +223 -0
  196. package/Sources/MapKit/Map.swift +638 -0
  197. package/Sources/MapKit/MapView.swift +229 -0
  198. package/Sources/MobileCoreServices/MobileCoreServices.swift +28 -0
  199. package/Sources/NaturalLanguage/LanguageIdentifier.swift +317 -0
  200. package/Sources/NaturalLanguage/NaturalLanguage.swift +135 -0
  201. package/Sources/OSLog/OSLog.swift +6 -0
  202. package/Sources/Photos/Photos.swift +572 -0
  203. package/Sources/PhotosUI/PhotosPicker.swift +248 -0
  204. package/Sources/PhotosUI/PhotosPickerItem.swift +261 -0
  205. package/Sources/QuickLook/QuickLook.swift +168 -0
  206. package/Sources/SafariServices/SafariServices.swift +106 -0
  207. package/Sources/Security/Security.swift +332 -0
  208. package/Sources/StoreKit/AppStore.swift +207 -0
  209. package/Sources/StoreKit/Products.swift +280 -0
  210. package/Sources/StoreKit/RequestReview.swift +28 -0
  211. package/Sources/StoreKit/StoreKit1.swift +486 -0
  212. package/Sources/StoreKit/StoreViews.swift +428 -0
  213. package/Sources/StoreKit/Transactions.swift +197 -0
  214. package/Sources/SwiftData/Macros.swift +58 -0
  215. package/Sources/SwiftData/ModelActor.swift +38 -0
  216. package/Sources/SwiftData/ModelContainer.swift +129 -0
  217. package/Sources/SwiftData/ModelContext.swift +451 -0
  218. package/Sources/SwiftData/PersistentModel.swift +238 -0
  219. package/Sources/SwiftData/Properties.swift +261 -0
  220. package/Sources/SwiftData/Query.swift +256 -0
  221. package/Sources/SwiftData/Schema.swift +192 -0
  222. package/Sources/SwiftData/Store.swift +146 -0
  223. package/Sources/SwiftData/StringPredicates.swift +125 -0
  224. package/Sources/SwiftDataMacros/EntryMacro.swift +82 -0
  225. package/Sources/SwiftDataMacros/GenerableMacro.swift +183 -0
  226. package/Sources/SwiftDataMacros/ModelActorMacro.swift +46 -0
  227. package/Sources/SwiftDataMacros/ModelMacro.swift +260 -0
  228. package/Sources/SwiftDataMacros/Plugin.swift +18 -0
  229. package/Sources/SwiftDataMacros/PredicateMacro.swift +1244 -0
  230. package/Sources/SwiftUI/API/Accessibility.swift +55 -6
  231. package/Sources/SwiftUI/API/AccessibilityExtras.swift +33 -0
  232. package/Sources/SwiftUI/API/AccessibilityFocus.swift +63 -0
  233. package/Sources/SwiftUI/API/Alert.swift +1 -1
  234. package/Sources/SwiftUI/API/AlignmentGuides.swift +68 -0
  235. package/Sources/SwiftUI/API/Animation.swift +37 -5
  236. package/Sources/SwiftUI/API/App.swift +31 -1
  237. package/Sources/SwiftUI/API/BindingCollections.swift +35 -0
  238. package/Sources/SwiftUI/API/BindingForEach.swift +27 -0
  239. package/Sources/SwiftUI/API/Bitmaps.swift +254 -0
  240. package/Sources/SwiftUI/API/ButtonStyles.swift +96 -0
  241. package/Sources/SwiftUI/API/Canvas.swift +337 -0
  242. package/Sources/SwiftUI/API/Color.swift +104 -28
  243. package/Sources/SwiftUI/API/ColorEffects.swift +52 -0
  244. package/Sources/SwiftUI/API/ColorPickerAndLabeledContent.swift +138 -0
  245. package/Sources/SwiftUI/API/ColorSpaces.swift +161 -0
  246. package/Sources/SwiftUI/API/Commands.swift +106 -0
  247. package/Sources/SwiftUI/API/ContainerBackground.swift +28 -0
  248. package/Sources/SwiftUI/API/ContentShape.swift +33 -0
  249. package/Sources/SwiftUI/API/ContentSizeCategory.swift +35 -0
  250. package/Sources/SwiftUI/API/ContentUnavailable.swift +101 -0
  251. package/Sources/SwiftUI/API/Controls.swift +59 -27
  252. package/Sources/SwiftUI/API/CustomLayout.swift +49 -10
  253. package/Sources/SwiftUI/API/CustomToolbarContent.swift +46 -0
  254. package/Sources/SwiftUI/API/Decorations.swift +1 -1
  255. package/Sources/SwiftUI/API/EditMode.swift +25 -82
  256. package/Sources/SwiftUI/API/Effects.swift +125 -10
  257. package/Sources/SwiftUI/API/Entry.swift +8 -0
  258. package/Sources/SwiftUI/API/Environment.swift +100 -9
  259. package/Sources/SwiftUI/API/EnvironmentSendable.swift +6 -0
  260. package/Sources/SwiftUI/API/Focus.swift +11 -3
  261. package/Sources/SwiftUI/API/Gauge.swift +26 -10
  262. package/Sources/SwiftUI/API/GeometryEffects.swift +232 -0
  263. package/Sources/SwiftUI/API/GeometryReader.swift +13 -2
  264. package/Sources/SwiftUI/API/GestureState.swift +65 -0
  265. package/Sources/SwiftUI/API/Gestures.swift +1 -1
  266. package/Sources/SwiftUI/API/Glass.swift +127 -0
  267. package/Sources/SwiftUI/API/Grid.swift +1 -1
  268. package/Sources/SwiftUI/API/GroupBoxAndControlGroup.swift +130 -0
  269. package/Sources/SwiftUI/API/KeyPress.swift +54 -0
  270. package/Sources/SwiftUI/API/LabelStyle.swift +13 -6
  271. package/Sources/SwiftUI/API/Layout.swift +71 -12
  272. package/Sources/SwiftUI/API/LazyStacks.swift +49 -0
  273. package/Sources/SwiftUI/API/LegacyAlert.swift +97 -0
  274. package/Sources/SwiftUI/API/Lists.swift +61 -6
  275. package/Sources/SwiftUI/API/Menu.swift +8 -1
  276. package/Sources/SwiftUI/API/Modifiers.swift +88 -12
  277. package/Sources/SwiftUI/API/MoreModifiers.swift +100 -0
  278. package/Sources/SwiftUI/API/Namespace.swift +44 -0
  279. package/Sources/SwiftUI/API/Navigation.swift +24 -9
  280. package/Sources/SwiftUI/API/NavigationDestinationBindings.swift +55 -0
  281. package/Sources/SwiftUI/API/ObservableObject.swift +16 -2
  282. package/Sources/SwiftUI/API/Path.swift +543 -0
  283. package/Sources/SwiftUI/API/Phase8Controls.swift +48 -31
  284. package/Sources/SwiftUI/API/PhaseAnimator.swift +130 -0
  285. package/Sources/SwiftUI/API/Picker.swift +14 -4
  286. package/Sources/SwiftUI/API/PointerAndKeyboard.swift +77 -0
  287. package/Sources/SwiftUI/API/Popover.swift +41 -0
  288. package/Sources/SwiftUI/API/Preferences.swift +262 -0
  289. package/Sources/SwiftUI/API/Presentation.swift +17 -4
  290. package/Sources/SwiftUI/API/PresentationExtras.swift +60 -0
  291. package/Sources/SwiftUI/API/Primitives.swift +168 -39
  292. package/Sources/SwiftUI/API/Publishers.swift +4 -4
  293. package/Sources/SwiftUI/API/Redaction.swift +75 -0
  294. package/Sources/SwiftUI/API/RowActions.swift +3 -2
  295. package/Sources/SwiftUI/API/Scenes.swift +131 -0
  296. package/Sources/SwiftUI/API/ScrollModifiers.swift +132 -0
  297. package/Sources/SwiftUI/API/ScrollTargets.swift +3 -3
  298. package/Sources/SwiftUI/API/ScrollViewReader.swift +144 -0
  299. package/Sources/SwiftUI/API/SectionSupplements.swift +86 -0
  300. package/Sources/SwiftUI/API/Shape.swift +214 -0
  301. package/Sources/SwiftUI/API/ShapeStyle.swift +187 -8
  302. package/Sources/SwiftUI/API/ShapeTrim.swift +78 -0
  303. package/Sources/SwiftUI/API/Sharing.swift +193 -0
  304. package/Sources/SwiftUI/API/StackLayouts.swift +199 -0
  305. package/Sources/SwiftUI/API/State.swift +18 -1
  306. package/Sources/SwiftUI/API/StatusGaps.swift +121 -0
  307. package/Sources/SwiftUI/API/SymbolEffects.swift +203 -0
  308. package/Sources/SwiftUI/API/TabContent.swift +841 -0
  309. package/Sources/SwiftUI/API/Table.swift +382 -0
  310. package/Sources/SwiftUI/API/Tabs.swift +148 -4
  311. package/Sources/SwiftUI/API/Tasks.swift +2 -2
  312. package/Sources/SwiftUI/API/Text.swift +386 -0
  313. package/Sources/SwiftUI/API/TextDates.swift +61 -0
  314. package/Sources/SwiftUI/API/TextDecorations.swift +52 -0
  315. package/Sources/SwiftUI/API/TextFieldAxis.swift +103 -0
  316. package/Sources/SwiftUI/API/TextFieldCommit.swift +52 -0
  317. package/Sources/SwiftUI/API/Toolbar.swift +52 -16
  318. package/Sources/SwiftUI/API/Transfer.swift +145 -0
  319. package/Sources/SwiftUI/API/UnevenRoundedRectangle.swift +66 -0
  320. package/Sources/SwiftUI/API/Video.swift +64 -0
  321. package/Sources/SwiftUI/API/View.swift +46 -2
  322. package/Sources/SwiftUI/API/ViewModifier.swift +4 -1
  323. package/Sources/SwiftUI/API/VisualEffect.swift +74 -1
  324. package/Sources/SwiftUI/API/Windows.swift +25 -0
  325. package/Sources/SwiftUI/Charts/Axis.swift +7 -1
  326. package/Sources/SwiftUI/Charts/AxisFormatsAndRanges.swift +68 -0
  327. package/Sources/SwiftUI/Charts/Chart.swift +17 -3
  328. package/Sources/SwiftUI/Charts/ChartProxy.swift +57 -0
  329. package/Sources/SwiftUI/Localization/Localization.swift +524 -0
  330. package/Sources/SwiftUI/Localization/PluralRules.swift +127 -0
  331. package/Sources/SwiftUI/Runtime/Executor.swift +2 -0
  332. package/Sources/SwiftUI/Runtime/Exports.swift +37 -1
  333. package/Sources/SwiftUI/Runtime/ImageSource.swift +310 -0
  334. package/Sources/SwiftUI/Runtime/JSON.swift +76 -4
  335. package/Sources/SwiftUI/Runtime/LazyRows.swift +99 -0
  336. package/Sources/SwiftUI/Runtime/Measurements.swift +140 -0
  337. package/Sources/SwiftUI/Runtime/Ops.swift +58 -12
  338. package/Sources/SwiftUI/Runtime/Reconciler.swift +10 -1
  339. package/Sources/SwiftUI/Runtime/Replay.swift +382 -0
  340. package/Sources/SwiftUI/Runtime/Runtime.swift +977 -198
  341. package/Sources/SwiftUI/Runtime/Sameness.swift +152 -0
  342. package/Sources/SwiftUI/Runtime/ViewPath.swift +89 -0
  343. package/Sources/SwiftUI/Runtime/ViewTraits.swift +175 -0
  344. package/Sources/SwiftUI/Runtime/WebAuthHeadless.swift +49 -0
  345. package/Sources/Symbols/Symbols.swift +25 -0
  346. package/Sources/Translation/Translation.swift +423 -0
  347. package/Sources/UIKit/Activity.swift +213 -0
  348. package/Sources/UIKit/Alerts.swift +69 -0
  349. package/Sources/UIKit/AppDelegate.swift +222 -0
  350. package/Sources/UIKit/Appearance.swift +84 -0
  351. package/Sources/UIKit/ColorConversions.swift +70 -0
  352. package/Sources/UIKit/Controls.swift +225 -0
  353. package/Sources/UIKit/Device.swift +200 -0
  354. package/Sources/UIKit/Feedback.swift +65 -0
  355. package/Sources/UIKit/Font.swift +307 -0
  356. package/Sources/UIKit/Graphics.swift +204 -0
  357. package/Sources/UIKit/HostingController.swift +185 -0
  358. package/Sources/UIKit/Identity.swift +24 -0
  359. package/Sources/UIKit/Introspectable.swift +510 -0
  360. package/Sources/UIKit/Pasteboard.swift +363 -0
  361. package/Sources/UIKit/PhotoAlbum.swift +81 -0
  362. package/Sources/UIKit/RemoteNotifications.swift +15 -0
  363. package/Sources/UIKit/Representable.swift +288 -0
  364. package/Sources/UIKit/SafeArea.swift +12 -0
  365. package/Sources/UIKit/SceneDelegates.swift +132 -0
  366. package/Sources/UIKit/Scenes.swift +40 -0
  367. package/Sources/UIKit/ScrollView.swift +157 -0
  368. package/Sources/UIKit/ShortcutItems.swift +67 -0
  369. package/Sources/UIKit/SymbolConfiguration.swift +124 -0
  370. package/Sources/UIKit/TargetAction.swift +123 -0
  371. package/Sources/UIKit/TextPaste.swift +161 -0
  372. package/Sources/UIKit/TextView.swift +538 -0
  373. package/Sources/UIKit/UIKit.swift +292 -0
  374. package/Sources/UIKit/UIView.swift +855 -0
  375. package/Sources/UIKit/ViewTranslation.swift +311 -0
  376. package/Sources/UniformTypeIdentifiers/UTType.swift +196 -0
  377. package/Sources/UserNotifications/Content.swift +370 -0
  378. package/Sources/UserNotifications/NotificationCenter.swift +309 -0
  379. package/Sources/WeatherKit/WeatherKit.swift +228 -0
  380. package/Sources/WebKit/WebKit.swift +245 -0
  381. package/Sources/os/os.swift +376 -0
  382. package/Sources/zlib/zlib.swift +6 -0
  383. package/index.d.ts +1 -1
  384. package/index.mjs +2 -2
  385. package/manifest.d.mts +2 -0
  386. package/manifest.mjs +41 -0
  387. package/package.json +5 -3
  388. package/sync-sources.mjs +21 -7
  389. package/Sources/FoundationLite/Common.swift +0 -3
  390. package/Sources/FoundationLite/FoundationLite.swift +0 -171
  391. package/Sources/FoundationLite/Interaction.swift +0 -146
  392. package/Sources/FoundationLite/Timer.swift +0 -48
  393. package/Sources/_FoundationCommon/_FoundationCommon.swift +0 -10
@@ -0,0 +1,140 @@
1
+ // Views measured by the page for code that asks for a view's size
2
+ // synchronously: `UIHostingController.sizeThatFits(in:)` (UIKit), which
3
+ // packages call to size SwiftUI views before laying them out themselves
4
+ // (WrappingHStack measures each item this way to break its lines). Swift has
5
+ // no text metrics, so the answer comes from the page, one render late:
6
+ //
7
+ // 1. The first ask for a view finds no measurement: the answer is nil (the
8
+ // hosting controller reports a zero size) and the view joins the pending
9
+ // list.
10
+ // 2. The render that asked also renders the pending views, off screen: as
11
+ // the children of a `layout` element of their own at the root, transparent
12
+ // and not hit-testable. The page measures a `layout` element's children
13
+ // (minimum, ideal and maximum size) and sends them in a `layout` event,
14
+ // as it does for a custom `Layout` (CustomLayout.swift).
15
+ // 3. The event fills the cache and asks for a render in which no body is
16
+ // skipped as unchanged, so the code that measured runs again and finds
17
+ // the sizes (the library keeps them in objects of its own, which only a
18
+ // body that re-runs makes again).
19
+ //
20
+ // A view is known by its reflected description (`String(reflecting:)`): its
21
+ // type and stored values, as SwiftUI's own diffing sees them. Content a body
22
+ // reads from elsewhere (an observable object) is not part of it, so such a
23
+ // view keeps its first measurement. Views are measured in the root's
24
+ // environment, as Apple's hosting controller measures them in its own,
25
+ // without the environment of the view that asks. A view whose description
26
+ // changes on every render (one that stores a new UUID each time) would be
27
+ // measured forever: after a few rounds in a row that only found new views,
28
+ // measurements are still taken but no longer re-render.
29
+
30
+ /// A view's sizes as the page measured them: for a zero, unspecified and infinite proposal.
31
+ struct _MeasuredSizes: Equatable {
32
+ var min: CGSize
33
+ var ideal: CGSize
34
+ var max: CGSize
35
+
36
+ /// The size for `proposal`, as `LayoutSubview.sizeThatFits` answers it.
37
+ func fitting(_ proposal: ProposedViewSize) -> CGSize {
38
+ CGSize(
39
+ width: LayoutSubview.fit(proposal.width, min: min.width, ideal: ideal.width, max: max.width),
40
+ height: LayoutSubview.fit(proposal.height, min: min.height, ideal: ideal.height, max: max.height)
41
+ )
42
+ }
43
+ }
44
+
45
+ /// The page's measurements of views, by description, and the views waiting for one.
46
+ @MainActor
47
+ public final class _ViewMeasurements {
48
+ public static let shared = _ViewMeasurements()
49
+
50
+ private var sizes: [String: _MeasuredSizes] = [:]
51
+ /// Waiting views, in the order the measuring element renders them.
52
+ private(set) var pending: [(key: String, view: AnyView)] = []
53
+ private var pendingKeys: Set<String> = []
54
+ /// Rounds in a row in which measurements arrived and the render that
55
+ /// followed asked for new views again.
56
+ private var rounds = 0
57
+ static let maxRounds = 4
58
+ /// The runtime that rendered last: the one to render again.
59
+ weak var runtime: _Runtime?
60
+
61
+ init() {}
62
+
63
+ /// The size `view` takes for `proposal`, from the page's measurement;
64
+ /// nil until the page has measured it, and the view is then queued.
65
+ public func size<V: View>(of view: V, fitting proposal: ProposedViewSize) -> CGSize? {
66
+ let key = String(reflecting: view)
67
+ if let measured = sizes[key] { return measured.fitting(proposal) }
68
+ if pendingKeys.insert(key).inserted {
69
+ pending.append((key, AnyView(view)))
70
+ runtime?._scheduleRender()
71
+ }
72
+ return nil
73
+ }
74
+
75
+ /// Records the sizes of the pending views the page measured (`keys`, in order).
76
+ func record(_ measured: [(key: String, sizes: _MeasuredSizes)]) {
77
+ var added = false
78
+ for (key, value) in measured where pendingKeys.contains(key) {
79
+ sizes[key] = value
80
+ pendingKeys.remove(key)
81
+ added = true
82
+ }
83
+ guard added else { return }
84
+ pending.removeAll { sizes[$0.key] != nil }
85
+ rounds += 1
86
+ if rounds <= Self.maxRounds { runtime?._renderEverything() }
87
+ }
88
+
89
+ /// Called after a render: a render that asked for nothing new ends the rounds.
90
+ func renderFinished() {
91
+ if pending.isEmpty { rounds = 0 }
92
+ }
93
+
94
+ /// Forgets everything (tests).
95
+ func reset() {
96
+ sizes = [:]
97
+ pending = []
98
+ pendingKeys = []
99
+ rounds = 0
100
+ }
101
+ }
102
+
103
+ /// The off-screen element that has the pending views measured: a `layout`
104
+ /// element whose children are the views, transparent and not hit-testable.
105
+ struct _MeasuringView: View {
106
+ let entries: [(key: String, view: AnyView)]
107
+
108
+ var body: some View {
109
+ _MeasuringLayout(entries: entries)
110
+ .opacity(0)
111
+ .allowsHitTesting(false)
112
+ }
113
+ }
114
+
115
+ struct _MeasuringLayout {
116
+ let entries: [(key: String, view: AnyView)]
117
+ }
118
+ extension _MeasuringLayout: _PrimitiveView {}
119
+ extension _MeasuringLayout: _HostView {
120
+ var _kind: String { "layout" }
121
+
122
+ func _props(in environment: EnvironmentValues) -> _JSON {
123
+ .object(["proposal": .null, "size": .null, "frames": .null])
124
+ }
125
+
126
+ var _content: [any View] { entries.map(\.view) }
127
+
128
+ var _handler: ((_JSON) -> Void)? {
129
+ let keys = entries.map(\.key)
130
+ return { event in
131
+ // An event for another set of children (a render added views
132
+ // since) is left for the event about the current ones.
133
+ guard let (_, subviews) = _parseLayoutEvent(event), subviews.count == keys.count else { return }
134
+ let measured = zip(keys, subviews).map { key, subview in
135
+ (key: key, sizes: _MeasuredSizes(min: subview.min, ideal: subview.ideal, max: subview.max))
136
+ }
137
+ MainActor.assumeIsolated { _ViewMeasurements.shared.record(measured) }
138
+ }
139
+ }
140
+ }
@@ -12,6 +12,17 @@ public enum _Op: Equatable, Sendable {
12
12
  /// The app's `UserDefaults`, whole, after a change; the host stores them
13
13
  /// and hands them back at the next launch (`SB_DEFAULTS`).
14
14
  case defaults(_JSON)
15
+ /// Phase 13: something only the host can do (`fetch`, an `image` probe);
16
+ /// the host answers with a `response` event carrying the same id.
17
+ case request(id: Int, kind: String, params: _JSON)
18
+ /// Phase 18: one saved batch of a SwiftData store (an object with `store`,
19
+ /// `put` and `delete`); the host writes it to IndexedDB and hands the
20
+ /// store back at the next launch (`SB_SWIFTDATA`).
21
+ case swiftData(_JSON)
22
+ /// A bitmap's source (an image file, or a generator's pixels) under its
23
+ /// content key, sent once before the first element that draws it; the
24
+ /// host keeps it for the run (docs/ops-protocol.md, "Bitmaps and Core Image").
25
+ case imageData(_JSON)
15
26
 
16
27
  public var json: _JSON {
17
28
  switch self {
@@ -35,6 +46,20 @@ public enum _Op: Equatable, Sendable {
35
46
  return .object(["op": .string("event")])
36
47
  case .defaults(let values):
37
48
  return .object(["op": .string("defaults"), "values": values])
49
+ case .request(let id, let kind, let params):
50
+ return .object(["op": .string("request"), "id": .number(Double(id)), "kind": .string(kind), "params": params])
51
+ case .swiftData(let change):
52
+ if case .object(var fields) = change {
53
+ fields["op"] = .string("swiftdata")
54
+ return .object(fields)
55
+ }
56
+ return .object(["op": .string("swiftdata")])
57
+ case .imageData(let source):
58
+ if case .object(var fields) = source {
59
+ fields["op"] = .string("imagedata")
60
+ return .object(fields)
61
+ }
62
+ return .object(["op": .string("imagedata")])
38
63
  }
39
64
  }
40
65
  }
@@ -54,34 +79,55 @@ final class _HeadlessHost: _RenderHost {
54
79
  }
55
80
  }
56
81
 
57
- /// Keeps pending ops serialized as a JSON array in a stable heap buffer that
58
- /// the browser reads out of linear memory via the `sb_ops_*` exports.
82
+ /// Keeps pending ops serialized as a JSON array in a heap buffer that the
83
+ /// browser reads out of linear memory via the `sb_ops_*` exports, and their
84
+ /// `.bytes` values beside it (`sb_bytes_*`) once the page has asked for them
85
+ /// (else as base64). The ops are serialized when the page reads them, once
86
+ /// per batch, not on every `apply`.
59
87
  @MainActor
60
88
  final class _BufferedHost: _RenderHost {
61
89
  static let shared = _BufferedHost()
62
90
 
63
91
  private(set) var pending: [_Op] = []
64
92
  private var buffer: UnsafeMutableBufferPointer<UInt8> = .allocate(capacity: 2)
65
-
66
- init() {
67
- reserialize()
68
- }
93
+ private let outbox = _BytesOutbox()
94
+ private var stale = true
95
+ /// The page reads `.bytes` beside the JSON: it called `sb_bytes_count`.
96
+ private var bytesBeside = false
69
97
 
70
98
  func apply(_ ops: [_Op]) {
71
99
  pending.append(contentsOf: ops)
72
- reserialize()
100
+ stale = true
73
101
  }
74
102
 
75
103
  func clear() {
76
104
  pending.removeAll()
77
- reserialize()
105
+ stale = true
78
106
  }
79
107
 
80
- var pointer: Int32 { Int32(Int(bitPattern: buffer.baseAddress)) }
81
- var length: Int32 { Int32(buffer.count) }
108
+ var pointer: Int32 { serialize(); return Int32(Int(bitPattern: buffer.baseAddress)) }
109
+ var length: Int32 { serialize(); return Int32(buffer.count) }
110
+
111
+ /// The buffer's text (tests: `pointer` is a wasm32 address).
112
+ var text: String { serialize(); return String(decoding: buffer, as: UTF8.self) }
113
+
114
+ /// The `.bytes` values of the pending ops, by the `n` of their `{"$bytes": n}`.
115
+ /// The first read opts the page in: from then on the ops name them as `{"$bytes": n}`.
116
+ var bytes: [UnsafeMutableBufferPointer<UInt8>] {
117
+ if !bytesBeside {
118
+ bytesBeside = true
119
+ stale = true
120
+ }
121
+ serialize()
122
+ return outbox.items
123
+ }
82
124
 
83
- private func reserialize() {
84
- let bytes = Array(_JSON.array(pending.map(\.json)).serialized.utf8)
125
+ private func serialize() {
126
+ guard stale else { return }
127
+ stale = false
128
+ outbox.removeAll()
129
+ let json = _JSON.array(pending.map(\.json))
130
+ let bytes = Array((bytesBeside ? json.serialized(outbox: outbox) : json.serialized).utf8)
85
131
  buffer.deallocate()
86
132
  buffer = .allocate(capacity: bytes.count)
87
133
  _ = buffer.initialize(from: bytes)
@@ -8,6 +8,11 @@ struct _Element {
8
8
  var key: AnyHashable?
9
9
  /// What makes this element a selectable or deletable `List` row (Phase 8).
10
10
  var row: _RowMark?
11
+ /// `accessibilityLabel` / `accessibilityIdentifier`, for event scripts only.
12
+ var accessibility: _AccessibilityMark?
13
+ /// `.layoutValue(key:value:)` above this element, by key type: what a
14
+ /// custom layout's subview reads (CustomLayout.swift).
15
+ var layoutValues: [ObjectIdentifier: Any]?
11
16
  /// Identity of this element value: a fresh token per produced element,
12
17
  /// kept by copies. Two elements with one token are the same subtree (a
13
18
  /// clean node's cached output), which the reconciler skips whole.
@@ -59,6 +64,8 @@ final class _Reconciler {
59
64
  var key: AnyHashable?
60
65
  /// The token of the element this was rendered from.
61
66
  var token: Int
67
+ /// Not sent to the host; headless event scripts match it.
68
+ var accessibility: _AccessibilityMark?
62
69
  }
63
70
 
64
71
  private(set) var roots: [Rendered] = []
@@ -152,6 +159,7 @@ final class _Reconciler {
152
159
  if previous.token == element.token { return previous }
153
160
  var rendered = previous
154
161
  rendered.token = element.token
162
+ rendered.accessibility = element.accessibility
155
163
  if rendered.props != element.props {
156
164
  ops.append(.update(id: rendered.id, props: element.props))
157
165
  rendered.props = element.props
@@ -170,7 +178,8 @@ final class _Reconciler {
170
178
  let children = element.children.enumerated().map { childIndex, child in
171
179
  create(child, parent: id, index: childIndex, ops: &ops)
172
180
  }
173
- return Rendered(id: id, kind: element.kind, props: element.props, children: children, key: element.key, token: element.token)
181
+ return Rendered(id: id, kind: element.kind, props: element.props, children: children, key: element.key,
182
+ token: element.token, accessibility: element.accessibility)
174
183
  }
175
184
 
176
185
  private func destroy(_ rendered: Rendered, ops: inout [_Op]) {
@@ -0,0 +1,382 @@
1
+ // Headless event scripts (SB_EVENTS, Examples/*/__snapshots__/events.txt).
2
+ //
3
+ // An event names its target by element id (`tap:12`) or by a selector
4
+ // resolved against what is on screen when the event is replayed:
5
+ //
6
+ // tap:"Add Samples 2" the tappable element showing that text
7
+ // tap:"Edit"#2 the second of several matches
8
+ // tap:@save `.accessibilityIdentifier("save")`
9
+ // select:[tabview]:"Settings" an element of that kind (here by a tab title)
10
+ // text:"Name":Paul a field by its placeholder
11
+ // back, dismiss the visible screen's back button, the top sheet or alert
12
+ //
13
+ // A selector that matches nothing, or several elements without `#n`, stops
14
+ // the replay with an error op, so a script never taps the wrong element.
15
+
16
+ /// A replay target written as a selector.
17
+ struct _Selector: CustomStringConvertible {
18
+ var kind: String?
19
+ var text: String?
20
+ var identifier: String?
21
+ /// 1-based, from `#n`.
22
+ var index: Int?
23
+
24
+ var description: String {
25
+ var out = ""
26
+ if let kind { out += "[\(kind)]" }
27
+ if let text { out += _quoted(text) }
28
+ if let identifier { out += "@\(identifier)" }
29
+ if let index { out += "#\(index)" }
30
+ return out
31
+ }
32
+ }
33
+
34
+ enum _ScriptTarget {
35
+ case id(Int)
36
+ case selector(_Selector)
37
+ }
38
+
39
+ /// One event of a script, parsed but not yet resolved.
40
+ struct _ScriptEvent {
41
+ var type: String
42
+ var target: _ScriptTarget?
43
+ /// What follows the target (`Paul` in `text:"Name":Paul`), unquoted.
44
+ var value: String?
45
+ /// Whether `value` was written in quotes (`select:"Sort":"Title"` names an option).
46
+ var valueQuoted = false
47
+ var raw: String
48
+ }
49
+
50
+ struct _ScriptError: Error {
51
+ let message: String
52
+ }
53
+
54
+ private func _quoted(_ text: String) -> String {
55
+ "\"" + text.replacingAll("\\", with: "\\\\").replacingAll("\"", with: "\\\"") + "\""
56
+ }
57
+
58
+ extension String {
59
+ func trimmingSpaces() -> String {
60
+ String(drop(while: { $0 == " " }).reversed().drop(while: { $0 == " " }).reversed())
61
+ }
62
+
63
+ fileprivate func replacingAll(_ target: Character, with replacement: String) -> String {
64
+ var out = ""
65
+ for character in self {
66
+ if character == target { out += replacement } else { out.append(character) }
67
+ }
68
+ return out
69
+ }
70
+ }
71
+
72
+ /// The event types whose first field is a target.
73
+ private let _targetedEvents: Set<String> = [
74
+ "tap", "text", "toggle", "select", "step", "slide", "date", "geometry",
75
+ "drag", "longpress", "delete", "refresh",
76
+ ]
77
+
78
+ /// The element kinds a selector picks for each event type, unless it names
79
+ /// a kind itself (`[navscreen]`). A selector resolves to the nearest element
80
+ /// at or above a match that has a handler and is one of these, as a tap on
81
+ /// a button's text reaches the button.
82
+ private let _targetKinds: [String: Set<String>] = [
83
+ "tap": ["button", "navlink", "listrow", "tapgesture"],
84
+ "longpress": ["gesture"],
85
+ "drag": ["gesture"],
86
+ "delete": ["listrow"],
87
+ "text": ["textfield", "searchfield", "texteditor"],
88
+ "toggle": ["toggle"],
89
+ "select": ["picker", "tabview"],
90
+ "step": ["stepper"],
91
+ "slide": ["slider"],
92
+ "date": ["datepicker"],
93
+ "geometry": ["geometry"],
94
+ ]
95
+
96
+ enum _EventScript {
97
+ /// Splits a script into events at commas and line breaks outside quotes.
98
+ /// A line whose first non-blank character is `#` is a comment.
99
+ static func split(_ script: String) -> [String] {
100
+ var events: [String] = []
101
+ var current = ""
102
+ var inQuotes = false
103
+ var escaped = false
104
+ var inComment = false
105
+ func flush() {
106
+ let trimmed = current.trimmingSpaces()
107
+ if !trimmed.isEmpty { events.append(trimmed) }
108
+ current = ""
109
+ }
110
+ for character in script {
111
+ if inComment {
112
+ if character == "\n" { inComment = false }
113
+ continue
114
+ }
115
+ if inQuotes {
116
+ current.append(character)
117
+ if escaped {
118
+ escaped = false
119
+ } else if character == "\\" {
120
+ escaped = true
121
+ } else if character == "\"" {
122
+ inQuotes = false
123
+ }
124
+ continue
125
+ }
126
+ switch character {
127
+ case "\"":
128
+ inQuotes = true
129
+ current.append(character)
130
+ case ",", "\n", "\r":
131
+ flush()
132
+ case "#" where current.trimmingSpaces().isEmpty:
133
+ inComment = true
134
+ default:
135
+ current.append(character)
136
+ }
137
+ }
138
+ flush()
139
+ return events
140
+ }
141
+
142
+ static func parse(_ raw: String) throws(_ScriptError) -> _ScriptEvent {
143
+ var scanner = _Scanner(raw)
144
+ let type = scanner.read(until: ":").trimmingSpaces()
145
+ var event = _ScriptEvent(type: type, raw: raw)
146
+ guard scanner.skip(":") else { return event }
147
+ if _targetedEvents.contains(type) {
148
+ event.target = try scanner.readTarget(in: raw)
149
+ guard scanner.skip(":") else {
150
+ guard scanner.atEnd else { throw _ScriptError(message: "unexpected text after the target in \(raw)") }
151
+ return event
152
+ }
153
+ }
154
+ if scanner.peek == "\"" {
155
+ event.value = try scanner.readQuoted(in: raw)
156
+ event.valueQuoted = true
157
+ guard scanner.atEnd else { throw _ScriptError(message: "unexpected text after the value in \(raw)") }
158
+ } else {
159
+ event.value = scanner.rest.trimmingSpaces()
160
+ }
161
+ return event
162
+ }
163
+ }
164
+
165
+ private struct _Scanner {
166
+ let characters: [Character]
167
+ var position = 0
168
+
169
+ init(_ text: String) { characters = Array(text) }
170
+
171
+ var atEnd: Bool { position >= characters.count }
172
+ var peek: Character? { atEnd ? nil : characters[position] }
173
+ var rest: String { String(characters[position...]) }
174
+
175
+ mutating func skip(_ character: Character) -> Bool {
176
+ guard peek == character else { return false }
177
+ position += 1
178
+ return true
179
+ }
180
+
181
+ mutating func read(until stop: Character) -> String {
182
+ var out = ""
183
+ while let next = peek, next != stop {
184
+ out.append(next)
185
+ position += 1
186
+ }
187
+ return out
188
+ }
189
+
190
+ mutating func skipSpaces() {
191
+ while peek == " " { position += 1 }
192
+ }
193
+
194
+ mutating func readQuoted(in raw: String) throws(_ScriptError) -> String {
195
+ guard skip("\"") else { throw _ScriptError(message: "expected a quoted string in \(raw)") }
196
+ var out = ""
197
+ while let next = peek {
198
+ position += 1
199
+ if next == "\"" { return out }
200
+ if next == "\\", let escaped = peek {
201
+ position += 1
202
+ out.append(escaped)
203
+ } else {
204
+ out.append(next)
205
+ }
206
+ }
207
+ throw _ScriptError(message: "unterminated string in \(raw)")
208
+ }
209
+
210
+ /// An element id, or a selector: `[kind]`, then `"text"` or `@identifier`, then `#n`.
211
+ mutating func readTarget(in raw: String) throws(_ScriptError) -> _ScriptTarget {
212
+ skipSpaces()
213
+ if let first = peek, first.isNumber || first == "-" {
214
+ let text = read(until: ":").trimmingSpaces()
215
+ guard let id = Int(text) else { throw _ScriptError(message: "bad element id \(text) in \(raw)") }
216
+ return .id(id)
217
+ }
218
+ var selector = _Selector()
219
+ if skip("[") {
220
+ selector.kind = read(until: "]")
221
+ guard skip("]"), !(selector.kind ?? "").isEmpty else {
222
+ throw _ScriptError(message: "expected [kind] in \(raw)")
223
+ }
224
+ }
225
+ if peek == "\"" {
226
+ selector.text = try readQuoted(in: raw)
227
+ } else if skip("@") {
228
+ var identifier = ""
229
+ while let next = peek, next != ":", next != "#" {
230
+ identifier.append(next)
231
+ position += 1
232
+ }
233
+ selector.identifier = identifier.trimmingSpaces()
234
+ guard !(selector.identifier ?? "").isEmpty else { throw _ScriptError(message: "expected an identifier after @ in \(raw)") }
235
+ }
236
+ if skip("#") {
237
+ let digits = read(until: ":").trimmingSpaces()
238
+ guard let index = Int(digits), index > 0 else { throw _ScriptError(message: "bad match number #\(digits) in \(raw)") }
239
+ selector.index = index
240
+ }
241
+ skipSpaces()
242
+ guard selector.kind != nil || selector.text != nil || selector.identifier != nil else {
243
+ throw _ScriptError(message: "expected an element id or a selector in \(raw)")
244
+ }
245
+ return .selector(selector)
246
+ }
247
+ }
248
+
249
+ // MARK: - Resolving selectors
250
+
251
+ extension _Reconciler.Rendered {
252
+ func string(_ key: String) -> String? {
253
+ if case .object(let fields) = props, case .string(let value)? = fields[key] { return value }
254
+ return nil
255
+ }
256
+
257
+ func number(_ key: String) -> Double? {
258
+ if case .object(let fields) = props, case .number(let value)? = fields[key] { return value }
259
+ return nil
260
+ }
261
+
262
+ /// Whether `text` is what this element shows or is labelled with: a
263
+ /// `text`, an image's name, a screen title, a control's label or
264
+ /// placeholder, or its `accessibilityLabel`.
265
+ func shows(_ text: String) -> Bool {
266
+ if accessibility?.label == text { return true }
267
+ switch kind {
268
+ case "text": return string("text") == text
269
+ case "image": return string("systemName") == text || string("name") == text
270
+ default:
271
+ return string("title") == text || string("label") == text || string("placeholder") == text
272
+ }
273
+ }
274
+ }
275
+
276
+ @MainActor
277
+ extension _Reconciler {
278
+ /// The elements on screen: the top sheet or alert if one is presented,
279
+ /// else everything, without the screens a stack has pushed over and
280
+ /// the unselected tabs.
281
+ private var visibleRoots: [Rendered] {
282
+ if let modal = roots.last(where: { $0.kind == "sheet" || $0.kind == "alert" }) { return [modal] }
283
+ return roots
284
+ }
285
+
286
+ private func visibleChildren(of element: Rendered) -> [Rendered] {
287
+ switch element.kind {
288
+ case "navstack":
289
+ guard let top = element.children.lastIndex(where: { $0.kind == "navscreen" }) else { return element.children }
290
+ return element.children.enumerated().filter { index, child in
291
+ child.kind != "navscreen" || index == top
292
+ }.map(\.1)
293
+ case "tabview":
294
+ guard let selected = element.number("selected").map(Int.init),
295
+ element.children.indices.contains(selected) else { return element.children }
296
+ return [element.children[selected]]
297
+ default:
298
+ return element.children
299
+ }
300
+ }
301
+
302
+ /// The element a selector names for an event of this type.
303
+ func resolve(_ selector: _Selector, for type: String) throws(_ScriptError) -> Rendered {
304
+ let kinds = selector.kind.map { Set([$0]) } ?? _targetKinds[type]
305
+ func isTarget(_ element: Rendered) -> Bool {
306
+ handlers[element.id] != nil && (kinds?.contains(element.kind) ?? true)
307
+ }
308
+ func isMatch(_ element: Rendered) -> Bool {
309
+ if let identifier = selector.identifier { return element.accessibility?.identifier == identifier }
310
+ if let text = selector.text { return element.shows(text) }
311
+ return element.kind == selector.kind
312
+ }
313
+
314
+ var found: [Rendered] = []
315
+ func visit(_ element: Rendered, path: inout [Rendered]) {
316
+ path.append(element)
317
+ if isMatch(element), let target = path.last(where: isTarget), !found.contains(where: { $0.id == target.id }) {
318
+ found.append(target)
319
+ }
320
+ for child in visibleChildren(of: element) { visit(child, path: &path) }
321
+ path.removeLast()
322
+ }
323
+ var path: [Rendered] = []
324
+ for root in visibleRoots { visit(root, path: &path) }
325
+
326
+ let event = "\(type):\(selector)"
327
+ guard !found.isEmpty else { throw _ScriptError(message: "\(event) matches nothing on screen") }
328
+ if let index = selector.index {
329
+ guard index <= found.count else {
330
+ throw _ScriptError(message: "\(event): only \(found.count) element\(found.count == 1 ? "" : "s") match")
331
+ }
332
+ return found[index - 1]
333
+ }
334
+ guard found.count == 1 else {
335
+ let ids = found.map { "\($0.kind) \($0.id)" }.joined(separator: ", ")
336
+ throw _ScriptError(message: "\(event) matches \(found.count) elements (\(ids)); pick one with #1…#\(found.count)")
337
+ }
338
+ return found[0]
339
+ }
340
+
341
+ /// The rendered element with this id, if it is on the tree.
342
+ func element(id: Int) -> Rendered? {
343
+ func find(in elements: [Rendered]) -> Rendered? {
344
+ for element in elements {
345
+ if element.id == id { return element }
346
+ if let found = find(in: element.children) { return found }
347
+ }
348
+ return nil
349
+ }
350
+ return find(in: roots)
351
+ }
352
+
353
+ /// `back`: the visible screen's back button.
354
+ func backTarget() throws(_ScriptError) -> Rendered {
355
+ try resolve(_Selector(kind: "navscreen"), for: "tap")
356
+ }
357
+
358
+ /// `dismiss`: the top sheet or alert.
359
+ func dismissTarget() throws(_ScriptError) -> Rendered {
360
+ guard let modal = roots.last(where: { $0.kind == "sheet" || $0.kind == "alert" }), handlers[modal.id] != nil else {
361
+ throw _ScriptError(message: "dismiss: no sheet or alert is presented")
362
+ }
363
+ return modal
364
+ }
365
+
366
+ /// The index `select:…:"Option"` names: a picker option or a tab title.
367
+ func optionIndex(_ option: String, of element: Rendered) throws(_ScriptError) -> Int {
368
+ let names: [String?]
369
+ if element.kind == "tabview" {
370
+ names = element.children.map { $0.string("title") }
371
+ } else if case .object(let fields) = element.props, case .array(let options)? = fields["options"] {
372
+ names = options.map { if case .string(let name) = $0 { name } else { nil } }
373
+ } else {
374
+ names = []
375
+ }
376
+ guard let index = names.firstIndex(of: option) else {
377
+ let known = names.compactMap { $0 }.map(_quoted).joined(separator: ", ")
378
+ throw _ScriptError(message: "\(element.kind) \(element.id) has no option \(_quoted(option)) (options: \(known))")
379
+ }
380
+ return index
381
+ }
382
+ }