@jbrowse/display-ui 4.3.0 → 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.
- package/README.md +53 -13
- package/esm/AxisCaption.d.ts +24 -0
- package/esm/AxisCaption.js +57 -0
- package/esm/AxisGutter.d.ts +12 -0
- package/esm/AxisGutter.js +15 -0
- package/esm/CrossHatches.d.ts +12 -0
- package/esm/CrossHatches.js +31 -0
- package/esm/FloatingLegend.d.ts +4 -6
- package/esm/FloatingLegend.js +93 -29
- package/esm/FloatingSvgOverlay.d.ts +5 -0
- package/esm/FloatingSvgOverlay.js +25 -0
- package/esm/FloatingText.d.ts +50 -0
- package/esm/FloatingText.js +48 -0
- package/esm/PointerLayer.d.ts +12 -18
- package/esm/PointerLayer.js +14 -20
- package/esm/ScoreDomainCaption.d.ts +15 -0
- package/esm/ScoreDomainCaption.js +23 -0
- package/esm/ScoreRules.d.ts +21 -0
- package/esm/ScoreRules.js +46 -0
- package/esm/YScaleBar.d.ts +6 -0
- package/esm/YScaleBar.js +47 -0
- package/esm/YScaleBarOverlay.d.ts +11 -0
- package/esm/YScaleBarOverlay.js +20 -0
- package/esm/axisPlacement.d.ts +44 -0
- package/esm/axisPlacement.js +70 -0
- package/esm/bottomRightCorner.d.ts +3 -4
- package/esm/bottomRightCorner.js +3 -4
- package/esm/chromeOverlayContext.d.ts +1 -1
- package/esm/chromeOverlayContext.js +4 -5
- package/esm/chromeOverlays.d.ts +4 -2
- package/esm/cullOverlappingLabels.d.ts +14 -0
- package/esm/cullOverlappingLabels.js +31 -0
- package/esm/embed/index.d.ts +70 -0
- package/esm/embed/index.js +72 -0
- package/esm/embed/legend.d.ts +10 -0
- package/esm/embed/legend.js +40 -0
- package/esm/embed/location.d.ts +17 -0
- package/esm/embed/location.js +65 -0
- package/esm/embed/provider.d.ts +11 -0
- package/esm/embed/provider.js +6 -0
- package/esm/embed/regions.d.ts +53 -0
- package/esm/embed/regions.js +95 -0
- package/esm/floatingLegendTop.d.ts +2 -0
- package/esm/floatingLegendTop.js +3 -0
- package/esm/index.d.ts +19 -0
- package/esm/index.js +24 -0
- package/esm/plainChromeOverlays.d.ts +1 -1
- package/esm/plainChromeOverlays.js +6 -6
- package/esm/tooltip/Tooltip.d.ts +3 -3
- package/esm/tooltip/Tooltip.js +3 -3
- package/esm/trackControl/trackControlContext.d.ts +1 -1
- package/esm/trackControl/trackControlContext.js +6 -6
- package/esm/trackOverlay/TrackOverlaySlot.d.ts +2 -2
- package/esm/trackOverlay/TrackOverlaySlot.js +2 -2
- package/esm/valueScale.d.ts +72 -0
- package/esm/valueScale.js +1 -0
- package/esm/yAxisConstants.d.ts +7 -0
- package/esm/yAxisConstants.js +19 -0
- package/esm/yScaleTicks.d.ts +49 -0
- package/esm/yScaleTicks.js +47 -0
- package/package.json +11 -7
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, Suspense } from 'react';
|
|
3
|
+
import AppReadyMarker from '@jbrowse/core/ui/AppReadyMarker';
|
|
4
|
+
import { useSlowLoad, useStalled, useWidthSetter, } from '@jbrowse/core/util/hooks';
|
|
5
|
+
import { getSession } from '@jbrowse/core/util/mstUtils';
|
|
6
|
+
import { usePanZoom } from '@jbrowse/core/util/usePanZoom';
|
|
7
|
+
import { useResizeDrag } from '@jbrowse/core/util/useResizeDrag';
|
|
8
|
+
import { observer } from 'mobx-react';
|
|
9
|
+
import { TrackOverlaySlot } from '../trackOverlay/TrackOverlaySlot.js';
|
|
10
|
+
export { Legend } from './legend.js';
|
|
11
|
+
export { LocationBox, useLocationBox } from './location.js';
|
|
12
|
+
export { EmbedProvider } from './provider.js';
|
|
13
|
+
export { Highlights, RegionSeams, Scalebar } from './regions.js';
|
|
14
|
+
export const Track = observer(function Track({ view, trackId, }) {
|
|
15
|
+
const display = view.getTrack(trackId)?.activeDisplay;
|
|
16
|
+
if (!display) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const { RenderingComponent } = display;
|
|
20
|
+
return (_jsx(TrackOverlaySlot, { zIndex: 3, style: { height: display.height }, children: _jsx("div", { style: { position: 'absolute', inset: 0, contain: 'strict' }, children: _jsx(Suspense, { fallback: null, children: _jsx(RenderingComponent, { model: display, onHorizontalScroll: view.horizontalScroll }) }) }) }));
|
|
21
|
+
});
|
|
22
|
+
export const ResizeHandle = observer(function ResizeHandle({ view, trackId, style, }) {
|
|
23
|
+
const display = view.getTrack(trackId)?.activeDisplay;
|
|
24
|
+
const props = useResizeDrag({
|
|
25
|
+
onDragStart: () => {
|
|
26
|
+
display?.setResizing(true);
|
|
27
|
+
},
|
|
28
|
+
onDrag: distance => {
|
|
29
|
+
display?.resizeHeight(distance);
|
|
30
|
+
},
|
|
31
|
+
onDragEnd: () => {
|
|
32
|
+
display?.setResizing(false);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
return display ? (_jsx("div", { ...props, "data-gesture-owner": "true", "aria-label": `Resize ${trackId}`, style: {
|
|
36
|
+
height: 4,
|
|
37
|
+
cursor: 'row-resize',
|
|
38
|
+
touchAction: 'none',
|
|
39
|
+
background: 'color-mix(in srgb, currentColor 20%, transparent)',
|
|
40
|
+
...style,
|
|
41
|
+
} })) : null;
|
|
42
|
+
});
|
|
43
|
+
export const TrackToggle = observer(function TrackToggle({ view, trackId, style, children, }) {
|
|
44
|
+
return (_jsxs("label", { style: style, children: [_jsx("input", { type: "checkbox", checked: view.tracks.some(t => t.configuration.trackId === trackId), onChange: () => {
|
|
45
|
+
void view.launchToggleTrack(trackId);
|
|
46
|
+
} }), children] }));
|
|
47
|
+
});
|
|
48
|
+
function ViewLoading({ message, progress, source }) {
|
|
49
|
+
const slow = useSlowLoad();
|
|
50
|
+
const stalled = useStalled(`${message}|${progress}|${source}`);
|
|
51
|
+
return (_jsxs(_Fragment, { children: [slow ? message : 'Loading', !slow || progress === undefined ? null : (_jsx("progress", { value: progress, max: 1, style: { display: 'block', width: 300, maxWidth: '100%' } })), stalled && source ? (_jsxs("div", { style: { overflowWrap: 'anywhere' }, children: ["still waiting on ", source] })) : null] }));
|
|
52
|
+
}
|
|
53
|
+
export const ViewStatus = observer(function ViewStatus({ view, style, }) {
|
|
54
|
+
const { status } = view;
|
|
55
|
+
return status.type === 'ready' ? null : (_jsx("div", { role: status.type === 'error' ? 'alert' : 'status', style: {
|
|
56
|
+
padding: '10px 12px',
|
|
57
|
+
fontSize: '0.85rem',
|
|
58
|
+
opacity: 0.75,
|
|
59
|
+
...style,
|
|
60
|
+
}, children: status.type === 'error' ? (`Could not load: ${status.error instanceof Error ? status.error.message : String(status.error)}`) : status.type === 'loading' ? (_jsx(ViewLoading, { ...status })) : ('Nothing to show yet') }));
|
|
61
|
+
});
|
|
62
|
+
export const TrackStack = observer(function TrackStack({ view, trackIds, renderTrack, style, children, }) {
|
|
63
|
+
const ref = useWidthSetter(view);
|
|
64
|
+
const { containerProps } = usePanZoom(ref, view);
|
|
65
|
+
const ids = trackIds ?? view.tracks.map(t => t.configuration.trackId);
|
|
66
|
+
return (_jsxs("div", { ref: ref, ...containerProps, style: {
|
|
67
|
+
position: 'relative',
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
cursor: 'grab',
|
|
70
|
+
...style,
|
|
71
|
+
}, children: [view.status.type === 'ready' ? (_jsxs(_Fragment, { children: [children, ids.map(id => renderTrack ? (_jsx(Fragment, { children: renderTrack(id) }, id)) : (_jsx(Track, { view: view, trackId: id }, id)))] })) : (_jsx(ViewStatus, { view: view })), _jsx(AppReadyMarker, { session: getSession(view) })] }));
|
|
72
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export declare const Legend: ({ view, trackId, style, }: {
|
|
3
|
+
view: {
|
|
4
|
+
getTrack: (trackId: string) => {
|
|
5
|
+
activeDisplay: unknown;
|
|
6
|
+
} | undefined;
|
|
7
|
+
};
|
|
8
|
+
trackId: string;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
}) => React.JSX.Element | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LegendSwatchGlyph } from '@jbrowse/core/ui/LegendSwatchGlyph';
|
|
3
|
+
import { legendSwatches, nonEmptyLegendSections, } from '@jbrowse/core/ui/legendSpec';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
const SWATCH = 12;
|
|
6
|
+
function gradientCss({ stops }) {
|
|
7
|
+
return `linear-gradient(to right, ${stops
|
|
8
|
+
.map(({ color, opacity = 1, offset }) => `color-mix(in srgb, ${color} ${Math.round(opacity * 100)}%, transparent) ${offset * 100}%`)
|
|
9
|
+
.join(', ')})`;
|
|
10
|
+
}
|
|
11
|
+
function Row({ item }) {
|
|
12
|
+
const { gradient, label, hidden } = item;
|
|
13
|
+
return gradient ? (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 4 }, children: [label, _jsx("span", { children: gradient.minLabel }), _jsx("span", { style: {
|
|
14
|
+
width: 80,
|
|
15
|
+
height: SWATCH,
|
|
16
|
+
backgroundImage: gradientCss(gradient),
|
|
17
|
+
} }), _jsx("span", { children: gradient.maxLabel })] })) : (_jsxs("span", { style: {
|
|
18
|
+
display: 'inline-flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
gap: 4,
|
|
21
|
+
opacity: hidden ? 0.5 : undefined,
|
|
22
|
+
textDecoration: hidden ? 'line-through' : undefined,
|
|
23
|
+
}, children: [legendSwatches(item).map(swatch => (_jsx("svg", { "aria-hidden": true, width: SWATCH, height: SWATCH, children: _jsx(LegendSwatchGlyph, { swatch: swatch, size: SWATCH }) }, swatch.color))), label] }));
|
|
24
|
+
}
|
|
25
|
+
function legendSpecIn(display) {
|
|
26
|
+
return display && typeof display === 'object' && 'legendSpec' in display
|
|
27
|
+
? display.legendSpec
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
export const Legend = observer(function Legend({ view, trackId, style, }) {
|
|
31
|
+
const legendSpec = legendSpecIn(view.getTrack(trackId)?.activeDisplay);
|
|
32
|
+
const sections = nonEmptyLegendSections(legendSpec?.sections ?? []);
|
|
33
|
+
return legendSpec && sections.length ? (_jsxs("div", { "data-testid": "embed-legend", style: {
|
|
34
|
+
display: 'flex',
|
|
35
|
+
flexWrap: 'wrap',
|
|
36
|
+
gap: '2px 12px',
|
|
37
|
+
fontSize: '0.75rem',
|
|
38
|
+
...style,
|
|
39
|
+
}, children: [legendSpec.title ? _jsx("strong", { children: legendSpec.title }) : null, sections.map(section => (_jsxs("span", { style: { display: 'contents' }, children: [sections.length > 1 && section.title ? (_jsx("strong", { children: section.title })) : null, section.note ? (_jsx("span", { style: { opacity: 0.7 }, children: section.note })) : null, section.items.map((item, i) => (_jsx(Row, { item: item }, i)))] }, section.id)))] })) : null;
|
|
40
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface LocationView {
|
|
3
|
+
coarseVisibleLocStrings: string;
|
|
4
|
+
navToLocString: (input: string) => Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
export declare function useLocationBox(view: LocationView): {
|
|
7
|
+
value: string;
|
|
8
|
+
pending: boolean;
|
|
9
|
+
error: unknown;
|
|
10
|
+
edit(text: string): void;
|
|
11
|
+
reset(): void;
|
|
12
|
+
go(input?: string): void;
|
|
13
|
+
};
|
|
14
|
+
export declare const LocationBox: ({ view, style, }: {
|
|
15
|
+
view: LocationView;
|
|
16
|
+
style?: React.CSSProperties;
|
|
17
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
export function useLocationBox(view) {
|
|
5
|
+
const [draft, setDraft] = useState();
|
|
6
|
+
const [pending, setPending] = useState(false);
|
|
7
|
+
const [error, setError] = useState();
|
|
8
|
+
const requests = useRef(0);
|
|
9
|
+
const value = draft ?? view.coarseVisibleLocStrings;
|
|
10
|
+
return {
|
|
11
|
+
value,
|
|
12
|
+
pending,
|
|
13
|
+
error,
|
|
14
|
+
edit(text) {
|
|
15
|
+
setDraft(text);
|
|
16
|
+
},
|
|
17
|
+
reset() {
|
|
18
|
+
setDraft(undefined);
|
|
19
|
+
setError(undefined);
|
|
20
|
+
},
|
|
21
|
+
go(input = value) {
|
|
22
|
+
const request = ++requests.current;
|
|
23
|
+
const isLatest = () => request === requests.current;
|
|
24
|
+
setDraft(input);
|
|
25
|
+
setError(undefined);
|
|
26
|
+
setPending(true);
|
|
27
|
+
view
|
|
28
|
+
.navToLocString(input)
|
|
29
|
+
.then(() => {
|
|
30
|
+
if (isLatest()) {
|
|
31
|
+
setDraft(undefined);
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.catch((e) => {
|
|
35
|
+
if (isLatest()) {
|
|
36
|
+
setError(e);
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
.finally(() => {
|
|
40
|
+
if (isLatest()) {
|
|
41
|
+
setPending(false);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export const LocationBox = observer(function LocationBox({ view, style, }) {
|
|
48
|
+
const box = useLocationBox(view);
|
|
49
|
+
return (_jsxs("form", { style: {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexWrap: 'wrap',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
gap: 4,
|
|
54
|
+
...style,
|
|
55
|
+
}, onSubmit: event => {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
box.go();
|
|
58
|
+
}, children: [_jsx("input", { "aria-label": "Location", value: box.value, size: 38, style: { font: 'inherit', padding: '2px 4px' }, onChange: event => {
|
|
59
|
+
box.edit(event.target.value);
|
|
60
|
+
}, onKeyDown: event => {
|
|
61
|
+
if (event.key === 'Escape') {
|
|
62
|
+
box.reset();
|
|
63
|
+
}
|
|
64
|
+
} }), _jsx("button", { type: "submit", disabled: box.pending, style: { font: 'inherit' }, children: "Go" }), box.error ? (_jsx("span", { role: "alert", children: box.error instanceof Error ? box.error.message : String(box.error) })) : null] }));
|
|
65
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DisplayChromeOverlays } from '../chromeOverlays.ts';
|
|
2
|
+
import type { TrackControlComponent } from '../trackControl/types.ts';
|
|
3
|
+
import type { ThemeModeSession } from '@jbrowse/core/ui/PaletteContext';
|
|
4
|
+
import type React from 'react';
|
|
5
|
+
export declare function EmbedProvider({ session, mode, overlays, trackControl, children, }: {
|
|
6
|
+
session: ThemeModeSession;
|
|
7
|
+
mode?: 'light' | 'dark';
|
|
8
|
+
overlays?: Partial<DisplayChromeOverlays>;
|
|
9
|
+
trackControl?: TrackControlComponent;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import DisplayUIProvider from '../DisplayUIProvider.js';
|
|
4
|
+
export function EmbedProvider({ session, mode, overlays, trackControl, children, }) {
|
|
5
|
+
return (_jsx(SessionPaletteProvider, { session: session, mode: mode, children: _jsx(DisplayUIProvider, { overlays: overlays, trackControl: trackControl, children: children }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface RegionSeamsView {
|
|
3
|
+
staticBlocksTranslateX: number;
|
|
4
|
+
paddingSpans: readonly {
|
|
5
|
+
key: string;
|
|
6
|
+
x: number;
|
|
7
|
+
width: number;
|
|
8
|
+
kind: 'seam' | 'elided' | 'boundary';
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
export interface ScalebarView {
|
|
12
|
+
staticBlocks: {
|
|
13
|
+
totalWidthPx: number;
|
|
14
|
+
};
|
|
15
|
+
staticBlocksTranslateX: number;
|
|
16
|
+
scalebarLabels: readonly {
|
|
17
|
+
x: number;
|
|
18
|
+
label: string;
|
|
19
|
+
}[];
|
|
20
|
+
scalebarRefNameLabels: {
|
|
21
|
+
labels: readonly {
|
|
22
|
+
key: string;
|
|
23
|
+
text: string;
|
|
24
|
+
transform: number;
|
|
25
|
+
maxWidth: number;
|
|
26
|
+
paddingLeft: number;
|
|
27
|
+
}[];
|
|
28
|
+
caption?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface HighlightsView {
|
|
32
|
+
highlights: readonly {
|
|
33
|
+
assemblyName?: string;
|
|
34
|
+
refName: string;
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
color?: string;
|
|
38
|
+
label?: string;
|
|
39
|
+
}[];
|
|
40
|
+
getHighlightCoords: (highlight: HighlightsView['highlights'][number]) => {
|
|
41
|
+
left: number;
|
|
42
|
+
width: number;
|
|
43
|
+
} | undefined;
|
|
44
|
+
}
|
|
45
|
+
export declare const RegionSeams: ({ view, }: {
|
|
46
|
+
view: RegionSeamsView;
|
|
47
|
+
}) => React.JSX.Element;
|
|
48
|
+
export declare const Highlights: ({ view, }: {
|
|
49
|
+
view: HighlightsView;
|
|
50
|
+
}) => (React.JSX.Element | null)[];
|
|
51
|
+
export declare const Scalebar: ({ view, style, ...props }: {
|
|
52
|
+
view: ScalebarView;
|
|
53
|
+
} & React.ComponentProps<'div'>) => React.JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import { highlightKey } from '@jbrowse/core/util/highlights';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
const spanFill = {
|
|
6
|
+
seam: 'color-mix(in srgb, CanvasText 45%, Canvas)',
|
|
7
|
+
boundary: 'color-mix(in srgb, CanvasText 12%, Canvas)',
|
|
8
|
+
elided: 'color-mix(in srgb, CanvasText 30%, Canvas)',
|
|
9
|
+
};
|
|
10
|
+
export const RegionSeams = observer(function RegionSeams({ view, }) {
|
|
11
|
+
return (_jsx("div", { "aria-hidden": true, "data-region-seams": true, style: {
|
|
12
|
+
position: 'absolute',
|
|
13
|
+
top: 0,
|
|
14
|
+
bottom: 0,
|
|
15
|
+
left: 0,
|
|
16
|
+
zIndex: 2,
|
|
17
|
+
pointerEvents: 'none',
|
|
18
|
+
transform: `translateX(${view.staticBlocksTranslateX}px)`,
|
|
19
|
+
}, children: view.paddingSpans.map(({ key, x, width, kind }) => (_jsx("div", { "data-span": kind, style: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
top: 0,
|
|
22
|
+
bottom: 0,
|
|
23
|
+
left: x,
|
|
24
|
+
width,
|
|
25
|
+
background: spanFill[kind],
|
|
26
|
+
} }, key))) }));
|
|
27
|
+
});
|
|
28
|
+
export const Highlights = observer(function Highlights({ view, }) {
|
|
29
|
+
const palette = usePalette();
|
|
30
|
+
return view.highlights.map((highlight, i) => {
|
|
31
|
+
const coords = view.getHighlightCoords(highlight);
|
|
32
|
+
return coords ? (_jsx("div", { "data-testid": "highlight-band", style: {
|
|
33
|
+
position: 'absolute',
|
|
34
|
+
top: 0,
|
|
35
|
+
bottom: 0,
|
|
36
|
+
left: 0,
|
|
37
|
+
width: coords.width,
|
|
38
|
+
transform: `translateX(${coords.left}px)`,
|
|
39
|
+
zIndex: 3,
|
|
40
|
+
pointerEvents: 'none',
|
|
41
|
+
background: highlight.color ??
|
|
42
|
+
`color-mix(in srgb, ${palette.highlight.main} 20%, transparent)`,
|
|
43
|
+
}, children: highlight.label ? (_jsx("span", { style: {
|
|
44
|
+
position: 'absolute',
|
|
45
|
+
top: 0,
|
|
46
|
+
left: 3,
|
|
47
|
+
fontSize: '0.7rem',
|
|
48
|
+
whiteSpace: 'nowrap',
|
|
49
|
+
color: palette.text.primary,
|
|
50
|
+
}, children: highlight.label })) : null }, highlightKey(highlight, i))) : null;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const SCALEBAR_HEIGHT = 20;
|
|
54
|
+
export const Scalebar = observer(function Scalebar({ view, style, ...props }) {
|
|
55
|
+
const palette = usePalette();
|
|
56
|
+
const { scalebarLabels, scalebarRefNameLabels, staticBlocks, staticBlocksTranslateX, } = view;
|
|
57
|
+
const chip = {
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
top: 0,
|
|
60
|
+
whiteSpace: 'nowrap',
|
|
61
|
+
background: palette.background.paper,
|
|
62
|
+
color: palette.text.primary,
|
|
63
|
+
};
|
|
64
|
+
return (_jsxs("div", { "data-gesture-owner": "true", ...props, style: {
|
|
65
|
+
position: 'relative',
|
|
66
|
+
height: SCALEBAR_HEIGHT,
|
|
67
|
+
lineHeight: `${SCALEBAR_HEIGHT}px`,
|
|
68
|
+
fontSize: '0.7rem',
|
|
69
|
+
overflow: 'clip',
|
|
70
|
+
userSelect: 'none',
|
|
71
|
+
borderBottom: `1px solid ${palette.divider}`,
|
|
72
|
+
...style,
|
|
73
|
+
}, children: [_jsx("div", { style: {
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
top: 0,
|
|
76
|
+
left: 0,
|
|
77
|
+
height: '100%',
|
|
78
|
+
width: staticBlocks.totalWidthPx,
|
|
79
|
+
transform: `translateX(${Math.round(staticBlocksTranslateX)}px)`,
|
|
80
|
+
}, children: scalebarLabels.map(({ x, label }, i) => (_jsx("span", { style: {
|
|
81
|
+
...chip,
|
|
82
|
+
left: x,
|
|
83
|
+
transform: 'translateX(-50%)',
|
|
84
|
+
padding: '0 3px',
|
|
85
|
+
}, children: label }, i))) }), scalebarRefNameLabels.labels.map(({ key, text, transform, maxWidth, paddingLeft }) => (_jsx("span", { style: {
|
|
86
|
+
...chip,
|
|
87
|
+
left: 0,
|
|
88
|
+
transform: `translateX(${transform}px)`,
|
|
89
|
+
maxWidth,
|
|
90
|
+
paddingLeft,
|
|
91
|
+
boxSizing: 'border-box',
|
|
92
|
+
fontWeight: 'bold',
|
|
93
|
+
overflow: 'clip',
|
|
94
|
+
}, children: text }, key))), scalebarRefNameLabels.caption === undefined ? null : (_jsx("span", { style: { ...chip, left: 0, fontWeight: 'bold' }, children: scalebarRefNameLabels.caption }))] }));
|
|
95
|
+
});
|
package/esm/index.d.ts
CHANGED
|
@@ -8,7 +8,12 @@ export { useTrackControlMenu } from './trackControl/useTrackControlMenu.tsx';
|
|
|
8
8
|
export type { TrackControlMenu } from './trackControl/useTrackControlMenu.tsx';
|
|
9
9
|
export type { TrackControlComponent, TrackControlIcon, TrackControlOption, TrackControlProps, } from './trackControl/types.ts';
|
|
10
10
|
export { default as FloatingLegend } from './FloatingLegend.tsx';
|
|
11
|
+
export { FLOATING_LEGEND_RIGHT_PX, FLOATING_LEGEND_TOP_PX, } from './floatingLegendTop.ts';
|
|
11
12
|
export type { LegendItem, LegendSection } from './FloatingLegend.tsx';
|
|
13
|
+
export { FloatingSvgOverlay } from './FloatingSvgOverlay.tsx';
|
|
14
|
+
export { FloatingText, SvgHaloText, TEXT_BASELINE_RATIO, textHalo, } from './FloatingText.tsx';
|
|
15
|
+
export { cullOverlappingLabels } from './cullOverlappingLabels.ts';
|
|
16
|
+
export type { LabelBox } from './cullOverlappingLabels.ts';
|
|
12
17
|
export { default as Tooltip } from './tooltip/Tooltip.tsx';
|
|
13
18
|
export { useTooltip } from './tooltip/useTooltip.tsx';
|
|
14
19
|
export type { TooltipTrigger } from './tooltip/useTooltip.tsx';
|
|
@@ -20,3 +25,17 @@ export { TrackOverlaySlot } from './trackOverlay/TrackOverlaySlot.tsx';
|
|
|
20
25
|
export { tooLargeBannerText } from './tooLargeBannerText.ts';
|
|
21
26
|
export { isLiveModel } from './isLiveModel.ts';
|
|
22
27
|
export { BOTTOM_RIGHT_CONTROLS_ORDER, BottomRightCornerContext, } from './bottomRightCorner.ts';
|
|
28
|
+
export type { AxisSide, ValueScale, ValueScaleRule, YAxis, } from './valueScale.ts';
|
|
29
|
+
export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from './yScaleTicks.ts';
|
|
30
|
+
export type { YScaleTicks } from './yScaleTicks.ts';
|
|
31
|
+
export { DEFAULT_RULE_COLOR, SCORE_CAPTION_HEIGHT, YSCALEBAR_LABEL_OFFSET, } from './yAxisConstants.ts';
|
|
32
|
+
export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, } from './axisPlacement.ts';
|
|
33
|
+
export { default as AxisGutter } from './AxisGutter.tsx';
|
|
34
|
+
export { AxisCaptionOverlay, default as AxisCaption } from './AxisCaption.tsx';
|
|
35
|
+
export { default as YScaleBar } from './YScaleBar.tsx';
|
|
36
|
+
export { default as YScaleBarOverlay } from './YScaleBarOverlay.tsx';
|
|
37
|
+
export { default as ScoreDomainCaption } from './ScoreDomainCaption.tsx';
|
|
38
|
+
export { formatScore } from '@jbrowse/core/util/numericUtils';
|
|
39
|
+
export { CrossHatchLines, default as CrossHatches } from './CrossHatches.tsx';
|
|
40
|
+
export { ScoreRuleLines, default as ScoreRules } from './ScoreRules.tsx';
|
|
41
|
+
export type { ScoreRuleMark } from './ScoreRules.tsx';
|
package/esm/index.js
CHANGED
|
@@ -23,6 +23,19 @@ export { useTrackControlMenu } from './trackControl/useTrackControlMenu.js';
|
|
|
23
23
|
// a display draws that is not its data. `@jbrowse/plugin-linear-genome-view`
|
|
24
24
|
// re-exports it, so its published name is unchanged.
|
|
25
25
|
export { default as FloatingLegend } from './FloatingLegend.js';
|
|
26
|
+
export { FLOATING_LEGEND_RIGHT_PX, FLOATING_LEGEND_TOP_PX, } from './floatingLegendTop.js';
|
|
27
|
+
// The <svg> counterpart of that legend, for a display whose key is drawn by an
|
|
28
|
+
// SVG-export path as well as on screen. Beside it for the same reasons — it
|
|
29
|
+
// reaches no UI toolkit, and portaling chrome above the region masks is what
|
|
30
|
+
// this package is — and re-exported by `@jbrowse/plugin-linear-genome-view`, so
|
|
31
|
+
// its published name is unchanged.
|
|
32
|
+
export { FloatingSvgOverlay } from './FloatingSvgOverlay.js';
|
|
33
|
+
// One line of text floated over a display, and its `<text>` twin for an
|
|
34
|
+
// export: the canvas feature labels, the mark display's text marks and the
|
|
35
|
+
// arc labels' export emit through these, so a floated label's typography is
|
|
36
|
+
// one declaration and each display keeps only where it puts them.
|
|
37
|
+
export { FloatingText, SvgHaloText, TEXT_BASELINE_RATIO, textHalo, } from './FloatingText.js';
|
|
38
|
+
export { cullOverlappingLabels } from './cullOverlappingLabels.js';
|
|
26
39
|
// The hover label every control in here used to delegate to the browser's
|
|
27
40
|
// `title` attribute. The box it draws is `@jbrowse/core`'s `BaseTooltip`, the
|
|
28
41
|
// same one the display tooltips draw, anchored to the control instead of to the
|
|
@@ -49,3 +62,14 @@ export { isLiveModel } from './isLiveModel.js';
|
|
|
49
62
|
// contract the prop types cannot carry, so it belongs with them rather than
|
|
50
63
|
// with the chrome that happens to mount it.
|
|
51
64
|
export { BOTTOM_RIGHT_CONTROLS_ORDER, BottomRightCornerContext, } from './bottomRightCorner.js';
|
|
65
|
+
export { AXIS_GUTTER_WIDTH_PX, axisPlotBox, clampStrokeInsideAxis, scoreToAxisY, } from './yScaleTicks.js';
|
|
66
|
+
export { DEFAULT_RULE_COLOR, SCORE_CAPTION_HEIGHT, YSCALEBAR_LABEL_OFFSET, } from './yAxisConstants.js';
|
|
67
|
+
export { AXIS_RIGHT_INSET_PX, COMPACT_AXIS_HEIGHT, axisDrawn, axisGutterLeft, axisGutterWidth, } from './axisPlacement.js';
|
|
68
|
+
export { default as AxisGutter } from './AxisGutter.js';
|
|
69
|
+
export { AxisCaptionOverlay, default as AxisCaption } from './AxisCaption.js';
|
|
70
|
+
export { default as YScaleBar } from './YScaleBar.js';
|
|
71
|
+
export { default as YScaleBarOverlay } from './YScaleBarOverlay.js';
|
|
72
|
+
export { default as ScoreDomainCaption } from './ScoreDomainCaption.js';
|
|
73
|
+
export { formatScore } from '@jbrowse/core/util/numericUtils';
|
|
74
|
+
export { CrossHatchLines, default as CrossHatches } from './CrossHatches.js';
|
|
75
|
+
export { ScoreRuleLines, default as ScoreRules } from './ScoreRules.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DisplayChromeOverlays } from './chromeOverlays.ts';
|
|
2
2
|
/**
|
|
3
3
|
* #api
|
|
4
|
-
* The
|
|
4
|
+
* The `displayPhase` states drawn with no UI toolkit: no theme provider to
|
|
5
5
|
* mount, no emotion in the host page, and nothing that reads as a stray Material
|
|
6
6
|
* widget inside someone else's design system.
|
|
7
7
|
*
|
|
@@ -19,9 +19,9 @@ import Tooltip from './tooltip/Tooltip.js';
|
|
|
19
19
|
// host's own cascade drives them. Restyle by wrapping these, or write your own
|
|
20
20
|
// set against the same interface.
|
|
21
21
|
//
|
|
22
|
-
// The testids are NOT cosmetic. `loading-overlay`, `loading-overlay-
|
|
23
|
-
// `loading-overlay-retry`, `progress-chip`,
|
|
24
|
-
// `use_canvas2d_button` are a contract across four test systems (jest, browser
|
|
22
|
+
// The testids are NOT cosmetic. `loading-overlay`, `loading-overlay-canceled`,
|
|
23
|
+
// `loading-overlay-cancel`, `loading-overlay-retry`, `progress-chip`,
|
|
24
|
+
// `reload_button` and `use_canvas2d_button` are a contract across four test systems (jest, browser
|
|
25
25
|
// tests, screenshot harness, jbrowse-img), so a replacement set that wants to
|
|
26
26
|
// run the existing suites has to keep them.
|
|
27
27
|
// The scrim and the too-large/error banners all need to sit over the canvas
|
|
@@ -95,7 +95,7 @@ function errorText(error) {
|
|
|
95
95
|
// `aria-valuenow` changes on a `progressbar`, so the value is reachable on
|
|
96
96
|
// demand and silent otherwise — which is the behaviour a progress indicator
|
|
97
97
|
// should have. `StatusProgressBar` in core does the same thing and is
|
|
98
|
-
// toolkit-free, but it paints `
|
|
98
|
+
// toolkit-free, but it paints the palette's `accent`; every rule in this
|
|
99
99
|
// file reads the host's own cascade instead, so the bar is drawn here rather
|
|
100
100
|
// than imported.
|
|
101
101
|
function ProgressBar({ fraction }) {
|
|
@@ -149,7 +149,7 @@ const PlainLoading = observer(function PlainLoading({ model, visible, }) {
|
|
|
149
149
|
return null;
|
|
150
150
|
}
|
|
151
151
|
const { statusMessage, statusProgress, fetchCanceled } = model;
|
|
152
|
-
return (_jsx("div", { style: overlayBox, "data-testid":
|
|
152
|
+
return (_jsx("div", { style: overlayBox, "data-testid": fetchCanceled ? 'loading-overlay-canceled' : 'loading-overlay', children: _jsx("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", children: fetchCanceled ? (_jsxs(_Fragment, { children: [_jsx("span", { children: "Loading canceled" }), model.reload ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-retry", onClick: () => {
|
|
153
153
|
model.reload?.();
|
|
154
154
|
}, children: "Retry" })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: statusProgress === undefined
|
|
155
155
|
? `${statusMessage || 'Loading'}...`
|
|
@@ -166,7 +166,7 @@ const PlainBackgroundProgress = observer(function PlainBackgroundProgress({ mode
|
|
|
166
166
|
});
|
|
167
167
|
/**
|
|
168
168
|
* #api
|
|
169
|
-
* The
|
|
169
|
+
* The `displayPhase` states drawn with no UI toolkit: no theme provider to
|
|
170
170
|
* mount, no emotion in the host page, and nothing that reads as a stray Material
|
|
171
171
|
* widget inside someone else's design system.
|
|
172
172
|
*
|
package/esm/tooltip/Tooltip.d.ts
CHANGED
|
@@ -25,9 +25,9 @@ interface TriggerProps {
|
|
|
25
25
|
* Takes a single element child and clones it rather than wrapping it: the
|
|
26
26
|
* controls that want a tooltip are absolutely positioned inside a legend or sit
|
|
27
27
|
* in a flex row, where an extra `<span>` moves them. The child keeps its own
|
|
28
|
-
* handlers — these compose on top of them.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* handlers — these compose on top of them. MUI's `Tooltip`, which the rest of
|
|
29
|
+
* JBrowse's chrome is built on, uses the same trigger pattern; only the toolkit
|
|
30
|
+
* differs.
|
|
31
31
|
*
|
|
32
32
|
* ```tsx
|
|
33
33
|
* <Tooltip title="Hide legend">
|
package/esm/tooltip/Tooltip.js
CHANGED
|
@@ -24,9 +24,9 @@ function compose(theirs, ours) {
|
|
|
24
24
|
* Takes a single element child and clones it rather than wrapping it: the
|
|
25
25
|
* controls that want a tooltip are absolutely positioned inside a legend or sit
|
|
26
26
|
* in a flex row, where an extra `<span>` moves them. The child keeps its own
|
|
27
|
-
* handlers — these compose on top of them.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* handlers — these compose on top of them. MUI's `Tooltip`, which the rest of
|
|
28
|
+
* JBrowse's chrome is built on, uses the same trigger pattern; only the toolkit
|
|
29
|
+
* differs.
|
|
30
30
|
*
|
|
31
31
|
* ```tsx
|
|
32
32
|
* <Tooltip title="Hide legend">
|
|
@@ -2,6 +2,6 @@ import type { TrackControlComponent } from './types.ts';
|
|
|
2
2
|
export declare const TrackControlProvider: import("react").Provider<TrackControlComponent | undefined>;
|
|
3
3
|
/**
|
|
4
4
|
* The component a provider installed, or `undefined` for "nobody asked". The
|
|
5
|
-
* caller supplies the default,
|
|
5
|
+
* caller supplies the default, so this module binds none.
|
|
6
6
|
*/
|
|
7
7
|
export declare function useTrackControlOverride(): TrackControlComponent | undefined;
|
|
@@ -6,9 +6,9 @@ import { createContext, use } from 'react';
|
|
|
6
6
|
/**
|
|
7
7
|
* Runtime override for how ambient track controls are drawn. **Defaults to
|
|
8
8
|
* undefined, not to a component** — the same choice the overlay context makes,
|
|
9
|
-
* for the same reason: a plain default would
|
|
10
|
-
*
|
|
11
|
-
* breakpoint-split-view's overlayUtils)
|
|
9
|
+
* for the same reason: a plain default would change the look of every display
|
|
10
|
+
* that renders outside a provider (unit tests, SVG export,
|
|
11
|
+
* breakpoint-split-view's overlayUtils) while the display keeps working.
|
|
12
12
|
* Undefined means "nobody asked", so JBrowse keeps its own look.
|
|
13
13
|
*
|
|
14
14
|
* This is the corner-control half of the bring-your-own-UI story; the status
|
|
@@ -16,14 +16,14 @@ import { createContext, use } from 'react';
|
|
|
16
16
|
* context. Two contexts because two different things render them — the chrome
|
|
17
17
|
* renders the status states, the display's own component renders the controls —
|
|
18
18
|
* and folding them together would put entries in `DisplayChromeOverlays` that
|
|
19
|
-
* `DisplayChromeBase` never uses.
|
|
20
|
-
*
|
|
19
|
+
* `DisplayChromeBase` never uses. An embedder usually wants both, and
|
|
20
|
+
* `DisplayUIProvider` mounts both.
|
|
21
21
|
*/
|
|
22
22
|
const TrackControlContext = createContext(undefined);
|
|
23
23
|
export const TrackControlProvider = TrackControlContext.Provider;
|
|
24
24
|
/**
|
|
25
25
|
* The component a provider installed, or `undefined` for "nobody asked". The
|
|
26
|
-
* caller supplies the default,
|
|
26
|
+
* caller supplies the default, so this module binds none.
|
|
27
27
|
*/
|
|
28
28
|
export function useTrackControlOverride() {
|
|
29
29
|
return use(TrackControlContext);
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
* this component: JBrowse's own track container passes 100, which is positioned
|
|
33
33
|
* above `PaddingBlocks` and below `TrackLabel` at 200, and means nothing to a
|
|
34
34
|
* host whose masks sit at 2. A default would be a number that is right in one
|
|
35
|
-
* layout and
|
|
36
|
-
*
|
|
35
|
+
* layout and wrong in every other, and the failure — chrome painted under a
|
|
36
|
+
* mask — goes unseen until someone looks at the right zoom.
|
|
37
37
|
*
|
|
38
38
|
* The node takes no pointer events, so it does not eat the canvas's. Chrome that
|
|
39
39
|
* wants them takes them back on its own positioned box; it also carries
|
|
@@ -35,8 +35,8 @@ import { TrackOverlayContext } from './TrackOverlayContext.js';
|
|
|
35
35
|
* this component: JBrowse's own track container passes 100, which is positioned
|
|
36
36
|
* above `PaddingBlocks` and below `TrackLabel` at 200, and means nothing to a
|
|
37
37
|
* host whose masks sit at 2. A default would be a number that is right in one
|
|
38
|
-
* layout and
|
|
39
|
-
*
|
|
38
|
+
* layout and wrong in every other, and the failure — chrome painted under a
|
|
39
|
+
* mask — goes unseen until someone looks at the right zoom.
|
|
40
40
|
*
|
|
41
41
|
* The node takes no pointer events, so it does not eat the canvas's. Chrome that
|
|
42
42
|
* wants them takes them back on its own positioned box; it also carries
|