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] List and enum attribute converters #4

Open
1 task done
justinfagnani opened this issue Aug 12, 2022 · 2 comments
Open
1 task done

[RRFC] List and enum attribute converters #4

justinfagnani opened this issue Aug 12, 2022 · 2 comments

Comments

@justinfagnani
Copy link
Contributor

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

Motivation

It's relatively common to have attributes that take a list of tokens (ie, class, part) or a token from a specific enum (input/type, rel, etc.). Lit doesn't provide converters for these common cases, but probably should.

Example

Enums

An attribute may have a set of valid values, say "happy" and "sad" in this example.

export type Mood = 'happy' | 'sad';

@customElement('mood-display')
export class MoodDisplay extends LitElement {
  @property({
    reflect: true,
    converter: new EnumConverter({values: ['happy', 'sad'], default: 'happy')})
  mood = 'happy';
}
<mood-display mood="happy"></mood-display>

When the attribute value is valid, the property will have the same value as the attribute. When the attribute value is invalid, we can choose the fallback value.

<mood-display mood="confused"></mood-display>
moodElement.mood; // "happy"

The default fallback would be undefined.

Option possible features

Case-insensitivity / Normalization

We can match attributes case insensitively and normalize property values to upper or lower case.

Non-string Enums

For non-string properties we could offer a mapping (bi-directional for reflection). That could be a function or an object provided to the converter.

Lists

@customElement('x-count')
export class XCount extends LitElement {
  @property({
    converter: new ListConverter()
  items = [];
}
<x-count items="one two five"></x-count>
countElement.items; // ['one', 'two', 'five']

Authors may want to support a different list separator, but we should encourage space as a separator to be compatible with the attribute selector operator |=.

How

Current Behavior

Authors have to write their own converters.

Desired Behavior

Authors can use one of our well-written ones.

References

  • n/a
@sorvell
Copy link
Member

sorvell commented Aug 12, 2022

Great suggestion, some questions:

  • Do these support reflection? If so, for lists do we want the property value to be an array that only updates if it's reset or a bespoke DOMTokenList-like object like classList which would reflect upon mutation?
  • Is it too limiting to place the validation and default purely at the attribute boundary? Why doesn't the property share these features?
  • Space separated is common for lists (class, part) but other forms exist that we might want to support like exportparts ("innerA: outerA, innerB: outerB").
  • input.type seems similar to the enum case and perhaps we should model after it? In that case setting the attribute sets the property to one of the enum values or text as default and setting the property actually does the same (e.g. input.type = 'foo' => type attr == 'foo', type prop == 'text')

@justinfagnani
Copy link
Contributor Author

Reflection

I think they should support reflection, so should implement toAttribute and serializing to a string should be straight forward.

I don't think it's really necessary to implement a DOMTokenList-like thing. That would be a lot more infrastructure than just a converter and I don't think it's a great API, since the more natural thing is to mutate the array. Not that the DOM has an observable array, we might also consider offering an observable array that's also a reactive controller, like:

class MyElement extends LitElement {
  @property({converter: new ListConverter()})
  items = new ObservableArray(this, []);
}

Validating properties

Good point. input/type has the behavior where if you set it to an invalid value and then read it, the value is "text". I actually don't like this behavior much, since it violates the standard behavior of properties. I expect that when I set a property and read it right back, it'll have the value I set.

I weakly feel like if an author wants to go that far, maybe they should implement their own setter. I think not changing a property from the value that's set is coherent with the attribute behavior too: a converter will not change the value of an invalid attribute, it'll just reflect the default value to the property.

Separators

I mention that we could support custom separators. I think it's ok, but likely a bad pattern to use too much. A microsyntax like for exportparts will require a custom converter anyway.

Input

See above. I think <input> in general is a little problematic to use a prototype case to follow - it's far too overridden IMO. I'd look to see how other enum values behave in the platform, and even then I'm not personally sold on properties spontaneously changing without any other code apparently running, if that's done elsewhere.

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

2 participants