lit / rfcs

RFCs for changes to Lit
BSD 3-Clause "New" or "Revised" License
15 stars 10 forks source link

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

Open sorvell opened 1 year ago

sorvell commented 1 year ago

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)

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 commented 2 months ago

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, children:TemplateResult|string}): TemplateResult {
  return html`<a class="my-link" target="_blank" href=${href}>${children}</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 })