@jbrowse/display-ui 5.0.0-beta.13 → 5.0.0-beta.14
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 +70 -76
- package/esm/AxisCaption.d.ts +2 -2
- package/esm/AxisCaption.js +6 -4
- package/esm/AxisGutter.d.ts +2 -2
- package/esm/AxisGutter.js +5 -4
- package/esm/CrossHatches.d.ts +3 -2
- package/esm/CrossHatches.js +5 -2
- package/esm/DisplayUIProvider.d.ts +3 -2
- package/esm/DisplayUIProvider.js +4 -2
- package/esm/FloatingLegend.js +2 -2
- package/esm/FloatingText.d.ts +5 -5
- package/esm/FloatingText.js +6 -5
- package/esm/ScoreDomainCaption.d.ts +2 -2
- package/esm/ScoreDomainCaption.js +3 -2
- package/esm/ScoreRules.d.ts +3 -2
- package/esm/ScoreRules.js +5 -2
- package/esm/YScaleBar.js +1 -8
- package/esm/YScaleBarOverlay.d.ts +3 -2
- package/esm/YScaleBarOverlay.js +4 -2
- package/esm/axisPlacement.d.ts +11 -5
- package/esm/axisPlacement.js +15 -8
- package/esm/bottomRightCorner.d.ts +19 -47
- package/esm/bottomRightCorner.js +19 -47
- package/esm/chromeOverlayContext.js +10 -25
- package/esm/chromeOverlays.d.ts +73 -67
- package/esm/embed/engineBoundary.d.ts +34 -0
- package/esm/embed/engineBoundary.js +29 -0
- package/esm/embed/index.d.ts +6 -0
- package/esm/embed/index.js +18 -3
- package/esm/embed/legend.js +2 -2
- package/esm/embed/location.d.ts +2 -1
- package/esm/embed/location.js +18 -17
- package/esm/embed/notifications.d.ts +29 -0
- package/esm/embed/notifications.js +38 -0
- package/esm/embed/provider.d.ts +5 -3
- package/esm/embed/provider.js +6 -4
- package/esm/embed/regions.d.ts +3 -2
- package/esm/embed/regions.js +9 -7
- package/esm/embed/searchPicker.d.ts +24 -0
- package/esm/embed/searchPicker.js +14 -0
- package/esm/embed/toolbar.d.ts +16 -0
- package/esm/embed/toolbar.js +22 -0
- package/esm/index.d.ts +1 -1
- package/esm/isLiveModel.d.ts +10 -15
- package/esm/isLiveModel.js +10 -15
- package/esm/plainChromeOverlays.d.ts +2 -2
- package/esm/plainChromeOverlays.js +17 -44
- package/esm/tooltip/Tooltip.d.ts +3 -3
- package/esm/tooltip/Tooltip.js +4 -2
- package/esm/trackControl/plainTrackControl.d.ts +2 -1
- package/esm/trackControl/plainTrackControl.js +8 -6
- package/esm/trackOverlay/TrackOverlaySlot.d.ts +3 -3
- package/esm/trackOverlay/TrackOverlaySlot.js +4 -3
- package/esm/valueScale.d.ts +5 -5
- package/package.json +3 -3
package/esm/embed/legend.js
CHANGED
|
@@ -8,7 +8,7 @@ function gradientCss({ stops }) {
|
|
|
8
8
|
.map(({ color, opacity = 1, offset }) => `color-mix(in srgb, ${color} ${Math.round(opacity * 100)}%, transparent) ${offset * 100}%`)
|
|
9
9
|
.join(', ')})`;
|
|
10
10
|
}
|
|
11
|
-
function Row({ item }) {
|
|
11
|
+
const Row = observer(function Row({ item }) {
|
|
12
12
|
const { gradient, label, hidden } = item;
|
|
13
13
|
return gradient ? (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 4 }, children: [label, _jsx("span", { children: gradient.minLabel }), _jsx("span", { style: {
|
|
14
14
|
width: 80,
|
|
@@ -21,7 +21,7 @@ function Row({ item }) {
|
|
|
21
21
|
opacity: hidden ? 0.5 : undefined,
|
|
22
22
|
textDecoration: hidden ? 'line-through' : undefined,
|
|
23
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
|
-
}
|
|
24
|
+
});
|
|
25
25
|
function legendSpecIn(display) {
|
|
26
26
|
return display && typeof display === 'object' && 'legendSpec' in display
|
|
27
27
|
? display.legendSpec
|
package/esm/embed/location.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SearchPickerView } from './searchPicker.tsx';
|
|
1
2
|
import type React from 'react';
|
|
2
3
|
export interface LocationView {
|
|
3
4
|
coarseVisibleLocStrings: string;
|
|
@@ -12,6 +13,6 @@ export declare function useLocationBox(view: LocationView): {
|
|
|
12
13
|
go(input?: string): void;
|
|
13
14
|
};
|
|
14
15
|
export declare const LocationBox: ({ view, style, }: {
|
|
15
|
-
view: LocationView;
|
|
16
|
+
view: LocationView & SearchPickerView;
|
|
16
17
|
style?: React.CSSProperties;
|
|
17
18
|
}) => React.JSX.Element;
|
package/esm/embed/location.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
|
+
import { SearchPicker } from './searchPicker.js';
|
|
4
5
|
export function useLocationBox(view) {
|
|
5
6
|
const [draft, setDraft] = useState();
|
|
6
7
|
const [pending, setPending] = useState(false);
|
|
@@ -46,20 +47,20 @@ export function useLocationBox(view) {
|
|
|
46
47
|
}
|
|
47
48
|
export const LocationBox = observer(function LocationBox({ view, style, }) {
|
|
48
49
|
const box = useLocationBox(view);
|
|
49
|
-
return (_jsxs("form", { style: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
return (_jsxs(_Fragment, { children: [_jsxs("form", { style: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
flexWrap: 'wrap',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
gap: 4,
|
|
55
|
+
...style,
|
|
56
|
+
}, onSubmit: event => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
box.go();
|
|
59
|
+
}, children: [_jsx("input", { "aria-label": "Location", value: box.value, size: 38, style: { font: 'inherit', padding: '2px 4px' }, onChange: event => {
|
|
60
|
+
box.edit(event.target.value);
|
|
61
|
+
}, onKeyDown: event => {
|
|
62
|
+
if (event.key === 'Escape') {
|
|
63
|
+
box.reset();
|
|
64
|
+
}
|
|
65
|
+
} }), _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] }), _jsx(SearchPicker, { view: view })] }));
|
|
65
66
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface NotificationsSession {
|
|
3
|
+
snackbarMessages: readonly {
|
|
4
|
+
message: string;
|
|
5
|
+
level?: string;
|
|
6
|
+
actions?: readonly {
|
|
7
|
+
name: React.ReactNode;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}[];
|
|
10
|
+
}[];
|
|
11
|
+
popSnackbarMessage: () => unknown;
|
|
12
|
+
queueOfDialogs: readonly unknown[];
|
|
13
|
+
removeActiveDialog: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Draws the two session channels a host with its own UI otherwise drops: the
|
|
17
|
+
* latest of `session.snackbarMessages`, where a failed navigation, track launch
|
|
18
|
+
* or config lands, and a line for each dialog JBrowse queued, which only a
|
|
19
|
+
* Material host can open. A name search's picker is not one: it is
|
|
20
|
+
* `view.searchPicker`, drawn by `SearchPicker`.
|
|
21
|
+
*
|
|
22
|
+
* `EmbedProvider` mounts one below its children. Pass it
|
|
23
|
+
* `notifications={false}` to place this elsewhere or to draw the two queues
|
|
24
|
+
* yourself.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Notifications: ({ session, style, }: {
|
|
27
|
+
session: NotificationsSession;
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
/**
|
|
5
|
+
* Draws the two session channels a host with its own UI otherwise drops: the
|
|
6
|
+
* latest of `session.snackbarMessages`, where a failed navigation, track launch
|
|
7
|
+
* or config lands, and a line for each dialog JBrowse queued, which only a
|
|
8
|
+
* Material host can open. A name search's picker is not one: it is
|
|
9
|
+
* `view.searchPicker`, drawn by `SearchPicker`.
|
|
10
|
+
*
|
|
11
|
+
* `EmbedProvider` mounts one below its children. Pass it
|
|
12
|
+
* `notifications={false}` to place this elsewhere or to draw the two queues
|
|
13
|
+
* yourself.
|
|
14
|
+
*/
|
|
15
|
+
export const Notifications = observer(function Notifications({ session, style, }) {
|
|
16
|
+
const palette = usePalette();
|
|
17
|
+
const latest = session.snackbarMessages.at(-1);
|
|
18
|
+
const queued = session.queueOfDialogs.length;
|
|
19
|
+
const row = {
|
|
20
|
+
display: 'flex',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: 8,
|
|
23
|
+
marginTop: 8,
|
|
24
|
+
padding: '4px 8px',
|
|
25
|
+
fontSize: '0.8rem',
|
|
26
|
+
colorScheme: palette.mode,
|
|
27
|
+
color: palette.text.primary,
|
|
28
|
+
background: `color-mix(in srgb, ${palette.text.primary} 8%, ${palette.background.paper})`,
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs(_Fragment, { children: [latest ? (_jsxs("div", { role: latest.level === 'error' ? 'alert' : 'status', "data-testid": "embed-notification", style: { ...row, ...style }, children: [_jsx("span", { style: { flex: 1, overflowWrap: 'anywhere' }, children: latest.message }), latest.actions
|
|
31
|
+
// 'report' opens the stack-trace dialog, which is Material
|
|
32
|
+
?.filter(action => action.name !== 'report')
|
|
33
|
+
.map((action, i) => (_jsx("button", { type: "button", onClick: action.onClick, children: action.name }, i))), _jsx("button", { type: "button", "aria-label": "Dismiss notification", onClick: () => {
|
|
34
|
+
session.popSnackbarMessage();
|
|
35
|
+
}, children: "\u2715" })] })) : null, queued ? (_jsxs("div", { role: "status", "data-testid": "queued-dialog-notice", style: { ...row, ...style }, children: [_jsxs("span", { style: { flex: 1 }, children: ["JBrowse queued ", queued, " dialog", queued > 1 ? 's' : '', ", and this page draws none."] }), _jsx("button", { type: "button", onClick: () => {
|
|
36
|
+
session.removeActiveDialog();
|
|
37
|
+
}, children: "Dismiss" })] })) : null] }));
|
|
38
|
+
});
|
package/esm/embed/provider.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { DisplayChromeOverlays } from '../chromeOverlays.ts';
|
|
2
2
|
import type { TrackControlComponent } from '../trackControl/types.ts';
|
|
3
|
+
import type { NotificationsSession } from './notifications.tsx';
|
|
3
4
|
import type { ThemeModeSession } from '@jbrowse/core/ui/PaletteContext';
|
|
4
5
|
import type React from 'react';
|
|
5
|
-
export declare
|
|
6
|
-
session: ThemeModeSession;
|
|
6
|
+
export declare const EmbedProvider: ({ session, mode, overlays, trackControl, notifications, children, }: {
|
|
7
|
+
session: ThemeModeSession & NotificationsSession;
|
|
7
8
|
mode?: 'light' | 'dark';
|
|
8
9
|
overlays?: Partial<DisplayChromeOverlays>;
|
|
9
10
|
trackControl?: TrackControlComponent;
|
|
11
|
+
notifications?: boolean;
|
|
10
12
|
children: React.ReactNode;
|
|
11
|
-
})
|
|
13
|
+
}) => React.JSX.Element;
|
package/esm/embed/provider.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
3
4
|
import DisplayUIProvider from '../DisplayUIProvider.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
5
|
+
import { Notifications } from './notifications.js';
|
|
6
|
+
export const EmbedProvider = observer(function EmbedProvider({ session, mode, overlays, trackControl, notifications = true, children, }) {
|
|
7
|
+
return (_jsx(SessionPaletteProvider, { session: session, mode: mode, children: _jsxs(DisplayUIProvider, { overlays: overlays, trackControl: trackControl, children: [children, notifications ? _jsx(Notifications, { session: session }) : null] }) }));
|
|
8
|
+
});
|
package/esm/embed/regions.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
1
2
|
import type React from 'react';
|
|
2
3
|
export interface RegionSeamsView {
|
|
3
4
|
staticBlocksTranslateX: number;
|
|
@@ -28,7 +29,7 @@ export interface ScalebarView {
|
|
|
28
29
|
caption?: string;
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
export interface HighlightsView {
|
|
32
|
+
export interface HighlightsView extends IStateTreeNode {
|
|
32
33
|
highlights: readonly {
|
|
33
34
|
assemblyName?: string;
|
|
34
35
|
refName: string;
|
|
@@ -47,7 +48,7 @@ export declare const RegionSeams: ({ view, }: {
|
|
|
47
48
|
}) => React.JSX.Element;
|
|
48
49
|
export declare const Highlights: ({ view, }: {
|
|
49
50
|
view: HighlightsView;
|
|
50
|
-
}) => (React.JSX.Element | null)[];
|
|
51
|
+
}) => (React.JSX.Element | null)[] | null;
|
|
51
52
|
export declare const Scalebar: ({ view, style, ...props }: {
|
|
52
53
|
view: ScalebarView;
|
|
53
54
|
} & React.ComponentProps<'div'>) => React.JSX.Element;
|
package/esm/embed/regions.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
3
3
|
import { highlightKey } from '@jbrowse/core/util/highlights';
|
|
4
|
+
import { getSession } from '@jbrowse/core/util/mstUtils';
|
|
4
5
|
import { observer } from 'mobx-react';
|
|
5
|
-
const
|
|
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
|
-
};
|
|
6
|
+
const spanInk = { seam: 45, boundary: 12, elided: 30 };
|
|
10
7
|
export const RegionSeams = observer(function RegionSeams({ view, }) {
|
|
8
|
+
const { text, background } = usePalette();
|
|
11
9
|
return (_jsx("div", { "aria-hidden": true, "data-region-seams": true, style: {
|
|
12
10
|
position: 'absolute',
|
|
13
11
|
top: 0,
|
|
@@ -22,11 +20,15 @@ export const RegionSeams = observer(function RegionSeams({ view, }) {
|
|
|
22
20
|
bottom: 0,
|
|
23
21
|
left: x,
|
|
24
22
|
width,
|
|
25
|
-
background:
|
|
23
|
+
background: `color-mix(in srgb, ${text.primary} ${spanInk[kind]}%, ${background.default})`,
|
|
26
24
|
} }, key))) }));
|
|
27
25
|
});
|
|
28
26
|
export const Highlights = observer(function Highlights({ view, }) {
|
|
29
27
|
const palette = usePalette();
|
|
28
|
+
const { highlightsVisible, highlightLabelsVisible } = getSession(view);
|
|
29
|
+
if (!highlightsVisible) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
30
32
|
return view.highlights.map((highlight, i) => {
|
|
31
33
|
const coords = view.getHighlightCoords(highlight);
|
|
32
34
|
return coords ? (_jsx("div", { "data-testid": "highlight-band", style: {
|
|
@@ -40,7 +42,7 @@ export const Highlights = observer(function Highlights({ view, }) {
|
|
|
40
42
|
pointerEvents: 'none',
|
|
41
43
|
background: highlight.color ??
|
|
42
44
|
`color-mix(in srgb, ${palette.highlight.main} 20%, transparent)`,
|
|
43
|
-
}, children: highlight.label ? (_jsx("span", { style: {
|
|
45
|
+
}, children: highlight.label && highlightLabelsVisible ? (_jsx("span", { style: {
|
|
44
46
|
position: 'absolute',
|
|
45
47
|
top: 0,
|
|
46
48
|
left: 3,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface SearchPickerView {
|
|
3
|
+
searchPicker?: {
|
|
4
|
+
query: string;
|
|
5
|
+
rows: readonly {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
location: string;
|
|
9
|
+
trackName: string;
|
|
10
|
+
}[];
|
|
11
|
+
pick: (id: string) => Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
closeSearchPicker: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Lists the places a name search points at when it matches several and none
|
|
17
|
+
* exactly, which `view.navToLocString` leaves to the host instead of
|
|
18
|
+
* navigating. Draws nothing until a search sets `view.searchPicker`. A
|
|
19
|
+
* `LocationBox` mounts one under itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SearchPicker: ({ view, style, }: {
|
|
22
|
+
view: SearchPickerView;
|
|
23
|
+
style?: React.CSSProperties;
|
|
24
|
+
}) => React.JSX.Element | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
/**
|
|
4
|
+
* Lists the places a name search points at when it matches several and none
|
|
5
|
+
* exactly, which `view.navToLocString` leaves to the host instead of
|
|
6
|
+
* navigating. Draws nothing until a search sets `view.searchPicker`. A
|
|
7
|
+
* `LocationBox` mounts one under itself.
|
|
8
|
+
*/
|
|
9
|
+
export const SearchPicker = observer(function SearchPicker({ view, style, }) {
|
|
10
|
+
const { searchPicker } = view;
|
|
11
|
+
return searchPicker ? (_jsxs("div", { role: "dialog", "aria-label": "Search results", "data-testid": "search-picker", style: { fontSize: '0.8rem', paddingBottom: 8, ...style }, children: [_jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsxs("strong", { style: { flex: 1 }, children: ["\u201C", searchPicker.query, "\u201D matches ", searchPicker.rows.length, " places"] }), _jsx("button", { type: "button", "aria-label": "Close search results", onClick: view.closeSearchPicker, children: "\u2715" })] }), _jsx("ul", { style: { listStyle: 'none', margin: 0, padding: 0 }, children: searchPicker.rows.map(row => (_jsx("li", { children: _jsxs("button", { type: "button", style: { width: '100%', textAlign: 'left' }, onClick: () => {
|
|
12
|
+
void searchPicker.pick(row.id);
|
|
13
|
+
}, children: [_jsx("strong", { children: row.label }), " ", row.location, " ", row.trackName] }) }, row.id))) })] })) : null;
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
export declare const Toolbar: ({ style, children, }: {
|
|
4
|
+
style?: React.CSSProperties;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
}) => React.JSX.Element;
|
|
7
|
+
export interface NavView extends IStateTreeNode {
|
|
8
|
+
navToLocString: (input: string) => Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare const NavButton: ({ view, loc, onClick, style, children, }: {
|
|
11
|
+
view: NavView;
|
|
12
|
+
loc: string;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getSession } from '@jbrowse/core/util/mstUtils';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
export const Toolbar = observer(function Toolbar({ style, children, }) {
|
|
5
|
+
return (_jsx("div", { style: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexWrap: 'wrap',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
gap: 12,
|
|
10
|
+
paddingBottom: 8,
|
|
11
|
+
fontSize: '0.85rem',
|
|
12
|
+
...style,
|
|
13
|
+
}, children: children }));
|
|
14
|
+
});
|
|
15
|
+
export const NavButton = observer(function NavButton({ view, loc, onClick, style, children, }) {
|
|
16
|
+
return (_jsx("button", { type: "button", style: style, onClick: () => {
|
|
17
|
+
onClick?.();
|
|
18
|
+
view.navToLocString(loc).catch((e) => {
|
|
19
|
+
getSession(view).notifyError(`${e}`, e);
|
|
20
|
+
});
|
|
21
|
+
}, children: children ?? loc }));
|
|
22
|
+
});
|
package/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as DisplayUIProvider, resolveOverlays, } from './DisplayUIProvider.tsx';
|
|
2
2
|
export { DisplayChromeOverlayProvider, useChromeOverlayOverride, } from './chromeOverlayContext.ts';
|
|
3
3
|
export { default as plainChromeOverlays } from './plainChromeOverlays.tsx';
|
|
4
|
-
export type {
|
|
4
|
+
export type { BackgroundProgressProps, DisplayChromeOverlays, ErrorBarProps, LoadingProps, RenderErrorProps, TooLargeProps, } from './chromeOverlays.ts';
|
|
5
5
|
export { TrackControlProvider, useTrackControlOverride, } from './trackControl/trackControlContext.ts';
|
|
6
6
|
export { default as plainTrackControl } from './trackControl/plainTrackControl.tsx';
|
|
7
7
|
export { useTrackControlMenu } from './trackControl/useTrackControlMenu.tsx';
|
package/esm/isLiveModel.d.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* #api
|
|
3
|
-
* Whether
|
|
3
|
+
* Whether a display model can still be called into.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The chrome's overlay slots guard every button with it: the terminal states
|
|
6
|
+
* unmount the canvas, so a click landing after the track was closed would
|
|
7
|
+
* otherwise run an action on a destroyed node.
|
|
8
8
|
*
|
|
9
|
-
* **`isAlive` alone is wrong here, and it throws rather than answering.**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* button looking live and doing nothing, which is the exact state
|
|
16
|
-
* `DisplayChromeOverlays.TooLarge` documents itself as existing to prevent.
|
|
17
|
-
*
|
|
18
|
-
* A plain object is never destroyed, so it is always callable. The liveness
|
|
19
|
-
* question only exists for an MST node.
|
|
9
|
+
* **`isAlive` alone is wrong here, and it throws rather than answering.** A
|
|
10
|
+
* host writing their own display over `DisplayChromeBase` may hand the chrome a
|
|
11
|
+
* plain object, and `isAlive` runs `assertIsStateTreeNode`, which throws inside
|
|
12
|
+
* the click handler, where React logs it and moves on: a Force load button that
|
|
13
|
+
* looks live and does nothing. A plain object is never destroyed, so it is
|
|
14
|
+
* always callable.
|
|
20
15
|
*/
|
|
21
16
|
export declare function isLiveModel(model: unknown): boolean;
|
package/esm/isLiveModel.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import { isAlive, isStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
/**
|
|
3
3
|
* #api
|
|
4
|
-
* Whether
|
|
4
|
+
* Whether a display model can still be called into.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The chrome's overlay slots guard every button with it: the terminal states
|
|
7
|
+
* unmount the canvas, so a click landing after the track was closed would
|
|
8
|
+
* otherwise run an action on a destroyed node.
|
|
9
9
|
*
|
|
10
|
-
* **`isAlive` alone is wrong here, and it throws rather than answering.**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* button looking live and doing nothing, which is the exact state
|
|
17
|
-
* `DisplayChromeOverlays.TooLarge` documents itself as existing to prevent.
|
|
18
|
-
*
|
|
19
|
-
* A plain object is never destroyed, so it is always callable. The liveness
|
|
20
|
-
* question only exists for an MST node.
|
|
10
|
+
* **`isAlive` alone is wrong here, and it throws rather than answering.** A
|
|
11
|
+
* host writing their own display over `DisplayChromeBase` may hand the chrome a
|
|
12
|
+
* plain object, and `isAlive` runs `assertIsStateTreeNode`, which throws inside
|
|
13
|
+
* the click handler, where React logs it and moves on: a Force load button that
|
|
14
|
+
* looks live and does nothing. A plain object is never destroyed, so it is
|
|
15
|
+
* always callable.
|
|
21
16
|
*/
|
|
22
17
|
export function isLiveModel(model) {
|
|
23
18
|
return !isStateTreeNode(model) || isAlive(model);
|
|
@@ -7,8 +7,8 @@ import type { DisplayChromeOverlays } from './chromeOverlays.ts';
|
|
|
7
7
|
*
|
|
8
8
|
* `DisplayUIProvider` installs this by default, so mount that rather than
|
|
9
9
|
* naming this — reach for it directly only to wrap a state or to build a
|
|
10
|
-
* context value by hand.
|
|
11
|
-
*
|
|
10
|
+
* context value by hand. Colors come from `currentColor` and the CSS system
|
|
11
|
+
* colors, so the host's own cascade drives them in both light and dark.
|
|
12
12
|
*
|
|
13
13
|
* The `data-testid` values it renders are a contract four of JBrowse's test
|
|
14
14
|
* systems key on, so a replacement set that keeps them can be driven by those
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { isRegionTooLargeError } from '@jbrowse/core/rpc/byteBudget';
|
|
3
2
|
// the subpath, not the `@jbrowse/core/ui` barrel: this file is the MUI-free
|
|
4
3
|
// chrome, and gpuFallback.ts is React-free for exactly that reason
|
|
5
4
|
import { GPU_FALLBACK_LABEL, GPU_FALLBACK_TOOLTIP, disableGpuRendering, shouldOfferGpuFallback, } from '@jbrowse/core/ui/gpuFallback';
|
|
6
5
|
import { progressLabel } from '@jbrowse/core/util/progress';
|
|
7
6
|
import { observer } from 'mobx-react';
|
|
8
|
-
import { isLiveModel } from './isLiveModel.js';
|
|
9
|
-
import { tooLargeBannerText } from './tooLargeBannerText.js';
|
|
10
7
|
import Tooltip from './tooltip/Tooltip.js';
|
|
11
8
|
// A dependency-free `DisplayChromeOverlays` for embedders who don't want MUI:
|
|
12
9
|
// no theme provider to mount, no emotion injected into the host page, and
|
|
@@ -38,9 +35,9 @@ const overlayBox = {
|
|
|
38
35
|
pointerEvents: 'none',
|
|
39
36
|
zIndex: 1,
|
|
40
37
|
};
|
|
41
|
-
// Nearly opaque on purpose. These sit over a canvas whose
|
|
38
|
+
// Nearly opaque on purpose. These sit over a canvas whose colors are not known
|
|
42
39
|
// here, and a translucent chip over dense data was measurably hard to read.
|
|
43
|
-
// `Canvas`/`CanvasText` are the CSS system
|
|
40
|
+
// `Canvas`/`CanvasText` are the CSS system colors, so this tracks the host's
|
|
44
41
|
// light/dark mode without needing a theme object.
|
|
45
42
|
const chip = {
|
|
46
43
|
pointerEvents: 'auto',
|
|
@@ -99,7 +96,7 @@ function errorText(error) {
|
|
|
99
96
|
// toolkit-free, but it paints the palette's `accent`; every rule in this
|
|
100
97
|
// file reads the host's own cascade instead, so the bar is drawn here rather
|
|
101
98
|
// than imported.
|
|
102
|
-
function ProgressBar({ fraction }) {
|
|
99
|
+
const ProgressBar = observer(function ProgressBar({ fraction, }) {
|
|
103
100
|
const filled = Math.min(1, Math.max(0, fraction));
|
|
104
101
|
return (_jsx("div", { role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(filled * 100), style: {
|
|
105
102
|
overflow: 'hidden',
|
|
@@ -117,57 +114,33 @@ function ProgressBar({ fraction }) {
|
|
|
117
114
|
transform: `scaleX(${filled})`,
|
|
118
115
|
transition: 'transform 200ms linear',
|
|
119
116
|
} }) }));
|
|
120
|
-
}
|
|
117
|
+
});
|
|
121
118
|
const PlainRenderError = observer(function PlainRenderError({ error, onRetry, height, }) {
|
|
122
119
|
return (_jsxs("div", { style: banner(height), role: "alert", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), _jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: onRetry, children: "Retry" }), shouldOfferGpuFallback(error) ? (_jsx(Tooltip, { title: GPU_FALLBACK_TOOLTIP, children: _jsx("button", { type: "button", style: button, "data-testid": "use_canvas2d_button", onClick: () => {
|
|
123
120
|
disableGpuRendering();
|
|
124
121
|
onRetry();
|
|
125
122
|
}, children: GPU_FALLBACK_LABEL }) })) : null] }));
|
|
126
123
|
});
|
|
127
|
-
const PlainTooLarge = observer(function PlainTooLarge({
|
|
128
|
-
return (_jsxs("div", { style: banner(), role: "status", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children:
|
|
129
|
-
zoomCanRelease: model.zoomCanReleaseGate,
|
|
130
|
-
}) }), _jsx("button", { type: "button", style: button, onClick: () => {
|
|
131
|
-
// the banner unmounts the canvas, so a click landing after the track
|
|
132
|
-
// was closed would otherwise call an action on a dead node. Not bare
|
|
133
|
-
// `isAlive` — this model may be the plain object the contract invites,
|
|
134
|
-
// which that throws on. See isLiveModel.ts.
|
|
135
|
-
if (isLiveModel(model)) {
|
|
136
|
-
model.forceLoad();
|
|
137
|
-
}
|
|
138
|
-
}, children: "Force load" })] }));
|
|
124
|
+
const PlainTooLarge = observer(function PlainTooLarge({ message, onForceLoad, }) {
|
|
125
|
+
return (_jsxs("div", { style: banner(), role: "status", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: message }), _jsx("button", { type: "button", style: button, onClick: onForceLoad, children: "Force load" })] }));
|
|
139
126
|
});
|
|
140
|
-
const PlainErrorBar = observer(function PlainErrorBar({
|
|
141
|
-
|
|
142
|
-
if (!visible || !error) {
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
return isRegionTooLargeError(error) ? (_jsx("div", { style: overlayBox, role: "status", children: _jsx("div", { style: chip, children: _jsx("span", { style: { wordBreak: 'break-word' }, children: error.message }) }) })) : (_jsx("div", { style: overlayBox, role: "alert", children: _jsxs("div", { style: chip, children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), _jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: () => {
|
|
146
|
-
model.reload();
|
|
147
|
-
}, children: "Retry" })] }) }));
|
|
127
|
+
const PlainErrorBar = observer(function PlainErrorBar({ error, onRetry, }) {
|
|
128
|
+
return (_jsx("div", { style: overlayBox, role: onRetry ? 'alert' : 'status', children: _jsxs("div", { style: chip, children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), onRetry ? (_jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: onRetry, children: "Retry" })) : null] }) }));
|
|
148
129
|
});
|
|
149
130
|
// No anti-flash delay here, unlike the MUI set: that timing is a product
|
|
150
131
|
// judgment JBrowse made for its own UI, and an embedder pacing their own
|
|
151
132
|
// loading affordances shouldn't inherit it invisibly. Wrap this if you want it.
|
|
152
|
-
const PlainLoading = observer(function PlainLoading({
|
|
133
|
+
const PlainLoading = observer(function PlainLoading({ visible, message, progress, canceled, onCancel, onRetry, }) {
|
|
153
134
|
if (!visible) {
|
|
154
135
|
return null;
|
|
155
136
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}, children: "Retry" })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: statusProgress === undefined
|
|
160
|
-
? `${statusMessage || 'Loading'}...`
|
|
161
|
-
: progressLabel(statusMessage || 'Loading', statusProgress) }), statusProgress === undefined ? null : (_jsx(ProgressBar, { fraction: statusProgress })), model.cancelFetchByUser ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-cancel", onClick: () => {
|
|
162
|
-
model.cancelFetchByUser?.();
|
|
163
|
-
}, children: "Cancel" })) : null] })) }) }));
|
|
137
|
+
return (_jsx("div", { style: overlayBox, "data-testid": canceled ? 'loading-overlay-canceled' : 'loading-overlay', children: _jsx("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", children: canceled ? (_jsxs(_Fragment, { children: [_jsx("span", { children: "Loading canceled" }), onRetry ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-retry", onClick: onRetry, children: "Retry" })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: progress === undefined
|
|
138
|
+
? `${message || 'Loading'}...`
|
|
139
|
+
: progressLabel(message || 'Loading', progress) }), progress === undefined ? null : (_jsx(ProgressBar, { fraction: progress })), onCancel ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-cancel", onClick: onCancel, children: "Cancel" })) : null] })) }) }));
|
|
164
140
|
});
|
|
165
|
-
const PlainBackgroundProgress = observer(function PlainBackgroundProgress({
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
// row, so this is laid out there. Claiming `inset: 0` here would collapse
|
|
169
|
-
// against that box, and pinning to the corner would sit under the row.
|
|
170
|
-
return visible && model.statusMessage ? (_jsxs("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", "data-testid": "progress-chip", children: [_jsx("span", { children: progressLabel(model.statusMessage, model.statusProgress) }), model.statusProgress === undefined ? null : (_jsx(ProgressBar, { fraction: model.statusProgress }))] })) : null;
|
|
141
|
+
const PlainBackgroundProgress = observer(function PlainBackgroundProgress({ message, progress, }) {
|
|
142
|
+
// in-flow: the chrome anchors the corner it shares with the control row
|
|
143
|
+
return (_jsxs("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", "data-testid": "progress-chip", children: [_jsx("span", { children: progressLabel(message || 'Loading', progress) }), progress === undefined ? null : _jsx(ProgressBar, { fraction: progress })] }));
|
|
171
144
|
});
|
|
172
145
|
/**
|
|
173
146
|
* #api
|
|
@@ -177,8 +150,8 @@ const PlainBackgroundProgress = observer(function PlainBackgroundProgress({ mode
|
|
|
177
150
|
*
|
|
178
151
|
* `DisplayUIProvider` installs this by default, so mount that rather than
|
|
179
152
|
* naming this — reach for it directly only to wrap a state or to build a
|
|
180
|
-
* context value by hand.
|
|
181
|
-
*
|
|
153
|
+
* context value by hand. Colors come from `currentColor` and the CSS system
|
|
154
|
+
* colors, so the host's own cascade drives them in both light and dark.
|
|
182
155
|
*
|
|
183
156
|
* The `data-testid` values it renders are a contract four of JBrowse's test
|
|
184
157
|
* systems key on, so a replacement set that keeps them can be driven by those
|
package/esm/tooltip/Tooltip.d.ts
CHANGED
|
@@ -41,12 +41,12 @@ interface TriggerProps {
|
|
|
41
41
|
* `aria-describedby` and never `aria-label` — see {@link useTooltip}, which is
|
|
42
42
|
* this without the cloning, for a host writing its own markup.
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
declare const Tooltip: ({ title, placement, children, }: {
|
|
45
45
|
/** What the bubble says. Nothing renders while this is empty. */
|
|
46
46
|
title: ReactNode;
|
|
47
47
|
/** Preferred side. `flip()` overrides it near a viewport edge. */
|
|
48
48
|
placement?: TooltipPlacement;
|
|
49
49
|
/** Exactly one element, cloned rather than wrapped. */
|
|
50
50
|
children: ReactElement<TriggerProps>;
|
|
51
|
-
})
|
|
52
|
-
export
|
|
51
|
+
}) => import("react").JSX.Element;
|
|
52
|
+
export default Tooltip;
|
package/esm/tooltip/Tooltip.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cloneElement } from 'react';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
3
4
|
import { useTooltip } from './useTooltip.js';
|
|
4
5
|
function compose(theirs, ours) {
|
|
5
6
|
return (event) => {
|
|
@@ -40,7 +41,7 @@ function compose(theirs, ours) {
|
|
|
40
41
|
* `aria-describedby` and never `aria-label` — see {@link useTooltip}, which is
|
|
41
42
|
* this without the cloning, for a host writing its own markup.
|
|
42
43
|
*/
|
|
43
|
-
|
|
44
|
+
const Tooltip = observer(function Tooltip({ title, placement, children, }) {
|
|
44
45
|
const { triggerProps, tooltip } = useTooltip(title, { placement });
|
|
45
46
|
const props = children.props;
|
|
46
47
|
// eslint-disable-next-line @eslint-react/no-clone-element -- wrapping instead would move the absolutely-positioned and flex-row controls this exists for
|
|
@@ -64,4 +65,5 @@ export default function Tooltip({ title, placement, children, }) {
|
|
|
64
65
|
onBlur: compose(props.onBlur, triggerProps.onBlur),
|
|
65
66
|
});
|
|
66
67
|
return (_jsxs(_Fragment, { children: [trigger, tooltip] }));
|
|
67
|
-
}
|
|
68
|
+
});
|
|
69
|
+
export default Tooltip;
|
|
@@ -14,4 +14,5 @@ import type { TrackControlProps } from './types.ts';
|
|
|
14
14
|
* `plainChromeOverlays` — the two are a pair, and both are things you hand to a
|
|
15
15
|
* provider rather than render yourself.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
declare const PlainTrackControl: ({ icon, tooltip, label, options, onClick, onMenuClose, onDelete, warning, }: TrackControlProps) => import("react").JSX.Element;
|
|
18
|
+
export default PlainTrackControl;
|