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

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 (145) 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 +73 -59
  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.js +2 -2
  16. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  17. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  18. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  19. package/esm/LinearGenomeView/components/ImportForm.js +2 -2
  20. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
  21. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
  22. package/esm/LinearGenomeView/components/MiniControls.js +3 -2
  23. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  24. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  25. package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
  26. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  27. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  28. package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
  29. package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
  30. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  31. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  32. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
  33. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  34. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  35. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  36. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  37. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  38. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
  39. package/esm/LinearGenomeView/components/SearchBox.js +8 -7
  40. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
  41. package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
  42. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  43. package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
  44. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  45. package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
  46. package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
  47. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  48. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
  49. package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
  50. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
  51. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
  52. package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
  53. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  54. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  55. package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
  56. package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
  57. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  58. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  59. package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
  60. package/esm/LinearGenomeView/components/searchModes.js +1 -1
  61. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  62. package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
  63. package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
  64. package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
  65. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  66. package/esm/LinearGenomeView/components/util.js +4 -2
  67. package/esm/LinearGenomeView/consts.d.ts +2 -0
  68. package/esm/LinearGenomeView/consts.js +3 -1
  69. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  70. package/esm/LinearGenomeView/containingLgv.js +23 -0
  71. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  72. package/esm/LinearGenomeView/headerFit.js +20 -7
  73. package/esm/LinearGenomeView/index.d.ts +1 -5
  74. package/esm/LinearGenomeView/index.js +10 -7
  75. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  76. package/esm/LinearGenomeView/keyboardHandler.js +15 -1
  77. package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
  78. package/esm/LinearGenomeView/launchKeys.js +27 -0
  79. package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
  80. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  81. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  82. package/esm/LinearGenomeView/linkedViewSync.js +27 -43
  83. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  84. package/esm/LinearGenomeView/menuItems.js +242 -151
  85. package/esm/LinearGenomeView/model.d.ts +2009 -200
  86. package/esm/LinearGenomeView/model.js +545 -395
  87. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  88. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  89. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  90. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  91. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  92. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  93. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  94. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
  95. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  96. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
  97. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  98. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  99. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  100. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  101. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
  102. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  103. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +15 -6
  104. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
  105. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
  106. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
  107. package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
  108. package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
  109. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  110. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
  111. package/esm/LinearGenomeView/svgcomponents/util.d.ts +9 -5
  112. package/esm/LinearGenomeView/svgcomponents/util.js +26 -21
  113. package/esm/LinearGenomeView/types.d.ts +8 -11
  114. package/esm/LinearGenomeView/util.d.ts +60 -45
  115. package/esm/LinearGenomeView/util.js +131 -160
  116. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
  117. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  118. package/esm/MultiLevelRubberband/index.js +2 -0
  119. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  120. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  121. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  122. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  123. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  124. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  125. package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
  126. package/esm/displayKitTests/perRegionTestEnv.d.ts +214 -86
  127. package/esm/displayKitTests/perRegionTestEnv.js +30 -29
  128. package/esm/index.d.ts +15 -85
  129. package/esm/index.js +13 -36
  130. package/esm/searchUtils.d.ts +18 -9
  131. package/esm/searchUtils.js +85 -69
  132. package/esm/shared/RubberbandSpan.js +2 -1
  133. package/esm/shared/coordLabels.d.ts +7 -0
  134. package/esm/shared/coordLabels.js +15 -1
  135. package/esm/shared/useWindowDrag.d.ts +18 -0
  136. package/esm/shared/useWindowDrag.js +45 -0
  137. package/package.json +10 -10
  138. package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
  139. package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
  140. package/esm/LinearGenomeView/initKeys.d.ts +0 -13
  141. package/esm/LinearGenomeView/initKeys.js +0 -104
  142. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  143. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  144. package/esm/lazyPluginExports.d.ts +0 -23
  145. package/esm/lazyPluginExports.js +0 -36
@@ -1,125 +1,175 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useRef } from 'react';
2
+ import { transaction } from 'mobx';
2
3
  // A press that travels further than this is a pan, and the click that ends it
3
4
  // is not a click. A hand that wobbles two pixels can still select something.
4
5
  const PAN_CLICK_THRESHOLD_PX = 4;
5
6
  // The pan publishes its state as attributes on the element the press landed in
6
7
  // (TracksContainer), so a track's own pointer handlers read it with `closest`
7
8
  // and nothing has to hand them a ref or a model field — the same marker shape
8
- // as `data-gesture-owner`. `data-pan-dragging` is present from mousedown to
9
- // mouseup; `data-pan-moved` is set once the press travels past the threshold
10
- // and cleared by the next press, so it still answers for the click that
11
- // follows mouseup.
9
+ // as `data-gesture-owner`. `data-pan-dragging` is present from press to
10
+ // release; `data-pan-moved` is set once the press travels past the threshold
11
+ // or becomes a pinch, and cleared by the next press, so it still answers for
12
+ // the click that follows the release: `clickCapture` swallows that click
13
+ // before any track sees it.
12
14
  const PAN_DRAGGING_ATTR = 'data-pan-dragging';
13
15
  const PAN_MOVED_ATTR = 'data-pan-moved';
16
+ function preventDefault(event) {
17
+ event.preventDefault();
18
+ }
19
+ function pointOf(event) {
20
+ return { x: event.clientX, y: event.clientY };
21
+ }
22
+ /**
23
+ * Drag to pan and pinch to zoom, on pointer events so a finger works the same
24
+ * as a mouse. A second finger turns a pan into a pinch: the spread between the
25
+ * two zooms about their midpoint, and moving the midpoint pans. Lifting either
26
+ * finger goes back to panning with the other.
27
+ *
28
+ * The host sets `touch-action` to leave the browser only the gestures this
29
+ * does not want (TracksContainer keeps vertical scroll); without it a touch
30
+ * drag is a page scroll and a pinch zooms the page, and no pointer stream
31
+ * arrives.
32
+ *
33
+ * The window listeners go on in the press handler itself rather than in an
34
+ * effect, so a release that lands before React commits still finds them.
35
+ */
14
36
  export function useSideScroll(model) {
15
- const [mouseDragging, setMouseDragging] = useState(false);
16
- const scheduledRef = useRef(false);
17
- const rafRef = useRef(null);
18
- const startXRef = useRef(0);
19
- const prevXRef = useRef(0);
20
- const currXRef = useRef(0);
21
- const hostRef = useRef(null);
22
- useEffect(() => {
23
- // apply the movement accumulated since the previous frame, then advance the
24
- // baseline. shared by the rAF tick and the mouseup flush
25
- function flushScroll() {
26
- const distance = currXRef.current - prevXRef.current;
27
- if (distance) {
28
- model.horizontalScroll(-distance);
29
- prevXRef.current = currXRef.current;
30
- }
31
- }
32
- function globalMouseMove(event) {
33
- event.preventDefault();
34
- currXRef.current = event.clientX;
35
- if (Math.abs(currXRef.current - startXRef.current) > PAN_CLICK_THRESHOLD_PX) {
36
- hostRef.current?.setAttribute(PAN_MOVED_ATTR, '');
37
- }
38
- const distance = currXRef.current - prevXRef.current;
39
- // use rAF to make it so multiple event handlers aren't fired per-frame
40
- // see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
41
- if (distance && !scheduledRef.current) {
42
- scheduledRef.current = true;
43
- rafRef.current = window.requestAnimationFrame(() => {
44
- rafRef.current = null;
45
- scheduledRef.current = false;
46
- flushScroll();
47
- });
48
- }
49
- }
50
- function globalMouseUp() {
51
- // flush any movement still queued for the next frame before ending the
52
- // drag; otherwise a quick flick (mousedown/move/up within one frame) or
53
- // the cleanup below would cancel it and drop the scroll
54
- if (rafRef.current !== null) {
55
- window.cancelAnimationFrame(rafRef.current);
56
- rafRef.current = null;
57
- scheduledRef.current = false;
58
- flushScroll();
59
- }
60
- prevXRef.current = 0;
61
- hostRef.current?.removeAttribute(PAN_DRAGGING_ATTR);
62
- if (mouseDragging) {
63
- setMouseDragging(false);
64
- }
65
- }
66
- if (mouseDragging) {
67
- window.addEventListener('mousemove', globalMouseMove, true);
68
- window.addEventListener('mouseup', globalMouseUp, true);
69
- return () => {
70
- window.removeEventListener('mousemove', globalMouseMove, true);
71
- window.removeEventListener('mouseup', globalMouseUp, true);
72
- // drop a frame queued mid-drag so it can't fire a stray scroll after
73
- // unmount (matches `useVirtualScrollWheel`'s cleanup)
74
- if (rafRef.current !== null) {
75
- window.cancelAnimationFrame(rafRef.current);
76
- rafRef.current = null;
77
- }
78
- scheduledRef.current = false;
79
- };
37
+ const gestureRef = useRef(undefined);
38
+ useEffect(() => () => {
39
+ gestureRef.current?.cancel();
40
+ }, []);
41
+ function pointerDown(event) {
42
+ const host = event.currentTarget;
43
+ if (gestureRef.current) {
44
+ gestureRef.current.join(event);
45
+ return;
80
46
  }
81
- return undefined;
82
- }, [model, mouseDragging]);
83
- function mouseDown(event) {
84
- // Cleared for EVERY press, ahead of the returns below, because the marker
85
- // outlives the gesture that set it: a pan, then a shift-press or a press on
86
- // a button, used to leave `data-pan-moved` standing on the container. The
87
- // one reader today re-enters through a press that reaches the bottom of
88
- // this function, so nothing was wrong — but these attributes are a DOM
89
- // contract other displays read with `closest`, and a stale true is what the
90
- // next reader would inherit.
91
- event.currentTarget.removeAttribute(PAN_MOVED_ATTR);
92
- if (event.shiftKey) {
47
+ // Cleared for EVERY press, ahead of the returns below: the marker outlives
48
+ // the gesture that set it, and a stale true is what the next reader of
49
+ // this DOM contract would inherit.
50
+ host.removeAttribute(PAN_MOVED_ATTR);
51
+ if (event.shiftKey || event.button !== 0 || !event.isPrimary) {
93
52
  return;
94
53
  }
95
- // skip the click-drag pan when pressing an interactive control: a
96
- // draggable element, a control that claimed the press (resize handles, the
97
- // scalebar), or a button (e.g. the menu button on a highlight/bookmark
98
- // chip, whose actual target is the icon inside it). All three are matched
99
- // with `closest`, since the press usually lands on a child of the control
100
- // rather than the control itself.
54
+ // a draggable element, a control that claimed the press (resize handles,
55
+ // the scalebar, a legend), or a button: `closest`, since the press usually
56
+ // lands on a child of the control
101
57
  const target = event.target;
102
58
  if (target.draggable ||
103
59
  target.closest('[data-gesture-owner]') ||
104
60
  target.closest('button')) {
105
61
  return;
106
62
  }
107
- // otherwise do click and drag scroll
108
- if (event.button === 0) {
109
- hostRef.current = event.currentTarget;
110
- event.currentTarget.setAttribute(PAN_DRAGGING_ATTR, '');
111
- startXRef.current = event.clientX;
112
- prevXRef.current = event.clientX;
113
- currXRef.current = event.clientX;
114
- setMouseDragging(true);
63
+ const left = host.getBoundingClientRect().left;
64
+ const startX = event.clientX;
65
+ // where each finger is now, and where it was when the view last moved
66
+ const pointers = new Map([[event.pointerId, pointOf(event)]]);
67
+ let applied = new Map(pointers);
68
+ let frame;
69
+ function flush() {
70
+ frame = undefined;
71
+ const [a, b] = [...pointers.keys()];
72
+ const curr1 = pointers.get(a);
73
+ const prev1 = applied.get(a);
74
+ if (b === undefined) {
75
+ if (curr1.x !== prev1.x) {
76
+ model.horizontalScroll(prev1.x - curr1.x);
77
+ }
78
+ }
79
+ else {
80
+ const curr2 = pointers.get(b);
81
+ const prev2 = applied.get(b);
82
+ const prevSpread = Math.hypot(prev1.x - prev2.x, prev1.y - prev2.y);
83
+ const spread = Math.hypot(curr1.x - curr2.x, curr1.y - curr2.y);
84
+ const prevMid = (prev1.x + prev2.x) / 2;
85
+ const mid = (curr1.x + curr2.x) / 2;
86
+ transaction(() => {
87
+ if (prevSpread > 0 && spread > 0) {
88
+ model.zoomTo((model.bpPerPx * prevSpread) / spread, prevMid - left);
89
+ }
90
+ if (mid !== prevMid) {
91
+ model.horizontalScroll(prevMid - mid);
92
+ }
93
+ });
94
+ }
95
+ applied = new Map(pointers);
96
+ }
97
+ // Applies what is queued before the set of fingers changes, so the next
98
+ // frame measures from where every finger is now rather than jumping by the
99
+ // distance between them.
100
+ function settle() {
101
+ if (frame !== undefined) {
102
+ window.cancelAnimationFrame(frame);
103
+ flush();
104
+ }
105
+ }
106
+ function move(e) {
107
+ if (!pointers.has(e.pointerId)) {
108
+ return;
109
+ }
110
+ pointers.set(e.pointerId, pointOf(e));
111
+ if (Math.abs(e.clientX - startX) > PAN_CLICK_THRESHOLD_PX) {
112
+ host.setAttribute(PAN_MOVED_ATTR, '');
113
+ }
114
+ frame ??= window.requestAnimationFrame(flush);
115
115
  }
116
+ function end(applyQueued) {
117
+ if (applyQueued) {
118
+ settle();
119
+ }
120
+ else if (frame !== undefined) {
121
+ window.cancelAnimationFrame(frame);
122
+ }
123
+ host.removeAttribute(PAN_DRAGGING_ATTR);
124
+ window.removeEventListener('pointermove', move, true);
125
+ window.removeEventListener('pointerup', release, true);
126
+ window.removeEventListener('pointercancel', release, true);
127
+ window.removeEventListener('selectstart', preventDefault, true);
128
+ gestureRef.current = undefined;
129
+ }
130
+ // pointercancel as well as pointerup: the browser cancels a touch it takes
131
+ // over as a vertical scroll, and the gesture would otherwise stay latched
132
+ function release(e) {
133
+ if (!pointers.has(e.pointerId)) {
134
+ return;
135
+ }
136
+ if (pointers.size > 1) {
137
+ settle();
138
+ pointers.delete(e.pointerId);
139
+ applied.delete(e.pointerId);
140
+ }
141
+ else {
142
+ end(true);
143
+ }
144
+ }
145
+ host.setAttribute(PAN_DRAGGING_ATTR, '');
146
+ window.addEventListener('pointermove', move, true);
147
+ window.addEventListener('pointerup', release, true);
148
+ window.addEventListener('pointercancel', release, true);
149
+ window.addEventListener('selectstart', preventDefault, true);
150
+ gestureRef.current = {
151
+ join(e) {
152
+ if (e.pointerType !== 'touch' || pointers.size > 1) {
153
+ return;
154
+ }
155
+ settle();
156
+ pointers.set(e.pointerId, pointOf(e));
157
+ applied = new Map(pointers);
158
+ host.setAttribute(PAN_MOVED_ATTR, '');
159
+ model.cancelZoomAnimation();
160
+ },
161
+ cancel() {
162
+ end(false);
163
+ },
164
+ };
116
165
  }
117
- // this local mouseup is used in addition to the global because sometimes
118
- // the global add/remove are not called in time, resulting in issue #533
119
- function mouseUp(event) {
120
- event.preventDefault();
121
- hostRef.current?.removeAttribute(PAN_DRAGGING_ATTR);
122
- setMouseDragging(false);
166
+ // The browser fires `click` after a release on whatever the press and the
167
+ // release share, which over a track is its canvas, so a pan that ended on a
168
+ // gene would open that gene's details.
169
+ function clickCapture(event) {
170
+ if (event.currentTarget.hasAttribute(PAN_MOVED_ATTR)) {
171
+ event.stopPropagation();
172
+ }
123
173
  }
124
- return { mouseDown, mouseUp };
174
+ return { pointerDown, clickCapture };
125
175
  }
@@ -1,6 +1,10 @@
1
1
  import type { Assembly } from '@jbrowse/core/assemblyManager/assembly';
2
2
  import type { ViewLayout } from '@jbrowse/core/util/Base1DUtils';
3
3
  export { getHighlightColor, highlightKey } from '@jbrowse/core/util/highlights';
4
+ export declare const scalebarRefLabelProps: {
5
+ 'data-scalebar-ref-label': string;
6
+ };
7
+ export declare function isOnScalebarRefLabel(target: EventTarget | null): boolean;
4
8
  export declare const elidedBlockStyles: {
5
9
  readonly backgroundColor: '#999';
6
10
  readonly backgroundImage: 'repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,255,255,.5) 1px, rgba(255,255,255,.5) 3px)';
@@ -3,8 +3,10 @@ import { pxToBp } from '@jbrowse/core/util/Base1DUtils';
3
3
  // getHighlightColor/highlightKey live in core so the dotplot highlight bands can
4
4
  // share the exact same color and React-key semantics
5
5
  export { getHighlightColor, highlightKey } from '@jbrowse/core/util/highlights';
6
- // Shared style for elided (collapsed) blocks — striped grey pattern used
7
- // consistently across OverviewScalebar, ScalebarCoordinateLabels, and Gridlines
6
+ export const scalebarRefLabelProps = { 'data-scalebar-ref-label': '' };
7
+ export function isOnScalebarRefLabel(target) {
8
+ return (target instanceof Element && !!target.closest('[data-scalebar-ref-label]'));
9
+ }
8
10
  export const elidedBlockStyles = {
9
11
  backgroundColor: '#999',
10
12
  backgroundImage: 'repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,255,255,.5) 1px, rgba(255,255,255,.5) 3px)',
@@ -1,10 +1,12 @@
1
1
  export declare const MIN_BP_PER_PX: number;
2
+ export declare const MINIMUM_BLOCK_WIDTH = 3;
2
3
  export declare const SHOW_ALL_REGIONS_FILL = 0.9;
3
4
  export declare const HEADER_BAR_HEIGHT = 48;
4
5
  export declare const HEADER_OVERVIEW_HEIGHT = 20;
5
6
  export declare const SCALE_BAR_HEIGHT = 17;
6
7
  export declare const SVG_SCALEBAR_CAP = 5;
7
8
  export declare const RESIZE_HANDLE_HEIGHT = 4;
9
+ export declare const RESIZE_ALL_HANDLE_HEIGHT = 6;
8
10
  export declare const TRACK_TOP_GAP = 2;
9
11
  export declare const TRACK_OUTLINE_BORDER = 1;
10
12
  export declare const MINIMIZED_TRACK_HEIGHT = 20;
@@ -1,5 +1,6 @@
1
1
  // most zoomed-in level: 50px per bp
2
2
  export const MIN_BP_PER_PX = 1 / 50;
3
+ export const MINIMUM_BLOCK_WIDTH = 3;
3
4
  // fraction of the view width the whole genome fills at the most zoomed-out
4
5
  // level, leaving a 10% margin
5
6
  export const SHOW_ALL_REGIONS_FILL = 0.9;
@@ -14,8 +15,9 @@ export const SVG_SCALEBAR_CAP = 5;
14
15
  // TrackContainer lays its Paper out from these same three and nothing else —
15
16
  // the previous spelling had the model on a 3 while the CSS rendered 2 + 1 + 4,
16
17
  // which put every model-derived track offset 5px per track above the pixels.
17
- // The track *label* is deliberately not among them; see `trackChromeHeight`.
18
+ // The track *label* is not among them: it is measured, see `trackLabelBand`.
18
19
  export const RESIZE_HANDLE_HEIGHT = 4;
20
+ export const RESIZE_ALL_HANDLE_HEIGHT = 6;
19
21
  export const TRACK_TOP_GAP = 2;
20
22
  export const TRACK_OUTLINE_BORDER = 1;
21
23
  export const MINIMIZED_TRACK_HEIGHT = 20;
@@ -0,0 +1,21 @@
1
+ import type { LinearGenomeViewModel } from './model.ts';
2
+ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
3
+ /**
4
+ * The containing view as the linear genome view itself, for a display that
5
+ * needs `pxToBp`, `showTrack`, `navTo` or the view's own chrome settings.
6
+ *
7
+ * The twin of display-kit's `containingHost`, and deliberately not the same
8
+ * function: `RegionHost` is the region contract display-kit reads, and widening
9
+ * it to carry the LGV's own surface is how the display layer would come to
10
+ * depend on the view plugin. A display that wants the view names the view.
11
+ *
12
+ * A view that lays its displays along a strip of its own — the circular view's
13
+ * ring host — is answered here too, the way `containingHost` answers it: the
14
+ * strip carries the members a display reads to draw (`bpToPx`, `pxToBp`, the
15
+ * blocks), and a display drawn there that reaches for a member the strip has
16
+ * not is the display that stays off the circle.
17
+ *
18
+ * Components and structural helpers taking duck-typed model shapes keep calling
19
+ * `getContainingView` — the same carve-out `containingHost` documents.
20
+ */
21
+ export declare function containingLgv(self: IStateTreeNode): LinearGenomeViewModel;
@@ -0,0 +1,23 @@
1
+ import { getContainingView } from '@jbrowse/core/util';
2
+ /**
3
+ * The containing view as the linear genome view itself, for a display that
4
+ * needs `pxToBp`, `showTrack`, `navTo` or the view's own chrome settings.
5
+ *
6
+ * The twin of display-kit's `containingHost`, and deliberately not the same
7
+ * function: `RegionHost` is the region contract display-kit reads, and widening
8
+ * it to carry the LGV's own surface is how the display layer would come to
9
+ * depend on the view plugin. A display that wants the view names the view.
10
+ *
11
+ * A view that lays its displays along a strip of its own — the circular view's
12
+ * ring host — is answered here too, the way `containingHost` answers it: the
13
+ * strip carries the members a display reads to draw (`bpToPx`, `pxToBp`, the
14
+ * blocks), and a display drawn there that reaches for a member the strip has
15
+ * not is the display that stays off the circle.
16
+ *
17
+ * Components and structural helpers taking duck-typed model shapes keep calling
18
+ * `getContainingView` — the same carve-out `containingHost` documents.
19
+ */
20
+ export function containingLgv(self) {
21
+ const view = getContainingView(self);
22
+ return (view.regionHost ?? view);
23
+ }
@@ -1,4 +1,4 @@
1
- declare const SHEDDABLE: readonly [readonly ['trackSelectorIndent', 16], readonly ['panButtonSpacing', 100], readonly ['scrollZoomLabel', 83], readonly ['zoomSlider', 100], readonly ['regionWidth', 54]];
1
+ declare const SHEDDABLE: readonly [readonly ['trackSelectorIndent', 16], readonly ['panButtonSpacing', 100], readonly ['scrollZoomLabel', 88], readonly ['zoomSlider', 100], readonly ['regionWidth', 54]];
2
2
  export type HeaderFit = Record<(typeof SHEDDABLE)[number][0], boolean>;
3
3
  /**
4
4
  * Which of the header bar's optional pieces a header `width` px wide still has
@@ -19,9 +19,11 @@ export type HeaderFit = Record<(typeof SHEDDABLE)[number][0], boolean>;
19
19
  * not a cliff: it shrinks from what it asked for rather than from the 101px the
20
20
  * unshed row used to leave it.
21
21
  */
22
- export declare function headerFit({ width, searchBoxPx, clearHighlight, }: {
22
+ export declare function headerFit({ width, searchBoxPx, clearHighlight, viewMenu, touch, }: {
23
23
  width: number | undefined;
24
24
  searchBoxPx: number;
25
25
  clearHighlight?: boolean;
26
+ viewMenu?: boolean;
27
+ touch?: boolean;
26
28
  }): HeaderFit;
27
29
  export {};
@@ -1,15 +1,23 @@
1
1
  // Widths measured against a built jbrowse-web at the default theme and root
2
2
  // font size — `products/jbrowse-web/browser-tests/probe-lgv-header-fit.ts`
3
- // prints the row piece by piece. It spends 591px on everything that is not the
4
- // search box (47 track selector, 114 scroll-zoom toggle, 156 pan buttons, 50 bp
3
+ // prints the row piece by piece. It spends 596px on everything that is not the
4
+ // search box (47 track selector, 119 scroll-zoom checkbox, 156 pan buttons, 50 bp
5
5
  // readout, 192 zoom controls, 32 of flex gap). What the box itself asks for is
6
6
  // the caller's, because it follows the locstring rather than being a constant.
7
- const ROW_WITHOUT_SEARCH_PX = 591;
7
+ const ROW_WITHOUT_SEARCH_PX = 596;
8
8
  // The one piece of the row that comes and goes on its own. A text search that
9
9
  // lands on a feature raises it — the same flow that was just using the search
10
10
  // box this whole table exists to protect — so its width is taken off the top
11
11
  // rather than left for the search box to absorb.
12
12
  const CLEAR_HIGHLIGHT_PX = 35;
13
+ // An embedded view has no title bar, so its menu button leads this row: a
14
+ // 40px icon button and its flex gap.
15
+ const VIEW_MENU_PX = 44;
16
+ // A touch device drops the scroll-zoom toggle, which has no wheel to
17
+ // govern, and the pan buttons, which a swipe replaces: 119 + 156, and the flex
18
+ // gaps of the three items.
19
+ const TOUCH_ABSENT_PX = 287;
20
+ const TOUCH_ABSENT = new Set(['panButtonSpacing', 'scrollZoomLabel']);
13
21
  // What the header gives up as its window narrows, cheapest first, with the
14
22
  // pixels each one frees. Whitespace goes before words, words before a redundant
15
23
  // control, and that before a readout. The search box is not on the list: it is
@@ -18,7 +26,7 @@ const CLEAR_HIGHLIGHT_PX = 35;
18
26
  const SHEDDABLE = [
19
27
  ['trackSelectorIndent', 16],
20
28
  ['panButtonSpacing', 100],
21
- ['scrollZoomLabel', 83],
29
+ ['scrollZoomLabel', 88],
22
30
  ['zoomSlider', 100],
23
31
  ['regionWidth', 54],
24
32
  ];
@@ -41,17 +49,22 @@ const SHEDDABLE = [
41
49
  * not a cliff: it shrinks from what it asked for rather than from the 101px the
42
50
  * unshed row used to leave it.
43
51
  */
44
- export function headerFit({ width, searchBoxPx, clearHighlight = false, }) {
52
+ export function headerFit({ width, searchBoxPx, clearHighlight = false, viewMenu = false, touch = false, }) {
45
53
  const fit = Object.fromEntries(SHEDDABLE.map(([k]) => [k, true]));
46
54
  if (width === undefined) {
47
55
  return fit;
48
56
  }
49
- const room = width - (clearHighlight ? CLEAR_HIGHLIGHT_PX : 0);
50
- let need = ROW_WITHOUT_SEARCH_PX + searchBoxPx;
57
+ const room = width -
58
+ (clearHighlight ? CLEAR_HIGHLIGHT_PX : 0) -
59
+ (viewMenu ? VIEW_MENU_PX : 0);
60
+ let need = ROW_WITHOUT_SEARCH_PX + searchBoxPx - (touch ? TOUCH_ABSENT_PX : 0);
51
61
  for (const [key, saves] of SHEDDABLE) {
52
62
  if (need <= room) {
53
63
  break;
54
64
  }
65
+ if (touch && TOUCH_ABSENT.has(key)) {
66
+ continue;
67
+ }
55
68
  fit[key] = false;
56
69
  need -= saves;
57
70
  }
@@ -26,9 +26,5 @@ declare module '@jbrowse/core/PluginManager' {
26
26
  export default function LinearGenomeViewF(pluginManager: PluginManager): void;
27
27
  export { stateModelFactory } from './model.ts';
28
28
  export type { LinearGenomeViewModel, LinearGenomeViewStateModel, } from './model.ts';
29
- export type { BpOffset, ExportSvgOptions, HighlightType, InitState, LinearGenomeViewLaunchProps, NavLocation, TrackLabelMode, VolatileGuide, } from './types.ts';
30
- export { type SyncableViewAction, installLinkedViewSync, } from './linkedViewSync.ts';
31
- export { default as HighlightBand } from './components/HighlightBand.tsx';
29
+ export { installLinkedViewSync } from './linkedViewSync.ts';
32
30
  export { default as HighlightChip } from './components/HighlightChip.tsx';
33
- export { default as OverviewHighlightBand } from './components/OverviewHighlightBand.tsx';
34
- export { default as SVGHighlightBand } from './components/SVGHighlightBand.tsx';
@@ -1,19 +1,22 @@
1
- import { lazy } from 'react';
2
1
  import { ViewType } from '@jbrowse/core/pluggableElementTypes';
2
+ import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
3
+ import { lgvLaunchKeys } from './launchKeys.js';
3
4
  import { stateModelFactory } from './model.js';
4
5
  export default function LinearGenomeViewF(pluginManager) {
5
6
  pluginManager.addViewType(() => {
7
+ // annotated against the registry rather than inferred, which is what
8
+ // makes a hand-written augmentation earn what `getViewType` promises
9
+ // its callers — see `ViewTypeRegistry`
10
+ const stateModel = stateModelFactory(pluginManager);
6
11
  return new ViewType({
7
12
  name: 'LinearGenomeView',
8
13
  displayName: 'Linear genome view',
9
- stateModel: stateModelFactory(pluginManager),
10
- ReactComponent: lazy(() => import('./components/LinearGenomeView.js')),
14
+ stateModel,
15
+ launchKeys: lgvLaunchKeys,
16
+ ReactComponent: lazyWithPreload(() => import('./components/LinearGenomeView.js')),
11
17
  });
12
18
  });
13
19
  }
14
20
  export { stateModelFactory } from './model.js';
15
- export { installLinkedViewSync, } from './linkedViewSync.js';
16
- export { default as HighlightBand } from './components/HighlightBand.js';
21
+ export { installLinkedViewSync } from './linkedViewSync.js';
17
22
  export { default as HighlightChip } from './components/HighlightChip.js';
18
- export { default as OverviewHighlightBand } from './components/OverviewHighlightBand.js';
19
- export { default as SVGHighlightBand } from './components/SVGHighlightBand.js';
@@ -5,5 +5,7 @@ import type { LinearGenomeViewModel } from './model.ts';
5
5
  * - Ctrl/Cmd + ArrowRight: slide right
6
6
  * - Ctrl/Cmd + ArrowUp: zoom in
7
7
  * - Ctrl/Cmd + ArrowDown: zoom out
8
+ * - Ctrl/Cmd + Shift + D: highlight the region in view
9
+ * - Ctrl/Cmd + Shift + M: go to the newest highlight
8
10
  */
9
11
  export declare function setupKeyboardHandler(self: LinearGenomeViewModel): void;
@@ -1,4 +1,5 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
+ import { isTextEntryFocused } from '@jbrowse/core/util/isTextEntryFocused';
2
3
  import { addDisposer } from '@jbrowse/mobx-state-tree';
3
4
  /**
4
5
  * Sets up keyboard shortcuts for the LinearGenomeView
@@ -6,6 +7,8 @@ import { addDisposer } from '@jbrowse/mobx-state-tree';
6
7
  * - Ctrl/Cmd + ArrowRight: slide right
7
8
  * - Ctrl/Cmd + ArrowUp: zoom in
8
9
  * - Ctrl/Cmd + ArrowDown: zoom out
10
+ * - Ctrl/Cmd + Shift + D: highlight the region in view
11
+ * - Ctrl/Cmd + Shift + M: go to the newest highlight
9
12
  */
10
13
  export function setupKeyboardHandler(self) {
11
14
  if (typeof document === 'undefined') {
@@ -13,7 +16,9 @@ export function setupKeyboardHandler(self) {
13
16
  }
14
17
  function handler(e) {
15
18
  const session = getSession(self);
16
- if (session.focusedViewId === self.id && (e.ctrlKey || e.metaKey)) {
19
+ if (session.focusedViewId === self.id &&
20
+ (e.ctrlKey || e.metaKey) &&
21
+ !isTextEntryFocused()) {
17
22
  if (e.code === 'ArrowLeft') {
18
23
  e.preventDefault();
19
24
  self.slide(-0.9);
@@ -30,6 +35,15 @@ export function setupKeyboardHandler(self) {
30
35
  e.preventDefault();
31
36
  self.zoom(self.bpPerPx * 2);
32
37
  }
38
+ else if (e.shiftKey && e.code === 'KeyD') {
39
+ e.preventDefault();
40
+ self.highlightCurrentRegion();
41
+ session.notify('Region highlighted.', 'success');
42
+ }
43
+ else if (e.shiftKey && e.code === 'KeyM') {
44
+ e.preventDefault();
45
+ void self.navigateNewestHighlight();
46
+ }
33
47
  }
34
48
  }
35
49
  document.addEventListener('keydown', handler);
@@ -0,0 +1,2 @@
1
+ import type { InitState } from './types.ts';
2
+ export declare const lgvLaunchKeys: import("@jbrowse/core/util/withLaunchInput").LaunchKeyRegistration<InitState, "bpPerPx" | "offsetPx" | "showCytobandsSetting">;
@@ -0,0 +1,27 @@
1
+ import { defineLaunchKeys } from '@jbrowse/core/util/withLaunchInput';
2
+ // The argument is `Record<keyof InitState, LaunchKeySpec>`, so a command this
3
+ // view interprets and nobody registered here is a compile error rather than a
4
+ // key that partitions as a typo.
5
+ //
6
+ // `bpPerPx`/`offsetPx` are v4's viewport spelling from before it was stored as
7
+ // a window, and `showCytobandsSetting` its cytoband toggle. None is a declared
8
+ // property, so the partition would read them as typos; the model's own
9
+ // preProcessSnapshot still converts them, so a URL or saved spec naming them
10
+ // goes on working. Share links carry all three, so they stay.
11
+ //
12
+ // #launchKeys LinearGenomeView — the URL parameters page renders this list
13
+ // rather than restating it; each of these keys has its own `&param=` section
14
+ // there, which is why no description is attached here.
15
+ export const lgvLaunchKeys = defineLaunchKeys()({
16
+ loc: { kind: 'launch' },
17
+ grow: { kind: 'launch' },
18
+ showHitTrack: { kind: 'launch' },
19
+ assembly: { kind: 'launch' },
20
+ displayedRegionNames: { kind: 'launch' },
21
+ tracklist: { kind: 'launch' },
22
+ nav: { kind: 'launch' },
23
+ tracks: { kind: 'trackEntries' },
24
+ highlight: { kind: 'launch' },
25
+ }, {
26
+ passThrough: ['bpPerPx', 'offsetPx', 'showCytobandsSetting'],
27
+ });
@@ -5,10 +5,7 @@ export declare const ReturnToImportFormDialog: import("react").LazyExoticCompone
5
5
  handleClose: () => void;
6
6
  }) => import("react").JSX.Element>;
7
7
  export declare const SequenceSearchDialog: import("react").LazyExoticComponent<({ model, handleClose, }: {
8
- model: {
9
- assemblyNames: string[];
10
- showTrack: (trackId: string) => void;
11
- };
8
+ model: import("./components/searchModes.ts").SequenceSearchModel;
12
9
  handleClose: () => void;
13
10
  }) => import("react").JSX.Element>;
14
11
  export declare const ExportSvgDialog: import("react").LazyExoticComponent<typeof import("./components/ExportSvgDialog.tsx").default>;
@@ -17,3 +14,4 @@ export declare const GetSequenceDialog: import("react").LazyExoticComponent<({ m
17
14
  regions: import("@jbrowse/core/util").Region[];
18
15
  handleClose: () => void;
19
16
  }) => import("react").JSX.Element>;
17
+ export declare const RegionWidthEditorDialog: import("react").LazyExoticComponent<typeof import("./components/RegionWidthEditorDialog.tsx").default>;
@@ -3,3 +3,4 @@ export const ReturnToImportFormDialog = lazy(() => import('@jbrowse/core/ui/Retu
3
3
  export const SequenceSearchDialog = lazy(() => import('./components/SequenceSearchDialog.js'));
4
4
  export const ExportSvgDialog = lazy(() => import('./components/ExportSvgDialog.js'));
5
5
  export const GetSequenceDialog = lazy(() => import('./components/GetSequenceDialog.js'));
6
+ export const RegionWidthEditorDialog = lazy(() => import('./components/RegionWidthEditorDialog.js'));
@@ -1,20 +1,12 @@
1
1
  import type { LinearGenomeViewModel } from './model.ts';
2
2
  import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
3
- /**
4
- * The single source of truth for which LinearGenomeView actions can be
5
- * replayed across linked views. Keeping it here (rather than as loose string
6
- * arrays in each parent view) means the dispatch below stays exhaustive and a
7
- * typo can't silently produce a no-op sync.
8
- */
9
- export type SyncableViewAction = 'horizontalScroll' | 'zoomTo' | 'showTrack' | 'toggleTrack' | 'hideTrack' | 'setTrackLabels' | 'setShowCenterLine';
10
3
  interface LinkableViews {
11
4
  linkViews: boolean;
12
5
  views: LinearGenomeViewModel[];
13
6
  }
14
7
  /**
15
- * Install a middleware that, while `linkViews` is on, replays each listed
16
- * action onto every other sub-view so panning/zooming/track-toggling stays in
17
- * sync. Used by linear-comparative-view and breakpoint-split-view.
8
+ * While `linkViews` is on, replay each pan, zoom and track toggle made on one
9
+ * sub-view onto every other, with the same arguments.
18
10
  */
19
- export declare function installLinkedViewSync(self: IStateTreeNode & LinkableViews, syncActions: readonly SyncableViewAction[]): void;
11
+ export declare function installLinkedViewSync(self: IStateTreeNode & LinkableViews): void;
20
12
  export {};