@@ -23,21 +23,27 @@ public extension Addons {
2323```
2424
2525Then, add an enum called ` CustomModifier ` that has cases for each modifier to include.
26- The framework uses this type to parse modifiers in a stylesheet.
26+ The framework uses this type to decode modifiers in a stylesheet.
2727
28- Use the `` CustomModifierGroupParser `` to include multiple modifiers.
28+ Conform to the ` Decodable ` protocol and attempt to decode each modifier type in the ` init(from:) ` implementation.
29+
30+ ` init(from:) ` * must* throw if no modifiers can be decoded.
31+ If your ` CustomModifier ` catches unknown modifiers, modifiers from other addons will get ignored.
2932
3033``` swift
3134@Addon
3235struct MyAddon <Root : RootRegistry > {
33- enum CustomModifier : ViewModifier , ParseableModifierValue {
36+ enum CustomModifier : ViewModifier , @preconcurrency Decodable {
3437 case myFirstModifier (MyFirstModifier<Root>)
3538 case mySecondModifier (MySecondModifier<Root>)
36-
37- static func parser (in context : ParseableModifierContext) -> some Parser<Substring.UTF8View , Self > {
38- CustomModifierGroupParser (output : Self .self ) {
39- MyFirstModifier< Root> .parser (in : context).map (Self .myFirstModifier )
40- MySecondModifier< Root> .parser (in : context).map (Self .mySecondModifier )
39+
40+ init (from decoder : any Decoder) throws {
41+ let container = try decoder.singleValueContainer ()
42+
43+ if let modifier = try ? container.decode (MyFirstModifier< Root> .self ) {
44+ self = .myFirstModifier (modifier)
45+ } else {
46+ self = .mySecondModifier (try container.decode (MySecondModifier< Root> .self ))
4147 }
4248 }
4349
@@ -53,118 +59,264 @@ struct MyAddon<Root: RootRegistry> {
5359}
5460```
5561
56- ## Parseable Modifiers
62+ ## Decoding Modifiers
5763
58- Each modifier should conform to `` LiveViewNativeStylesheet/ParseableModifierValue `` .
59- You can use the `` LiveViewNativeStylesheet/ParseableExpression() `` macro to synthesize this conformance .
64+ LiveView Native allows you to write modifiers in a stylesheet or ` style ` attribute with Swift syntax .
65+ These modifiers are converted to an abstract syntax tree format and encoded to JSON .
6066
61- The macro will synthesize a parser for each ` init ` clause.
67+ ``` swift
68+ // stylesheet:
69+ foregroundStyle (Color.red )
70+ ```
6271
63- Any type conforming to `` LiveViewNativeStylesheet/ParseableModifierValue `` can be used as an argument in an ` init ` clause.
72+ ``` json
73+ [" foregroundStyle" , { ... }, [[" ." , { ... }, [" Color" , " red" ]]]]
74+ ```
6475
65- ``` swift
66- @ParseableExpression
67- struct MyFirstModifier <Root : RootRegistry >: ViewModifier {
68- static var name: String { " myFirstModifier" }
76+ Each modifier conforms to ` Decodable ` , and is expected to decode itself from this JSON format.
77+
78+ ### Automatic Decoding
79+ The `` ASTDecodable(_:options:) `` macro can synthesize a decoder directly from your Swift code.
80+
81+ Add the `` ASTDecodable(_:options:) `` macro to your struct, and pass it the name of the modifier.
6982
70- let color: Color
83+ It will synthesize a decoder for each ` init ` clause in the struct.
7184
72- init (_ color : Color) {
73- self .color = color
85+ ``` swift
86+ @ASTDecodable (" labeled" )
87+ struct LabeledModifier <Root : RootRegistry >: ViewModifier , @preconcurrency Decodable {
88+ let label: String
89+
90+ init (as label : Int ) {
91+ self .label = String (label)
7492 }
7593
76- init (red : Double , green : Double , blue : Double ) {
77- self .color = Color (. sRGB , red : red, green : green, blue : blue)
94+ init (as label : String ) {
95+ self .label = label
7896 }
7997
8098 func body (content : Content) -> some View {
81- content
82- .bold ()
83- .background (color)
99+ LabeledContent {
100+ content
101+ } label : {
102+ Text (label)
103+ }
84104 }
85105}
86106```
87107
88- In the stylesheet, you can use either clause :
108+ In the stylesheet, you can use either ` init ` :
89109
90110``` swift
91- // myFirstModifier(_:)
92- myFirstModifier (.red )
93- myFirstModifier (.blue )
94- myFirstModifier (Color (white : 0.5 ))
111+ labeled (as : 5 )
112+ labeled (as : " Label" )
113+ ```
114+
115+ `` ASTDecodable(_:options:) `` will also synthesize decoders for enum cases, static functions, static members, properties, and member functions.
116+ To exclude any of these declarations from the decoder, either prefix them with an underscore (` _ ` ), or define them in an extension.
117+ Static functions, static members, properties, and member functions will only receive a synthesized decoder if they return ` Self ` (or a type name that matches the declaration `` ASTDecodable(_:options:) `` is attached to).
118+
119+ ``` swift
120+ @ASTDecodable (" MyType" )
121+ enum MyType : Decodable {
122+ init () {} // decodable
123+
124+ case enumCase // decodable
125+
126+ static func staticFunction () -> Self { ... } // decodable
127+ static func staticFunction () -> OtherType { ... } // not decodable
128+ static func _staticFunction () -> OtherType { ... } // not decodable
129+
130+ static var staticMember: Self { ... } // decodable
131+ static var staticMember: OtherType { ... } // not decodable
132+ static var _staticMember: OtherType { ... } // not decodable
133+
134+ func memberFunction () -> Self { ... } // decodable
135+ func memberFunction () -> OtherType { ... } // not decodable
136+ func _memberFunction () -> OtherType { ... } // not decodable
137+
138+ var property: Self { ... } // decodable
139+ var property: OtherType { ... } // not decodable
140+ var _property: OtherType { ... } // not decodable
141+ }
95142
96- // myFirstModifier(red:green:blue:)
97- myFirstModifier (red : 1 , green : 0.5 , blue : 0 )
143+ extension MyType {
144+ static func staticFunction () -> Self { ... } // not decodable
145+ static var staticMember: Self { ... } // not decodable
146+ func memberFunction () -> Self { ... } // not decodable
147+ var property: Self { ... } // not decodable
148+ }
98149```
99150
100- ### Nested Content
101- Use `` ObservedElement `` and `` LiveContext `` to access properties/children of the element the modifier is applied to .
151+ ### Attribute References
152+ Any type that conforms to `` AttributeDecodable `` can be wrapped with `` AttributeReference `` .
102153
103- The `` ViewReference `` type can be used to refer to nested children with a ` template ` attribute .
154+ This will make it usable with the ` attr(<name>) ` helper in a stylesheet .
104155
105- ``` swift
106- @ParseableExpression
107- struct MyBackgroundModifier <Root : RootRegistry >: ViewModifier {
108- static var name: String { " myBackground" }
156+ Use the `` ObservedElement `` and `` LiveContext `` property wrappers to access the element and context needed to resolve these references.
109157
158+ ``` swift
159+ @ASTDecodable (" labeled" )
160+ struct LabeledModifier <Root : RootRegistry >: ViewModifier , @preconcurrency Decodable {
110161 @ObservedElement private var element
111162 @LiveContext < Root> private var context
112163
113- let content: ViewReference
164+ let label: AttributeReference< String >
114165
115- init (_ content : ViewReference ) {
116- self .content = content
166+ init (as label : AttributeReference< String > ) {
167+ self .label = label
117168 }
118169
119170 func body (content : Content) -> some View {
120- content.background (content.resolve (on : element, in : context))
171+ LabeledContent {
172+ content
173+ } label : {
174+ Text (label.resolve (on : element, in : context))
175+ }
121176 }
122177}
123178```
124179
125180``` elixir
126- " my-background " do
127- myBackground ( :my_content )
181+ " my-title " do
182+ labeled ( as: attr ( " label " ) )
128183end
129184```
130185
131186``` html
132- <Element class =" my-background" >
133- <Text template =" my_content" >Nested Content</Text >
134- </Element >
187+ <Element class =" my-title" label =" My Label" />
135188```
136189
137- ### Attribute References
138- Any type that conforms to `` AttributeDecodable `` can be wrapped with `` AttributeReference `` .
190+ ### Resolvable Types
191+ Stylesheets are static assets, and the modifiers defined in your templates do not change after the application loads.
192+ However, some of their properties can be dynamic using helpers like ` attr(<name>) ` or ` gesture_state(...) ` .
139193
140- This will make it usable with the ` attr(<name>) ` helper in a stylesheet.
194+ The `` StylesheetResolvable `` protocol defines a type that must be resolved before it can be used.
195+ Many types built-in to SwiftUI have been given a nested ` Resolvable ` type that conforms to this protocol.
196+ This nested type can be used to decode a SwiftUI primitive in your modifier.
197+
198+ Call `` StylesheetResolvable/resolve(on:in:) `` to get the resolved value for an `` ElementNode `` in a `` LiveContext `` .
199+ Use the `` ObservedElement `` and `` LiveContext `` property wrappers to access the element and context needed to resolve these types.
200+
201+ For example, SwiftUI has a built-in ` HorizontalAlignment ` type. We can use ` HorizontalAlignment.Resolvable ` to include this in our custom modifier.
141202
142203``` swift
143- @ParseableExpression
144- struct MyTitleModifier <Root : RootRegistry >: ViewModifier {
145- static var name: String { " myTitle" }
204+ @ASTDecodable (" labeled" )
205+ struct LabeledModifier <Root : RootRegistry >: ViewModifier , @preconcurrency Decodable {
206+ let label: String
207+ let alignment: HorizontalAlignment.Resolvable
208+
209+ init (as label : String , alignment : HorizontalAlignment.Resolvable) {
210+ self .label = label
211+ self .alignment = alignment
212+ }
213+
214+ func body (content : Content) -> some View {
215+ VStack (alignment : alignment.resolve (on : element, in : context)) {
216+ Text (label)
217+ content
218+ }
219+ }
220+ }
221+ ```
222+ ``` swift
223+ // stylesheet:
224+ labeled (as : " Label" , alignment : .trailing )
225+ ```
226+
227+ #### Resolvable Protocols
228+
229+ Some protocols also have `` StylesheetResolvable `` implementations.
230+ For example, to use ` some ShapeStyle ` in your modifier, use the `` StylesheetResolvableShapeStyle `` type.
231+ It will resolve to a type-erased ` ShapeStyle ` .
232+
233+ ``` swift
234+ @ASTDecodable (" fillBackground" )
235+ struct FillBackgroundModifier <Root : RootRegistry >: ViewModifier , @preconcurrency Decodable {
236+ let fill: StylesheetResolvableShapeStyle
237+
238+ init (_ fill : StylesheetResolvableShapeStyle) {
239+ self .fill = fill
240+ }
241+
242+ func body (content : Content) -> some View {
243+ content.background (fill)
244+ }
245+ }
246+ ```
247+ ``` swift
248+ // stylesheet:
249+ fillBackground (.regularMaterial )
250+ fillBackground (.red .opacity (attr (" opacity" )))
251+ ```
252+
253+ #### Custom Resolvable Types
254+
255+ You can also conform your own types to ` StylesheetResolvable ` . This is most useful when you want some properties of your type to support the ` attr(<name>) ` helper.
256+
257+ ``` swift
258+ struct Video {
259+ let url: String
260+ let resolution: Int
261+
262+ @ASTDecodable (" Video" )
263+ struct Resolvable : StylesheetResolvable , Decodable {
264+ let url: AttributeReference<String >
265+ let resolution: AttributeReference<Int >
266+
267+ init (_ url : AttributeReference<String >, in resolution : AttributeReference<Int >) {
268+ self .url = url
269+ self .resolution = resolution
270+ }
271+
272+ func resolve (on element : ElementNode, in context : LiveContext<some RootRegistry>) -> Video {
273+ Video (
274+ url : url.resolve (on : element, in : context),
275+ resolution : resolution.resolve (on : element, in : context)
276+ )
277+ }
278+ }
279+ }
280+ ```
281+ ``` swift
282+ // stylesheet:
283+ backgroundVideo (Video (" ..." , in : 1080 ))
284+ backgroundVideo (Video (attr (" url" ), in : attr (" resolution" )))
285+ ```
286+
287+ ### Nested Content
288+ Use `` ObservedElement `` and `` LiveContext `` to access properties/children of the element the modifier is applied to.
289+
290+ The `` ViewReference `` type can be used to refer to nested children with a ` template ` attribute.
146291
292+ ``` swift
293+ @ASTDecodable (" myBackground" )
294+ struct MyBackgroundModifier <Root : RootRegistry >: ViewModifier , @preconcurrency Decodable {
147295 @ObservedElement private var element
148296 @LiveContext < Root> private var context
149297
150- let title: AttributeReference< String >
298+ let content: ViewReference
151299
152- init (_ title : AttributeReference< String > ) {
153- self .title = title
300+ init (_ content : ViewReference ) {
301+ self .content = content
154302 }
155303
156304 func body (content : Content) -> some View {
157- content.navigationTitle (title.resolve (on : element, in : context))
305+ content.background {
306+ content.resolve (on : element, in : context)
307+ }
158308 }
159309}
160310```
161311
162312``` elixir
163- " my-title " do
164- myTitle ( attr ( " title " ) )
313+ " my-background " do
314+ myBackground ( :my_content )
165315end
166316```
167317
168318``` html
169- <Element class =" my-title" title =" My Title" />
319+ <Element class =" my-background" >
320+ <Text template =" my_content" >Nested Content</Text >
321+ </Element >
170322```
0 commit comments