Skip to content

Commit

Permalink
inputs edits (observablehq#792)
Browse files Browse the repository at this point in the history
* inputs edits

* tweak inline example

* trim inputs overview

* button input

* checkbox edits

* color input

* date input

* file edits

* form edits

* radio edits

* range edits

* search edits

* select edits

* table edits

* text edits

* textarea edits

* toggle edits
  • Loading branch information
mbostock committed Feb 14, 2024
1 parent 1b7e1f2 commit 4f654b4
Show file tree
Hide file tree
Showing 22 changed files with 497 additions and 619 deletions.
6 changes: 6 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ index: false
margin-left: 0.25rem;
}

@media (prefers-color-scheme: light) {
h1 {
--theme-red: #d75c48;
}
}

</style>

<div class="hero">
Expand Down
54 changes: 21 additions & 33 deletions docs/inputs/button.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
# Button input

[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#button)
<a href="https://github.com/observablehq/inputs/blob/main/README.md#button" target="_blank">API</a> · <a href="https://github.com/observablehq/inputs/blob/main/src/button.js" target="_blank">Source</a> · The button input emits an *input* event when you click it. Buttons may be used to trigger the evaluation of cells, say to restart an animation. For example, below is an animation that progressively hides a bar. Clicking the button will restart the animation.

The button input emits an *input* event when you click it. Buttons may be used to trigger the evaluation of cells, say to restart an animation.

For example, below is an animation (using [yield](../javascript/generators)) that progressively hides a bar.

```js echo
import * as Inputs from "npm:@observablehq/inputs";
```
<canvas id="canvas" width="360" height="20" style="max-width: 100%; color: var(--theme-foreground-focus); border: solid 1px var(--theme-foreground);">

```js echo
const width = 360;
const height = 20;
const style = "max-width: 100%; border: solid 1px black;";
const replay = view(Inputs.button("Replay"));
```

```html
<canvas id="canvas" width="${width}" height="${height}" style="${style}">
```
The code block below references <code>replay</code>, so it will run automatically whenever the replay button is clicked.

```js echo
const replay = view(Inputs.button("Replay"));
```js
const canvas = document.querySelector("#canvas");
const context = canvas.getContext("2d");
context.fillStyle = getComputedStyle(canvas).color;
```

The code block below references <code>replay</code>, so it will run automatically whenever the replay button is clicked. If you click the button while the animation is still running, the animation will be interrupted and restart from the beginning.

```js echo
replay;
replay; // run this block when the button is clicked
const progress = (function* () {
for (let i = width; i >= 0; --i) {
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, i, height);
yield context.canvas;
for (let i = canvas.width; i >= 0; --i) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillRect(0, 0, i, canvas.height);
yield canvas;
}
})();
```

<div class="note">The <code>progress</code> top-level variable is declared as a <a href="../javascript/generators">generator</a>. This causes the Observable runtime to automatically advance the generator on each animation frame. If you prefer, you could write this animation using a standard <code>requestAnimationFrame</code> loop, but generators are nice because the animation will automatically be interrupted when the code is <a href="../javascript/reactivity#invalidation">invalidated</a>.</div>

You can also use buttons to count clicks. While the value of a button is often not needed, it defaults to zero and is incremented each time the button is clicked.

```js echo
Expand All @@ -49,15 +39,13 @@ const clicks = view(Inputs.button("Click me"));
clicks
```

Interpolate input values into Markdown using [inline expressions](../javascript#inline-expressions):

You have clicked ${clicks} times.
You have clicked ${clicks} times.

```md
You have clicked ${clicks} times.
```

You can change this behavior by specifying the *value* and *reduce* options: *value* is the initial value, and *reduce* is called whenever the button is clicked, being passed the current value and returning the new value. The value of the button below is the last time the button was clicked, or null if the button has not been clicked.
While buttons count clicks by default, you can change the behavior by specifying the *value* and *reduce* options: *value* is the initial value, and *reduce* is called whenever the button is clicked, being passed the current value and returning the new value. The value of the button below is the last time the button was clicked, or null if the button has not been clicked.

```js echo
const time = view(Inputs.button("Update", {value: null, reduce: () => new Date}));
Expand All @@ -83,7 +71,7 @@ const counter = view(Inputs.button([
counter
```

The first argument to `Inputs.button()` is the contents of the button. It’s not required, but it’s strongly encouraged.
The first argument to `Inputs.button()` is the contents of the button. It’s not required, but is strongly encouraged.

```js echo
const x = view(Inputs.button());
Expand Down Expand Up @@ -121,9 +109,9 @@ Inputs.button("Copy to clipboard", {value: null, reduce: () => navigator.clipboa

The available button input options are:

* *label* - a label; either a string or an HTML element.
* *label* - a label; either a string or an HTML element
* *required* - if true, the initial value defaults to undefined.
* *value* - the initial value; defaults to 0 or null if *required* is false.
* *reduce* - a function to update the value on click; by default returns *value* + 1.
* *width* - the width of the input (not including the label).
* *disabled* - whether input is disabled; defaults to false.
* *value* - the initial value; defaults to 0 or null if *required* is false
* *reduce* - a function to update the value on click; by default returns *value* + 1
* *width* - the width of the input (not including the label)
* *disabled* - whether input is disabled; defaults to false
83 changes: 54 additions & 29 deletions docs/inputs/checkbox.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Checkbox input

[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#checkbox)

The checkbox input allows the user to choose any of a given set of values. (See the [radio](./radio) input for single-choice.) A checkbox is recommended over a [select](./select) input when the number of values to choose from is small — say, seven or fewer — because all choices will be visible up-front, improving usability. For zero or one choice, see the [toggle](./toggle) input.
<a href="https://github.com/observablehq/inputs/blob/main/README.md#checkbox" target="_blank">API</a> · <a href="https://github.com/observablehq/inputs/blob/main/src/checkbox.js" target="_blank">Source</a> · The checkbox input allows the user to choose any of a given set of values. (See the [radio](./radio) input for single-choice.) A checkbox is recommended over a [select](./select) input when the number of values to choose from is small — say, seven or fewer — because all choices will be visible up-front, improving usability. For zero or one choice, see the [toggle](./toggle) input.

The initial value of a checkbox defaults to an empty array. You can override this by specifying the *value* option, which should also be an array (or iterable).

Expand All @@ -11,11 +9,7 @@ const colors = view(Inputs.checkbox(["red", "green", "blue"], {label: "color"}))
```

```js echo
colors
```

```html echo
<div style="display: flex;">${colors.map(color => html`<div style="background-color: ${color}; width: 25px; height: 25px;">`)}
colors
```

A checkbox’s values need not be strings: they can be anything. Specify a *format* function to control how these values are presented to the reader.
Expand All @@ -31,7 +25,7 @@ const teams = [
```

```js echo
const watching = view(Inputs.checkbox(teams, {label: "Watching", format: x => x.name}));
const watching = view(Inputs.checkbox(teams, {label: "Watching", format: (x) => x.name}));
```

```js echo
Expand All @@ -51,17 +45,38 @@ vowels
The *format* function, like the *label*, can return either a text string or an HTML element. This allows extensive control over the appearance of the checkbox, if desired.

```js echo
const colors2 = view(Inputs.checkbox(["red", "green", "blue"], {value: ["red"], label: html`<b>Colors</b>`, format: x => html`<span style="text-transform: capitalize; border-bottom: solid 2px ${x}; margin-bottom: -2px;">${x}`}));
const colors2 = view(
Inputs.checkbox(["red", "green", "blue"], {
value: ["red"],
label: html`<b>Colors</b>`,
format: (x) =>
html`<span style="
text-transform: capitalize;
border-bottom: solid 2px ${x};
margin-bottom: -2px;
">${x}</span>`
})
);
```

```js echo
colors2
```

If the checkbox’s data are specified as a Map, the values will be the map’s values while the keys will be the displayed options. (This behavior can be customized by passing *keyof* and *valueof* function options.) Below, the displayed sizes are named, but the value is the corresponding number of fluid ounces.
If the checkbox’s data are specified as a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), the values will be the map’s values while the keys will be the displayed options. (This behavior can be customized by passing *keyof* and *valueof* function options.) Below, the displayed sizes are named, but the value is the corresponding number of fluid ounces.

```js echo
const sizes = view(Inputs.checkbox(new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]), {value: [12], label: "Size"}));
const sizes = view(
Inputs.checkbox(
new Map([
["Short", 8],
["Tall", 12],
["Grande", 16],
["Venti", 20]
]),
{value: [12], label: "Size"}
)
);
```

```js echo
Expand All @@ -71,10 +86,17 @@ sizes
Since the *format* function is passed elements from the data, it can access both the key and value from the corresponding Map entry.

```js echo
const size2 = view(Inputs.checkbox(
new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]),
{value: [12], label: "Size", format: ([name, value]) => `${name} (${value} oz)`}
));
const size2 = view(
Inputs.checkbox(
new Map([
["Short", 8],
["Tall", 12],
["Grande", 16],
["Venti", 20]
]),
{value: [12], label: "Size", format: ([name, value]) => `${name} (${value} oz)`}
)
);
```

```js echo
Expand All @@ -84,14 +106,19 @@ size2
Passing a Map to checkbox is especially useful in conjunction with [d3.group](https://d3js.org/d3-array/group). For example, given a the sample `olympians` dataset of Olympic athletes, we can use d3.group to group them by gold medal count, and then checkbox to select the athletes for the chosen count. Note that the value of the checkbox will be an array of arrays, since d3.group returns a Map from key to array; use [*array*.flat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) to merge these arrays if desired.

```js echo
const goldAthletes = view(Inputs.checkbox(d3.group(olympians, d => d.gold), {label: "Gold medal count", sort: "descending", key: [4, 5]}));
const goldAthletes = view(
Inputs.checkbox(
d3.group(olympians, (d) => d.gold),
{label: "Gold medal count", sort: "descending", key: [4, 5]}
)
);
```

```js echo
goldAthletes.flat()
```

If the *sort* and *unique* options are specified, the checkbox’s keys will be sorted and duplicate keys will be discarded, respectively.
If the *sort* and *unique* options are specified, the checkbox’s keys will be sorted and duplicate keys will be discarded, respectively.

```js echo
const bases = view(Inputs.checkbox("GATTACA", {sort: true, unique: true}));
Expand All @@ -107,14 +134,12 @@ bases

The available checkbox input options are:

* *label* - a label; either a string or an HTML element.
* *sort* - true, “ascending”, “descending”, or a comparator function to sort keys; defaults to false.
* *unique* - true to only show unique keys; defaults to false.
* *locale* - the current locale; defaults to English.
* *format* - a format function; defaults to [formatLocaleAuto](https://github.com/observablehq/inputs/blob/main/README.md#inputsformatlocaleautolocale) composed with *keyof*.
* *keyof* - a function to return the key for the given element in *data*.
* *valueof* - a function to return the value of the given element in *data*.
* *value* - the initial value, an array; defaults to an empty array (no selection).
* *disabled* - whether input is disabled, or the disabled values; defaults to false.

<!-- TODO check formatLocaleAuto link-->
* *label* - a label; either a string or an HTML element
* *sort* - true, *ascending*, *descending*, or a comparator to sort keys; defaults to false
* *unique* - true to only show unique keys; defaults to false
* *locale* - the current locale; defaults to English
* *format* - a format function; defaults to [formatLocaleAuto](https://github.com/observablehq/inputs/blob/main/README.md#inputsformatlocaleautolocale) composed with *keyof*
* *keyof* - a function to return the key for the given element in *data*
* *valueof* - a function to return the value of the given element in *data*
* *value* - the initial value, an array; defaults to an empty array (no selection)
* *disabled* - whether input is disabled, or the disabled values; defaults to false
6 changes: 2 additions & 4 deletions docs/inputs/color.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Color input

[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#inputscoloroptions)

The color input specifies an RGB color as a hexadecimal string `#rrggbb`. The initial value defaults to black (`#000000`) and can be specified with the *value* option.
<a href="https://github.com/observablehq/inputs/blob/main/README.md#inputscoloroptions" target="_blank">API</a> · <a href="https://github.com/observablehq/inputs/blob/main/src/color.js" target="_blank">Source</a> · The color input specifies an RGB color as a hexadecimal string `#rrggbb`. The initial value defaults to black (`#000000`) and can be specified with the *value* option.

```js echo
const color = view(Inputs.color({label: "Favorite color", value: "#4682b4"}));
Expand Down Expand Up @@ -44,4 +42,4 @@ disabled

**Inputs.color(*options*)**

Like [Inputs.text](./text), but where *type* is color. The color value is represented as an RGB hexadecimal string such as #ff00ff. This type of input does not support the following options: *placeholder*, *pattern*, *spellcheck*, *autocomplete*, *autocapitalize*, *min*, *max*, *minlength*, *maxlength*.
Like [Inputs.text](./text), but where *type* is color. The color value is represented as an RGB hexadecimal string such as #ff00ff. This type of input does not support the following options: *placeholder*, *pattern*, *spellcheck*, *autocomplete*, *autocapitalize*, *min*, *max*, *minlength*, *maxlength*.
24 changes: 11 additions & 13 deletions docs/inputs/date.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Date input

[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#date)

The date input specifies a date.
<a href="https://github.com/observablehq/inputs/blob/main/README.md#date" target="_blank">API</a> · <a href="https://github.com/observablehq/inputs/blob/main/src/date.js" target="_blank">Source</a> · The date input specifies a date.

```js echo
const date = view(Inputs.date());
Expand Down Expand Up @@ -86,15 +84,15 @@ readonly

The available date input options are:

* *label* - a label; either a string or an HTML element.
* *value* - the initial value, as a JavaScript Date or formatted as an ISO string (yyyy-mm-dd); defaults to null.
* *min* - [minimum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min) attribute.
* *max* - [maximum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max) attribute.
* *required* - if true, the input must be a valid date.
* *validate* - a function to check whether the text input is valid.
* *width* - the width of the input (not including the label).
* *submit* - whether to require explicit submission before updating; defaults to false.
* *readonly* - whether input is readonly; defaults to false.
* *disabled* - whether input is disabled; defaults to false.
* *label* - a label; either a string or an HTML element
* *value* - the initial value as a [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) or `YYYY-MM-DD` string; defaults to null
* *min* - [minimum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min) attribute
* *max* - [maximum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max) attribute
* *required* - if true, the input must be a valid date
* *validate* - a function to check whether the text input is valid
* *width* - the width of the input (not including the label)
* *submit* - whether to require explicit submission; defaults to false
* *readonly* - whether input is readonly; defaults to false
* *disabled* - whether input is disabled; defaults to false

The value of the input is a Date instance at UTC midnight of the specified date, or null if no (valid) value has been specified. Note that the displayed date format is [based on the browser’s locale](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date).
Loading

0 comments on commit 4f654b4

Please sign in to comment.