11import SwiftUI
22
3- /// A property wrapper type that stores or binds a ``VDFlow/Step`` and invalidates a view whenever the step changes.
3+ /// A property wrapper type that stores or binds a navigation step collection and invalidates a view whenever the step changes.
44///
5- /// @StateStep var router = StepsStruct()
6- /// let stepState = StateStep(stepBinding)
5+ /// `StateStep` serves as the primary way to integrate VDFlow's navigation with SwiftUI's view system.
6+ /// It can either own the state itself (like `@State`) or bind to external state (like `@Binding`).
7+ ///
8+ /// There are three primary ways to use `StateStep`:
9+ ///
10+ /// 1. Create and own the state:
11+ /// ```swift
12+ /// struct ContentView: View {
13+ /// @StateStep var steps = TabSteps.home
14+ ///
15+ /// var body: some View {
16+ /// TabView(selection: $steps.selected) {
17+ /// HomeView()
18+ /// .step(_steps.$home)
19+ /// ProfileView()
20+ /// .step(_steps.$profile)
21+ /// }
22+ /// }
23+ /// }
24+ /// ```
25+ ///
26+ /// 2. Bind to a parent's state:
27+ /// ```swift
28+ /// struct ChildView: View {
29+ /// @StateStep var steps: TabSteps
30+ ///
31+ /// var body: some View {
32+ /// // Access and modify steps here
33+ /// Button("Go Home") {
34+ /// steps.selected = .home
35+ /// }
36+ /// }
37+ /// }
38+ ///
39+ /// // In parent view:
40+ /// ChildView()
41+ /// .stepEnvironment($parentSteps)
42+ /// ```
43+ ///
44+ /// 3. Create from an existing binding:
45+ /// ```swift
46+ /// struct DetailView: View {
47+ /// @ObservedObject var viewModel: ViewModel // Has a steps property
48+ /// @StateStep var steps: DetailSteps
49+ ///
50+ /// init(viewModel: ViewModel) {
51+ /// self.viewModel = viewModel
52+ /// _steps = StateStep(viewModel.$steps)
53+ /// }
54+ ///
55+ /// var body: some View {
56+ /// // Use steps here
57+ /// }
58+ /// }
59+ /// ```
60+ ///
61+ /// - Important: When accessing the wrapper itself, use the underscore prefix: `_steps`
762@propertyWrapper
863public struct StateStep < Value> : DynamicProperty {
964
@@ -25,10 +80,21 @@ public struct StateStep<Value>: DynamicProperty {
2580 }
2681 }
2782
83+ /// Creates a new state step that owns its state.
84+ ///
85+ /// This initializer creates a state step that owns and manages its own state, similar to SwiftUI's `@State`.
86+ ///
87+ /// - Parameter wrappedValue: The initial value for the state.
2888 public init ( wrappedValue: Value ) {
2989 self . init ( binding: . state( wrappedValue) )
3090 }
3191
92+ /// Creates a new state step that binds to external state.
93+ ///
94+ /// This initializer creates a state step that binds to external state, similar to SwiftUI's `@Binding`.
95+ /// Use this when you want to integrate with existing state management like `ObservableObject` models.
96+ ///
97+ /// - Parameter binding: A binding to the external state.
3298 public init ( _ binding: Binding < Value > ) {
3399 self . init ( binding: . binding( binding) )
34100 }
@@ -45,6 +111,9 @@ public struct StateStep<Value>: DynamicProperty {
45111
46112public extension StateStep where Value == EmptyStep {
47113
114+ /// Creates a new state step with an empty step value.
115+ ///
116+ /// This is a convenience initializer for cases where you need a placeholder step that doesn't contain any data.
48117 init ( ) {
49118 self . init ( wrappedValue: EmptyStep ( ) )
50119 }
@@ -60,6 +129,23 @@ extension EnvironmentValues {
60129
61130public extension View {
62131
132+ /// Associates a view with a specific step in a step collection and sets up the environment for that step.
133+ ///
134+ /// This modifier combines `.tag()` and `.stepEnvironment()` to fully configure a view for a specific step.
135+ /// Use this with tab views, lists, or any other view where selection state is needed.
136+ ///
137+ /// - Parameter binding: A binding to a step wrapper, typically accessed through `_steps.$stepName`.
138+ /// - Returns: A view modified with the appropriate tag and environment for this step.
139+ ///
140+ /// ```swift
141+ /// TabView(selection: $steps.selected) {
142+ /// HomeView()
143+ /// .step(_steps.$home)
144+ ///
145+ /// ProfileView()
146+ /// .step(_steps.$profile)
147+ /// }
148+ /// ```
63149 func step< Root: StepsCollection , Value> (
64150 _ binding: Binding < StepWrapper < Root , Value > >
65151 ) -> some View {
@@ -70,13 +156,48 @@ public extension View {
70156 . stepTag ( binding. wrappedValue. id)
71157 }
72158
159+ /// Sets up the environment to provide a binding to the specified value to child views.
160+ ///
161+ /// This is particularly useful for passing a step binding down the view hierarchy without
162+ /// needing to explicitly pass it through initializers.
163+ ///
164+ /// - Parameter binding: A binding to a value that will be made available to child views.
165+ /// - Returns: A view with the environment set up for the provided binding.
166+ ///
167+ /// ```swift
168+ /// NavigationView {
169+ /// RootView()
170+ /// .stepEnvironment($steps.$detailFlow)
171+ /// }
172+ ///
173+ /// // In a child view:
174+ /// struct DetailView: View {
175+ /// @StateStep var detailFlow: DetailSteps
176+ /// // This will automatically receive the binding from the environment
177+ /// }
178+ /// ```
73179 func stepEnvironment< Value> ( _ binding: Binding < Value > ) -> some View {
74180 environment ( \. [ StateStep< Value> . StepKey( ) ] , binding)
75181 }
76182}
77183
78184public extension Binding where Value: StepsCollection , Value. AllSteps: ExpressibleByNilLiteral {
79185
186+ /// Creates a binding to an optional value that reflects whether a specific step is selected.
187+ ///
188+ /// This is useful for integrating with SwiftUI's `isPresented` and similar APIs that use optional bindings.
189+ /// The binding's value will be non-nil when the step is selected, and nil otherwise.
190+ ///
191+ /// - Parameter step: The key path to the step to check.
192+ /// - Returns: A binding to an optional value that is non-nil when the step is selected.
193+ ///
194+ /// ```swift
195+ /// NavigationLink(isActive: $steps.isSelected(\.detail)) {
196+ /// DetailView()
197+ /// } label: {
198+ /// Text("Go to Detail")
199+ /// }
200+ /// ```
80201 func isSelected< T> ( _ step: WritableKeyPath < Value , Value . StepID < T > > ) -> Binding < T ? > {
81202 Binding < T ? > {
82203 if wrappedValue. isSelected ( step) {
@@ -93,6 +214,25 @@ public extension Binding where Value: StepsCollection, Value.AllSteps: Expressib
93214 }
94215 }
95216
217+ /// Creates a binding to a Boolean value that reflects whether a specific step is selected.
218+ ///
219+ /// This is useful for integrating with SwiftUI's `isPresented` and similar APIs that use Boolean bindings.
220+ ///
221+ /// - Parameter step: The step to check.
222+ /// - Returns: A binding to a Boolean value that is `true` when the step is selected.
223+ ///
224+ /// ```swift
225+ /// Button("Go to Settings") {
226+ /// $flow.isSelected(.settings).wrappedValue = true
227+ /// }
228+ ///
229+ /// // Or with NavigationLink:
230+ /// NavigationLink(isActive: $flow.isSelected(.details)) {
231+ /// DetailsView()
232+ /// } label: {
233+ /// Text("Show Details")
234+ /// }
235+ /// ```
96236 func isSelected( _ step: Value . AllSteps ) -> Binding < Bool > {
97237 Binding < Bool > {
98238 wrappedValue. selected == step
0 commit comments