html icon indicating copy to clipboard operation
html copied to clipboard

Custom Elements definition/styling challenges/use cases

Open bkardell opened this issue 4 years ago • 6 comments

Overview: Custom elements introduce what is effectively an "indeterminite" state about whether an element has a definition, which is resolved asynchronously... maybe. CSS currently doesn't offer expressive power beyond 2 boolean states via the :defined pseudo class. This is problematic because there is no good way for authors to create components which are both resilient to failure and provide a good user experience. This is inadequate because elements are by default not defined and we have no idea if or when they will even try to be defined, or, much worse, that such a thing failed.

Discussing with several developers, what we would like is the ability to declaratively handle this appropriately such that:

  • if script is disabled, authors can style something
  • if script is enabled, authors can style this in order to hide something visually/reserve space
  • if a known attempt to define fails, authors can style this and not just leave a hidden space
  • if a certain amount of time passes and no attempt to define is even made, authors can style style this and not just leave a hidden space

@jonathanneal made this handy diagram which attempts to visualize the states/desires, if that is easier to understand

Unmet, but basic Custom Elements case: Failures and delays described

A common example of this is that several elements I have seen are developed pretty ideally: Using PE, composition over inheritance (doesn't violate LSP) and so on, and then use :not(:defined) {visibility: hidden;} to prevent a FOUC, and the result is that all of that resilence is out the window. If scrtipt is disabled, their fallback content isn't shown. If a netowrk call fails and their element is not defined, no content is ever shown, etc.

I would like to suggest that there are several use case/wants here and that we lack the powers to handle them to give authors the ability to create things with a good user experience.

Examples

"Failure-insensitive"

Jon would like to include an element in his page:

<next-article>  
  <a href-"..."></a>
<next-article>

Jon's component thought about PE. In an ideal world, this component will be defined and complete work before the author sees it, and it will load the content of the next page in the background and make it available to the user fast and through some pleasant experience. This helps him paint the main thing the user wants on the screen fast, but also give them more content quickly, or maybe even control when that initiaties work to update the page automatically via IntersectionObserver, or something. Jon would prefer that the page have a moment to load before showing, so that someone doesn't click the link while it's still upgrading or see it change in most cases -- however, it's not critical. What matters most to Jon though is that his users not have to wait too long for content.

What he wants is a model more like fonts: Hide it for some time, but it's ok to give up and let it be shown because he thinks that is the right tradeoff for his user.

Jon would also like to be able to check whether he's hit this limit during definition so that he can decide to not upgrade if he chooses (again, like fonts)

"Fail-sensitive"

Lajja would like to write a <my-calendar> component which uses the users' login to locate their calendar and present information in the page about today's events. They have no fallback, but assume that the calendar will load and put a loading indicator inside. Frank is a user of Lajja's service who commutes on a bus and loses connectivity, or has very slow connections sometimes. Frank doesn't want to be told it's loading if it isn't because the network failed to load the definition in the first place. Frank is also happy to wait for content in these situations as long as it is still trying, but if the network drops and the element will never be defined, he would love to know that! Lajja's has another user, Hanna who likes to disable JavaScript by default and turns it on selectively if she really wants that content (her browser makes this easy). Hana is used to a lesser experience, and she's fine with that - but she doesn't like to not know there is content she could get, because it doesn't show up, nor to be lied to that something is loading forever when it isn't either. What can Lajja do to make her users happy?

What Lajja wants to do to make her users happy is to be able to know when we know that there has been an attempt to define, and that it failed, for whatever reason. This would let her show Hanna some notification when she has JavaScript disabled, it would let Frank know when we actually failed, but let him know we're trying.

Note that this case also applies to some of Lajja's "simpler" PE-like cases like trying to decorate a stateful interactive element simply to pretty it up, like

<x-slider>
 <input type="range">
</x-slider>

While this is potentially non-critical, as long as there is an attempt underway to define it, it is arguably better to not show the fallback content... Not only because there is FOUC, but because this creates potentially complex situations if the user begins interacting with it. If such an attempt fails, however, we'd like to show the still functional component.

What's missing

What authors could really use is a simple declarative way to style these use cases

  • There is no script support, I can style that case
  • There is script support, but it is not known about yet, I can style that case
  • There is a script that is attempting to load a definition for this
  • An error occurred while trying to load the definition, so it is unknown and wont be known, and I can style that failure case
  • Yay, it is defined.

But in the platform today, there's no way to map that out easily without developing your own infrastructure to map the definitions to scripts and manage special attributes or classses representing those things and twiddling a value on root or something.

There are many, many ways to solve this but an easy way to think about it is that what you'd like is the ability to express that some script intends to provide definitions for some elements, and for this to make information available via CSS to respond when that happens to not be the ccase.

In previous employment and projects since 2014, in cases where I had control over more of the base, I developed a few patterns to address this, but I'm more interested in agreement that there is a problem than suggesting any specific solutions.

bkardell avatar Dec 16 '20 19:12 bkardell

Here’s the chart that Brian mentioned. It walks the web component lifecycle that Brian describes, only with some imaginary pseudo-classes thrown in to describe the states we can’t target.

Chart describing the lifecycle of a custom element

I would express the chart like this:

  1. The document encounters <host-element>.
    1. Now, is host-element defined?
      1. Yes? Then host-element is :defined.
      2. No? Then host-element is :not(:defined).
    2. Three seconds later, is host-element defined?
      1. Yes? Then host-element is :defined.
      2. No? Then host-element is :not(:defined).
    3. This raises the following questions:
      1. How would we style an element that we intend to define?
        1. Maybe something like :will-define?
      2. How would we style an element that we intended to define and then at some step did become defined?
        1. Maybe something like :defined:not(:will-define)?
      3. How would we style an element that we intended to define and then at some step failed to become defined?
        1. Maybe something like :not(:defined, :will-define)?

jonathantneal avatar Dec 16 '20 19:12 jonathantneal

I think the use cases presented here are valid.

An alternative to :will-define is to add :upgrade-failed. If we're going with :will-define route, we should probably call it something like :awaits-definition since we never know if a given custom element will ever be defined or not.

rniwa avatar Dec 19 '20 00:12 rniwa

Thanks for filing this @bkardell and @jonathantneal - I don't want to bikeshed the name as I agree with @rniwa that it's a valid usecase that we should solve. One thing that I would like to point out and am curious on people's thoughts, even if we do have something like :will-define do we really want this type of approach to your event lifecycle states:

How would we style an element that we intended to define and then at some step did become defined? Maybe something like :defined:not(:will-define)? How would we style an element that we intended to define and then at some step failed to become defined? Maybe something like :not(:defined, :will-define)?

Goodness, that is a good question and I really think it may shine a light on the need for more pseudo classes as reading those selectors is painful, at least for me. I think this may be @rniwa point regarding :upgrade-failed as each one of those states are valid and I'd prefer a straight forward state.

gregwhitworth avatar Apr 21 '21 04:04 gregwhitworth

Yeah, fwiw, this was my original idea as well, but I tried to stay away from any actual proposals about solutions and stick to the use cases. I believe @jonathantneal suggested these names based on a parallel with fonts. I guess I can let him speak for himself, but I belive that what he was trying to articulate was that perhaps there could be a desire that was "more like fonts" in the sense that maybe you want to say "If I haven't defined it by this time, I'd like to show the user some fallback, even if it would be defined later". I'm not sure though.

bkardell avatar Apr 22 '21 16:04 bkardell

To confirm — Yes, you have correctly articulated my intentions, @bkardell. It was easier for me to describe my needs as pseudo-classes, because pseudo-classes were the tool I was given to work with. Similarly, you are correct that I have reasoned about custom element states as I have reasons about custom font states.

I appreciate that you’ve focused on the identifying on and agreeing on the issues. 😄

jonathantneal avatar Apr 22 '21 17:04 jonathantneal

So, just to clarify on here - we'd like this to have some kind of timeout idea like fonts (easier, probably) or we'd like to somehow track that a script intends to define so that we know it failed (harder)

bkardell avatar Jul 25 '22 19:07 bkardell

Seems related https://github.com/WICG/webcomponents/issues/782

bkardell avatar Apr 20 '23 17:04 bkardell