Skip to content

Commit a22d80e

Browse files
authored
added binding for colors to support site theming (#43)
1 parent 25b4acb commit a22d80e

File tree

4 files changed

+181
-86
lines changed

4 files changed

+181
-86
lines changed

Sources/BuilderIO/Components/BuilderImage.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ struct BuilderImage: BuilderViewProtocol {
3434
(block.component?.options?.dictionaryValue?["fitContent"]?.boolValue ?? false)
3535
&& !(block.children?.isEmpty ?? true)
3636

37-
if let imageBinding = block.codeBindings(for: "image")?.stringValue {
38-
self.imageURL = URL(
39-
string: imageBinding)
40-
}
41-
4237
}
4338

4439
var body: some View {

Sources/BuilderIO/Components/BuilderText.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ struct BuilderText: BuilderViewProtocol {
1818

1919
self.responsiveStyles = getFinalStyle(responsiveStyles: block.responsiveStyles)
2020

21-
if let textBinding = block.codeBindings(for: "text") {
22-
self.text = textBinding.stringValue
23-
}
2421
}
2522

2623
var body: some View {

Sources/BuilderIO/Components/BuilderVideo.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ struct BuilderVideo: BuilderViewProtocol {
3636
if let videoString = options?["video"]?.stringValue {
3737
self.videoURL = URL(string: videoString)
3838
}
39-
if let videoBinding = block.codeBindings(for: "video")?.stringValue {
40-
self.videoURL = URL(string: videoBinding)
41-
}
4239

4340
// Video options
4441
self.autoPlay = options?["autoPlay"]?.boolValue ?? false
Lines changed: 181 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,221 @@
1-
import Foundation // For Codable, Data, etc.
1+
import Foundation
22

3-
// Schema for Builder blocks
3+
// MARK: - Core Data Models
4+
5+
/// Represents a single Builder.io block, conforming to Codable and Identifiable.
46
public struct BuilderBlockModel: Codable, Identifiable {
57
public var id: String
6-
public var properties: [String: String]? = [:]
7-
public var bindings: [String: String]? = [:]
8-
public var children: [BuilderBlockModel]? = []
9-
public var component: BuilderBlockComponent? = nil
10-
public var responsiveStyles: BuilderBlockResponsiveStyles? = BuilderBlockResponsiveStyles() // for inner style of the component
11-
public var actions: AnyCodable? = nil
12-
public var code: AnyCodable? = nil
13-
public var meta: AnyCodable? = nil
14-
public var linkUrl: String? = nil
15-
public var `repeat`: [String: String]? = [:]
16-
17-
public var stateBoundObjectModel: StateModel? = nil
18-
public var stateRepeatCollectionKey: StateRepeatCollectionKey? = nil
19-
20-
public var locale: String? = nil // Optional locale for the block
8+
public var properties: [String: String]?
9+
public var bindings: [String: String]?
10+
public var children: [BuilderBlockModel]?
11+
public var component: BuilderBlockComponent?
12+
public var responsiveStyles: BuilderBlockResponsiveStyles
13+
public var actions: AnyCodable?
14+
public var code: AnyCodable?
15+
public var meta: AnyCodable?
16+
public var linkUrl: String?
17+
public var `repeat`: [String: String]?
18+
public var locale: String?
19+
20+
// Internal state properties
21+
var stateBoundObjectModel: StateModel?
22+
var stateRepeatCollectionKey: StateRepeatCollectionKey?
23+
24+
public init(
25+
id: String = UUID().uuidString,
26+
properties: [String: String]? = nil,
27+
bindings: [String: String]? = nil,
28+
children: [BuilderBlockModel]? = nil,
29+
component: BuilderBlockComponent? = nil,
30+
responsiveStyles: BuilderBlockResponsiveStyles = .init(),
31+
actions: AnyCodable? = nil,
32+
code: AnyCodable? = nil,
33+
meta: AnyCodable? = nil,
34+
linkUrl: String? = nil,
35+
`repeat`: [String: String]? = nil,
36+
locale: String? = nil
37+
) {
38+
self.id = id
39+
self.properties = properties
40+
self.bindings = bindings
41+
self.children = children
42+
self.component = component
43+
self.responsiveStyles = responsiveStyles
44+
self.actions = actions
45+
self.code = code
46+
self.meta = meta
47+
self.linkUrl = linkUrl
48+
self.repeat = `repeat`
49+
self.locale = locale
50+
}
51+
}
2152

53+
/// Represents component-specific data for a block.
54+
public struct BuilderBlockComponent: Codable {
55+
public var name: String
56+
public var options: AnyCodable?
2257
}
2358

59+
/// Defines responsive style properties for a block.
60+
public struct BuilderBlockResponsiveStyles: Codable {
61+
public var large: [String: String]?
62+
public var medium: [String: String]?
63+
public var small: [String: String]?
64+
65+
public init(
66+
large: [String: String]? = nil,
67+
medium: [String: String]? = nil,
68+
small: [String: String]? = nil
69+
) {
70+
self.large = large
71+
self.medium = medium
72+
self.small = small
73+
}
74+
}
75+
76+
/// Stores information for repeating a block based on a collection.
2477
public struct StateRepeatCollectionKey: Codable {
2578
public var index: Int
2679
public var collection: String
27-
2880
}
2981

82+
// MARK: - Extension for Logic
83+
3084
extension BuilderBlockModel {
31-
/// Recursively sets the `stateBoundObjectModel` for this block and all its children.
85+
/// Recursively propagates state and applies bindings to this block and its children.
3286
public mutating func propagateStateBoundObjectModel(
3387
_ model: StateModel?, stateRepeatCollectionKey: StateRepeatCollectionKey? = nil
3488
) {
3589
self.stateBoundObjectModel = model
3690
self.stateRepeatCollectionKey = stateRepeatCollectionKey
3791

38-
if var children = self.children {
39-
for index in children.indices {
40-
children[index].propagateStateBoundObjectModel(
41-
model, stateRepeatCollectionKey: stateRepeatCollectionKey)
42-
}
43-
self.children = children
44-
}
92+
applyCodeBindings()
93+
propagateStateToChildren(model, stateRepeatCollectionKey: stateRepeatCollectionKey)
4594
}
4695

47-
public func codeBindings(for key: String) -> AnyCodable? {
48-
guard let code = code,
49-
let codeConfig = code.dictionaryValue,
50-
let bindingsConfig = codeConfig["bindings"],
51-
let bindings = bindingsConfig.dictionaryValue
52-
else {
53-
return nil
96+
/// Sets the locale for the current block and all its children recursively.
97+
public mutating func setLocaleRecursively(_ newLocale: String) {
98+
self.locale = newLocale
99+
self.id = UUID().uuidString // Reset ID for uniqueness after a locale change
100+
101+
guard var children = children else { return }
102+
for index in children.indices {
103+
children[index].setLocaleRecursively(newLocale)
54104
}
105+
self.children = children
106+
}
107+
}
55108

56-
if let stateModel = stateBoundObjectModel {
109+
// MARK: - Private Helpers
110+
111+
extension BuilderBlockModel {
112+
/// Applies code bindings to component options like 'text' and 'image'.
113+
fileprivate mutating func applyCodeBindings() {
114+
var options = component?.options?.dictionaryValue
115+
var styling = responsiveStyles.small ?? [:]
116+
117+
if var options = options {
118+
if let bindingText = evaluateCodeBinding(for: "text") {
119+
options["text"] = bindingText
120+
}
121+
122+
if let bindingImage = evaluateCodeBinding(for: "image") {
123+
options["image"] = bindingImage
124+
}
125+
126+
if let bindingVideo = evaluateCodeBinding(for: "video") {
127+
options["video"] = bindingVideo
128+
}
57129

58-
//binding is in a list
59-
if let stateRepeatCollectionKey = stateRepeatCollectionKey {
60-
let collection = stateModel.getCollectionFromStateData(
61-
keyString: stateRepeatCollectionKey.collection)
130+
self.component?.options = AnyCodable.dictionary(options)
131+
}
62132

63-
let model = collection?[stateRepeatCollectionKey.index].dictionaryValue
133+
if let bindingColor = evaluateCodeBinding(for: "color") {
134+
styling["color"] = bindingColor.stringValue
135+
}
64136

65-
for (bindingKey, value) in bindings {
66-
let lastComponent = bindingKey.split(separator: ".").last.map(String.init)
67-
if key == lastComponent {
68-
if let lookupKey = value.stringValue {
137+
if let backgroundColor = evaluateCodeBinding(for: "backgroundColor") {
138+
styling["backgroundColor"] = backgroundColor.stringValue
139+
}
69140

70-
return model?[lookupKey.split(separator: ".").last.map(String.init) ?? ""]
71-
}
141+
if let borderColor = evaluateCodeBinding(for: "borderColor") {
142+
styling["borderColor"] = borderColor.stringValue
143+
}
72144

73-
}
74-
}
75-
} else {
145+
self.responsiveStyles.small = styling
76146

77-
for (bindingKey, value) in bindings {
78-
let lastComponent = bindingKey.split(separator: ".").last.map(String.init)
79-
if key == lastComponent {
80-
return stateModel.getValueFromStateData(
81-
keyString: value.stringValue ?? "")
82-
}
147+
}
83148

84-
}
85-
}
149+
/// Recursively propagates the state model to child blocks.
150+
fileprivate mutating func propagateStateToChildren(
151+
_ model: StateModel?, stateRepeatCollectionKey: StateRepeatCollectionKey?
152+
) {
153+
guard var children = children else { return }
154+
for index in children.indices {
155+
children[index].propagateStateBoundObjectModel(
156+
model, stateRepeatCollectionKey: stateRepeatCollectionKey)
157+
}
158+
self.children = children
159+
}
86160

161+
/// Evaluates a single code binding for a given key.
162+
fileprivate func evaluateCodeBinding(for key: String) -> AnyCodable? {
163+
guard let bindings = getBindings() else { return nil }
164+
165+
if let stateRepeatCollectionKey = stateRepeatCollectionKey,
166+
let stateModel = stateBoundObjectModel
167+
{
168+
return resolveBindingInRepeatCollection(
169+
key: key, bindings: bindings, stateModel: stateModel,
170+
stateRepeatCollectionKey: stateRepeatCollectionKey)
171+
} else if let stateModel = stateBoundObjectModel {
172+
return resolveBindingInStandardState(key: key, bindings: bindings, stateModel: stateModel)
87173
}
88174

89175
return nil
90176
}
91177

92-
public mutating func setLocaleRecursively(_ newLocale: String) {
93-
self.locale = newLocale
94-
self.id = UUID().uuidString // Reset ID to ensure uniqueness after locale change
95-
if let children = self.children {
96-
var newChildren = children
97-
for i in 0..<newChildren.count {
98-
newChildren[i].setLocaleRecursively(newLocale)
99-
}
100-
self.children = newChildren
101-
}
178+
/// Extracts the bindings dictionary from the code property.
179+
fileprivate func getBindings() -> [String: AnyCodable]? {
180+
return code?.dictionaryValue?["bindings"]?.dictionaryValue
102181
}
103182

104-
}
183+
/// Resolves a binding within a repeated collection.
184+
fileprivate func resolveBindingInRepeatCollection(
185+
key: String, bindings: [String: AnyCodable], stateModel: StateModel,
186+
stateRepeatCollectionKey: StateRepeatCollectionKey
187+
) -> AnyCodable? {
188+
guard
189+
let collection = stateModel.getCollectionFromStateData(
190+
keyString: stateRepeatCollectionKey.collection),
191+
stateRepeatCollectionKey.index < collection.count,
192+
let model = collection[stateRepeatCollectionKey.index].dictionaryValue
193+
else {
194+
return nil
195+
}
105196

106-
public struct BuilderBlockComponent: Codable {
107-
public var name: String
108-
public var options: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
109-
}
197+
for (bindingKey, value) in bindings {
198+
if bindingKey.split(separator: ".").last.map(String.init) == key,
199+
let lookupKey = value.stringValue,
200+
let finalKey = lookupKey.split(separator: ".").last.map(String.init)
201+
{
202+
return model[finalKey]
203+
}
204+
}
205+
return nil
206+
}
110207

111-
public struct BuilderBlockResponsiveStyles: Codable {
112-
var large: [String: String]? = [:]
113-
var medium: [String: String]? = [:]
114-
var small: [String: String]? = [:]
208+
/// Resolves a binding from the standard state model.
209+
fileprivate func resolveBindingInStandardState(
210+
key: String, bindings: [String: AnyCodable], stateModel: StateModel
211+
) -> AnyCodable? {
212+
for (bindingKey, value) in bindings {
213+
if bindingKey.split(separator: ".").last.map(String.init) == key,
214+
let keyString = value.stringValue
215+
{
216+
return stateModel.getValueFromStateData(keyString: keyString)
217+
}
218+
}
219+
return nil
220+
}
115221
}

0 commit comments

Comments
 (0)