WebReflection / regular-elements

Custom Elements made available for any node, and through CSS selectors
https://webreflection.github.io/regular-elements/test/
ISC License
90 stars 3 forks source link

Call ondisconnected() when element no longer matches selector #18

Closed basham closed 5 years ago

basham commented 5 years ago

ondisconnected() is called when the element is removed from the DOM, but not when the element no longer matches the selector provided in define(). For example, an element attribute could mutate, "invalidating" the element compared to its respective selector. But as is, it means that the same element gets booted up twice via two definitions, but the first initialization is never shut down (via ondisconnected()) until the element is removed from the DOM.

wickedElements.define('[is="foo"]', ...)
wickedElements.define('[is="bar"]', ...)

html`<div is=${isFoo ? 'foo' : 'bar'} />`

Maybe regularElements or wickedElements needs some other lifecycle method, if ondisconnected() is an inappropriate place for this behavior. But it feels as if there needs to be some way to undo initializing an element with the same rules for why it was initialized in the first place.

I haven't tested it, but it would be good as a comparison to know how Custom Elements handles this, for both the cases of a new tag or tag extension.

For reference, this is how when-elements behaves.

WebReflection commented 5 years ago

Dom nodes cannot change tag name, neither is attribute, once upgraded, so I'm not sure it's a valid example.

Both connected and disconnected work exactly like custom elements, so it looks like you want an attribute changed in the middle, but you're after a "destructor", which is weird cause that also means "init" might be invoked twice for the same element, and the same definition.

Accordingly, disconnected is not the right place to destructor, but also destructor is usually considered a footgun.

I'll think about this, but maybe it's ok if when-element has a different meaning/outcome, and wicked elements sticks with the platform verbs and concepts

WebReflection commented 5 years ago

OK, after going through the code, I've decided to keep the project as is, symmetric with CustomElementsRegistry where, once you define a selector behavior, the node will follow all the primitives, no matter what.

I think when-element has different semantics and goals, so I'm OK in it having its own way of doing things, here I'll just try to keep consistency and symmetry with documented standard.

I hope that's OK, so that users will have choices 👋

basham commented 5 years ago

Dom nodes cannot change tag name, neither is attribute, once upgraded, so I'm not sure it's a valid example.

Perhaps the example I gave wasn't the best, as it relied on the is attribute. This new example is quite contrived, but imagine something like this. It upgrades the same element in two different ways. One, it makes the button behave like a disclosure. Two, it adds a custom tooltip to the button.

wickedElements.define('[aria-expanded]',
  // Clicking the element will
  // toggle the aria-expanded attribute and
  // toggle the visibility of the element referenced by data-expands.
)
wickedElements.define('[data-tooltip]',
  // Reveal a custom tooltip upon hover or focus.
  // Remove the tooltip if the element no longer has the attribute.
)

html`
  <button
    aria-expanded="false"
    data-expands="answer-1"
    data-tooltip="Ask me the questions, Bridgekeeper. I am not afraid.">
    What is your favorite color?
  </button>
  <div id="answer-1">
    Sir Galahad: Blue. No yellOOOOOOW!
  </div>
`

Both connected and disconnected work exactly like custom elements, so it looks like you want an attribute changed in the middle, but you're after a "destructor", which is weird cause that also means "init" might be invoked twice for the same element, and the same definition.

Yes, I could see it being odd for the case of wickedElements, if the presence of [data-tooltip] triggered init(), then the attribute was removed and added again, causing init() to be triggered again. Some different semantics would be needed for that to feel okay.

Nevertheless, if the primary purpose of onconnected(), init(), or whenAdded() is to upgrade an element, I'm struggling with finding compelling reasons to support a method for downgrading the element (by knowing when the element no longer matches the original selector and somehow undoing its setup). It's far easier to just destroy the element, do some clean up, and replace it with something new. And as you suggest, downgrading may just lead to the developer shooting themselves in the foot. I will likely remove this feature from when-elements, and that will happily simplify the whenRemoved() API. However, I could also see allowing the developer to register a custom when*-like callback, so they could integrate their own hooks into the system as needed. This expansion-ability is hinted at in the backlog.

Yes, I understand wanting to keep in symmetry with CustomElementsRegistry, and when-elements is purposely different from that that API. I'm glad we could clarify the goals of our respective projects.