@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
@@ -1,6 +1,14 @@
1
1
  import type { AssemblyManager, ParsedLocString } from '@jbrowse/core/util';
2
+ import type { PxToBpResult } from '@jbrowse/core/util/Base1DUtils';
2
3
  import type { BaseBlock, ContentBlock } from '@jbrowse/core/util/blockTypes';
3
4
  import type { Region } from '@jbrowse/core/util/types';
5
+ interface CenterKeepingView {
6
+ width: number;
7
+ displayedRegions: Region[];
8
+ pxToBp: (px: number) => PxToBpResult;
9
+ setDisplayedRegions: (regions: Region[]) => void;
10
+ centerAt: (coord: number, refName: string, index?: number) => void;
11
+ }
4
12
  /**
5
13
  * The stored viewport of a view showing `totalBp` of displayed regions fitted to
6
14
  * `width` — the regions filling it edge to edge, and centered in it only where
@@ -64,7 +72,7 @@ export interface Tick {
64
72
  * `makeBlockTicks`. What the two do share is already shared: `chooseGridPitch`,
65
73
  * and `base` as the 0-based coordinate that `getTickDisplayStr` labels `base+1`.
66
74
  */
67
- export declare function makeTicks(start: number, end: number, bpPerPx: number, emitMajor?: boolean, emitMinor?: boolean): Tick[];
75
+ export declare function makeTicks(start: number, end: number, bpPerPx: number, includeMinor?: boolean): Tick[];
68
76
  /** A block's refName, or undefined for non-content (elided/padding) blocks. */
69
77
  export declare function getBlockRefName(block: BaseBlock): string | undefined;
70
78
  /**
@@ -104,48 +112,31 @@ export interface ScalebarRefNameLabel {
104
112
  }
105
113
  /**
106
114
  * Builds the refName labels drawn along the scalebar as plain data (no JSX): one
107
- * label per run of same-refName regions (deduped so collapsed introns don't
108
- * repeat the name) plus a "sticky" label pinned to the left edge naming the
109
- * refName under the viewport's left border.
110
- *
111
- * `caption` is the row-level chip at the viewport's left edge, and it says two
112
- * things: which assembly this row is (`prefix`, an assembly name, synteny only)
113
- * and whether the row is flipped. Without it a row of stacked genomes would
114
- * label some rows with the assembly name and some without, depending only on
115
- * how wide their leftmost chromosome happens to be.
115
+ * label per run of same-refName regions, the leftmost of them "sticky" — pinned
116
+ * so the run under the left edge stays named once its own left edge scrolls off.
116
117
  *
117
- * The assembly name alone folds into the sticky label instead — "prefix:refName"
118
- * — whenever the two would collide at the left edge, and stands alone when the
119
- * sticky label sits far enough right not to (a view scrolled left of its first
120
- * region, so the row's data starts mid-viewport) or when there is no sticky
121
- * label at all.
118
+ * `caption` is the row-level chip at the viewport's left edge: which assembly
119
+ * the row is (`prefix`, synteny only) and whether the whole row is flipped. The
120
+ * chip takes `captionSpanPx` of the strip and the labels get the rest, so the
121
+ * sticky label pins to the chip's right edge ("hg38" then "chr1") and rides the
122
+ * run under THAT edge. A run the chip covers entirely goes unnamed, and the
123
+ * chip is not shortened to uncover it: a width fitted to the run under it
124
+ * changes with every run that pans past, and a fixed cap short enough to
125
+ * uncover a 55px chromosome cuts ordinary names. The assembly's `displayName`
126
+ * is where a long name gets shortened.
122
127
  *
123
- * `orientation` decides the ` [rev]` marker, and it is the row's
124
- * (`displayedRegionsOrientation`) rather than anything read off the blocks —
125
- * blocks cover the window, and a row is flipped or not whatever part of it you
126
- * are looking at. The rule is `assembleLocStrings`': say it where it
127
- * distinguishes, once where it does not.
128
+ * `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
129
+ * it where it distinguishes, once where it does not.
128
130
  *
129
- * - `reversed`: the whole row is flipped, which is a fact about the row, so the
130
- * caption carries it and NO name does. The caption cannot fold in this state
131
- * — the sticky label is pushed clear of it instead — because a marker sitting
132
- * on a chromosome name is how the mixed case spells a fact about that one
133
- * region. `chr1 [rev] | chr2` would otherwise mean both "the row is flipped,
134
- * said once" and "chr1 is flipped and chr2 is not", in identical pixels.
135
- * - `mixed`: only some regions are flipped — reachable through the label menu's
136
- * per-region "Reverse region" — so every reversed run is marked and the
137
- * forward ones are left alone. Here the marker on a name is the only thing
138
- * telling two labels apart.
131
+ * - `reversed`: the caption carries the marker and no name does. A marker on a
132
+ * chromosome name is how the mixed case flags that one region.
133
+ * - `mixed`: every reversed run is marked. A marked label that fails the fit
134
+ * test falls back to the bare name rather than dropping.
139
135
  * - `forward`: no marker.
140
136
  *
141
- * Saying it once is the point: a flipped whole-genome row would otherwise
142
- * repeat `[rev]` after all 24 chromosomes to say a single thing about the row.
143
- *
144
- * Under `mixed` the marker never costs a name: a suffixed label that fails the
145
- * fit test falls back to the bare name rather than being dropped. Widening the
146
- * text moves the region width a name needs from ~30px to ~56px at 11px bold,
147
- * and a feature meant to surface information should not be deleting chromosome
148
- * names in the narrow half of that gap.
137
+ * `maxWidth` is the whole label box, `paddingLeft` included — both consumers
138
+ * clip from the box's left edge. A name is drawn whole or not at all: clipped
139
+ * mid-glyph, "chr16" reads as "chr1".
149
140
  */
150
141
  export declare function getScalebarRefNameLabels({ blocks, offsetPx, prefix, orientation, }: {
151
142
  blocks: BaseBlock[];
@@ -192,13 +183,35 @@ export declare function regionMoveActions(idx: number, numRegions: number): {
192
183
  to: number;
193
184
  }[];
194
185
  /**
195
- * The region reorderings offered by the refName-label menu, as pure list
196
- * transforms feeding setDisplayedRegions (which re-clamps bpPerPx/offsetPx for
197
- * the new region set).
186
+ * A new region list, and where each old index went in it (-1 for a region it
187
+ * dropped). The index map is what lets a region displayed twice be followed.
198
188
  */
199
- export declare function withRegionMoved(regions: Region[], from: number, to: number): Region[];
200
- export declare function withRegionRemoved(regions: Region[], index: number): Region[];
201
- export declare function withRegionReversed(regions: Region[], index: number): Region[];
189
+ export interface RegionEdit {
190
+ regions: Region[];
191
+ newIndexOf: (oldIndex: number) => number;
192
+ }
193
+ export declare function withRegionMoved(regions: Region[], from: number, to: number): RegionEdit;
194
+ export declare function withRegionRemoved(regions: Region[], index: number): RegionEdit;
195
+ export declare function withRegionsKept(regions: Region[], first: number, last: number): RegionEdit;
196
+ export declare function withRegionReversed(regions: Region[], index: number): RegionEdit;
197
+ /**
198
+ * Apply a region edit and keep the base under the middle of the viewport
199
+ * there, where the edit kept its region. `setDisplayedRegions` alone carries
200
+ * `offsetPx` into a coordinate space the edit redefined, which lands on
201
+ * whatever region now sits at that offset.
202
+ */
203
+ export declare function setDisplayedRegionsKeepingCenter(model: CenterKeepingView, { regions, newIndexOf }: RegionEdit): void;
204
+ /**
205
+ * The full run of same-orientation regions on one refName that a scalebar
206
+ * label's `firstRegionIndex`..`lastRegionIndex` sits inside. Those come off the
207
+ * static blocks, which are clipped to the viewport and broken by an elided
208
+ * block, so a zoomed-in collapsed-introns view brackets only the exons on
209
+ * screen — which is the wrong answer for anything acting on the whole run.
210
+ */
211
+ export declare function regionRunBounds(regions: Region[], firstIndex: number, lastIndex: number): {
212
+ first: number;
213
+ last: number;
214
+ };
202
215
  /**
203
216
  * Whether a label occupying [leftPx, leftPx + labelWidth] fits within a block
204
217
  * of the given pixel width, used to skip tick labels that would be clipped at a
@@ -264,6 +277,7 @@ export interface BlockRun {
264
277
  reversed: boolean;
265
278
  refName: string;
266
279
  isLeftEndOfDisplayedRegion: boolean;
280
+ labeled: boolean;
267
281
  }
268
282
  export declare function groupContiguousBlocks(blocks: BaseBlock[]): BlockRun[];
269
283
  /**
@@ -300,7 +314,7 @@ export declare function makeBlockTicks({ start, end, reversed, }: {
300
314
  start: number;
301
315
  end: number;
302
316
  reversed?: boolean;
303
- }, bpPerPx: number, emitMajor?: boolean, emitMinor?: boolean): {
317
+ }, bpPerPx: number, includeMinor?: boolean): {
304
318
  type: 'major' | 'minor';
305
319
  base: number;
306
320
  x: number;
@@ -348,3 +362,4 @@ export declare function generateLocations({ regions, assemblyManager, assemblyNa
348
362
  */
349
363
  export declare function parseLocStrings(input: string, assemblyName: string, isValidRefName: (str: string, assemblyName: string) => boolean): ParsedLocString[];
350
364
  export declare function calculateVisibleLocStrings(contentBlocks: ContentBlock[]): string;
365
+ export {};
@@ -89,7 +89,7 @@ export function makeOverviewTicks(start, end, bpPerPx, reversed = false) {
89
89
  * `makeBlockTicks`. What the two do share is already shared: `chooseGridPitch`,
90
90
  * and `base` as the 0-based coordinate that `getTickDisplayStr` labels `base+1`.
91
91
  */
92
- export function makeTicks(start, end, bpPerPx, emitMajor = true, emitMinor = true) {
92
+ export function makeTicks(start, end, bpPerPx, includeMinor = true) {
93
93
  // Ask for the ~200px spacing this ruler actually wants, and mark majors at
94
94
  // that pitch. This used to ask for 60px and then mark majors every *two*
95
95
  // pitches, which spaced them the same but off chooseGridPitch's 1/2/5 ladder:
@@ -106,12 +106,12 @@ export function makeTicks(start, end, bpPerPx, emitMajor = true, emitMinor = tru
106
106
  const iterPitch = minorPitch || majorPitch;
107
107
  const ticks = [];
108
108
  for (let base = Math.floor(minBase / iterPitch) * iterPitch; base < Math.ceil(maxBase / iterPitch) * iterPitch + 1; base += iterPitch) {
109
- if (emitMinor && base % majorPitch) {
110
- ticks.push({ type: 'minor', base: base - 1 });
111
- }
112
- else if (emitMajor && !(base % majorPitch)) {
109
+ if (!(base % majorPitch)) {
113
110
  ticks.push({ type: 'major', base: base - 1 });
114
111
  }
112
+ else if (includeMinor) {
113
+ ticks.push({ type: 'minor', base: base - 1 });
114
+ }
115
115
  }
116
116
  return ticks;
117
117
  }
@@ -177,13 +177,12 @@ function refNameRuns(blocks) {
177
177
  return runs;
178
178
  }
179
179
  /**
180
- * Index of the run carrying the "sticky" refName label pinned to the left edge:
181
- * the rightmost run whose left edge has scrolled off the left of the viewport,
182
- * or the first run when none have.
180
+ * Index of the run carrying the "sticky" refName label pinned to the strip's
181
+ * left edge: the rightmost run starting left of `leftEdgePx` (block frame), or
182
+ * the first run when none does.
183
183
  */
184
- function stickyRunIndex(runs, offsetPx) {
185
- const scrolledOff = runs.findLastIndex(run => run.offsetPx < offsetPx);
186
- return scrolledOff === -1 ? 0 : scrolledOff;
184
+ function stickyRunIndex(runs, leftEdgePx) {
185
+ return Math.max(0, runs.findLastIndex(run => run.offsetPx < leftEdgePx));
187
186
  }
188
187
  export function regionsOrientation(regions) {
189
188
  const first = !!regions[0]?.reversed;
@@ -202,161 +201,79 @@ export function regionsOrientation(regions) {
202
201
  * upstream is whole-or-nothing, and it would go on believing a glyph fits.
203
202
  */
204
203
  const REV_MARKER = ' [rev]';
205
- /** Clearance between the standalone assembly-name chip and a sticky label. */
206
- const PREFIX_GAP = 4;
207
- /** Left inset of a non-sticky refName label from its region's left edge. */
208
- const REF_NAME_LABEL_PADDING_PX = 7;
204
+ /** Clearance between the caption chip and the sticky label after it. */
205
+ const CAPTION_GAP = 4;
209
206
  /**
210
- * translateX, maxWidth and paddingLeft for one refName label. Sticky labels
211
- * start at the viewport's left edge; others start at their run's left edge.
212
- *
213
- * maxWidth is the width of the whole label box, paddingLeft included, so the
214
- * text has `maxWidth - paddingLeft` to draw in — that is what both consumers
215
- * clip to (a border-box max-width on the span, an SVG clip rect from the box's
216
- * own left edge). The caller decides whether the name it wants to draw fits in
217
- * that (see refNameLabelWidth); a name is drawn whole or not at all.
207
+ * Left inset of a non-sticky refName label from its region's left edge, clear
208
+ * of the 3px region divider drawn there.
218
209
  */
219
- function refLabelLayout({ run, offsetPx, sticky,
220
- // left-edge x a sticky label may not start before, so a caption chip drawn
221
- // there keeps the strip to itself. Zero wherever the caption folds into the
222
- // label instead (the assembly prefix does)
223
- minTransform = 0, }) {
224
- const transform = sticky
225
- ? Math.max(minTransform, -offsetPx)
226
- : run.offsetPx - offsetPx - 1;
227
- // block-frame x where the label actually starts. Derived from `transform` (=
228
- // transform + offsetPx) so the width-to-run-end clip stays in lockstep with
229
- // where the label is drawn: a sticky label pins to the run's left edge, not
230
- // the viewport's, whenever the view is left-overscrolled (offsetPx < 0) —
231
- // reading offsetPx directly there over-counted the available width by
232
- // |offsetPx|, letting the name bleed past its run's right edge.
233
- const labelStartPx = transform + offsetPx;
234
- // A non-sticky label's transform anchors at the same x as the region
235
- // divider drawn just to its left (SVGRegionSeparators, a 3px bar spanning
236
- // local [0,3] from this same run.offsetPx edge), so paddingLeft must clear
237
- // that bar plus a few px of breathing room, else the text visually touches
238
- // the divider. Sticky labels sit at the viewport's own left edge, no divider.
239
- const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
240
- return { transform, maxWidth: run.endPx - labelStartPx - 1, paddingLeft };
241
- }
210
+ const REF_NAME_LABEL_PADDING_PX = 7;
242
211
  /**
243
212
  * Builds the refName labels drawn along the scalebar as plain data (no JSX): one
244
- * label per run of same-refName regions (deduped so collapsed introns don't
245
- * repeat the name) plus a "sticky" label pinned to the left edge naming the
246
- * refName under the viewport's left border.
247
- *
248
- * `caption` is the row-level chip at the viewport's left edge, and it says two
249
- * things: which assembly this row is (`prefix`, an assembly name, synteny only)
250
- * and whether the row is flipped. Without it a row of stacked genomes would
251
- * label some rows with the assembly name and some without, depending only on
252
- * how wide their leftmost chromosome happens to be.
213
+ * label per run of same-refName regions, the leftmost of them "sticky" — pinned
214
+ * so the run under the left edge stays named once its own left edge scrolls off.
253
215
  *
254
- * The assembly name alone folds into the sticky label instead — "prefix:refName"
255
- * — whenever the two would collide at the left edge, and stands alone when the
256
- * sticky label sits far enough right not to (a view scrolled left of its first
257
- * region, so the row's data starts mid-viewport) or when there is no sticky
258
- * label at all.
216
+ * `caption` is the row-level chip at the viewport's left edge: which assembly
217
+ * the row is (`prefix`, synteny only) and whether the whole row is flipped. The
218
+ * chip takes `captionSpanPx` of the strip and the labels get the rest, so the
219
+ * sticky label pins to the chip's right edge ("hg38" then "chr1") and rides the
220
+ * run under THAT edge. A run the chip covers entirely goes unnamed, and the
221
+ * chip is not shortened to uncover it: a width fitted to the run under it
222
+ * changes with every run that pans past, and a fixed cap short enough to
223
+ * uncover a 55px chromosome cuts ordinary names. The assembly's `displayName`
224
+ * is where a long name gets shortened.
259
225
  *
260
- * `orientation` decides the ` [rev]` marker, and it is the row's
261
- * (`displayedRegionsOrientation`) rather than anything read off the blocks —
262
- * blocks cover the window, and a row is flipped or not whatever part of it you
263
- * are looking at. The rule is `assembleLocStrings`': say it where it
264
- * distinguishes, once where it does not.
226
+ * `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
227
+ * it where it distinguishes, once where it does not.
265
228
  *
266
- * - `reversed`: the whole row is flipped, which is a fact about the row, so the
267
- * caption carries it and NO name does. The caption cannot fold in this state
268
- * — the sticky label is pushed clear of it instead — because a marker sitting
269
- * on a chromosome name is how the mixed case spells a fact about that one
270
- * region. `chr1 [rev] | chr2` would otherwise mean both "the row is flipped,
271
- * said once" and "chr1 is flipped and chr2 is not", in identical pixels.
272
- * - `mixed`: only some regions are flipped — reachable through the label menu's
273
- * per-region "Reverse region" — so every reversed run is marked and the
274
- * forward ones are left alone. Here the marker on a name is the only thing
275
- * telling two labels apart.
229
+ * - `reversed`: the caption carries the marker and no name does. A marker on a
230
+ * chromosome name is how the mixed case flags that one region.
231
+ * - `mixed`: every reversed run is marked. A marked label that fails the fit
232
+ * test falls back to the bare name rather than dropping.
276
233
  * - `forward`: no marker.
277
234
  *
278
- * Saying it once is the point: a flipped whole-genome row would otherwise
279
- * repeat `[rev]` after all 24 chromosomes to say a single thing about the row.
280
- *
281
- * Under `mixed` the marker never costs a name: a suffixed label that fails the
282
- * fit test falls back to the bare name rather than being dropped. Widening the
283
- * text moves the region width a name needs from ~30px to ~56px at 11px bold,
284
- * and a feature meant to surface information should not be deleting chromosome
285
- * names in the narrow half of that gap.
235
+ * `maxWidth` is the whole label box, `paddingLeft` included — both consumers
236
+ * clip from the box's left edge. A name is drawn whole or not at all: clipped
237
+ * mid-glyph, "chr16" reads as "chr1".
286
238
  */
287
239
  export function getScalebarRefNameLabels({ blocks, offsetPx, prefix, orientation = 'forward', }) {
288
- const hasPrefix = prefix !== undefined && prefix !== '';
289
- const rowIsReversed = orientation === 'reversed';
290
- const captionText = rowIsReversed
291
- ? `${hasPrefix ? prefix : ''}${REV_MARKER}`.trimStart()
292
- : prefix;
293
- // the caption chip occupies the viewport's left edge, so a sticky label
294
- // starting inside that span has to yield: absorb the assembly name, or, where
295
- // the caption carries the row's orientation and so cannot fold, start after
296
- // it. The gap is part of the span: a sticky label carries no padding of its
297
- // own, so one starting at exactly the chip's right edge abuts it and the two
298
- // read as a single word ("volvoxctgA") for the few px of scroll around the
299
- // threshold
300
- const captionSpanPx = captionText
301
- ? refNameLabelWidth(captionText) + PREFIX_GAP
302
- : 0;
240
+ const caption = [prefix, orientation === 'reversed' ? REV_MARKER.trim() : undefined]
241
+ .filter(Boolean)
242
+ .join(' ') || undefined;
243
+ const captionSpanPx = caption ? refNameLabelWidth(caption) + CAPTION_GAP : 0;
303
244
  const runs = refNameRuns(blocks);
304
- const stickyIdx = stickyRunIndex(runs, offsetPx);
245
+ const stickyIdx = stickyRunIndex(runs, offsetPx + captionSpanPx);
305
246
  const labels = [];
306
- let stickyHasPrefix = false;
307
247
  for (const [i, run] of runs.entries()) {
308
248
  const sticky = i === stickyIdx;
309
- // A non-sticky run starting left of the viewport is entirely off-screen:
310
- // the sticky run is by definition the rightmost one starting there, so
311
- // every earlier run also *ends* left of the viewport. Its label would just
312
- // repeat the name the sticky label already shows, drawn off-canvas —
313
- // invisible on screen (overflow:clip) but bleeding into the margin of an
314
- // SVG export, which has no such clip.
315
- const runStart = run.offsetPx >= offsetPx && run.isLeftEndOfDisplayedRegion;
316
- if (!(sticky || runStart)) {
249
+ // runs left of the sticky one end under the caption or off screen
250
+ if (i < stickyIdx || !(sticky || run.isLeftEndOfDisplayedRegion)) {
317
251
  continue;
318
252
  }
319
- const layout = refLabelLayout({
320
- run,
321
- offsetPx,
322
- sticky,
323
- minTransform: rowIsReversed ? captionSpanPx : 0,
324
- });
325
- const withPrefix = !rowIsReversed && sticky && hasPrefix && layout.transform < captionSpanPx;
326
- const name = withPrefix ? `${prefix}:${run.refName}` : run.refName;
327
- // draw the name whole or not at all: a name clipped mid-glyph reads as a
328
- // different chromosome ("LG2" cut to "LG"), and measuring it means a short
329
- // name gets its label in a region a fixed minimum width would have skipped.
330
- // The marker is the one part that degrades instead of dropping the label
253
+ const runStartPx = run.offsetPx - offsetPx;
254
+ const transform = sticky
255
+ ? Math.max(captionSpanPx, runStartPx)
256
+ : runStartPx - 1;
257
+ const paddingLeft = sticky ? 0 : REF_NAME_LABEL_PADDING_PX;
258
+ const maxWidth = run.endPx - offsetPx - transform - 1;
331
259
  const text = (orientation === 'mixed' && run.reversed
332
- ? [`${name}${REV_MARKER}`, name]
333
- : [name]).find(candidate => layout.maxWidth >= layout.paddingLeft + refNameLabelWidth(candidate));
334
- if (text === undefined) {
335
- continue;
260
+ ? [`${run.refName}${REV_MARKER}`, run.refName]
261
+ : [run.refName]).find(candidate => maxWidth >= paddingLeft + refNameLabelWidth(candidate));
262
+ if (text !== undefined) {
263
+ labels.push({
264
+ key: run.key,
265
+ refName: run.refName,
266
+ displayedRegionIndex: run.firstRegionIndex,
267
+ lastDisplayedRegionIndex: run.lastRegionIndex,
268
+ sticky,
269
+ transform,
270
+ maxWidth,
271
+ paddingLeft,
272
+ text,
273
+ });
336
274
  }
337
- stickyHasPrefix ||= withPrefix;
338
- labels.push({
339
- key: run.key,
340
- refName: run.refName,
341
- displayedRegionIndex: run.firstRegionIndex,
342
- lastDisplayedRegionIndex: run.lastRegionIndex,
343
- sticky,
344
- transform: layout.transform,
345
- maxWidth: layout.maxWidth,
346
- paddingLeft: layout.paddingLeft,
347
- text,
348
- });
349
275
  }
350
- return {
351
- labels,
352
- // the chip to draw at the left edge, or undefined for none. A caption
353
- // carrying the orientation always draws; one carrying only the assembly
354
- // name draws when no sticky label folded it in
355
- caption: rowIsReversed || (hasPrefix && !stickyHasPrefix)
356
- ? captionText
357
- : undefined,
358
- captionSpanPx,
359
- };
276
+ return { labels, caption, captionSpanPx };
360
277
  }
361
278
  /**
362
279
  * Screen-x span a refName label paints over: its padding and its glyphs, which
@@ -404,22 +321,74 @@ export function regionMoveActions(idx, numRegions) {
404
321
  },
405
322
  ].flatMap(({ when, label, to }) => (when ? [{ label, to }] : []));
406
323
  }
407
- /**
408
- * The region reorderings offered by the refName-label menu, as pure list
409
- * transforms feeding setDisplayedRegions (which re-clamps bpPerPx/offsetPx for
410
- * the new region set).
411
- */
412
324
  export function withRegionMoved(regions, from, to) {
413
325
  const out = [...regions];
414
326
  const [moved] = out.splice(from, 1);
415
327
  out.splice(to, 0, moved);
416
- return out;
328
+ return {
329
+ regions: out,
330
+ newIndexOf: i => i === from
331
+ ? to
332
+ : i - (i > from && i <= to ? 1 : 0) + (i >= to && i < from ? 1 : 0),
333
+ };
417
334
  }
418
335
  export function withRegionRemoved(regions, index) {
419
- return regions.filter((_, i) => i !== index);
336
+ return {
337
+ regions: regions.filter((_, i) => i !== index),
338
+ newIndexOf: i => (i === index ? -1 : i > index ? i - 1 : i),
339
+ };
340
+ }
341
+ export function withRegionsKept(regions, first, last) {
342
+ return {
343
+ regions: regions.slice(first, last + 1),
344
+ newIndexOf: i => (i >= first && i <= last ? i - first : -1),
345
+ };
420
346
  }
421
347
  export function withRegionReversed(regions, index) {
422
- return regions.map((region, i) => i === index ? { ...region, reversed: !region.reversed } : region);
348
+ return {
349
+ regions: regions.map((region, i) => i === index ? { ...region, reversed: !region.reversed } : region),
350
+ newIndexOf: i => i,
351
+ };
352
+ }
353
+ /**
354
+ * Apply a region edit and keep the base under the middle of the viewport
355
+ * there, where the edit kept its region. `setDisplayedRegions` alone carries
356
+ * `offsetPx` into a coordinate space the edit redefined, which lands on
357
+ * whatever region now sits at that offset.
358
+ */
359
+ export function setDisplayedRegionsKeepingCenter(model, { regions, newIndexOf }) {
360
+ const center = model.displayedRegions.length
361
+ ? model.pxToBp(model.width / 2)
362
+ : undefined;
363
+ model.setDisplayedRegions(regions);
364
+ const index = center ? newIndexOf(center.index) : -1;
365
+ if (center && index !== -1) {
366
+ model.centerAt(center.coord0, center.refName, index);
367
+ }
368
+ }
369
+ /**
370
+ * The full run of same-orientation regions on one refName that a scalebar
371
+ * label's `firstRegionIndex`..`lastRegionIndex` sits inside. Those come off the
372
+ * static blocks, which are clipped to the viewport and broken by an elided
373
+ * block, so a zoomed-in collapsed-introns view brackets only the exons on
374
+ * screen — which is the wrong answer for anything acting on the whole run.
375
+ */
376
+ export function regionRunBounds(regions, firstIndex, lastIndex) {
377
+ const anchor = regions[firstIndex];
378
+ const inRun = (r) => r !== undefined &&
379
+ anchor !== undefined &&
380
+ r.refName === anchor.refName &&
381
+ r.assemblyName === anchor.assemblyName &&
382
+ !!r.reversed === !!anchor.reversed;
383
+ let first = firstIndex;
384
+ let last = lastIndex;
385
+ while (inRun(regions[first - 1])) {
386
+ first--;
387
+ }
388
+ while (inRun(regions[last + 1])) {
389
+ last++;
390
+ }
391
+ return { first, last };
423
392
  }
424
393
  /**
425
394
  * Whether a label occupying [leftPx, leftPx + labelWidth] fits within a block
@@ -508,14 +477,16 @@ export function groupContiguousBlocks(blocks) {
508
477
  current.end = Math.max(current.end, block.end);
509
478
  }
510
479
  else {
480
+ const reversed = !!block.reversed;
511
481
  current = {
512
482
  offsetPx: block.offsetPx,
513
483
  widthPx: block.widthPx,
514
484
  start: block.start,
515
485
  end: block.end,
516
- reversed: !!block.reversed,
486
+ reversed,
517
487
  refName: block.refName,
518
488
  isLeftEndOfDisplayedRegion: !!block.isLeftEndOfDisplayedRegion,
489
+ labeled: current?.refName !== block.refName || current.reversed !== reversed,
519
490
  };
520
491
  currentRegionIndex = block.displayedRegionIndex;
521
492
  runs.push(current);
@@ -553,8 +524,7 @@ export function groupContiguousBlocks(blocks) {
553
524
  * this frame.
554
525
  */
555
526
  export function runRefNameLabelPx(runs, orientation = 'forward') {
556
- const isRunStart = showRefNameLabels(runs, run => run.refName);
557
- return runs.map((run, i) => isRunStart[i] && run.isLeftEndOfDisplayedRegion
527
+ return runs.map(run => run.labeled && run.isLeftEndOfDisplayedRegion
558
528
  ? REF_NAME_LABEL_PADDING_PX +
559
529
  refNameLabelWidth(orientation === 'mixed' && run.reversed
560
530
  ? `${run.refName}${REV_MARKER}`
@@ -566,10 +536,10 @@ export function runRefNameLabelPx(runs, orientation = 'forward') {
566
536
  * regions. Single source of the tick→px formula shared by gridlines, the
567
537
  * scalebar coordinate labels, and SVG export so their positions can't drift.
568
538
  */
569
- export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, emitMajor = true, emitMinor = true) {
570
- return makeTicks(start, end, bpPerPx, emitMajor, emitMinor).map(tick => ({
539
+ export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, includeMinor = true) {
540
+ return makeTicks(start, end, bpPerPx, includeMinor).map(tick => ({
571
541
  ...tick,
572
- x: (reversed ? end - tick.base : tick.base - start) / bpPerPx,
542
+ x: bpOffsetInRegion({ start, end, reversed }, tick.base) / bpPerPx,
573
543
  }));
574
544
  }
575
545
  /**
@@ -656,6 +626,7 @@ export function parseLocStrings(input, assemblyName, isValidRefName) {
656
626
  // in this form as in the colon form.
657
627
  const [refName, start = '', end = ''] = inputs;
658
628
  if (e instanceof UnknownRefNameError &&
629
+ inputs.length === 3 &&
659
630
  parseBpString(start) !== undefined &&
660
631
  parseBpString(end) !== undefined) {
661
632
  return [
@@ -5,7 +5,7 @@ import { getBpDisplayStr, stringify } from '@jbrowse/core/util';
5
5
  import { makeStyles } from '@jbrowse/core/util/tss-react';
6
6
  import { observer } from 'mobx-react';
7
7
  import RubberbandSpan from '../shared/RubberbandSpan.js';
8
- import VerticalGuide from './VerticalGuide.js';
8
+ import { VerticalGuideLine } from '../shared/coordLabels.js';
9
9
  import { useRangeSelect } from './useRangeSelect.js';
10
10
  const useStyles = makeStyles()({
11
11
  rubberbandControl: {
@@ -19,11 +19,14 @@ const useStyles = makeStyles()({
19
19
  const MultiLevelRubberband = observer(function MultiLevelRubberband({ model, ControlComponent = _jsx("div", {}), }) {
20
20
  const ref = useRef(null);
21
21
  const { classes } = useStyles();
22
- const { guideX, rubberbandOn, leftBpOffset, rightBpOffset, numOfBpSelected, width, left, anchorPosition, open, handleMenuItemClick, handleClose, mouseMove, mouseDown, mouseOut, } = useRangeSelect(ref, model);
23
- return (_jsxs(_Fragment, { children: [guideX !== undefined ? (_jsx(VerticalGuide, { model: model, coordX: guideX })) : rubberbandOn ? (_jsx(RubberbandSpan, { leftLabel: _jsx(PerLevelRows, { rows: leftBpOffset.map(l => stringify(l, true)) }), rightLabel: _jsx(PerLevelRows, { rows: rightBpOffset.map(r => stringify(r, true)) }), size: _jsx(PerLevelRows, { rows: numOfBpSelected.map(getBpDisplayStr) }), width: width, left: left, viewWidth: Math.min(...model.views.map(view => view.width)), stickyTop: undefined })) : null, anchorPosition ? (_jsx(Menu, { anchorReference: "anchorPosition", anchorPosition: {
22
+ const { isClick, guideX, rubberbandOn, leftBpOffset, rightBpOffset, numOfBpSelected, width, left, anchorPosition, handleMenuItemClick, handleClose, mouseMove, mouseDown, mouseOut, } = useRangeSelect(ref, model);
23
+ const viewWidth = Math.min(...model.views.map(view => view.width));
24
+ return (_jsxs(_Fragment, { children: [guideX !== undefined ? (_jsx(VerticalGuideLine, { coordX: guideX, viewWidth: viewWidth, stickyTop: undefined, children: _jsx(PerLevelRows, { rows: model.views.map(view => stringify(view.pxToBp(guideX), true)) }) })) : rubberbandOn ? (_jsx(RubberbandSpan, { leftLabel: _jsx(PerLevelRows, { rows: leftBpOffset.map(l => stringify(l, true)) }), rightLabel: _jsx(PerLevelRows, { rows: rightBpOffset.map(r => stringify(r, true)) }), size: _jsx(PerLevelRows, { rows: numOfBpSelected.map(getBpDisplayStr) }), width: width, left: left, viewWidth: viewWidth, stickyTop: undefined })) : null, anchorPosition ? (_jsx(Menu, { anchorReference: "anchorPosition", anchorPosition: {
24
25
  left: anchorPosition.clientX,
25
26
  top: anchorPosition.clientY,
26
- }, onMenuItemClick: handleMenuItemClick, open: open, onClose: handleClose, menuItems: model.rubberBandMenuItems() })) : null, _jsx("div", { "data-testid": "rubberband_controls", className: classes.rubberbandControl, ref: ref, onMouseDown: mouseDown, onMouseMove: mouseMove,
27
+ }, onMenuItemClick: handleMenuItemClick, open: true, onClose: handleClose, menuItems: isClick
28
+ ? model.rubberbandClickMenuItemsAtPx(anchorPosition.offsetX)
29
+ : model.rubberBandMenuItems() })) : null, _jsx("div", { "data-testid": "rubberband_controls", className: classes.rubberbandControl, ref: ref, onMouseDown: mouseDown, onMouseMove: mouseMove,
27
30
  // Leave, not out: mouseout bubbles up from every child, so moving across
28
31
  // the ControlComponent's own children fired it and flickered the guide.
29
32
  // Matches Rubberband/OverviewRubberband.
@@ -1,2 +1,4 @@
1
1
  export { default as MultiLevelRubberband } from './MultiLevelRubberband.tsx';
2
+ export { multiLevelRowClickMenuItems, multiLevelRowMenuItems, } from './menuItems.ts';
3
+ export { rowLabels } from './rowLabel.ts';
2
4
  export type { MultiLevelRubberbandModel } from './types.ts';
@@ -1 +1,3 @@
1
1
  export { default as MultiLevelRubberband } from './MultiLevelRubberband.js';
2
+ export { multiLevelRowClickMenuItems, multiLevelRowMenuItems, } from './menuItems.js';
3
+ export { rowLabels } from './rowLabel.js';
@@ -0,0 +1,34 @@
1
+ import type { LinearGenomeViewModel } from '../LinearGenomeView/model.ts';
2
+ import type { MenuItem } from '@jbrowse/core/ui';
3
+ /**
4
+ * One row per genome row, each holding that row's own rubberband menu — Get
5
+ * sequence, Copy range, Highlight region, and whatever a plugin contributes
6
+ * under Launch.
7
+ *
8
+ * A drag on the multi-level strip commits offsets on EVERY row (see
9
+ * `useRangeSelect`), so each row's `rubberBandMenuItems()` already describes a
10
+ * real selection and was simply never offered: the stacked views' menu held
11
+ * "Zoom to region(s)" and nothing else, while the same drag in a plain linear
12
+ * genome view offered five actions. Per row rather than applied across rows
13
+ * because each row is its own assembly — one sequence to fetch, one range to
14
+ * copy, one highlight to place — so there is no "all rows" answer to give.
15
+ *
16
+ * Zoom is the exception and stays where each view already has it: zooming every
17
+ * row at once is what the gesture means, and the synteny view holds its follow
18
+ * anchor across the loop.
19
+ */
20
+ export declare function multiLevelRowMenuItems(views: LinearGenomeViewModel[]): MenuItem[];
21
+ /**
22
+ * The same per-row rows for a bare CLICK on the strip rather than a drag — each
23
+ * row's Center view here, Zoom to base level and Copy coordinate, at the base
24
+ * that row paints under `px`.
25
+ *
26
+ * No all-rows row above them, unlike the drag's "Zoom to region(s)". A drag
27
+ * names a span in pixels and every row can act on it; a click names a
28
+ * COORDINATE, and the rows are different assemblies, so which genome's base the
29
+ * pointer is over is the question the menu has to ask rather than answer.
30
+ *
31
+ * `px` rather than a resolved offset: each row maps the pixel through its own
32
+ * `pxToBp`, so there is no one offset to pass.
33
+ */
34
+ export declare function multiLevelRowClickMenuItems(views: LinearGenomeViewModel[], px: number): MenuItem[];