WICG / webcomponents

Web Components specifications
Other
4.36k stars 370 forks source link

Simplified custom element definition #1064

Open dead-claudia opened 1 month ago

dead-claudia commented 1 month ago

Currently, classes have to manage a bunch of explicit wiring that has to be done almost every time. This could all be simplified a lot, to a module that doesn't need to do nearly as much.

<define name="my-counter" attributes="initial">
  <template shadowrootmode="open">
    <button data-inc="+1">+</button>
    <span>{{count}}</span>
    <button data-inc="-1">-</button>
  </template>
  <script type="module">
    export default function initialize(shadowRoot, signal) {
      let count = +this.getAttribute("initial") || 0
      let changed = false

      shadowRoot.updateTemplate({count})

      shadowRoot.addEventListener("click", (ev) => {
        const {inc} = ev.target.dataset
        if (inc !== undefined) {
          ev.stopPropagation()
          count += inc
          changed = true
          shadowRoot.updateTemplate({count})
          this.dispatchEvent(new Event("change"))
        }
      }, {signal})

      shadowRoot.addEventListener("attributechanged", (event) => {
        if (!changed) count = +event.newValue || 0
      }, {signal})
    }
  </script>
</define>

The <define> element would accept four attributes:

Its children consist of zero or more of the following:

State reset can be done via elem.reset(), and is done automatically on form reset.

Form controls get elem.form and an implicitly handled for attribute to help simplify everything.

Why not a class? Classes have tons of setup boilerplate. This hides all of that and offloads it all to the browser.

It'd also be nice to see customElements.define extended to likewise support such a simplified definition:

// `this` is an element instance
// `signal` is aborted and the body re-initialized on state and form reset
callback SimpleInitializer = CustomElementLifecycle (ShadowRoot root, AbortSignal signal);

enum SimpleInitializerType {
  "simple",
};

dictionary SimpleInitializerOptions {
  required DOMString name;
  DOMString? extends;
  sequence<DOMString> attributes = [];
  boolean formAssociated = false;
  (HTMLTemplateElement or DocumentFragment or TrustedHTML or DOMString) template;
  required SimpleInitializer initialize;
};

enum FormStateRestoreType {
  "restore",
  "autocomplete",
};

interface AttributeChangedEvent extends Event {
  readonly attribute DOMString attributeName;
  readonly attribute DOMString? oldValue;
  readonly attribute DOMString? newValue;
}

interface FormAssociatedEvent extends Event {
  readonly attribute HTMLFormElement form;
}

interface FormDisabledEvent extends Event {
  readonly attribute boolean disabled;
}

interface FormStateRestoreEvent extends Event {
  readonly attribute FormStateRestoreType restoreType;
}

partial interface CustomElementRegistry {
    void define(SimpleInitializerOptions options);
};
EisenbergEffect commented 1 month ago

Related WIP HTML Modules and Declarative Custom Elements Proposal Idea:

https://gist.github.com/EisenbergEffect/8ec5eaf93283fb5651196e0fdf304555

justinfagnani commented 1 month ago
<define name="my-counter" attributes="initial">
  <template shadowrootmode="open">

One nit, this <template> element shouldn't have a shadowrootmode attribute. We want to add a ShadowRoot to the <define> element there.

Westbrook commented 1 month ago

It's cool to see even more approaches to this! We reviewed a handful of different approaches to "declarative web components" at the Web Components Community Group in the spring. It would be great to get your thoughts on the various discussions available here.

If you're interested, the community chats in Discord and meets on close to a monthly basis at meetings tracked on this calendar. We'd love to have your voice included in the conversation!

MrHBS commented 1 month ago

@EisenbergEffect Very solid proposal Rob! Any chance you can make the compiler public?

EisenbergEffect commented 1 month ago

@MrHBS Thank you! I would like to make it all public. I need to find a way to get funded to work on it though. Maintaining it and managing the community would be a fulltime job. So, I've been hesitant to make it public without a real plan there. If you know of any companies that would be interested, please send them my way.

dead-claudia commented 1 week ago

Okay, I took all my (extremely rough) notes from my phone and translated it into a navigable repo, for my idea of CSS instancing and CSS-based templating: https://github.com/dead-claudia/css-web-components-idea

Apologies in advance, there's zero WebIDL and zero polyfills there. (It'd be an extremely tricky polyfill, anyways.)

dead-claudia commented 1 week ago

@Westbrook Sorry, missed the bus for that Discord link. Could you post an updated one?

Westbrook commented 1 week ago

Try this one: https://discord.gg/CWWnxy3V

dead-claudia commented 1 week ago

@Westbrook Thanks! (You can mark these as off-topic now.)