@jbrowse/plugin-linear-genome-view 5.0.0-beta.2 → 5.0.0-beta.20

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 (177) hide show
  1. package/esm/LinearGenomeView/afterAttach.d.ts +22 -5
  2. package/esm/LinearGenomeView/afterAttach.js +129 -106
  3. package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
  4. package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
  5. package/esm/LinearGenomeView/components/CenterLine.js +10 -5
  6. package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
  7. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +9 -4
  9. package/esm/LinearGenomeView/components/ExportSvgDialog.js +45 -8
  10. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  11. package/esm/LinearGenomeView/components/Header.js +11 -1
  12. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  13. package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
  14. package/esm/LinearGenomeView/components/HeaderPanControls.js +6 -3
  15. package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
  16. package/esm/LinearGenomeView/components/HeaderZoomControls.js +6 -35
  17. package/esm/LinearGenomeView/components/Highlight.js +27 -11
  18. package/esm/LinearGenomeView/components/HighlightBand.d.ts +11 -2
  19. package/esm/LinearGenomeView/components/HighlightBand.js +32 -13
  20. package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -3
  21. package/esm/LinearGenomeView/components/HighlightChip.js +13 -8
  22. package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
  23. package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
  24. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  25. package/esm/LinearGenomeView/components/ImportForm.js +7 -9
  26. package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
  27. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +13 -14
  28. package/esm/LinearGenomeView/components/MiniControls.js +11 -5
  29. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  30. package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
  31. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +6 -5
  32. package/esm/LinearGenomeView/components/OverviewRubberband.js +25 -48
  33. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  34. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  35. package/esm/LinearGenomeView/components/PaddingBlocks.js +3 -8
  36. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  37. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
  38. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
  39. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  40. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +74 -0
  41. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  42. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  43. package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
  44. package/esm/LinearGenomeView/components/SVGHighlightBand.js +16 -5
  45. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  46. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  47. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +51 -36
  48. package/esm/LinearGenomeView/components/SearchBox.js +10 -16
  49. package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
  50. package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
  51. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -8
  52. package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  53. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +4 -7
  54. package/esm/LinearGenomeView/components/SearchResultsTable.js +25 -53
  55. package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
  56. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  57. package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
  58. package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
  59. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
  60. package/esm/LinearGenomeView/components/TrackContainer.js +63 -10
  61. package/esm/LinearGenomeView/components/TrackLabel.js +14 -4
  62. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  63. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +4 -5
  64. package/esm/LinearGenomeView/components/TrackLabelMenu.js +11 -14
  65. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
  66. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
  67. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +6 -4
  68. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +12 -24
  69. package/esm/LinearGenomeView/components/TracksContainer.js +11 -23
  70. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  71. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  72. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  73. package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
  74. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  75. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  76. package/esm/LinearGenomeView/components/useRangeSelect.js +26 -61
  77. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
  78. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
  79. package/esm/LinearGenomeView/components/useSideScroll.d.ts +17 -2
  80. package/esm/LinearGenomeView/components/useSideScroll.js +156 -102
  81. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  82. package/esm/LinearGenomeView/components/util.js +4 -2
  83. package/esm/LinearGenomeView/consts.d.ts +2 -0
  84. package/esm/LinearGenomeView/consts.js +3 -1
  85. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  86. package/esm/LinearGenomeView/containingLgv.js +23 -0
  87. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  88. package/esm/LinearGenomeView/headerFit.js +20 -7
  89. package/esm/LinearGenomeView/index.d.ts +2 -7
  90. package/esm/LinearGenomeView/index.js +5 -7
  91. package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -7
  92. package/esm/LinearGenomeView/keyboardHandler.js +61 -23
  93. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  94. package/esm/LinearGenomeView/launchKeys.js +11 -6
  95. package/esm/LinearGenomeView/lazyDialogs.d.ts +16 -3
  96. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  97. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  98. package/esm/LinearGenomeView/linkedViewSync.js +27 -49
  99. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  100. package/esm/LinearGenomeView/menuItems.js +264 -164
  101. package/esm/LinearGenomeView/model.d.ts +1192 -560
  102. package/esm/LinearGenomeView/model.js +805 -590
  103. package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +17 -0
  104. package/esm/LinearGenomeView/rExport/exportLinearView.js +117 -0
  105. package/esm/LinearGenomeView/rExport/rScript.d.ts +110 -0
  106. package/esm/LinearGenomeView/rExport/rScript.js +524 -0
  107. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  108. package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
  109. package/esm/LinearGenomeView/showFiles.d.ts +2 -0
  110. package/esm/LinearGenomeView/showFiles.js +19 -0
  111. package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
  112. package/esm/LinearGenomeView/showRegionsWithUndo.js +13 -5
  113. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  114. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  115. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  116. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  117. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  118. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
  119. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +9 -9
  120. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
  121. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +5 -5
  122. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +13 -18
  123. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  124. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  125. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  126. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  127. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +26 -0
  128. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  129. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +21 -13
  130. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -5
  131. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +7 -6
  132. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +2 -3
  133. package/esm/LinearGenomeView/svgcomponents/SVGView.js +17 -3
  134. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -20
  135. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
  136. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +74 -77
  137. package/esm/LinearGenomeView/svgcomponents/util.d.ts +28 -12
  138. package/esm/LinearGenomeView/svgcomponents/util.js +41 -29
  139. package/esm/LinearGenomeView/types.d.ts +13 -10
  140. package/esm/LinearGenomeView/types.js +0 -1
  141. package/esm/LinearGenomeView/util.d.ts +103 -46
  142. package/esm/LinearGenomeView/util.js +222 -170
  143. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +9 -6
  144. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  145. package/esm/MultiLevelRubberband/index.js +2 -0
  146. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  147. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  148. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  149. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  150. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  151. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  152. package/esm/MultiLevelRubberband/useRangeSelect.js +45 -59
  153. package/esm/TrackModifiers/index.d.ts +83 -0
  154. package/esm/TrackModifiers/index.js +231 -0
  155. package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -68
  156. package/esm/displayKitTests/perRegionTestEnv.js +30 -26
  157. package/esm/index.d.ts +21 -99
  158. package/esm/index.js +17 -38
  159. package/esm/searchUtils.d.ts +26 -10
  160. package/esm/searchUtils.js +137 -137
  161. package/esm/shared/RubberbandSpan.d.ts +3 -2
  162. package/esm/shared/RubberbandSpan.js +6 -3
  163. package/esm/shared/coordLabels.d.ts +11 -4
  164. package/esm/shared/coordLabels.js +20 -5
  165. package/esm/shared/useWindowDrag.d.ts +18 -0
  166. package/esm/shared/useWindowDrag.js +47 -0
  167. package/package.json +12 -11
  168. package/esm/FeatureTrack/configSchema.d.ts +0 -124
  169. package/esm/FeatureTrack/configSchema.js +0 -54
  170. package/esm/FeatureTrack/index.d.ts +0 -2
  171. package/esm/FeatureTrack/index.js +0 -13
  172. package/esm/LinearGenomeView/initKeys.d.ts +0 -12
  173. package/esm/LinearGenomeView/initKeys.js +0 -68
  174. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  175. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  176. package/esm/lazyPluginExports.d.ts +0 -23
  177. package/esm/lazyPluginExports.js +0 -36
@@ -1,4 +1,4 @@
1
- import { UnknownRefNameError, assembleLocStrings, getTickDisplayStr, measureText, parseBpString, parseLocString, } from '@jbrowse/core/util';
1
+ import { UnknownRefNameError, assembleLocString, assembleLocStrings, getTickDisplayStr, measureText, parseBpString, parseLocString, } from '@jbrowse/core/util';
2
2
  import { bpOffsetInRegion } from '@jbrowse/core/util/Base1DUtils';
3
3
  import { chooseGridPitch } from '@jbrowse/core/util/chooseGridPitch';
4
4
  import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
@@ -18,7 +18,7 @@ import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
18
18
  * A caller building a view SNAPSHOT does: it has no model to call
19
19
  * `fitAllRegions()` on, and naming `bpPerPx`/`offsetPx` instead is dropped
20
20
  * whenever the snapshot also carries a `windowWidthBp` — which one copied off an
21
- * existing view always does. plugin-canvas's collapsed-intron launch is that
21
+ * existing view always does. plugin-features's collapsed-intron launch is that
22
22
  * caller. A live view calls `fitAllRegions()`.
23
23
  *
24
24
  * `width` enters only through the zoom-in floor, which bites for a region set
@@ -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.
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.
247
215
  *
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.
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.
253
225
  *
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.
226
+ * `orientation` decides the ` [rev]` marker, following `assembleLocStrings`: say
227
+ * it where it distinguishes, once where it does not.
259
228
  *
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.
265
- *
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
  /**
@@ -588,6 +558,10 @@ export function makeBlockTicks({ start, end, reversed = false, }, bpPerPx, emitM
588
558
  * navigating to a feature
589
559
  */
590
560
  export async function generateLocations({ regions, assemblyManager, assemblyName, grow, }) {
561
+ const backwards = regions.find(({ start, end }) => start !== undefined && end !== undefined && start > end);
562
+ if (backwards) {
563
+ throw new Error(`found start greater than end in ${assembleLocString(backwards)}`);
564
+ }
591
565
  return Promise.all(regions.map(async (region) => {
592
566
  const asmName = region.assemblyName || assemblyName;
593
567
  if (!asmName) {
@@ -639,14 +613,12 @@ export async function generateLocations({ regions, assemblyManager, assemblyName
639
613
  * Used by navToLocString
640
614
  */
641
615
  export function parseLocStrings(input, assemblyName, isValidRefName) {
642
- const inputs = input
643
- .split(/(\s+)/)
644
- .map(f => f.trim())
645
- .filter(f => !!f);
616
+ const inputs = input.split(/\s+/).filter(Boolean);
617
+ const isRef = (ref, asm) => isValidRefName(ref, asm ?? assemblyName);
646
618
  // first try interpreting as a whitespace-separated sequence of
647
619
  // multiple locstrings
648
620
  try {
649
- return inputs.map(loc => parseLocString(loc, ref => isValidRefName(ref, assemblyName)));
621
+ return inputs.map(loc => parseLocString(loc, isRef));
650
622
  }
651
623
  catch (e) {
652
624
  // if this fails, try interpreting as a whitespace-separated refname,
@@ -656,15 +628,95 @@ export function parseLocStrings(input, assemblyName, isValidRefName) {
656
628
  // in this form as in the colon form.
657
629
  const [refName, start = '', end = ''] = inputs;
658
630
  if (e instanceof UnknownRefNameError &&
631
+ inputs.length === 3 &&
659
632
  parseBpString(start) !== undefined &&
660
633
  parseBpString(end) !== undefined) {
661
- return [
662
- parseLocString(`${refName}:${start}..${end}`, ref => isValidRefName(ref, assemblyName)),
663
- ];
634
+ return [parseLocString(`${refName}:${start}..${end}`, isRef)];
664
635
  }
665
636
  throw e;
666
637
  }
667
638
  }
639
+ // Frozen because every consumer maps over it and a caller that sorted in place
640
+ // would do so for the whole session.
641
+ const NO_PADDING_SPANS = Object.freeze([]);
642
+ /**
643
+ * Tick positions in the staticBlocks frame, one pass per merged run so a tick
644
+ * on an internal ~800px chunk boundary is emitted once.
645
+ */
646
+ export function getGridlineTicks({ blocks, firstBlockOffset, bpPerPx, }) {
647
+ const ticks = [];
648
+ for (const run of groupContiguousBlocks(blocks)) {
649
+ const runLeft = run.offsetPx - firstBlockOffset;
650
+ for (const { type, x } of makeBlockTicks(run, bpPerPx)) {
651
+ if (x >= 0 && x <= run.widthPx) {
652
+ ticks.push({ x: runLeft + x, major: type === 'major' });
653
+ }
654
+ }
655
+ }
656
+ return ticks;
657
+ }
658
+ /**
659
+ * Coordinate labels in the staticBlocks frame, off the same ticks as
660
+ * `getGridlineTicks`. A run that can hold too few labels to make a ruler goes
661
+ * unnumbered, and the bold refName label pinned at a run's left edge takes
662
+ * precedence over a number.
663
+ */
664
+ export function getScalebarLabels({ blocks, firstBlockOffset, bpPerPx, orientation, }) {
665
+ const labels = [];
666
+ const runs = groupContiguousBlocks(blocks);
667
+ const refNameLabelPx = runRefNameLabelPx(runs, orientation);
668
+ for (const [i, run] of runs.entries()) {
669
+ const runLeft = run.offsetPx - firstBlockOffset;
670
+ const runLabels = [];
671
+ for (const { base, x } of makeBlockTicks(run, bpPerPx, false)) {
672
+ const label = getTickDisplayStr(base + 1, bpPerPx);
673
+ const w = tickLabelWidth(label);
674
+ if (x - w / 2 >= refNameLabelPx[i] &&
675
+ labelFitsInBlock(x - w / 2, w, run.widthPx)) {
676
+ runLabels.push({
677
+ x: runLeft + x,
678
+ label,
679
+ key: `${run.offsetPx}-${base}`,
680
+ });
681
+ }
682
+ }
683
+ if (tickLabelsWorthDrawing(runLabels.length)) {
684
+ labels.push(...runLabels);
685
+ }
686
+ }
687
+ return labels;
688
+ }
689
+ /**
690
+ * Every span along the row that is not track data, in the staticBlocks frame.
691
+ * Nothing to draw returns one shared array, so the computed's value repeats and
692
+ * MobX's `===` stops the chain: a view inside one contig has no seam, elision
693
+ * or boundary, and a fresh `[]` each frame re-rendered every track's padding.
694
+ */
695
+ export function getPaddingSpans(blocks, firstBlockOffset) {
696
+ const spans = [];
697
+ for (const block of blocks) {
698
+ const x = block.offsetPx - firstBlockOffset;
699
+ if (block.type === 'ContentBlock') {
700
+ if (block.isRightEndOfDisplayedRegion) {
701
+ spans.push({
702
+ key: `${block.key}-sep`,
703
+ x: x + block.widthPx - 1,
704
+ width: 3,
705
+ kind: 'seam',
706
+ });
707
+ }
708
+ }
709
+ else {
710
+ spans.push({
711
+ key: block.key,
712
+ x,
713
+ width: block.widthPx,
714
+ kind: block.type === 'ElidedBlock' ? 'elided' : 'boundary',
715
+ });
716
+ }
717
+ }
718
+ return spans.length > 0 ? spans : NO_PADDING_SPANS;
719
+ }
668
720
  export function calculateVisibleLocStrings(contentBlocks) {
669
721
  return assembleLocStrings(contentBlocks.map(block => ({
670
722
  refName: block.refName,
@@ -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.
@@ -31,7 +34,7 @@ const MultiLevelRubberband = observer(function MultiLevelRubberband({ model, Con
31
34
  });
32
35
  // one row per rubberband level, in level order. values repeat across levels
33
36
  // whenever the assemblies line up, so the index is the only stable key
34
- function PerLevelRows({ rows }) {
37
+ const PerLevelRows = observer(function PerLevelRows({ rows, }) {
35
38
  return rows.map((row, idx) => (_jsx("div", { children: row }, idx)));
36
- }
39
+ });
37
40
  export default MultiLevelRubberband;
@@ -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[];