Open fbartho opened 1 month ago
This is a big proposal, so I'd be keen to see it split up. If we could start with something like an empty environment that developers could place things into, that would be a big step forward. A logical next step (for me) would be to read publishing data from that environment, e.g. site details, article details, etc – like using @Query
with SwiftData, for example. The next step (again, in my eyes!) would be to add in there environment modifiers, such as letter font()
be applied anywhere and it have flow down the environment. And so on… lots of smaller steps working towards a bigger goal, but each one landing separately so we have time to evaluate them individually.
How does that sound?
That definitely makes sense. I’ll proceed with attempting just the first step for now, and then other PRs can discuss/bikeshed SiteContext, PageContext, ContentContext or other “Default Environmental Info”
Okay, after sleeping on it, I have an idea how this could be implemented:
@UsesEnvironment
struct SomeComponent: BaseElement {
@Environment(\.someValueKey) var value: MyType
func render(context: RenderContext) -> String {
…
}
}
@Environment
would be a class-based property wrapper. (Class-based so the element as a whole still is a struct, but the property can still be mutated)@UsesEnvironment
would be a macro that wraps render with a call to self.willRender(context: RenderContext)
willRender
is to be implemented in an extension on BaseElement
. It uses reflection to find all properties on the element tagged with @Environment
and injects the RenderContext
RenderContext
should be a struct containing PublishingContext, and something like environmentValues: EnvironmentValues
where struct EnvironmentValues
has an internal var storage: [WritableKeyPath: Any]
. This is the only breaking change, but it affects all Elements.environment(…)
modifier creates a mutated copy of EnvironmentValues
that it passes down to any nested renders.All names are placeholders please suggest alternates! I don’t love introducing a macro, but that’s the only way I could see to provide an ambient, but non-global value to the property-wrappers.
What do you think @twostraws?
I think I need to investigate this further. Bringing macros into the equation is something I'd rather avoid if possible.
We don’t need a macro.
If we make the change for RenderContext, then we have the right location for a hierarchical store of data. The problem is that the @Environment
property-wrapper doesn’t have a clean way to access the render-context without injecting that render-context explicitly at some point so the macro was one way to solve that. We could alternatively say that in Ignite, Environment fields are accessed by doing something like context.environment.myField
but that’s obviously clunkier (+open questions about strong typing).
Because each Element is responsible for calling the render method of its children there’s no place for the Framework to hook behavior in between render layers. — This is actually a difference between Ignite & SwiftUI — SwiftUI’s body property is of type some View
while Ignite returns string
.
If Ignite’s tree was instead returning Element
and it got turned into string
inside the framework then the framework could do lifecycle things in between each render. Potentially this would be helpful for allowing some components to be async-rendered #3 while others are sync.
I am very excited to see how this plays out and is implemented because I think this is critical for proper mobile optimization (#7). Right now I am adding custom inline css with ternary operators based on the width of the page. It's really an ugly work around.
This is an unrelated issue – this is a build-time environment rather than a run-time environment. The problem with the logo forcing a navigation bar wrap is a CSS issue, and should be resolved with a flexible size. I've updated the IgniteSamples repository with an example of how this is done; it should now adjust smoothly smaller than even the smallest iPhone in portrait.
Ah, I must have misread the conversation then. My bad.
Hey everybody!
I have prepared an implementation of @Environment
under the #28 Pull Request . Take a look to see if it is what you are looking for. Tomorrow I will conduct tests and submit PR for review.
This implementation provides EnvironmentValues
that handle global environment variables. This can be extended the same way we do in SwiftUI. Following the implementation of the original, @Environment
you can only read the value. There is no possibility to set an environment value outside the package. All value setters take place in the package internally.
Please let me know if there's a way to do this already and I just missed it!
Purpose
I'd like to build components that render differently depending on their position in the Element Hierarchy.
Some concrete examples:
Example 1: Dynamic Header (Rank)
(Virtual HTML-style DOM tree)
In this example, the hypothetical
SectionWithHeader
component would use the.environment()
-context to help generate the following html:… but only if it can keep track of how deep the component is within the tree.
Notice: you could implement this component to automatically provide data for a statically generated Outline, with automatic anchor-links to each heading
Example 2: Dynamic Environmental Overrides
In various past projects, I've found it useful to be able to preview a component, but rendered side-by-side with different contexts. One example is simultaneously viewing light/dark themes of the same component.
Output:
Related use-cases:
Some browsers have some of these as a tools at the browser/debugger level, but I've found those to be pretty inconvenient.
Suggested Solution
If we implement something like the SwiftUI's Environment modifier or React's Context then we can provide necessary data for implementing these use cases above, and plenty of others not listed.
Alternatives Considered