Skip to content

Commit

Permalink
Switch to using dashes in attribute values, remove mixin from Input (#…
Browse files Browse the repository at this point in the history
…1074)

This updates the Invokers explainer to match the current conclusions
based on WHATWG discussions.
  • Loading branch information
keithamus authored Jul 25, 2024
1 parent a02209a commit 156ff3f
Showing 1 changed file with 67 additions and 65 deletions.
132 changes: 67 additions & 65 deletions site/src/pages/components/invokers.explainer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,20 @@ layout: ../../layouts/ComponentLayout.astro
## Pitch in Code

```html
<button command="showModal" commandfor="my-dialog">This opens a dialog</button>
<button command="show-modal" commandfor="my-dialog">This opens a dialog</button>

<dialog id="my-dialog">This is the dialog</dialog>
```


## Introduction

Adding `commandfor` and `command` attributes to `<button>` and
`<input type="button">` / `<input type="reset">` elements would allow authors to
assign behaviour to buttons in a more accessible and declarative way, while
reducing bugs and simplifying the amount of JavaScript pages are required to
ship for interactivity. Buttons with `command` will - when clicked, touched, or
enacted via keypress - dispatch a `CommandEvent` on the element referenced by
`commandfor`, with some default behaviours.
Adding `commandfor` and `command` attributes to `<button>` and elements would
allow authors to assign behaviour to buttons in a more accessible and
declarative way, while reducing bugs and simplifying the amount of JavaScript
pages are required to ship for interactivity. Buttons with `command` will -
when clicked, touched, or enacted via keypress - dispatch a `CommandEvent`
on the element referenced by `commandfor`, with some default behaviours.

## Background

Expand Down Expand Up @@ -90,17 +89,15 @@ the balance.
## Proposed Plan

In the style of `popovertarget`, this document proposes we add
`commandForElement`, and `command` as available attributes to `<button>`,
`<input type="button">` and `<input type="reset">` elements.
`commandForElement`, and `command` as available attributes to `<button>`.

```webidl
interface mixin InvokerElement {
interface mixin CommandElement {
[CEReactions] attribute Element? commandForElement;
[CEReactions] attribute DOMString command;
};
HTMLButtonElement extends InvokerElement
HTMLInputElement extends InvokerElement
```

The `commandfor` value should be an IDREF pointing to an element within the
Expand All @@ -110,41 +107,42 @@ some cases, see
[the popovertarget attr-asociated element steps](https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-element)
for more).

The `command` (and the `.command` reflected property) is a freeform hint to the
invoker target. Command can be a "built-in" action or a "custom" action. If
`command` is not supplied then it is considered invalid and no action will take
place, and no event will be fired.
The `command` attribute (and the `.command` reflected property) is a freeform
hint to the invoker target. Command can be a "built-in" action or a "custom"
action. If `command` is not supplied then it is considered invalid and no
action will take place, and no event will be fired.

Custom command values _must_ contain a `-`. They will _never_ trigger a browser
default behaviour, aside from dispatching an `CommandEvent`. This allows web
developers to create custom Invoke actions for their components.
Custom command values _must_ start with a `-`. This is a safe namespace to use,
and browsers guarantee to _never_ have built-in values starting with a `-`.
The only browser behaviour for those values will be dispatching a `CommandEvent`
on the invokee. This allows web developers to create custom Invoke actions for
their components.

Built-in interactive elements have built-in behaviours (detailed below) which
are determined by the `command` attribue/property. The built-in names _must not_
contain a `-`. A `command` without a dash that is not a built-in is considered
invalid, and will not dispatch an CommandEvent.
start with a `-`.

Valid commands (that is: custom commands or a valid built-in) will
dispatch CommandEvents, allowing custom code to take control of invocations (for
example calling `.preventDefault()` or executing custom side-effects).

Invokers will dispatch an `CommandEvent` on the _Invokee_ (the element referenced
by `commandfor`) when the element is _Invoked_. The `CommandEvent`'s `type` is
always `command`. The event also contains an `invoker` property that will reference
the _Invoker_ element. `CommandEvents` are always non-bubbling, composed, trusted,
cancellable events.
always `command`. The event also contains an `source` property that will reference
button that was enacted causing the `CommandEvent` to fire. The dispatched
`CommandEvents` are always non-bubbling, composed, trusted, cancellable events.

```webidl
[Exposed=Window]
interface CommandEvent : Event {
constructor(CommandEventInit invokeEventInit);
readonly attribute Element invoker;
readonly attribute Element source;
readonly attribute DOMString type = "command";
readonly attribute DOMString command;
};
dictionary CommandEventInit : EventInit {
DOMString action = "";
Element invoker;
Element source;
};
```

Expand All @@ -153,10 +151,12 @@ attributes set, then `popovertarget` _must_ be ignored: `command` takes
precedence.

If a `<button>` is a form participant, or has `type=submit`, then `command`
_must_ be ignored.
_must_ be ignored. It also _must_ not submit the form it is attached to.
Buttons like this are "author errors", and browsers may issue warnings in the
developer console explaining to the developer why this button doesn't work.

If an `<input>` is a form participant, or has a `type` other than `reset` or
`button`, then `command` _must_ be ignored.
This means a `<button>` with `command`/`commandfor` that is inside a form
_must_ also have `type=button`.

### Example Code

Expand All @@ -178,7 +178,7 @@ When pointing to a `<dialog>`, `command` can toggle a `<dialog>`'s
openness.

```html
<button commandfor="my-dialog" command="showModal">Open Dialog</button>
<button commandfor="my-dialog" command="show-modal">Open Dialog</button>

<dialog id="my-dialog">
Hello world!
Expand Down Expand Up @@ -287,41 +287,43 @@ element types are handled.

When the `command` attribute is missing it will default to an invalid state.

| Invokee Element | `action` hint | Behaviour |
| :-------------- | :-------------------- | :----------------------------------------------------------------------------------- |
| `<* popover>` | `'togglePopover'` | Call `.togglePopover()` on the invokee |
| `<* popover>` | `'hidePopover'` | Call `.hidePopover()` on the invokee |
| `<* popover>` | `'showPopover'` | Call `.showPopover()` on the invokee |
| `<dialog>` | `'showModal'` | If the `<dialog>` is not `open`, call `showModal()` |
| `<dialog>` | `'close'` | If the `<dialog>` is `open`, close and use the button `value` for returnValue |

Further behaviours have been designed and may ship after the initial release of
Invokers, the names and exact semantics may be subject to change:

| Invokee Element | `action` hint | Behaviour |
| :-------------- | :-------------------- | :----------------------------------------------------------------------------------- |
| `<details>` | `'toggle'` | If the `<details>` is `open`, then close it, otherwise open it |
| `<details>` | `'open'` | If the `<details>` is not `open`, then open it |
| `<details>` | `'close'` | If the `<details>` is `open`, then close it |
| `<dialog>` | `'toggle'` | If the `<dialog>` is `open`, then close it and use the button `value` for returnValue, otherwise call `showModal()` |
| `<dialog>` | `'cancel'` | If the `<dialog>` is `open`, cancel the dialog |
| `<select>` | `'showPicker'` | Call `.showPicker()` on the invokee |
| `<input>` | `'showPicker'` | Call `.showPicker()` on the invokee |
| `<video>` | `'playpause'` | Toggle the `.playing` value |
| `<video>` | `'pause'` | If `.playing` is `true`, set it to `false` |
| `<video>` | `'play'` | If `.playing` is `false`, set it to `true` |
| `<video>` | `'toggleMuted'` | Toggle the `.muted` value |
| `<audio>` | `'playpause'` | Toggle the `.playing` value |
| `<audio>` | `'pause'` | If `.playing` is `true`, set it to `false` |
| `<audio>` | `'play'` | If `.playing` is `false`, set it to `true` |
| `<audio>` | `'toggleMuted'` | Toggle the `.muted` value |
| `<*>` | `'toggleFullscreen'` | If the element is fullscreen, then exit, otherwise request to enter |
| `<*>` | `'requestFullscreen'` | Request the element to enter into 'fullscreen' mode |
| `<*>` | `'exitFullscreen'` | Request the element to exit 'fullscreen' mode |
| `<input type=number>` | `'stepUp'` | Call `.stepUp()` on the invokee |
| `<input type=number>` | `'stepDown'` | Call `.stepDown()` on the invokee |

> Note: The above tables are an attempt at wide coverage, but ideas are welcome. Please submit a PR if you have one!
| Invokee Element | `action` hint | Behaviour |
| :-------------- | :-------------------- | :--------------------------------------------------------------------------------------------------------- |
| `<* popover>` | `'toggle-popover'` | Shows the `popover` if closed, otherwise hides. Similar to `.togglePopover()` |
| `<* popover>` | `'hide-popover'` | Hides the `popover` if open, otherwise does nothing. Similar to `.hidePopover()` |
| `<* popover>` | `'show-popover'` | Shows the `popover` if closes, otherwise does nothing. Similar to `.showPopover()` |
| `<dialog>` | `'show-modal'` | If the `<dialog>` is not `open`, shows it as modal. Similar to `.showModal()` |
| `<dialog>` | `'close'` | If the `<dialog>` is `open`, close and use the button `value` for returnValue. Similar to `.close(value) |

Further behaviours have been proposed, but need further design on exactly how
they will behave based on implications such as accessibility, security,
interactivity, and how the button may need to respond to such actions.

| Invokee Element | `action` hint | Behaviour |
| :-------------------- | :--------------------- | :------------------------------------------------------------------------------------------------------------- |
| `<details>` | `'toggle'` | If the `<details>` is `open`, then close it, otherwise open it |
| `<details>` | `'open'` | If the `<details>` is not `open`, then open it |
| `<details>` | `'close'` | If the `<details>` is `open`, then close it |
| `<dialog>` | `'toggle'` | If the `<dialog>` is `open`, then close it and use the button `value` for returnValue, otherwise open as modal |
| `<dialog>` | `'cancel'` | If the `<dialog>` is `open`, cancel the dialog |
| `<select>` | `'show-picker'` | Shows the native picker. Similar to `.showPicker()` on the invokee |
| `<input>` | `'show-picker'` | Shows the native picker. Similar to `.showPicker()` on the invokee |
| `<video>` | `'play-pause'` | If the video is not playing, plays the video. Otherwise pauses it. Similar to `el.playing = !el.playing` |
| `<video>` | `'pause'` | If the video is playing, pause the video. Similar `.playing = false` |
| `<video>` | `'play'` | If the video is not playing, play the video. Similar to `.playing = true` |
| `<video>` | `'toggle-muted'` | If the video is muted, it unmutes the video, otherwise it mutes it. Similar to `el.muted = !el.muted` |
| `<audio>` | `'play-pause'` | If the audio is not playing, plays the audio. Otherwise pauses it. Similar to `el.playing = !el.playing` |
| `<audio>` | `'pause'` | If the audio is playing, pause the audio. Similar `.playing = false` |
| `<audio>` | `'play'` | If the audio is not playing, play the audio. Similar to `.playing = true` |
| `<audio>` | `'toggle-muted'` | If the audio is muted, it unmutes the audio, otherwise it mutes it. Similar to `el.muted = !el.muted` |
| `<*>` | `'toggle-fullscreen'` | If the element is fullscreen, then exit, otherwise request to enter |
| `<*>` | `'request-fullscreen'` | Request the element to enter into 'fullscreen' mode |
| `<*>` | `'exit-fullscreen'` | If the element is fullscreen, then exit |
| `<input type=number>` | `'step-up'` | Call `.stepUp()` on the invokee |
| `<input type=number>` | `'step-down'` | Call `.stepDown()` on the invokee |

> Note: The above tables are an attempt at wide coverage, but ideas are welcome.
> Please submit a PR if you have one!
Further to the initial ship we're also exploring implicit `command` or implicit
`commandfor` values where the value can easily be inferred.
Expand Down

0 comments on commit 156ff3f

Please sign in to comment.