# display-ui

Auto-generated from exported functions tagged `#api` in the source. See
[imports and re-exports](https://jbrowse.org/jb2/docs/developer_guides/imports_and_reexports) for how to
import these from a plugin.

## BottomRightCornerContext

The one box per display that owns the bottom-right corner.

It lives beside the overlay contract rather than with the chrome that mounts
it, because it is the half of that contract the types cannot carry:
`DisplayChromeOverlays.BackgroundProgress` is the one state told to render an
in-flow chip with no `position` and no corner offsets, and this is the box it
is laid out in. A host writing their own display over `DisplayChromeBase`
needs to be able to build that corner; while this sat one layer up in the LGV
plugin, the rule was a paragraph they could only obey by hand.

Two independent things want that corner and neither could see the other:
the display's own control row (`BottomRightIndicators` — track sizing, the
isoform notice, the solo chip) and the chrome's background-progress chip
(`DisplayChromeOverlays.BackgroundProgress`, the status channel for work with
no fetch behind it). Both portal into the *same* per-track overlay node, and
both used to claim `bottom: 2; right: 2` there with their own
`position: absolute` box — so they simply drew on top of each other, the
controls winning on z-index and the status text disappearing under them.

It has never been reachable: the two displays that render the control row
(alignments, canvas) are not among the four that report a `ready`-phase
status (clustering, on multi-wiggle / multi-row features / maf / the
multi-sample variant pair). That is the *reason* to make it structural rather
than to leave it — nothing on either side is aware of the constraint, so the
first display to want both would find the bug, and it presents as a status
message that silently never appears.

`BottomRightIndicators` already described itself as "the single anchor point
for every bottom-right overlay ... so they lay out as one row instead of each
picking their own position and colliding". This makes that true of the chip
too: the chrome renders the anchor, puts its chip in it, and publishes the
node here so the display's row — rendered several components away, inside the
chrome's body — lands in the same flex box instead of over it.

`null` outside a chrome (a display an embedder mounts standalone, a unit
test, the SVG export), where `BottomRightIndicators` keeps its own anchored
box. That fallback is why this can be added without every consumer changing.

```js
// type signature
Context<HTMLElement | null>
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/bottomRightCorner.ts)

## DisplayUIProvider

Both bring-your-own seams at once: what a display draws that is not data.

There are two contexts underneath because two different things render them —
the chrome around a display draws the status states, the display itself draws
its bottom-right controls — and that split is real at the implementation
level, since `DisplayChromeBase` takes its overlay set as a *prop* and never
renders a track control. It is not real for an embedder: nobody wants stock
Material loading scrims with plain corner controls, or the reverse. Every
consumer in this repo mounts the two together.

```tsx
<DisplayUIProvider>{tracks}</DisplayUIProvider>
```

Both props default to the plain, toolkit-free sets, so the common case — "I
do not want Material UI in my app" — needs no arguments and no second import.

**`overlays` is a partial set**, merged over the plain one, so replacing a
single state is one entry rather than five:

```tsx
<DisplayUIProvider overlays={{ ErrorBar: MyErrorBar }}>
```

Partial rather than whole for two reasons. A host writing four of the five
states by hand only ever wanted one of them, and every example of this had
spread `plainChromeOverlays` in to say so. And a *sixth* state is a thing
JBrowse can add: with a whole set the host's object goes stale on upgrade —
a compile error if they typecheck, a missing component if they ship JS — while
a partial one keeps working and picks up the new plain default.

Declare that object at module scope if you can. The merge is stable per
object (`resolveOverlays`), so a constant costs one merge for the life of the
app; a literal written inline in JSX is a new object every render, and this
value goes into a context, so every display beneath re-renders with it.

**The contexts themselves still default to `undefined`, and that stays true.**
A display rendering outside any provider — a unit test, the SVG export,
breakpoint-split-view's `overlayUtils` — keeps JBrowse's own Material look,
because a plain ambient default would degrade those invisibly. Defaulting
*this component's props* is a different thing: mounting it is a deliberate
act, and the act means "not the Material default". Nothing gets a plain set
without someone having asked.

Colors are not a seam and are not here. A display reads `usePalette()` for its
own content colors, which is a palette of strings rather than a UI toolkit, so
it arrives through `SessionPaletteProvider`
(`@jbrowse/core/ui/PaletteContext`) whatever these are set to. A feature track
needs it even with plain chrome.

This module reaches no `@mui/*` module, and `muiFree.test.ts` keeps it
that way — asking for less Material UI must not download more of it. What it
cannot do is unship the Material components a *stock display* imports:
`DisplayChrome` and `TrackControl` are in that display's chunk either way,
and merely stop rendering. Keeping them out of the graph means writing your
own display component over `DisplayChromeBase`, which takes `overlays` as a
prop and imports no toolkit. See
[DISPLAYCHROME.md](https://github.com/GMOD/jbrowse-components/blob/main/agent-docs/reference/DISPLAYCHROME.md).

```js
// type signature
({ overlays, trackControl, children, }: { overlays?: Partial<…> | undefined; trackControl?: TrackControlComponent | undefined; children: ReactNode; }) => Element
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/DisplayUIProvider.tsx)

## isLiveModel

Whether an overlay's model can still be called into.

The terminal states unmount the canvas, so a click landing after the track was
closed would otherwise run an action on a destroyed node — which is why both
overlay sets guard their one button with `isAlive`.

**`isAlive` alone is wrong here, and it throws rather than answering.** The
four model shapes in `chromeOverlays.ts` are structural on purpose ("A display
satisfies one by having the fields; no mixin has to be composed"), so a host
writing their own display over `DisplayChromeBase` may hand these components a
plain object. `isAlive` runs `assertIsStateTreeNode` and throws on one, inside
an event handler, where React logs it and moves on — leaving the Force load
button looking live and doing nothing, which is the exact state
`DisplayChromeOverlays.TooLarge` documents itself as existing to prevent.

A plain object is never destroyed, so it is always callable. The liveness
question only exists for an MST node.

```js
// type signature
(model: unknown) => boolean
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/isLiveModel.ts)

## plainChromeOverlays

The five `displayPhase` states drawn with no UI toolkit: no theme provider to
mount, no emotion in the host page, and nothing that reads as a stray Material
widget inside someone else's design system.

`DisplayUIProvider` installs this by default, so mount that rather than
naming this — reach for it directly only to wrap a state or to build a
context value by hand. Colours come from `currentColor` and the CSS system
colours, so the host's own cascade drives them in both light and dark.

The `data-testid` values it renders are a contract four of JBrowse's test
systems key on, so a replacement set that keeps them can be driven by those
suites too.

```js
// type signature
DisplayChromeOverlays
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/plainChromeOverlays.tsx)

## PlainTrackControl

A display's ambient bottom-right control — track sizing, the isoform notice,
the show-only badge — drawn with no UI toolkit, the corner-control
counterpart of `plainChromeOverlays`.

`DisplayUIProvider` installs this by default. The behaviour is not in here:
dismissal, the keyboard, focus, the top layer and the anchoring are
`useTrackControlMenu`, so writing your own control means writing markup
rather than re-deriving why the menu opens upward.

The package exports it as `plainTrackControl`, lower-cased to match
`plainChromeOverlays` — the two are a pair, and both are things you hand to a
provider rather than render yourself.

```js
// type signature
({ icon, tooltip, label, options, onClick, onMenuClose, onDelete, warning, }: TrackControlProps) => Element
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/trackControl/plainTrackControl.tsx)

## tooLargeBannerText

Which axis tripped (empty when the display gates without a reason), then the
way out.

`zoomCanRelease` decides whether "zoom in" is offered, and it has to be asked
because the advice is not always true. It was, once: the `AUTO_FORCE_LOAD_BP`
floor turned the byte gate off below 20kb, so zooming far enough always
worked. The byte gate no longer stops at any floor, and an index quotes whole
blocks — so for a file whose blocks are large the same bytes come down however
far the user goes, and telling them to keep zooming into a fetch whose cost
cannot fall is the one thing the banner must not do. `zoomCanReleaseGate`
answers it from two consecutive measurements rather than from a threshold; see
`ByteEstimate.zoomIneffective`.

```js
// type signature
(regionTooLargeReason: string, { zoomCanRelease }?: { zoomCanRelease?: boolean | undefined; }) => string
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/tooLargeBannerText.ts)

## Tooltip

A hover/focus label for a control, drawn rather than delegated to the
browser's `title` attribute — positioned so it clears the display's
`contain: strict` box and the window edge, dismissed by Escape, and drawn
like every other JBrowse tooltip instead of like whatever the host OS
renders.

`title` is what this chrome used to use, and it was the wrong tool three
ways: it can be neither styled nor positioned, it waits about a second and
then disappears on a timer of its own, and on a control that already carries
an `aria-label` some screen readers announce both strings. This reaches no UI
toolkit, so the package's no-Material-UI guarantee holds.

Takes a single element child and clones it rather than wrapping it: the
controls that want a tooltip are absolutely positioned inside a legend or sit
in a flex row, where an extra `<span>` moves them. The child keeps its own
handlers — these compose on top of them. This is the same trigger pattern
MUI's own `Tooltip` uses, which is what the rest of JBrowse's chrome is built
on; the toolkit is what differs here, not the shape.

```tsx
<Tooltip title="Hide legend">
  <button type="button" aria-label="Hide legend" onClick={onDismiss}>
    ×
  </button>
</Tooltip>
```

**The child still needs its own accessible name**, because this sets
`aria-describedby` and never `aria-label` — see useTooltip, which is
this without the cloning, for a host writing its own markup.

```js
// type signature
({ title, placement, children, }: { title: ReactNode; placement?: TooltipPlacement | undefined; children: ReactElement<…>; }) => Element
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/tooltip/Tooltip.tsx)

## TrackOverlayPortal

Lift floating track chrome out of the display's `contain: strict` sandbox and
into the host's overlay node, so the LGV's inter-region masks cannot bury it
at multi-region scale. `TrackOverlaySlot` is the other end.

The overlay node takes no pointer events, so anything of yours the user
hovers or clicks sets `pointer-events: auto` on its own positioned box.

With no slot above it this renders the children in place (`fallbackInline`,
the default), which preserves chrome that is still meaningful unescaped. Pass
`fallbackInline={false}` for chrome that only makes sense above the masks and
draws itself some other way elsewhere.

```js
// type signature
({ children, fallbackInline, }: { children: ReactNode; fallbackInline?: boolean | undefined; }) => ReactNode
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/trackOverlay/TrackOverlayPortal.tsx)

## TrackOverlaySlot

The box a display is mounted in, plus the overlay node its floating chrome
escapes into. Pair to `TrackOverlayPortal`, which is the other end.

A display's React tree is sealed in a `contain: strict` sandbox — that is what
isolates its paint, and dropping it is measured and rejected (ADR-058). A
stacking context comes with the isolation, so floating chrome a display draws
(a colour key, hi-c's overlay panel, maf's row labels) cannot out-z-index
anything painted over the track stack from outside. The escape is a node
mounted *beside* the sandbox rather than inside it, published through
`TrackOverlayContext`; this component is that node, its context and the paint
order between them, in the one place they have to agree.

`TrackContainer` uses it, so JBrowse's own layout and an embedder's go through
the same code rather than two copies of one rule. **An embedder mounting
`RenderingComponent` directly needs it too**, and that is the case it was
added for: with no provider the context is null, the portal falls back to
rendering inline, and a host that paints region seams over its column buries
the chrome under them with nothing to say so.

```tsx
<TrackOverlaySlot zIndex={3} style={{ height: display.height }}>
  <div style={{ position: 'absolute', inset: 0, contain: 'strict' }}>
    <RenderingComponent model={display} />
  </div>
</TrackOverlaySlot>
```

**`zIndex` is required, and deliberately has no default.** It is the answer to
"above what?", and that is a fact about the caller's layout rather than about
this component: JBrowse's own track container passes 100, which is positioned
above `PaddingBlocks` and below `TrackLabel` at 200, and means nothing to a
host whose masks sit at 2. A default would be a number that is right in one
layout and silently wrong in every other, and the failure — chrome painted
under a mask — is invisible until someone looks at the right zoom.

The node takes no pointer events, so it does not eat the canvas's. Chrome that
wants them takes them back on its own positioned box; it also carries
`data-gesture-owner`, so anything that does is already exempt from the LGV's
click-drag pan.

```js
// type signature
({ children, zIndex, style, overlayStyle, }: { children: ReactNode; zIndex: number; style?: CSSProperties | undefined; overlayStyle?: CSSProperties | undefined; }) => Element
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/trackOverlay/TrackOverlaySlot.tsx)

## useTooltip

A hover/focus label for one control, as props to spread — the headless half
of Tooltip, for a host writing its own chrome rather than restyling
ours. Same relationship `useTrackControlMenu` has to `plainTrackControl`.

```tsx
const { triggerProps, tooltip } = useTooltip('Hide legend')
return (
  <>
    <button {...triggerProps} aria-label="Hide legend" onClick={onDismiss}>
      ×
    </button>
    {tooltip}
  </>
)
```

`triggerProps` carries no `onClick`, so a control's own handler does not
collide with it. Any other handler on this list has to compose rather than
replace — spread first, then call `triggerProps.onFocus` from yours.

```js
// type signature
(title: ReactNode, { placement }?: { placement?: TooltipPlacement | undefined; }) => TooltipTrigger
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/tooltip/useTooltip.tsx)

## useTrackControlMenu

The behaviour behind a bottom-right track control's menu, as prop getters to
spread — dismissal (Escape, an outside press, an ancestor scrolling), the
keyboard (arrows, Home/End), focus, and the anchoring that clears both the
display's `contain: strict` box and the window edge.

For writing your own control rather than restyling `plainTrackControl`: each
of those rules is a bug when missed and none of them shows up in a
screenshot. Render `menuProps` only while `open`, and portal it to
`document.body` — `createPortal` is the caller's to aim, the maths is here.
`menuProps.style` carries position only.

```js
// type signature
(onClose?: (() => void) | undefined) => TrackControlMenu
```

[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/display-ui/src/trackControl/useTrackControlMenu.tsx)

