@jbrowse/display-ui 5.0.0-beta.11 → 5.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +70 -76
  2. package/esm/AxisCaption.d.ts +2 -2
  3. package/esm/AxisCaption.js +6 -4
  4. package/esm/AxisGutter.d.ts +2 -2
  5. package/esm/AxisGutter.js +5 -4
  6. package/esm/CrossHatches.d.ts +3 -2
  7. package/esm/CrossHatches.js +5 -2
  8. package/esm/DisplayUIProvider.d.ts +3 -2
  9. package/esm/DisplayUIProvider.js +4 -2
  10. package/esm/FloatingLegend.js +7 -3
  11. package/esm/FloatingText.d.ts +9 -8
  12. package/esm/FloatingText.js +9 -7
  13. package/esm/ScoreDomainCaption.d.ts +2 -2
  14. package/esm/ScoreDomainCaption.js +3 -2
  15. package/esm/ScoreRules.d.ts +3 -2
  16. package/esm/ScoreRules.js +5 -2
  17. package/esm/YScaleBar.js +1 -8
  18. package/esm/YScaleBarOverlay.d.ts +3 -2
  19. package/esm/YScaleBarOverlay.js +4 -2
  20. package/esm/axisPlacement.d.ts +16 -9
  21. package/esm/axisPlacement.js +22 -14
  22. package/esm/bottomRightCorner.d.ts +19 -47
  23. package/esm/bottomRightCorner.js +19 -47
  24. package/esm/chromeOverlayContext.js +10 -25
  25. package/esm/chromeOverlays.d.ts +73 -67
  26. package/esm/embed/engineBoundary.d.ts +34 -0
  27. package/esm/embed/engineBoundary.js +29 -0
  28. package/esm/embed/index.d.ts +6 -0
  29. package/esm/embed/index.js +18 -3
  30. package/esm/embed/legend.js +2 -2
  31. package/esm/embed/location.d.ts +2 -1
  32. package/esm/embed/location.js +18 -17
  33. package/esm/embed/notifications.d.ts +29 -0
  34. package/esm/embed/notifications.js +38 -0
  35. package/esm/embed/provider.d.ts +5 -3
  36. package/esm/embed/provider.js +6 -4
  37. package/esm/embed/regions.d.ts +3 -2
  38. package/esm/embed/regions.js +9 -7
  39. package/esm/embed/searchPicker.d.ts +24 -0
  40. package/esm/embed/searchPicker.js +14 -0
  41. package/esm/embed/toolbar.d.ts +16 -0
  42. package/esm/embed/toolbar.js +22 -0
  43. package/esm/index.d.ts +1 -1
  44. package/esm/isLiveModel.d.ts +10 -15
  45. package/esm/isLiveModel.js +10 -15
  46. package/esm/plainChromeOverlays.d.ts +2 -2
  47. package/esm/plainChromeOverlays.js +17 -39
  48. package/esm/tooltip/Tooltip.d.ts +3 -3
  49. package/esm/tooltip/Tooltip.js +4 -2
  50. package/esm/trackControl/plainTrackControl.d.ts +2 -1
  51. package/esm/trackControl/plainTrackControl.js +8 -6
  52. package/esm/trackOverlay/TrackOverlaySlot.d.ts +3 -3
  53. package/esm/trackOverlay/TrackOverlaySlot.js +4 -3
  54. package/esm/valueScale.d.ts +5 -5
  55. package/package.json +4 -4
package/esm/YScaleBar.js CHANGED
@@ -2,18 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { usePalette } from '@jbrowse/core/ui/PaletteContext';
3
3
  import { AXIS_FONT_PX, TICK_LABEL_X_PX } from './yAxisConstants.js';
4
4
  import { clampStrokeInsideAxis } from './yScaleTicks.js';
5
- // Not an `observer`, and deliberately: there is nothing here to observe. Every
6
- // caller already reads `model.ticks` inside its own observer and hands the
7
- // resolved plain object down, and `usePalette` is a React context, not MobX.
8
- // Wrapping it anyway allocated a Reaction per instance — multi-wiggle renders
9
- // one per sample row, on screen as well as in export — and, per CLAUDE.md, put
10
- // the component out of `babel-plugin-react-compiler`'s reach, so it got neither
11
- // the MobX tracking it doesn't need nor the memoization it does. Its sibling
12
- // `CrossHatchLines`, consuming the same ticks, is already plain.
13
5
  // Half the vertical space a 10px label needs, halo included: digits reach about
14
6
  // 3.6px above the centered baseline and the 2.5px-wide background stroke another
15
7
  // 1.25px past that.
16
8
  const LABEL_HALF_HEIGHT_PX = 5;
9
+ // eslint-disable-next-line no-restricted-syntax -- drawn inside a frozen SVG figure
17
10
  export default function YScaleBar({ ticks, orientation, bandHeight, }) {
18
11
  const palette = usePalette();
19
12
  if (!ticks) {
@@ -4,8 +4,9 @@ import type { YAxis } from './valueScale.ts';
4
4
  * positioned absolutely over the band at `top`, with `AxisGutter` inside. A
5
5
  * right-side gutter clears the vertical scrollbar a display may mount.
6
6
  */
7
- export default function YScaleBarOverlay({ axis, top, width, }: {
7
+ declare const YScaleBarOverlay: ({ axis, top, width, }: {
8
8
  axis: YAxis;
9
9
  top: number;
10
10
  width: number;
11
- }): import("react").JSX.Element;
11
+ }) => import("react").JSX.Element;
12
+ export default YScaleBarOverlay;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { VERTICAL_SCROLLBAR_CLEARANCE } from '@jbrowse/core/ui/VerticalScrollbar';
3
+ import { observer } from 'mobx-react';
3
4
  import AxisGutter from './AxisGutter.js';
4
5
  import { axisGutterLeft, axisGutterWidth } from './axisPlacement.js';
5
6
  /**
@@ -7,7 +8,7 @@ import { axisGutterLeft, axisGutterWidth } from './axisPlacement.js';
7
8
  * positioned absolutely over the band at `top`, with `AxisGutter` inside. A
8
9
  * right-side gutter clears the vertical scrollbar a display may mount.
9
10
  */
10
- export default function YScaleBarOverlay({ axis, top, width, }) {
11
+ const YScaleBarOverlay = observer(function YScaleBarOverlay({ axis, top, width, }) {
11
12
  return (_jsx("svg", { style: {
12
13
  position: 'absolute',
13
14
  top,
@@ -17,4 +18,5 @@ export default function YScaleBarOverlay({ axis, top, width, }) {
17
18
  width: axisGutterWidth(axis),
18
19
  overflow: 'visible',
19
20
  }, children: _jsx(AxisGutter, { axis: axis }) }));
20
- }
21
+ });
22
+ export default YScaleBarOverlay;
@@ -8,7 +8,7 @@ import type { ValueScale, YAxis } from './valueScale.ts';
8
8
  export declare const COMPACT_AXIS_HEIGHT = 30;
9
9
  /**
10
10
  * Whether a scale places anything by y, which is whether its rules draw: it
11
- * rules at least one band, where `[]` is a scale mapped to colour instead.
11
+ * rules at least one band, where `[]` is a scale mapped to color instead.
12
12
  */
13
13
  export declare function rulesABand(scale: Pick<ValueScale, 'bandTops'>): boolean;
14
14
  /**
@@ -30,11 +30,19 @@ export declare function axisCaptionY(axis: Pick<YAxis, 'ticks'>, bandTops: numbe
30
30
  */
31
31
  export declare function axisGutterWidth(axis: Pick<YAxis, 'ticks' | 'caption'>): number;
32
32
  /**
33
- * Px the export's left margin holds for these axes: the widest left-side axis
34
- * that draws, 0 when none does. A sidebar the export parks left of the figure
35
- * sits past this strip so the scalebars stay beside the plot.
33
+ * Px the export's left margin holds for these axes, 0 when no left-side axis
34
+ * draws. `axisGutterLeft` puts every left axis's outer edge this far out, a
35
+ * wider gutter growing inward over the plot, so a sidebar the export parks left
36
+ * of the figure sits past this strip.
36
37
  */
37
- export declare function leftAxisGutterWidth(axes: readonly Pick<YAxis, 'side' | 'ticks' | 'caption' | 'height' | 'bandTops'>[]): number;
38
+ export declare function leftAxisGutterWidth(axes: readonly Pick<YAxis, 'side' | 'height' | 'bandTops'>[]): 0 | 50;
39
+ /**
40
+ * Whether an axis's numbers grow rightward from its spine: a right-side axis
41
+ * keeps them inside the plot's right edge, and a left-side axis set past a
42
+ * panel keeps its spine against the panel. Only a left-side axis at the
43
+ * display's own edge grows them leftward, toward that edge.
44
+ */
45
+ export declare function numbersGrowRight(axis: Pick<YAxis, 'side' | 'left'>): boolean;
38
46
  /** How far in from the plot's right edge a right-side gutter ends in an export. */
39
47
  export declare const AXIS_RIGHT_INSET_PX = 4;
40
48
  /**
@@ -42,9 +50,8 @@ export declare const AXIS_RIGHT_INSET_PX = 4;
42
50
  * gutter inside the plot's right edge, less `rightInset` (the vertical
43
51
  * scrollbar a display may mount on screen; a margin in an export). A left-side
44
52
  * axis takes the gutter at the display's left edge, past whatever panel the
45
- * scale's `left` reserves — except in an export, whose margin is the gutter,
46
- * so an axis nothing pushes right sits in the margin with its numbers outside
47
- * the plot and its spine on the content edge, or past it by what
48
- * `axisGutterWidth` grew.
53
+ * scale's `left` reserves — except in an export, whose margin is the gutter:
54
+ * the export parks that panel left of the margin, so the axis sits between
55
+ * the two with its numbers outside the plot.
49
56
  */
50
57
  export declare function axisGutterLeft(axis: Pick<YAxis, 'side' | 'left' | 'ticks' | 'caption'>, width: number, rightInset: number, exportContentLeft?: number): number;
@@ -10,7 +10,7 @@ import { AXIS_GUTTER_WIDTH_PX } from './yScaleTicks.js';
10
10
  export const COMPACT_AXIS_HEIGHT = 30;
11
11
  /**
12
12
  * Whether a scale places anything by y, which is whether its rules draw: it
13
- * rules at least one band, where `[]` is a scale mapped to colour instead.
13
+ * rules at least one band, where `[]` is a scale mapped to color instead.
14
14
  */
15
15
  export function rulesABand(scale) {
16
16
  return (scale.bandTops?.length ?? 1) > 0;
@@ -48,14 +48,24 @@ export function axisGutterWidth(axis) {
48
48
  return Math.max(AXIS_GUTTER_WIDTH_PX, Math.ceil(CAPTION_BAND_PX + widest + TICK_LABEL_X_PX));
49
49
  }
50
50
  /**
51
- * Px the export's left margin holds for these axes: the widest left-side axis
52
- * that draws, 0 when none does. A sidebar the export parks left of the figure
53
- * sits past this strip so the scalebars stay beside the plot.
51
+ * Px the export's left margin holds for these axes, 0 when no left-side axis
52
+ * draws. `axisGutterLeft` puts every left axis's outer edge this far out, a
53
+ * wider gutter growing inward over the plot, so a sidebar the export parks left
54
+ * of the figure sits past this strip.
54
55
  */
55
56
  export function leftAxisGutterWidth(axes) {
56
- return Math.max(0, ...axes
57
- .filter(axis => axis.side !== 'right' && axisDrawn(axis))
58
- .map(axisGutterWidth));
57
+ return axes.some(axis => axis.side !== 'right' && axisDrawn(axis))
58
+ ? AXIS_GUTTER_WIDTH_PX
59
+ : 0;
60
+ }
61
+ /**
62
+ * Whether an axis's numbers grow rightward from its spine: a right-side axis
63
+ * keeps them inside the plot's right edge, and a left-side axis set past a
64
+ * panel keeps its spine against the panel. Only a left-side axis at the
65
+ * display's own edge grows them leftward, toward that edge.
66
+ */
67
+ export function numbersGrowRight(axis) {
68
+ return axis.side === 'right' || (axis.left ?? 0) > 0;
59
69
  }
60
70
  /** How far in from the plot's right edge a right-side gutter ends in an export. */
61
71
  export const AXIS_RIGHT_INSET_PX = 4;
@@ -64,17 +74,15 @@ export const AXIS_RIGHT_INSET_PX = 4;
64
74
  * gutter inside the plot's right edge, less `rightInset` (the vertical
65
75
  * scrollbar a display may mount on screen; a margin in an export). A left-side
66
76
  * axis takes the gutter at the display's left edge, past whatever panel the
67
- * scale's `left` reserves — except in an export, whose margin is the gutter,
68
- * so an axis nothing pushes right sits in the margin with its numbers outside
69
- * the plot and its spine on the content edge, or past it by what
70
- * `axisGutterWidth` grew.
77
+ * scale's `left` reserves — except in an export, whose margin is the gutter:
78
+ * the export parks that panel left of the margin, so the axis sits between
79
+ * the two with its numbers outside the plot.
71
80
  */
72
81
  export function axisGutterLeft(axis, width, rightInset, exportContentLeft) {
73
82
  if (axis.side === 'right') {
74
83
  return width - rightInset - axisGutterWidth(axis);
75
84
  }
76
- const left = axis.left ?? 0;
77
- return exportContentLeft !== undefined && left === 0
85
+ return exportContentLeft !== undefined
78
86
  ? exportContentLeft - AXIS_GUTTER_WIDTH_PX
79
- : left;
87
+ : (axis.left ?? 0);
80
88
  }
@@ -2,56 +2,28 @@
2
2
  * #api
3
3
  * The one box per display that owns the bottom-right corner.
4
4
  *
5
- * It lives beside the overlay contract rather than with the chrome that mounts
6
- * it, because it is the half of that contract the types cannot carry:
7
- * `DisplayChromeOverlays.BackgroundProgress` is the one state told to render an
8
- * in-flow chip with no `position` and no corner offsets, and this is the box it
9
- * is laid out in. A host writing their own display over `DisplayChromeBase`
10
- * needs to be able to build that corner; while this sat one layer up in the LGV
11
- * plugin, the rule was a paragraph they could only obey by hand.
5
+ * Two members share it: the display's own control row
6
+ * (`BottomRightIndicators`) and the chrome's background-progress chip
7
+ * (`DisplayChromeOverlays.BackgroundProgress`). Both portal into the same
8
+ * per-track overlay node, so each claiming the corner with its own
9
+ * `position: absolute` box draws one over the other. The chrome renders this
10
+ * box, lays its chip out in it, and publishes the node here so the control row,
11
+ * rendered inside the chrome's body, lands in the same flex column.
12
12
  *
13
- * Two independent things want that corner and neither could see the other:
14
- * the display's own control row (`BottomRightIndicators` — track sizing, the
15
- * isoform notice, the solo chip) and the chrome's background-progress chip
16
- * (`DisplayChromeOverlays.BackgroundProgress`, the status channel for work with
17
- * no fetch behind it). Both portal into the *same* per-track overlay node, and
18
- * both used to claim `bottom: 2; right: 2` there with their own
19
- * `position: absolute` box — so they simply drew on top of each other, the
20
- * controls winning on z-index and the status text disappearing under them.
21
- *
22
- * It has never been reachable: the two displays that render the control row
23
- * (alignments, canvas) are not among the four that report a `ready`-phase
24
- * status (clustering, on multi-wiggle / multi-row features / maf / the
25
- * multi-sample variant pair). The layout is structural anyway, because neither
26
- * side checks for the constraint, so the first display to want both would hit
27
- * the bug as a status message that never appears.
28
- *
29
- * `BottomRightIndicators` already described itself as "the single anchor point
30
- * for every bottom-right overlay ... so they lay out as one row instead of each
31
- * picking their own position and colliding". This makes that true of the chip
32
- * too: the chrome renders the anchor, puts its chip in it, and publishes the
33
- * node here so the display's row — rendered several components away, inside the
34
- * chrome's body — lands in the same flex box instead of over it.
35
- *
36
- * `null` outside a chrome (a display an embedder mounts standalone, a unit
37
- * test, the SVG export), where `BottomRightIndicators` keeps its own anchored
38
- * box. That fallback is why this can be added without every consumer changing.
13
+ * - A host writing their own display over `DisplayChromeBase` builds the corner
14
+ * from this; the chip is told to render in-flow, with no `position` and no
15
+ * corner offsets.
16
+ * - `null` outside a chrome (a standalone mount, a unit test, the SVG export),
17
+ * where `BottomRightIndicators` anchors its own box.
39
18
  */
40
19
  export declare const BottomRightCornerContext: import("react").Context<HTMLElement | null>;
41
20
  /**
42
- * Members are stacked, not laid side by side, and ordered explicitly.
43
- *
44
- * Stacked because the status chip is much wider than a control (it carries a
45
- * label and a 120px bar): in one horizontal row it would shove the controls
46
- * leftwards whenever a background job started and let them snap back when it
47
- * finished.
48
- *
49
- * Ordered by `order` rather than by DOM position because one member arrives as
50
- * a portal and the other as an ordinary child, and the relative order of those
51
- * two inside the container is not something React documents. Only the control
52
- * row needs a value: the status chip takes CSS's default 0, so anything the
53
- * chrome adds to the corner later sorts above the controls unless it says
54
- * otherwise — which is the right default, the controls being the thing whose
55
- * position in the corner users learn.
21
+ * The control row's flex `order` in the corner column. The members stack
22
+ * rather than sit side by side, so a wide status chip starting or finishing
23
+ * never shoves the controls sideways. `order` sets the sequence because one
24
+ * member arrives as a portal and the other as a child, and React does not
25
+ * document their relative DOM order. The chip takes CSS's default 0, so
26
+ * anything the chrome adds later sorts above the controls, whose position users
27
+ * learn.
56
28
  */
57
29
  export declare const BOTTOM_RIGHT_CONTROLS_ORDER = 1;
@@ -3,56 +3,28 @@ import { createContext } from 'react';
3
3
  * #api
4
4
  * The one box per display that owns the bottom-right corner.
5
5
  *
6
- * It lives beside the overlay contract rather than with the chrome that mounts
7
- * it, because it is the half of that contract the types cannot carry:
8
- * `DisplayChromeOverlays.BackgroundProgress` is the one state told to render an
9
- * in-flow chip with no `position` and no corner offsets, and this is the box it
10
- * is laid out in. A host writing their own display over `DisplayChromeBase`
11
- * needs to be able to build that corner; while this sat one layer up in the LGV
12
- * plugin, the rule was a paragraph they could only obey by hand.
6
+ * Two members share it: the display's own control row
7
+ * (`BottomRightIndicators`) and the chrome's background-progress chip
8
+ * (`DisplayChromeOverlays.BackgroundProgress`). Both portal into the same
9
+ * per-track overlay node, so each claiming the corner with its own
10
+ * `position: absolute` box draws one over the other. The chrome renders this
11
+ * box, lays its chip out in it, and publishes the node here so the control row,
12
+ * rendered inside the chrome's body, lands in the same flex column.
13
13
  *
14
- * Two independent things want that corner and neither could see the other:
15
- * the display's own control row (`BottomRightIndicators` — track sizing, the
16
- * isoform notice, the solo chip) and the chrome's background-progress chip
17
- * (`DisplayChromeOverlays.BackgroundProgress`, the status channel for work with
18
- * no fetch behind it). Both portal into the *same* per-track overlay node, and
19
- * both used to claim `bottom: 2; right: 2` there with their own
20
- * `position: absolute` box — so they simply drew on top of each other, the
21
- * controls winning on z-index and the status text disappearing under them.
22
- *
23
- * It has never been reachable: the two displays that render the control row
24
- * (alignments, canvas) are not among the four that report a `ready`-phase
25
- * status (clustering, on multi-wiggle / multi-row features / maf / the
26
- * multi-sample variant pair). The layout is structural anyway, because neither
27
- * side checks for the constraint, so the first display to want both would hit
28
- * the bug as a status message that never appears.
29
- *
30
- * `BottomRightIndicators` already described itself as "the single anchor point
31
- * for every bottom-right overlay ... so they lay out as one row instead of each
32
- * picking their own position and colliding". This makes that true of the chip
33
- * too: the chrome renders the anchor, puts its chip in it, and publishes the
34
- * node here so the display's row — rendered several components away, inside the
35
- * chrome's body — lands in the same flex box instead of over it.
36
- *
37
- * `null` outside a chrome (a display an embedder mounts standalone, a unit
38
- * test, the SVG export), where `BottomRightIndicators` keeps its own anchored
39
- * box. That fallback is why this can be added without every consumer changing.
14
+ * - A host writing their own display over `DisplayChromeBase` builds the corner
15
+ * from this; the chip is told to render in-flow, with no `position` and no
16
+ * corner offsets.
17
+ * - `null` outside a chrome (a standalone mount, a unit test, the SVG export),
18
+ * where `BottomRightIndicators` anchors its own box.
40
19
  */
41
20
  export const BottomRightCornerContext = createContext(null);
42
21
  /**
43
- * Members are stacked, not laid side by side, and ordered explicitly.
44
- *
45
- * Stacked because the status chip is much wider than a control (it carries a
46
- * label and a 120px bar): in one horizontal row it would shove the controls
47
- * leftwards whenever a background job started and let them snap back when it
48
- * finished.
49
- *
50
- * Ordered by `order` rather than by DOM position because one member arrives as
51
- * a portal and the other as an ordinary child, and the relative order of those
52
- * two inside the container is not something React documents. Only the control
53
- * row needs a value: the status chip takes CSS's default 0, so anything the
54
- * chrome adds to the corner later sorts above the controls unless it says
55
- * otherwise — which is the right default, the controls being the thing whose
56
- * position in the corner users learn.
22
+ * The control row's flex `order` in the corner column. The members stack
23
+ * rather than sit side by side, so a wide status chip starting or finishing
24
+ * never shoves the controls sideways. `order` sets the sequence because one
25
+ * member arrives as a portal and the other as a child, and React does not
26
+ * document their relative DOM order. The chip takes CSS's default 0, so
27
+ * anything the chrome adds later sorts above the controls, whose position users
28
+ * learn.
57
29
  */
58
30
  export const BOTTOM_RIGHT_CONTROLS_ORDER = 1;
@@ -1,32 +1,17 @@
1
1
  import { createContext, use } from 'react';
2
- // The context alone, in a module of its own, so that consuming the seam does not
3
- // import the thing the seam exists to replace.
4
- //
5
- // `DisplayChrome.tsx` is where the Material set is bound, and it used to hold
6
- // this `createContext` too. That put 45 `@mui/*`-importing modules one hop from
7
- // `DisplayUIProvider` — the component whose whole purpose is "I do not want
8
- // Material UI in my app" — and the build agreed: the build-your-own site's
9
- // "Removing Material UI" page carried 34 first-party eager modules importing
10
- // `@mui/material` against 16 on the page that deliberately leaves the Material
11
- // chrome on screen, and 53 KB gzip more. Nothing rendered them. A DOM census
12
- // cannot see a module graph, so nothing said so.
13
- //
14
- // The rule that falls out, and it applies to any seam: **the override channel
15
- // must not live in the module that binds the default.** Everything toolkit-free
16
- // (`plainChromeOverlays`, `plainTrackControl`, `DisplayChromeBase`,
17
- // `DisplayUIProvider`) reaches zero `@mui/*` modules, and
18
- // `muiFreeSeam.test.ts` fails naming the import trail if that stops being true.
2
+ // The context alone, in a module of its own: the override channel must not live
3
+ // in the module that binds the default. `DisplayChrome.tsx` binds the Material
4
+ // set, so a provider imported from there put Material UI in the graph of the
5
+ // apps asking to leave it out. `muiFree.test.ts` fails naming the import trail
6
+ // if a toolkit-free module reaches `@mui/*` again.
19
7
  /**
20
8
  * Runtime override for the overlay set. **Defaults to undefined, not to a
21
- * component set**. A plain default would degrade every display that renders
22
- * outside a provider (unit tests, SVG export, breakpoint-split-view's
23
- * overlayUtils): the display would still work, but stop looking like JBrowse. Undefined means
24
- * "nobody asked", so `DisplayChrome` keeps its MUI set and nothing changes.
9
+ * component set**: undefined means "nobody asked", so a display rendered
10
+ * outside a provider (unit tests, SVG export) keeps JBrowse's own look.
25
11
  *
26
- * This is the escape hatch for embedders who want JBrowse's *own* displays
27
- * (wiggle, alignments, variants — all of which import `DisplayChrome` directly
28
- * and so can't be redirected at the import level) to draw with their overlays:
29
- * no MUI rendered, no ThemeProvider needed, no emotion in their page.
12
+ * This is how an embedder makes JBrowse's own displays, which import
13
+ * `DisplayChrome` directly, draw with their overlays: no MUI rendered, no
14
+ * ThemeProvider needed, no emotion in their page.
30
15
  *
31
16
  * Prefer `DisplayUIProvider`, which mounts this and the track-control context
32
17
  * together and accepts a partial set. Reach for this one only to override
@@ -1,90 +1,96 @@
1
1
  import type { ComponentType } from 'react';
2
- /** What `ErrorBar` reads: a failed fetch, and the way to run it again. */
3
- export interface DisplayErrorBarModel {
2
+ /** What `RenderError` draws: a render-backend failure and the re-init. */
3
+ export interface RenderErrorProps {
4
4
  error: unknown;
5
- reload: () => void;
5
+ onRetry: () => void;
6
+ height: number;
6
7
  }
7
- /** What `Loading` reads. Everything is optional — a display that reports no
8
- * progress and offers no cancel still gets a scrim. */
9
- export interface DisplayLoadingOverlayModel {
10
- statusMessage?: string;
11
- statusProgress?: number;
12
- fetchCanceled?: boolean;
13
- cancelFetchByUser?: () => void;
14
- reload?: () => void;
8
+ /**
9
+ * What `TooLarge` draws: the byte gate's banner text and the way past it.
10
+ * `onForceLoad` is the whole point of the state; without it the region is
11
+ * unreachable for the rest of the session.
12
+ */
13
+ export interface TooLargeProps {
14
+ message: string;
15
+ onForceLoad: () => void;
15
16
  }
16
- /** What `BackgroundProgress` reads: the status channel for work with no fetch
17
- * behind it, while the phase is `ready`. */
18
- export interface DisplayBackgroundProgressModel {
19
- statusMessage?: string;
20
- statusProgress?: number;
17
+ /**
18
+ * What `ErrorBar` draws: a failed fetch. No `onRetry` means a Retry cannot
19
+ * clear it (the byte gate tripped inside the fetch), so draw the message as
20
+ * information.
21
+ */
22
+ export interface ErrorBarProps {
23
+ error: unknown;
24
+ onRetry?: () => void;
25
+ }
26
+ /**
27
+ * What `Loading` draws. A display that reports no progress and offers no cancel
28
+ * passes neither, and still gets a scrim. `canceled` swaps Cancel for Retry.
29
+ */
30
+ export interface LoadingProps {
31
+ visible: boolean;
32
+ immediate?: boolean;
33
+ message?: string;
34
+ progress?: number;
35
+ canceled?: boolean;
36
+ onCancel?: () => void;
37
+ onRetry?: () => void;
21
38
  }
22
- /** What `TooLarge` reads. `forceLoad` is the whole point of the state — see the
23
- * entry below. */
24
- export interface TooLargeMessageModel {
25
- regionTooLargeReason: string;
26
- zoomCanReleaseGate: boolean;
27
- forceLoad: () => void;
39
+ /** What `BackgroundProgress` draws: work with no fetch behind it. */
40
+ export interface BackgroundProgressProps {
41
+ message?: string;
42
+ progress?: number;
28
43
  }
44
+ /**
45
+ * The five components that draw `displayPhase`'s terminal and overlay states.
46
+ * `DisplayChromeBase` decides which renders, reads the display model, and hands
47
+ * each plain values and callbacks, so a set needs no model, no MobX and no UI
48
+ * toolkit: an embedder with their own design system supplies plain markup and
49
+ * ships no ThemeProvider. Every import above is type-only, so this module pulls
50
+ * no runtime dependency.
51
+ *
52
+ * A replacement set must render something for each state; four test systems
53
+ * key on the testids in `plainChromeOverlays.tsx`.
54
+ *
55
+ * `ErrorBar`, `Loading` and `BackgroundProgress` are portalled as a group into
56
+ * the LGV's per-track overlay layer, which is `pointer-events: none`, so
57
+ * anything of yours the user clicks sets `pointer-events: auto` on its own
58
+ * positioned box. `BackgroundProgress` alone does not own its box; see its
59
+ * entry.
60
+ */
29
61
  export interface DisplayChromeOverlays {
30
62
  /**
31
63
  * GPU/render-backend failure. A subtree-replacing terminal state: the canvas
32
64
  * unmounts and the backend disposes, so this owns the full display area.
33
65
  */
34
- RenderError: ComponentType<{
35
- error: unknown;
36
- onRetry: () => void;
37
- height: number;
38
- }>;
39
- /**
40
- * The byte gate tripped. Also subtree-replacing. Must offer `model.forceLoad`
41
- * or the region becomes unreachable for the rest of the session.
42
- */
43
- TooLarge: ComponentType<{
44
- model: TooLargeMessageModel;
45
- }>;
66
+ RenderError: ComponentType<RenderErrorProps>;
67
+ /** The byte gate tripped. Also subtree-replacing. */
68
+ TooLarge: ComponentType<TooLargeProps>;
46
69
  /**
47
70
  * Fetch error, drawn *over* a live canvas rather than replacing it. Mounted
48
- * unconditionally like the two below, so `visible` (`displayPhase ===
49
- * 'error'`) is the gate -- don't re-derive it from `model.error`, which is
50
- * the same subtraction `displayPhase` exists to retire.
71
+ * only while the phase is `error`.
51
72
  */
52
- ErrorBar: ComponentType<{
53
- model: DisplayErrorBarModel;
54
- visible: boolean;
55
- }>;
73
+ ErrorBar: ComponentType<ErrorBarProps>;
56
74
  /**
57
- * The loading scrim. `visible` is `displayPhase` `loading` or `canceled`,
58
- * the second drawn from `model.fetchCanceled` with Retry in place of Cancel;
59
- * `immediate` asks it to skip its anti-flash delay because nothing is painted
75
+ * The loading scrim, `visible` in the `loading` and `canceled` phases;
76
+ * `immediate` asks it to skip any anti-flash delay because nothing is painted
60
77
  * yet.
61
78
  *
62
- * Mounted unconditionally, so it must handle `visible === false` itself — and
63
- * that is load-bearing rather than a style choice: the anti-flash delay is
64
- * component state, so a chrome that mounted this only while loading would
65
- * restart the timer on every activation and never reach it. Keep any
66
- * replacement mountable-while-hidden for the same reason.
79
+ * Mounted unconditionally, so it handles `visible === false` itself. An
80
+ * anti-flash delay is component state, so a chrome that mounted this only
81
+ * while loading would restart the timer on every activation and never reach
82
+ * it. Keep any replacement mountable-while-hidden for the same reason.
67
83
  */
68
- Loading: ComponentType<{
69
- model: DisplayLoadingOverlayModel;
70
- visible: boolean;
71
- immediate?: boolean;
72
- }>;
84
+ Loading: ComponentType<LoadingProps>;
73
85
  /**
74
- * Status for work with no fetch behind it, while the phase is `ready`.
75
- * Mounted unconditionally; gates on `visible` itself.
86
+ * Status for work with no fetch behind it, mounted only while that work
87
+ * runs: in the LGV chrome, a status message set while the phase is `ready`.
76
88
  *
77
89
  * **The one state that does not own its own box.** The chrome anchors the
78
- * bottom-right corner and lays this out there, because that corner is shared
79
- * with the display's own control row (`BottomRightIndicators`) and two boxes
80
- * claiming it independently is exactly what they used to do — each pinning
81
- * itself to `bottom: 2; right: 2` of the same overlay layer, the controls
82
- * winning on z-index and the status text vanishing underneath. So render an
83
- * **in-flow** chip: no `position`, no `inset`, no corner offsets. Sizing and
84
- * colours are yours; placement is not. See `bottomRightCorner.ts`.
90
+ * bottom-right corner and lays this out there, sharing it with the display's
91
+ * own control row (`BottomRightIndicators`). So render an **in-flow** chip:
92
+ * no `position`, no `inset`, no corner offsets. Sizing and colors are yours;
93
+ * placement is not. See `bottomRightCorner.ts`.
85
94
  */
86
- BackgroundProgress: ComponentType<{
87
- model: DisplayBackgroundProgressModel;
88
- visible: boolean;
89
- }>;
95
+ BackgroundProgress: ComponentType<BackgroundProgressProps>;
90
96
  }
@@ -0,0 +1,34 @@
1
+ import { Component } from 'react';
2
+ import type React from 'react';
3
+ export interface EngineFallbackProps {
4
+ error: unknown;
5
+ retry: () => void;
6
+ }
7
+ /**
8
+ * Catches what `useCreateViewState` rethrows from render when the engine
9
+ * itself cannot be built (a hub genome that does not exist, a plugin that
10
+ * fails to load), where there is no view yet for `ViewStatus` to draw. Wrap
11
+ * the component that calls the hook. `retry` remounts it, which builds again.
12
+ *
13
+ * Its own class rather than `@jbrowse/core/ui/ErrorBoundary`: a bundler groups
14
+ * that module with core's Material error UI, and importing it eagerly ships
15
+ * ~40 KB gzip of Material with the page.
16
+ */
17
+ export declare class EngineBoundary extends Component<{
18
+ children: React.ReactNode;
19
+ fallback?: React.ComponentType<EngineFallbackProps>;
20
+ }, {
21
+ error: unknown;
22
+ failed: boolean;
23
+ }> {
24
+ state: {
25
+ error: unknown;
26
+ failed: boolean;
27
+ };
28
+ static getDerivedStateFromError(error: unknown): {
29
+ error: unknown;
30
+ failed: boolean;
31
+ };
32
+ retry: () => void;
33
+ render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | Iterable<React.ReactNode> | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | React.ReactPortal | null | undefined> | null | undefined;
34
+ }
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Component } from 'react';
3
+ import { observer } from 'mobx-react';
4
+ const DefaultFallback = observer(function DefaultFallback({ error, retry, }) {
5
+ return (_jsxs("div", { role: "alert", style: { padding: '10px 12px', fontSize: '0.85rem' }, children: ["Could not build the view:", ' ', error instanceof Error ? error.message : String(error), ' ', _jsx("button", { type: "button", onClick: retry, children: "Retry" })] }));
6
+ });
7
+ /**
8
+ * Catches what `useCreateViewState` rethrows from render when the engine
9
+ * itself cannot be built (a hub genome that does not exist, a plugin that
10
+ * fails to load), where there is no view yet for `ViewStatus` to draw. Wrap
11
+ * the component that calls the hook. `retry` remounts it, which builds again.
12
+ *
13
+ * Its own class rather than `@jbrowse/core/ui/ErrorBoundary`: a bundler groups
14
+ * that module with core's Material error UI, and importing it eagerly ships
15
+ * ~40 KB gzip of Material with the page.
16
+ */
17
+ export class EngineBoundary extends Component {
18
+ state = { error: undefined, failed: false };
19
+ static getDerivedStateFromError(error) {
20
+ return { error, failed: true };
21
+ }
22
+ retry = () => {
23
+ this.setState({ error: undefined, failed: false });
24
+ };
25
+ render() {
26
+ const Fallback = this.props.fallback ?? DefaultFallback;
27
+ return this.state.failed ? (_jsx(Fallback, { error: this.state.error, retry: this.retry })) : (this.props.children);
28
+ }
29
+ }