open-ui
open-ui copied to clipboard
[Invokers] Expose API for default `invokeaction` handlers
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)-> BooleanHTMLElement.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);
}
});
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);
There hasn't been any discussion on this issue for a while, so we're marking it as stale. If you choose to kick off the discussion again, we'll remove the 'stale' label.
I'm going to go ahead and close this based on recent discussions in another issue.
TLDR you can feature detect if a browser is aware of a certain named command using the command idl reflection (if something is completely invalid it will be an empty string).
Beyond that it's unclear the exact shape this API would need to take and whether it warrants the added complexity.
If you feel strongly about the need for this it's worth potentially raising with the whatwg after the initial landing of command invokers.