@swiftbrowser/swift 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.swift +214 -0
- package/README.md +10 -2
- package/Sources/AVFoundation/AVFoundation.swift +131 -0
- package/Sources/AVFoundation/Asset.swift +487 -0
- package/Sources/AVFoundation/AudioSession.swift +144 -0
- package/Sources/AVFoundation/CMTime.swift +303 -0
- package/Sources/AVFoundation/Capture.swift +688 -0
- package/Sources/AVFoundation/Player.swift +749 -0
- package/Sources/AVFoundation/PlayerView.swift +117 -0
- package/Sources/AVKit/AVKit.swift +132 -0
- package/Sources/Accelerate/Accelerate.swift +122 -0
- package/Sources/AppIntents/AppIntents.swift +144 -0
- package/Sources/AppIntents/Parameters.swift +220 -0
- package/Sources/AppIntents/Shortcuts.swift +159 -0
- package/Sources/AudioToolbox/AudioToolbox.swift +55 -0
- package/Sources/AuthenticationServices/AuthenticationServices.swift +257 -0
- package/Sources/AuthenticationServices/Authorization.swift +251 -0
- package/Sources/Charts/Charts.swift +61 -0
- package/Sources/Combine/AnyCancellable.swift +65 -0
- package/Sources/Combine/AnyPublisher.swift +148 -0
- package/Sources/Combine/AnySubscriber.swift +230 -0
- package/Sources/Combine/Cancellable.swift +35 -0
- package/Sources/Combine/Codable.swift +29 -0
- package/Sources/Combine/CombineIdentifier.swift +45 -0
- package/Sources/Combine/Concurrency/ConcurrencyHelpers.swift +24 -0
- package/Sources/Combine/Concurrency/Future+Concurrency.swift +136 -0
- package/Sources/Combine/Concurrency/GENERATED-Publisher+Concurrency.swift +397 -0
- package/Sources/Combine/CurrentValueSubject.swift +252 -0
- package/Sources/Combine/CustomCombineIdentifierConvertible.swift +28 -0
- package/Sources/Combine/Future.swift +228 -0
- package/Sources/Combine/GENERATED-RootProtocols.swift +426 -0
- package/Sources/Combine/Helpers/ConduitBase.swift +40 -0
- package/Sources/Combine/Helpers/ConduitList.swift +59 -0
- package/Sources/Combine/Helpers/DebugHook.swift +177 -0
- package/Sources/Combine/Helpers/FilterProducer.swift +174 -0
- package/Sources/Combine/Helpers/Locking.swift +30 -0
- package/Sources/Combine/Helpers/PartialCompletion.swift +26 -0
- package/Sources/Combine/Helpers/ReduceProducer.swift +249 -0
- package/Sources/Combine/Helpers/SubjectSubscriber.swift +96 -0
- package/Sources/Combine/Helpers/SubscriberTap.swift +69 -0
- package/Sources/Combine/Helpers/SubscriptionStatus.swift +33 -0
- package/Sources/Combine/Helpers/SubscriptionTap.swift +33 -0
- package/Sources/Combine/Helpers/Utils.swift +30 -0
- package/Sources/Combine/Helpers/Violations.swift +33 -0
- package/Sources/Combine/ImmediateScheduler.swift +178 -0
- package/Sources/Combine/LICENSE +21 -0
- package/Sources/Combine/NotificationPublisher.swift +48 -0
- package/Sources/Combine/ObjectWillChange.swift +46 -0
- package/Sources/Combine/PassthroughSubject.swift +222 -0
- package/Sources/Combine/Publisher+Subscribe.swift +53 -0
- package/Sources/Combine/Publishers/Deferred.swift +48 -0
- package/Sources/Combine/Publishers/Empty.swift +54 -0
- package/Sources/Combine/Publishers/Fail.swift +42 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.Catch.swift +673 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.Encode.swift +388 -0
- package/Sources/Combine/Publishers/GENERATED-Publishers.MapKeyPath.swift +384 -0
- package/Sources/Combine/Publishers/Just.swift +313 -0
- package/Sources/Combine/Publishers/Optional.Publisher.swift +315 -0
- package/Sources/Combine/Publishers/Publishers.AllSatisfy.swift +217 -0
- package/Sources/Combine/Publishers/Publishers.AssertNoFailure.swift +158 -0
- package/Sources/Combine/Publishers/Publishers.Autoconnect.swift +193 -0
- package/Sources/Combine/Publishers/Publishers.Breakpoint.swift +231 -0
- package/Sources/Combine/Publishers/Publishers.Buffer.swift +356 -0
- package/Sources/Combine/Publishers/Publishers.Collect.swift +102 -0
- package/Sources/Combine/Publishers/Publishers.CollectByCount.swift +195 -0
- package/Sources/Combine/Publishers/Publishers.CompactMap.swift +241 -0
- package/Sources/Combine/Publishers/Publishers.Comparison.swift +371 -0
- package/Sources/Combine/Publishers/Publishers.Concatenate.swift +474 -0
- package/Sources/Combine/Publishers/Publishers.Contains.swift +293 -0
- package/Sources/Combine/Publishers/Publishers.Count.swift +87 -0
- package/Sources/Combine/Publishers/Publishers.Debounce.swift +332 -0
- package/Sources/Combine/Publishers/Publishers.Delay.swift +269 -0
- package/Sources/Combine/Publishers/Publishers.Drop.swift +155 -0
- package/Sources/Combine/Publishers/Publishers.DropUntilOutput.swift +283 -0
- package/Sources/Combine/Publishers/Publishers.DropWhile.swift +378 -0
- package/Sources/Combine/Publishers/Publishers.Filter.swift +248 -0
- package/Sources/Combine/Publishers/Publishers.First.swift +261 -0
- package/Sources/Combine/Publishers/Publishers.FlatMap.swift +533 -0
- package/Sources/Combine/Publishers/Publishers.HandleEvents.swift +260 -0
- package/Sources/Combine/Publishers/Publishers.IgnoreOutput.swift +109 -0
- package/Sources/Combine/Publishers/Publishers.Last.swift +252 -0
- package/Sources/Combine/Publishers/Publishers.MakeConnectable.swift +82 -0
- package/Sources/Combine/Publishers/Publishers.Map.swift +351 -0
- package/Sources/Combine/Publishers/Publishers.MapError.swift +140 -0
- package/Sources/Combine/Publishers/Publishers.MeasureInterval.swift +187 -0
- package/Sources/Combine/Publishers/Publishers.Multicast.swift +284 -0
- package/Sources/Combine/Publishers/Publishers.Output.swift +241 -0
- package/Sources/Combine/Publishers/Publishers.PrefixUntilOutput.swift +197 -0
- package/Sources/Combine/Publishers/Publishers.PrefixWhile.swift +176 -0
- package/Sources/Combine/Publishers/Publishers.Print.swift +206 -0
- package/Sources/Combine/Publishers/Publishers.ReceiveOn.swift +227 -0
- package/Sources/Combine/Publishers/Publishers.Reduce.swift +204 -0
- package/Sources/Combine/Publishers/Publishers.RemoveDuplicates.swift +264 -0
- package/Sources/Combine/Publishers/Publishers.ReplaceEmpty.swift +201 -0
- package/Sources/Combine/Publishers/Publishers.ReplaceError.swift +211 -0
- package/Sources/Combine/Publishers/Publishers.Retry.swift +274 -0
- package/Sources/Combine/Publishers/Publishers.Scan.swift +331 -0
- package/Sources/Combine/Publishers/Publishers.Sequence.swift +437 -0
- package/Sources/Combine/Publishers/Publishers.SetFailureType.swift +120 -0
- package/Sources/Combine/Publishers/Publishers.Share.swift +104 -0
- package/Sources/Combine/Publishers/Publishers.SubscribeOn.swift +197 -0
- package/Sources/Combine/Publishers/Publishers.SwitchToLatest.swift +444 -0
- package/Sources/Combine/Publishers/Publishers.Throttle.swift +359 -0
- package/Sources/Combine/Publishers/Publishers.Timeout.swift +301 -0
- package/Sources/Combine/Publishers/Publishers.Zip.swift +720 -0
- package/Sources/Combine/Publishers/Publishers.swift +13 -0
- package/Sources/Combine/Publishers/Record.swift +237 -0
- package/Sources/Combine/Publishers/Result.Publisher.swift +337 -0
- package/Sources/Combine/Result.swift +46 -0
- package/Sources/Combine/Scheduler.swift +89 -0
- package/Sources/Combine/Schedulers.swift +211 -0
- package/Sources/Combine/Subject+Void.swift +17 -0
- package/Sources/Combine/Subscriber+Void.swift +20 -0
- package/Sources/Combine/Subscribers/Subscribers.Assign.swift +175 -0
- package/Sources/Combine/Subscribers/Subscribers.Completion.swift +90 -0
- package/Sources/Combine/Subscribers/Subscribers.Demand.swift +476 -0
- package/Sources/Combine/Subscribers/Subscribers.Sink.swift +206 -0
- package/Sources/Combine/Subscribers/Subscribers.swift +9 -0
- package/Sources/Combine/Subscription.swift +22 -0
- package/Sources/Combine/Subscriptions.swift +42 -0
- package/Sources/Combine/SwiftUIObservation.swift +10 -0
- package/Sources/Combine/_Introspection.swift +107 -0
- package/Sources/CommonCrypto/CommonCrypto.swift +80 -0
- package/Sources/Compression/Compression.swift +365 -0
- package/Sources/Compression/Deflate.swift +407 -0
- package/Sources/Compression/Zlib.swift +179 -0
- package/Sources/CoreFoundationTypes/CoreFoundationTypes.swift +101 -0
- package/Sources/CoreGraphics/CoreGraphics.swift +411 -0
- package/Sources/CoreGraphics/ProjectionTransforms.swift +28 -0
- package/Sources/CoreHaptics/CoreHaptics.swift +399 -0
- package/Sources/CoreImage/CIColor.swift +41 -0
- package/Sources/CoreImage/CIContext.swift +31 -0
- package/Sources/CoreImage/CIDetector.swift +57 -0
- package/Sources/CoreImage/CIFilter.swift +243 -0
- package/Sources/CoreImage/CIFilterBuiltins.swift +136 -0
- package/Sources/CoreImage/CIImage.swift +162 -0
- package/Sources/CoreImage/QRCode.swift +393 -0
- package/Sources/CoreLocation/Coordinate.swift +30 -0
- package/Sources/CoreLocation/Location.swift +65 -0
- package/Sources/CoreML/Evaluation.swift +414 -0
- package/Sources/CoreML/FeatureValue.swift +266 -0
- package/Sources/CoreML/Model.swift +269 -0
- package/Sources/CoreML/MultiArray.swift +92 -0
- package/Sources/CoreML/Specification.swift +233 -0
- package/Sources/CoreText/CoreText.swift +64 -0
- package/Sources/CoreTransferable/CoreTransferable.swift +606 -0
- package/Sources/CryptoKit/ChaChaPoly.swift +258 -0
- package/Sources/CryptoKit/CryptoKit.swift +375 -0
- package/Sources/CryptoKit/Curve25519.swift +317 -0
- package/Sources/CryptoKit/P256.swift +321 -0
- package/Sources/FoundationBridge/AppStorage.swift +165 -0
- package/Sources/FoundationBridge/AssetResources.swift +90 -0
- package/Sources/FoundationBridge/AsyncBytes.swift +128 -0
- package/Sources/FoundationBridge/AsyncImage.swift +116 -0
- package/Sources/FoundationBridge/AttributedStrings.swift +16 -0
- package/Sources/FoundationBridge/Bundle.swift +119 -0
- package/Sources/FoundationBridge/CombineExports.swift +8 -0
- package/Sources/FoundationBridge/CoreFoundation.swift +77 -0
- package/Sources/FoundationBridge/Dispatch.swift +458 -0
- package/Sources/FoundationBridge/EnvironmentFoundation.swift +41 -0
- package/Sources/FoundationBridge/FileImporter.swift +128 -0
- package/Sources/FoundationBridge/FileSystem.swift +68 -0
- package/Sources/FoundationBridge/FoundationBridge.swift +29 -0
- package/Sources/FoundationBridge/HostResolution.swift +31 -0
- package/Sources/FoundationBridge/ItemProvider.swift +655 -0
- package/Sources/FoundationBridge/KeyValueObserving.swift +138 -0
- package/Sources/FoundationBridge/Links.swift +127 -0
- package/Sources/FoundationBridge/ListFormatter.swift +32 -0
- package/Sources/FoundationBridge/Locale.swift +86 -0
- package/Sources/FoundationBridge/Localization.swift +149 -0
- package/Sources/FoundationBridge/Locks.swift +50 -0
- package/Sources/FoundationBridge/Markdown.swift +527 -0
- package/Sources/FoundationBridge/MeasurementFormat.swift +95 -0
- package/Sources/FoundationBridge/NotificationCenter.swift +154 -0
- package/Sources/FoundationBridge/OpenURLHandlers.swift +60 -0
- package/Sources/FoundationBridge/Operation.swift +195 -0
- package/Sources/FoundationBridge/ProcessInfo.swift +32 -0
- package/Sources/FoundationBridge/RelativeDateTimeFormatter.swift +136 -0
- package/Sources/FoundationBridge/SceneStorage.swift +78 -0
- package/Sources/FoundationBridge/SecurityExports.swift +8 -0
- package/Sources/FoundationBridge/Selector.swift +226 -0
- package/Sources/FoundationBridge/StringComparators.swift +17 -0
- package/Sources/FoundationBridge/Table.swift +211 -0
- package/Sources/FoundationBridge/TextAttributes.swift +42 -0
- package/Sources/FoundationBridge/Timer.swift +53 -0
- package/Sources/FoundationBridge/UITextChecker.swift +30 -0
- package/Sources/FoundationBridge/URLSession.swift +718 -0
- package/Sources/FoundationBridge/UserDefaults.swift +183 -0
- package/Sources/FoundationBridge/WebAuthRedirects.swift +227 -0
- package/Sources/FoundationBridge/WebSocket.swift +187 -0
- package/Sources/FoundationModels/FoundationModels.swift +1098 -0
- package/Sources/ImageIO/GIF.swift +72 -0
- package/Sources/ImageIO/ImageIO.swift +248 -0
- package/Sources/ImageIO/ImageTypes.swift +28 -0
- package/Sources/LinkPresentation/LinkPresentation.swift +249 -0
- package/Sources/LocalAuthentication/LAContext.swift +86 -0
- package/Sources/MapKit/Geometry.swift +223 -0
- package/Sources/MapKit/Map.swift +638 -0
- package/Sources/MapKit/MapView.swift +229 -0
- package/Sources/MobileCoreServices/MobileCoreServices.swift +28 -0
- package/Sources/NaturalLanguage/LanguageIdentifier.swift +317 -0
- package/Sources/NaturalLanguage/NaturalLanguage.swift +135 -0
- package/Sources/OSLog/OSLog.swift +6 -0
- package/Sources/Photos/Photos.swift +572 -0
- package/Sources/PhotosUI/PhotosPicker.swift +248 -0
- package/Sources/PhotosUI/PhotosPickerItem.swift +261 -0
- package/Sources/QuickLook/QuickLook.swift +168 -0
- package/Sources/SafariServices/SafariServices.swift +106 -0
- package/Sources/Security/Security.swift +332 -0
- package/Sources/StoreKit/AppStore.swift +207 -0
- package/Sources/StoreKit/Products.swift +280 -0
- package/Sources/StoreKit/RequestReview.swift +28 -0
- package/Sources/StoreKit/StoreKit1.swift +486 -0
- package/Sources/StoreKit/StoreViews.swift +428 -0
- package/Sources/StoreKit/Transactions.swift +197 -0
- package/Sources/SwiftData/Macros.swift +58 -0
- package/Sources/SwiftData/ModelActor.swift +38 -0
- package/Sources/SwiftData/ModelContainer.swift +129 -0
- package/Sources/SwiftData/ModelContext.swift +451 -0
- package/Sources/SwiftData/PersistentModel.swift +238 -0
- package/Sources/SwiftData/Properties.swift +261 -0
- package/Sources/SwiftData/Query.swift +256 -0
- package/Sources/SwiftData/Schema.swift +192 -0
- package/Sources/SwiftData/Store.swift +146 -0
- package/Sources/SwiftData/StringPredicates.swift +125 -0
- package/Sources/SwiftDataMacros/EntryMacro.swift +82 -0
- package/Sources/SwiftDataMacros/GenerableMacro.swift +183 -0
- package/Sources/SwiftDataMacros/ModelActorMacro.swift +46 -0
- package/Sources/SwiftDataMacros/ModelMacro.swift +260 -0
- package/Sources/SwiftDataMacros/Plugin.swift +18 -0
- package/Sources/SwiftDataMacros/PredicateMacro.swift +1244 -0
- package/Sources/SwiftUI/API/Accessibility.swift +181 -0
- package/Sources/SwiftUI/API/AccessibilityExtras.swift +33 -0
- package/Sources/SwiftUI/API/AccessibilityFocus.swift +63 -0
- package/Sources/SwiftUI/API/Alert.swift +221 -0
- package/Sources/SwiftUI/API/AlignmentGuides.swift +65 -0
- package/Sources/SwiftUI/API/Animation.swift +285 -0
- package/Sources/SwiftUI/API/App.swift +87 -0
- package/Sources/SwiftUI/API/BindingCollections.swift +35 -0
- package/Sources/SwiftUI/API/BindingForEach.swift +27 -0
- package/Sources/SwiftUI/API/Bitmaps.swift +254 -0
- package/Sources/SwiftUI/API/ButtonStyles.swift +96 -0
- package/Sources/SwiftUI/API/Canvas.swift +337 -0
- package/Sources/SwiftUI/API/Color.swift +237 -0
- package/Sources/SwiftUI/API/ColorEffects.swift +52 -0
- package/Sources/SwiftUI/API/ColorPickerAndLabeledContent.swift +138 -0
- package/Sources/SwiftUI/API/ColorSpaces.swift +161 -0
- package/Sources/SwiftUI/API/Commands.swift +106 -0
- package/Sources/SwiftUI/API/ContainerBackground.swift +28 -0
- package/Sources/SwiftUI/API/ContentShape.swift +33 -0
- package/Sources/SwiftUI/API/ContentSizeCategory.swift +35 -0
- package/Sources/SwiftUI/API/ContentUnavailable.swift +101 -0
- package/Sources/SwiftUI/API/Controls.swift +300 -0
- package/Sources/SwiftUI/API/CustomLayout.swift +442 -0
- package/Sources/SwiftUI/API/CustomToolbarContent.swift +46 -0
- package/Sources/SwiftUI/API/Decorations.swift +92 -0
- package/Sources/SwiftUI/API/EditMode.swift +212 -0
- package/Sources/SwiftUI/API/Effects.swift +388 -0
- package/Sources/SwiftUI/API/Entry.swift +8 -0
- package/Sources/SwiftUI/API/Environment.swift +439 -0
- package/Sources/SwiftUI/API/EnvironmentSendable.swift +6 -0
- package/Sources/SwiftUI/API/Focus.swift +137 -0
- package/Sources/SwiftUI/API/Font.swift +131 -0
- package/Sources/SwiftUI/API/Gauge.swift +290 -0
- package/Sources/SwiftUI/API/Geometry.swift +89 -0
- package/Sources/SwiftUI/API/GeometryEffects.swift +232 -0
- package/Sources/SwiftUI/API/GeometryReader.swift +135 -0
- package/Sources/SwiftUI/API/GestureState.swift +65 -0
- package/Sources/SwiftUI/API/Gestures.swift +342 -0
- package/Sources/SwiftUI/API/Glass.swift +127 -0
- package/Sources/SwiftUI/API/Grid.swift +188 -0
- package/Sources/SwiftUI/API/GroupBoxAndControlGroup.swift +130 -0
- package/Sources/SwiftUI/API/KeyPress.swift +54 -0
- package/Sources/SwiftUI/API/LabelStyle.swift +124 -0
- package/Sources/SwiftUI/API/Layout.swift +220 -0
- package/Sources/SwiftUI/API/LazyStacks.swift +49 -0
- package/Sources/SwiftUI/API/LegacyAlert.swift +97 -0
- package/Sources/SwiftUI/API/Lists.swift +331 -0
- package/Sources/SwiftUI/API/Menu.swift +58 -0
- package/Sources/SwiftUI/API/Modifiers.swift +313 -0
- package/Sources/SwiftUI/API/MoreModifiers.swift +100 -0
- package/Sources/SwiftUI/API/Namespace.swift +44 -0
- package/Sources/SwiftUI/API/Navigation.swift +386 -0
- package/Sources/SwiftUI/API/NavigationDestinationBindings.swift +55 -0
- package/Sources/SwiftUI/API/ObservableObject.swift +293 -0
- package/Sources/SwiftUI/API/OnChange.swift +48 -0
- package/Sources/SwiftUI/API/Path.swift +543 -0
- package/Sources/SwiftUI/API/Phase8Controls.swift +411 -0
- package/Sources/SwiftUI/API/PhaseAnimator.swift +130 -0
- package/Sources/SwiftUI/API/Picker.swift +185 -0
- package/Sources/SwiftUI/API/PointerAndKeyboard.swift +77 -0
- package/Sources/SwiftUI/API/Popover.swift +41 -0
- package/Sources/SwiftUI/API/Preferences.swift +262 -0
- package/Sources/SwiftUI/API/Presentation.swift +122 -0
- package/Sources/SwiftUI/API/PresentationExtras.swift +60 -0
- package/Sources/SwiftUI/API/Primitives.swift +517 -0
- package/Sources/SwiftUI/API/Publishers.swift +107 -0
- package/Sources/SwiftUI/API/Redaction.swift +75 -0
- package/Sources/SwiftUI/API/RowActions.swift +84 -0
- package/Sources/SwiftUI/API/Scenes.swift +131 -0
- package/Sources/SwiftUI/API/ScrollModifiers.swift +132 -0
- package/Sources/SwiftUI/API/ScrollTargets.swift +104 -0
- package/Sources/SwiftUI/API/ScrollViewReader.swift +144 -0
- package/Sources/SwiftUI/API/SectionSupplements.swift +96 -0
- package/Sources/SwiftUI/API/Shape.swift +214 -0
- package/Sources/SwiftUI/API/ShapeStyle.swift +409 -0
- package/Sources/SwiftUI/API/ShapeTrim.swift +78 -0
- package/Sources/SwiftUI/API/Sharing.swift +193 -0
- package/Sources/SwiftUI/API/StackLayouts.swift +199 -0
- package/Sources/SwiftUI/API/State.swift +144 -0
- package/Sources/SwiftUI/API/StatusGaps.swift +121 -0
- package/Sources/SwiftUI/API/StrokeStyle.swift +30 -0
- package/Sources/SwiftUI/API/SymbolEffects.swift +203 -0
- package/Sources/SwiftUI/API/TabContent.swift +841 -0
- package/Sources/SwiftUI/API/Table.swift +382 -0
- package/Sources/SwiftUI/API/Tabs.swift +263 -0
- package/Sources/SwiftUI/API/Tasks.swift +35 -0
- package/Sources/SwiftUI/API/Text.swift +386 -0
- package/Sources/SwiftUI/API/TextDates.swift +61 -0
- package/Sources/SwiftUI/API/TextDecorations.swift +52 -0
- package/Sources/SwiftUI/API/TextFieldAxis.swift +103 -0
- package/Sources/SwiftUI/API/TextFieldCommit.swift +52 -0
- package/Sources/SwiftUI/API/Timeline.swift +212 -0
- package/Sources/SwiftUI/API/Toolbar.swift +251 -0
- package/Sources/SwiftUI/API/Transfer.swift +145 -0
- package/Sources/SwiftUI/API/UnevenRoundedRectangle.swift +66 -0
- package/Sources/SwiftUI/API/Video.swift +64 -0
- package/Sources/SwiftUI/API/View.swift +215 -0
- package/Sources/SwiftUI/API/ViewModifier.swift +43 -0
- package/Sources/SwiftUI/API/VisualEffect.swift +155 -0
- package/Sources/SwiftUI/API/Windows.swift +25 -0
- package/Sources/SwiftUI/Charts/Axis.swift +325 -0
- package/Sources/SwiftUI/Charts/AxisFormatsAndRanges.swift +68 -0
- package/Sources/SwiftUI/Charts/Chart.swift +660 -0
- package/Sources/SwiftUI/Charts/ChartContent.swift +376 -0
- package/Sources/SwiftUI/Charts/ChartProxy.swift +57 -0
- package/Sources/SwiftUI/Charts/Marks.swift +204 -0
- package/Sources/SwiftUI/Charts/Plottable.swift +99 -0
- package/Sources/SwiftUI/Charts/Scales.swift +326 -0
- package/Sources/SwiftUI/Localization/Localization.swift +498 -0
- package/Sources/SwiftUI/Localization/PluralRules.swift +127 -0
- package/Sources/SwiftUI/Runtime/Executor.swift +85 -0
- package/Sources/SwiftUI/Runtime/Exports.swift +101 -0
- package/Sources/SwiftUI/Runtime/ImageSource.swift +310 -0
- package/Sources/SwiftUI/Runtime/JSON.swift +143 -0
- package/Sources/SwiftUI/Runtime/JSONParser.swift +443 -0
- package/Sources/SwiftUI/Runtime/LazyRows.swift +99 -0
- package/Sources/SwiftUI/Runtime/Measurements.swift +140 -0
- package/Sources/SwiftUI/Runtime/Ops.swift +152 -0
- package/Sources/SwiftUI/Runtime/Reconciler.swift +219 -0
- package/Sources/SwiftUI/Runtime/Replay.swift +382 -0
- package/Sources/SwiftUI/Runtime/Runtime.swift +2146 -0
- package/Sources/SwiftUI/Runtime/Sameness.swift +152 -0
- package/Sources/SwiftUI/Runtime/ViewPath.swift +89 -0
- package/Sources/SwiftUI/Runtime/ViewTraits.swift +175 -0
- package/Sources/SwiftUI/Runtime/WebAuthHeadless.swift +49 -0
- package/Sources/Symbols/Symbols.swift +25 -0
- package/Sources/Translation/Translation.swift +423 -0
- package/Sources/UIKit/Activity.swift +213 -0
- package/Sources/UIKit/Alerts.swift +69 -0
- package/Sources/UIKit/AppDelegate.swift +222 -0
- package/Sources/UIKit/Appearance.swift +84 -0
- package/Sources/UIKit/ColorConversions.swift +70 -0
- package/Sources/UIKit/Controls.swift +225 -0
- package/Sources/UIKit/Device.swift +200 -0
- package/Sources/UIKit/Feedback.swift +65 -0
- package/Sources/UIKit/Font.swift +307 -0
- package/Sources/UIKit/Graphics.swift +204 -0
- package/Sources/UIKit/HostingController.swift +185 -0
- package/Sources/UIKit/Identity.swift +24 -0
- package/Sources/UIKit/Introspectable.swift +510 -0
- package/Sources/UIKit/Pasteboard.swift +363 -0
- package/Sources/UIKit/PhotoAlbum.swift +81 -0
- package/Sources/UIKit/RemoteNotifications.swift +15 -0
- package/Sources/UIKit/Representable.swift +288 -0
- package/Sources/UIKit/SafeArea.swift +12 -0
- package/Sources/UIKit/SceneDelegates.swift +132 -0
- package/Sources/UIKit/Scenes.swift +40 -0
- package/Sources/UIKit/ScrollView.swift +157 -0
- package/Sources/UIKit/ShortcutItems.swift +67 -0
- package/Sources/UIKit/SymbolConfiguration.swift +124 -0
- package/Sources/UIKit/TargetAction.swift +123 -0
- package/Sources/UIKit/TextPaste.swift +161 -0
- package/Sources/UIKit/TextView.swift +538 -0
- package/Sources/UIKit/UIKit.swift +292 -0
- package/Sources/UIKit/UIView.swift +855 -0
- package/Sources/UIKit/ViewTranslation.swift +311 -0
- package/Sources/UniformTypeIdentifiers/UTType.swift +196 -0
- package/Sources/UserNotifications/Content.swift +370 -0
- package/Sources/UserNotifications/NotificationCenter.swift +309 -0
- package/Sources/WeatherKit/WeatherKit.swift +228 -0
- package/Sources/WebKit/WebKit.swift +245 -0
- package/Sources/os/os.swift +376 -0
- package/Sources/zlib/zlib.swift +6 -0
- package/index.d.ts +2 -0
- package/index.mjs +16 -0
- package/manifest.d.mts +2 -0
- package/manifest.mjs +39 -0
- package/package.json +30 -4
- package/sync-sources.mjs +33 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// `PhotosPicker` and `.photosPicker(isPresented:)`: the page's file chooser
|
|
2
|
+
// in place of the photo library. A pick is a `pickFiles` host request
|
|
3
|
+
// (Phase 13's channel; docs/ops-protocol.md, "Photos picker") with the
|
|
4
|
+
// filter's `accept` and whether several files may be chosen. The page opens
|
|
5
|
+
// an `<input type="file">` from the tap that sent it (the request goes out
|
|
6
|
+
// within the same click, which a browser requires to show a chooser) and
|
|
7
|
+
// answers with each chosen file's bytes, type and, for a picture, an object
|
|
8
|
+
// URL and pixel size. A dismissed chooser answers with no files, which
|
|
9
|
+
// leaves the selection as it was, as on iOS.
|
|
10
|
+
|
|
11
|
+
import SwiftUI
|
|
12
|
+
|
|
13
|
+
/// One pick: what to offer, how many, and what to do with the result.
|
|
14
|
+
struct _PhotosPick: Sendable {
|
|
15
|
+
let filter: PHPickerFilter?
|
|
16
|
+
/// At most this many items (nil: no limit).
|
|
17
|
+
let limit: Int?
|
|
18
|
+
/// Whether the chooser lets the viewer pick several files.
|
|
19
|
+
let multiple: Bool
|
|
20
|
+
/// Whether picked items expose an `itemIdentifier` (a `photoLibrary` was given).
|
|
21
|
+
let exposesIdentifiers: Bool
|
|
22
|
+
|
|
23
|
+
/// The request's params: `{accept, multiple}`. No filter offers pictures and movies, the library's contents.
|
|
24
|
+
var params: _JSON {
|
|
25
|
+
.object([
|
|
26
|
+
"accept": .string((filter ?? .any(of: [.images, .videos])).accept),
|
|
27
|
+
"multiple": .bool(multiple),
|
|
28
|
+
])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Asks the page and waits for the viewer. Empty when the chooser was
|
|
32
|
+
/// dismissed, or the page could not open one.
|
|
33
|
+
@MainActor func run() async -> [PhotosPickerItem] {
|
|
34
|
+
guard let value = try? await _Runtime.shared._hostRequest(kind: "pickFiles", params: params) else { return [] }
|
|
35
|
+
return items(from: value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// The items of a `pickFiles` answer (`{items: [...]}`): files the
|
|
39
|
+
/// filter rejects are dropped (a chooser's "All files" lets any through),
|
|
40
|
+
/// then the list is cut to the limit.
|
|
41
|
+
@MainActor func items(from value: _JSON) -> [PhotosPickerItem] {
|
|
42
|
+
guard case .object(let fields) = value, case .array(let entries)? = fields["items"] else { return [] }
|
|
43
|
+
let matching = filter ?? .any(of: [.images, .videos])
|
|
44
|
+
var items: [PhotosPickerItem] = []
|
|
45
|
+
for entry in entries {
|
|
46
|
+
guard let file = _PickedFile(entry), matching.matches(mimeType: file.effectiveMIMEType) else { continue }
|
|
47
|
+
if let limit, limit > 0, items.count >= limit { break }
|
|
48
|
+
_PhotosPick.picked += 1
|
|
49
|
+
items.append(PhotosPickerItem(file: file, key: "picked-\(_PhotosPick.picked)", exposesIdentifier: exposesIdentifiers))
|
|
50
|
+
}
|
|
51
|
+
return items
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Items picked so far in this run: each new one gets the next key.
|
|
55
|
+
@MainActor static var picked = 0
|
|
56
|
+
|
|
57
|
+
/// Runs the pick in a task (a tap's executor run, so the request leaves
|
|
58
|
+
/// within the click) and hands a non-empty result to `apply`.
|
|
59
|
+
@MainActor func start(_ apply: @escaping @MainActor ([PhotosPickerItem]) -> Void, finished: @escaping @MainActor () -> Void = {}) {
|
|
60
|
+
Task { @MainActor in
|
|
61
|
+
let items = await run()
|
|
62
|
+
if !items.isEmpty { apply(items) }
|
|
63
|
+
finished()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// MARK: PhotosPicker
|
|
69
|
+
|
|
70
|
+
/// A button that opens the photo picker (the browser's file chooser) and
|
|
71
|
+
/// puts what the viewer picks into `selection`.
|
|
72
|
+
public struct PhotosPicker<Label: View>: View {
|
|
73
|
+
let pick: _PhotosPick
|
|
74
|
+
let apply: @MainActor ([PhotosPickerItem]) -> Void
|
|
75
|
+
let label: Label
|
|
76
|
+
|
|
77
|
+
init(pick: _PhotosPick, apply: @escaping @MainActor ([PhotosPickerItem]) -> Void, label: Label) {
|
|
78
|
+
self.pick = pick
|
|
79
|
+
self.apply = apply
|
|
80
|
+
self.label = label
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public var body: some View {
|
|
84
|
+
let pick = pick
|
|
85
|
+
let apply = apply
|
|
86
|
+
return Button {
|
|
87
|
+
pick.start(apply)
|
|
88
|
+
} label: {
|
|
89
|
+
label
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// One item.
|
|
94
|
+
|
|
95
|
+
public init(selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
96
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
97
|
+
@ViewBuilder label: () -> Label) {
|
|
98
|
+
self.init(pick: _PhotosPick(filter: filter, limit: 1, multiple: false, exposesIdentifiers: false),
|
|
99
|
+
apply: { selection.wrappedValue = $0.first }, label: label())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public init(selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
103
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
104
|
+
photoLibrary: PHPhotoLibrary, @ViewBuilder label: () -> Label) {
|
|
105
|
+
self.init(pick: _PhotosPick(filter: filter, limit: 1, multiple: false, exposesIdentifiers: true),
|
|
106
|
+
apply: { selection.wrappedValue = $0.first }, label: label())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Several items.
|
|
110
|
+
|
|
111
|
+
public init(selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
112
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
113
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
114
|
+
@ViewBuilder label: () -> Label) {
|
|
115
|
+
self.init(pick: _PhotosPick(filter: filter, limit: maxSelectionCount, multiple: maxSelectionCount != 1, exposesIdentifiers: false),
|
|
116
|
+
apply: { selection.wrappedValue = $0 }, label: label())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public init(selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
120
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
121
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
122
|
+
photoLibrary: PHPhotoLibrary, @ViewBuilder label: () -> Label) {
|
|
123
|
+
self.init(pick: _PhotosPick(filter: filter, limit: maxSelectionCount, multiple: maxSelectionCount != 1, exposesIdentifiers: true),
|
|
124
|
+
apply: { selection.wrappedValue = $0 }, label: label())
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
extension PhotosPicker where Label == Text {
|
|
129
|
+
// One item, with a title.
|
|
130
|
+
|
|
131
|
+
public init(_ titleKey: LocalizedStringKey, selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
132
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) {
|
|
133
|
+
self.init(selection: selection, matching: filter, preferredItemEncoding: preferredItemEncoding) { Text(titleKey) }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@_disfavoredOverload
|
|
137
|
+
public init<S: StringProtocol>(_ title: S, selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
138
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) {
|
|
139
|
+
self.init(selection: selection, matching: filter, preferredItemEncoding: preferredItemEncoding) { Text(title) }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public init(_ titleKey: LocalizedStringKey, selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
143
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic, photoLibrary: PHPhotoLibrary) {
|
|
144
|
+
self.init(selection: selection, matching: filter, preferredItemEncoding: preferredItemEncoding, photoLibrary: photoLibrary) { Text(titleKey) }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@_disfavoredOverload
|
|
148
|
+
public init<S: StringProtocol>(_ title: S, selection: Binding<PhotosPickerItem?>, matching filter: PHPickerFilter? = nil,
|
|
149
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
150
|
+
photoLibrary: PHPhotoLibrary) {
|
|
151
|
+
self.init(selection: selection, matching: filter, preferredItemEncoding: preferredItemEncoding, photoLibrary: photoLibrary) { Text(title) }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Several items, with a title.
|
|
155
|
+
|
|
156
|
+
public init(_ titleKey: LocalizedStringKey, selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
157
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
158
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) {
|
|
159
|
+
self.init(selection: selection, maxSelectionCount: maxSelectionCount, selectionBehavior: selectionBehavior,
|
|
160
|
+
matching: filter, preferredItemEncoding: preferredItemEncoding) { Text(titleKey) }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@_disfavoredOverload
|
|
164
|
+
public init<S: StringProtocol>(_ title: S, selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
165
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
166
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) {
|
|
167
|
+
self.init(selection: selection, maxSelectionCount: maxSelectionCount, selectionBehavior: selectionBehavior,
|
|
168
|
+
matching: filter, preferredItemEncoding: preferredItemEncoding) { Text(title) }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public init(_ titleKey: LocalizedStringKey, selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
172
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
173
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic, photoLibrary: PHPhotoLibrary) {
|
|
174
|
+
self.init(selection: selection, maxSelectionCount: maxSelectionCount, selectionBehavior: selectionBehavior,
|
|
175
|
+
matching: filter, preferredItemEncoding: preferredItemEncoding, photoLibrary: photoLibrary) { Text(titleKey) }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@_disfavoredOverload
|
|
179
|
+
public init<S: StringProtocol>(_ title: S, selection: Binding<[PhotosPickerItem]>, maxSelectionCount: Int? = nil,
|
|
180
|
+
selectionBehavior: PhotosPickerSelectionBehavior = .default, matching filter: PHPickerFilter? = nil,
|
|
181
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
182
|
+
photoLibrary: PHPhotoLibrary) {
|
|
183
|
+
self.init(selection: selection, maxSelectionCount: maxSelectionCount, selectionBehavior: selectionBehavior,
|
|
184
|
+
matching: filter, preferredItemEncoding: preferredItemEncoding, photoLibrary: photoLibrary) { Text(title) }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// MARK: .photosPicker(isPresented:)
|
|
189
|
+
|
|
190
|
+
/// Opens the picker when `isPresented` becomes true (or is true when the
|
|
191
|
+
/// view appears) and sets it back to false once the viewer is done. Opening
|
|
192
|
+
/// the chooser needs a tap, so set `isPresented` from a button's action.
|
|
193
|
+
struct _PhotosPickerPresenter<Content: View>: View {
|
|
194
|
+
let content: Content
|
|
195
|
+
let isPresented: Binding<Bool>
|
|
196
|
+
let pick: _PhotosPick
|
|
197
|
+
let apply: @MainActor ([PhotosPickerItem]) -> Void
|
|
198
|
+
|
|
199
|
+
var body: some View {
|
|
200
|
+
let isPresented = isPresented
|
|
201
|
+
let pick = pick
|
|
202
|
+
let apply = apply
|
|
203
|
+
return content.onChange(of: isPresented.wrappedValue, initial: true) { _, presented in
|
|
204
|
+
guard presented else { return }
|
|
205
|
+
MainActor.assumeIsolated {
|
|
206
|
+
pick.start(apply) { isPresented.wrappedValue = false }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
extension View {
|
|
213
|
+
public func photosPicker(isPresented: Binding<Bool>, selection: Binding<PhotosPickerItem?>,
|
|
214
|
+
matching filter: PHPickerFilter? = nil,
|
|
215
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) -> some View {
|
|
216
|
+
_PhotosPickerPresenter(content: self, isPresented: isPresented,
|
|
217
|
+
pick: _PhotosPick(filter: filter, limit: 1, multiple: false, exposesIdentifiers: false),
|
|
218
|
+
apply: { selection.wrappedValue = $0.first })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public func photosPicker(isPresented: Binding<Bool>, selection: Binding<PhotosPickerItem?>,
|
|
222
|
+
matching filter: PHPickerFilter? = nil,
|
|
223
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
224
|
+
photoLibrary: PHPhotoLibrary) -> some View {
|
|
225
|
+
_PhotosPickerPresenter(content: self, isPresented: isPresented,
|
|
226
|
+
pick: _PhotosPick(filter: filter, limit: 1, multiple: false, exposesIdentifiers: true),
|
|
227
|
+
apply: { selection.wrappedValue = $0.first })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public func photosPicker(isPresented: Binding<Bool>, selection: Binding<[PhotosPickerItem]>,
|
|
231
|
+
maxSelectionCount: Int? = nil, selectionBehavior: PhotosPickerSelectionBehavior = .default,
|
|
232
|
+
matching filter: PHPickerFilter? = nil,
|
|
233
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic) -> some View {
|
|
234
|
+
_PhotosPickerPresenter(content: self, isPresented: isPresented,
|
|
235
|
+
pick: _PhotosPick(filter: filter, limit: maxSelectionCount, multiple: maxSelectionCount != 1, exposesIdentifiers: false),
|
|
236
|
+
apply: { selection.wrappedValue = $0 })
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public func photosPicker(isPresented: Binding<Bool>, selection: Binding<[PhotosPickerItem]>,
|
|
240
|
+
maxSelectionCount: Int? = nil, selectionBehavior: PhotosPickerSelectionBehavior = .default,
|
|
241
|
+
matching filter: PHPickerFilter? = nil,
|
|
242
|
+
preferredItemEncoding: PhotosPickerItem.EncodingDisambiguationPolicy = .automatic,
|
|
243
|
+
photoLibrary: PHPhotoLibrary) -> some View {
|
|
244
|
+
_PhotosPickerPresenter(content: self, isPresented: isPresented,
|
|
245
|
+
pick: _PhotosPick(filter: filter, limit: maxSelectionCount, multiple: maxSelectionCount != 1, exposesIdentifiers: true),
|
|
246
|
+
apply: { selection.wrappedValue = $0 })
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// PhotosUI's model types: the picked item, the filter and the library. The
|
|
2
|
+
// browser has no photo library; the page's file chooser stands in for it
|
|
3
|
+
// (docs/ops-protocol.md, "Photos picker"). A pick delivers every chosen
|
|
4
|
+
// file's bytes with the selection, so `loadTransferable` answers at once.
|
|
5
|
+
|
|
6
|
+
import Foundation
|
|
7
|
+
import FoundationBridge
|
|
8
|
+
// As on iOS, PhotosUI brings Photos along (`PHPhotoLibrary`, `PHAsset`).
|
|
9
|
+
@_exported import Photos
|
|
10
|
+
import SwiftUI
|
|
11
|
+
import UniformTypeIdentifiers
|
|
12
|
+
|
|
13
|
+
/// One picked photo or video. Two items are equal when they come from the
|
|
14
|
+
/// same pick of the same file; picking the same file again makes a new item,
|
|
15
|
+
/// so `onChange(of:)` sees every pick.
|
|
16
|
+
public struct PhotosPickerItem: Equatable, Hashable, Sendable {
|
|
17
|
+
/// How the picker hands over a photo with several encodings. Accepted for
|
|
18
|
+
/// source compatibility: the browser delivers the file as it is.
|
|
19
|
+
public enum EncodingDisambiguationPolicy: Equatable, Hashable, Sendable {
|
|
20
|
+
case automatic
|
|
21
|
+
case current
|
|
22
|
+
case compatible
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// The asset's identifier in the photo library: like iOS, nil unless the
|
|
26
|
+
/// picker was given a `photoLibrary`.
|
|
27
|
+
public let itemIdentifier: String?
|
|
28
|
+
/// The file's type (from its MIME type, else its extension).
|
|
29
|
+
public let supportedContentTypes: [UTType]
|
|
30
|
+
|
|
31
|
+
/// Identity: unique per picked file per pick.
|
|
32
|
+
let key: String
|
|
33
|
+
let file: _PickedFile?
|
|
34
|
+
|
|
35
|
+
/// An item for an asset you already know the identifier of. The browser
|
|
36
|
+
/// has no library to load it from: `loadTransferable` returns nil.
|
|
37
|
+
public init(itemIdentifier: String) {
|
|
38
|
+
self.itemIdentifier = itemIdentifier
|
|
39
|
+
supportedContentTypes = []
|
|
40
|
+
key = "id:\(itemIdentifier)"
|
|
41
|
+
file = nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
init(file: _PickedFile, key: String, exposesIdentifier: Bool) {
|
|
45
|
+
itemIdentifier = exposesIdentifier ? key : nil
|
|
46
|
+
supportedContentTypes = file.contentType.map { [$0] } ?? []
|
|
47
|
+
self.key = key
|
|
48
|
+
self.file = file
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static func == (lhs: PhotosPickerItem, rhs: PhotosPickerItem) -> Bool { lhs.key == rhs.key }
|
|
52
|
+
public func hash(into hasher: inout Hasher) { hasher.combine(key) }
|
|
53
|
+
|
|
54
|
+
/// The item as `type`. `Data` is the file's bytes; `Image` shows the
|
|
55
|
+
/// picture (a still image the browser could decode). Any other type
|
|
56
|
+
/// imports through its first representation that takes the file's type
|
|
57
|
+
/// (a `FileRepresentation(importedContentType: .image)` receives a copy
|
|
58
|
+
/// of the file); nil when none does.
|
|
59
|
+
nonisolated(nonsending) public func loadTransferable<T: Transferable>(type: T.Type) async throws -> T? {
|
|
60
|
+
if let known = try loadKnown(type) { return known }
|
|
61
|
+
return try await loadImported(type)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// The completion-handler form. For `Data` and `Image` the handler runs
|
|
65
|
+
/// before this returns; other types load in a task.
|
|
66
|
+
@discardableResult
|
|
67
|
+
public func loadTransferable<T: Transferable>(type: T.Type,
|
|
68
|
+
completionHandler: @escaping @Sendable (Result<T?, any Error>) -> Void) -> Progress {
|
|
69
|
+
let progress = Progress(totalUnitCount: 1)
|
|
70
|
+
do {
|
|
71
|
+
if let known = try loadKnown(type) {
|
|
72
|
+
completionHandler(.success(known))
|
|
73
|
+
progress.completedUnitCount = 1
|
|
74
|
+
return progress
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
completionHandler(.failure(error))
|
|
78
|
+
progress.completedUnitCount = 1
|
|
79
|
+
return progress
|
|
80
|
+
}
|
|
81
|
+
let item = self
|
|
82
|
+
let job = _PhotosLoadJob(progress: progress, work: { try await item.loadImported(type) },
|
|
83
|
+
done: { result in completionHandler(result.map { $0 as? T }) })
|
|
84
|
+
job.start()
|
|
85
|
+
return progress
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// `Data` and `Image`, answered from the pick at once: `.some(value)`;
|
|
89
|
+
/// nil for the other types.
|
|
90
|
+
private func loadKnown<T: Transferable>(_ type: T.Type) throws -> T?? {
|
|
91
|
+
guard T.self == Data.self || T.self == Image.self else { return nil }
|
|
92
|
+
guard let file else { return .some(nil) }
|
|
93
|
+
if T.self == Data.self {
|
|
94
|
+
guard let data = Data(base64Encoded: file.base64) else {
|
|
95
|
+
throw CocoaError(.fileReadCorruptFile)
|
|
96
|
+
}
|
|
97
|
+
return .some(data as? T)
|
|
98
|
+
}
|
|
99
|
+
guard let url = file.url, let width = file.pixelWidth, let height = file.pixelHeight else { return .some(nil) }
|
|
100
|
+
return .some(Image(_url: url, pixelWidth: width, pixelHeight: height, scale: 1) as? T)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Any other type, through its importing representations.
|
|
104
|
+
nonisolated(nonsending) func loadImported<T: Transferable>(_ type: T.Type) async throws -> T? {
|
|
105
|
+
guard let file, let contentType = file.contentType else { return nil }
|
|
106
|
+
guard _Transfer.canImport(T.self, contentType: contentType) else { return nil }
|
|
107
|
+
guard let data = Data(base64Encoded: file.base64) else { throw CocoaError(.fileReadCorruptFile) }
|
|
108
|
+
let name = file.name.isEmpty ? nil : (file.name as NSString).deletingPathExtension
|
|
109
|
+
return try await _Transfer.import(T.self, from: _TransferContent(contentType: contentType, data: data, suggestedName: name))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// One completion-handler load of a type that imports through its
|
|
114
|
+
/// representations (type-erased, so the task carries no generic context).
|
|
115
|
+
final class _PhotosLoadJob: @unchecked Sendable {
|
|
116
|
+
let work: () async throws -> Any?
|
|
117
|
+
let done: (Result<Any?, any Error>) -> Void
|
|
118
|
+
let progress: Progress
|
|
119
|
+
|
|
120
|
+
init(progress: Progress, work: @escaping () async throws -> Any?, done: @escaping (Result<Any?, any Error>) -> Void) {
|
|
121
|
+
self.work = work
|
|
122
|
+
self.done = done
|
|
123
|
+
self.progress = progress
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func start() {
|
|
127
|
+
Task { await self.run() }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func run() async {
|
|
131
|
+
var result: Result<Any?, any Error>
|
|
132
|
+
do {
|
|
133
|
+
let value = try await work()
|
|
134
|
+
result = .success(value)
|
|
135
|
+
} catch {
|
|
136
|
+
result = .failure(error)
|
|
137
|
+
}
|
|
138
|
+
progress.completedUnitCount = 1
|
|
139
|
+
done(result)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// A file the page picked: its bytes (base64), type and, for a picture the
|
|
144
|
+
/// browser could decode, an object URL it can show and its pixel size.
|
|
145
|
+
struct _PickedFile: Sendable {
|
|
146
|
+
let name: String
|
|
147
|
+
let mimeType: String
|
|
148
|
+
let base64: String
|
|
149
|
+
let url: String?
|
|
150
|
+
let pixelWidth: Double?
|
|
151
|
+
let pixelHeight: Double?
|
|
152
|
+
|
|
153
|
+
/// From one entry of a `pickFiles` answer: `{name, type, data, url?, width?, height?}`.
|
|
154
|
+
init?(_ json: _JSON) {
|
|
155
|
+
guard case .object(let fields) = json, case .string(let data)? = fields["data"] else { return nil }
|
|
156
|
+
if case .string(let name)? = fields["name"] { self.name = name } else { name = "" }
|
|
157
|
+
if case .string(let type)? = fields["type"] { mimeType = type.lowercased() } else { mimeType = "" }
|
|
158
|
+
base64 = data
|
|
159
|
+
if case .string(let url)? = fields["url"] { self.url = url } else { url = nil }
|
|
160
|
+
if case .number(let width)? = fields["width"], case .number(let height)? = fields["height"], width > 0, height > 0 {
|
|
161
|
+
pixelWidth = width
|
|
162
|
+
pixelHeight = height
|
|
163
|
+
} else {
|
|
164
|
+
pixelWidth = nil
|
|
165
|
+
pixelHeight = nil
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// The file's type: its MIME type when the browser knows it, else its extension.
|
|
170
|
+
var contentType: UTType? {
|
|
171
|
+
if !mimeType.isEmpty, let type = UTType(mimeType: mimeType) { return type }
|
|
172
|
+
guard let dot = name.lastIndex(of: "."), dot != name.index(before: name.endIndex) else { return nil }
|
|
173
|
+
return UTType(filenameExtension: String(name[name.index(after: dot)...]))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// The MIME type the filter is matched against (from the extension when the browser reported none).
|
|
177
|
+
var effectiveMIMEType: String {
|
|
178
|
+
mimeType.isEmpty ? (contentType?.preferredMIMEType ?? "") : mimeType
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// MARK: Filter
|
|
184
|
+
|
|
185
|
+
/// Which assets the picker offers. In the browser it becomes the file
|
|
186
|
+
/// chooser's `accept` (`image/*`, `video/*`); the picked files are matched
|
|
187
|
+
/// against it again, since a chooser lets the viewer pick any file.
|
|
188
|
+
public struct PHPickerFilter: Hashable, Sendable {
|
|
189
|
+
indirect enum Rule: Hashable, Sendable {
|
|
190
|
+
case media(Media)
|
|
191
|
+
case any([PHPickerFilter])
|
|
192
|
+
case all([PHPickerFilter])
|
|
193
|
+
case not(PHPickerFilter)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// What a kind of asset is made of, as far as a file's MIME type tells.
|
|
197
|
+
enum Media: Hashable, Sendable {
|
|
198
|
+
case image, video, imageOrVideo
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let rule: Rule
|
|
202
|
+
|
|
203
|
+
public static let images = PHPickerFilter(rule: .media(.image))
|
|
204
|
+
public static let videos = PHPickerFilter(rule: .media(.video))
|
|
205
|
+
public static let livePhotos = PHPickerFilter(rule: .media(.image))
|
|
206
|
+
public static let depthEffectPhotos = PHPickerFilter(rule: .media(.image))
|
|
207
|
+
public static let panoramas = PHPickerFilter(rule: .media(.image))
|
|
208
|
+
public static let screenshots = PHPickerFilter(rule: .media(.image))
|
|
209
|
+
public static let bursts = PHPickerFilter(rule: .media(.image))
|
|
210
|
+
public static let screenRecordings = PHPickerFilter(rule: .media(.video))
|
|
211
|
+
public static let slomoVideos = PHPickerFilter(rule: .media(.video))
|
|
212
|
+
public static let timelapseVideos = PHPickerFilter(rule: .media(.video))
|
|
213
|
+
public static let cinematicVideos = PHPickerFilter(rule: .media(.video))
|
|
214
|
+
public static let spatialMedia = PHPickerFilter(rule: .media(.imageOrVideo))
|
|
215
|
+
|
|
216
|
+
public static func any(of subfilters: [PHPickerFilter]) -> PHPickerFilter { PHPickerFilter(rule: .any(subfilters)) }
|
|
217
|
+
public static func all(of subfilters: [PHPickerFilter]) -> PHPickerFilter { PHPickerFilter(rule: .all(subfilters)) }
|
|
218
|
+
public static func not(_ filter: PHPickerFilter) -> PHPickerFilter { PHPickerFilter(rule: .not(filter)) }
|
|
219
|
+
|
|
220
|
+
/// Whether a file of this MIME type passes. A photo library holds only
|
|
221
|
+
/// pictures and movies, so nothing else ever does.
|
|
222
|
+
func matches(mimeType: String) -> Bool {
|
|
223
|
+
let isImage = mimeType.hasPrefix("image/")
|
|
224
|
+
let isVideo = mimeType.hasPrefix("video/")
|
|
225
|
+
guard isImage || isVideo else { return false }
|
|
226
|
+
return evaluate(isImage: isImage)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private func evaluate(isImage: Bool) -> Bool {
|
|
230
|
+
switch rule {
|
|
231
|
+
case .media(.image): return isImage
|
|
232
|
+
case .media(.video): return !isImage
|
|
233
|
+
case .media(.imageOrVideo): return true
|
|
234
|
+
case .any(let filters): return filters.contains { $0.evaluate(isImage: isImage) }
|
|
235
|
+
case .all(let filters): return filters.allSatisfy { $0.evaluate(isImage: isImage) }
|
|
236
|
+
case .not(let filter): return !filter.evaluate(isImage: isImage)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// The file chooser's `accept`: the media kinds that can pass.
|
|
241
|
+
var accept: String {
|
|
242
|
+
[(evaluate(isImage: true), "image/*"), (evaluate(isImage: false), "video/*")]
|
|
243
|
+
.filter(\.0).map(\.1).joined(separator: ",")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MARK: Library
|
|
248
|
+
|
|
249
|
+
// `PHPhotoLibrary`, `PHAccessLevel` and `PHAuthorizationStatus` are Photos'
|
|
250
|
+
// (Sources/Photos), which PhotosUI re-exports, as on iOS.
|
|
251
|
+
|
|
252
|
+
/// How a multiple-selection picker orders and updates its selection.
|
|
253
|
+
/// Accepted for source compatibility: the browser's chooser returns the
|
|
254
|
+
/// files in its own order, once.
|
|
255
|
+
public struct PhotosPickerSelectionBehavior: Equatable, Hashable, Sendable {
|
|
256
|
+
let name: String
|
|
257
|
+
public static let `default` = PhotosPickerSelectionBehavior(name: "default")
|
|
258
|
+
public static let ordered = PhotosPickerSelectionBehavior(name: "ordered")
|
|
259
|
+
public static let continuous = PhotosPickerSelectionBehavior(name: "continuous")
|
|
260
|
+
public static let continuousAndOrdered = PhotosPickerSelectionBehavior(name: "continuousAndOrdered")
|
|
261
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// `import QuickLook` for the browser: a preview is the browser's own viewer
|
|
2
|
+
// in a new tab. `.quickLookPreview($url)` (and `(_:in:)`, which previews the
|
|
3
|
+
// selection) opens the URL when the binding is set, then sets it back to nil,
|
|
4
|
+
// as the preview's dismissal does on iOS: a web URL opens as a link would,
|
|
5
|
+
// and a file of the app's (Ice Cubes saves the picture it previews into its
|
|
6
|
+
// caches) is handed to the page, which opens it as a blob in a new tab, or
|
|
7
|
+
// downloads it when the browser blocks the tab (a tab opened long after the
|
|
8
|
+
// tap that asked for it). Both are a `media` host request with `action:
|
|
9
|
+
// "open"` (docs/ops-protocol.md, "Video and audio").
|
|
10
|
+
//
|
|
11
|
+
// `QLPreviewController` does the same for its data source's current item when
|
|
12
|
+
// presented, then dismisses itself, like SafariServices' controller.
|
|
13
|
+
|
|
14
|
+
import Foundation
|
|
15
|
+
import FoundationBridge
|
|
16
|
+
import SwiftUI
|
|
17
|
+
import UIKit
|
|
18
|
+
|
|
19
|
+
/// The page's side of a preview; tests replace `open`.
|
|
20
|
+
@MainActor
|
|
21
|
+
public enum _QuickLookHost {
|
|
22
|
+
public static var open: @MainActor (URL) async -> Void = { url in
|
|
23
|
+
let params: _JSON
|
|
24
|
+
if url.isFileURL {
|
|
25
|
+
guard let data = try? Data(contentsOf: url) else { return }
|
|
26
|
+
params = .object([
|
|
27
|
+
"action": .string("open"),
|
|
28
|
+
"name": .string(url.lastPathComponent),
|
|
29
|
+
"type": .string(_mediaType(forPathExtension: url.pathExtension)),
|
|
30
|
+
"data": .string(_base64(Array(data))),
|
|
31
|
+
])
|
|
32
|
+
} else {
|
|
33
|
+
params = .object(["action": .string("open"), "url": .string(url.absoluteString)])
|
|
34
|
+
}
|
|
35
|
+
_ = try? await _Runtime.shared._hostRequest(kind: "media", params: params)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// The media type the page opens a file as, from its extension.
|
|
40
|
+
func _mediaType(forPathExtension pathExtension: String) -> String {
|
|
41
|
+
switch pathExtension.lowercased() {
|
|
42
|
+
case "jpg", "jpeg": "image/jpeg"
|
|
43
|
+
case "png": "image/png"
|
|
44
|
+
case "gif": "image/gif"
|
|
45
|
+
case "webp": "image/webp"
|
|
46
|
+
case "heic", "heif": "image/heic"
|
|
47
|
+
case "avif": "image/avif"
|
|
48
|
+
case "svg": "image/svg+xml"
|
|
49
|
+
case "pdf": "application/pdf"
|
|
50
|
+
case "mp4", "m4v": "video/mp4"
|
|
51
|
+
case "mov": "video/quicktime"
|
|
52
|
+
case "webm": "video/webm"
|
|
53
|
+
case "mp3": "audio/mpeg"
|
|
54
|
+
case "m4a": "audio/mp4"
|
|
55
|
+
case "wav": "audio/wav"
|
|
56
|
+
case "txt", "md", "swift": "text/plain"
|
|
57
|
+
case "html", "htm": "text/html"
|
|
58
|
+
case "json": "application/json"
|
|
59
|
+
case "csv": "text/csv"
|
|
60
|
+
default: "application/octet-stream"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extension View {
|
|
65
|
+
/// Previews the URL in a new tab when `item` is set, then sets it to nil.
|
|
66
|
+
public func quickLookPreview(_ item: Binding<URL?>) -> some View {
|
|
67
|
+
modifier(_QuickLookPreviewModifier(item: item))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Previews the selected URL (the browser shows one file at a time; the
|
|
71
|
+
/// other items are not offered), then sets the selection to nil.
|
|
72
|
+
public func quickLookPreview<Items: RandomAccessCollection>(_ selection: Binding<Items.Element?>, in items: Items) -> some View
|
|
73
|
+
where Items.Element == URL {
|
|
74
|
+
modifier(_QuickLookPreviewModifier(item: selection))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
struct _QuickLookPreviewModifier: ViewModifier {
|
|
79
|
+
@Binding var item: URL?
|
|
80
|
+
|
|
81
|
+
func body(content: Content) -> some View {
|
|
82
|
+
content.onChange(of: item, initial: true) { _, url in
|
|
83
|
+
guard let url else { return }
|
|
84
|
+
item = nil
|
|
85
|
+
Task { @MainActor in await _QuickLookHost.open(url) }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// MARK: - QLPreviewController
|
|
91
|
+
|
|
92
|
+
/// Something a preview controller can show. `URL` conforms.
|
|
93
|
+
public protocol QLPreviewItem {
|
|
94
|
+
var previewItemURL: URL? { get }
|
|
95
|
+
var previewItemTitle: String? { get }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
extension QLPreviewItem {
|
|
99
|
+
public var previewItemTitle: String? { nil }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
extension URL: QLPreviewItem {
|
|
103
|
+
public var previewItemURL: URL? { self }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
extension NSURL: QLPreviewItem {
|
|
107
|
+
public var previewItemURL: URL? { self as URL }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@MainActor
|
|
111
|
+
public protocol QLPreviewControllerDataSource: AnyObject {
|
|
112
|
+
func numberOfPreviewItems(in controller: QLPreviewController) -> Int
|
|
113
|
+
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> any QLPreviewItem
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// What a preview controller tells its owner. Every method is optional.
|
|
117
|
+
@MainActor
|
|
118
|
+
public protocol QLPreviewControllerDelegate: AnyObject {
|
|
119
|
+
func previewControllerWillDismiss(_ controller: QLPreviewController)
|
|
120
|
+
func previewControllerDidDismiss(_ controller: QLPreviewController)
|
|
121
|
+
func previewController(_ controller: QLPreviewController, shouldOpen url: URL, for item: any QLPreviewItem) -> Bool
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
extension QLPreviewControllerDelegate {
|
|
125
|
+
public func previewControllerWillDismiss(_ controller: QLPreviewController) {}
|
|
126
|
+
public func previewControllerDidDismiss(_ controller: QLPreviewController) {}
|
|
127
|
+
public func previewController(_ controller: QLPreviewController, shouldOpen url: URL, for item: any QLPreviewItem) -> Bool { true }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Previews its data source's current item in a new tab when presented (see
|
|
131
|
+
/// the top of the file), then dismisses itself.
|
|
132
|
+
@MainActor
|
|
133
|
+
open class QLPreviewController: UIViewController {
|
|
134
|
+
open weak var dataSource: (any QLPreviewControllerDataSource)?
|
|
135
|
+
open weak var delegate: (any QLPreviewControllerDelegate)?
|
|
136
|
+
open var currentPreviewItemIndex = 0
|
|
137
|
+
private var opened = false
|
|
138
|
+
|
|
139
|
+
public init() {
|
|
140
|
+
super.init(nibName: nil, bundle: nil)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public required init?(coder: NSCoder) { nil }
|
|
144
|
+
|
|
145
|
+
/// Any URL the page can open, or a file it can hand over.
|
|
146
|
+
public static func canPreview(_ item: any QLPreviewItem) -> Bool { item.previewItemURL != nil }
|
|
147
|
+
|
|
148
|
+
open var currentPreviewItem: (any QLPreviewItem)? {
|
|
149
|
+
guard let dataSource, currentPreviewItemIndex >= 0,
|
|
150
|
+
currentPreviewItemIndex < dataSource.numberOfPreviewItems(in: self) else { return nil }
|
|
151
|
+
return dataSource.previewController(self, previewItemAt: currentPreviewItemIndex)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
open func reloadData() {}
|
|
155
|
+
open func refreshCurrentPreviewItem() {}
|
|
156
|
+
|
|
157
|
+
open override func viewDidAppear(_ animated: Bool) {
|
|
158
|
+
super.viewDidAppear(animated)
|
|
159
|
+
guard !opened else { return }
|
|
160
|
+
opened = true
|
|
161
|
+
if let url = currentPreviewItem?.previewItemURL {
|
|
162
|
+
Task { @MainActor in await _QuickLookHost.open(url) }
|
|
163
|
+
}
|
|
164
|
+
delegate?.previewControllerWillDismiss(self)
|
|
165
|
+
dismiss(animated: animated)
|
|
166
|
+
delegate?.previewControllerDidDismiss(self)
|
|
167
|
+
}
|
|
168
|
+
}
|