Skip to content

Commit cc81a33

Browse files
committed
Update custom modifier creation documentation
1 parent 12fd461 commit cc81a33

File tree

1 file changed

+136
-22
lines changed

1 file changed

+136
-22
lines changed

Sources/LiveViewNative/LiveViewNative.docc/AddCustomModifier.md

Lines changed: 136 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,167 @@ Use the ``RootRegistry`` protocol to define how custom modifiers in the DOM are
44

55
## Overview
66

7-
If you don't already have one, create a type that conforms to the ``RootRegistry`` protocol and provide it as the eneric type paramter to your ``LiveViewCoordinator``.
7+
If you don't already have one, create a type that uses the ``Addon()`` macro, and include it in the list of `addons` on your ``LiveView``.
88

99
```swift
10-
struct MyRegistry: RootRegistry {
10+
public extension Addons {
11+
@Addon
12+
struct MyAddon<Root: RootRegistry> {
13+
// ...
14+
}
1115
}
1216
```
1317

14-
Then, add an enum called `AttributeName` that has strings for raw values and conforms to `Equatable`. The framework will use this type to check if your registry supports a particular attribute name. All of the string values should be lowercase, otherwise the framework will not use them.
18+
```swift
19+
#LiveView(
20+
.localhost,
21+
addons: [.myAddon]
22+
)
23+
```
24+
25+
Then, 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.
27+
28+
Use the ``CustomModifierGroupParser`` to include multiple modifiers.
1529

1630
```swift
17-
struct MyRegistry: RootRegistry {
18-
enum ModifierType: String {
19-
case myFont = "my_font"
31+
@Addon
32+
struct MyAddon<Root: RootRegistry> {
33+
enum CustomModifier: ViewModifier, ParseableModifierValue {
34+
case myFirstModifier(MyFirstModifier<Root>)
35+
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)
41+
}
42+
}
43+
44+
func body(content: Content) -> some View {
45+
switch self {
46+
case .myFirstModifier(let modifier):
47+
content.modifier(modifier)
48+
case .mySecondModifier(let modifier):
49+
content.modifier(modifier)
50+
}
51+
}
2052
}
2153
}
2254
```
2355

24-
To define the view modifier for this attributes, implement the ``CustomRegistry/decodeModifier(_:from:)-9gliz`` method. This method is automatically treated as a ``ViewModifierBuilder``, so simply construct your modifier rather than returning it.
56+
## Parseable Modifiers
57+
58+
Each modifier should conform to ``LiveViewNativeStylesheet/ParseableModifierValue``.
59+
You can use the ``LiveViewNativeStylesheet/ParseableExpression()`` macro to synthesize this conformance.
60+
61+
The macro will synthesize a parser for each `init` clause.
2562

26-
In the following example, a modifier like `{"type": "my_font", "size": 22}` could be used to apply the custom font named "My Font" with a fixed size of 22pt.
63+
Any type conforming to ``LiveViewNativeStylesheet/ParseableModifierValue`` can be used as an argument in an `init` clause.
2764

2865
```swift
29-
struct MyRegistry: RootRegistry {
30-
enum ModifierType: String {
31-
case myFont = "my_font"
66+
@ParseableExpression
67+
struct MyFirstModifier<Root: RootRegistry>: ViewModifier {
68+
static var name: String { "myFirstModifier" }
69+
70+
let color: Color
71+
72+
init(_ color: Color) {
73+
self.color = color
3274
}
3375

34-
static func decodeModifier(_ type: ModifierType, from decoder: Decoder) throws -> some ViewModifier {
35-
switch name {
36-
case .myFont:
37-
try MyFontModifier(from: decoder)
38-
}
76+
init(red: Double, green: Double, blue: Double) {
77+
self.color = Color(.sRGB, red: red, green: green, blue: blue)
78+
}
79+
80+
func body(content: Content) -> some View {
81+
content
82+
.bold()
83+
.background(color)
84+
}
85+
}
86+
```
87+
88+
In the stylesheet, you can use either clause:
89+
90+
```swift
91+
// myFirstModifier(_:)
92+
myFirstModifier(.red)
93+
myFirstModifier(.blue)
94+
myFirstModifier(Color(white: 0.5))
95+
96+
// myFirstModifier(red:green:blue:)
97+
myFirstModifier(red: 1, green: 0.5, blue: 0)
98+
```
99+
100+
### Nested Content
101+
Use ``ObservedElement`` and ``LiveContext`` to access properties/children of the element the modifier is applied to.
102+
103+
The ``ViewReference`` type can be used to refer to nested children with a `template` attribute.
104+
105+
```swift
106+
@ParseableExpression
107+
struct MyBackgroundModifier<Root: RootRegistry>: ViewModifier {
108+
static var name: String { "myBackground" }
109+
110+
@ObservedElement private var element
111+
@LiveContext<Root> private var context
112+
113+
let content: ViewReference
114+
115+
init(_ content: ViewReference) {
116+
self.content = content
117+
}
118+
119+
func body(content: Content) -> some View {
120+
content.background(content.resolve(on: element, in: context))
39121
}
40122
}
41123
```
42124

43-
Because an enum is used for the attribute name, do not include a `default` branch in your `switch` statement so that Swift will check if for exhaustiveness.
125+
```elixir
126+
"my-background" do
127+
myBackground(:my_content)
128+
end
129+
```
130+
131+
```html
132+
<Element class="my-background">
133+
<Text template="my_content">Nested Content</Text>
134+
</Element>
135+
```
136+
137+
### Attribute References
138+
Any type that conforms to ``AttributeDecodable`` can be wrapped with ``AttributeReference``.
44139

45-
Then, implement the `MyFontModifier` struct. By conforming to the `Decodable` protocol, the `init(from:)` initializer that handles decoding the modifier from JSON can be synthesized automatically. The `body(content:)` method modifies the `content` view it receives based on whatever values were decoded.
140+
This will make it usable with the `attr(<name>)` helper in a stylesheet.
46141

47142
```swift
48-
struct MyFontModifier: ViewModifier, Decodable {
49-
let size: Double?
143+
@ParseableExpression
144+
struct MyTitleModifier<Root: RootRegistry>: ViewModifier {
145+
static var name: String { "myTitle" }
50146

147+
@ObservedElement private var element
148+
@LiveContext<Root> private var context
149+
150+
let title: AttributeReference<String>
151+
152+
init(_ title: AttributeReference<String>) {
153+
self.title = title
154+
}
155+
51156
func body(content: Content) -> some View {
52-
content
53-
.font(.custom("My Font", fixedSize: size ?? 13))
157+
content.navigationTitle(title.resolve(on: element, in: context))
54158
}
55159
}
56160
```
161+
162+
```elixir
163+
"my-title" do
164+
myTitle(attr("title"))
165+
end
166+
```
167+
168+
```html
169+
<Element class="my-title" title="My Title" />
170+
```

0 commit comments

Comments
 (0)