diff --git a/Package.resolved b/Package.resolved index 0bd3fca8b..0851e737d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,12 +1,12 @@ { "pins" : [ { - "identity" : "liveview-native-core-swift", + "identity" : "liveview-native-core", "kind" : "remoteSourceControl", - "location" : "https://github.com/liveview-native/liveview-native-core-swift.git", + "location" : "https://github.com/liveview-native/liveview-native-core.git", "state" : { - "revision" : "339594b395938e38f1fb2216a31c8791030ec9ba", - "version" : "0.2.1" + "revision" : "ced90995fda19a1b4eba0ea379112822246ee7ce", + "version" : "0.4.0-alpha-1" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/davidstump/SwiftPhoenixClient.git", "state" : { - "revision" : "613989bf2e562d8a851ea83741681c3439353b45", - "version" : "5.0.0" + "revision" : "588bf6baab5d049752748e19a4bff32421ea40ec", + "version" : "5.3.2" } }, { diff --git a/Package.swift b/Package.swift index 73b7b8695..06bbb9350 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.3.2"), .package(url: "https://github.com/davidstump/SwiftPhoenixClient.git", .upToNextMinor(from: "5.3.2")), .package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"), - .package(url: "https://github.com/liveview-native/liveview-native-core-swift.git", exact: "0.2.1"), + .package(url: "https://github.com/liveview-native/liveview-native-core.git", exact: "0.4.0-alpha-1"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.2"), .package(url: "https://github.com/apple/swift-markdown.git", from: "0.2.0"), @@ -45,7 +45,7 @@ let package = Package( "SwiftSoup", "SwiftPhoenixClient", .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), - .product(name: "LiveViewNativeCore", package: "liveview-native-core-swift"), + .product(name: "LiveViewNativeCore", package: "liveview-native-core"), "LiveViewNativeMacros", "LiveViewNativeStylesheet" ], @@ -146,7 +146,7 @@ let package = Package( name: "LiveViewNativeStylesheet", dependencies: [ "LiveViewNativeStylesheetMacros", - .product(name: "LiveViewNativeCore", package: "liveview-native-core-swift"), + .product(name: "LiveViewNativeCore", package: "liveview-native-core"), .product(name: "Parsing", package: "swift-parsing"), ] ), diff --git a/Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift b/Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift index bf51fe513..66b0a6906 100644 --- a/Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift +++ b/Sources/LiveViewNative/Coordinators/LiveSessionCoordinator.swift @@ -46,12 +46,12 @@ public class LiveSessionCoordinator: ObservableObject { @Published private(set) var stylesheet: Stylesheet? // Socket connection - var socket: Socket? + var socket: SwiftPhoenixClient.Socket? private var domValues: DOMValues! - private var liveReloadSocket: Socket? - private var liveReloadChannel: Channel? + private var liveReloadSocket: SwiftPhoenixClient.Socket? + private var liveReloadChannel: SwiftPhoenixClient.Channel? private var cancellables = Set() diff --git a/Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift b/Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift index 5104ef190..60f29702e 100644 --- a/Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift +++ b/Sources/LiveViewNative/Coordinators/LiveViewCoordinator.swift @@ -35,7 +35,7 @@ public class LiveViewCoordinator: ObservableObject { @_spi(LiveForm) public let session: LiveSessionCoordinator var url: URL - private var channel: Channel? + private var channel: SwiftPhoenixClient.Channel? @Published var document: LiveViewNativeCore.Document? private var elementChangedSubjects = [NodeRef:ObjectWillChangePublisher]() @@ -197,9 +197,7 @@ public class LiveViewCoordinator: ObservableObject { private func handleDiff(payload: Payload, baseURL: URL) throws { handleEvents(payload: payload) - let diff = try RootDiff(from: FragmentDecoder(data: payload)) - self.rendered = try self.rendered.merge(with: diff) - self.document?.merge(with: try Document.parse(self.rendered.buildString())) + try self.document?.mergeFragmentJson(payload) } private func handleEvents(payload: Payload) { @@ -329,7 +327,7 @@ public class LiveViewCoordinator: ObservableObject { case redirect(LiveRedirect) } - private func join(channel: Channel) -> AsyncThrowingStream { + private func join(channel: SwiftPhoenixClient.Channel) -> AsyncThrowingStream { return AsyncThrowingStream { [weak channel] (continuation: AsyncThrowingStream.Continuation) -> Void in channel?.join() .receive("ok") { [weak self, weak channel] message in @@ -385,21 +383,20 @@ public class LiveViewCoordinator: ObservableObject { private func handleJoinPayload(renderedPayload: Payload) { // todo: what should happen if decoding or parsing fails? - self.rendered = try! Root(from: FragmentDecoder(data: renderedPayload)) - self.document = try! LiveViewNativeCore.Document.parse(rendered.buildString()) - self.document?.on(.changed) { [unowned self] doc, nodeRef in - switch doc[nodeRef].data { + self.document = try! LiveViewNativeCore.Document.parseFragmentJson(payload: renderedPayload) + self.document?.on(.changed) { nodeRef, nodeData, parent in + switch nodeData { case .root: // when the root changes, update the `NavStackEntry` itself. self.objectWillChange.send() case .leaf: // text nodes don't have their own views, changes to them need to be handled by the parent Text view - if let parent = doc.getParent(nodeRef) { + if let parent = parent { self.elementChanged(nodeRef).send() } else { self.elementChanged(nodeRef).send() } - case .element: + case .nodeElement: // when a single element changes, send an update only to that element. self.elementChanged(nodeRef).send() } diff --git a/Sources/LiveViewNative/Live/LiveElement.swift b/Sources/LiveViewNative/Live/LiveElement.swift index 6e82b5772..6fbcc5581 100644 --- a/Sources/LiveViewNative/Live/LiveElement.swift +++ b/Sources/LiveViewNative/Live/LiveElement.swift @@ -115,7 +115,7 @@ public extension _LiveElementTracked { /// /// - Parameter predicate: The filter used to select child nodes for render. func children(_ predicate: (Node) -> Bool = { node in - !node.attributes.contains(where: { $0.name.namespace == nil && $0.name.name == "template" }) + !node.attributes().contains(where: { $0.name.namespace == nil && $0.name.name == "template" }) }) -> some View { context.coordinator.builder.fromNodes(_element.children.filter(predicate), context: context.storage) } @@ -126,9 +126,9 @@ public extension _LiveElementTracked { /// - Parameter includeDefault: Whether elements without a `template` attribute should be included in the filter. Defaults to `false`. func children(in template: Template, default includeDefault: Bool = false) -> some View { children { - $0.attributes.contains(where: { + $0.attributes().contains(where: { $0 == template - }) || (includeDefault && !$0.attributes.contains(where: { $0.name.namespace == nil && $0.name.name == "template" })) + }) || (includeDefault && !$0.attributes().contains(where: { $0.name.namespace == nil && $0.name.name == "template" })) } } @@ -138,7 +138,7 @@ public extension _LiveElementTracked { /// - Parameter includeDefault: Whether elements without a `template` attribute should be included in the filter. Defaults to `false`. func hasTemplate(_ template: Template, default includeDefault: Bool = false) -> Bool { _element.children.contains(where: { - for attribute in $0.attributes { + for attribute in $0.attributes() { if attribute == template { return true } else if includeDefault && attribute.name.namespace == nil && attribute.name.name == "template" { diff --git a/Sources/LiveViewNative/Property Wrappers/LiveContext.swift b/Sources/LiveViewNative/Property Wrappers/LiveContext.swift index 5caa80eca..b2f4f5269 100644 --- a/Sources/LiveViewNative/Property Wrappers/LiveContext.swift +++ b/Sources/LiveViewNative/Property Wrappers/LiveContext.swift @@ -58,7 +58,7 @@ public struct LiveContext: DynamicProperty { /// Ignores any children with a `template` attribute. public func buildChildren(of element: ElementNode) -> some View { return coordinator.builder.fromNodes(element.children().filter({ - if case let .element(element) = $0.data { + if case let .nodeElement(element) = $0.data() { return !element.attributes.contains(where: { $0.name == "template" }) } else { return true @@ -70,7 +70,7 @@ public struct LiveContext: DynamicProperty { _ template: String ) -> (NodeChildrenSequence.Element) -> Bool { { child in - if case let .element(element) = child.data, + if case let .nodeElement(element) = child.data(), element.attributes.first(where: { $0.name == "template" })?.value == template { return true @@ -81,7 +81,7 @@ public struct LiveContext: DynamicProperty { } private static func hasTemplateAttribute(_ child: NodeChildrenSequence.Element) -> Bool { - if case let .element(element) = child.data, + if case let .nodeElement(element) = child.data(), element.attributes.contains(where: { $0.name == "template" }) { return true @@ -127,7 +127,7 @@ public struct LiveContext: DynamicProperty { let namedSlotChildren = children.filter(Self.isTemplateElement(template)) if namedSlotChildren.isEmpty && includeDefaultSlot { let defaultSlotChildren = children.filter({ - if case let .element(element) = $0.data { + if case let .nodeElement(element) = $0.data() { return !element.attributes.contains(where: { $0.name.rawValue == "template" }) @@ -169,7 +169,7 @@ public struct LiveContext: DynamicProperty { of element: ElementNode ) -> [NodeChildrenSequence.Element] { element.children().filter { - !$0.attributes.contains(where: { $0.name.rawValue == "template" }) + !$0.attributes().contains(where: { $0.name.rawValue == "template" }) } } } diff --git a/Sources/LiveViewNative/Property Wrappers/ObservedElement.swift b/Sources/LiveViewNative/Property Wrappers/ObservedElement.swift index c4911567e..3c6485678 100644 --- a/Sources/LiveViewNative/Property Wrappers/ObservedElement.swift +++ b/Sources/LiveViewNative/Property Wrappers/ObservedElement.swift @@ -119,7 +119,7 @@ extension ObservedElement { if let _resolvedChildIDs { return _resolvedChildIDs } else { - let result = Set(self.resolvedChildren.map(\.id)) + let result = Set(self.resolvedChildren.map { $0.id()}) _resolvedChildIDs = result return result } @@ -178,3 +178,9 @@ extension ObservedElement { } } } + +private extension Optional where Wrapped == ElementNode { + var nodeRef: NodeRef? { + self?.node.id() + } +} diff --git a/Sources/LiveViewNative/Protocols/ContentBuilder.swift b/Sources/LiveViewNative/Protocols/ContentBuilder.swift index 887ca3d01..33643ff7f 100644 --- a/Sources/LiveViewNative/Protocols/ContentBuilder.swift +++ b/Sources/LiveViewNative/Protocols/ContentBuilder.swift @@ -484,14 +484,14 @@ public extension ContentBuilder { return try build( element .children() - .filter({ $0.attributes.contains(where: { $0.name == "template" && $0.value == template }) }), + .filter({ $0.attributes().contains(where: { $0.name == "template" && $0.value == template }) }), in: context ) } else { return try build( element .children() - .filter({ !$0.attributes.contains(where: { $0.name == "template" }) }), + .filter({ !$0.attributes().contains(where: { $0.name == "template" }) }), in: context ) } @@ -505,7 +505,7 @@ public extension ContentBuilder { return try build( element .children() - .filter({ $0.attributes.contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }), + .filter({ $0.attributes().contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }), in: context ) } @@ -552,7 +552,7 @@ public extension ContentBuilder { if let template { ViewTreeBuilder().fromNodes( element.children() - .filter({ $0.attributes.contains(where: { $0.name == "template" && $0.value == template }) }), + .filter({ $0.attributes().contains(where: { $0.name == "template" && $0.value == template }) }), context: context.context ) .environment(\.coordinatorEnvironment, context.coordinatorEnvironment) @@ -560,7 +560,7 @@ public extension ContentBuilder { } else { ViewTreeBuilder().fromNodes( element.children() - .filter({ !$0.attributes.contains(where: { $0.name == "template" }) }), + .filter({ !$0.attributes().contains(where: { $0.name == "template" }) }), context: context.context ) .environment(\.coordinatorEnvironment, context.coordinatorEnvironment) @@ -576,7 +576,7 @@ public extension ContentBuilder { ) -> some View { ViewTreeBuilder().fromNodes( element.children() - .filter({ $0.attributes.contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }), + .filter({ $0.attributes().contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }), context: context.context ) .environment(\.coordinatorEnvironment, context.coordinatorEnvironment) @@ -591,7 +591,7 @@ public extension ContentBuilder { ) -> SwiftUI.Text { element.children() .lazy - .filter({ $0.attributes.contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }) + .filter({ $0.attributes().contains(where: { $0.name == "template" && template.value.contains($0.value ?? "") }) }) .first?.asElement().flatMap({ Text(element: $0, overrideStylesheet: context.stylesheet).body }) ?? SwiftUI.Text("") } diff --git a/Sources/LiveViewNative/Stylesheets/ViewReference.swift b/Sources/LiveViewNative/Stylesheets/ViewReference.swift index dfe575133..ed3ba32cf 100644 --- a/Sources/LiveViewNative/Stylesheets/ViewReference.swift +++ b/Sources/LiveViewNative/Stylesheets/ViewReference.swift @@ -201,7 +201,7 @@ struct ToolbarTreeBuilder { // ToolbarTreeBuilder.fromNode may not be called with a root or leaf node switch node { case let .n(node): - if case .element(let element) = node.data { + if case .nodeElement(let element) = node.data() { Self.lookup(ElementNode(node: node, data: element)) } case let .e(error): @@ -271,7 +271,7 @@ struct CustomizableToolbarTreeBuilder { // CustomizableToolbarTreeBuilder.fromNode may not be called with a root or leaf node switch node { case let .n(node): - if case .element(let element) = node.data { + if case .nodeElement(let element) = node.data() { Self.lookup(ElementNode(node: node, data: element)) } case let .e(error): diff --git a/Sources/LiveViewNative/Utils/DOM.swift b/Sources/LiveViewNative/Utils/DOM.swift index a94c9e153..e641879c5 100644 --- a/Sources/LiveViewNative/Utils/DOM.swift +++ b/Sources/LiveViewNative/Utils/DOM.swift @@ -25,9 +25,9 @@ import LiveViewNativeCore /// - ``innerText()`` public struct ElementNode { let node: Node - let data: ElementData + let data: Element - init(node: Node, data: ElementData) { + init(node: Node, data: Element) { self.node = node self.data = data } @@ -40,9 +40,9 @@ public struct ElementNode { public func elementChildren() -> [ElementNode] { node.children().compactMap({ $0.asElement() }) } /// The namespace of the element. - public var namespace: String? { data.namespace } + public var namespace: String? { data.name.namespace } /// The tag name of the element. - public var tag: String { data.tag } + public var tag: String { data.name.name } /// The list of attributes present on this element. public var attributes: [LiveViewNativeCore.Attribute] { data.attributes } /// The attribute with the given name, or `nil` if there is no such attribute. @@ -91,7 +91,7 @@ public struct ElementNode { public func innerText() -> String { // TODO: should follow the spec and insert/collapse whitespace around elements self.children().compactMap { node in - if case .leaf(let content) = node.data { + if case .leaf(let content) = node.data() { return content } else { return nil @@ -113,7 +113,7 @@ public struct ElementNode { extension Node { func asElement() -> ElementNode? { - if case .element(let data) = self.data { + if case .nodeElement(let data) = self.data() { return ElementNode(node: self, data: data) } else { return nil diff --git a/Sources/LiveViewNative/ViewTree.swift b/Sources/LiveViewNative/ViewTree.swift index aeb3ba46e..473986ce3 100644 --- a/Sources/LiveViewNative/ViewTree.swift +++ b/Sources/LiveViewNative/ViewTree.swift @@ -29,12 +29,12 @@ struct ViewTreeBuilder { let context: LiveContextStorage var body: some View { - switch node.data { + switch node.data() { case .root: fatalError("ViewTreeBuilder.fromNode may not be called with the root node") case .leaf(let content): SwiftUI.Text(content) - case .element(let element): + case .nodeElement(let element): context.coordinator.builder.fromElement(ElementNode(node: node, data: element), context: context) } } @@ -48,7 +48,7 @@ struct ViewTreeBuilder { let withID = applyID(element: element, to: bound) let withIDAndTag = applyTag(element: element, to: withID) - return ObservedElement.Observer.Applicator(element.node.id) { + return ObservedElement.Observer.Applicator(element.node.id()) { withIDAndTag .environment(\.element, element) } @@ -288,7 +288,7 @@ private enum ForEachElement: Identifiable { case let .keyed(_, id): return id case let .unkeyed(node): - return "\(node.id)" + return "\(node.id().ref())" } } diff --git a/Sources/LiveViewNative/Views/Controls and Indicators/Links/ShareLink.swift b/Sources/LiveViewNative/Views/Controls and Indicators/Links/ShareLink.swift index ea5b00291..34f28bae6 100644 --- a/Sources/LiveViewNative/Views/Controls and Indicators/Links/ShareLink.swift +++ b/Sources/LiveViewNative/Views/Controls and Indicators/Links/ShareLink.swift @@ -106,8 +106,8 @@ struct ShareLink: View { public var body: some View { #if !os(tvOS) let useDefaultLabel = $liveElement.childNodes.filter({ - guard case let .element(data) = $0.data else { return true } - return data.tag != "SharePreview" + guard case let .nodeElement(data) = $0.data() else { return true } + return data.name.name != "SharePreview" }).isEmpty let subject = self.subject.flatMap(SwiftUI.Text.init) diff --git a/Sources/LiveViewNative/Views/Images/ImageView.swift b/Sources/LiveViewNative/Views/Images/ImageView.swift index 6e3c5928c..83fba9d22 100644 --- a/Sources/LiveViewNative/Views/Images/ImageView.swift +++ b/Sources/LiveViewNative/Views/Images/ImageView.swift @@ -129,8 +129,8 @@ struct ImageView: View { var label: SwiftUI.Text? { if let labelNode = $liveElement.childNodes.first { - switch labelNode.data { - case let .element(element): + switch labelNode.data() { + case let .nodeElement(element): return Text(element: ElementNode(node: labelNode, data: element), overrideStylesheet: nil).body case let .leaf(label): return .init(label) diff --git a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift index 77046a76a..9fbd8834a 100644 --- a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift +++ b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift @@ -171,7 +171,7 @@ struct List: View { private var content: some View { forEach( nodes: $liveElement.childNodes.filter({ - !$0.attributes.contains(where: { $0.name.namespace == nil && $0.name.name == "template" }) + !$0.attributes().contains(where: { $0.name.namespace == nil && $0.name.name == "template" }) }), context: $liveElement.context.storage ) diff --git a/Sources/LiveViewNative/Views/Text Input and Output/Text.swift b/Sources/LiveViewNative/Views/Text Input and Output/Text.swift index 487dd4bca..24a2c4449 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/Text.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/Text.swift @@ -213,14 +213,14 @@ struct Text: View { } } else { return $liveElement.childNodes.reduce(into: SwiftUI.Text("")) { prev, next in - switch next.data { - case let .element(data): + switch next.data() { + case let .nodeElement(data): guard !data.attributes.contains(where: { $0.name.namespace == nil && $0.name.name == "template" }) else { return } - + let element = ElementNode(node: next, data: data) - - switch data.tag { + + switch data.name.name { case "Text": prev = prev + Self( element: element,