Lit-element: Ideas for LitElement 3.0

Created on 10 Sep 2020  Â·  76Comments  Â·  Source: Polymer/lit-element

LitElement 2.x has been out for about a year, and we’ve been considering what changes we should make for the next major version. We wanted to share our thoughts and get feedback from our users about potential improvements and breaking changes.

Overall, we’re generally pretty satisfied with the core functionality and API and we definitely want to keep in mind that dealing with breaking changes is a hassle for users. We do, however, think there are some good opportunities for improvement. The lit-html library is also considering changes for the next major version, and we’ll definitely be including the next version of lit-html with the next version of LitElement.

Goals

Backwards compatibility for most users

We know that dealing with breaking changes is annoying and we’re trying to carefully manage our change budget such that each change has either a big return on investment or is likely to affect very few users. If necessary, we may elect to create a compatibility version that maintains 2.x behavior if it makes sense.

Performance

We love performance and are always striving to improve it. The next version of lit-html has a number of performance improvements from which LitElement will naturally benefit.

Size reductions

There’s some good opportunity for improvement in the core size of LitElement. First, the next version of lit-html should be getting a good bit smaller. Then, there’s a good bit of refactoring we can do to help make sure users are only getting the code that they use, including making old browser support opt-in, making decorators opt-in and more granular, and streamlining some of our internal API.

In addition, we may publish a minified, bundled build so that we can ensure that a heavily minified version works correctly. While we believe minification is ideally an app concern, we realize that apps won't know all the transforms that can be safely applied to our code.

Developer Experience

We want to make sure LitElement is easy to use for all our users. We know that some web devs love the pure experience of downloading some code, editing in a text editor, and seeing the output in a browser and we want to make sure that we support that use case better.

We would like to improve error messages, and not let publishing minified code negatively impact development. We'll look into producing both production and development builds.

Easier SSR

We know users want SSR, both for SEO and first paint performance. By leveraging the support for SSR being built into the next version of lit-html, LitElement will support SSR and it will be able to take advantage of the declarative Shadow DOM feature being added to Chrome.

New Features

While we want to consider some feature additions, we’re pretty happy with the core functionality. One thing we’d like to explore is providing a package of helpers for features not everyone needs but are really helpful for specific use cases or are just one way to skin the cat.

Fix bugs that require minor breaking changes

We have a number of longstanding minor issues that we haven’t been able to fix because they are just slightly breaking changes. We’d like to evaluate those and address what we can.

API cleanup

We think there’s a number of opportunities to reduce code size by carefully sweeping through the API. This should mostly be internal changes, but there may be some minor changes to the public facing API where we think there’s a strong benefit.

Potential Changes

Opt-in polyfill support

Although the Shady DOM polyfill itself is opt-in, there is some code baked into LitElement to use the polyfill that all users must pay for, which adds close to 1KB (~15%) to the bundle size. We’d like to make this opt-in so users who don’t need to support older browsers don’t pay the cost of downloading the polyfill specific code.

Opt-in and granular decorators

LitElement's main module exports all of the decorators we support regardless of whether or not they are used. We will break these out into granular modules that users will import if they use. This will also allow us to more freely add new decorators where useful.

Package that works out of the box without building

For optimal code size a build will always be best, but we want to provide an easy way to try LitElement without using npm and building the code.

Guidance for use in popular frameworks

The main value proposition of web components is that they work everywhere the (modern) web works. That’s true but they should also work seamlessly with modern web frameworks as well. We want to make sure LitElements work great in popular frameworks like React, Vue, and Angular. As shown on custom elements everywhere, the situation today is pretty good, and we may only need to create a set of examples showing what’s possible. However, we will be considering patterns and helpers that can make things easier for framework users.

Code sharing primitive

We think that subclassing and mixins cover most of the cases where users want to share code between elements. However, there are some cases they don’t cover. We’ve been impressed by efforts in the community to address those use cases, in particular React Hooks and the Vue composition API. We think there’s an opportunity to do something similar but probably a lot simpler for LitElement. This might take the form of controllers that can bundle functionality via a has-a relationship with the element and that can interact with the lifecycle of LitElement. Since not everyone needs this feature, this is probably a good candidate for a helper package.

SSR Support

When SSR output is being generated we need to consider which parts of the element lifecycle to run. It’s a goal of our SSR implementation not to require a server side DOM implementation and therefore code that uses DOM APIs will have no or very limited support. We will likely be modifying the update cycle for the element when it’s run on the server to avoid methods which often perform DOM manipulation, for example the firstUpdated and updated methods.

Theming

Platform support for theming is still incomplete. CSS custom properties are great for tree-based theming of known sets of properties. The new ::part selector works well for arbitrary theming of specific elements. However, the exportparts attribute is a cumbersome way to expose parts up the tree. In addition, it’s often desirable to allow users to style a set of unknown properties but not everything. There are a lot of patterns that can help here and we may end up supporting a few of them, perhaps in a helper package. We like the Vaadin themable-mixin and something like that for LitElement may make sense. We also like the power and configurability of Sass mixins and may explore exposing something similar via a Javascript API.

Declarative events

Declaring what events your element fires is great for self-documenting. We can also follow the DOM convention of providing an on prefixed property to listen to the event. Having on prefixed properties also improves integration with frameworks like React that configure events this way. We will probably implement this as a decorator but will have some form of raw JS support as well. See this issue for more info.

Common customization like shadowRoot rendering

To customize how the element’s shadowRoot is created you currently have to override createRenderRoot. We’d like to make this a bit easier and may add a decorator or static field with which you can supply the options object to attachShadow. For example:

static shadowRootOptions = {mode: ‘open’, delegatesFocus: true}

We may also explore this for other common but simple to configure customization points.

Declarative host manipulation

While we use lit-html to render the element’s Shadow DOM, attributes and event listeners on the hosting element itself must be added imperatively. We may be able to use some of the new features in lit-html 2.x to create a declarative way of doing this. This might take the form of a renderHost method which returns a lit-html TemplateResult that’s rendered on the host element. See this issue for more info.

Update coordination

LitElement updates asynchronously at microtask timing, and while this can’t be observed by the user, it can be by code. After an element has been interacted with, it’s sometimes useful to know when it’s finished rendering; for example, this is useful in testing, when firing events, or when you need to know the element’s rendering is stable. Although this can also be used for measuring DOM, it’s usually better to measure in a requestAnimationFrame callback. LitElement provides the updateComplete promise for these use cases. However, this only covers the element itself and not any LitElements in its shadowRoot. To wait for those elements, typically you override the updateComplete property and await those elements as well. That’s a bit cumbersome, and we want to explore adding a helper that makes this easier. Since this is only sometimes needed, this might be another feature which gets added to a helper package.

API Core Libraries Needs Discussion Enhancement Enable new use cases Improve ergonomics Improve performance Reduce adoption friction

Most helpful comment

@funglaub

A functional approach such as react hooks would really be nice.

We feel that we already have a pretty functional approach with the render() method of LitElement.

There's a lot of dislike of classes out there, but for this case where we 1) are creating objects by nature of the DOM and 2) do need to declare properties and attributes, classes are actually more declarative than a "pure" functional API where attributes and properties must be declared without the advantage of native syntax for it.

All 76 comments

Excited for this. A couple things that came to my mind:

  • Maybe https://github.com/Polymer/lit-element/issues/469 can be revisited? It is no longer much of an issue for us, because we nowadays mostly use the updated callback, but I still find the issue I raised there to be worthwhile fixing and now might be the time.
  • Using updated extensively, I like it, but I feel some of the user experience could be improved:

    • If someone renames a property they also have to rename all the occurences of that property in updated. Worse even there would be no (compile) error, so this would only ever be noticed when the component is in use or testing.

    • It would be nice to have this typed too.

      I do not know how to make this happen I am afraid, but maybe there is something Typescript can provide here.

  • Maybe the docs could provide some additional information on bindings and how to handle rerendering. Sometimes we encounter an issue that a property changes within a component and then the component gets rerendered and the state is lost. I would love some more info on when this happens, how this happens and how to best deal with it.

Oooo, lots of good stuff to chew on here… I'm particularly interested in the "Code sharing primitive" angle, as it something I was thinking about recently as well. I did a lot of StimulusJS work before becoming enamored with LitElement, and one advantage Stimulus has is you can attach multiple controllers to a single element. It would be cool if I could explore an analogous pattern in the LitElement world…

@Christian24 #469 is already addressed in 3.0. requestUpdate() does not return a Promise anymore, and you have to specifically call await this.updateComplete.

may we have good documentation on bundling large apps, and coding exercises / challenges.

A functional approach such as react hooks would really be nice.

may we have good documentation on bundling large apps, and coding exercises / challenges.

I think that's already taken care of by open-wc?

How early do you think we could start to see tech specs on the controller proposal mentioned herein? It sounds like a great tool for empowering both early LitElement learning as well as advanced/shared application development. I’d love to see whether you’d see it deeply integrated with LitElement, a pair to lit-html, or something more abstractly applicable to UI development at large.

@funglaub

A functional approach such as react hooks would really be nice.

We feel that we already have a pretty functional approach with the render() method of LitElement.

There's a lot of dislike of classes out there, but for this case where we 1) are creating objects by nature of the DOM and 2) do need to declare properties and attributes, classes are actually more declarative than a "pure" functional API where attributes and properties must be declared without the advantage of native syntax for it.

@Westbrook

How early do you think we could start to see tech specs on the controller proposal mentioned herein?

I'll try to publish an issue today.

Exciting ! :)

On Fri, Sep 11, 2020, 11:28 AM Justin Fagnani notifications@github.com
wrote:

@Westbrook https://github.com/Westbrook

How early do you think we could start to see tech specs on the controller
proposal mentioned herein?

I'll try to publish an issue today.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/Polymer/lit-element/issues/1077#issuecomment-691162521,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AFSDL2W6OPJSKVVVSCGAXI3SFI62JANCNFSM4RDTWGKQ
.

I really like LitElement, so thank you in advance!

I think it would be awesome to see like a showcase of who else/what companies are also using it as this would be great publicity and help advocate its usage in organisations.

I think more demos/starter kits, already mentioned is how to use it with other frameworks but I think examples of large applications/advanced usage would be great and also starter kits that can generate the boilerplate for standard usages e.g. with an app shell, routing, linting, testing etc. Would be happy to contribute to these!

Replace html templates with compiled js. Like Svelte and solid-js

@kmmbvnr

Replace html templates with compiled js. Like Svelte and solid-js

Any reason why?

@justinfagnani

Any reason why?

Performance ?

It would be great to get support for <slot> or an equivalent for the light dom. If I'm not mistaken, Stencil supports this. The thing is, you don't always need or want shadow dom, but the concept of slots is still very handy.

It could be nice to streamline this kind of pattern with decorator options

class WaitsForChildren extends LitElement {
  render() {
    return html`
      <x-l id="a"></x-l>
      <x-l id="b"></x-l>
    `;
  }

  async _getUpdateComplete() {
    await super._getUpdateComplete();
    await Promise.all(Array.from(this.shadowRoot.querySelectorAll('x-l'), x => x.updateComplete);
  }
}

``ts class WaitsForChildren extends LitElement { @query('#a', { awaitUpdate: true }) a: XL; @query('#b', { awaitUpdate: true }) b: XL; render() { return html


`;
}
}

class CustomInput extends LitMixin(HTMLInputElement) { ... }

// or

@builtIn(HTMLInputElement)
class CustomInput extends LitElement { ... }

// or any other solution to reconstruct the prototype chain

One thing I've been running into a bunch lately is when I have a Lit component that requires me to know my own component bounds as part of the render (I'm absolutely positioning elements), there's no great update mechanism. In fact it was kind of terrible previously, but at least now I can listen to ResizeObserver. It might be nice to have some set of bindable properties that the user doesn't change, but they can opt into that trigger requestUpdate.

For example:

@property({ type: Number, internal: 'bounds.width }) protected componentWidth: number;

Bounds of course would be from this.getBoundingClientRect.

I'm not married to the my implementation suggestion by any means, but all the same, if it was similarly implemented, it could open the door to anything that you might add-on here if there was an API to do so (timers, sensors, data request results, etc)

Honestly, this type of functionality probably doesn't have to be part of Lit, but I'm kinda sick of re-writing ways to handle this. So if not Lit core, some kind of companion util might nice

@bengfarrell I think this might be nicely addressed with what I've been calling recently "reactive controllers" - helper objects that are able to hook and trigger the host element's lifecycle.

Imagine a controller called BoundsController which installs a ResizeObserver and on resize pulls the bounding rect and triggers an update. It'd be useable like:

class MyElement extends LitElement {
  private _bounds = new BoundsController(this);

  render() {
    return html`<pre>width: ${this._bounds.width}, height: ${this._bounds.height}</pre>`
  }
}

Note, this is pretty easily writable today - it doesn't require any new APIs on LitElement.

@justinfagnani I think that's exactly the type of thing I was steering towards if I put more thought into it. And yes, it IS easily writeable today, but as a user the design pattern didn't really occur to me until I thought about what I was missing. And this is why I'm wondering if a few reactive controllers could be shipped alongside Lit (just import when you need it, not part of the core lib), in the same way you're offering extra directives. The few that ship could solve some common problems like bounds, but more importantly offer guidance to create your own. Of course, this might fall under the category of "too opinionated" a design pattern to ship with Lit

@justinfagnani CustomElements spec allow you to register a component only once which is very much sensible but it would be great if the decorator @customElement('my-element') can skip declaration if it is already in the registry instead of failing with an error.

Currently I am doing customElements.get('my-element') || customElements.define('my-element', MyElement); to support this.

This is relevant because I use storybook for demoing and strangely, when you include same components in multiple different stories, they get registered multiple times throwing errors in the storybook.

A similar issue in a diffent context was raised here I guess: https://github.com/Polymer/lit-element/issues/207

Another thing which may not be relevant to this context is that, while everything is awesome, VSCode support is limiting. There are 1 or 2 extensions but they don't support everything like css language support within the css tags (or am I doing something wrong?)

1

Also, there is no syntax highlighting in this case 😢

Infact, I am even wondering if it is recommended to put the .css file separately or write css within the css tags.

PS: The <style> tag within html works well though but I just use html tag for html and css tag for css

Also, for people who are starting off something which you guys did like PWA helpers (https://github.com/Polymer/pwa-helpers) for lit would be great. While open-wc has https://open-wc.org/developing/lit-helpers.html it may either need more additions to it or you may want to take it up. For eg. the recommended way to do routing (with each element handling its own routing) would be great. But, this is not a must have and not related to lit 3.0 either, just a suggestion.

@tvvignesh

@customElement('my-element') can skip declaration if it is already in the registry instead of failing with an error.

We'd rather rely on a standard solution to this like Scoped CustomElementRegistry. Protecting against this exception can mask true errors in a program. We don't think that first-to-register is a reliable way to handle conflicts.

I also wouldn't want to make @customElement harder to explain. There's a big benefit to saying that it's just a declarative form of customElements.define() and behaves the same.

Re: VS Code plugins, I get CSS highlighting with lit-plugin: https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin I'm not sure what's happening for you, but I'd use that or file an issue if you already are.

Re: App recommendations. We're definitely looking into addressing these topics directly in docs. I'm not sure we'll call them recommendations or not, but we do want to guide developers towards answers for common needs like routing.

Hello all !

Some really great stuff going on in here - I wanted to post a snippet of some code which aims to give inspiration as to how we use controllers and lit-element within Runtime.dev today in our products - which is built on the component controller model as well as on top of LitElement. I also wanted to share some thoughts on the aforementioned topics!

Runtime.dev's component lib is built on a component controller model and seeks to achieve some of the things that are being noted here, Injectable styles, differential rendering, separation of state/logic and markup, both DOM and JS event dispatching & execution management, graph-linked/connected controllers, dynamic accessibility & focus management through controllers, etc. The list goes on.

protected description = {
        // Icon Controller Used in coordination with the input's validation state
        icon: new IconController({
            // Initial Icon SVGTemplate
            svg: CIRCLE_ICON,
            // Standardized rem sizing - can use IconSizeEnum.INHERIT to style externally 
            size: IconSizeEnum.SMALL,
            accessible: true, 
            aria: {
                label: 'Description Completed Icon',
                role: AriaRoleEnum.PRESENTATION
            }
        }),
        input: new InputController({
            // Differential Theme
            theme: DefaultTheme,
            // Initial States of input validation 
            valid: false,
            // Fully async Validation Function 
            validate: async ({ value }) => {
                const valid = Boolean(value);
                // Updating icon of other controllers when validated state changes 
                this.description.icon.svg = valid ? CHECK_ICON : CIRCLE_ICON;
                return {
                    valid,
                    error: {
                        message: '',
                        caught: false
                    }
                }
            },
            placeholder: 'A description also...',
            // Injectable templates that get injected into shadowRoot as opposed to slots 
            trailTemplateFn: ({ valid }) => html`<runtime-icon .controller=${this.description.icon} class=${valid ? 'valid' : 'invalid'}></runtime-icon>`,
            // Injectable Styles which apply to injected content
            styles: [css`
                runtime-icon.invalid { 
                    ${svgPrimaryColorLightest};
                }

                .valid { 
                    ${svgPrimaryColor};
                }
            `],
            //JS Tied in event callback
            engagedCb: async (e) => console.log(e)
        }),
    };

    get details() {
        const { information: { card, descriptor }, name, description, } = this;
        //Inject all controllers into markup
        //Components handle Animation, Interaction, Layout, and Styles
        return html`
            <runtime-card marginTop mdPadding .controller=${card}>
                <runtime-vertical-layout startAligned id="details">
                    <runtime-text .controller=${descriptor}></runtime-text>
                    <runtime-input marginTop .controller=${name.input}></runtime-input>
                    <runtime-input marginTop .controller=${description.input}></runtime-input>
                </runtime-vertical-layout>
            </runtime-card>
        `;
    };

Obviously Runtime.dev takes this model to an extreme and is several generations in but I think that the direction of LitElement/HTML 3.0 through a component-controller model is a great evolution.

A few things to note IMO:

  • The Hybrid Approach of drop-in controllers (Runtime V1 did this), it's too flexible and doesn't scale well. We moved to an entirely controlled structure where if a component supports a controller - it is dependent on that controller. Otherwise it's considered a dumb component and is primarily visual/structural. Additionally all components have one controller that encapsulates a subset of functionality.

  • Sizing best to be managed externally we have Positioning Engines and Virtualization Engines Dedicated to this - as opposed to being handled internally. Adding a resize observer and then running getBoundingClientRect may drill perf especially if you use it like this often. Also - developed systems being to want to understand how things are sized in relation to each other and how things may render in context of other things rendering around them, this is particularly important for virtualization, masonry layouts, and grids. (Virtualization of Google Photos)

  • Waiting on children to update/render is also easily implemented if all of your children have controllers as opposed to some of them however - if only some children have controllers and others don't then this introduces inconsistencies/uniformity problems. I would suggest this type of thing to be implemented on the component level as opposed to through a controller infrastructure. The same thing applies to focus management. It works out best if everything that is focusable is also controlled.

  • I would suggest that elements take in only one controller and that controller is explicit in its capabilities so that the expectations of what is available (Sizing, Theming, Updates, Etc.) becomes standard almost as we expect things such as connectedCallback and attributesChangedCallback within native WC. Treating controllers as directives is something that I would recommend avoiding - the extension based controller system has worked out very well for us: note in the image below our InputController extends an InteractiveController. Which guarantees particular behaviors and functionality as opposed to having to remember if a component has x/y/z controllers such as theming/bounds/etc. This also makes it easier to build systems that manage groups of controllers such as focus engines, placement engines, and state engines.

Screen Shot 2020-09-17 at 2 34 18 PM

If you want to get a sense of of this direction and how it impacts WC applications at scale take a look at these:

Component Controller Model & LitElement

Component Controller Model At Scale Google Slides

Talk for Slides

Nevertheless - a bit of a shotgun thought process here due to controllers simply opening the floodgates on possibilities. Our lit-element WC have been controlled for about a year now and we have learned a lot. I would love to see a community perspective on this as it may impact how we move into the future and perhaps how we can help you avoid the madness we ran into here and there.

Would be happy to have a live chat/meet and go over a couple of these things/answer some questions. Runtime isn't open sourced yet - we are developing our public License right and scoping public demand for such a thing.

It is however actively being Licensed to our partners and we are under serval NDAs with Google, Microsoft, and others so I am available to guide/brainstorm pretty extensively without getting in trouble.

We are currently focused on building an upcoming product but we are always improving Runtime and we would love help bringing it into the community in conjunction with LitElement 3.0

I also read the PR's and Issues for LitElement and LitHTML daily and so I am very informed and happy to give my perspective on design choices being made and why we did or didn't do something similar.

Feel free to reach out personally at tsavo.[email protected] or @KnottTsavo on twitter.

Sorry for the typo's I'm on the run today!

Anyway - have a great day and happy coding!

All the best,
-Tsavo

@tvvignesh

@customElement('my-element') can skip declaration if it is already in the registry instead of failing with an error.

We'd rather rely on a standard solution to this like Scoped CustomElementRegistry. Protecting against this exception can mask true errors in a program. We don't think that first-to-register is a reliable way to handle conflicts.

I also wouldn't want to make @customElement harder to explain. There's a big benefit to saying that it's just a declarative form of customElements.define() and behaves the same.

Re: VS Code plugins, I get CSS highlighting with lit-plugin: https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin I'm not sure what's happening for you, but I'd use that or file an issue if you already are.

Re: App recommendations. We're definitely looking into addressing these topics directly in docs. I'm not sure we'll call them recommendations or not, but we do want to guide developers towards answers for common needs like routing.

@justinfagnani Thanks for your hint regarding the extension. Switching to the extension you had suggested worked great (able to get syntax highlighting and autocomplete for everything now 😃) I was using https://marketplace.visualstudio.com/items?itemName=bierner.lit-html before which has huge number of downloads.

Capture

Regarding scoped custom element registry, I read about the discussion you had initiated here: https://github.com/w3c/webcomponents/issues/716 and your gist here: https://gist.github.com/justinfagnani/d67d5a5175ec220e1f3768ec67a056bc . Looks like it has been 3 years since it started. Would it get implemented? 🤔 And even if it does at some point, I guess it would take ages for all the users to get the updates with all the browser vendors taking their own time to support it 😟 Anyways, I guess I have to go with my hack till then.

Btw, on a side note I remember this talk back when Polymer was the thing: https://www.youtube.com/watch?v=tNulrEbTQf8
Does Youtube still use Polymer or has it migrated to Lit? Any reference architecture we can get from there would be great. Currently I am sticking to the Open-wc recommendations but some real world prod reference would be great to have.

Switching to the extension you had suggested worked great (able to get syntax highlighting and autocomplete for everything now 😃

I miss code collapse support: https://github.com/runem/lit-analyzer/issues/31

I really like LitElement but tooling support is way behind other libraries like React, Vue

Switching to the extension you had suggested worked great (able to get syntax highlighting and autocomplete for everything now 😃

I miss code collapse support: runem/lit-analyzer#31

I really like LitElement but tooling support is way behind other libraries like React, Vue

Yup. I completely agree with you. Lit is great for the most part with all the features it currently has. So, instead of bloating it or changing things up a lot, its more important to put the investment to efforts like what Open-wc (Now modern-web) is doing, emphasising more on the performance and maybe create helpers, tooling and focus more on efforts towards things like this: https://github.com/material-components/material-components-web-components which can be a boon for not just people using Lit but also for people coming from other frameworks.

@tvvignesh

I agree that Lit* (in fact web components) needs improves its ecosystem with e.g. more mature feature rich UI libraries / design systems but in my opinion performance is pretty good as is.

Developer experience should be priority (it's already good but things like missing code collapse in IDE gets in my way from time to time)

@tvvignesh

I agree that Lit* (in fact web components) needs improves its ecosystem with e.g. more mature feature rich UI libraries / design systems but in my opinion performance is pretty good as is.

Developer experience should be priority (it's already good but things like missing code collapse in IDE gets in my way from time to time)

DId you try setting "editor.foldingStrategy": "indentation" as mentioned there. It worked for me.

DId you try setting "editor.foldingStrategy": "indentation" as mentioned there. It worked for me.

No. See my comment there.

Thats just like i said above: from time to time issues in lit-* tooling requires me to think about it, look for workarounds etc. In a mature ecosystem this does not occurs

@bengfarrell I think this might be nicely addressed with what I've been calling recently "reactive controllers" - helper objects that are able to hook and trigger the host element's lifecycle.

Imagine a controller called BoundsController which installs a ResizeObserver and on resize pulls the bounding rect and triggers an update.

Why not go all the way and also trigger life-cycle events from data change observations, like in MVVM? That could replace property change observations and allow for a pluggable way of triggering updates.

I would also like to throw revisiting #812 in the mix. The issue is about using ES2015 proxies to support reacting to changes to an object property or an element in an array. Currently such assignments are not recognized and need workarounds leading to confusion for some of my colleagues and errors.

Vue 3 is implementing the same feature and as shown by the last comment the implementation is fairly simple. As the next version of lit-html is not going to include IE11 support in the core library I think this is a good time to implement this.

Make custom elements compatible with native elements

@septatrix lit-mobx already does a great job of that: https://github.com/adobe/lit-mobx It's very similar to Vue 3's reactivity library. There's a number of different way that LitElement authors are handling state change tracking, from MobX to Redux, to events + immutable data. I don't think we want to chose a particular way, but enable many of them. Being able to hook update() and call requestUpdate() are powerful primitives for these libraries to use.

@busynest I'm not sure what you mean

@justinfagnani With a proxy based observable approach one can replace a big part of lit-element's property tracking. A nice library to use for that is https://github.com/nx-js/observer-util.
I have done exactly that with https://github.com/kwaclaw/KdSoft.lit-mvvm.

I also see this as a superseeding of the current approach. Proxies allow a greater control and are able to react to more changes. Furthermore implementing this natively could be done in only a few lines and not add the weight of a comparatively huge dependency lige mobx, redux etc.

My biggest problem was always that the I couldn't just develop with it because of the module specifiers. In the end the solution I had to use was to use rollup at npm install time to copy to a client/libs directory (most of my apps each has a common node_modules and separate server and client directories). I like that you are considering the out of the box ready to use - but please note that node_modules isn't always located at the root of the client directory.

Other than that lit-html, lit-element and webcomponents loader is all I've needed (other than my own utilities) (I don't build, my production systems don't seem to need it - not even minified anything).

@akc42 I am using Snowpack for the same purpose.

Nice work, excited about "Declarative Events"!

Be able to pass an @query to another child element.

Currently if I need to pass a sibling to another child element I would do something like this:

@query('foo-element') foo!:FooElement;

render() {
  return html`
    <foo-element></foo-element>
    <other-element .foo=${this.foo}></other-element>
  `;
}

However that will not work on first render since isn't in the dom yet and the query will fail. So currently you have to work around this by waiting for first update and then querying and then manually passing to the other child. It would be great if there was some mechanism that the property could automatically be resolved without rendering twice or reverting to some imperative programming.

Typed PropertyValues would be really clutch. For static properties getter, I'm sure it could be inferred. not sure about decorators, though.

@property() newPropName: string;

updated(changed: PropertyValues) {
  if (changed.has('oldPropName')) 
    whoops();
}

I would love to see a considered answer to the question, "what does it mean to render without a shadow root?"

In practice, one does not always need to create a shadow root in order to have a useful component implementation. There are a handful of cases today where it would be better not to use a shadow root.

  • A11y may take priority over style encapsulation
  • SSR may be desired without the complexity of emulating declarative shadow roots
  • I may wish to render a top-level document that is more complex than a single application element

LitElement as of 2.x makes rigid assumptions (as a matter of internal implementation) regarding the usage of a shadow root. This suggests to users that although it may be possible to create components without a shadow root, it is not the intended usage of LitElement. This in turn interrupts the exploration and adoption of novel, higher-order usages of LitElement.

It is not necessary for LitElement to establish a first-class workflow for components that do not use shadow roots. However, the LitElement base class should become a viable candidate for such a workflow, one that does not levy a cognitive or performance tax on authors who seek to use it that way.

Idea for lit-labs package: consider implementing a portal directive which could serve for building modals, tooltips etc.

Right now pretty much every more or less complex web components library has its own mechanism. At Vaadin, we use vaadin-overlay which teleports elements under document body. Other alternatives include scanning DOM tree to manage z-index.

It would be nice to have a recommended approach. There used to be iron-overlay prototype under PolymerLabs but it hasn't been finished.

https://github.com/Polymer/lit-element/issues/1077#issuecomment-694749771

Does Youtube still use Polymer or has it migrated to Lit?

(totally off topic lol) Yikes. YouTube still uses Polymer?! No wonder its so slow /s. I had been using an extension to use the old YouTube until YouTube broke it earlier this year. :(((
um. yay opt-in polyfills. ie needs to die.

Any solution to solve unistore binding to LitElement.

My not ideal solution...
I would like to declare dynamically properties for the state props. I must declare observedState object now.

export const connect = store => superclass =>
  class extends superclass {
    static get properties() {
      return {
        // The observedState contains selected the store state props by observedStateProps var.
        // Any change of observedState will be run render() of LitElement.
        observedState: { type: Object },
      };
    }

    constructor() {
      super();
      this.state = {};
      this.oldState = {};
      this.observedState = {};
      this.observedStateProps = [];
      this._stateChanged = this._stateChanged.bind(this);
    }

    connectedCallback() {
      if (super.connectedCallback) super.connectedCallback();

      store.subscribe(this._stateChanged);
      this._stateChanged(store.getState());
    }

    disconnectedCallback() {
      store.unsubscribe(this._stateChanged);

      if (super.disconnectedCallback) super.disconnectedCallback();
    }

    // The `_stateChanged(state)` method will be called when the state is updated.
    _stateChanged(state) {
      const observedState = {};

      this.observedStateProps.forEach(prop => {
        observedState[prop] = state[prop];
      });

      this.oldState = this.state;
      this.state = state;

      if (
        Object.keys(observedState).length &&
        !equals(this.observedState, observedState)
      )
        this.observedState = observedState;

      this.stateChanged(state);
    }

    // eslint-disable-next-line class-methods-use-this
    stateChanged() {}
  };

Inspired by

(apologies, as this is somewhat off-topic)

@JosefJezek I have experimented with Unistore-like patterns on top of LitElement. I have a very radioactive / not-well-documented repo with my experiments here: https://github.com/cdata/bag-of-tricks

Usage ends up looking like this:

// A store:

const store = new Store<FooState>({
  bar: 'baz'
});

// An action creator that operates on the store:

const setBar = (bar: string): Action<FooState> => (getState, dispatch) => {
  return {
    ...getState(),
    bar
  };
};

// A connected component:

@customElement('foo-component')
export class FooComponent extends connect<FooState>()(LitElement) {
  @property({ type: String })
  @selector<FooState>((state) => state.bar)
  bar: string?;

  #setBar = selfDispatch(setBar);

  render() {
    return html`
<button @click="${() => this.#setBar('zot')}">Set bar to zot</button>
    `;
  }
}

// Meanwhile, in some entrypoint:

provide(self, store);

Some details about the above example:

  • Store extends EventTarget, and state changes are announced with dispatchEvent
  • A connect mixin creates a "connected" component without coupling it to a specific Store
  • selfDispatch is a helper that takes any action creator and returns a self-dispatching one (e.g., it automatically invokes store.dispatch(...) on the currently connected Store when invoked)
  • The provide helper takes any EventTarget and sets it up to provide connected components with the designated store as they are attached down-tree
  • The @selector decorator binds state in the store to properties on the component

The thing I like about this approach is that it neatly decouples the store from the component, which makes testing a lot simpler. In the course of a test, we can trivially substitute different stores without changing the implementation of a component (or trying to come up with some kind of fancy module substitution for its dependencies).

Anyway, feel free to borrow any of those ideas. I would be happy to talk about it more in an issue thread in that repo, if it interests you.

@justinfagnani Great things are being proposed here, I only wanted to ask you to keep LitElement free from to much "sugar code" and keep LitElement standards, I personally choose LitElement because is near to the platform and it doesn't look like Angular or React or any other Framework/Library...

right now LitElement does so magical merging of properties... which means that if you do

static get properties() {
   return {
     ...super.properties,
     newProperty: { type: String },
  }
}

there are some things that seem to have "bad side effects" (at least for us if we do this a lot of tests fails)

Could we get rid of this "magical merging" in LitElement 3?

@daKmoR I think that would stretch the breaking change budget, as it would break all subclasses of superclasses with properties.

UpdatingElement's property declaration merging is designed to mirror class inheritance so there's as close of a match in behavior between properties block and decorated class fields. With class fields you only have to define the class's own field and you automatically inherit the superclass's fields.

Why are you trying to spread super properties? Is something not working with the built-in inheritance?

@justinfagnani we would like to use typescript but not decorators.

And if you do not do ...super.properties then typescript rightfully complains

consider this example (also live on webcomponents.dev):

import { LitElement, html, css, CSSResult, TemplateResult } from "lit-element";

export class MyElement extends LitElement {
  static get properties() {
    return {
      greet: { type: String },
    };
  }

  greet = "nobody";

  protected render(): TemplateResult {
    return html`<p>Hello <strong>${this.greet}</strong>!</p> `;
  }

  static get styles(): CSSResult {
    return css`
      :host {
        display: block;
      }
    `;
  }
}

export class SweetElement extends MyElement {
  static get properties() {
    return {
      ...super.properties, // without this TS complains
      nice: { type: String },
    };
  }

  nice = "sweet";

  protected render(): TemplateResult {
    return html`<p>Hello <em>${this.nice}</em> ${this.greet} 🤗</p> `;
  }
}

customElements.define("my-element", MyElement);
customElements.define("my-sweet-element", SweetElement);

so if this ...super.properties is supposed to work (for us it does not - while it does in simple examples) then we could consider it a bug and I can look into making a reduced example on how ...super.properties breaks our code...

PS: we did not raise an issue before as we assumed ...super.properties is not supposed to work 🙈

...super.properties is not the intended use of the static properties declaration. Whether or not it works, I can't say because we don't test it. I don't particularly think we should make it work, but it goes against the design of the properties declaration, which is supposed to stay close to class fields.

What error happens if you remove the spread? I can't edit the webcomponents.dev example.

I think we need a separate issue for this.

you can click on "fork" (top right) and then you can edit

I saved a fork where it's removed
https://webcomponents.dev/edit/kMzS6JOhPAPsEHtV4zK5

and I get this
Screenshot 2020-10-29 at 20 17 27

so unless there is an easy answer on how to solve this then yes a dedicated issue makes sense 🤗

another topic

when writing mixins for LitElement it is somewhat confusing how to merge styles.

I would assume a simple array merge should work

static get styles() {
  return [
    ...super.styles,
    css` // my styles `
  ]
}

it however fails if the element the mixin is applied to is doing

static get styles() {
  return css` // parent styles `;
}

I started writing merges like this 😭

    static get styles() {
      let parentStyles = [];
      if (Array.isArray(super.styles)) {
         parentStyles = super.styles;
      } else if (super.styles) {
        parentStyles = [super.styles];
      }
      return [
        ...parentStyles,
        css`

PS: I know that you could use super.styles ? super.styles : [], because of some flattening magic of LitElement... however I could not remember this magic and honestly a "no magic" array merge seems just simpler... as it won't need to be teached

Suggestion

LitElement should always require an array for static get styles.

Let's move the static properties discussion here: https://github.com/Polymer/lit-html/issues/1413. As noted in that issue, you can use the permissive PropertyDeclarations type to avoid the issue, but it's also not clear why spreading superclass properties wouldn't work here.

@daKmoR We've addressed the static styles type issue by making css return a CSSResultGroup which is a single result or an array. This should address the core issue without incurring the migration pain of changing the type. Let's process any feedback on that approach in a [lit-next] issue.

I'm an Angular developer and I've just started wading into the world of web components for a side project where I really need something much lighter-weight. I'm really enjoying working with LitElement so far, but there are a handful of things I would love to see from a DX perspective.

On the subject of code-sharing primitives: I _really_ miss Angular directives. I know lit-html has something they call directives, but what I'm thinking of is more like something that can decorate an element with additional functionality _at the template_. A good example use case would be something like Angular Material's Menu Trigger directive:

<button [menuTriggerFor]="myMenu">Open Menu</button>
<mat-menu #myMenu>
  <mat-menuitem>Lorem Ipsum</mat-menuitem>
  ...
</mat-menu>

An Angular-style directive is a good fit for this sort of use case because:

  • Not every <button> should be a menu trigger
  • Not every menu trigger need be a <button>
  • It's not just syntactic sugar for a click handler — the ARIA keyboard operability and focus management specs for widgets like this tend to be really complex, so there's a legitimate need to abstract that behavior
  • Mixins could work, but it's a little clumsy needing to define a whole new element class to capture every possible intersection of any given set of common behaviors

On the subject of more granular decorators: this is a relatively minor concern, but I wonder if it would be possible to break up some of the decorators that currently take an "options" object parameter into smaller, composable decorators?

I.e., instead of:

@property({
  type: Boolean,
  reflect: true,
  attribute: 'aria-disabled',
})
disabled: boolean;

I think something like this would make for a nicer syntax in addition to offering more flexibility:

@reflect('disabled', { converter: toggleAttr })
@reflect('aria-disabled', { converter: toString })
@property(Boolean)
disabled: boolean;

I actually just looked up the current property API and realized that's not how that currently works at all. :P The way I'm picturing this working is that you would set the property via the actual property name (?disabled=${foo}}), and the reflect decorators would separately reflect the value to the given attribute name in the DOM via a to-attribute converter.

If you wanted to bind its value via a different attribute name from the outside, and/or specify a custom from-attribute converter, I would probably suggest something like this:

@reflect('disabled', { converter: toggleAttr })
@reflect('aria-disabled', { converter: toString })
@property(Boolean, { alias: 'isDisabled', converter: coerceBoolean })
disabled: boolean;
<!-- Input: -->
<my-element ?isDisabled=${foo}></my-element>

<!-- If foo == true, renders: -->
<my-element disabled aria-disabled="true"></my-element>

<!-- Else, renders: -->
<my-element aria-disabled="false"></my-element>

Thanks for the suggestions @dannymcgee

On the directive front, I do think we'd be able to accomplish what Angular does there with a lit-html directive. A directive on an element can add event handlers, set attributes, accessibility object-model properties, etc.

Currently such a directive would require an attribute binding to be attached to an element, but we're working on "element position" bindings which would make it syntactically more natural. Something like this:

html`
  <button ${menuTrigger('myMenu')}>Open Menu</button>
  <mwc-menu id="myMenu">
    <mwc-menuitem>Lorem Ipsum</mwc-menuitem>
    ...
  </mwc-menu>
`

The menuTrigger directive would need easy access to the host, which we're also planning on adding, so that it could query for the menu element. It would then setup the appropriate click, keyboard listeners, and ARIA roles.

Another approach for associating the elements which I like, would be to use a ref so there's an explicit connection w/o any querying:

const menuRef = new Ref();
html`
  <button ${menuTrigger(menuRef)}>Open Menu</button>
  <mwc-menu ${ref(menuRef)}>
    <mwc-menuitem>Lorem Ipsum</mwc-menuitem>
    ...
  </mwc-menu>
`;

@sorvell @kevinpschaaf this is a great use case we should prototype. @dfreedm would this make sense to add to MWC?

The decorator ideas are interesting, especially being able to reflect to multiple attributes. Our big constraint there is that we have a relatively small breaking change budget for this release and we want it to be mostly a drop-in replacement. We may be able to do something additive.

One issue on the API is that we create an associated attribute for setting a property by default with @property, even though we require an opt-in for reflection. If we moved all attribute-related features to a separate decorator, which would include the type and converter, I could see something like this:

@attribute('disabled', {type: Boolean})
@attribute('aria-disabled', {converter: booleanString})
@property()
disabled: boolean;

The differences here are that we have no type or converter for the property, because the property itself doesn't manage any serialization, only the attributes do. I don't think you need an alias for the property in lit-html, since you can bind to the property directly:

html`<my-element ?isDisabled=${foo}></my-element>`

If you want to set this up from plain HTML, you could set either the disabled or aria-disabled and the reflection could take care of setting the other, though that's a bit weird in terms of expected HTML element behavior, and has a cycle that could be a problem with non-coherent converters.

Again, I don't think we could do this by default for 3.0, but we could aim for more modular decorators in the future. One nice thing about decorators is we can offer different sets of them that operate on the same underlying metadata. We'd probably want to avoid documenting multiple ways of doing things though.

A directive on an element can add event handlers, set attributes, accessibility object-model properties, etc.

Currently such a directive would require an attribute binding to be attached to an element, but we're working on "element position" bindings which would make it syntactically more natural. Something like this:

That's awesome to hear, thanks for the response! I haven't gotten the chance to really play with lit-html directives yet, but the examples I saw mostly revolved around control structures (like a "structural" directive in Angular). The element position binding syntax looks really exciting.

The menuTrigger directive would need easy access to the host, which we're also planning on adding, so that it could query for the menu element.

If a directive is always a function that gets called from within an html tagged template, which (in the context of lit-element) is always returned from a LitElement.render method, would it be possible to just do something like this, or is that not a safe assumption to make?

function menuTriggerFor<T extents HTMLElement>(this: T, menuSelector: string) {
  let menu = this.querySelector(menuSelector);
  ...
}

Though if you're using an ID you wouldn't even need any knowledge of the host element:

function menuTriggerFor(menuId: string) {
  let menu = document.getElementById(menuId);
  ...
}

@dannymcgee Using document.getElementById(menuId); might not work well in all cases since the menu need not be always in the parent dom, rather can be nested within a shadow dom of multiple levels and so, just a plain id selector wont find it. Therefore in my opinion, passing the reference to the object directly (without ID or class) might work better.

Ahh, I forgot about shadow DOM. What a time to be alive.

Maybe lit-element/lit-html ecosystem can provide something like Protractor for Angular?

Motivation: E2E, with Shadow DOM involved, is still a huge pain (unless I'm missing something). Manually querying every shadow root on the path to the target element introduces a lot of friction and is very fragile. Usually we have trees about 5-6 levels deep. Given that, writing E2E tests is on the edge of being practical at all. There are solutions, but they more like ad-hoc hacks than universally approved way of doing things, so this blocks forming of proper reusable infrastructure.

Maybe lit-element/lit-html ecosystem can provide something like Protractor for Angular?

Motivation: E2E, with Shadow DOM involved, is still a huge pain (unless I'm missing something). Manually querying every shadow root on the path to the target element introduces a lot of friction and is very fragile. Usually we have trees about 5-6 levels deep. Given that, writing E2E tests is on the edge of being practical at all. There are solutions, but they more like ad-hoc hacks than universally approved way of doing things, so this blocks forming of proper reusable infrastructure.

Playwright could be a helpful tool for that. No?

Maybe lit-element/lit-html ecosystem can provide something like Protractor for Angular?

Motivation: E2E, with Shadow DOM involved, is still a huge pain (unless I'm missing something). Manually querying every shadow root on the path to the target element introduces a lot of friction and is very fragile. Usually we have trees about 5-6 levels deep. Given that, writing E2E tests is on the edge of being practical at all. There are solutions, but they more like ad-hoc hacks than universally approved way of doing things, so this blocks forming of proper reusable infrastructure.

I'd rather point to https://modern-web.dev/docs/test-runner/overview/

Thanks @motss @maartenst ! Good to know that people are shifting from the horrible ">>/>>>/:deep". There are alternatives beyond playwright, that support this, but it looks like everyone is doing their own thing.

Anyway, given that playwright indeed gains more and more popularity, my comment is not so valid I guess. Too bad we are stuck with Selenium for a couple of years.

Like said in this issue:
https://github.com/Polymer/lit-element/issues/1101

Add support for event handlers for custom events in html code:

<my-component onmycustomevent="(function()(e) => alert(`MyEventValue: ${e.detail.myeventvalue}`))();></my-component>

Currently it is only possible to handle events with. addEventListener() which is inconvenient in frameworks like Blazor or WebForms.

Probably also React and Angular, but here I am missing expertise

@MichaelPeter Custom events are already supported since the beginning. Refer https://lit-element.polymer-project.org/guide/events#custom-events

You can listen to it by just using @yourCustomEvent in your component

@tvvignesh : Thanks for the answer, did not know this was also possible for custom events and outside of components.

A missing feature that I forgot to mention in my last comment: I would love to be able to extend element classes other than the generic HTMLElement. I realize the utility of this is kind of limited since there are so many base elements you can't append a shadow root to, but it would still be great to have the option. It seems like it would be relatively easy to pull off by just turning LitElement/UpdatingElement into mixin functions:

@customElement('my-element')
class MyElement extends LitElement(HTMLTableElement) {
  ...
}
md5-3251f1fba542f395a56b453442a5c48f


If I am not wrong, it should already be possible (https://github.com/Polymer/lit-element/pull/228) but not documented as mentioned in https://github.com/Polymer/lit-element/issues/829 and you can probably also have a look at how @openwc does this here: https://open-wc.org/docs/development/dedupe-mixin/

Awesome, thanks for the info @tvvignesh!

829 is about writing mixins that extend LitElement, not making LitElement itself a mixin. We're not very interested in pursuing making LitElement a mixin since Safari/Webkit will not implement customized built-ins. We don't want to encourage making elements that require polyfills forever for a significant number of users.

...Safari/Webkit will not implement customized built-ins. We don't want to encourage making elements that require polyfills forever for a significant number of users.

Damn, I didn't realize that; thanks for the info. Totally understandable.

Bind properties to pseudo class using javascript

Was this page helpful?
0 / 5 - 0 ratings