@jbrowse/display-ui 5.0.0-beta.10 → 5.0.0-beta.13

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.
@@ -37,20 +37,9 @@ const useStyles = makeStyles()(theme => ({
37
37
  // rendered inline, where events already pass through)
38
38
  pointerEvents: 'auto',
39
39
  },
40
- // A plain <button>, not MUI's IconButton, and the glyph is `×` rather than
41
- // the Material Close icon.
42
- //
43
- // This component is rendered by canvas, alignments, variants and multi-wiggle
44
- // displays *directly*, behind neither bring-your-own seam, so a Material
45
- // widget here reaches an embedder who has opted out of both — and the
46
- // build-your-own site's zero-MUI census, which no page currently trips only
47
- // because no page turns on a colorBy that raises a legend. Same call
48
- // `BaseTooltip` made in 2026-08: what it needed was colors, and colors have a
49
- // toolkit-free home already. See DISPLAYCHROME.md, "a third seam was
50
- // considered for the tooltip and rejected".
51
- //
52
- // `×` is what `SvgColorLegend` already draws for this control, so the
53
- // exported legend and the on-screen one now agree.
40
+ // A plain <button> with a `×` glyph, not MUI: displays render this legend
41
+ // directly, behind neither bring-your-own seam. See DISPLAYCHROME.md, "The
42
+ // bring-your-own seams".
54
43
  closeButton: {
55
44
  position: 'absolute',
56
45
  top: 0,
@@ -234,7 +223,7 @@ const LegendItemList = observer(function LegendItemList({ items, maxItems, onIte
234
223
  }
235
224
  const row = (_jsxs(_Fragment, { children: [_jsx("div", { className: classes.swatches, style: { minWidth: columns * SWATCH + (columns - 1) * 2 }, children: legendSwatches(item).map(swatch => (_jsx("svg", { width: SWATCH, height: SWATCH, children: _jsx(LegendSwatchGlyph, { swatch: swatch, size: SWATCH }) }, `${swatch.shape}-${swatch.color}`))) }), _jsx("span", { className: classes.label, children: item.label })] }));
236
225
  const itemClass = cx(classes.item, item.hidden && classes.hiddenItem);
237
- return onItemClick ? (_jsx("button", { type: "button", className: cx(itemClass, classes.itemButton), title: "Show only these rows", onClick: () => {
226
+ return onItemClick && legendSwatches(item).length > 0 ? (_jsx("button", { type: "button", className: cx(itemClass, classes.itemButton), title: "Show only these rows", onClick: () => {
238
227
  onItemClick(item);
239
228
  }, children: row }, `${item.label}-${i}`)) : (_jsx("div", { className: itemClass, children: row }, `${item.label}-${i}`));
240
229
  }), collapsible ? (_jsx("button", { type: "button", className: classes.toggle, onClick: () => {
@@ -250,6 +239,10 @@ const LegendItemList = observer(function LegendItemList({ items, maxItems, onIte
250
239
  // `onItemClick` makes the rows of a section declaring `focusesRows` act —
251
240
  // clicking an entry focuses the rows it names. A section coloring features or
252
241
  // cells stays inert text beside it.
242
+ //
243
+ // The key covers data at the lane's top right, an overlap Colin accepted on
244
+ // 2026-09-30. A lane shorter than the key scrolls it rather than clipping it,
245
+ // which a still cannot show, so a figure gives that lane more height.
253
246
  const FloatingLegend = observer(function FloatingLegend({ sections, title, onDismiss, onDismissSection, onItemClick, maxItems = DEFAULT_MAX_ITEMS, maxWidth = DEFAULT_MAX_WIDTH, top = FLOATING_LEGEND_TOP_PX, right = FLOATING_LEGEND_RIGHT_PX, }) {
254
247
  const { classes } = useStyles();
255
248
  const nonEmpty = nonEmptyLegendSections(sections);
@@ -34,8 +34,9 @@ export declare function FloatingText({ x, y, color, fontSize, halo, style, child
34
34
  * The same line in an SVG export: two `<text>`s at the baseline `y`, a stroke
35
35
  * in the surface colour under the glyphs, since SVG paints stroke over fill
36
36
  * and a thick stroke beneath a second copy is how a halo is drawn there. Both
37
- * carry the size and family explicitly, because a `<text>` with neither takes
38
- * SVG's 16px default in a saved file.
37
+ * carry the size, because a `<text>` without one takes SVG's 16px default in a
38
+ * saved file. An export leaves `fontFamily` out, so the label inherits the
39
+ * family the export dialog put on the root.
39
40
  */
40
41
  export declare function SvgHaloText({ x, y, fill, halo, haloWidth, fontSize, fontFamily, anchor, children, }: {
41
42
  x: number;
@@ -44,7 +45,7 @@ export declare function SvgHaloText({ x, y, fill, halo, haloWidth, fontSize, fon
44
45
  halo: string;
45
46
  haloWidth: number | string;
46
47
  fontSize: number;
47
- fontFamily: string;
48
+ fontFamily?: string;
48
49
  anchor?: 'start' | 'middle' | 'end';
49
50
  children: ReactNode;
50
51
  }): import("react").JSX.Element;
@@ -39,8 +39,9 @@ export function FloatingText({ x, y, color, fontSize, halo, style, children, ...
39
39
  * The same line in an SVG export: two `<text>`s at the baseline `y`, a stroke
40
40
  * in the surface colour under the glyphs, since SVG paints stroke over fill
41
41
  * and a thick stroke beneath a second copy is how a halo is drawn there. Both
42
- * carry the size and family explicitly, because a `<text>` with neither takes
43
- * SVG's 16px default in a saved file.
42
+ * carry the size, because a `<text>` without one takes SVG's 16px default in a
43
+ * saved file. An export leaves `fontFamily` out, so the label inherits the
44
+ * family the export dialog put on the root.
44
45
  */
45
46
  export function SvgHaloText({ x, y, fill, halo, haloWidth, fontSize, fontFamily, anchor = 'start', children, }) {
46
47
  const attrs = { x, y, fontSize, fontFamily, textAnchor: anchor };
@@ -29,6 +29,13 @@ export declare function axisCaptionY(axis: Pick<YAxis, 'ticks'>, bandTops: numbe
29
29
  * beside the spine.
30
30
  */
31
31
  export declare function axisGutterWidth(axis: Pick<YAxis, 'ticks' | 'caption'>): number;
32
+ /**
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.
37
+ */
38
+ export declare function leftAxisGutterWidth(axes: readonly Pick<YAxis, 'side' | 'height' | 'bandTops'>[]): 0 | 50;
32
39
  /** How far in from the plot's right edge a right-side gutter ends in an export. */
33
40
  export declare const AXIS_RIGHT_INSET_PX = 4;
34
41
  /**
@@ -47,6 +47,17 @@ export function axisGutterWidth(axis) {
47
47
  }
48
48
  return Math.max(AXIS_GUTTER_WIDTH_PX, Math.ceil(CAPTION_BAND_PX + widest + TICK_LABEL_X_PX));
49
49
  }
50
+ /**
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.
55
+ */
56
+ export function leftAxisGutterWidth(axes) {
57
+ return axes.some(axis => axis.side !== 'right' && axisDrawn(axis))
58
+ ? AXIS_GUTTER_WIDTH_PX
59
+ : 0;
60
+ }
50
61
  /** How far in from the plot's right edge a right-side gutter ends in an export. */
51
62
  export const AXIS_RIGHT_INSET_PX = 4;
52
63
  /**
package/esm/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export type { AxisSide, ValueScale, ValueScaleRule, YAxis, } from './valueScale.
29
29
  export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from './yScaleTicks.ts';
30
30
  export type { YScaleTicks } from './yScaleTicks.ts';
31
31
  export { DEFAULT_RULE_COLOR, SCORE_CAPTION_HEIGHT, YSCALEBAR_LABEL_OFFSET, } from './yAxisConstants.ts';
32
- export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, } from './axisPlacement.ts';
32
+ export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, leftAxisGutterWidth, } from './axisPlacement.ts';
33
33
  export { default as AxisGutter } from './AxisGutter.tsx';
34
34
  export { AxisCaptionOverlay, default as AxisCaption } from './AxisCaption.tsx';
35
35
  export { default as YScaleBar } from './YScaleBar.tsx';
package/esm/index.js CHANGED
@@ -64,7 +64,7 @@ export { isLiveModel } from './isLiveModel.js';
64
64
  export { BOTTOM_RIGHT_CONTROLS_ORDER, BottomRightCornerContext, } from './bottomRightCorner.js';
65
65
  export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from './yScaleTicks.js';
66
66
  export { DEFAULT_RULE_COLOR, SCORE_CAPTION_HEIGHT, YSCALEBAR_LABEL_OFFSET, } from './yAxisConstants.js';
67
- export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, } from './axisPlacement.js';
67
+ export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, leftAxisGutterWidth, } from './axisPlacement.js';
68
68
  export { default as AxisGutter } from './AxisGutter.js';
69
69
  export { AxisCaptionOverlay, default as AxisCaption } from './AxisCaption.js';
70
70
  export { default as YScaleBar } from './YScaleBar.js';
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isRegionTooLargeError } from '@jbrowse/core/rpc/byteBudget';
2
3
  // the subpath, not the `@jbrowse/core/ui` barrel: this file is the MUI-free
3
4
  // chrome, and gpuFallback.ts is React-free for exactly that reason
4
5
  import { GPU_FALLBACK_LABEL, GPU_FALLBACK_TOOLTIP, disableGpuRendering, shouldOfferGpuFallback, } from '@jbrowse/core/ui/gpuFallback';
@@ -137,9 +138,13 @@ const PlainTooLarge = observer(function PlainTooLarge({ model, }) {
137
138
  }, children: "Force load" })] }));
138
139
  });
139
140
  const PlainErrorBar = observer(function PlainErrorBar({ model, visible, }) {
140
- return visible && model.error ? (_jsx("div", { style: overlayBox, role: "alert", children: _jsxs("div", { style: chip, children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(model.error) }), _jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: () => {
141
+ const { error } = model;
142
+ if (!visible || !error) {
143
+ return null;
144
+ }
145
+ return isRegionTooLargeError(error) ? (_jsx("div", { style: overlayBox, role: "status", children: _jsx("div", { style: chip, children: _jsx("span", { style: { wordBreak: 'break-word' }, children: error.message }) }) })) : (_jsx("div", { style: overlayBox, role: "alert", children: _jsxs("div", { style: chip, children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), _jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: () => {
141
146
  model.reload();
142
- }, children: "Retry" })] }) })) : null;
147
+ }, children: "Retry" })] }) }));
143
148
  });
144
149
  // No anti-flash delay here, unlike the MUI set: that timing is a product
145
150
  // judgment JBrowse made for its own UI, and an embedder pacing their own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/display-ui",
3
- "version": "5.0.0-beta.10",
3
+ "version": "5.0.0-beta.13",
4
4
  "type": "module",
5
5
  "description": "The UI a display draws that is not data: the swappable chrome contract, its toolkit-free implementations, and the track overlay layer",
6
6
  "keywords": [
@@ -29,9 +29,10 @@
29
29
  "esm"
30
30
  ],
31
31
  "dependencies": {
32
- "@jbrowse/mobx-state-tree": "^6.6.1",
32
+ "@jbrowse/mobx-state-tree": "^6.7.0",
33
+ "mobx": "^7.0.6",
33
34
  "mobx-react": "^10.0.2",
34
- "@jbrowse/core": "^5.0.0-beta.10"
35
+ "@jbrowse/core": "^5.0.0-beta.13"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "react": ">=19.0.0",