# picker

**`picker3d` — choosing one of many.** A select is right for five options and a
scroll for five hundred; this is the other end. It composes controls that
already exist — a [[table]] for the virtualized, filterable list, an
[[keyboard|inputField]] for the query, a [[widgets3d|select3d]] for the group —
so there is no new machinery here, only the arrangement.

## Demo

Five hundred and sixty-one names, in the shape a real model library has them:
`family_thing-variant`. Type to narrow, or pick a family first.

```js
import { panel3d, label3d, picker3d } from 'tosijs-3d'
import { elements } from 'tosijs'

const { div } = elements
const readout = div({ class: 'readout' }, 'nothing picked')

// Stand-in for a library catalogue — same naming shape, generated so the demo
// carries no 561-entry literal.
const FAMILIES = ['commercial', 'residential', 'roads', 'car', 'nature', 'furniture', 'industry', 'signage', 'debris']
const KINDS = ['building', 'block', 'tower', 'bend', 'straight', 'corner', 'crossing', 'barrier', 'lamp', 'bench', 'crate']
const options = []
for (const f of FAMILIES) {
  for (const k of KINDS) {
    for (const v of ['a', 'b', 'c', 'd', 'e']) {
      options.push({ value: `${f}_${k}-${v}`, label: `${k}-${v}`, group: f })
    }
  }
}

preview.append(
  panel3d({ width: 320 },
    label3d({ text: `library — ${options.length} models` }),
    picker3d({
      label: 'model',
      value: options[0].value,
      options,
      handleChange: (v) => { readout.textContent = `picked: ${v}` },
    })
  ),
  readout
)
```
```css
.preview { padding: 12px; }
.readout {
  padding: 10px 2px;
  font-family: ui-monospace, monospace;
  color: #9aa4b2;
}
```

## Why not a bigger popup

`tosijs-3d-ensemble` put it exactly, having landed 561 Kenney models into a
palette built on `select3d`:

> The original item asked for "a real popup select" because `select3d` is a
> cycler. With 561 entries that understates it: **even a flat popup is a scroll,
> not a choice.**

A list you scroll is a list you read. The fix is not more pixels, it is fewer
candidates — so the popup's job is to get from five hundred to five before you
look at anything.

## Two levels, without a tree

Their content's own names give a natural taxonomy —
`commercial_building-a`, `roads_road-bend-barrier`, `car_debris-bolt` — and the
obvious response is a tree widget. This does it with **a group chooser and a
filter field** instead, which reaches the same place with two controls you
already know how to drive.

That is not only a simplicity argument. A tree needs a pointer to expand and
collapse; two flat controls need a D-pad and a text field, both of which work
from a controller in a headset. The tree would have been the version that only
works flat.

## Filtering is AND across terms, over the whole name

Typing `commercial bend` matches `commercial_road-bend-a`, because every
space-separated term must appear somewhere in the group, the label or the value.
One term over a concatenated string would make word ORDER matter, which is the
thing nobody can guess: an author who thinks "the bend, in commercial" would
type it that way and find nothing.

Selection survives filtering — that is [[table]]'s rule, and it matters most
here: a search box that discards what you had chosen is destructive rather than
helpful.

## It asks for a LAYER, and copes with being refused

A five-hundred-row list is bigger than most panels, and `showPopup` caps to the
panel's own bounds — which on a short panel gives a list squeezed to nothing,
placed over the control it belongs to. So this asks for `showLayer` (unbounded,
above the panel) and falls back to a bounded popup sized to what is actually
available. Same reasoning as the on-screen keyboard, and the same fallback.

## Options

| option | | |
| --- | --- | --- |
| `options` | | `string[]`, or `{value, label?, group?}[]` |
| `value` | `''` | the current selection |
| `label` | — | drawn to the left of the closed control |
| `filter` | `'auto'` | `'auto'` shows the query field above `filterAbove` options |
| `filterAbove` | `12` | where `'auto'` starts showing it |
| `groups` | `'auto'` | `'auto'` shows the group chooser when there is more than one group |
| `placeholder` | `'choose…'` | shown when nothing is selected |
| `handleChange` | | the chosen value; fires on pick, and the popup closes |
