WithScope
Marks a class that it has Scope which can be transferred for adding/receiving additional information.
The concept of the Scope is to provide a consistent mechanism to pass arbitrary data down the DOM tree from a hierarchical higher node to "unknown" consumer nodes down the tree. This is very important for all kind of dev.fritz2.headless.components (not necessarily fritz2's dev.fritz2.headless.components!), that should adapt to its context. Some higher node places some information tagged by a unique key into the scope and passes this further down to its children, which themselves just passes this scope further down, optionally adding or manipulating the scope for their children on their own. Somewhere down the tree a node can evaluate the scope passed to him and look out for some key it want to react to. If the key is present it can then apply its value or just behave in some specific way different to its default. If there is no key, the node just applies its default behaviour.
To give a practical example: Imagine some button component, which normally uses the "primary" color as background. This works fine for most of the time. But now imagine a buttons-bar on the bottom edge of a modal for example to provide the typical buttons like "ok", "cancel", "yes", "no" or alike. This bar uses the primary color as background too, to have a high contrast against the content above. The two dev.fritz2.headless.components do not work well together this way! The user would have to manually apply some other color to the buttons when using them inside the bar, in order to preserve a good contrast to it. To achieve this behaviour automatically, the scope comes to the rescue: The buttons-bar component can define a global scope-key buttonsBar
by using the Scope.keyOf function. Then it can add some key-value pair to the scope like set(buttonsBar, true)
in order to signal all child nodes that they appear within the context of a buttons bar. The button component could be aware of the key and implement some different behaviour concerning the color, if it detects that it is used within a buttons-bar.
The scope only changes conformal to the node hierarchy. That is the scope is empty at the top level render function call and may be filled or changed by each child. But a change by some node is only propagated to the children of that node. The children of the next siblings of the changing node are not affected and do not see those scope values!
Example:
div { // initial scope -> empty!
val sizes = keyOf<String>("sizes") // normally define scope-keys globally
div(scope = {
set(sizes, "small") // add some key-value to the scope
}) {
// all children will get this scope instance
p {
scope.asDataAttr() // -> { "sizes": "small" }
}
section {
when (scope[sizes]) {
"small" -> div({ fontSize { "0.8rem" } }) { +"small text" }
"normal" -> div({ fontSize { "1rem" } }) { +"normal text" }
"large" -> div({ fontSize { "1.2rem" } }) { +"large text" }
else -> div { +"no size scope available" }
}
}
// end of children
}
// next sibling -> only parent scope available, which is empty!
p {
scope.asDataAttr() // -> {}
}
}
It is intentional that the key is not tied to some component or restricted in any other way. A client should strive for a key management, that is driven by the "producing" node, not the "consuming" one! That means one should prefer to encode that some specific context now exist or that some value is now available, instead of setting a client node tailored rule. This enables more freedom for future usages and adaptions by other consuming dev.fritz2.headless.components.
To continue the first example: A buttons-bar component should better not inject some "buttonsColor" into the scope, but better just some "buttonsBar" key without any value (Unit). As a creator you just cannot anticipate all situations and future usage of the buttons-bar component. It might be possible that a client wants to put something different to a button into the bar, that also should react to the context. Then a key (and value) tailored to the button does not make sense anymore.
See also
Inheritors
Functions
Allows to access the nearest MountPoint from any WithScope