Interest Invokers (Explainer)
- Authors: Keith Cirkel
Interest Invokers
Pitch in Code
<button interesttarget="my-tooltip">Hover/Focus to show the tooltip</button>
<span popover=hint id="my-toolip">This is the tooltip</span>
Introduction
Following invokers, adding an interesttarget
and interestaction
attributes to interactive elements: starting with <button>
, <input type="button">
/<input type="reset">
/<input type="submit">
, <a>
, and <area>
(perhaps expanding to <input>
, <textarea>
, <select>
, <summary>
or maybe more, see #14). This would allow disclosure of high fidelity tooltips in a more accessible and declarative way. Elements with interesttarget
will - when hovered, or focussed (or an equivalent user-agent determined action) - dispatch an InterestEvent
on the element referenced by interesttarget
, with some default behaviours.
Terms
- Interest/Shows Interest: The action of Interest refers to the user “landing” on an element without invoking it, using a Human Input Device (HID). See below for how various HIDs can show interest.
- Loses Interest/Lost Interest: The action of Loses Interest refers to the user “moving away” from an element, or its interestee, using a HID; in other words interest must be on a different element that is neither. Elements can only Lose Interest if they are in the state of Showing Interest. See below for how various HIDs can lose interest.
- Interestee: An element which is referenced to by an Interest element, via the
interesttarget
attribute.
HIDs and interest
Interest is shown and lost based on a user gesture, which will be dependent on the users Human Input Device (HID). A keyboard will use focus to determine interest equivalent to the focusin
/focusout
events available today. A pointer device, such as a mouse, will use the pointerenter
/pointerleave
.
“Interest” is an intentional abstraction from traditional keyboard and pointer events to allow other HIDs to map an appropriate (privacy preserving) user gesture, without emulating or otherwise conflicting with keyboard or pointer events. For example a user agent with a touch screen HID might map showing interest to a menu item in the devices long-press context-menu, while a device with hand tracking (such as mixed reality headsets) might offer an explicit hand gesture.
While the proposal aims to encompass alternate HIDs and decvices, the specifics precisely how these HIDs will show Interest is not part of the initial proposal, and is an area of active discussion. It also stands to reason that to-be-invented devices may introduce new and novel concepts of showing/losing interest.
Proposed Plan
In the style of invoketarget
, this document proposes we add interesttarget
and interestaction
as available attributes to <button>
, <a>
, <area>
and <input>
elements.
interface mixin InterestInvokerElement {
[CEReactions] attribute Element? interestTargetElement;
[CEReactions] attribute DOMString interestAction;
};
HTMLButtonElement includes InterestInvokerElement
HTMLInputElement includes InterestInvokerElement
HTMLAnchorElement includes InterestInvokerElement
HTMLAreaElement includes InterestInvokerElement
SVGAElement includes InterestInvokerElement
The interesttarget
value should be an IDREF pointing to an element within the document. .interestTargetElement
also exists on the element to imperatively assign a node to be the invoker target, allowing for cross-root invokers (in some cases, see attr-asociated element steps for more).
The interestaction
(and the interestAction
reflected property) is a freeform hint to the interestee. InterestAction can be a “built-in” action or a “custom” action. If interestaction
is a false value (''``,
null, etc), then it will default to an
auto` state.
Built-in interactive elements have built-in behaviours (detailed below) which are determined by the interestaction
. The built-in names must not contain a -
. An interestaction
without a dash that is not a built-in is considered invalid, and will not dispatch an InterestEvent.
Valid interestactions (that is: custom interestaction or a valid built-in) will dispatch InterestEvent, allowing custom code to take control of interest invocations (for example calling .preventDefault()
or executing custom side-effects).
Elements with interesttarget
set will dispatch an InterestEvent
on the Interestee (the element referenced by interesttarget
) when the element Shows Interest or Loses Interest. When the element Shows Interest the event type will be 'interest'
. If the element has Shown Interest, and interest moves away from both the Interest Element and the Interestee, then the element Loses Interest and an InterestEvent
with the type of 'loseinterest'
will be dispatched on the Interestee. The event also contains a invoker
property that will reference the Interest element. InterestEvent
objects are always non-bubbling, composed, cancellable events.
[Exposed=Window]
interface InterestEvent : Event {
constructor(DOMString type, InterestEventInit interestEventInit);
readonly attribute Element invoker;
readonly attribute DOMString type = "interest";
readonly attribute DOMString action;
};
dictionary InterestEventInit : EventInit {
DOMString action = "";
Element invoker;
};
Both interesttarget
and invoketarget
can exist on the same element at the same time, and both should be respected.
If a <button>
is a form participant, or has type=submit
, then invoketarget
must be ignored. interesttarget
is still valid in these scenarios.
If an <input>
is a form participant, or has a type
other than reset
or button
, then invoketarget
and interesttarget
must be ignored. interesttarget
additionally works with type
of submit
.
Example Code
Popovers
An interesttarget
allows for tooltips using popover
:
<button interesttarget="my-popover">Open Popover</button>
<div id="my-popover" popover="hint">Hello world</div>
Custom behaviour
Elements with Interest will dispatch events on the Interestee element, allowing for custom JavaScript to be triggered without having to wire up manual event handlers to the Interest elements.
<button interesttarget="my-custom" interestaction="custom-action">
When this button shows interest, the below div will display.
</button>
<div id="my-custom">Supplementary information</div>
<script>
const custom = document.getElementById("my-custom");
custom.addEventListener("interest", (e) => {
if (e.action !== "custom-action") return;
custom.classList.add('active')
});
custom.addEventListener("loseinterest", (e) => {
if (e.action !== "custom-action") return;
custom.classList.remove('active')
});
</script>
Usability
The events interest
and loseinterest
are intentionally abstract to allow more complex usability concepts to unfold. One such area being explored is the ability to add “safe areas” or “hit triangles”, which allow the user to move the pointer between the Interest Invoker (e.g. the button) and Interestee (e.g. the tooltip). See #963 for more.
Accessibility
Warning: This section is TBD. PRs and discussions welcome.
See the popover=hint
explainer’s accessibility section for additional comments. In particular, many of the relationships that are provided for “regular” invokers such as popovertarget
apply to interest-based invokers also. For example, an aria-details
relationship should be established between an interest invoker and its target.
In particular, for interest-based triggering of popovers, there is a problem with hidden objects. In general, the details relation does not work for hidden objects, and we can’t necessarily expect the popover to be complete before it becomes visible, as that may happen on-demand. So what happens if the user navigates to a popover’s trigger, and it’s not open? If they tabbed there, then we can make sure the popover is shown on focus. But, if they are navigating with a virtual buffer as is likely, how will they trigger the hint popover to open (the user would need to know to focus the trigger, or JAWS/NVDA would need to start focusing objects during navigation as other screen readers do), and then and how will the user know it’s there (should we use a new value of the haspopup object attribute for these, e.g. haspopup=hint
)? See https://issues.chromium.org/issues/377923492 for more discussion.
Defaults
Depending on the target set by interesttarget
, showing interest or losing interest can trigger additional behaviours alongside the event dispatch. Showing/losing interest on an interesttarget will always dispatch a trusted InterestEvent
, but in addition the following table represents how interest on specific element types are handled. Note that this list is ordered and higher rules take precedence:
When the interestaction
attribute is missing it will default to an auto state.
Interestee Element | action hint | Event Type | Behaviour |
---|---|---|---|
<* popover=*> | (auto) | 'interest' | Call .togglePopover() on the invokee |
<* popover=*> | togglePopover | 'interest' | Call .togglePopover() on the invokee |
<* popover=*> | (auto) | 'loseinterest' | Call .togglePopover() on the invokee |
<* popover=*> | togglePopover | 'loseinterest' | Call .togglePopover() on the invokee |
Note: The above table is an attempt at wide coverage, but ideas are welcome. Please submit a PR if you have one!
PAQ (Potentially Asked Questions)
Why the name interest
? Why not hover
or focus
?
Much like click
, hover
or focus
are specific to certain types of HID, and are not terms which encompass all viable methods of interaction. Lots of alternatives were discussed and it was deemed that interest
is the best name to explain the concept of a “hover or focus or equivalent”.
Why is interesttarget
on a lot more elements than invoketarget
?
While invocation should only be limited to buttons, disclosure of supplementary information can be expanded to all interactive elements. There are many useful use cases for offering a tooltip on anchors, such as signalling that they are external, or that they will open in a new window, or to show preview information (think: preview windows on iOS Safari or the hovercards that display on GitHub over a user’s handle).
Why is interesttarget
not unlimited, like title
is?
It could be considered a mistake to allow title
on all elements; as adding interactivity to non-interactive elements creates many problems. Limiting where interesttarget
is allowed aims to create a “pit of success”, guiding developers to use it only on interactive elements, where it makes sense.