@swiftbrowser/native 0.14.1 → 0.15.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.json +3 -3
- package/swift/Package.swift +5 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +6 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/RemoteImage.swift +88 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/TextAndImages.swift +3 -10
- package/wasm/SwiftBrowserNative.wasm +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftbrowser/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
|
|
6
6
|
"repository": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@babel/core": "^7.29.7",
|
|
62
|
-
"@swiftbrowser/react": "0.
|
|
63
|
-
"@swiftbrowser/web": "0.
|
|
62
|
+
"@swiftbrowser/react": "0.15.0",
|
|
63
|
+
"@swiftbrowser/web": "0.15.0",
|
|
64
64
|
"esbuild": "^0.28.2",
|
|
65
65
|
"react-refresh": "^0.17.0"
|
|
66
66
|
},
|
package/swift/Package.swift
CHANGED
|
@@ -23,10 +23,14 @@ let package = Package(
|
|
|
23
23
|
.library(name: "SwiftBrowserProtocol", targets: ["SwiftBrowserProtocol"]),
|
|
24
24
|
.library(name: "SwiftBrowserTree", targets: ["SwiftBrowserTree"]),
|
|
25
25
|
],
|
|
26
|
+
dependencies: [
|
|
27
|
+
// SVG images, which the system's image decoding does not read (Views/Kinds/RemoteImage.swift).
|
|
28
|
+
.package(url: "https://github.com/swhitty/SwiftDraw.git", .upToNextMinor(from: "0.29.0")),
|
|
29
|
+
],
|
|
26
30
|
targets: [
|
|
27
31
|
.target(name: "SwiftBrowserProtocol"),
|
|
28
32
|
.target(name: "SwiftBrowserTree", dependencies: ["SwiftBrowserProtocol"]),
|
|
29
|
-
.target(name: "SwiftBrowserNative", dependencies: ["SwiftBrowserProtocol", "SwiftBrowserTree"]),
|
|
33
|
+
.target(name: "SwiftBrowserNative", dependencies: ["SwiftBrowserProtocol", "SwiftBrowserTree", .product(name: "SwiftDraw", package: "SwiftDraw", condition: .when(platforms: [.iOS, .macOS]))]),
|
|
30
34
|
// Apple platforms only (JavaScriptCore): the workers; empty elsewhere.
|
|
31
35
|
]
|
|
32
36
|
)
|
|
@@ -156,9 +156,14 @@ struct NavigationScreenElement: View {
|
|
|
156
156
|
case "secondary": return .secondaryAction
|
|
157
157
|
case "bottom": return bottomPlacement
|
|
158
158
|
case "largeTitle":
|
|
159
|
-
//
|
|
159
|
+
// In the large title's place: the shim's placement, and SwiftUI's
|
|
160
|
+
// from iOS 26 (it stands in for `navigationTitle`'s text while the
|
|
161
|
+
// title is large). Before that SwiftUI has none: the bar's trailing edge.
|
|
160
162
|
#if canImport(FoundationBridge)
|
|
161
163
|
return .largeTitle
|
|
164
|
+
#elseif os(iOS)
|
|
165
|
+
if #available(iOS 26, *) { return .largeTitle }
|
|
166
|
+
return trailingPlacement
|
|
162
167
|
#else
|
|
163
168
|
return trailingPlacement
|
|
164
169
|
#endif
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#if canImport(SwiftUI)
|
|
2
|
+
import Foundation
|
|
3
|
+
import SwiftUI
|
|
4
|
+
#if canImport(FoundationBridge)
|
|
5
|
+
import FoundationBridge // `AsyncImage`, against the shim
|
|
6
|
+
#endif
|
|
7
|
+
#if canImport(SwiftDraw)
|
|
8
|
+
import SwiftDraw
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
/// An `image` with a URL: SwiftUI's `AsyncImage`, and for an SVG, which the
|
|
12
|
+
/// system's image decoding does not read (a logo found on a website often
|
|
13
|
+
/// is one), SwiftDraw's parser and renderer, drawn as vectors at any size.
|
|
14
|
+
/// SwiftDraw is the package's dependency on Apple platforms only; the round
|
|
15
|
+
/// trip's build of these views has the page, which draws SVG itself.
|
|
16
|
+
struct RemoteImage: View {
|
|
17
|
+
let url: URL?
|
|
18
|
+
let resizable: Bool
|
|
19
|
+
let width: Double
|
|
20
|
+
let height: Double
|
|
21
|
+
|
|
22
|
+
var body: some View {
|
|
23
|
+
AsyncImage(url: url) { phase in
|
|
24
|
+
if let image = phase.image {
|
|
25
|
+
if resizable { image.resizable() } else { image }
|
|
26
|
+
} else if phase.error != nil, let url {
|
|
27
|
+
undecoded(url)
|
|
28
|
+
} else {
|
|
29
|
+
placeholder
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Nothing, at the image's size when the app knows it.
|
|
35
|
+
@ViewBuilder private var placeholder: some View {
|
|
36
|
+
if width > 0, height > 0 {
|
|
37
|
+
Color.clear.frame(width: width, height: height)
|
|
38
|
+
} else {
|
|
39
|
+
Color.clear
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// What `AsyncImage` could not decode: an SVG, drawn by SwiftDraw, else nothing.
|
|
44
|
+
@ViewBuilder private func undecoded(_ url: URL) -> some View {
|
|
45
|
+
#if canImport(SwiftDraw)
|
|
46
|
+
RemoteSVG(url: url, resizable: resizable) { placeholder }
|
|
47
|
+
#else
|
|
48
|
+
placeholder
|
|
49
|
+
#endif
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#if canImport(SwiftDraw)
|
|
54
|
+
/// The SVG at `url`, loaded through the shared session (so the cookies and
|
|
55
|
+
/// cache `AsyncImage` used apply), or the placeholder while it loads and
|
|
56
|
+
/// when the data is not an SVG.
|
|
57
|
+
struct RemoteSVG<Placeholder: View>: View {
|
|
58
|
+
let url: URL
|
|
59
|
+
let resizable: Bool
|
|
60
|
+
@ViewBuilder let placeholder: () -> Placeholder
|
|
61
|
+
@State private var loaded: LoadedSVG?
|
|
62
|
+
|
|
63
|
+
var body: some View {
|
|
64
|
+
Group {
|
|
65
|
+
if let loaded, loaded.url == url, let svg = loaded.svg {
|
|
66
|
+
if resizable { SVGView(svg: svg).resizable() } else { SVGView(svg: svg) }
|
|
67
|
+
} else {
|
|
68
|
+
placeholder()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
.task(id: url) {
|
|
72
|
+
loaded = LoadedSVG(url: url, svg: await loadSVG(from: url))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct LoadedSVG: Equatable {
|
|
78
|
+
let url: URL
|
|
79
|
+
let svg: SVG?
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The SVG at `url`, or nil when it cannot be loaded or is not an SVG.
|
|
83
|
+
func loadSVG(from url: URL, session: URLSession = .shared) async -> SVG? {
|
|
84
|
+
guard let data = try? await session.data(from: url).0 else { return nil }
|
|
85
|
+
return SVG(data: data)
|
|
86
|
+
}
|
|
87
|
+
#endif
|
|
88
|
+
#endif
|
|
@@ -60,7 +60,8 @@ struct TextElement: View {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/// `image`: an SF Symbol (every name is right here, unlike the web's mapped
|
|
63
|
-
/// glyphs), an asset catalog image, a remote image
|
|
63
|
+
/// glyphs), an asset catalog image, a remote image (RemoteImage.swift: SVG
|
|
64
|
+
/// too), or a bitmap recipe (not drawn yet).
|
|
64
65
|
struct ImageElement: View {
|
|
65
66
|
let node: ElementNode
|
|
66
67
|
|
|
@@ -82,15 +83,7 @@ struct ImageElement: View {
|
|
|
82
83
|
Image(name)
|
|
83
84
|
}
|
|
84
85
|
case .url(let url, let resizable, let width, let height):
|
|
85
|
-
|
|
86
|
-
if let image = phase.image {
|
|
87
|
-
if resizable { image.resizable() } else { image }
|
|
88
|
-
} else if width > 0, height > 0 {
|
|
89
|
-
Color.clear.frame(width: width, height: height)
|
|
90
|
-
} else {
|
|
91
|
-
Color.clear
|
|
92
|
-
}
|
|
93
|
-
}
|
|
86
|
+
RemoteImage(url: URL(string: url), resizable: resizable, width: width, height: height)
|
|
94
87
|
case .bitmap(_, _, let width, let height):
|
|
95
88
|
// A `UIImage` recipe the page would decode; not drawn natively yet.
|
|
96
89
|
Rectangle().fill(.quaternary).frame(width: width > 0 ? CGFloat(width) : nil, height: height > 0 ? CGFloat(height) : nil)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/wasm/SwiftBrowserNative.wasm
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 49302313 bytes
|
|
4
|
+
sha256: a2426ea2bd5032a173f7975b94c57b9f331404b419e57e87c5573e7350eac8da
|