Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit da9afa8

Browse files
authoredJan 18, 2024
Components (#522)
Adds the following Components docs: - Components overview - Inputs overview - Layout section - Grid - Card - Resize - Charts section (Observable Plot snippets, grouped into pages by general type, e.g. "Area charts") - Inputs section (single page per input)
1 parent 2bcb447 commit da9afa8

33 files changed

+13988
-13
lines changed
 

‎docs/charts/area.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Area chart
2+
3+
Copy the code snippets below, paste into a JavaScript code block, then substitute your own data to create area charts using the [area mark](https://observablehq.com/plot/marks/area) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
## Area chart
6+
7+
```js echo
8+
Plot.plot({
9+
marks: [
10+
Plot.areaY(aapl, {x: "Date", y: "Close"}),
11+
Plot.ruleY([0])
12+
]
13+
})
14+
```
15+
16+
## Band area chart
17+
18+
```js echo
19+
Plot.plot({
20+
marks: [
21+
Plot.areaY(weather.slice(-365), {x: "date", y1: "temp_min", y2: "temp_max", curve: "step"})
22+
]
23+
})
24+
```
25+
26+
## Stacked area chart
27+
28+
```js echo
29+
Plot.plot({
30+
y: {
31+
tickFormat: "s"
32+
},
33+
marks: [
34+
Plot.areaY(industries, {x: "date", y: "unemployed", fill: "industry"}),
35+
Plot.ruleY([0])
36+
]
37+
})
38+
```
39+
40+

‎docs/charts/arrow.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Arrow chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create arrow charts using the [arrow mark](https://observablehq.com/plot/marks/arrow) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
```js echo
6+
Plot.plot({
7+
x: {
8+
type: "log"
9+
},
10+
marks: [
11+
Plot.arrow(citywages, {
12+
x1: "POP_1980",
13+
y1: "R90_10_1980",
14+
x2: "POP_2015",
15+
y2: "R90_10_2015",
16+
bend: true
17+
})
18+
]
19+
})
20+
```

‎docs/charts/bar.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Bar chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create bar charts using the [bar mark](https://observablehq.com/plot/marks/bar) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
## Sorted bar chart
6+
7+
```js echo
8+
Plot.plot({
9+
marks: [
10+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reverse: true}}),
11+
Plot.ruleY([0])
12+
]
13+
})
14+
```
15+
16+
## Horizontal bar chart
17+
18+
```js echo
19+
Plot.plot({
20+
marks: [
21+
Plot.barX(alphabet, {x: "frequency", y: "letter", sort: {y: "x", reverse: true}}),
22+
Plot.ruleX([0])
23+
]
24+
})
25+
```
26+
27+
## Top 10 bar chart
28+
29+
```js echo
30+
Plot.plot({
31+
marks: [
32+
Plot.barX(olympians, Plot.groupY({x: "count"}, {y: "nationality", sort: {y: "x", reverse: true, limit: 10}})),
33+
Plot.ruleX([0])
34+
]
35+
})
36+
```
37+
38+
## Weighted top 10 bar chart
39+
40+
```js echo
41+
Plot.plot({
42+
marks: [
43+
Plot.barX(olympians, Plot.groupY({x: "sum"}, {x: "gold", y: "nationality", sort: {y: "x", reverse: true, limit: 10}})),
44+
Plot.ruleX([0])
45+
]
46+
})
47+
```
48+
49+
## Temporal bar chart
50+
51+
```js echo
52+
Plot.plot({
53+
marks: [
54+
Plot.rectY(weather.slice(-42), {x: "date", y: "wind", interval: d3.utcDay}),
55+
Plot.ruleY([0])
56+
]
57+
})
58+
```

‎docs/charts/cell.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Cell chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a cell chart using the [cell mark](https://observablehq.com/plot/marks/cell) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
```js echo
6+
Plot.plot({
7+
marks: [
8+
Plot.cell(weather.slice(-365), {
9+
x: d => d.date.getUTCDate(),
10+
y: d => d.date.getUTCMonth(),
11+
fill: "temp_max"
12+
})
13+
]
14+
})
15+
```

‎docs/charts/dot.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Scatterplot
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a scatterplot chart using the [dot mark](https://observablehq.com/plot/marks/dot) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
```js echo
6+
Plot.plot({
7+
marks: [
8+
Plot.dot(cars, {x: "power (hp)", y: "economy (mpg)"})
9+
]
10+
})
11+
```

‎docs/charts/facets.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Faceted chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a faceted chart with small multiples using the [facet channel](https://observablehq.com/plot/features/facets) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
```js echo
6+
Plot.plot({
7+
facet: {
8+
data: penguins,
9+
x: "species"
10+
},
11+
marks: [
12+
Plot.frame(),
13+
Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"})
14+
]
15+
})
16+
```

‎docs/charts/grouping-data.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Grouping data
2+
3+
[Observable Plot](https://observablehq.com/plot/) provides a number of [transforms](https://observablehq.com/plot/features/transforms) that help you perform common data transformations. The [group](https://observablehq.com/plot/transforms/group) and [bin](https://observablehq.com/plot/transforms/bin) transforms (for categorical and quantitative dimensions, respectively) group data into discrete bins. A reducer (_e.g._ sum, count, or mean) can then be applied to visualize summary values by bin.
4+
5+
## Hexbin chart
6+
7+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a hexbin chart using the [dot mark](https://observablehq.com/plot/marks/dot) and the [hexbin transform](https://observablehq.com/plot/transforms/hexbin).
8+
9+
```js echo
10+
Plot.plot({
11+
color: {scheme: "ylgnbu"},
12+
marks: [
13+
Plot.dot(olympians, Plot.hexbin({fill: "sum"}, {x: "weight", y: "height"}))
14+
]
15+
})
16+
```
17+
18+
## Histogram
19+
20+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a histogram using the [rect mark](https://observablehq.com/plot/marks/rect) and the [bin transform](https://observablehq.com/plot/transforms/bin).
21+
22+
```js echo
23+
Plot.plot({
24+
marks: [
25+
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"})),
26+
Plot.ruleY([0])
27+
]
28+
})
29+
```

‎docs/charts/line.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Line chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a line chart using the [line mark](https://observablehq.com/plot/marks/line) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
## Basic line chart
6+
7+
```js echo
8+
Plot.plot({
9+
marks: [
10+
Plot.ruleY([0]),
11+
Plot.lineY(aapl, {x: "Date", y: "Close"})
12+
]
13+
})
14+
```
15+
16+
## Multi-series line chart
17+
18+
```js echo
19+
Plot.plot({
20+
marks: [
21+
Plot.ruleY([0]),
22+
Plot.lineY(industries, {x: "date", y: "unemployed", z: "industry"})
23+
]
24+
})
25+
```
26+
27+
## Moving average line chart
28+
29+
```js echo
30+
Plot.plot({
31+
marks: [
32+
Plot.ruleY([0]),
33+
Plot.lineY(aapl, Plot.windowY({x: "Date", y: "Close", k: 10, reduce: "mean"}))
34+
]
35+
})
36+
```

‎docs/charts/tick.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Tick chart
2+
3+
Copy the code snippet below, paste into a JavaScript code block, then substitute your own data to create a tick chart using the [tick mark](https://observablehq.com/plot/marks/tick) from [Observable Plot](https://observablehq.com/plot/).
4+
5+
```js echo
6+
Plot.plot({
7+
marks: [
8+
Plot.tickX(cars, {x: "economy (mpg)", y: "year"})
9+
]
10+
})
11+
```

‎docs/components.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Components
2+
3+
You don’t have to start from scratch: components are reusable pieces of code (functions, themes, snippets, etc.) that make it quicker to update page layout and appearance, and add common page content.
4+
5+
The Observable CLI offers three flavors of components: [layout helpers](#layout-helpers), [Observable Plot snippets](#observable-plot-snippets), and [Observable Inputs](#observable-inputs).
6+
7+
## Layout helpers
8+
9+
A collection of elements useful for formatting page content: themes, `card` and `grid` CSS classes, and the `resize` function.
10+
11+
### Themes
12+
13+
<!-- TODO update link to themes gallery layout/themes.md page once added-->
14+
Observable Markdown offers a number of [built-in themes](./config#theme) that you can compose to create, say, wide pages with an alternative dark color theme:
15+
16+
```js run=false
17+
theme: ["dark", "alt", "wide"]
18+
```
19+
20+
The code above, when included in the [config file](./config), specifies the default theme for the project. In addition, you can specify the theme for a single page in its [front matter](markdown#front-matter):
21+
22+
```yaml
23+
---
24+
theme: [dark, alt, wide]
25+
---
26+
```
27+
28+
You are not limited to the built-in themes. For complete control over the design of your project, see the [style option](./config/#style) instead.
29+
30+
### Grid
31+
32+
The included [`grid`](./layout/grid) CSS classes make it easier to control how page content is arranged.
33+
34+
<div class="grid grid-cols-2">
35+
<div class="card"><h1>A</h1>1 × 1</div>
36+
<div class="card grid-rowspan-2"><h1>B</h1>1 × 2</div>
37+
<div class="card"><h1>C</h1>1 × 1</div>
38+
<div class="card grid-colspan-2"><h1>D</h1>1 × 2</div>
39+
</div>
40+
41+
```html run=false
42+
<div class="grid grid-cols-2">
43+
<div class="card"><h1>A</h1>1 × 1</div>
44+
<div class="card grid-rowspan-2"><h1>B</h1> 1 × 2</div>
45+
<div class="card"><h1>C</h1>1 × 1</div>
46+
<div class="card grid-colspan-2"><h1>D</h1>1 × 2</div>
47+
</div>
48+
```
49+
50+
### Card
51+
52+
The [`card`](./layout/card) CSS class has default styles that help create a card: container borders, background color, padding and optional titles and subtitles.
53+
54+
<div class="grid grid-cols-2">
55+
<div class="card">
56+
<h2>A card title</h2>
57+
<h3>A card subtitle</h3>
58+
${
59+
Plot.plot({
60+
marks: [
61+
Plot.dot(penguins, {x: "body_mass_g", y: "flipper_length_mm"})
62+
]
63+
})
64+
}
65+
</div>
66+
<div class="card">
67+
<p>Tortor condimentum lacinia quis vel eros. Arcu risus quis varius quam quisque id. Magnis dis parturient montes nascetur ridiculus mus mauris. Porttitor leo a diam sollicitudin. Odio facilisis mauris sit amet massa vitae tortor. Nibh venenatis cras sed felis eget velit aliquet sagittis. Ullamcorper sit amet risus nullam eget felis eget nunc. In egestas erat imperdiet sed euismod nisi porta lorem mollis. A erat nam at lectus urna duis convallis. Id eu nisl nunc mi ipsum faucibus vitae. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac.</p>
68+
</div>
69+
</div>
70+
71+
```html run=false
72+
<div class="grid grid-cols-2">
73+
<div class="card">
74+
<h2>A card title</h2>
75+
<h3>A card subtitle</h3>
76+
${
77+
Plot.plot({
78+
marks: [
79+
Plot.dot(penguins, {x: "body_mass_g", y: "flipper_length_mm"})
80+
]
81+
})
82+
}
83+
</div>
84+
<div class="card">
85+
<p>Tortor condimentum lacinia quis vel eros. Arcu risus quis varius quam quisque id. Magnis dis parturient montes nascetur ridiculus mus mauris. Porttitor leo a diam sollicitudin. Odio facilisis mauris sit amet massa vitae tortor. Nibh venenatis cras sed felis eget velit aliquet sagittis. Ullamcorper sit amet risus nullam eget felis eget nunc. In egestas erat imperdiet sed euismod nisi porta lorem mollis. A erat nam at lectus urna duis convallis. Id eu nisl nunc mi ipsum faucibus vitae. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac.</p>
86+
</div>
87+
</div>
88+
```
89+
90+
### Resize
91+
92+
The [`resize`](./layout/resize) function automatically recomputes a DOM element (often, a chart) when the dimensions of its parent container change.
93+
94+
Resize exists in the Observable standard library, or can be imported explicitly:
95+
96+
```js echo
97+
import {resize} from "npm:@observablehq/stdlib";
98+
```
99+
100+
<div>
101+
${resize((width) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width}))}
102+
</div>
103+
104+
```html run=false
105+
<div>
106+
${resize((width) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width}))}
107+
</div>
108+
```
109+
110+
## Observable Plot snippets
111+
112+
[Observable Plot](https://observablehq.com/plot/) is a free, open source JavaScript library for concise and expressive data visualization, built by Observable.
113+
114+
Several examples of Observable Plot code are included in this documentation, covering some common chart types including area charts ([stacked](./charts/area#stacked-area-chart) and [band area](./charts/area#band-area-chart)), bar charts ([sorted](./charts/bar#sorted-bar-chart), [temporal](./charts/bar#temporal-bar-chart), and [weighted](./charts/bar#weighted-top-10-bar-chart)), line charts ([single-series](./charts/line#basic-line-chart), [multi-series](./charts/line#multi-series-line-chart) and [moving average](./charts/line#moving-average-line-chart)), [scatterplots](./charts/dot#scatterplot), and more. See [Observable Plot’s gallery](https://observablehq.com/@observablehq/plot-gallery) for even more examples.
115+
116+
All examples use common datasets that are loaded when referenced by name, such as the `weather` dataset in the code snippet below.
117+
118+
```js echo
119+
Plot.plot({
120+
marks: [
121+
Plot.cell(weather, {
122+
x: d => d.date.getUTCDate(),
123+
y: d => d.date.getUTCMonth(),
124+
fill: "temp_max"
125+
})
126+
]
127+
})
128+
```
129+
130+
If the chart type you want to add is not included as a snippet here, don’t sweat - a great number of examples (in both [Observable Plot](https://observablehq.com/@observablehq/plot-gallery) and [D3](https://observablehq.com/@d3/gallery)) are available to explore and reuse.
131+
132+
**Can I use other data visualization libraries?** Absolutely. Use any other visualization library you like by [importing from npm](./javascript/imports).
133+
134+
## Observable Inputs
135+
136+
The [Observable Inputs](./lib/inputs) library provides a suite of lightweight interface components — buttons, sliders, dropdowns, checkboxes, and the like — that viewers can update to explore interactive displays (for example, selecting only a few of many categories to show in a bar chart).
137+
138+
The [radio input](./inputs/radio) prompts a user to select a penguin species:
139+
140+
```js echo
141+
const pickSpecies = view(Inputs.radio(["Adelie", "Chinstrap", "Gentoo"], {value: "Gentoo", label: "Penguin species:"}))
142+
```
143+
144+
The value of `pickSpecies` (<tt>="${pickSpecies}"</tt>) can then be accessed elsewhere in the page, as a parameter in other computations, and to create interactive charts, tables or text with [inline expressions](./javascript#inline-expressions).

‎docs/inputs/button.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Button input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#button)
4+
5+
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.
6+
7+
For example, below is an animation (using [yield](../javascript/generators)) that progressively hides a bar.
8+
9+
```js echo
10+
import * as Inputs from "npm:@observablehq/inputs";
11+
```
12+
13+
```js echo
14+
const width = 360;
15+
const height = 20;
16+
const style = "max-width: 100%; border: solid 1px black;";
17+
```
18+
19+
```html
20+
<canvas id="canvas" width="${width}" height="${height}" style="${style}">
21+
```
22+
23+
```js echo
24+
const replay = view(Inputs.button("Replay"));
25+
const canvas = document.querySelector("#canvas");
26+
const context = canvas.getContext("2d");
27+
```
28+
29+
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.
30+
31+
```js echo
32+
replay;
33+
const progress = (function* () {
34+
for (let i = width; i >= 0; --i) {
35+
context.clearRect(0, 0, width, height);
36+
context.fillRect(0, 0, i, height);
37+
yield context.canvas;
38+
}
39+
})();
40+
```
41+
42+
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.
43+
44+
```js echo
45+
const clicks = view(Inputs.button("Click me"));
46+
```
47+
48+
```js echo
49+
clicks
50+
```
51+
52+
Interpolate input values into Markdown using [inline expressions](../javascript#inline-expressions):
53+
54+
You have clicked ${clicks} times.
55+
56+
```md
57+
You have clicked ${clicks} times.
58+
```
59+
60+
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.
61+
62+
```js echo
63+
const time = view(Inputs.button("Update", {value: null, reduce: () => new Date}));
64+
```
65+
66+
```js
67+
time
68+
```
69+
70+
Note that even if the value of the button doesn’t change, it will still trigger any cells that reference the button’s value to run. (The Observable runtime listens for *input* events on the view, and doesn’t check whether the value of the view has changed.)
71+
72+
For multiple buttons, pass an array of [*content*, *reduce*] tuples. For example, to have a counter that can be incremented, decremented, and reset:
73+
74+
```js echo
75+
const counter = view(Inputs.button([
76+
["Increment", value => value + 1],
77+
["Decrement", value => value - 1],
78+
["Reset", value => 0]
79+
], {value: 0, label: "Counter"}));
80+
```
81+
82+
```js echo
83+
counter
84+
```
85+
86+
The first argument to `Inputs.button()` is the contents of the button. It’s not required, but it’s strongly encouraged.
87+
88+
```js echo
89+
const x = view(Inputs.button());
90+
```
91+
92+
The contents of the button input can be an HTML element if desired, say for control over typography.
93+
94+
```js echo
95+
const y = view(Inputs.button(html`<i>Fancy</i>`));
96+
```
97+
98+
Like other basic inputs, buttons can have an optional label, which can also be either a text string or an HTML element.
99+
100+
```js echo
101+
const confirm = view(Inputs.button("OK", {label: "Continue?"}));
102+
```
103+
104+
You can change the rendered text in Markdown based on whether a button is clicked. Try clicking the `OK` button with the `Continue?` label.
105+
106+
```md echo run=false
107+
confirm ? "Confirmed!" : "Awaiting confirmation..."
108+
```
109+
110+
${confirm ? "Confirmed!" : "Awaiting confirmation..."}
111+
112+
You can also use a button to copy something to the clipboard.
113+
114+
```js echo
115+
Inputs.button("Copy to clipboard", {value: null, reduce: () => navigator.clipboard.writeText(time)})
116+
```
117+
118+
## Options
119+
120+
**Inputs.button(*content*, *options*)**
121+
122+
The available button input options are:
123+
124+
* *label* - a label; either a string or an HTML element.
125+
* *required* - if true, the initial value defaults to undefined.
126+
* *value* - the initial value; defaults to 0 or null if *required* is false.
127+
* *reduce* - a function to update the value on click; by default returns *value* + 1.
128+
* *width* - the width of the input (not including the label).
129+
* *disabled* - whether input is disabled; defaults to false.

‎docs/inputs/checkbox.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Checkbox input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#checkbox)
4+
5+
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.
6+
7+
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).
8+
9+
```js echo
10+
const colors = view(Inputs.checkbox(["red", "green", "blue"], {label: "color"}));
11+
```
12+
13+
```js echo
14+
colors
15+
```
16+
17+
```html echo
18+
<div style="display: flex;">${colors.map(color => html`<div style="background-color: ${color}; width: 25px; height: 25px;">`)}
19+
```
20+
21+
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.
22+
23+
```js echo
24+
const teams = [
25+
{name: "Lakers", location: "Los Angeles, California"},
26+
{name: "Warriors", location: "San Francisco, California"},
27+
{name: "Celtics", location: "Boston, Massachusetts"},
28+
{name: "Nets", location: "New York City, New York"},
29+
{name: "Raptors", location: "Toronto, Ontario"},
30+
];
31+
```
32+
33+
```js echo
34+
const watching = view(Inputs.checkbox(teams, {label: "Watching", format: x => x.name}));
35+
```
36+
37+
```js echo
38+
watching
39+
```
40+
41+
A checkbox can be disabled by setting the *disabled* option to true. Alternatively, specific options can be disabled by passing an array of values to disable.
42+
43+
```js echo
44+
const vowels = view(Inputs.checkbox([..."AEIOUY"], {label: "Vowel", disabled: ["Y"]}));
45+
```
46+
47+
```js echo
48+
vowels
49+
```
50+
51+
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.
52+
53+
```js echo
54+
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}`}));
55+
```
56+
57+
```js echo
58+
colors2
59+
```
60+
61+
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.
62+
63+
```js echo
64+
const sizes = view(Inputs.checkbox(new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]), {value: [12], label: "Size"}));
65+
```
66+
67+
```js echo
68+
sizes
69+
```
70+
71+
Since the *format* function is passed elements from the data, it can access both the key and value from the corresponding Map entry.
72+
73+
```js echo
74+
const size2 = view(Inputs.checkbox(
75+
new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]),
76+
{value: [12], label: "Size", format: ([name, value]) => `${name} (${value} oz)`}
77+
));
78+
```
79+
80+
```js echo
81+
size2
82+
```
83+
84+
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.
85+
86+
```js echo
87+
const goldAthletes = view(Inputs.checkbox(d3.group(olympians, d => d.gold), {label: "Gold medal count", sort: "descending", key: [4, 5]}));
88+
```
89+
90+
```js echo
91+
goldAthletes.flat()
92+
```
93+
94+
If the *sort* and *unique* options are specified, the checkbox’s keys will be sorted and duplicate keys will be discarded, respectively.
95+
96+
```js echo
97+
const bases = view(Inputs.checkbox("GATTACA", {sort: true, unique: true}));
98+
```
99+
100+
```js echo
101+
bases
102+
```
103+
104+
## Options
105+
106+
**Inputs.checkbox(*data*, *options*)**
107+
108+
The available checkbox input options are:
109+
110+
* *label* - a label; either a string or an HTML element.
111+
* *sort* - true, “ascending”, “descending”, or a comparator function to sort keys; defaults to false.
112+
* *unique* - true to only show unique keys; defaults to false.
113+
* *locale* - the current locale; defaults to English.
114+
* *format* - a format function; defaults to [formatLocaleAuto](https://github.com/observablehq/inputs/blob/main/README.md#inputsformatlocaleautolocale) composed with *keyof*.
115+
* *keyof* - a function to return the key for the given element in *data*.
116+
* *valueof* - a function to return the value of the given element in *data*.
117+
* *value* - the initial value, an array; defaults to an empty array (no selection).
118+
* *disabled* - whether input is disabled, or the disabled values; defaults to false.
119+
120+
<!-- TODO check formatLocaleAuto link-->

‎docs/inputs/color.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Color input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#inputscoloroptions)
4+
5+
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.
6+
7+
```js echo
8+
const color = view(Inputs.color({label: "Favorite color", value: "#4682b4"}));
9+
```
10+
11+
```js echo
12+
color
13+
```
14+
15+
The color input is currently strict in regards to input: it does not accept any CSS color string. If you’d like greater flexibility, consider using D3 to parse colors and format them as hexadecimal.
16+
17+
```js echo
18+
const fill = view(Inputs.color({label: "Fill", value: d3.color("steelblue").formatHex()}));
19+
```
20+
21+
If you specify the *datalist* option as an array of hexadecimal color strings, the color picker will show this set of colors for convenient picking. (The user will still be allowed to pick another color, however; if you want to limit the choice to a specific set, then a radio or select input may be more appropriate.)
22+
23+
<!-- [TODO] update to the new Observable10 color palette? -->
24+
25+
```js echo
26+
const stroke = view(Inputs.color({label: "Stroke", datalist: d3.schemeTableau10}));
27+
```
28+
29+
```js echo
30+
stroke
31+
```
32+
33+
The *readonly* property is not supported for color inputs, but you can use the *disabled* option to prevent the input value from being changed.
34+
35+
```js echo
36+
const disabled = view(Inputs.color({label: "Disabled", value: "#f28e2c", disabled: true}));
37+
```
38+
39+
```js echo
40+
disabled
41+
```
42+
43+
## Options
44+
45+
**Inputs.color(*options*)**
46+
47+
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*.

‎docs/inputs/date.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Date input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#date)
4+
5+
The date input specifies a date.
6+
7+
```js echo
8+
const date = view(Inputs.date());
9+
```
10+
11+
```js echo
12+
date
13+
```
14+
15+
We recommend providing a *label* to improve usability. You can also supply an initial *value*; this can be specified as a Date instance, a string of the form *YYYY-MM-DD*, or the corresponding number of milliseconds since UNIX epoch.
16+
17+
```js echo
18+
const start = view(Inputs.date({label: "Start", value: "2021-09-21"}));
19+
```
20+
21+
```js echo
22+
start
23+
```
24+
25+
The value of a date input is a [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance at UTC midnight, or null if an initial value is not specified. If the *required* option is set to true, then the initial value of the date input will be undefined instead of null, causing referencing cells to wait until a valid input is entered.
26+
27+
```js echo
28+
const rdate = view(Inputs.date({label: "Date", required: true}));
29+
```
30+
31+
```js echo
32+
rdate
33+
```
34+
35+
The datetime input is similar to the date input, except it allows a time to be specified in addition to a date. The time is specified in the user’s local time zone (for you, that’s ${Intl.DateTimeFormat().resolvedOptions().timeZone}). Like a date input, the value is exposed as a Date instance. Dates are formatted by the Observable inspector as UTC; note the `Z`.
36+
37+
```js echo
38+
const datetime = view(Inputs.datetime({label: "Moment"}));
39+
```
40+
41+
```js echo
42+
datetime
43+
```
44+
45+
The *min* and *max* option allow you to set a lower and upper bound for valid inputs. Like the *value* option, these options may be specified either as a Date instance, as *YYYY-MM-DD* strings, or epoch milliseconds.
46+
47+
```js echo
48+
const birthday = view(Inputs.date({label: "Birthday", min: "2021-01-01", max: "2021-12-31"}));
49+
```
50+
51+
```js echo
52+
birthday
53+
```
54+
55+
If the input will trigger some expensive calculation, such as fetching from a remote server, the *submit* option can be used to defer the input from reporting the new value until the user clicks the Submit button or hits Enter. The value of *submit* can also be the desired contents of the submit button (a string or HTML).
56+
57+
```js echo
58+
const sdate = view(Inputs.date({label: "Date", submit: true}));
59+
```
60+
61+
```js echo
62+
sdate
63+
```
64+
65+
To prevent the value from being changed, use the *disabled* or *readonly* option.
66+
67+
```js echo
68+
const fixed = view(Inputs.date({label: "Fixed date", value: "2021-01-01", disabled: true}));
69+
```
70+
71+
```js echo
72+
fixed
73+
```
74+
75+
```js echo
76+
const readonly = view(Inputs.date({label: "Readonly date", value: "2021-01-01", readonly: true}));
77+
```
78+
79+
```js echo
80+
readonly
81+
```
82+
83+
## Options
84+
85+
**Inputs.date(*options*)**
86+
87+
The available date input options are:
88+
89+
* *label* - a label; either a string or an HTML element.
90+
* *value* - the initial value, as a JavaScript Date or formatted as an ISO string (yyyy-mm-dd); defaults to null.
91+
* *min* - [minimum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min) attribute.
92+
* *max* - [maximum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max) attribute.
93+
* *required* - if true, the input must be a valid date.
94+
* *validate* - a function to check whether the text input is valid.
95+
* *width* - the width of the input (not including the label).
96+
* *submit* - whether to require explicit submission before updating; defaults to false.
97+
* *readonly* - whether input is readonly; defaults to false.
98+
* *disabled* - whether input is disabled; defaults to false.
99+
100+
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).

‎docs/inputs/file.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# File input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#file)
4+
5+
The file input specifies a local file. The exposed value provides the same interface as an Observable [file attachment](../javascript/files) for convenient parsing in various formats such as text, image, JSON, CSV, ZIP, and XLSX; however, the file is not uploaded and is only available temporarily in memory.
6+
7+
By default, any file is allowed, and the value of the input resolves to null.
8+
9+
```js echo
10+
const file = view(Inputs.file());
11+
```
12+
13+
```js echo
14+
file
15+
```
16+
17+
We recommend providing a *label* to improve usability.
18+
19+
Specify the *accept* option to limit the allowed file extensions. This is useful when you intend to parse the file as a specific file format, such as CSV. By setting the *required* option to true, the value of the input won’t resolve until the user choses a file.
20+
21+
```js echo
22+
const csvfile = view(Inputs.file({label: "CSV file", accept: ".csv", required: true}));
23+
```
24+
25+
Once a file has been selected, you can read its contents like so:
26+
27+
28+
```js echo
29+
const data = display(await csvfile.csv({typed: true}));
30+
```
31+
32+
Here are examples of other supported file types.
33+
34+
```js echo
35+
const jsonfile = view(Inputs.file({label: "JSON file", accept: ".json", required: true}));
36+
```
37+
38+
```js echo
39+
const data = display(await jsonfile.json());
40+
```
41+
42+
```js echo
43+
const textfile = view(Inputs.file({label: "Text file", accept: ".txt", required: true}));
44+
```
45+
46+
```js echo
47+
const data = display(await textfile.text());
48+
```
49+
50+
```js echo
51+
const imgfile = view(Inputs.file({label: "Image file", accept: ".png,.jpg", required: true}));
52+
```
53+
54+
```js echo
55+
const image = display(await imgfile.image());
56+
```
57+
58+
```js echo
59+
const xlsxfile = view(Inputs.file({label: "Excel file", accept: ".xlsx", required: true}));
60+
```
61+
62+
```js echo
63+
const workbook = display(await xlsxfile.xlsx());
64+
```
65+
66+
```js echo
67+
const zipfile = view(Inputs.file({label: "ZIP archive", accept: ".zip", required: true}));
68+
```
69+
70+
```js echo
71+
const archive = display(await zipfile.zip())
72+
```
73+
74+
The *multiple* option allows the user to pick multiple files. In this mode, the exposed value is an array of files instead of a single file.
75+
76+
```js echo
77+
const files = view(Inputs.file({label: "Files", multiple: true}));
78+
```
79+
80+
```js echo
81+
files
82+
```
83+
84+
## Options
85+
86+
**Inputs.file(*options*)**
87+
88+
The available file input options are:
89+
90+
* *label* - a label; either a string or an HTML element.
91+
* *required* - if true, a valid file must be selected.
92+
* *validate* - a function to check whether the file input is valid.
93+
* *accept* - the [acceptable file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept).
94+
* *capture* - for [capturing image or video data](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#capture).
95+
* *multiple* - whether to allow multiple files to be selected; defaults to false.
96+
* *width* - the width of the input (not including the label).
97+
* *disabled* - whether input is disabled; defaults to false.
98+
99+
Note that the value of file input cannot be set programmatically; it can only be changed by the user.
100+
101+
<!-- TODO check: Delete? (In vanilla JavaScript, the Inputs.file method is not exposed directly. Instead, an Inputs.fileOf method is exposed which takes an AbstractFile implementation and returns the Inputs.file method. This avoids a circular dependency between Observable Inputs and the Observable standard library.)-->

‎docs/inputs/form.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Form input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#inputsforminputs-options)
4+
5+
The form input combines a number of inputs into a single compound input. It’s intended for a more compact display of closely-related inputs, say for a color’s red, green, and blue channels.
6+
7+
```js echo
8+
const rgb = view(Inputs.form([
9+
Inputs.range([0, 255], {step: 1, label: "r"}),
10+
Inputs.range([0, 255], {step: 1, label: "g"}),
11+
Inputs.range([0, 255], {step: 1, label: "b"})
12+
]));
13+
```
14+
15+
```js echo
16+
rgb
17+
```
18+
19+
You can pass either an array of inputs to Inputs.form, as shown above, or a simple object with enumerable properties whose values are inputs. In the latter case, the value of the form input is an object with the same structure whose values are the respective input’s value.
20+
21+
```js echo
22+
const rgb2 = view(Inputs.form({
23+
r: Inputs.range([0, 255], {step: 1, label: "r"}),
24+
g: Inputs.range([0, 255], {step: 1, label: "g"}),
25+
b: Inputs.range([0, 255], {step: 1, label: "b"})
26+
}));
27+
```
28+
29+
```js echo
30+
rgb2
31+
```
32+
33+
## Options
34+
35+
**Inputs.form(*inputs*, *options*)**
36+
37+
The available form input options are:
38+
39+
* *template* - a function that takes the given *inputs* and returns an HTML element to display.
40+
41+
If the *template* object is not specified, the given inputs are wrapped in a DIV.

‎docs/inputs/radio.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Radio input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#radio)
4+
5+
The radio input allows the user to choose one of a given set of values. (See the [checkbox](./checkbox) input for multiple-choice.) A radio 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.
6+
7+
```js echo
8+
const color = view(Inputs.radio(["red", "green", "blue"], {label: "color"}));
9+
```
10+
11+
```js echo
12+
color
13+
```
14+
15+
```html echo
16+
<div style="background-color: ${color}; width: 25px; height: 25px;">
17+
```
18+
19+
Note that a radio cannot be cleared by the user once selected; if you wish to allow no selection, include null in the allowed values.
20+
21+
```js echo
22+
const vote = view(Inputs.radio(["Yea", "Nay", null], {value: null, format: x => x ?? "Abstain"}));
23+
```
24+
25+
```js echo
26+
vote
27+
```
28+
29+
A radio’s values need not be strings: they can be anything. Specify a *format* function to control how these values are presented to the reader.
30+
31+
```js echo
32+
const teams = [
33+
{name: "Lakers", location: "Los Angeles, California"},
34+
{name: "Warriors", location: "San Francisco, California"},
35+
{name: "Celtics", location: "Boston, Massachusetts"},
36+
{name: "Nets", location: "New York City, New York"},
37+
{name: "Raptors", location: "Toronto, Ontario"},
38+
];
39+
```
40+
41+
```js echo
42+
const favorite = view(Inputs.radio(teams, {label: "Favorite team", format: x => x.name}));
43+
```
44+
45+
```js echo
46+
favorite
47+
```
48+
49+
A radio can be disabled by setting the *disabled* option to true. Alternatively, specific options can be disabled by passing an array of values to disable.
50+
51+
```js echo
52+
const vowel = view(Inputs.radio([..."AEIOUY"], {label: "Vowel", disabled: ["Y"]}));
53+
```
54+
55+
```js echo
56+
vowel
57+
```
58+
59+
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 radio, if desired.
60+
61+
```js echo
62+
const color2 = view(Inputs.radio(["red", "green", "blue"], {value: "red", label: html`<b>Color</b>`, format: x => html`<span style="text-transform: capitalize; border-bottom: solid 2px ${x}; margin-bottom: -2px;">${x}`}));
63+
```
64+
65+
```js echo
66+
color2
67+
```
68+
69+
If the radio’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.
70+
71+
```js echo
72+
const size = view(Inputs.radio(new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]), {value: 12, label: "Size"}));
73+
```
74+
75+
```js echo
76+
size
77+
```
78+
79+
Since the *format* function is passed elements from the data, it can access both the key and value from the corresponding Map entry.
80+
81+
```js echo
82+
const size2 = view(Inputs.radio(
83+
new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]),
84+
{value: 12, label: "Size", format: ([name, value]) => `${name} (${value} oz)`}
85+
));
86+
```
87+
88+
```js echo
89+
size2
90+
```
91+
92+
Passing a Map to radio is especially useful in conjunction with [d3.group](https://d3js.org/d3-array/group). For example, given a tabular dataset of Olympic athletes (`olympians`), we can use d3.group to group them by gold medal count, and then a radio input to select the athletes for the chosen count.
93+
94+
```js echo
95+
const goldAthletes = view(Inputs.radio(d3.group(olympians, d => d.gold), {label: "Gold medal count", sort: "descending"}));
96+
```
97+
98+
```js echo
99+
goldAthletes
100+
```
101+
102+
If the *sort* and *unique* options are specified, the radio’s keys will be sorted and duplicate keys will be discarded, respectively.
103+
104+
```js echo
105+
const base = view(Inputs.radio("GATTACA", {sort: true, unique: true}));
106+
```
107+
108+
```js echo
109+
base
110+
```
111+
112+
## Options
113+
114+
**Inputs.radio(*data*, *options*)**
115+
116+
The available radio input options are:
117+
118+
* *label* - a label; either a string or an HTML element.
119+
* *sort* - true, “ascending”, “descending”, or a comparator function to sort keys; defaults to false.
120+
* *unique* - true to only show unique keys; defaults to false.
121+
* *locale* - the current locale; defaults to English.
122+
* *format* - a format function; defaults to [formatLocaleAuto](https://github.com/observablehq/inputs/blob/main/README.md#inputsformatlocaleautolocale) composed with *keyof*.
123+
* *keyof* - a function to return the key for the given element in *data*.
124+
* *valueof* - a function to return the value of the given element in *data*.
125+
* *value* - the initial value; defaults to null (no selection).
126+
* *disabled* - whether input is disabled, or the disabled values; defaults to false.

‎docs/inputs/range.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Range input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#range)
4+
5+
The range input specifies a number between the given *min* and *max* (inclusive). This number can be adjusted roughly by sliding, or precisely by typing. A range input is also known as a slider.
6+
7+
By default, a range chooses a floating point number between 0 and 1 with full precision, which is often more precision than desired.
8+
9+
```js echo
10+
const x = view(Inputs.range());
11+
```
12+
13+
```js echo
14+
x
15+
```
16+
17+
The current value of *x* is ${x.toLocaleString("en")}.
18+
19+
The *step* option is strongly encouraged to set the desired precision (the interval between adjacent values). For integers, use *step* = 1. The up and down buttons in the number input will only work if a *step* is specified. To change the extent, pass [*min*, *max*] as the first argument.
20+
21+
```js echo
22+
const y = view(Inputs.range([0, 255], {step: 1}));
23+
```
24+
25+
The *min*, *max* and *step* options affect only the slider behavior, the number input’s buttons, and whether the browser shows a warning if a typed number is invalid; they do not constrain the typed number.
26+
27+
The *value* option sets the initial value, which defaults to the middle of the range: (*min* + *max*) / 2.
28+
29+
```js echo
30+
const z = view(Inputs.range([0, 255], {step: 1, value: 0}));
31+
```
32+
33+
```js echo
34+
z
35+
```
36+
37+
To describe the meaning of the input, supply a *label*. A *placeholder* string may also be specified; it will only be visible when the number input is empty.
38+
39+
```js echo
40+
const gain = view(Inputs.range([0, 11], {label: "Gain", step: 0.1, placeholder: "0–11"}));
41+
```
42+
43+
```js echo
44+
gain
45+
```
46+
47+
For more control over typography, the *label* may be an HTML element.
48+
49+
```js echo
50+
const n = view(Inputs.range([1, 10], {label: html`Top <i>n</i>`, step: 1}));
51+
```
52+
53+
You can even use a ${tex`\TeX`} label, if you’re into that sort of thing.
54+
55+
```js echo
56+
const psir = view(Inputs.range([0, 1], {label: tex`\psi(\textbf{r})`}));
57+
```
58+
59+
For an unbounded range, or simply to suppress the range input, you can use Inputs.number instead of Inputs.range. If you don’t specify an initial value, it defaults to undefined which causes referencing cells to wait for valid input.
60+
61+
```js echo
62+
const m = view(Inputs.number([0, Infinity], {step: 1, label: "Favorite integer", placeholder: ""}));
63+
```
64+
65+
```js echo
66+
m
67+
```
68+
69+
If differences in the numeric range are not uniformly interesting — for instance, when looking at log-distributed values — pass a *transform* function to produce a [nonlinear slider](https://mathisonian.github.io/idyll/nonlinear-sliders/). The built-in Math.log and Math.sqrt transform functions are recommended. If you supply a custom function, you should also provide an *invert* function that implements the inverse transform. (Otherwise, the Range will use [Newton’s method](https://en.wikipedia.org/wiki/Newton%27s_method) which may be inaccurate.)
70+
71+
```js echo
72+
Inputs.range([1, 100], {transform: Math.log})
73+
```
74+
75+
```js echo
76+
Inputs.range([0, 1], {transform: Math.sqrt})
77+
```
78+
79+
The *format* option allows you to specify a function that is called to format the displayed number. Note that the returned string must be a [valid floating-point number](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number) according to the HTML specification; no commas allowed!
80+
81+
```js echo
82+
const f = view(Inputs.range([0, 1], {format: x => x.toFixed(2)}));
83+
```
84+
85+
```js echo
86+
f
87+
```
88+
89+
To prevent a range’s value from being changed, use the *disabled* option.
90+
91+
```js echo
92+
const d = view(Inputs.range([0, 1], {disabled: true}));
93+
```
94+
95+
```js echo
96+
d
97+
```
98+
99+
## Options
100+
101+
**Inputs.range(*extent*, *options*)**
102+
103+
The available range input options are:
104+
105+
* *label* - a label; either a string or an HTML element.
106+
* *step* - the step (precision); the interval between adjacent values.
107+
* *format* - a format function; defaults to [formatTrim](https://github.com/observablehq/inputs?tab=readme-ov-file#inputsformattrimnumber).
108+
* *placeholder* - a placeholder string for when the input is empty.
109+
* *transform* - an optional non-linear transform.
110+
* *invert* - the inverse transform.
111+
* *validate* - a function to check whether the number input is valid.
112+
* *value* - the initial value; defaults to (*min* + *max*) / 2.
113+
* *width* - the width of the input (not including the label).
114+
* *disabled* - whether input is disabled; defaults to false.
115+
116+
The given *value* is clamped to the given extent, and rounded if *step* is defined. However, note that the *min*, *max* and *step* options affect only the slider behavior, the number input’s buttons, and whether the browser shows a warning if a typed number is invalid; they do not constrain the typed number.
117+
118+
If *validate* is not defined, [*number*.checkValidity](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity) is used. While the input is not considered valid, changes to the input will not be reported.
119+
120+
The *format* function should return a string value that is compatible with native number parsing. Hence, the default [formatTrim](https://github.com/observablehq/inputs?tab=readme-ov-file#inputsformattrimnumber) is recommended.
121+
122+
If a *transform* function is specified, an inverse transform function *invert* is strongly recommended. If *invert* is not provided, the Range will fallback to Newton’s method, but this may be slow or inaccurate. Passing Math.sqrt, Math.log, or Math.exp as a *transform* will automatically supply the corresponding *invert*. If *min* is greater than *max*, *i.e.* if the extent is inverted, then *transform* and *invert* will default to `value => -value`.

‎docs/inputs/search.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Search input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#search)
4+
5+
The search input allows freeform, full-text search of a tabular dataset (or a single column of values) using a simple query parser. It is often paired with a [table input](./table).
6+
7+
By default, the query is split into terms separated by spaces; each term is then prefix-matched against the property values (the fields) of each row in the data. Try searching for “gen” below to find Gentoo penguins.
8+
9+
```js echo
10+
const search = view(Inputs.search(penguins, {placeholder: "Search penguins..."}));
11+
```
12+
13+
```js echo
14+
search
15+
```
16+
17+
Or, as a table:
18+
19+
```js echo
20+
Inputs.table(search)
21+
```
22+
23+
<!-- TODO get this working or just leave as text below?
24+
```js
25+
const searchInput = Inputs.search(penguins);
26+
import {html} from "npm:htl";
27+
```
28+
29+
```js
30+
function genBisSearch() {
31+
searchInput.query = "gen bis";
32+
searchInput.dispatchEvent(new CustomEvent("input"));
33+
}
34+
35+
function genFemSearch() {
36+
searchInput.query = "gen fem";
37+
searchInput.dispatchEvent(new CustomEvent("input"));
38+
}
39+
```
40+
41+
If you search for multiple terms, such as ${html`<a style="cursor: pointer; border-bottom: dotted 1px;" onclick=${genBisSearch}>“gen bis”`} (for Gentoos on the Biscoe Islands) or ${html`<a style="cursor: pointer; border-bottom: dotted 1px;" onclick=${genFemSearch}>“gen fem”`} (for female Gentoos), every term must match: there’s an implicit logical AND. -->
42+
43+
If you search for multiple terms, such as “gen bis” (for Gentoos on the Biscoe Islands) or “gen fem” (for female Gentoos), every term must match: there’s an implicit logical AND.
44+
45+
The search input is designed to work with other [inputs](../javascript/inputs) and especially [tables](./table). You can also refer to the current search results from any cell using a [view](../javascript/inputs#view(element)). For example, to compute the median body mass of the matching penguins:
46+
47+
```js echo
48+
d3.median(search, d => d.body_mass_g)
49+
```
50+
51+
If you’d like different search syntax or behavior, pass the *filter* option. This function is passed the current search query and returns the [filter test function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) to be applied to the data.
52+
53+
## Options
54+
55+
**Inputs.search(*data*, *options*)**
56+
57+
The available search input options are:
58+
59+
* *label* - a label; either a string or an HTML element.
60+
* *query* - the initial search terms; defaults to the empty string.
61+
* *placeholder* - a placeholder string for when the query is empty.
62+
* *columns* - an array of columns to search; defaults to *data*.columns.
63+
* *locale* - the current locale; defaults to English.
64+
* *format* - a function to show the number of results.
65+
* *spellcheck* - whether to activate the browser’s spell-checker.
66+
* *autocomplete* - the [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute, as text or boolean (true for on, false for off).
67+
* *autocapitalize* - the [autocapitalize](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize) attribute, as text or boolean (true for on, false for off).
68+
* *filter* - the filter factory: a function that receives the query and returns a filter.
69+
* *width* - the width of the input (not including the label).
70+
* *datalist* - an iterable of suggested values.
71+
* *disabled* - whether input is disabled; defaults to false.
72+
* *required* - if true, the search’s value is all *data* if no query; defaults to true.
73+
74+
If a *filter* function is specified, it is invoked whenever the query changes; the function it returns is then passed each element from *data*, along with its zero-based index, and should return a truthy value if the given element matches the query. The default filter splits the current query into space-separated tokens and checks that each token matches the beginning of at least one string in the data’s columns, case-insensitive. For example, the query [hello world] will match the string “Worldwide Hello Services” but not “hello”.

‎docs/inputs/select.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Select input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#select)
4+
5+
The select input allows the user to choose from a given set of values. A select is recommended over a [radio](./radio) or [checkbox](./checkbox) input when the number of values to choose from is large — say, eight or more — to save space.
6+
7+
The default appearance of a select is a drop-down menu that allows you to choose a single value. The initial value is the first of the allowed values, but you can override this by specifying the *value* option.
8+
9+
```js
10+
const x11colors = ["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"];
11+
```
12+
13+
```js echo
14+
const color = view(Inputs.select(x11colors, {value: "steelblue", label: "Favorite color"}));
15+
```
16+
17+
```js echo
18+
color
19+
```
20+
21+
```html echo
22+
<div style="background: ${color}; width: 25px; height: 25px;">
23+
```
24+
25+
If the *multiple* option is true, the select will allow multiple values to be selected and the value of the select will be the array of selected values. The initial value is the empty array. You can choose a range of values by dragging or Shift-clicking, and select or deselect a value by Command-clicking.
26+
27+
```js echo
28+
const colors = view(Inputs.select(x11colors, {multiple: true, label: "Favorite colors"}));
29+
```
30+
31+
```js echo
32+
colors
33+
```
34+
35+
```html echo
36+
<div style="display: flex; flex-wrap: wrap; font: 13px/1 var(--sans-serif);">${colors.map(color => html`<div style="background: ${color}; padding: 0.5em;">${color}`)}
37+
```
38+
39+
The *multiple* option can also be a number indicating the desired size: the number of rows to show. If *multiple* is true, the size defaults to the number of allowed values, or ten, whichever is fewer.
40+
41+
```js echo
42+
const fewerColors = view(Inputs.select(x11colors, {multiple: 4, label: "Favorite colors"}));
43+
```
44+
45+
For single-choice selects, if you wish to allow no choice to be selected, we recommend including null as an explicit option.
46+
47+
```js echo
48+
const maybeColor = view(Inputs.select([null].concat(x11colors), {label: "Favorite color"}));
49+
```
50+
51+
```js echo
52+
maybeColor
53+
```
54+
55+
A select’s values need not be strings: they can be anything. Specify a *format* function to control how these values are presented to the reader.
56+
57+
```js echo
58+
const teams = [
59+
{name: "Lakers", location: "Los Angeles, California"},
60+
{name: "Warriors", location: "San Francisco, California"},
61+
{name: "Celtics", location: "Boston, Massachusetts"},
62+
{name: "Nets", location: "New York City, New York"},
63+
{name: "Raptors", location: "Toronto, Ontario"},
64+
];
65+
```
66+
67+
```js echo
68+
const favorite = view(Inputs.select(teams, {label: "Favorite team", format: x => x.name, value: teams.find(t => t.name === "Warriors")}));
69+
```
70+
71+
```js echo
72+
favorite
73+
```
74+
75+
If the select’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.
76+
77+
```js echo
78+
const size = view(Inputs.select(new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]), {value: 12, label: "Size"}));
79+
```
80+
81+
```js echo
82+
size
83+
```
84+
85+
Since the *format* function is passed elements from the data, it can access both the key and value from the corresponding Map entry.
86+
87+
```js echo
88+
const size2 = view(Inputs.select(new Map([["Short", 8], ["Tall", 12], ["Grande", 16], ["Venti", 20]]), {value: 12, label: "Size", format: ([name, value]) => `${name} (${value} oz)`}));
89+
```
90+
91+
```js echo
92+
size2
93+
```
94+
95+
Passing a Map to select is especially useful in conjunction with [d3.group](https://d3js.org/d3-array/group). For example, given a tabular dataset of Olympic athletes (`olympians`), we can use d3.group to group them by sport, and then the select input to select only athletes for the chosen sport.
96+
97+
```js echo
98+
const sportAthletes = view(Inputs.select(d3.group(olympians, d => d.sport), {label: "Sport"}));
99+
```
100+
101+
```js echo
102+
sportAthletes
103+
```
104+
105+
If the *sort* and *unique* options are specified, the select’s keys will be sorted and duplicate keys will be discarded, respectively.
106+
107+
```js echo
108+
const sport = view(Inputs.select(olympians.map(d => d.sport), {label: "Sport", sort: true, unique: true}));
109+
```
110+
111+
```js echo
112+
sport
113+
```
114+
115+
The *disabled* option, if true, disables the entire select. If *disabled* is an array, then only the specified values are disabled.
116+
117+
```js echo
118+
Inputs.select(["A", "E", "I", "O", "U", "Y"], {label: "Vowel", disabled: true})
119+
```
120+
121+
```js echo
122+
Inputs.select(["A", "E", "I", "O", "U", "Y"], {label: "Vowel", disabled: ["Y"]})
123+
```
124+
125+
## Options
126+
127+
**Inputs.select(*data*, *options*)**
128+
129+
The available select input options are:
130+
131+
* *label* - a label; either a string or an HTML element.
132+
* *multiple* - whether to allow multiple choice; defaults to false.
133+
* *size* - if *multiple* is true, the number of options to show.
134+
* *sort* - true, “ascending”, “descending”, or a comparator function to sort keys; defaults to false.
135+
* *unique* - true to only show unique keys; defaults to false.
136+
* *locale* - the current locale; defaults to English.
137+
* *format* - a format function; defaults to [formatLocaleAuto](https://github.com/observablehq/inputs/blob/main/README.md#inputsformatlocaleautolocale) composed with *keyof*.
138+
* *keyof* - a function to return the key for the given element in *data*.
139+
* *valueof* - a function to return the value of the given element in *data*.
140+
* *value* - the initial value, an array if multiple choice is allowed.
141+
* *width* - the width of the input (not including the label).
142+
* *disabled* - whether input is disabled, or the disabled values; defaults to false.
143+
144+
145+
146+
147+
148+

‎docs/inputs/table.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Table input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#table)
4+
5+
The table input displays tabular data. It’s fast: rows are rendered lazily on scroll. It sorts: click a header to sort, and click again to reverse. And it selects: click a checkbox on any row, and the selected rows are exported as a view value. (And for searching, see the [search input](./search).)
6+
7+
By default, all columns are visible. Only the first dozen rows are initially visible, but you can scroll to see more. Column headers are fixed for readability.
8+
9+
```js echo
10+
Inputs.table(penguins)
11+
```
12+
13+
To show a subset of columns, or to reorder them, pass an array of property names
14+
as the _columns_ option. By default, columns are inferred from _data_.columns if
15+
present, and otherwise by iterating over the data to union the property names.
16+
17+
The _header_ option lets you redefine the column title; this doesn’t change the
18+
name used to reference the data.
19+
20+
```js echo
21+
penguins.columns
22+
```
23+
24+
```js echo
25+
Inputs.table(penguins, {
26+
columns: [
27+
"species",
28+
"culmen_length_mm",
29+
"culmen_depth_mm",
30+
"flipper_length_mm"
31+
],
32+
header: {
33+
species: "Penguin Species",
34+
culmen_length_mm: "Culmen length (mm)",
35+
flipper_length_mm: "Flipper length (mm)",
36+
culmen_depth_mm: "Culmen Depth (mm)"
37+
}
38+
})
39+
```
40+
41+
By default, rows are displayed in input order. You can change the order by
42+
specifying the name of a column to _sort_ by, and optionally the _reverse_
43+
option for descending order. The male Gentoo penguins are the largest in this
44+
dataset, for example. Undefined values go to the end.
45+
46+
```js echo
47+
Inputs.table(penguins, {sort: "body_mass_g", reverse: true})
48+
```
49+
50+
Tables are [view-compatible](../javascript/inputs#view(element)): using the
51+
view function, you can use a table to select rows and refer to them from other
52+
cells, say to chart a subset of the data. Click the checkbox on the left edge of
53+
a row to select it, and click the checkbox in the header row to clear the
54+
selection. You can shift-click to select a range of rows.
55+
56+
```js echo
57+
const selection = view(Inputs.table(penguins, {required: false}));
58+
```
59+
60+
```js echo
61+
selection // Try selecting rows above!
62+
```
63+
64+
The _required_ option determines the selection when no items are selected from
65+
the table. If it is true (default), the selection contains the full dataset.
66+
Otherwise, the selection is empty.
67+
68+
The table input assumes that all values in a given column are the same type,
69+
and chooses a suitable formatter based on the first non-null value in each
70+
column.
71+
72+
- Numbers are formatted using
73+
[_number_.toLocaleString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString);
74+
- Dates are formatted in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601);
75+
- Undefined and NaN values are empty;
76+
- Everything else is displayed as-is.
77+
78+
To override the default formatting, pass _format_ options for the desired
79+
columns.
80+
81+
```js echo
82+
Inputs.table(penguins, {
83+
format: {
84+
culmen_length_mm: x => x.toFixed(1),
85+
culmen_depth_mm: x => x.toFixed(1),
86+
sex: x => x === "MALE" ? "M" : x === "FEMALE" ? "F" : ""
87+
}
88+
})
89+
```
90+
91+
The _format_ function can return a text string or an HTML element.
92+
For example, this can be used to render inline visualizations such as bars or [sparklines](https://observablehq.com/@mbostock/covid-cases-by-state).
93+
94+
```js echo
95+
Inputs.table(penguins, {
96+
format: {
97+
culmen_length_mm: sparkbar(d3.max(penguins, d => d.culmen_length_mm)),
98+
culmen_depth_mm: sparkbar(d3.max(penguins, d => d.culmen_depth_mm)),
99+
flipper_length_mm: sparkbar(d3.max(penguins, d => d.flipper_length_mm)),
100+
body_mass_g: sparkbar(d3.max(penguins, d => d.body_mass_g)),
101+
sex: x => x.toLowerCase()
102+
}
103+
})
104+
```
105+
106+
```js echo
107+
function sparkbar(max) {
108+
return x => htl.html`<div style="
109+
background: lightblue;
110+
width: ${100 * x / max}%;
111+
float: right;
112+
padding-right: 3px;
113+
box-sizing: border-box;
114+
overflow: visible;
115+
display: flex;
116+
justify-content: end;">${x.toLocaleString("en")}`
117+
}
118+
```
119+
120+
There’s a similar _width_ option if you want to give certain columns specific
121+
widths. The table input defaults to a fixed _layout_ if there are twelve or
122+
fewer columns; this improves performance and avoids reflow when scrolling.
123+
124+
You can switch _layout_ to auto if you prefer sizing columns based on content.
125+
This makes the columns widths resize with the data, which can cause the columns
126+
to jump around as the user scrolls. A horizontal scroll bar is added if the
127+
total width exceeds the table’s width.
128+
129+
Set _layout_ to fixed to pack all the columns into the width of the table.
130+
131+
The table’s width can be controlled by the _width_ option, in pixels. Individual
132+
column widths can alternatively be defined by specifying an object with column
133+
names as keys, and widths as values. Use the _maxWidth_ option if the sum of
134+
column widths exceeds the desired table’s width.
135+
136+
The _align_ option allows to change the text-alignment of cells, which can be
137+
right, left, or center; it defaults to right for numeric columns, and left for
138+
everything else.
139+
140+
The _rows_ option indicates the number of rows to display; it defaults to 11.5.
141+
The _height_ and _maxHeight_ options respectively set the height and maximum
142+
height of the table, in pixels. The height defaults to (rows + 1) \* 22 - 1.
143+
144+
```js echo
145+
Inputs.table(penguins, {
146+
width: {
147+
culmen_length_mm: 140,
148+
culmen_depth_mm: 140,
149+
flipper_length_mm: 140
150+
},
151+
align: {
152+
culmen_length_mm: "right",
153+
culmen_depth_mm: "center",
154+
flipper_length_mm: "left"
155+
},
156+
rows: 18,
157+
maxWidth: 840,
158+
multiple: false,
159+
layout: "fixed"
160+
})
161+
```
162+
163+
You can preselect some rows in the table by setting the _value_ option to an
164+
array of references to the actual objects in your data.
165+
166+
For example, to preselect the first two items, you could write:
167+
168+
```js echo run=false
169+
{ value: penguins.slice(0, 2)}
170+
```
171+
172+
or, if you just want to preselect the rows 1, 3, 7 and 9:
173+
174+
```js echo run=false
175+
{ value: penguins.filter((_,i)=> [1, 3, 7, 9].includes(i))}
176+
```
177+
178+
The _multiple_ option allows multiple rows to be selected (defaults to true).
179+
When false, only one row can be selected. To set the initial value in that case,
180+
just reference the preselected object:
181+
182+
```js echo run=false
183+
{ multiple: false, value: penguins[4] }
184+
```
185+
186+
```js echo
187+
Inputs.table(penguins, {
188+
value: penguins.filter((_, i) => [1, 3, 7, 9].includes(i)),
189+
multiple: true
190+
})
191+
```
192+
193+
Thanks to [Ilyá Belsky](https://observablehq.com/@oluckyman) and [Brett Cooper](https://observablehq.com/@hellonearthis) for suggestions.
194+
195+
## Options
196+
197+
**Inputs.table(*data*, *options*)**
198+
199+
The available table input options are:
200+
201+
* *columns* - the columns (property names) to show; defaults to *data*.columns.
202+
* *value* - a subset of *data* to use as the initial selection (checked rows), or a *data* item if *multiple* is false.
203+
* *rows* - the maximum number of rows to show; defaults to 11.5.
204+
* *sort* - the column to sort by; defaults to null (input order).
205+
* *reverse* - whether to reverse the initial sort (descending instead of ascending).
206+
* *format* - an object of column name to format function.
207+
* *align* - an object of column name to “left”, “right”, or “center”.
208+
* *header* - an object of column name to corresponding header; either a string or HTML element.
209+
* *width* - the table width, or an object of column name to width.
210+
* *maxWidth* - the maximum table width, if any.
211+
* *height* - the fixed table height, if any.
212+
* *maxHeight* - the maximum table height, if any; defaults to (*rows* + 1) * 22 - 1.
213+
* *layout* - the [table layout](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout); defaults to fixed for ≤12 columns.
214+
* *required* - if true, the table’s value is all *data* if no selection; defaults to true.
215+
* *multiple* - if true, allow multiple rows to be selected; defaults to true.
216+
217+
If *width* is “auto”, the table width will be based on the table contents; note that this may cause the table to resize as rows are lazily rendered.

‎docs/inputs/text.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Text input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#text)
4+
5+
The text input allows freeform single-line text entry. (For multiple lines, see the [text area](./textarea)) input.
6+
7+
In its most basic form, a text input is a blank box whose value is the empty string. The text’s value changes as the user types into the box.
8+
9+
```js echo
10+
const text = view(Inputs.text());
11+
```
12+
13+
```js echo
14+
text
15+
```
16+
17+
We recommend providing a *label* and *placeholder* to improve usability. You can also supply an initial *value* if desired.
18+
19+
```js echo
20+
const name = view(Inputs.text({label: "Name", placeholder: "Enter your name", value: "Anonymous"}));
21+
```
22+
23+
```js echo
24+
name
25+
```
26+
27+
The *label* may be either a text string or an HTML element, if more control over styling is desired.
28+
29+
```js echo
30+
const signature = view(Inputs.text({label: html`<b>Fancy</b>`, placeholder: "What’s your fancy?"}));
31+
```
32+
33+
For specific classes of text, such as email addresses and telephone numbers, you can supply one of the [HTML5 input types](https://developer.mozilla.org/en-US/docs/Learn/Forms/HTML5_input_types), such as email, tel (for a telephone number), or url, as the *type* option. Or, use a convenience method: Inputs.email, Inputs.password, Inputs.tel, or Inputs.url.
34+
35+
```js echo
36+
const password = view(Inputs.password({label: "Password", value: "open sesame"}));
37+
```
38+
39+
```js echo
40+
password
41+
```
42+
43+
The HTML5 *pattern*, *spellcheck*, *minlength*, and *maxlength* options are also supported. If the user enters invalid input, the browser may display a warning (e.g., “Enter an email address”). You can also check whether the current value is valid by calling [*form*.checkValidity](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity).
44+
45+
```js echo
46+
const email = view(Inputs.text({type: "email", label: "Email", placeholder: "Enter your email"}));
47+
```
48+
49+
<!-- [TODO] fix below (had viewof operator before email.checkValidity(), not sure how to update)
50+
51+
```js echo
52+
[email,email.checkValidity()]
53+
```
54+
55+
-->
56+
57+
If the input will trigger some expensive calculation, such as fetching from a remote server, the *submit* option can be used to defer the text input from reporting the new value until the user clicks the Submit button or hits Enter. The value of *submit* can also be the desired contents of the submit button (a string or HTML).
58+
59+
```js echo
60+
const query = view(Inputs.text({label: "Query", placeholder: "Search", submit: true}));
61+
```
62+
63+
```js echo
64+
query
65+
```
66+
67+
To provide a recommended set of values, pass an array of strings as the *datalist* option. For example, the input below is intended to accept the name of a U.S. state; you can either type the state name by hand or click one of the suggestions on focus.
68+
69+
```js echo
70+
const capitals = FileAttachment("us-state-capitals.tsv").tsv({typed: true});
71+
```
72+
73+
```js echo
74+
const state = view(Inputs.text({
75+
label: "U.S. state",
76+
placeholder: "Enter state name",
77+
datalist: capitals.map(d => d.State)
78+
}));
79+
```
80+
81+
```js echo
82+
state
83+
```
84+
85+
To prevent a text input’s value from being changed, use the *disabled* option.
86+
87+
```js echo
88+
const fixed = view(Inputs.text({label: "Fixed value", value: "Can’t edit me!", disabled: true}));
89+
```
90+
91+
```js echo
92+
fixed
93+
```
94+
95+
## Options
96+
97+
**Inputs.text(*options*)**
98+
99+
The available text input options are:
100+
101+
* *label* - a label; either a string or an HTML element.
102+
* *type* - the [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types), such as “password” or “email”; defaults to “text”.
103+
* *value* - the initial value; defaults to the empty string.
104+
* *placeholder* - the [placeholder](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/placeholder) attribute.
105+
* *spellcheck* - whether to activate the browser’s spell-checker.
106+
* *autocomplete* - the [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute, as text or boolean (true for on, false for off).
107+
* *autocapitalize* - the [autocapitalize](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize) attribute, as text or boolean (true for on, false for off).
108+
* *pattern* - the [pattern](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern) attribute.
109+
* *minlength* - [minimum length](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/minlength) attribute.
110+
* *maxlength* - [maximum length](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength) attribute.
111+
* *min* - [minimum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min) attribute; formatted appropriately, *e.g.* yyyy-mm-dd for the date type.
112+
* *max* - [maximum value](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max) attribute.
113+
* *required* - if true, the input must be non-empty; defaults to *minlength* > 0.
114+
* *validate* - a function to check whether the text input is valid.
115+
* *width* - the width of the input (not including the label).
116+
* *submit* - whether to require explicit submission before updating; defaults to false.
117+
* *datalist* - an iterable of suggested values.
118+
* *readonly* - whether input is readonly; defaults to false.
119+
* *disabled* - whether input is disabled; defaults to false.
120+
121+
If *validate* is not defined, [*text*.checkValidity](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity) is used. While the input is not considered valid, changes to the input will not be reported.

‎docs/inputs/textarea.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Text area input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#textarea)
4+
5+
The textarea input allows freeform multi-line text entry. (For a single line, see the [text](./text) input).
6+
7+
In its most basic form, a textarea is a blank box whose value is the empty string. The textarea’s value changes as the user types into the box.
8+
9+
```js echo
10+
const text = view(Inputs.textarea());
11+
```
12+
13+
```js echo
14+
text
15+
```
16+
17+
We recommend providing a *label* and *placeholder* to improve usability. You can also supply an initial *value* if desired. The *label* may be either a text string or an HTML element, if more control over styling is desired.
18+
19+
```js echo
20+
const bio = view(Inputs.textarea({label: "Biography", placeholder: "What’s your story?"}));
21+
```
22+
23+
```js echo
24+
bio
25+
```
26+
27+
If the input will trigger some expensive calculation, such as fetching from a remote server, the *submit* option can be used to defer the textarea from reporting the new value until the user clicks the Submit button or hits Command-Enter. The value of *submit* can also be the desired contents of the submit button (a string or HTML).
28+
29+
```js echo
30+
const essay = view(Inputs.textarea({label: "Essay", rows: 6, minlength: 40, submit: true}));
31+
```
32+
33+
```js echo
34+
essay
35+
```
36+
37+
The HTML5 *spellcheck*, *minlength*, and *maxlength* options are supported. If the user enters invalid input, the browser may display a warning (e.g., “Use at least 80 characters”). You can also check whether the current value is valid by calling [*form*.checkValidity](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity).
38+
39+
To prevent a textarea’s value from being changed, use the *disabled* option.
40+
41+
```js echo
42+
const fixed = view(Inputs.textarea({label: "Fixed value", value: "Can’t edit me!", disabled: true}));
43+
```
44+
45+
```js echo
46+
fixed
47+
```
48+
49+
## Options
50+
51+
**Inputs.textarea(*options*)**
52+
53+
The available text area options are:
54+
55+
* *label* - a label; either a string or an HTML element.
56+
* *value* - the initial value; defaults to the empty string.
57+
* *placeholder* - the [placeholder](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/placeholder) attribute.
58+
* *spellcheck* - whether to activate the browser’s spell-checker.
59+
* *autocomplete* - the [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute, as text or boolean (true for on, false for off).
60+
* *autocapitalize* - the [autocapitalize](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize) attribute, as text or boolean (true for on, false for off).
61+
* *minlength* - [minimum length](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/minlength) attribute.
62+
* *maxlength* - [maximum length](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength) attribute.
63+
* *required* - if true, the input must be non-empty; defaults to *minlength* > 0.
64+
* *validate* - a function to check whether the text input is valid.
65+
* *width* - the width of the input (not including the label).
66+
* *rows* - the number of rows of text to show.
67+
* *resize* - if true, allow vertical resizing; defaults to *rows* < 12.
68+
* *submit* - whether to require explicit submission before updating; defaults to false.
69+
* *readonly* - whether input is readonly; defaults to false.
70+
* *disabled* - whether input is disabled; defaults to false.
71+
* *monospace* - if true, use a monospace font.
72+
73+
If *validate* is not defined, [*text*.checkValidity](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity) is used. While the input is not considered valid, changes to the input will not be reported.

‎docs/inputs/toggle.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Toggle input
2+
3+
[API Reference ›](https://github.com/observablehq/inputs/blob/main/README.md#toggle)
4+
5+
The toggle input allows the user to choose one of two values, representing on or off. It is a specialized form of the [checkbox input](./checkbox).
6+
7+
The initial value of a toggle defaults to false. You can override this by specifying the *value* option.
8+
9+
```js echo
10+
const mute = view(Inputs.toggle({label: "Mute", value: true}));
11+
```
12+
13+
```js echo
14+
mute
15+
```
16+
17+
The on and off values of a toggle can be changed with the *values* option which defaults to [true, false].
18+
19+
```js echo
20+
const binary = view(Inputs.toggle({label: "Binary", values: [1, 0]}));
21+
```
22+
23+
```js echo
24+
binary
25+
```
26+
27+
The *label* can be either a text string or an HTML element. This allows more control over the label’s appearance, if desired.
28+
29+
```js echo
30+
const fancy = view(Inputs.toggle({label: html`<b>Fancy</b>`}));
31+
```
32+
33+
```js echo
34+
fancy
35+
```
36+
37+
A toggle can be disabled to prevent its value from being changed.
38+
39+
```js echo
40+
const frozen = view(Inputs.toggle({label: "Frozen", value: true, disabled: true}));
41+
```
42+
43+
```js echo
44+
frozen
45+
```
46+
47+
## Options
48+
49+
**Inputs.toggle(*options*)**
50+
51+
The available toggle input options are:
52+
53+
* *label* - a label; either a string or an HTML element.
54+
* *values* - the two values to toggle between; defaults to [true, false].
55+
* *value* - the initial value; defaults to the second value (false).
56+
* *disabled* - whether input is disabled; defaults to false.

‎docs/inputs/us-state-capitals.tsv

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
State Capital Since Area Population MSA/µSA CSA Rank
2+
Alabama Montgomery 1846-01-01 159.8 198525 373290 461516 3
3+
Alaska Juneau 1906-01-01 2716.7 32113 32113 3
4+
Arizona Phoenix 1912-01-01 517.6 1680992 4948203 5002221 1
5+
Arkansas Little Rock 1821-01-01 116.2 197312 742384 908941 1
6+
California Sacramento 1854-01-01 97.9 513624 2363730 2639124 6
7+
Colorado Denver 1867-01-01 153.3 727211 2967239 3617927 1
8+
Connecticut Hartford 1875-01-01 17.3 122105 1204877 1470083 3
9+
Delaware Dover 1777-01-01 22.4 38079 180786 7209620 2
10+
Florida Tallahassee 1824-01-01 95.7 194500 387227 7
11+
Georgia Atlanta 1868-01-01 133.5 506811 6020364 6853392 1
12+
Hawaii Honolulu 1845-01-01 68.4 345064 974563 1
13+
Idaho Boise 1865-01-01 63.8 228959 749202 831235 1
14+
Illinois Springfield 1837-01-01 54.0 114230 206868 306399 6
15+
Indiana Indianapolis 1825-01-01 361.5 876384 2074537 2457286 1
16+
Iowa Des Moines 1857-01-01 75.8 214237 699292 877991 1
17+
Kansas Topeka 1856-01-01 56.0 125310 231969 4
18+
Kentucky Frankfort 1792-01-01 14.7 27679 73663 745033 15
19+
Louisiana Baton Rouge 1880-01-01 76.8 220236 854884 2
20+
Maine Augusta 1832-01-01 55.4 18681 122302 8
21+
Maryland Annapolis 1694-01-01 6.73 39174 2800053 9814928 7
22+
Massachusetts Boston 1630-01-01 89.6 692600 4873019 8287710 1
23+
Michigan Lansing 1847-01-01 35.0 118210 550391 5
24+
Minnesota Saint Paul 1849-01-01 52.8 308096 3654908 4027861 2
25+
Mississippi Jackson 1821-01-01 104.9 160628 594806 674340 1
26+
Missouri Jefferson City 1826-01-01 27.3 42838 151235 15
27+
Montana Helena 1875-01-01 14.0 32315 77414 6
28+
Nebraska Lincoln 1867-01-01 74.6 289102 336374 357887 2
29+
Nevada Carson City 1861-01-01 143.4 55916 55916 637973 6
30+
New Hampshire Concord 1808-01-01 64.3 43627 151391 8287710 3
31+
New Jersey Trenton 1784-01-01 7.66 83203 367430 22589036 10
32+
New Mexico Santa Fe 1610-01-01 37.3 84683 150358 1158464 4
33+
New York Albany 1797-01-01 21.4 96460 880381 1167594 6
34+
North Carolina Raleigh 1792-01-01 114.6 474069 1390785 2079687 2
35+
North Dakota Bismarck 1883-01-01 26.9 73529 128949 2
36+
Ohio Columbus 1816-01-01 210.3 898553 2122271 2525639 1
37+
Oklahoma Oklahoma City 1910-01-01 620.3 655057 1408950 1481542 1
38+
Oregon Salem 1855-01-01 45.7 174365 433903 3259710 3
39+
Pennsylvania Harrisburg 1812-01-01 8.11 49528 577941 1271801 9
40+
Rhode Island Providence 1900-01-01 18.5 179883 1624578 8287710 1
41+
South Carolina Columbia 1786-01-01 125.2 131674 838433 963048 2
42+
South Dakota Pierre 1889-01-01 13.0 13646 20672 8
43+
Tennessee Nashville 1826-01-01 525.9 670820 1934317 2062547 1
44+
Texas Austin 1839-01-01 305.1 978908 2227083 4
45+
Utah Salt Lake City 1858-01-01 109.1 200567 1232696 2641048 1
46+
Vermont Montpelier 1805-01-01 10.2 7855 6
47+
Virginia Richmond 1780-01-01 60.1 230436 1291900 4
48+
Washington Olympia 1853-01-01 16.7 46478 290536 4903675 24
49+
West Virginia Charleston 1885-01-01 31.6 46536 257074 776694 1
50+
Wisconsin Madison 1838-01-01 68.7 259680 664865 892661 2
51+
Wyoming Cheyenne 1869-01-01 21.1 64235 99500 1

‎docs/javascript/athletes.csv

Lines changed: 11539 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/javascript/display.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,4 @@ Inputs.button("Click me", {value: 0, reduce: (i) => displayThere(++i)})
134134

135135
## view(*element*)
136136

137-
The `view` function displays the given DOM *element* (typically an input) and then returns its corresponding value [generator](./generators) via [`Generators.input`](../lib/generators#input(element)). Use this to display an input while also exposing the input’s value as a [reactive variable](./reactivity). For example, here is a simple HTML slider:
138-
139-
```js echo
140-
const gain = view(html`<input type=range step=0.1 min=0 max=11 value=5>`);
141-
```
142-
143-
Now you can reference the input’s value (here `gain`) anywhere. The code will run whenever the input changes; no event listeners required!
144-
145-
```md
146-
The current gain is ${gain}!
147-
```
148-
149-
The current gain is ${gain}!
137+
The [`view` function](./inputs#viewelement) is a special type of display function that inserts the given DOM *element* (typically an input), then returns its corresponding value [generator](./generators) via [`Generators.input`](../lib/generators#input(element)). When the user interacts with the input, this triggers the [reactive evaluation](reactivity) of all the JavaScript code that reference this value.

‎docs/javascript/inputs.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# JavaScript: Inputs
2+
3+
Inputs are user-interface elements that accept data from a user. In a data app, inputs might prompt a viewer to:
4+
5+
- Select a URL from a dropdown list to view site traffic for a specific page
6+
- Interactively subset a table of users by typing in a domain name
7+
- Choose a date range to explore software downloads over a period of interest
8+
9+
Inputs can be displayed with the [`view`](#view(element)) function, which is a special type of [display](display) function that additionally returns the input’s value generator, which can then be assigned to a variable for use elsewhere.
10+
11+
For example, the radio input below prompts a user to select one from a series of values:
12+
13+
```js echo
14+
const team = view(Inputs.radio(["Metropolis Meteors", "Rockford Peaches", "Bears"], {label: "Favorite team:", value: "Metropolis Meteors"}));
15+
```
16+
17+
The `team` variable in this example now reactively updates when the user interacts with the radio input, triggering a new evaluation of the dependent code. Select different teams in the radio input above to update the text.
18+
19+
```md
20+
My favorite baseball team is the ${team}!
21+
```
22+
23+
My favorite baseball team is the ${team}!
24+
25+
## view(*element*)
26+
27+
The `view` function used above does two things: (1) it displays the given DOM *element*, then (2) returns its corresponding value [generator](./generators), using [`Generators.input`](../lib/generators#input(element)) under the hood. Use `view` to display an input while also exposing the input’s value as a [reactive variable](./reactivity). You can reference the input’s value anywhere, and the code will run whenever the input changes.
28+
29+
The `view` function is not limited to Observable Inputs. For example, here is a simple range slider created with [html](../lib/htl):
30+
31+
```js echo
32+
const topn = view(html`<input type=range step=1 min=1 max=15 value=10>`);
33+
```
34+
35+
Now we can reference `topn` elsewhere, for example to control how many groups are displayed in a sorted bar chart:
36+
37+
```js echo
38+
Plot.plot({
39+
marginLeft: 50,
40+
marks: [
41+
Plot.barX(olympians, Plot.groupY({x: "count"}, {y: "nationality", sort: {y: "x", reverse: true, limit: topn}})),
42+
Plot.ruleX([0])
43+
]
44+
})
45+
```
46+
47+
## Observable Inputs
48+
49+
The [Observable Inputs](../lib/inputs) library implements commonly used inputs — buttons, sliders, dropdowns, tables, and the like — as functions. Each input function returns an HTML element that emits *input* events for compatibility with [`view`](#view(element)) and [Generators.input](../lib/generators#input(element)).
50+
51+
### Usage
52+
53+
Inputs are generally declared as follows:
54+
55+
```js run=false
56+
const value = view(Inputs.inputName(...));
57+
```
58+
59+
where *value* is the named input value, and *inputName* is the input name (like `radio`, `button`, or `checkbox`). See the full list of [available inputs](../lib/inputs) with live examples, or visit the [Observable Inputs API reference](https://github.com/observablehq/inputs/blob/main/README.md) for more detail and specific input options.
60+
61+
Options differ between inputs. For example, the checkbox input accepts options to disable all or certain values, sort displayed values, and only display repeated values *once* (among others):
62+
63+
```js echo
64+
const checkout = view(Inputs.checkbox(["B","A","Z","Z","F","D","G","G","G","Q"], {disabled: ["F", "Q"], sort: true, unique: true, value: "B", label: "Choose categories:"}));
65+
```
66+
67+
```js echo
68+
checkout
69+
```
70+
71+
### Analysis with Observable Inputs
72+
73+
To demonstrate Observable Inputs for data analysis, we’ll use the `olympians` sample dataset containing records on athletes that participated in the 2016 Rio olympics (from [Matt Riggott](https://flother.is/2017/olympic-games-data/)).
74+
75+
```js echo
76+
Inputs.table(olympians)
77+
```
78+
79+
Here, we create a subset of columns to simplify outputs:
80+
81+
```js echo
82+
const columns = olympians.columns.slice(1, -1); // hide the id and info column to simplify
83+
```
84+
85+
Now let’s wire up our data to a search input. Type whatever you want into the box and search will find matching rows in the data which we can then use in a table below.
86+
87+
A few examples to try: **[mal]** will match *sex* = male, but also names that start with “mal”, such as Anna Malova; **[1986]** will match anyone born in 1986 (and a few other results); **[USA gym]** will match USA’s gymnastics team. Each space-separated term in your query is prefix-matched against all columns in the data.
88+
89+
```js echo
90+
const search = view(Inputs.search(olympians, {
91+
datalist: ["mal", "1986", "USA gym"],
92+
placeholder: "Search athletes"
93+
}))
94+
```
95+
96+
```js echo
97+
Inputs.table(search, {columns})
98+
```
99+
100+
If you like, you can sort the table columns by clicking on the column name. Click once to sort ascending, and click again to sort descending. Note that the sort order is temporary: it’ll go away if you reload the page. Specify the column name as the *sort* option above if you want this order to persist.
101+
102+
For a more structured approach, we can use a select input to choose a sport, then *array*.filter to determine which rows are shown in the table. The *sort* and *unique* options tell the input to show only distinct values and to sort them alphabetically.
103+
104+
```js echo
105+
const sport = view(Inputs.select(olympians.map(d => d.sport), {sort: true, unique: true, label: "sport"}));
106+
```
107+
108+
```js echo
109+
const selectedAthletes = display(olympians.filter(d => d.sport === sport));
110+
```
111+
112+
```js echo
113+
Inputs.table(selectedAthletes, {columns})
114+
```
115+
116+
To visualize a column of data as a histogram, use the value of the select input with [Observable Plot](https://observablehq.com/plot/).
117+
118+
```js echo
119+
Plot.plot({
120+
x: {
121+
domain: [1.3, 2.2]
122+
},
123+
marks: [
124+
Plot.rectY(selectedAthletes, Plot.binX({y: "count"}, {x: "height", fill: "steelblue"})),
125+
Plot.ruleY([0])
126+
]
127+
})
128+
```
129+
130+
You can also pass grouped data to the select input as a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) from key to array of values, say using [d3.group](https://d3js.org/d3-array/group). The value of the select input in this mode is the data in the selected group. Note that *unique* is no longer required, and that *sort* works here, too, sorting the keys of the map returned by d3.group.
131+
132+
```js echo
133+
const groups = display(d3.group(olympians, d => d.sport));
134+
```
135+
136+
```js echo
137+
const sportAthletes = view(Inputs.select(groups, {sort: true, label: "sport"}));
138+
```
139+
140+
```js echo
141+
Inputs.table(sportAthletes, {columns})
142+
```
143+
144+
The select input works well for categorical data, such as sports or nationalities, but how about quantitative dimensions such as height or weight? Here’s a range input that lets you pick a target weight; we then filter the table rows for any athlete within 10% of the target weight. Notice that some columns, such as sport, are strongly correlated with weight.
145+
146+
```js echo
147+
const weight = view(Inputs.range(d3.extent(olympians, d => d.weight), {step: 1, label: "weight (kg)"}));
148+
```
149+
150+
```js echo
151+
Inputs.table(olympians.filter(d => d.weight < weight * 1.1 && weight * 0.9 < d.weight), {sort: "weight", columns})
152+
```
153+
154+
### License
155+
156+
Observable Inputs are released under the [ISC license](https://github.com/observablehq/inputs/blob/main/LICENSE) and depend only on [Hypertext Literal](../lib/htl), our tagged template literal for safely generating dynamic HTML. If you are interested in contributing or wish to report an issue, please see [our repository](https://github.com/observablehq/inputs). For recent changes, please see our [release notes](https://github.com/observablehq/inputs/releases).

‎docs/layout/card.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Layout: card
2+
3+
The `card` class adds polished styling to page content including a background and border color determined by the page theme, default padding, and an optional title and subtitle that match title styling for other components.
4+
5+
<div class="card">
6+
<h2>This is a card</h2>
7+
<h3>Cards add polished formatting to page content.</h3>
8+
${
9+
Plot.plot({
10+
marks: [
11+
Plot.cell(weather, {x: d => d.date.getUTCDate(), y: d => d.date.getUTCMonth(),fill: "temp_max"})
12+
]
13+
})
14+
}
15+
</div>
16+
17+
```html run=false
18+
<div class="card">
19+
<h2>This is a card</h2>
20+
<h3>Cards add polished formatting to page content.</h3>
21+
${
22+
Plot.plot({
23+
marks: [
24+
Plot.cell(weather, {x: d => d.date.getUTCDate(), y: d => d.date.getUTCMonth(),fill: "temp_max"})
25+
]
26+
})
27+
}
28+
</div>
29+
```
30+
31+
## Card content
32+
33+
Cards can contain whatever content you like, including text, images, charts, tables, inputs, and more.
34+
35+
<div class="grid grid-cols-2">
36+
<div class="card">
37+
<h2>Lorem ipsum</h2>
38+
<p>Id ornare arcu odio ut sem nulla pharetra. Aliquet lectus proin nibh nisl condimentum id venenatis a. Feugiat sed lectus vestibulum mattis ullamcorper velit. Aliquet nec ullamcorper sit amet. Sit amet tellus cras adipiscing. Condimentum id venenatis a condimentum vitae. Semper eget duis at tellus. Ut faucibus pulvinar elementum integer enim.</p>
39+
<p>Et malesuada fames ac turpis. Integer vitae justo eget magna fermentum iaculis eu non diam. Aliquet risus feugiat in ante metus dictum at. Consectetur purus ut faucibus pulvinar.</p>
40+
</div>
41+
<div class="card">
42+
<img src="../javascript/horse.jpg" width="100%">
43+
</div>
44+
<div class="card">
45+
${
46+
Plot.plot({
47+
marks: [
48+
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"})),
49+
Plot.ruleY([0])
50+
]
51+
})
52+
}
53+
</div>
54+
<div class="card">
55+
${pickIndustry}
56+
${Inputs.table(industries.filter(d => d.industry === industryInput))}
57+
</div>
58+
</div>
59+
60+
```js echo
61+
const pickIndustry = Inputs.select(industries.map(d => d.industry), {unique: true, label: "Industry:"});
62+
const industryInput = view(pickIndustry)
63+
```
64+
65+
```html run=false
66+
<div class="grid grid-cols-2">
67+
<div class="card">
68+
<h2>Lorem ipsum</h2>
69+
<p>Id ornare arcu odio ut sem nulla pharetra. Aliquet lectus proin nibh nisl condimentum id venenatis a. Feugiat sed lectus vestibulum mattis ullamcorper velit. Aliquet nec ullamcorper sit amet. Sit amet tellus cras adipiscing. Condimentum id venenatis a condimentum vitae. Semper eget duis at tellus. Ut faucibus pulvinar elementum integer enim.</p>
70+
<p>Et malesuada fames ac turpis. Integer vitae justo eget magna fermentum iaculis eu non diam. Aliquet risus feugiat in ante metus dictum at. Consectetur purus ut faucibus pulvinar.</p>
71+
</div>
72+
<div class="card">
73+
<img src="../javascript/horse.jpg" width="100%">
74+
</div>
75+
<div class="card">
76+
${
77+
Plot.plot({
78+
marks: [
79+
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight"})),
80+
Plot.ruleY([0])
81+
]
82+
})
83+
}
84+
</div>
85+
<div class="card">
86+
${pickIndustry}
87+
${Inputs.table(industries.filter(d => d.industry === industryInput))}
88+
</div>
89+
</div>
90+
```
91+
92+
## Title and subtitle
93+
94+
Card titles and subtitles are added with h2 and h3 headers, respectively, and match the title styling in [Observable Plot](https://observablehq.com/plot/features/plots#other-options):
95+
96+
<div class="grid grid-cols-2"">
97+
<div class="card">
98+
<h2>A card title added as an h2 element</h2>
99+
<h3>A card subtitle added as an h3 element</h3>
100+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
101+
<p>Massa sapien faucibus et molestie ac feugiat sed lectus. Sit amet volutpat consequat mauris nunc congue nisi. Maecenas pharetra convallis posuere morbi leo urna molestie at.</p>
102+
</div>
103+
<div class="card">
104+
${
105+
Plot.plot({
106+
title: "A chart title added in Observable Plot",
107+
subtitle: "A chart subtitle added in Observable Plot",
108+
marks: [
109+
Plot.areaY(industries, {x: "date", y: "unemployed", fill: "industry"}),
110+
]
111+
})
112+
}
113+
</div>
114+
</div>
115+
116+
```html run=false
117+
<div class="grid grid-cols-2"">
118+
<div class="card">
119+
<h2>A card title added as an h2 element</h2>
120+
<h3>A card subtitle added as an h3 element</h3>
121+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
122+
<p>Massa sapien faucibus et molestie ac feugiat sed lectus. Sit amet volutpat consequat mauris nunc congue nisi. Maecenas pharetra convallis posuere morbi leo urna molestie at.</p>
123+
</div>
124+
<div class="card">
125+
${
126+
Plot.plot({
127+
title: "A chart title added in Observable Plot",
128+
subtitle: "A chart subtitle added in Observable Plot",
129+
marks: [
130+
Plot.areaY(industries, {x: "date", y: "unemployed", fill: "industry"}),
131+
]
132+
})
133+
}
134+
</div>
135+
</div>
136+
```
137+
138+
## Content without cards
139+
140+
Page content does not have to be within a card. Below, explanatory text is added to the right of a card.
141+
142+
<div class="grid grid-cols-2"">
143+
<div class="card">
144+
${
145+
Plot.plot({
146+
color: {legend: true},
147+
marks: [
148+
Plot.dot(penguins, {x: "body_mass_g", y: "flipper_length_mm", fill: "species", tip: true})
149+
]
150+
})
151+
}
152+
</div>
153+
<div>
154+
<p>Body mass (g) and flipper length (mm) for ${penguins.length} individual penguins (${penguins.filter(d => d.species === "Adelie").length} Adélie, ${penguins.filter(d => d.species === "Chinstrap").length} Chinstrap, and ${penguins.filter(d => d.species === "Gentoo").length} Gentoo) recorded on Dream, Biscoe, or Torgersen islands near Palmer Archipelago, Antarctica from 2007 — 2009. Data: <a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0090081">K. B. Gorman et al. 2014.</a> </p>
155+
</div>
156+
</div>
157+
158+
```html run=false
159+
<div class="grid grid-cols-2"">
160+
<div class="card">
161+
${
162+
Plot.plot({
163+
color: {legend: true},
164+
marks: [
165+
Plot.dot(penguins, {x: "body_mass_g", y: "flipper_length_mm", fill: "species", tip: true})
166+
]
167+
})
168+
}
169+
</div>
170+
<div>
171+
<p>Body mass (g) and flipper length (mm) for ${penguins.length} individual penguins (${penguins.filter(d => d.species === "Adelie").length} Adélie, ${penguins.filter(d => d.species === "Chinstrap").length} Chinstrap, and ${penguins.filter(d => d.species === "Gentoo").length} Gentoo) recorded on Dream, Biscoe, or Torgersen islands near Palmer Archipelago, Antarctica from 2007 — 2009. Data: <a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0090081">K. B. Gorman et al. 2014.</a> </p>
172+
</div>
173+
</div>
174+
```

‎docs/layout/grid.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
toc: false
3+
theme: dashboard
4+
---
5+
6+
# Layout: grid
7+
8+
<!-- TODO update to point to themes preview page when merged-->
9+
The CLI provides a set of grid CSS classes to help layout page content. These grids pair well with the [dashboard theme](../config#theme) and the [card](./card) class. This page uses both.
10+
11+
To see these examples change dynamically, adjust the page width or collapse the sidebar on the left.
12+
13+
## Classes
14+
15+
The following CSS classes are provided for grid layouts:
16+
17+
Class | Description
18+
---------------- | ------------
19+
`grid` | specify a grid layout for an element
20+
`grid-cols-2` | restrict grid to two columns
21+
`grid-cols-3` | restrict grid to three columns
22+
`grid-cols-4` | restrict grid to four columns
23+
`grid-colspan-2` | specify that a grid cell spans two columns
24+
`grid-colspan-3` | specify that a grid cell spans three columns
25+
`grid-colspan-4` | specify that a grid cell spans four columns
26+
`grid-rowspan-2` | specify that a grid cell spans two rows
27+
`grid-rowspan-3` | specify that a grid cell spans three rows
28+
`grid-rowspan-4` | specify that a grid cell spans four rows
29+
30+
31+
## Two column grid
32+
33+
<div class="grid grid-cols-2">
34+
<div class="card"><h1>A</h1>1 × 1</div>
35+
<div class="card grid-rowspan-2"><h1>B</h1> 1 × 2</div>
36+
<div class="card"><h1>C</h1>1 × 1</div>
37+
<div class="card grid-colspan-2"><h1>D</h1>1 × 2</div>
38+
<div class="card grid-colspan-2 grid-rowspan-2"><h1>E</h1>2 × 2</div>
39+
</div>
40+
41+
```html run=false
42+
<div class="grid grid-cols-2">
43+
<div class="card"><h1>A</h1>1 × 1</div>
44+
<div class="card grid-rowspan-2"><h1>B</h1> 1 × 2</div>
45+
<div class="card"><h1>C</h1>1 × 1</div>
46+
<div class="card grid-colspan-2"><h1>D</h1>1 × 2</div>
47+
<div class="card grid-colspan-2 grid-rowspan-2"><h1>E</h1>2 × 2</div>
48+
</div>
49+
```
50+
51+
## Four column grid
52+
53+
<div class="grid grid-cols-4">
54+
<div class="card"><h1>A</h1>1 × 1</div>
55+
<div class="card"><h1>B</h1>1 × 1</div>
56+
<div class="card"><h1>C</h1>1 × 1</div>
57+
<div class="card"><h1>D</h1>1 × 1</div>
58+
<div class="card grid-colspan-3 grid-rowspan-2"><h1>E</h1>3 × 2</div>
59+
<div class="card grid-rowspan-2"><h1>F</h1>1 × 2</div>
60+
<div class="card grid-colspan-2"><h1>G</h1>2 × 1</div>
61+
<div class="card grid-colspan-2"><h1>H</h1>2 × 1</div>
62+
</div>
63+
64+
```html run=false
65+
<div class="grid grid-cols-4">
66+
<div class="card"><h1>A</h1>1 × 1</div>
67+
<div class="card"><h1>B</h1>1 × 1</div>
68+
<div class="card"><h1>C</h1>1 × 1</div>
69+
<div class="card"><h1>D</h1>1 × 1</div>
70+
<div class="card grid-colspan-3 grid-rowspan-2"><h1>E</h1>3 × 2</div>
71+
<div class="card grid-rowspan-2"><h1>F</h1>1 × 2</div>
72+
<div class="card grid-colspan-2"><h1>G</h1>2 × 1</div>
73+
<div class="card grid-colspan-2"><h1>H</h1>2 × 1</div>
74+
</div>
75+
```
76+
77+
## Three column grid with customizations
78+
79+
Note that the minimum row height is set to 150px, and cell **D** does not use the `card` class.
80+
81+
<div
82+
class="grid grid-cols-3"
83+
style="grid-auto-rows: minmax(150px, auto); color: red;"
84+
>
85+
<div class="card"><h1>A</h1>1 × 1</div>
86+
<div class="card"><h1>B</h1>1 × 1</div>
87+
<div class="card"><h1>C</h1>1 × 1</div>
88+
<div class="grid-rowspan-2"><h1>D</h1>1 × 2</div>
89+
<div class="card grid-colspan-2"><h1>E</h1>2 × 1</div>
90+
<div class="card grid-colspan-2"><h1>F</h1>2 × 1</div>
91+
</div>
92+
93+
```html run=false
94+
<div
95+
class="grid grid-cols-3"
96+
style="grid-auto-rows: minmax(150px, auto); color: red;"
97+
>
98+
<div class="card"><h1>A</h1>1 × 1</div>
99+
<div class="card"><h1>B</h1>1 × 1</div>
100+
<div class="card"><h1>C</h1>1 × 1</div>
101+
<div class="grid-rowspan-2"><h1>D</h1>1 × 2</div>
102+
<div class="card grid-colspan-2"><h1>E</h1>2 × 1</div>
103+
<div class="card grid-colspan-2"><h1>F</h1>2 × 1</div>
104+
</div>
105+
```

‎docs/layout/resize.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Layout: resize
2+
3+
`resize` is a helper function that provides a way to set the size of a chart, or other content, to fit into a container on your page.
4+
5+
`resize` takes a function that renders content, like a chart. When the page is rendered, `resize` calls the render function with the **width** and, optionally the **height**, of its parent container. If the page changes size, `resize` calls your function again with the new **width** and **height** values that re-renders the content to fit in the newly resized page.
6+
7+
## Width only
8+
9+
If your content defines its own height then only use the `width` parameter:
10+
11+
```js
12+
html`<div class="grid grid-cols-2">
13+
<div class="card">
14+
${resize((width) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width, height: 150}))}
15+
</div>
16+
<div class="card">
17+
${resize((width) => Plot.barY([3, 4, 2, 7, 5, 9, 4, 8, 1, 11]).plot({width, height: 150}))}
18+
</div>
19+
</div>`
20+
```
21+
22+
```html run=false
23+
<div class="grid grid-cols-2">
24+
<div class="card">
25+
${resize((width) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width, height: 150}))}
26+
</div>
27+
<div class="card">
28+
${resize((width) => Plot.barY([3, 4, 2, 7, 5, 9, 4, 8, 1, 11]).plot({width, height: 150}))}
29+
</div>
30+
</div>
31+
```
32+
33+
## Width and height
34+
35+
If your container defines a height, in this example `300px`, then you can use both the `width` and `height` parameters:
36+
37+
38+
```js
39+
html`<div class="grid grid-cols-2" style="grid-auto-rows: 300px;">
40+
<div class="card">
41+
${resize((width, height) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width, height}))}
42+
</div>
43+
<div class="card">
44+
${resize((width, height) => Plot.barY([3, 4, 2, 7, 5, 9, 4, 8, 1, 11]).plot({width, height}))}
45+
</div>
46+
</div>`
47+
```
48+
49+
```html run=false
50+
<div class="grid grid-cols-2" style="grid-auto-rows: 300px;">
51+
<div class="card">
52+
${resize((width, height) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width, height}))}
53+
</div>
54+
<div class="card">
55+
${resize((width, height) => Plot.barY([3, 4, 2, 7, 5, 9, 4, 8, 1, 11]).plot({width, height}))}
56+
</div>
57+
</div>
58+
```
59+
60+
Note: If you are using `resize` with both `width` and `height` and see nothing rendered, it may be because your parent container does not have its own height specified.

‎docs/layout/themes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Themes

‎observablehq.config.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,65 @@ export default {
66
{name: "Markdown", path: "/markdown"},
77
{name: "JavaScript", path: "/javascript"},
88
{name: "Data loaders", path: "/loaders"},
9+
{name: "Components", path: "/components"},
910
{name: "Configuration", path: "/config"},
1011
{
1112
name: "JavaScript",
1213
pages: [
1314
{name: "Reactivity", path: "/javascript/reactivity"},
1415
{name: "Display", path: "/javascript/display"},
16+
{name: "Inputs", path: "/javascript/inputs"},
1517
{name: "Imports", path: "/javascript/imports"},
1618
{name: "Files", path: "/javascript/files"},
1719
{name: "Promises", path: "/javascript/promises"},
1820
{name: "Generators", path: "/javascript/generators"},
1921
{name: "Mutables", path: "/javascript/mutables"},
2022
]
2123
},
24+
{
25+
name: "Layout",
26+
open: false,
27+
pages: [
28+
{name: "Grid", path: "/layout/grid"},
29+
{name: "Card", path: "/layout/card"},
30+
{name: "Resize", path: "/layout/resize"}
31+
]
32+
},
33+
{
34+
name: "Charts",
35+
open: false,
36+
pages: [
37+
{name: "Area", path: "/charts/area"},
38+
{name: "Arrow", path: "/charts/arrow"},
39+
{name: "Bar", path: "/charts/bar"},
40+
{name: "Cell", path: "/charts/cell"},
41+
{name: "Dot", path: "/charts/dot"},
42+
{name: "Facets", path: "/charts/facets"},
43+
{name: "Grouping data", path: "/charts/grouping-data"},
44+
{name: "Line", path: "/charts/line"},
45+
{name: "Tick", path: "/charts/tick"}
46+
]
47+
},
48+
{
49+
name: "Inputs",
50+
open: false,
51+
pages: [
52+
{name: "Button", path: "/inputs/button"},
53+
{name: "Checkbox", path: "/inputs/checkbox"},
54+
{name: "Color", path: "/inputs/color"},
55+
{name: "Date/Datetime", path: "/inputs/date"},
56+
{name: "File", path: "/inputs/file"},
57+
{name: "Form", path: "/inputs/form"},
58+
{name: "Radio", path: "/inputs/radio"},
59+
{name: "Range", path: "/inputs/range"},
60+
{name: "Search", path: "/inputs/search"},
61+
{name: "Select", path: "/inputs/select"},
62+
{name: "Table", path: "/inputs/table"},
63+
{name: "Text", path: "/inputs/text"},
64+
{name: "Textarea", path: "/inputs/textarea"},
65+
{name: "Toggle", path: "/inputs/toggle"}
66+
]
67+
},
2268
{
2369
name: "Libraries",
2470
open: false,

0 commit comments

Comments
 (0)
Please sign in to comment.