-
Notifications
You must be signed in to change notification settings - Fork 1k
[lit-html] Add spread directive and attr templates #1960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| readonly [name: string]: unknown; | ||
| } | ||
|
|
||
| class SpreadDirective extends Directive { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider whether this should be an AsyncDirective so that its disconnected callback can undo all the parts if you render a different value to the expression (or whether spread must be static), e.g....
<div ${bool ? spread({foo, bar}) : nothing}></div>To be fair, we don't do a good thing for e.g. classMap in this situation either (it would also need to be disconnectable), which seemed a bit overkill given the performance hit:
<div class=${bool ? classMap({foo, bar}) : nothing}></div>There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is the most complete implementation, but given that it's more costly it might be better to keep things simple. I think this pattern should suffice:
html`<div ${spread(bool ? {foo, bar} : undefined)}></div>`| } | ||
| } | ||
|
|
||
| render(_values: SpreadValues) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can imagine use cases where it'd be nice to let values be nullable, to avoid users from needing to always make a throwaway object in the alternate case:
<div ${spread(maybeGotStuff ?? {})}></div>or
<div ${spread(shouldRenderStuff ? stuff : {})}></div>Again, we don't do this in classMap/styleMap, but I think it's more likely to have patterns where entire bags are forwarded in (e.g. "formControlAttributes") vs. being generated by the user (otherwise the user could have just written the attribute bindings themselves).
But it does sort of beg the question what we expect spread to actually be used for in the OSS world.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, as per the other thread.
I imagine this form of spread to be a lower level thing that's often wrapped for better DX. An element might receive a bag of property or event bindings and need to add the correct prefix. attr will wrap this. Custom attribute-merging strategies will... do something. We need to make sure that's possible.
| } | ||
|
|
||
| render(_values: SpreadValues) { | ||
| // TODO for SSR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any nascent ideas here? Seems like we could go two ways here:
- Return an array of descriptors of the part types + values, and figure out how to reuse the
attribute-partopcode handling to handle the per-part-specific stuff:case 'attribute-part': { - Return the array of acutal part instances (iow, use the update code) that the
attribute-partopcode handling can directly use, and figure out how to deal with the fact that you don't have an actual part to get the element to pass inrender(which isn't technically a problem in SSR because it doesn't need the element).
We do need to reuse code in the attribute-part opcode handling to ensure directives passed to spread work correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure yet. I think something like just forwarding the object in an AttributeResult type might work, even if it's a bit weird to explain how that works or doesn't client-side.
I think figuring out hydration is the hard part here...
|
How do you feel about having a directive which spreads only properties, and one that spread binding like the current implementation? A very common use case is spreading an object of properties, often coming from some other source. Right now users would need to always transform the object to have all keys start with a |
|
I think that's a good idea, but hopefully can be well expressed as a wrapper on this: spreadProps = (props) => spread(Object.fromEntries(Object.entries().map(([k, v]) => ['.' + k, v])));If the |
|
@justinfagnani that wrapper would do quite a lot of work each render though, so would be nice to make that more convenient. |
|
Is there news on this? Thanks in advance! The Carbon team is interested in use it: It's one of the main things they are waiting to update to Lit v2. cc @jeffchew |
|
|
any chance this is going to happen within January 2022? we are thinking about waiting or hacking our own version for now π |
|
It'd be great to have this feature soon. I had upgraded without realizing it was missing and now I'll need to downgrade back. If figuring out how to be deterministic when there are multiple objects with duplicate keys, why not simply limit it to a maximum of one spread operation per element and let them merge objects before. Then you don't have to try and come up with a deterministic approach for everyone, as they can solve it beforehand themselves. This PR was started back 7 months ago and we still don't have a solution. =S |
|
It's been a whole year. What is the current status of this feature? |
This needs more tests and I'm not entirely happy with using the private object this way, but the general approach works and is small.
Tests needed: