Skip to content
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

[RRFC] Add ability to spread attributes, properties, and listeners to elements #26

Open
sorvell opened this issue Sep 18, 2023 · 2 comments

Comments

@sorvell
Copy link
Member

sorvell commented Sep 18, 2023

  • [X ] I searched for an existing RRFC which might be relevant to my RRFC

Motivation

This has been a long standing request and is generally useful when passing around blobs of related data that should be handed down between elements together.

Supporting this capability could also be used to satisfy [RRFC] Better Declarative Host Manipulation.

Example

html`<x-foo ${spread({value: this.value, class: {selected: this.selected}, '@foo': this.handleFoo})}>...`

How

Here is a slightly opinionated prototype of a spread directive. Sample usage with a bit of explanation inline:

html`<x-foo ${spread({
  '.prop': 5, 
  '@event': fn, 
  '?boolAttr': true,
   attrOrProp: 'prop if "in"',
   class: 'x' || {x: true},
   style: 'top: 0;' || {top: 0}     
})>...`

Opinions (favoring simplicity)

  • attribute or property is auto-detected with an in check
  • attributes automatically convert to boolean when value is boolean and unset whenever null-ish
  • classMap/styleMap behavior is built in

In addition, the prototype provides a host directive that can be used in ChildPart position, e.g.

html`${host({tabIndex: 0, 'aria-hidden': true, '@click': this.handleClick})}...`

And a tag directive that obviates the need to use static-html, also building upon spread, e.g.

html`${tag('button', {'@click': fn}, `Hi `, html`<img ...>`)}...`

Caveats

Values that are spread into elements can conflict with otherwise bound values. While not necessarily harmful and following a simple "last one wins" rule, this behavior could be unexpected or a potential foot-gun. It's up to the user to understand this and ensure proper configuration.

@maxpatiiuk
Copy link

maxpatiiuk commented Jul 12, 2024

A good workaround for spread attributes:

Instead of spreading attributes onto your element, have a function that applies the common attributes, and accepts additional customization props.

Thus, this:

const commonProps = {
  class: 'my-link',
  target: '_blank',
};
html`<a href="#" ...${commonProps}>${label}</a>` // not supported

turns into this:

function CommonLink({href, children}:{href:string, label:TemplateResult|string}): TemplateResult {
  return html`<a class="my-link" target="_blank" href=${href}>${label}</a>`;
}
/// later:
return CommonLink({href: '#', label: label })

Even better, CommonLink now supports spread syntax (for limited set of props), and you get good type-checking:

const commonProps = { href: '#' };
return CommonLink({...commonProps, label: label })

@filimon-danopoulos-stratsys

I don't think that this feature would be a good enhancement for Lit. The main drawback I see with it is that it muddles the template language in an unacceptable way.

The example posted gives the impression that the template is still legible.

html`<x-foo ${spread({
  '.prop': 5, 
  '@event': fn, 
  '?boolAttr': true,
   attrOrProp: 'prop if "in"',
   class: 'x' || {x: true},
   style: 'top: 0;' || {top: 0}     
})}>...`

I believe this example is not representative and a common template would be

html`<x-foo ${spread(props)}>...`

This is disruptive to how we read templates and makes it really hard to understand what a component does.
The use case of dynamic properties is niche enough that a "userland" solution is good enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants