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

Simplified custom element definition #1064

Open
dead-claudia opened this issue Jul 30, 2024 · 9 comments
Open

Simplified custom element definition #1064

dead-claudia opened this issue Jul 30, 2024 · 9 comments

Comments

@dead-claudia
Copy link

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:

  • name: the tag name
  • extends: the builtin it extends (if applicable)
  • attributes: the observed attributes
  • formassociated: whether this is form-associated (boolean)

Its children consist of zero or more of the following:

  • A <template>. If missing, it defaults to what's in its shadow root, replicating the shadow root options in its instances. This is cloned per-instance and used as the instances' children.
  • A <style> or <link rel="stylesheet">. This is adopted into the shadow root without cloning.
  • A <script type="module">. This has a single default export that initializes the instance state, receiving a shadow root and a signal (aborted on any reset). The shadow root would also receive all custom element lifecycle updates as events.

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
Copy link

Related WIP HTML Modules and Declarative Custom Elements Proposal Idea:

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

@justinfagnani
Copy link
Contributor

<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
Copy link
Collaborator

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
Copy link

MrHBS commented Aug 10, 2024

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

@EisenbergEffect
Copy link

@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
Copy link
Author

dead-claudia commented Sep 2, 2024

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

This comment was marked as off-topic.

@Westbrook

This comment was marked as off-topic.

@dead-claudia

This comment was marked as off-topic.

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

5 participants