Child to parent bindings
matthewp opened this issue · comments
Discussed this elsewhere, but custom functions could be made to work with pretty easily. I could see something like this:
registerCustomFunction('--resolve', class {
constructor() {
this.state = 'pending';
this.value = null;
this.reason = null;
}
check([val], { rebind }) {
Promise.resolve(val).then(resolvedValue => {
this.value = resolvedValue;
this.state = 'resolved';
}).catch(err => {
this.state = 'rejected';
this.reason = err;
});
}
call([]) {
return { value: this.value, state: this.state, reason: this.reason };
}
});
Which you could then use like this:
sheet`
.page {
--promise: --resolve(${Promise.resolve(1)});
--state: get(var(--promise), state);
class-toggle[--state]: true;
}
.page.pending {
text: "Waiting";
}
.page.rejected {
--reason: get(var(--promise), reason);
text: "Oops, there was a problem: " var(--reason);
}
.page.resolved {
--value: get(var(--promise), value);
text: "We got: " var(--value);
}
`;
dropping this idea in here from our convo yesterday related to the relationships between elements with behaviors; if I understood what you said correctly a moment ago, the idea above and this one are related at a deeper level in the implementation
omni-present behaviored element relationships
3 steps:
-
In the parent
sheet
:
--var: listen(--foreign-var, select(".element-with-behavior"));
resolves with the value of--foreign-var
from the already behavior-bound element returned byselect()
-
child behavior could have a dispatch fn in the
context
obj to dispatch --vars -
when listen captures in the parent/recipient, call that parent's
rebind()
And 3 notes:
-
a. Because we used
select()
in item 1, this implies pulling data up from a child and should be the recommended use. However, it doesn't prevent a dev from doing this instead if the need comes up, even if it's not something we necessarily want to promote because it could get spaghetti-y:
--var: listen(--foreign-var, ${domElWithBehaviorFromAnywhere});
This would replace the need for the idea of "portal" components because the relationship to data is omni-present. If the concept of a portal component is something the dev chooses to use in their implementation anyway (to avoid spaghetti code through convention), this would allow them to build one. -
b. the 3rd param of
listen()
could be a fallback like"loading..."
or anything -
c. This differs from the current
var()
inheritance in a specific and useful way that would be good to document (I don't remember the details but it boiled down to being able to chose one or the other as a way to choose the timing ofrebind()
updates)
Another idea I've been looking at is inspired by the CSS toggle spec and looks like this:
mount(root, class {
constructor(props, { stores }) {
// Can save stores here if you want
}
bind(props, { stores }) {
return sheet`
#app {
--app: "testing";
store-root: app;
}
.child {
store-set: app name "Wilbur";
}
.child button {
event[some-event]: ${() => stores.get('app')?.set('name', 'Anne')};
}
.sibling {
text: store-get(app, name);
}
#counter {
/** Can pass a store to a child behavior */
behavior: mount(counter, app);
}
`;
}
});
Bikeshedding on the name:
- store - As shown above, aligns with stores in other frameworks like Svelte.
- state - Maybe most obvious, but I kind of want to reserve that word for a future FSM feature.
- local - Like
local
keyword in some programming languages. - map - It's literally a Map, just be direct?
I went with the stores solution. It's available in 0.9.0 (a non-breaking change) and is documented here: https://corset.dev/learn/stores/
Here's a fetch example: https://codepen.io/matthewp/pen/qBVvZWr