@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.11

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 (148) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +142 -50
  2. package/esm/LaunchLinearGenomeView/index.js +2 -13
  3. package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
  4. package/esm/LinearGenomeView/afterAttach.js +75 -62
  5. package/esm/LinearGenomeView/components/CenterLine.js +5 -3
  6. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  7. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +3 -2
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
  9. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  10. package/esm/LinearGenomeView/components/Header.js +11 -1
  11. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  12. package/esm/LinearGenomeView/components/HeaderPanControls.js +2 -1
  13. package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
  14. package/esm/LinearGenomeView/components/Highlight.js +7 -7
  15. package/esm/LinearGenomeView/components/HighlightBand.d.ts +9 -1
  16. package/esm/LinearGenomeView/components/HighlightBand.js +15 -3
  17. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  18. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  19. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  20. package/esm/LinearGenomeView/components/ImportForm.js +2 -2
  21. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
  22. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
  23. package/esm/LinearGenomeView/components/MiniControls.js +3 -2
  24. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  25. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  26. package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
  27. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  28. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  29. package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
  30. package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
  31. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  32. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  33. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
  34. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  35. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  36. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  37. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  38. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  39. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
  40. package/esm/LinearGenomeView/components/SearchBox.js +8 -7
  41. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
  42. package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
  43. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  44. package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
  45. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  46. package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
  47. package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
  48. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  49. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
  50. package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
  51. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
  52. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
  53. package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
  54. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  55. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  56. package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
  57. package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
  58. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  59. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  60. package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
  61. package/esm/LinearGenomeView/components/searchModes.js +1 -1
  62. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  63. package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
  64. package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
  65. package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
  66. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  67. package/esm/LinearGenomeView/components/util.js +4 -2
  68. package/esm/LinearGenomeView/consts.d.ts +2 -0
  69. package/esm/LinearGenomeView/consts.js +3 -1
  70. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  71. package/esm/LinearGenomeView/containingLgv.js +23 -0
  72. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  73. package/esm/LinearGenomeView/headerFit.js +20 -7
  74. package/esm/LinearGenomeView/index.d.ts +1 -5
  75. package/esm/LinearGenomeView/index.js +10 -7
  76. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  77. package/esm/LinearGenomeView/keyboardHandler.js +15 -1
  78. package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
  79. package/esm/LinearGenomeView/launchKeys.js +27 -0
  80. package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
  81. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  82. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  83. package/esm/LinearGenomeView/linkedViewSync.js +27 -43
  84. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  85. package/esm/LinearGenomeView/menuItems.js +242 -151
  86. package/esm/LinearGenomeView/model.d.ts +2027 -200
  87. package/esm/LinearGenomeView/model.js +560 -395
  88. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  89. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  90. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  91. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  92. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  93. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  94. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  95. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
  96. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  97. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
  98. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  99. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  100. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  101. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  102. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
  103. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  104. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +2 -1
  105. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +29 -16
  106. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
  107. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
  108. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
  109. package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
  110. package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
  111. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +1 -0
  112. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  113. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
  114. package/esm/LinearGenomeView/svgcomponents/util.d.ts +20 -8
  115. package/esm/LinearGenomeView/svgcomponents/util.js +50 -29
  116. package/esm/LinearGenomeView/types.d.ts +8 -11
  117. package/esm/LinearGenomeView/util.d.ts +60 -45
  118. package/esm/LinearGenomeView/util.js +131 -160
  119. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
  120. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  121. package/esm/MultiLevelRubberband/index.js +2 -0
  122. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  123. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  124. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  125. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  126. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  127. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  128. package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
  129. package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -86
  130. package/esm/displayKitTests/perRegionTestEnv.js +30 -29
  131. package/esm/index.d.ts +15 -85
  132. package/esm/index.js +13 -36
  133. package/esm/searchUtils.d.ts +18 -9
  134. package/esm/searchUtils.js +85 -69
  135. package/esm/shared/RubberbandSpan.js +2 -1
  136. package/esm/shared/coordLabels.d.ts +7 -0
  137. package/esm/shared/coordLabels.js +15 -1
  138. package/esm/shared/useWindowDrag.d.ts +18 -0
  139. package/esm/shared/useWindowDrag.js +45 -0
  140. package/package.json +10 -10
  141. package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
  142. package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
  143. package/esm/LinearGenomeView/initKeys.d.ts +0 -13
  144. package/esm/LinearGenomeView/initKeys.js +0 -104
  145. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  146. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  147. package/esm/lazyPluginExports.d.ts +0 -23
  148. package/esm/lazyPluginExports.js +0 -36
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef } from 'react';
3
+ import { ResizeHandle } from '@jbrowse/core/ui';
4
+ import { scrollPortOf } from '@jbrowse/core/util/hooks';
5
+ import { isAlive } from '@jbrowse/mobx-state-tree';
6
+ import { observer } from 'mobx-react';
7
+ import { RESIZE_ALL_HANDLE_HEIGHT } from '../consts.js';
8
+ function useScrollPortExcess(ref, model, enabled) {
9
+ useLayoutEffect(() => {
10
+ const box = ref.current?.closest('[data-testid^="view-container-"]') ??
11
+ ref.current?.closest('[data-testid^="linear-genome-view-"]');
12
+ if (!enabled || !box || !('ResizeObserver' in window)) {
13
+ return;
14
+ }
15
+ const port = scrollPortOf(box);
16
+ const measure = () => {
17
+ const { marginTop, marginBottom } = getComputedStyle(box);
18
+ const outer = box.getBoundingClientRect().height +
19
+ Number.parseFloat(marginTop) +
20
+ Number.parseFloat(marginBottom);
21
+ model.setScrollPortExcess(Math.round(outer - (port?.clientHeight ?? window.innerHeight)));
22
+ };
23
+ measure();
24
+ const observer = new ResizeObserver(measure);
25
+ observer.observe(box);
26
+ if (port) {
27
+ observer.observe(port);
28
+ }
29
+ else {
30
+ window.addEventListener('resize', measure);
31
+ }
32
+ return () => {
33
+ observer.disconnect();
34
+ window.removeEventListener('resize', measure);
35
+ if (isAlive(model)) {
36
+ model.setScrollPortExcess(undefined);
37
+ }
38
+ };
39
+ }, [ref, model, enabled]);
40
+ }
41
+ const ResizeAllTracksHandle = observer(function ResizeAllTracksHandle({ model, }) {
42
+ const ref = useRef(null);
43
+ const drag = useRef({ from: new Map(), moved: 0 });
44
+ const { isTopLevelView } = model;
45
+ useScrollPortExcess(ref, model, isTopLevelView);
46
+ return (_jsx("div", { ref: ref, style: isTopLevelView
47
+ ? { position: 'sticky', bottom: 0, zIndex: 300 }
48
+ : { position: 'relative', height: 0, zIndex: 300 }, children: _jsx(ResizeHandle, { grip: isTopLevelView || 'hover', "data-testid": "resize-all-tracks", title: isTopLevelView
49
+ ? 'Drag to resize all tracks, double-click to fit them to the window'
50
+ : 'Drag to resize all tracks', style: isTopLevelView
51
+ ? { height: RESIZE_ALL_HANDLE_HEIGHT }
52
+ : {
53
+ height: RESIZE_ALL_HANDLE_HEIGHT,
54
+ position: 'absolute',
55
+ bottom: 0,
56
+ }, onDragStart: () => {
57
+ drag.current = { from: model.resizableTrackHeights, moved: 0 };
58
+ for (const track of model.tracks) {
59
+ track.setResizing(true);
60
+ }
61
+ }, onDrag: distance => {
62
+ drag.current.moved = model.resizeTracks(drag.current.moved + distance, drag.current.from);
63
+ }, onDragEnd: () => {
64
+ for (const track of model.tracks) {
65
+ track.setResizing(false);
66
+ }
67
+ }, onDoubleClick: () => {
68
+ model.fitTracksToWindow();
69
+ } }) }));
70
+ });
71
+ export default ResizeAllTracksHandle;
@@ -1,7 +1,6 @@
1
1
  import type { LinearGenomeViewModel } from '../index.ts';
2
2
  type LGV = LinearGenomeViewModel;
3
- declare const Rubberband: ({ model, ControlComponent, }: {
3
+ declare const Rubberband: ({ model }: {
4
4
  model: LGV;
5
- ControlComponent?: React.ReactElement;
6
5
  }) => import("react").JSX.Element;
7
6
  export default Rubberband;
@@ -4,6 +4,7 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
4
  import { observer } from 'mobx-react';
5
5
  import { stickyChromeTops } from '../stickyChrome.js';
6
6
  import RangeSelectOverlay from './RangeSelectOverlay.js';
7
+ import Scalebar from './Scalebar.js';
7
8
  import { useRangeSelect } from './useRangeSelect.js';
8
9
  const useStyles = makeStyles()({
9
10
  rubberbandControl: {
@@ -13,7 +14,7 @@ const useStyles = makeStyles()({
13
14
  zIndex: 825,
14
15
  },
15
16
  });
16
- const Rubberband = observer(function Rubberband({ model, ControlComponent = _jsx("div", {}), }) {
17
+ const Rubberband = observer(function Rubberband({ model }) {
17
18
  const ref = useRef(null);
18
19
  const { classes } = useStyles();
19
20
  const { stickyViewHeaders, headerHeight } = model;
@@ -24,6 +25,6 @@ const Rubberband = observer(function Rubberband({ model, ControlComponent = _jsx
24
25
  // (TracksContainer): mouseout bubbles from every child, so crossing a
25
26
  // refName or coordinate label inside the scalebar cleared the guide and
26
27
  // the next mousemove put it straight back
27
- onMouseLeave: range.mouseOut, children: ControlComponent })] }));
28
+ onMouseLeave: range.mouseOut, children: _jsx(Scalebar, { model: model }) })] }));
28
29
  });
29
30
  export default Rubberband;
@@ -1,9 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { getFillProps } from '@jbrowse/core/util';
3
3
  // One full-height SVG highlight band with an optional label at the top,
4
- // mirroring the on-screen HighlightBand div. Shared by the LGV native-highlight
5
- // export (SVGHighlights) and the grid-bookmark export (LGVHighlightSVG). The
6
- // label is hidden on bands too narrow to fit it.
4
+ // mirroring the on-screen HighlightBand div. The label is hidden on bands too
5
+ // narrow to fit it.
7
6
  export default function SVGHighlightBand({ coords, height, color, label, labelColor, }) {
8
7
  return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label && coords.width > 3 ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
9
8
  }
@@ -1,10 +1,6 @@
1
1
  import type { LinearGenomeViewModel } from '../index.ts';
2
- import type React from 'react';
3
2
  type LGV = LinearGenomeViewModel;
4
- interface ScalebarProps {
3
+ declare const Scalebar: ({ model }: {
5
4
  model: LGV;
6
- style?: React.CSSProperties;
7
- className?: string;
8
- }
9
- declare const Scalebar: ({ model, style, className, }: ScalebarProps) => React.JSX.Element;
5
+ }) => import("react").JSX.Element;
10
6
  export default Scalebar;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cx, makeStyles } from '@jbrowse/core/util/tss-react';
2
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
3
3
  import { Paper } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
5
+ import { SCALE_BAR_HEIGHT } from '../consts.js';
5
6
  import Gridlines from './Gridlines.js';
6
7
  import PaddingBlocks from './PaddingBlocks.js';
7
8
  import ScalebarCoordinateLabels from './ScalebarCoordinateLabels.js';
@@ -14,15 +15,17 @@ const useStyles = makeStyles()({
14
15
  container: {
15
16
  overflow: 'clip',
16
17
  position: 'relative',
18
+ height: SCALE_BAR_HEIGHT,
19
+ boxSizing: 'border-box',
17
20
  },
18
21
  });
19
- const Scalebar = observer(function Scalebar({ model, style, className, }) {
22
+ const Scalebar = observer(function Scalebar({ model }) {
20
23
  const { classes } = useStyles();
21
24
  return (_jsxs(Paper
22
- // The rubberband owns presses on the scalebar (it wraps this as its
23
- // ControlComponent), so the view's click-drag pan must not also start one
25
+ // The rubberband owns presses on the scalebar (it wraps this), so the
26
+ // view's click-drag pan must not also start one
24
27
  // on the paths where the rubberband doesn't stopPropagation — a press with
25
28
  // the refName menu already open. See ResizeHandle for the marker contract.
26
- , { "data-gesture-owner": "true", className: cx(classes.container, className), variant: "outlined", style: style, children: [_jsx(Gridlines, { model: model, offset: 1 }), _jsx(PaddingBlocks, { model: model, offset: 1 }), _jsx(ScalebarCoordinateLabels, { model: model }), _jsx(ScalebarRefNameLabels, { model: model })] }));
29
+ , { "data-gesture-owner": "true", className: classes.container, variant: "outlined", children: [_jsx(Gridlines, { model: model, offset: 1 }), _jsx(PaddingBlocks, { model: model, offset: 1 }), _jsx(ScalebarCoordinateLabels, { model: model }), _jsx(ScalebarRefNameLabels, { model: model })] }));
27
30
  });
28
31
  export default Scalebar;
@@ -5,7 +5,8 @@ import { assembleLocString, assembleLocStrings } from '@jbrowse/core/util';
5
5
  import { copyText } from '@jbrowse/core/util/copyText';
6
6
  import { makeStyles } from '@jbrowse/core/util/tss-react';
7
7
  import { observer } from 'mobx-react';
8
- import { REF_NAME_LABEL_FONT_SIZE, regionMoveActions, withRegionMoved, withRegionRemoved, withRegionReversed, } from '../util.js';
8
+ import { REF_NAME_LABEL_FONT_SIZE, regionMoveActions, regionRunBounds, withRegionMoved, withRegionRemoved, withRegionReversed, withRegionsKept, } from '../util.js';
9
+ import { scalebarRefLabelProps } from './util.js';
9
10
  const useStyles = makeStyles()(theme => ({
10
11
  refLabel: {
11
12
  // the width the fit test in getScalebarRefNameLabels measures against
@@ -31,65 +32,45 @@ const useStyles = makeStyles()(theme => ({
31
32
  overflow: 'clip',
32
33
  whiteSpace: 'nowrap',
33
34
  },
34
- // only a refName label opens a menu. The assembly-name chip beside it is a
35
- // caption, and wearing the pointer and the hover tint made it look like the
36
- // one thing on the row that does nothing when clicked
35
+ // refName labels only: the caption chip opens no menu
37
36
  clickable: {
38
37
  cursor: 'pointer',
39
38
  '&:hover': {
40
- // action.hover is a mode-aware translucent overlay; the old hardcoded
41
- // grey[300] stayed light in dark mode, washing out the light label text
42
39
  background: theme.palette.action.hover,
43
40
  },
44
41
  },
45
- prefixLabel: {
42
+ caption: {
46
43
  zIndex: 100,
47
44
  },
48
45
  }));
49
46
  const ScalebarRefNameLabels = observer(function ScalebarRefNameLabels({ model, }) {
50
47
  const { classes, cx } = useStyles();
51
48
  const [menuState, setMenuState] = useState();
52
- // `model.scalebarRefNameLabels`, not a getScalebarRefNameLabels call of its
53
- // own: a host drawing its own region names reads the same getter, so the
54
- // sticky/dedup/fit rules can't be one thing here and another there. The SVG
55
- // export still calls the helper directly, deliberately and with no prefix.
56
49
  const { labels, caption } = model.scalebarRefNameLabels;
57
50
  return (_jsxs(_Fragment, { children: [labels.map((label, i) => (_jsx(RefLabel
58
51
  // eslint-disable-next-line @eslint-react/no-array-index-key -- position IS the identity here; keying by the run makes the list churn on zoom
59
- , { model: model, label: label, onOpenMenu: state => {
60
- setMenuState(state);
61
- } }, i))), caption === undefined ? null : (_jsx("span", { className: cx(classes.prefixLabel, classes.refLabel), "data-testid": "refLabel-prefix", children: caption })), menuState ? (_jsx(RefNameMenu, { model: model, menuState: menuState, onClose: () => {
52
+ , { model: model, label: label, onOpenMenu: setMenuState }, i))), caption === undefined ? null : (_jsx("span", { className: cx(classes.caption, classes.refLabel), "data-testid": "refLabel-prefix", children: caption })), menuState ? (_jsx(RefNameMenu, { model: model, menuState: menuState, onClose: () => {
62
53
  model.setIsScalebarRefNameMenuOpen(false);
63
54
  setMenuState(undefined);
64
55
  } })) : null] }));
65
56
  });
66
- // One refName label. Not an observer: `label` is already plain data computed by
67
- // the parent, and `model` is only used to fire actions. mouseDown flags the
68
- // click as starting on a label so the scalebar's rubberband hands the click to
69
- // this label's own onClick instead of opening the range menu.
57
+ // Not an observer: `label` is plain data from the parent. The attribute tells
58
+ // the scalebar's rubberband to leave a click here to this label's onClick.
70
59
  function RefLabel({ model, label, onOpenMenu, }) {
71
60
  const { classes, cx } = useStyles();
72
- const { refName, displayedRegionIndex, lastDisplayedRegionIndex, transform, maxWidth, paddingLeft, text, } = label;
61
+ const { refName, transform, maxWidth, paddingLeft, text } = label;
73
62
  return (_jsx("span", { className: cx(classes.refLabel, classes.clickable), style: {
74
63
  transform: `translateX(${transform}px)`,
75
64
  paddingLeft,
76
65
  maxWidth,
77
- }, "data-testid": `refLabel-${refName}`, onMouseDown: () => {
78
- model.setScalebarRefNameClickPending(true);
79
- }, onClick: e => {
80
- model.setScalebarRefNameClickPending(false);
66
+ }, "data-testid": `refLabel-${refName}`, ...scalebarRefLabelProps, onClick: e => {
81
67
  model.setIsScalebarRefNameMenuOpen(true);
82
- onOpenMenu({
83
- anchorEl: e.currentTarget,
84
- refName,
85
- displayedRegionIndex,
86
- lastDisplayedRegionIndex,
87
- });
68
+ onOpenMenu({ anchorEl: e.currentTarget, label });
88
69
  }, children: text }));
89
70
  }
90
71
  const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }) {
91
72
  const { displayedRegions } = model;
92
- const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState;
73
+ const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState.label;
93
74
  const numRegions = displayedRegions.length;
94
75
  const labeled = displayedRegions.slice(idx, lastIdx + 1);
95
76
  // A label naming several regions is the collapsed-intron case: adjacent
@@ -104,6 +85,16 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
104
85
  const locString = oneRegion
105
86
  ? assembleLocString(displayedRegions[idx])
106
87
  : assembleLocStrings(labeled);
88
+ // The region this label's blocks were cut out of. Taken over the WHOLE run
89
+ // rather than `labeled`, which the viewport clips: a collapsed-introns view
90
+ // zoomed into two exons would otherwise offer a span that is not the gene's.
91
+ const { first: runFirst, last: runLast } = regionRunBounds(displayedRegions, idx, lastIdx);
92
+ const run = displayedRegions.slice(runFirst, runLast + 1);
93
+ const wholeSpan = {
94
+ ...run[0],
95
+ start: Math.min(...run.map(r => r.start)),
96
+ end: Math.max(...run.map(r => r.end)),
97
+ };
107
98
  return (_jsx(Menu, { anchorEl: menuState.anchorEl, open: true, onClose: onClose, onMenuItemClick: callback => {
108
99
  callback();
109
100
  onClose();
@@ -126,7 +117,31 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
126
117
  ? 'Show only this region'
127
118
  : `Show only ${refName}`,
128
119
  onClick: () => {
129
- model.setDisplayedRegions(labeled);
120
+ model.editDisplayedRegions(withRegionsKept(displayedRegions, idx, lastIdx));
121
+ },
122
+ },
123
+ ]
124
+ : []),
125
+ ...(runLast > runFirst
126
+ ? [
127
+ {
128
+ // Names its subject the way the two rows above it do; the span
129
+ // it lands on is not spelled into the label, which would move
130
+ // under the reader as they navigate.
131
+ label: `Show the whole span of ${refName}`,
132
+ // Focused rather than centre-kept: the rejoined region is one
133
+ // the old viewport never saw, so there is no centre to hold,
134
+ // and showing the span is the whole point of the row.
135
+ onClick: () => {
136
+ model.setDisplayedRegions([
137
+ ...displayedRegions.slice(0, runFirst),
138
+ wholeSpan,
139
+ ...displayedRegions.slice(runLast + 1),
140
+ ]);
141
+ model.moveTo({ index: runFirst, offset: 0 }, {
142
+ index: runFirst,
143
+ offset: wholeSpan.end - wholeSpan.start,
144
+ });
130
145
  },
131
146
  },
132
147
  ]
@@ -156,13 +171,13 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
156
171
  {
157
172
  label: 'Reverse region',
158
173
  onClick: () => {
159
- model.setDisplayedRegions(withRegionReversed(displayedRegions, idx));
174
+ model.editDisplayedRegions(withRegionReversed(displayedRegions, idx));
160
175
  },
161
176
  },
162
177
  ...regionMoveActions(idx, numRegions).map(({ label, to }) => ({
163
178
  label,
164
179
  onClick: () => {
165
- model.setDisplayedRegions(withRegionMoved(displayedRegions, idx, to));
180
+ model.editDisplayedRegions(withRegionMoved(displayedRegions, idx, to));
166
181
  },
167
182
  })),
168
183
  ...(numRegions > 1
@@ -170,7 +185,7 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
170
185
  {
171
186
  label: 'Remove this region from view',
172
187
  onClick: () => {
173
- model.setDisplayedRegions(withRegionRemoved(displayedRegions, idx));
188
+ model.editDisplayedRegions(withRegionRemoved(displayedRegions, idx));
174
189
  },
175
190
  },
176
191
  ]
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { RefNameAutocomplete, RefNameAutocompleteEndAdornment, adornmentReservePx, getInputWidth, useRecentLocations, } from '@jbrowse/core/ui';
3
+ import { usePalette } from '@jbrowse/core/ui/PaletteContext';
4
+ import { alpha } from '@jbrowse/core/ui/palette';
3
5
  import { getSession } from '@jbrowse/core/util';
4
- import { alpha, useTheme } from '@mui/material';
5
6
  import { observer } from 'mobx-react';
6
7
  import { fetchResults, navigateToSelectedOption, notifySearchFailure, } from '../../searchUtils.js';
7
8
  import { SPACING, WIDGET_HEIGHT } from '../consts.js';
@@ -31,7 +32,7 @@ export function searchBoxWidth(value) {
31
32
  2 * SPACING);
32
33
  }
33
34
  const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth = MIN_WIDTH, maxWidth = MAX_WIDTH, style = defaultStyle, }) {
34
- const theme = useTheme();
35
+ const palette = usePalette();
35
36
  const session = getSession(model);
36
37
  const { textSearchManager, assemblyManager } = session;
37
38
  const { assemblyNames } = model;
@@ -50,25 +51,25 @@ const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth
50
51
  const recentMenuItems = recentLocationsMenu({
51
52
  recentLocations,
52
53
  onNavigate: option => {
53
- navigate(option).catch(() => { });
54
+ void navigate(option);
54
55
  },
55
56
  onClear: clearRecentLocations,
56
57
  });
57
58
  return (_jsx(RefNameAutocomplete, { onSelect: option => {
58
- navigate(option).catch(() => { });
59
- }, assemblyName: assemblyName, fetchResults: (queryString, stopToken) => fetchResults({
59
+ void navigate(option);
60
+ }, assemblyName: assemblyName, fetchResults: (queryString, signal) => fetchResults({
60
61
  queryString,
61
62
  assemblyName,
62
63
  textSearchManager,
63
64
  assembly,
64
- stopToken,
65
+ signal,
65
66
  }), session: session, value: model.coarseVisibleLocStrings, minWidth: minWidth, maxWidth: maxWidth, adornmentWidth: adornmentReservePx({
66
67
  showHelp,
67
68
  menuItemCount: recentMenuItems.length,
68
69
  }), style: style, endAdornment: _jsx(RefNameAutocompleteEndAdornment, { showHelp: showHelp, menuItems: recentMenuItems }), inputStyle: {
69
70
  padding: 0,
70
71
  height: WIDGET_HEIGHT,
71
- background: alpha(theme.palette.background.paper, 0.8),
72
+ background: alpha(palette.background.paper, 0.8),
72
73
  } }));
73
74
  });
74
75
  export default SearchBox;
@@ -1,9 +1,10 @@
1
1
  import type { LinearGenomeViewModel } from '../../index.ts';
2
2
  import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
3
- export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, }: {
3
+ export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, onPick, }: {
4
4
  model: LinearGenomeViewModel;
5
5
  assemblyName: string;
6
6
  searchQuery: string;
7
7
  searchResults: BaseResult[];
8
8
  handleClose: () => void;
9
+ onPick: (result: BaseResult) => Promise<unknown>;
9
10
  }): import("react").JSX.Element;
@@ -2,6 +2,6 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { InfoDialog } from '@jbrowse/core/ui';
3
3
  import { Typography } from '@mui/material';
4
4
  import SearchResultsTable from './SearchResultsTable.js';
5
- export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, }) {
6
- return (_jsx(InfoDialog, { open: true, maxWidth: "xl", onClose: handleClose, title: "Search results", children: !searchResults.length ? (_jsxs(Typography, { children: ["No results found for ", _jsx("b", { children: searchQuery })] })) : (_jsxs(_Fragment, { children: [_jsxs(Typography, { children: ["Showing results for ", _jsx("b", { children: searchQuery })] }), _jsx(SearchResultsTable, { model: model, handleClose: handleClose, assemblyName: assemblyName, searchResults: searchResults })] })) }));
5
+ export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, onPick, }) {
6
+ return (_jsx(InfoDialog, { open: true, maxWidth: "xl", onClose: handleClose, title: "Search results", children: !searchResults.length ? (_jsxs(Typography, { children: ["No results found for ", _jsx("b", { children: searchQuery })] })) : (_jsxs(_Fragment, { children: [_jsxs(Typography, { children: ["Showing results for ", _jsx("b", { children: searchQuery })] }), _jsx(SearchResultsTable, { model: model, handleClose: handleClose, assemblyName: assemblyName, searchResults: searchResults, onPick: onPick })] })) }));
7
7
  }
@@ -1,9 +1,10 @@
1
1
  import type { LinearGenomeViewModel } from '../../index.ts';
2
2
  import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
3
- declare const SearchResultsTable: ({ searchResults, assemblyName, model, handleClose, }: {
3
+ declare const SearchResultsTable: ({ searchResults, assemblyName, model, handleClose, onPick, }: {
4
4
  searchResults: BaseResult[];
5
5
  assemblyName: string;
6
6
  model: LinearGenomeViewModel;
7
7
  handleClose: () => void;
8
+ onPick: (result: BaseResult) => Promise<unknown>;
8
9
  }) => import("react").JSX.Element;
9
10
  export default SearchResultsTable;
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
2
3
  import { readConfObject } from '@jbrowse/core/configuration';
3
- import { assembleLocString, getSession, parseLocString, } from '@jbrowse/core/util';
4
+ import { SanitizedHTML } from '@jbrowse/core/ui';
5
+ import { getSession } from '@jbrowse/core/util';
4
6
  import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
5
7
  import { observer } from 'mobx-react';
6
- import { isOpenInView, navToOption } from '../../searchUtils.js';
7
- const SearchResultsTable = observer(function SearchResultsTable({ searchResults, assemblyName, model, handleClose, }) {
8
+ import { isOpenInView } from '../../searchUtils.js';
9
+ const SearchResultsTable = observer(function SearchResultsTable({ searchResults, assemblyName, model, handleClose, onPick, }) {
8
10
  const session = getSession(model);
9
11
  const { assemblyManager } = session;
10
12
  const assembly = assemblyManager.get(assemblyName);
@@ -12,39 +14,20 @@ const SearchResultsTable = observer(function SearchResultsTable({ searchResults,
12
14
  const conf = trackId !== undefined ? session.getTrackById(trackId) : undefined;
13
15
  return conf ? readConfObject(conf, 'name') : '';
14
16
  }
15
- // the raw locString, prettified to the assembly's canonical refName when the
16
- // assembly is loaded and the string parses. A result the assembly can't
17
- // resolve is still listed as-is rather than blowing up the dialog: the Go
18
- // button navigates through navToOption, which reports its own failure
17
+ // A location the assembly can't resolve is listed as-is: Go navigates
18
+ // through navToOption, which reports its own failure
19
19
  function formatLocation(locString) {
20
- if (assembly && locString) {
21
- try {
22
- const loc = parseLocString(locString, refName => assembly.isValidRefName(refName));
23
- return assembleLocString({
24
- ...loc,
25
- refName: assembly.getCanonicalRefName2(loc.refName),
26
- });
27
- }
28
- catch (e) {
29
- console.warn('failed to parse location string', locString, e);
30
- return locString;
31
- }
32
- }
33
- else {
34
- return locString;
35
- }
20
+ return assembly && locString
21
+ ? canonicalLocString(locString, assembly)
22
+ : locString;
36
23
  }
37
24
  // A hit in a track that is already on screen is usually the one meant, so it
38
25
  // is listed first rather than given a control that says so. sort is stable,
39
26
  // so everything else keeps the ranked order it arrived in.
40
27
  const ordered = [...searchResults].sort((a, b) => Number(isOpenInView(b, model)) - Number(isOpenInView(a, model)));
41
- return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: ordered.map(result => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: formatLocation(result.getLocation()) }), _jsx(TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
28
+ return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: ordered.map(result => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: formatLocation(result.getLocation()) }), _jsx(TableCell, { align: "right", children: _jsx(SanitizedHTML, { html: getTrackName(result.getTrackId()) || 'N/A' }) }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
42
29
  try {
43
- await navToOption({
44
- option: result,
45
- model,
46
- assemblyName,
47
- });
30
+ await onPick(result);
48
31
  }
49
32
  catch (e) {
50
33
  console.error(e);
@@ -1,8 +1,6 @@
1
+ import type { SequenceSearchModel } from './searchModes.ts';
1
2
  declare const SequenceSearchDialog: ({ model, handleClose, }: {
2
- model: {
3
- assemblyNames: string[];
4
- showTrack: (trackId: string) => void;
5
- };
3
+ model: SequenceSearchModel;
6
4
  handleClose: () => void;
7
5
  }) => import("react").JSX.Element;
8
6
  export default SequenceSearchDialog;
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
2
3
  import { ErrorBanner, ResizeHandle } from '@jbrowse/core/ui';
3
4
  import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary';
4
5
  import { cx, makeStyles } from '@jbrowse/core/util/tss-react';
5
6
  import { TrackOverlaySlot } from '@jbrowse/display-ui';
7
+ import { isAlive } from '@jbrowse/mobx-state-tree';
6
8
  import { Paper } from '@mui/material';
7
9
  import { observer } from 'mobx-react';
8
10
  import { RESIZE_HANDLE_HEIGHT, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from '../consts.js';
@@ -16,8 +18,8 @@ const useStyles = makeStyles()({
16
18
  // described in TrackRenderingContainer. flow-root keeps the block formatting
17
19
  // context that `overflow: hidden` used to establish as a side effect (neither
18
20
  // `clip` nor no overflow does), so child margins still can't collapse through
19
- // and shift the track — which the breakpoint split view would pick up, since
20
- // its connector overlay measures trackRefs' getBoundingClientRect().top.
21
+ // and shift the track — which would put the pixels off the model's offset
22
+ // arithmetic, which the breakpoint split view draws its connectors from.
21
23
  root: {
22
24
  marginTop: TRACK_TOP_GAP,
23
25
  position: 'relative',
@@ -56,17 +58,62 @@ const useStyles = makeStyles()({
56
58
  // spends the other 4 on clearance.
57
59
  trackLabelOffset: {
58
60
  position: 'relative',
59
- display: 'inline-block',
60
61
  marginBottom: 8,
61
62
  },
62
63
  trackLabelOverlap: {
63
64
  position: 'absolute',
64
65
  },
66
+ // in flow, so the track keeps the banner's height, and raised over the
67
+ // `PaddingBlocks` painted after it
68
+ crashBanner: {
69
+ position: 'relative',
70
+ zIndex: 1,
71
+ },
65
72
  });
73
+ // Measures the in-flow label band, the distance from the Paper's content top to
74
+ // the rendering container, into the model. Observes the Paper: it grows and
75
+ // shrinks with the label, the label setting, and the display, and each of those
76
+ // can move the container. Published after a requestAnimationFrame like
77
+ // useWidthSetter, and cleared on unmount so a removed track leaves no band.
78
+ function useTrackLabelBand(model, trackId) {
79
+ const [paper, setPaper] = useState(null);
80
+ const [container, setContainer] = useState(null);
81
+ useEffect(() => {
82
+ if (paper && container && 'ResizeObserver' in window) {
83
+ let token;
84
+ const publish = () => {
85
+ token = requestAnimationFrame(() => {
86
+ if (isAlive(model)) {
87
+ const paperRect = paper.getBoundingClientRect();
88
+ const band = container.getBoundingClientRect().top -
89
+ paperRect.top -
90
+ paper.clientTop;
91
+ model.setTrackLabelBand(trackId, band);
92
+ }
93
+ });
94
+ };
95
+ publish();
96
+ const observer = new ResizeObserver(publish);
97
+ observer.observe(paper);
98
+ return () => {
99
+ observer.disconnect();
100
+ if (token !== undefined) {
101
+ cancelAnimationFrame(token);
102
+ }
103
+ if (isAlive(model)) {
104
+ model.setTrackLabelBand(trackId, 0);
105
+ }
106
+ };
107
+ }
108
+ return undefined;
109
+ }, [paper, container, model, trackId]);
110
+ return { setPaper, setContainer };
111
+ }
66
112
  const TrackContainer = observer(function TrackContainer({ model, track, }) {
67
113
  const { classes } = useStyles();
68
114
  // an LGV track always holds at least one linear display (activeDisplay =
69
- // displays[0]); narrow to the linear shape for prefersOffset/resizeHeight
115
+ // displays[0]). The one narrowing to the linear shape, handed down rather
116
+ // than repeated, so a track's chrome and its body can't read two displays.
70
117
  const display = track.activeDisplay;
71
118
  const { showTrackOutlines } = model;
72
119
  // The Paper's 1px border, present iff outlines are on. Everything laid out in
@@ -76,12 +123,19 @@ const TrackContainer = observer(function TrackContainer({ model, track, }) {
76
123
  // the data. One binding rather than three copies of the ternary, because the
77
124
  // three agreeing is the whole point.
78
125
  const outlineOffset = showTrackOutlines ? TRACK_OUTLINE_BORDER : 0;
79
- const trackLabelStyle = model.effectiveTrackLabels !== 'overlapping' || display.prefersOffset
80
- ? classes.trackLabelOffset
81
- : classes.trackLabelOverlap;
82
- return (_jsxs(Paper, { className: cx(classes.root, track.pinned ? null : classes.unpinnedTrack), variant: showTrackOutlines ? 'outlined' : undefined, elevation: showTrackOutlines ? undefined : 0, onDragOver: event => {
126
+ // A label in flow is a row of track height, which is most of what minimizing
127
+ // buys back — so a minimized track overlaps its label whatever the setting
128
+ // says, and there is nothing under it to print over. `prefersOffset` is the
129
+ // display asking for the offset row in 'overlapping' mode, because its left
130
+ // edge carries an axis or a sidebar the label would cover.
131
+ const trackLabelStyle = track.minimized ||
132
+ (model.effectiveTrackLabels === 'overlapping' && !display.prefersOffset)
133
+ ? classes.trackLabelOverlap
134
+ : classes.trackLabelOffset;
135
+ const { setPaper, setContainer } = useTrackLabelBand(model, track.trackId);
136
+ return (_jsxs(Paper, { ref: setPaper, className: cx(classes.root, track.pinned ? null : classes.unpinnedTrack), variant: showTrackOutlines ? 'outlined' : undefined, elevation: showTrackOutlines ? undefined : 0, onDragOver: event => {
83
137
  model.onTrackDragOver(track.id, event.clientY);
84
- }, children: [track.pinned ? _jsx(Gridlines, { model: model, offset: outlineOffset }) : null, model.effectiveTrackLabels !== 'hidden' ? (_jsx(TrackLabel, { track: track, className: trackLabelStyle })) : null, _jsx(TrackOverlaySlot, { zIndex: 100, overlayStyle: { left: -outlineOffset }, children: _jsx(ErrorBoundary, { resetKeys: [display.id], FallbackComponent: e => (_jsx(ErrorBanner, { error: e.error, onReset: e.resetErrorBoundary })), children: _jsx(TrackRenderingContainer, { model: model, track: track }) }) }), _jsx(PaddingBlocks, { model: model, offset: outlineOffset }), _jsx(ResizeHandle, { style: { height: RESIZE_HANDLE_HEIGHT }, onDrag: distance => display.resizeHeight(distance),
138
+ }, children: [track.pinned ? _jsx(Gridlines, { model: model, offset: outlineOffset }) : null, model.effectiveTrackLabels !== 'hidden' ? (_jsx(TrackLabel, { track: track, className: trackLabelStyle })) : null, _jsx(TrackOverlaySlot, { zIndex: 100, overlayStyle: { left: -outlineOffset }, children: _jsx(ErrorBoundary, { FallbackComponent: e => (_jsx("div", { className: classes.crashBanner, children: _jsx(ErrorBanner, { error: e.error, onReset: e.resetErrorBoundary }) })), children: _jsx(TrackRenderingContainer, { model: model, track: track, display: display, ref: setContainer }) }, display.id) }), _jsx(PaddingBlocks, { model: model, offset: outlineOffset }), _jsx(ResizeHandle, { style: { height: RESIZE_HANDLE_HEIGHT }, onDrag: distance => display.resizeHeight(distance),
85
139
  // the drag's shortcut: grow the track by whatever it is scrolled over,
86
140
  // so a pileup showing three rows of forty opens to all forty
87
141
  // Optional: only a display that scrolls its own content has anything
@@ -14,16 +14,23 @@ const useStyles = makeStyles()(theme => ({
14
14
  // above breakpoint split view
15
15
  zIndex: 200,
16
16
  background: theme.palette.background.paper,
17
+ display: 'inline-flex',
18
+ alignItems: 'center',
19
+ maxWidth: 'calc(100% - 16px)',
17
20
  },
21
+ // one line: a long name wrapping over a narrow view covered the data below
18
22
  trackName: {
19
- margin: '0 auto',
20
- width: '90%',
23
+ margin: theme.spacing(0, 0.5),
24
+ minWidth: 0,
25
+ overflow: 'hidden',
26
+ textOverflow: 'ellipsis',
27
+ whiteSpace: 'nowrap',
21
28
  pointerEvents: 'none',
22
29
  },
23
30
  }));
24
31
  const TrackLabel = observer(function TrackLabel({ track, className, }) {
25
32
  const { classes } = useStyles();
26
33
  const trackName = getTrackName(track.configuration, getSession(track));
27
- return (_jsxs(Paper, { className: cx(className, classes.root), children: [_jsx(TrackLabelDragHandle, { track: track }), _jsx(TrackLabelCloseButton, { track: track }), _jsx(TrackLabelMinimizeButton, { track: track }), _jsx(Typography, { variant: "body2", component: "span", className: classes.trackName, children: _jsx(SanitizedHTML, { html: trackName }) }), _jsx(TrackLabelRefNameWarning, { track: track }), _jsx(TrackLabelMenu, { track: track })] }));
34
+ return (_jsxs(Paper, { className: cx(className, classes.root), "data-testid": `trackLabel-${track.trackId}`, children: [_jsx(TrackLabelDragHandle, { track: track }), _jsx(TrackLabelCloseButton, { track: track }), _jsx(TrackLabelMinimizeButton, { track: track }), _jsx(Typography, { variant: "body2", component: "span", className: classes.trackName, children: _jsx(SanitizedHTML, { html: trackName }) }), _jsx(TrackLabelRefNameWarning, { track: track }), _jsx(TrackLabelMenu, { track: track })] }));
28
35
  });
29
36
  export default TrackLabel;