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

[Invokers] Expose API for default invokeaction handlers #1020

Open
shgysk8zer0 opened this issue Mar 26, 2024 · 1 comment
Open

[Invokers] Expose API for default invokeaction handlers #1020

shgysk8zer0 opened this issue Mar 26, 2024 · 1 comment

Comments

@shgysk8zer0
Copy link

Addressing the need for polyfills, including support for any future elements or actions, we'll need adequate means of progressive enhancement for this. Namely, I think elements will need methods for listing actions/checking for a handled action, as well as to register new handlers for actions.

I'm currently experimenting with the API in Firefox by toggling dom.element.invokers.enabled, and I'm finding that, while it does dispatch invoke events, it does not currently provide any default handling for various actions. And I find myself lacking the means of detecting if some given action for an element has a default handler or if I need to manually handle it. This is obviously just a problem of an incomplete implementation in this case, but I can see the same issue arising whenever the defaults are expanded upon.

So, I propose the following methods:

  • HTMLElement.hasInvokeAction(action) -> Boolean
  • HTMLElement.addInvokeAction(action, callback) -> Boolean (if there was previously and invoke action handler)
  • HTMLElement.getAllInvokeActions() -> Array or Set (not strictly necessary)
  • HTMLElement.getInvokeAction(action) -> Function (not necessary, but helpful for polyfills)

I'm not set on the method names, and only hasInvokeAction() and addInvokeAction() are strictly necessary. But I am thinking these would be best as static methods, and where eg HTMLDialogElement overrides the methods on HTMLElement. At least the hasInvokeAction() and addInvokeAction() would need to be static since they apply to all such elements rather than a particular instance.

Alternatively, this could be exposed via a map-like static actions property and Element.actions.has(action) and Element.actions.set(action, handler).

Example Usage:

if (! HTMLDialogElement.hasInvokeAction('showModal')) {
  HTMLDialogElement.addInvokeAction('showModal', (invokeEvent) => {
    if (! invokeEvent.target.open) {
      invokeEvent.target.showModal();
    }
  });
}

if (! HTMLFooElement.hasInvokeAction('bar')) {
  // Allow for adding new default invoke handlers on new tags and actions
  HTMLFooElement.addInvokeAction('bar', function({ target, action, invoker {) {
   HTMLBarElement.getInvokeAction('bar').call(null, { target,  action, invoker });
  });
}

Example of getInvokeAction() in Polyfill:

someEl.addEventListener('invoke', event => {
  // Static methods do make it a bit clunky to add invoke listeners
  // But it does provide a convenient way of using added action handlers

  if (event.target.constructor.hasInvokeAction(event.action)) {
    event.target.constructor.getInvokeAction(event.action).call(null, event);
  }
});
@keithamus
Copy link
Collaborator

keithamus commented Mar 26, 2024

Thanks for the issue @shgysk8zer0! This has been discussed before (I can't find the issue now) /cc @lukewarlow.

I'm not fully convinced we need new APIs for this. Aside from the polyfill case I struggle to find a good justification for adding what is quite a large API surface area.

It's possible to feature detect today by relying on the fact that invoke events are dispatched synchronously after a click. Waiting one microtask allows you to re-dispatch after the tick. This does come with the caveat that you're introducing a new microtask but it's possible to remove the observability of that, but I'm also not sure it's all that material. Anyway, an example of polyfilling just Dialog showModal:

let supports = false;
function setSupports() { supports = true }
document.addEventListener('click', e => {
  const invoker = e.target.closest('button[invoketarget][invokeaction=showmodal]');
  if (invoker && !supports) {
    const dialog = invoker.invokeTargetElement;
    dialog.addEventListener('invoke', setSupports);
    Promise.resolve().then(() => {
      dialog.removeEventListener('invoke', setSupports);
      if (!supports) {
        const continue = dialog.dispatchEvent(new InvokeEvent('invoke', { action: 'showmodal', invoker }));
        if (continue) dialog.showModal()
      }
    });
  }
}, true);

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

2 participants