@jbrowse/plugin-linear-genome-view 5.0.0-beta.16 → 5.0.0-beta.18

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 (33) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +3 -1
  2. package/esm/FeatureTrack/configSchema.js +3 -1
  3. package/esm/LinearGenomeView/afterAttach.js +6 -4
  4. package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
  5. package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
  6. package/esm/LinearGenomeView/components/Highlight.js +21 -5
  7. package/esm/LinearGenomeView/components/HighlightBand.js +18 -13
  8. package/esm/LinearGenomeView/components/HighlightChip.js +9 -4
  9. package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
  10. package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
  11. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -1
  12. package/esm/LinearGenomeView/components/SVGHighlightBand.js +3 -3
  13. package/esm/LinearGenomeView/components/TracksContainer.js +2 -2
  14. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +1 -1
  15. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +1 -1
  16. package/esm/LinearGenomeView/components/useSideScroll.d.ts +3 -2
  17. package/esm/LinearGenomeView/components/useSideScroll.js +13 -8
  18. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  19. package/esm/LinearGenomeView/launchKeys.js +6 -5
  20. package/esm/LinearGenomeView/menuItems.js +4 -4
  21. package/esm/LinearGenomeView/model.d.ts +114 -22
  22. package/esm/LinearGenomeView/model.js +133 -50
  23. package/esm/LinearGenomeView/rExport/rScript.d.ts +21 -0
  24. package/esm/LinearGenomeView/rExport/rScript.js +3 -3
  25. package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
  26. package/esm/LinearGenomeView/showFiles.d.ts +2 -0
  27. package/esm/LinearGenomeView/showFiles.js +19 -0
  28. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +2 -2
  29. package/esm/TrackModifiers/index.d.ts +80 -0
  30. package/esm/TrackModifiers/index.js +212 -0
  31. package/esm/index.d.ts +3 -1
  32. package/esm/index.js +1 -0
  33. package/package.json +9 -8
@@ -37,7 +37,9 @@ import type PluginManager from '@jbrowse/core/PluginManager';
37
37
  * type: 'Gff3TabixAdapter',
38
38
  * uri: 'https://example.com/genes.sorted.gff.gz',
39
39
  * },
40
- * displayDefaults: { color: "jexl:feature.type=='CDS'?'red':'blue'" },
40
+ * displayDefaults: {
41
+ * encoding: { color: { value: "jexl:feature.type=='CDS'?'red':'blue'" } },
42
+ * },
41
43
  * }
42
44
  * ```
43
45
  */
@@ -38,7 +38,9 @@ import { createBaseTrackConfig } from '@jbrowse/core/pluggableElementTypes';
38
38
  * type: 'Gff3TabixAdapter',
39
39
  * uri: 'https://example.com/genes.sorted.gff.gz',
40
40
  * },
41
- * displayDefaults: { color: "jexl:feature.type=='CDS'?'red':'blue'" },
41
+ * displayDefaults: {
42
+ * encoding: { color: { value: "jexl:feature.type=='CDS'?'red':'blue'" } },
43
+ * },
42
44
  * }
43
45
  * ```
44
46
  */
@@ -2,8 +2,10 @@ import { getSession, isSessionModelWithWidgets, resolveNamedRegions, } from '@jb
2
2
  import { coerceHighlight } from '@jbrowse/core/util/highlights';
3
3
  import { installInitAutorun } from '@jbrowse/core/util/installInitAutorun';
4
4
  import { normalizeTrackInit, warmTrackDisplayGeneric, } from '@jbrowse/core/util/tracks';
5
+ import { requestedDisplayType } from '@jbrowse/launch-args/displaySettings';
5
6
  import { addDisposer } from '@jbrowse/mobx-state-tree';
6
7
  import { autorun, untracked, when } from 'mobx';
8
+ import { launchTrackInit } from '../TrackModifiers/index.js';
7
9
  import { SearchResultsNotFoundError } from '../searchUtils.js';
8
10
  // `init` is a frozen blob that often comes from hand-authored JSON, where a
9
11
  // lone entry gets written bare (`tracks: 'genes'`). A string is iterable, so
@@ -127,8 +129,7 @@ async function navigateInit(self, session, init) {
127
129
  // display's state model first when it is registered lazily
128
130
  async function showInitTracks(self, init) {
129
131
  for (const t of asArray(init.tracks)) {
130
- const { trackId, trackSnapshot, displaySnapshot } = normalizeTrackInit(t);
131
- await self.launchTrack(trackId, trackSnapshot, displaySnapshot);
132
+ await launchTrackInit(self, t);
132
133
  }
133
134
  }
134
135
  /**
@@ -219,8 +220,9 @@ function setupInitWarmup(self) {
219
220
  }
220
221
  }
221
222
  for (const t of tracks) {
222
- const { trackId, displaySnapshot } = normalizeTrackInit(t);
223
- warmTrackDisplayGeneric(self, trackId, displaySnapshot, assembly);
223
+ const { trackId, displaySnapshot, modifiers } = normalizeTrackInit(t);
224
+ const type = requestedDisplayType(modifiers);
225
+ warmTrackDisplayGeneric(self, trackId, type ? { ...displaySnapshot, type } : displaySnapshot, assembly);
224
226
  }
225
227
  });
226
228
  }, { name: 'LGVInitWarmup' }));
@@ -0,0 +1,18 @@
1
+ /**
2
+ * #config LinearGenomeViewAxis
3
+ * #category view
4
+ * The linear genome view's `axis`: the ruler along its top, whose ticks
5
+ * `grid` carries down through the tracks.
6
+ *
7
+ * #example
8
+ * ```js
9
+ * { type: 'LinearGenomeView', axis: { grid: false } }
10
+ * ```
11
+ */
12
+ export declare const linearGenomeViewAxisConfigSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
13
+ readonly grid: {
14
+ type: "boolean";
15
+ } & {
16
+ defaultValue: boolean;
17
+ };
18
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
@@ -0,0 +1,22 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
+ /**
3
+ * #config LinearGenomeViewAxis
4
+ * #category view
5
+ * The linear genome view's `axis`: the ruler along its top, whose ticks
6
+ * `grid` carries down through the tracks.
7
+ *
8
+ * #example
9
+ * ```js
10
+ * { type: 'LinearGenomeView', axis: { grid: false } }
11
+ * ```
12
+ */
13
+ export const linearGenomeViewAxisConfigSchema = ConfigurationSchema('LinearGenomeViewAxis', {
14
+ /**
15
+ * #slot
16
+ */
17
+ grid: {
18
+ type: 'boolean',
19
+ defaultValue: true,
20
+ description: "carry the ruler's ticks down through the tracks",
21
+ },
22
+ }, { closed: true });
@@ -2,14 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useStyleTheme } from '@jbrowse/core/ui/PaletteContext';
3
3
  import { getSession } from '@jbrowse/core/util';
4
4
  import CloseIcon from '@mui/icons-material/Close';
5
+ import EditIcon from '@mui/icons-material/Edit';
5
6
  import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
6
7
  import { observer } from 'mobx-react';
7
8
  import HighlightBand from './HighlightBand.js';
8
9
  import HighlightChip from './HighlightChip.js';
10
+ import HighlightLabelDialog from './HighlightLabelDialog.js';
9
11
  import { getHighlightColor } from './util.js';
10
- // A band narrower than the link-icon chip clips it into a smudge, so below this
11
- // width the chip is dropped (the band itself still draws).
12
- const CHIP_MIN_WIDTH = 24;
13
12
  const Highlight = observer(function Highlight({ model, highlight, }) {
14
13
  const theme = useStyleTheme();
15
14
  const coords = model.getHighlightCoords(highlight);
@@ -17,7 +16,24 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
17
16
  const label = getSession(model).highlightLabelsVisible
18
17
  ? highlight.label
19
18
  : undefined;
20
- return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, gaps: model.nonGenomicTrackBands(), children: coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
19
+ return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, gaps: model.nonGenomicTrackBands(), children: _jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
20
+ {
21
+ label: 'Edit label...',
22
+ icon: EditIcon,
23
+ onClick: () => {
24
+ const session = getSession(model);
25
+ session.queueDialog(handleClose => [
26
+ HighlightLabelDialog,
27
+ {
28
+ label: highlight.label,
29
+ onSubmit: (label) => {
30
+ session.updateHighlight(highlight, { label });
31
+ },
32
+ handleClose,
33
+ },
34
+ ]);
35
+ },
36
+ },
21
37
  {
22
38
  label: 'Dismiss highlight',
23
39
  icon: CloseIcon,
@@ -33,6 +49,6 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
33
49
  },
34
50
  },
35
51
  ...model.highlightMenuItems(highlight),
36
- ] })) : null })) : null;
52
+ ] }) })) : null;
37
53
  });
38
54
  export default Highlight;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
3
  import { Typography } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
@@ -16,10 +16,15 @@ const useStyles = makeStyles()(theme => ({
16
16
  zIndex: 1,
17
17
  pointerEvents: 'none',
18
18
  },
19
+ // a sibling of the band, so a label wider than a narrow band runs past its
20
+ // right edge. No `top`: its static position is the band's top edge, where
21
+ // the first track's TrackLabel (zIndex 200) would otherwise cover it
19
22
  overlay: {
20
23
  position: 'absolute',
21
- top: 0,
22
24
  left: 3,
25
+ zIndex: 201,
26
+ pointerEvents: 'none',
27
+ whiteSpace: 'nowrap',
23
28
  },
24
29
  label: {
25
30
  color: theme.palette.text.primary,
@@ -35,20 +40,20 @@ export function gapMask(gaps) {
35
40
  return `linear-gradient(to bottom, ${stops.join(', ')})`;
36
41
  }
37
42
  // OverviewHighlight stays separate (different bpToPx API). A non-interactive
38
- // colored band with an
39
- // optional top label; when `children` (the highlight chip) is passed it renders
40
- // that instead of the plain label. Visibility is toggled from the view menu
43
+ // colored band with an optional top label; when `children` (the highlight
44
+ // chip) is passed it renders that instead of the plain label. Visibility is
45
+ // toggled from the view menu
41
46
  const HighlightBand = observer(function HighlightBand({ coords, background, label, gaps, children, }) {
42
47
  const { classes } = useStyles();
43
48
  const content = children ??
44
49
  (label ? (_jsx(Typography, { variant: "caption", className: classes.label, noWrap: true, children: label })) : null);
45
- return (_jsx("div", { "data-testid": "highlight-band", className: classes.highlight, style: {
46
- transform: `translateX(${coords.left}px)`,
47
- width: coords.width,
48
- background,
49
- ...(gaps?.length
50
- ? { maskImage: gapMask(gaps), WebkitMaskImage: gapMask(gaps) }
51
- : undefined),
52
- }, children: coords.width > 3 && content ? (_jsx("div", { className: classes.overlay, children: content })) : null }));
50
+ return (_jsxs(_Fragment, { children: [_jsx("div", { "data-testid": "highlight-band", className: classes.highlight, style: {
51
+ transform: `translateX(${coords.left}px)`,
52
+ width: coords.width,
53
+ background,
54
+ ...(gaps?.length
55
+ ? { maskImage: gapMask(gaps), WebkitMaskImage: gapMask(gaps) }
56
+ : undefined),
57
+ } }), content ? (_jsx("div", { className: classes.overlay, style: { transform: `translateX(${coords.left}px)` }, children: content })) : null] }));
53
58
  });
54
59
  export default HighlightBand;
@@ -3,13 +3,18 @@ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
3
3
  import LinkIcon from '@mui/icons-material/Link';
4
4
  import { Box, Tooltip, Typography } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
- // Interactive chip drawn inside a highlight band: a link icon tinted to the
7
- // band color plus an optional inline label, wrapped in a context menu. Sets its
8
- // own pointer-events since the band is click-through.
6
+ // Interactive chip at a highlight band's top: a link icon tinted to the band
7
+ // color plus an optional inline label, wrapped in a context menu. Its own
8
+ // background keeps it legible over the first track's label, and it sets its own
9
+ // pointer-events since the band is click-through.
9
10
  const HighlightChip = observer(function HighlightChip({ color, label, tooltip, menuItems, }) {
10
11
  // hide the icon color when the band is fully transparent ("label-only"
11
12
  // highlight); otherwise bump to 0.8 alpha so the chip stays legible
12
13
  const chipAlpha = color.alpha() === 0 ? 0 : 0.8;
13
- return (_jsx(Box, { sx: { pointerEvents: 'auto' }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems, children: _jsx(Tooltip, { title: tooltip, arrow: true, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(LinkIcon, { fontSize: "small", sx: { color: color.alpha(chipAlpha).toRgbString() } }), label ? (_jsx(Typography, { variant: "caption", noWrap: true, children: label })) : null] }) }) }) }));
14
+ return (_jsx(Box, { sx: {
15
+ pointerEvents: 'auto',
16
+ bgcolor: 'background.paper',
17
+ borderRadius: 1,
18
+ }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems, children: _jsx(Tooltip, { title: tooltip, arrow: true, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(LinkIcon, { fontSize: "small", sx: { color: color.alpha(chipAlpha).toRgbString() } }), label ? (_jsx(Typography, { variant: "caption", noWrap: true, children: label })) : null] }) }) }) }));
14
19
  });
15
20
  export default HighlightChip;
@@ -0,0 +1,6 @@
1
+ declare const HighlightLabelDialog: ({ label, onSubmit, handleClose, }: {
2
+ label?: string;
3
+ onSubmit: (label: string | undefined) => void;
4
+ handleClose: () => void;
5
+ }) => import("react").JSX.Element;
6
+ export default HighlightLabelDialog;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SubmitDialog } from '@jbrowse/core/ui';
4
+ import { TextField } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ // Names a highlight. The label is written on the band and in every view that
7
+ // draws the session's highlights, such as an alignment's column or a
8
+ // structure's residue; an empty label leaves the highlight unnamed.
9
+ const HighlightLabelDialog = observer(function HighlightLabelDialog({ label, onSubmit, handleClose, }) {
10
+ const [val, setVal] = useState(label ?? '');
11
+ return (_jsx(SubmitDialog, { open: true, maxWidth: "xs", fullWidth: true, title: "Edit highlight label", onCancel: handleClose, onSubmit: () => {
12
+ onSubmit(val.trim() || undefined);
13
+ handleClose();
14
+ }, children: _jsx(TextField, { label: "Label", autoFocus: true, fullWidth: true, variant: "outlined", value: val, onChange: event => {
15
+ setVal(event.target.value);
16
+ } }) }));
17
+ });
18
+ export default HighlightLabelDialog;
@@ -102,7 +102,9 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
102
102
  .flatMap(t => t.displays)
103
103
  .map(d => d.hoveredFeature)
104
104
  .find(Boolean);
105
- session.setHovered({ hoverPosition, hoverFeature });
105
+ // `viewId` lets a view paired with one linear view ignore another
106
+ // showing the same locus
107
+ session.setHovered({ hoverPosition, hoverFeature, viewId: model.id });
106
108
  }, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, "data-testid": `lgv-header-${model.id}`, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(NoTracksActiveButton, { model: model })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 2, "data-testid": "pinned-tracks", className: classes.pinnedTracks, style: {
107
109
  top: stickyTops.pinnedTracks,
108
110
  // the scroll port this sticks in, not the window: a
@@ -1,8 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { getFillProps } from '@jbrowse/core/util';
3
3
  // One full-height SVG highlight band with an optional label at the top,
4
- // mirroring the on-screen HighlightBand div. The label is hidden on bands too
5
- // narrow to fit it.
4
+ // mirroring the on-screen HighlightBand div: a label wider than its band runs
5
+ // past the band's right edge.
6
6
  export default function SVGHighlightBand({ coords, height, color, label, labelColor, }) {
7
- return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label && coords.width > 3 ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
7
+ return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
8
8
  }
@@ -40,13 +40,13 @@ const TracksContainer = observer(function TracksContainer({ children, model, })
40
40
  const { classes } = useStyles();
41
41
  const { pluginManager } = getEnv(model);
42
42
  const sideScroll = useSideScroll(model);
43
- const { showGridlines, showCenterLine } = model;
43
+ const { axisGrid: showGridlines, showCenterLine } = model;
44
44
  const ref = useRef(null);
45
45
  // shift-drag range select over the whole tracks area. This is intentionally
46
46
  // a separate range-select instance from the one inside <Rubberband> (the
47
47
  // scalebar control); don't dedupe them — they cover different regions.
48
48
  const range = useRangeSelect(ref, model, true);
49
- return (_jsxs("div", { ref: ref, "data-testid": "tracksContainer", className: classes.tracksContainer, onPointerDown: sideScroll.pointerDown, onClickCapture: sideScroll.clickCapture, onMouseDown: range.mouseDown, onMouseMove: event => {
49
+ return (_jsxs("div", { ref: ref, "data-testid": "tracksContainer", className: classes.tracksContainer, ...sideScroll, onMouseDown: range.mouseDown, onMouseMove: event => {
50
50
  range.mouseMove(event);
51
51
  }, onMouseLeave: () => {
52
52
  range.mouseOut();
@@ -22,7 +22,7 @@ export type SearchBoxPrefs = ReturnType<typeof useSearchBoxPrefs>;
22
22
  /**
23
23
  * The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
24
24
  * two headers name the same state and had drifted apart on it once already.
25
- * A caller spreading the rows into a "Show..." submenu passes `'Search boxes'`
25
+ * A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
26
26
  * as the toggle's label.
27
27
  */
28
28
  export declare function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }: SearchBoxPrefs, toggleLabel?: string): MenuItem[];
@@ -22,7 +22,7 @@ export function useSearchBoxPrefs(prefix, numViews) {
22
22
  /**
23
23
  * The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
24
24
  * two headers name the same state and had drifted apart on it once already.
25
- * A caller spreading the rows into a "Show..." submenu passes `'Search boxes'`
25
+ * A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
26
26
  * as the toggle's label.
27
27
  */
28
28
  export function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }, toggleLabel = 'Show search boxes') {
@@ -15,6 +15,7 @@ import type React from 'react';
15
15
  * effect, so a release that lands before React commits still finds them.
16
16
  */
17
17
  export declare function useSideScroll(model: LinearGenomeViewModel): {
18
- pointerDown: (event: React.PointerEvent) => void;
19
- clickCapture: (event: React.MouseEvent) => void;
18
+ onPointerDownCapture: (event: React.PointerEvent) => void;
19
+ onPointerDown: (event: React.PointerEvent) => void;
20
+ onClickCapture: (event: React.MouseEvent) => void;
20
21
  };
@@ -10,7 +10,7 @@ const PAN_CLICK_THRESHOLD_PX = 4;
10
10
  // as `data-gesture-owner`. `data-pan-dragging` is present from press to
11
11
  // release; `data-pan-moved` is set once the press travels past the threshold
12
12
  // or becomes a pinch, and cleared by the next press, so it still answers for
13
- // the click that follows the release: `clickCapture` swallows that click
13
+ // the click that follows the release: `onClickCapture` swallows that click
14
14
  // before any track sees it.
15
15
  const PAN_DRAGGING_ATTR = 'data-pan-dragging';
16
16
  const PAN_MOVED_ATTR = 'data-pan-moved';
@@ -36,16 +36,21 @@ export function useSideScroll(model) {
36
36
  useEffect(() => () => {
37
37
  gestureRef.current?.cancel();
38
38
  }, []);
39
- function pointerDown(event) {
39
+ // The marker outlives the gesture that set it, so every press clears it, in
40
+ // the capture phase: a track that keeps its own press and stops it bubbling
41
+ // (multiway's lane slide, maf's drag select) would otherwise leave the last
42
+ // pan's marker standing to swallow its click.
43
+ function onPointerDownCapture(event) {
44
+ if (!gestureRef.current) {
45
+ event.currentTarget.removeAttribute(PAN_MOVED_ATTR);
46
+ }
47
+ }
48
+ function onPointerDown(event) {
40
49
  const host = event.currentTarget;
41
50
  if (gestureRef.current) {
42
51
  gestureRef.current.join(event);
43
52
  return;
44
53
  }
45
- // Cleared for EVERY press, ahead of the returns below: the marker outlives
46
- // the gesture that set it, and a stale true is what the next reader of
47
- // this DOM contract would inherit.
48
- host.removeAttribute(PAN_MOVED_ATTR);
49
54
  if (event.shiftKey || event.button !== 0 || !event.isPrimary) {
50
55
  return;
51
56
  }
@@ -165,10 +170,10 @@ export function useSideScroll(model) {
165
170
  // The browser fires `click` after a release on whatever the press and the
166
171
  // release share, which over a track is its canvas, so a pan that ended on a
167
172
  // gene would open that gene's details.
168
- function clickCapture(event) {
173
+ function onClickCapture(event) {
169
174
  if (event.currentTarget.hasAttribute(PAN_MOVED_ATTR)) {
170
175
  event.stopPropagation();
171
176
  }
172
177
  }
173
- return { pointerDown, clickCapture };
178
+ return { onPointerDownCapture, onPointerDown, onClickCapture };
174
179
  }
@@ -1,2 +1,2 @@
1
1
  import type { InitState } from './types.ts';
2
- export declare const lgvLaunchKeys: import("@jbrowse/core/util/withLaunchInput").LaunchKeyRegistration<InitState, "bpPerPx" | "offsetPx" | "showCytobandsSetting">;
2
+ export declare const lgvLaunchKeys: import("@jbrowse/core/util/withLaunchInput").LaunchKeyRegistration<InitState, "bpPerPx" | "colorByCDS" | "offsetPx" | "showCytobandsSetting">;
@@ -4,10 +4,11 @@ import { defineLaunchKeys } from '@jbrowse/core/util/withLaunchInput';
4
4
  // key that partitions as a typo.
5
5
  //
6
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.
7
+ // a window, `showCytobandsSetting` its cytoband toggle and `colorByCDS` its
8
+ // frame coloring. None is a declared property, so the partition would read them
9
+ // as typos; the model's own preProcessSnapshot still converts them, so a URL or
10
+ // saved spec naming them goes on working. Share links carry all four, so they
11
+ // stay.
11
12
  //
12
13
  // #launchKeys LinearGenomeView — the URL parameters page renders this list
13
14
  // rather than restating it; each of these keys has its own `&param=` section
@@ -23,5 +24,5 @@ export const lgvLaunchKeys = defineLaunchKeys()({
23
24
  tracks: { kind: 'trackEntries' },
24
25
  highlight: { kind: 'launch' },
25
26
  }, {
26
- passThrough: ['bpPerPx', 'offsetPx', 'showCytobandsSetting'],
27
+ passThrough: ['bpPerPx', 'offsetPx', 'showCytobandsSetting', 'colorByCDS'],
27
28
  });
@@ -229,9 +229,9 @@ export function buildMenuItems(self) {
229
229
  {
230
230
  label: 'Guidelines',
231
231
  type: 'checkbox',
232
- checked: self.showGridlines,
232
+ checked: self.axisGrid,
233
233
  onClick: () => {
234
- self.setShowGridlines(!self.showGridlines);
234
+ self.setAxisGrid(!self.axisGrid);
235
235
  },
236
236
  },
237
237
  {
@@ -254,9 +254,9 @@ export function buildMenuItems(self) {
254
254
  {
255
255
  label: 'CDS reading frame colors',
256
256
  type: 'checkbox',
257
- checked: self.colorByCDS,
257
+ checked: self.colorByFrame,
258
258
  onClick: () => {
259
- self.setColorByCDS(!self.colorByCDS);
259
+ self.setColorByFrame(!self.colorByFrame);
260
260
  },
261
261
  },
262
262
  {