@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.11
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/esm/FeatureTrack/configSchema.d.ts +142 -50
- package/esm/LaunchLinearGenomeView/index.js +2 -13
- package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
- package/esm/LinearGenomeView/afterAttach.js +75 -62
- package/esm/LinearGenomeView/components/CenterLine.js +5 -3
- package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +11 -1
- package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
- package/esm/LinearGenomeView/components/HeaderPanControls.js +2 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
- package/esm/LinearGenomeView/components/Highlight.js +7 -7
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +9 -1
- package/esm/LinearGenomeView/components/HighlightBand.js +15 -3
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
- package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
- package/esm/LinearGenomeView/components/ImportForm.js +2 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
- package/esm/LinearGenomeView/components/MiniControls.js +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
- package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
- package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
- package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
- package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
- package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
- package/esm/LinearGenomeView/components/Rubberband.js +3 -2
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
- package/esm/LinearGenomeView/components/Scalebar.js +8 -5
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
- package/esm/LinearGenomeView/components/SearchBox.js +8 -7
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
- package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
- package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
- package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
- package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
- package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
- package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
- package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
- package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
- package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
- package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
- package/esm/LinearGenomeView/components/searchModes.js +1 -1
- package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
- package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
- package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
- package/esm/LinearGenomeView/components/util.d.ts +4 -0
- package/esm/LinearGenomeView/components/util.js +4 -2
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +3 -1
- package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
- package/esm/LinearGenomeView/containingLgv.js +23 -0
- package/esm/LinearGenomeView/headerFit.d.ts +4 -2
- package/esm/LinearGenomeView/headerFit.js +20 -7
- package/esm/LinearGenomeView/index.d.ts +1 -5
- package/esm/LinearGenomeView/index.js +10 -7
- package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
- package/esm/LinearGenomeView/keyboardHandler.js +15 -1
- package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
- package/esm/LinearGenomeView/launchKeys.js +27 -0
- package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
- package/esm/LinearGenomeView/lazyDialogs.js +1 -0
- package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
- package/esm/LinearGenomeView/linkedViewSync.js +27 -43
- package/esm/LinearGenomeView/menuItems.d.ts +16 -18
- package/esm/LinearGenomeView/menuItems.js +242 -151
- package/esm/LinearGenomeView/model.d.ts +2027 -200
- package/esm/LinearGenomeView/model.js +560 -395
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
- package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
- package/esm/LinearGenomeView/stickyChrome.js +5 -9
- package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
- package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
- package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
- package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +2 -1
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +29 -16
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +1 -0
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +20 -8
- package/esm/LinearGenomeView/svgcomponents/util.js +50 -29
- package/esm/LinearGenomeView/types.d.ts +8 -11
- package/esm/LinearGenomeView/util.d.ts +60 -45
- package/esm/LinearGenomeView/util.js +131 -160
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
- package/esm/MultiLevelRubberband/index.d.ts +2 -0
- package/esm/MultiLevelRubberband/index.js +2 -0
- package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
- package/esm/MultiLevelRubberband/menuItems.js +52 -0
- package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
- package/esm/MultiLevelRubberband/rowLabel.js +27 -0
- package/esm/MultiLevelRubberband/types.d.ts +1 -0
- package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
- package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
- package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -86
- package/esm/displayKitTests/perRegionTestEnv.js +30 -29
- package/esm/index.d.ts +15 -85
- package/esm/index.js +13 -36
- package/esm/searchUtils.d.ts +18 -9
- package/esm/searchUtils.js +85 -69
- package/esm/shared/RubberbandSpan.js +2 -1
- package/esm/shared/coordLabels.d.ts +7 -0
- package/esm/shared/coordLabels.js +15 -1
- package/esm/shared/useWindowDrag.d.ts +18 -0
- package/esm/shared/useWindowDrag.js +45 -0
- package/package.json +10 -10
- package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
- package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
- package/esm/LinearGenomeView/initKeys.d.ts +0 -13
- package/esm/LinearGenomeView/initKeys.js +0 -104
- package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
- package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
package/esm/searchUtils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RefSequenceResult } from '@jbrowse/core/TextSearch/BaseResults';
|
|
2
|
-
import {
|
|
2
|
+
import { groupByPlace } from '@jbrowse/core/TextSearch/places';
|
|
3
|
+
import { dedupe, getEnv, getNotificationSink, getSession, matchRefNames, MAX_GLOB_REGIONS, UnknownRefNameError, } from '@jbrowse/core/util';
|
|
3
4
|
import { isAlive } from '@jbrowse/mobx-state-tree';
|
|
4
5
|
import { parseLocStrings } from './LinearGenomeView/util.js';
|
|
5
6
|
// Budget for refName suggestions from a query. Deliberately far below the
|
|
@@ -36,37 +37,6 @@ export function isOpenInView(result, model) {
|
|
|
36
37
|
const trackId = result.getTrackId();
|
|
37
38
|
return trackId !== undefined && !!model.getTrack(trackId);
|
|
38
39
|
}
|
|
39
|
-
// One spelling of a locstring, so two indexes that answer `chr1:1-100` and
|
|
40
|
-
// `1:1..100` are recognised as one place. An unparseable string is compared
|
|
41
|
-
// raw, which can only ever split a group that would otherwise have merged —
|
|
42
|
-
// the safe direction, since the picker is what an unprovable match falls back
|
|
43
|
-
// to.
|
|
44
|
-
function canonicalLocString(locString, assembly) {
|
|
45
|
-
try {
|
|
46
|
-
const loc = parseLocString(locString, refName => assembly.isValidRefName(refName));
|
|
47
|
-
return assembleLocString({
|
|
48
|
-
...loc,
|
|
49
|
-
refName: assembly.getCanonicalRefName2(loc.refName),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
console.warn('failed to parse location string', locString, e);
|
|
54
|
-
return locString;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// What a hit means as a destination: the name it shows and the place it goes.
|
|
58
|
-
// A hit with no location has no destination and can never join a group.
|
|
59
|
-
function destination(result, assembly) {
|
|
60
|
-
const locString = result.getLocation();
|
|
61
|
-
return locString
|
|
62
|
-
? [
|
|
63
|
-
result.getDisplayString(),
|
|
64
|
-
assembly?.initialized
|
|
65
|
-
? canonicalLocString(locString, assembly)
|
|
66
|
-
: locString,
|
|
67
|
-
].join('\u0000')
|
|
68
|
-
: undefined;
|
|
69
|
-
}
|
|
70
40
|
// Which hit of an agreeing group to travel through, best rung first: a track
|
|
71
41
|
// the view already has on screen, then one the session could open if asked,
|
|
72
42
|
// then whatever the ranking put first. The middle rung is not hypothetical —
|
|
@@ -86,40 +56,48 @@ function trackRank(result, model, session) {
|
|
|
86
56
|
return session.getTrackById(trackId) ? 1 : 3;
|
|
87
57
|
}
|
|
88
58
|
}
|
|
89
|
-
// The
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
59
|
+
// The pick is a stable minimum, so a linear scan rather than a sort: keeping
|
|
60
|
+
// the first strictly-lowest rung leaves a group with nothing to choose between
|
|
61
|
+
// in the order the ranking handed it. A comparator would ask `trackRank`, and
|
|
62
|
+
// through it `session.getTrackById`, O(n log n) times.
|
|
63
|
+
function bestRanked(results, model, session) {
|
|
64
|
+
let best = results[0];
|
|
65
|
+
let bestRank = trackRank(best, model, session);
|
|
66
|
+
for (let i = 1; i < results.length; i++) {
|
|
67
|
+
const rank = trackRank(results[i], model, session);
|
|
68
|
+
if (rank < bestRank) {
|
|
69
|
+
best = results[i];
|
|
70
|
+
bestRank = rank;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return best;
|
|
74
|
+
}
|
|
75
|
+
// One hit per place, in the order the ranking first reached each. Hits that
|
|
76
|
+
// name one feature at one place are several indexes having found it, not a
|
|
77
|
+
// choice to put to the user: a handful of gene tracks turned every gene search
|
|
78
|
+
// into a picker whose only varying column was Track (issues #4302 and #5068),
|
|
79
|
+
// and two whose gene models end a few bases apart still did.
|
|
80
|
+
export function distinctDestinations({ results, model, session, assembly, }) {
|
|
81
|
+
return groupByPlace(results, assembly).map(group => bestRanked(group, model, session));
|
|
105
82
|
}
|
|
106
83
|
// Every multi-hit result set reaches the picker through here, so the two
|
|
107
|
-
// search surfaces cannot disagree about when one is worth raising
|
|
108
|
-
// whether the view moved.
|
|
109
|
-
export async function showSearchResults({ results, query, model, assemblyName, grow, }) {
|
|
84
|
+
// search surfaces cannot disagree about when one is worth raising: only when
|
|
85
|
+
// the hits name more than one place. Returns whether the view moved.
|
|
86
|
+
export async function showSearchResults({ results, query, model, assemblyName, grow, showHitTrack, }) {
|
|
110
87
|
const session = getSession(model);
|
|
111
|
-
const
|
|
88
|
+
const places = distinctDestinations({
|
|
112
89
|
results,
|
|
113
90
|
model,
|
|
114
91
|
session,
|
|
115
92
|
assembly: session.assemblyManager.get(assemblyName),
|
|
116
93
|
});
|
|
117
|
-
|
|
118
|
-
|
|
94
|
+
const land = (option) => navToOption({ option, model, assemblyName, grow, showHitTrack });
|
|
95
|
+
if (places.length === 1) {
|
|
96
|
+
await land(places[0]);
|
|
119
97
|
return true;
|
|
120
98
|
}
|
|
121
99
|
else {
|
|
122
|
-
model.setSearchResults(
|
|
100
|
+
model.setSearchResults(places, query, assemblyName, land);
|
|
123
101
|
return false;
|
|
124
102
|
}
|
|
125
103
|
}
|
|
@@ -127,17 +105,33 @@ export async function showSearchResults({ results, query, model, assemblyName, g
|
|
|
127
105
|
// own edges, which is what this default is; a caller that asked for a specific
|
|
128
106
|
// padding gets that instead.
|
|
129
107
|
const SEARCH_HIT_GROW = 0.2;
|
|
130
|
-
|
|
108
|
+
// The track to show beside a hit: the one its index came from, when this
|
|
109
|
+
// session has it. A shared index names tracks of whichever config it was built
|
|
110
|
+
// for — the UCSC hg38 names index says `hg38-ncbiRefSeqGff`, which the HPRC
|
|
111
|
+
// pangenome config reuses without carrying — and launching a track the session
|
|
112
|
+
// lacks turned every gene search there into a "Could not resolve identifier"
|
|
113
|
+
// error over a view that had navigated fine.
|
|
114
|
+
export function hitTrackToShow(result, session) {
|
|
115
|
+
const trackId = result.getTrackId();
|
|
116
|
+
return trackId !== undefined && session.getTrackById(trackId)
|
|
117
|
+
? trackId
|
|
118
|
+
: undefined;
|
|
119
|
+
}
|
|
120
|
+
// A hit lands with the track its index came from shown, which is what a name
|
|
121
|
+
// typed into the search box wants. A session spec that named its own tracks
|
|
122
|
+
// passes false: it asked for those tracks, and a hosted config's full RefSeq
|
|
123
|
+
// appearing beside the curated one it listed read as a bug.
|
|
124
|
+
export async function navToOption({ option, model, assemblyName, grow, showHitTrack = true, }) {
|
|
131
125
|
// getLocation() can be an empty string when a result reports hasLocation()
|
|
132
126
|
// but carries no coordinates; treat that as "no location" and fall back to
|
|
133
127
|
// the label rather than forwarding '' into an empty, view-blanking parse
|
|
134
128
|
const location = option.getLocation() || option.getLabel();
|
|
135
|
-
const trackId = option.getTrackId();
|
|
136
129
|
const session = getSession(model);
|
|
137
130
|
const { assemblyManager } = session;
|
|
138
131
|
await model.navToLocations(parseLocStrings(location, assemblyName, (ref, asm) => assemblyManager.isValidRefName(ref, asm)), assemblyName, grow ?? SEARCH_HIT_GROW);
|
|
132
|
+
const trackId = showHitTrack ? hitTrackToShow(option, session) : undefined;
|
|
139
133
|
if (trackId && isAlive(model)) {
|
|
140
|
-
model.
|
|
134
|
+
await model.launchTrack(trackId);
|
|
141
135
|
}
|
|
142
136
|
// same detach hazard as showTrack above: navToLocations awaits, and handlers
|
|
143
137
|
// read off the view (the canvas one calls getSession(model) and model.tracks),
|
|
@@ -174,7 +168,7 @@ export function notifySearchFailure(session, e) {
|
|
|
174
168
|
// about where to go and so raise the picker, and a view detached while the
|
|
175
169
|
// search RPC ran — and each of them resolves, so a caller awaiting this cannot
|
|
176
170
|
// otherwise tell "showing it" from "asked you which one".
|
|
177
|
-
export async function handleSelectedRegion({ input, model, assemblyName, grow, }) {
|
|
171
|
+
export async function handleSelectedRegion({ input, model, assemblyName, grow, showHitTrack, }) {
|
|
178
172
|
const { assemblyManager, textSearchManager } = getSession(model);
|
|
179
173
|
// resolves only once regions/aliases are loaded, which isValidRefName needs
|
|
180
174
|
// (it throws otherwise). A load failure is not this function's to report:
|
|
@@ -241,14 +235,14 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
|
|
|
241
235
|
// adapters answer 'exact' by filtering exactly this list. An adapter that
|
|
242
236
|
// tags nothing simply never wins the exact pass, which is the behaviour it
|
|
243
237
|
// had when it returned nothing for searchType: 'exact'.
|
|
244
|
-
const
|
|
238
|
+
const found = await searchNames({
|
|
245
239
|
queryString: input,
|
|
246
240
|
assemblyName,
|
|
247
241
|
textSearchManager,
|
|
248
242
|
assembly,
|
|
249
243
|
});
|
|
250
|
-
const exactResults =
|
|
251
|
-
const results = exactResults.length ? exactResults :
|
|
244
|
+
const exactResults = found.results.filter(r => r.isExact());
|
|
245
|
+
const results = exactResults.length ? exactResults : found.results;
|
|
252
246
|
// the view may have been closed/detached while the text-search RPC ran
|
|
253
247
|
if (!isAlive(model)) {
|
|
254
248
|
return false;
|
|
@@ -260,6 +254,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
|
|
|
260
254
|
model,
|
|
261
255
|
assemblyName,
|
|
262
256
|
grow,
|
|
257
|
+
showHitTrack,
|
|
263
258
|
});
|
|
264
259
|
}
|
|
265
260
|
else if (results.length === 1) {
|
|
@@ -272,6 +267,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
|
|
|
272
267
|
model,
|
|
273
268
|
assemblyName,
|
|
274
269
|
grow,
|
|
270
|
+
showHitTrack,
|
|
275
271
|
});
|
|
276
272
|
return true;
|
|
277
273
|
}
|
|
@@ -279,8 +275,8 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
|
|
|
279
275
|
// no search hits: still try to resolve the input as a locstring (bare
|
|
280
276
|
// refname, "ref start end" triplet, etc). if that also can't find a
|
|
281
277
|
// refname AND the input is a single bare token (a plausible gene name),
|
|
282
|
-
// reframe the unknown-ref error as a
|
|
283
|
-
//
|
|
278
|
+
// reframe the unknown-ref error as a name miss; keep the specific ref
|
|
279
|
+
// error for coordinate/multi-part queries
|
|
284
280
|
try {
|
|
285
281
|
await navToLocstrings();
|
|
286
282
|
return true;
|
|
@@ -288,7 +284,7 @@ export async function handleSelectedRegion({ input, model, assemblyName, grow, }
|
|
|
288
284
|
catch (e) {
|
|
289
285
|
const isPlainName = !input.includes(':') && !input.includes(' ');
|
|
290
286
|
if (e instanceof UnknownRefNameError && isPlainName) {
|
|
291
|
-
throw
|
|
287
|
+
throw nameNotFound(input, assemblyName, found);
|
|
292
288
|
}
|
|
293
289
|
else {
|
|
294
290
|
throw e;
|
|
@@ -301,11 +297,11 @@ export function checkRef(str, isRef) {
|
|
|
301
297
|
const [ref, rest] = splitLast(str, ':');
|
|
302
298
|
return isRef(str) || (isRef(ref) && /^\d/.test(rest));
|
|
303
299
|
}
|
|
304
|
-
|
|
305
|
-
const
|
|
300
|
+
async function searchNames({ queryString, searchType, assemblyName, textSearchManager, assembly, signal, }) {
|
|
301
|
+
const report = await textSearchManager?.searchIndexes({
|
|
306
302
|
queryString,
|
|
307
303
|
searchType,
|
|
308
|
-
|
|
304
|
+
signal,
|
|
309
305
|
}, assemblyName);
|
|
310
306
|
// ensure aliases are loaded: allRefNames is a pure getter, so reading it does
|
|
311
307
|
// not itself kick off the lazy load. A load failure just leaves allRefNames
|
|
@@ -314,7 +310,27 @@ export async function fetchResults({ queryString, searchType, assemblyName, text
|
|
|
314
310
|
const refNameResults = assembly
|
|
315
311
|
? searchRefNames(assembly, queryString, searchType)
|
|
316
312
|
: [];
|
|
317
|
-
return
|
|
313
|
+
return {
|
|
314
|
+
results: dedupe([...refNameResults, ...(report?.results ?? [])], elt => elt.getId()),
|
|
315
|
+
indexCount: report?.indexCount ?? 0,
|
|
316
|
+
failures: report?.failures ?? [],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
export async function fetchResults(args) {
|
|
320
|
+
return (await searchNames(args)).results;
|
|
321
|
+
}
|
|
322
|
+
// A miss says when an index could not answer: with none answering it is a
|
|
323
|
+
// failure, and with some answering it is still a miss, naming what failed
|
|
324
|
+
export function nameNotFound(input, assemblyName, { indexCount, failures }) {
|
|
325
|
+
const [failure] = failures;
|
|
326
|
+
const miss = `No results found for "${input}"`;
|
|
327
|
+
return failure === undefined
|
|
328
|
+
? new SearchResultsNotFoundError(indexCount ? miss : `${miss}: ${assemblyName} has no text search index`)
|
|
329
|
+
: failures.length === indexCount
|
|
330
|
+
? new Error(`Searching for "${input}" failed: ${failure}`, {
|
|
331
|
+
cause: failure,
|
|
332
|
+
})
|
|
333
|
+
: new SearchResultsNotFoundError(`${miss} (a search index failed: ${failure})`, { cause: failure });
|
|
318
334
|
}
|
|
319
335
|
// Scan assembly refnames for query matches, resolving aliases (e.g. 'contigB')
|
|
320
336
|
// to the canonical refname ('ctgB') so the dropdown shows the name that matches
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { alpha } from '@jbrowse/core/ui/palette';
|
|
2
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
|
-
import { Typography
|
|
4
|
+
import { Typography } from '@mui/material';
|
|
4
5
|
import { LABEL_WIDTH, SpanEdgeLabels } from './coordLabels.js';
|
|
5
6
|
const useStyles = makeStyles()(theme => ({
|
|
6
7
|
rubberband: {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export declare const LABEL_WIDTH = 100;
|
|
2
|
+
/** A red vertical line at `coordX` with `children` labelled above it. */
|
|
3
|
+
export declare function VerticalGuideLine({ coordX, viewWidth, stickyTop, children, }: {
|
|
4
|
+
coordX: number;
|
|
5
|
+
viewWidth: number;
|
|
6
|
+
stickyTop: string | undefined;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
2
9
|
/**
|
|
3
10
|
* The bp coordinate under a vertical guide, drawn just above the top of the
|
|
4
11
|
* guide line so it covers neither the line nor the cursor hovering the scalebar
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { alpha } from '@jbrowse/core/ui/palette';
|
|
3
4
|
import { MUI_TOOLTIP_Z_INDEX } from '@jbrowse/core/ui/zIndexes';
|
|
4
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import { alpha } from '@mui/material';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
// roughly how wide a coordinate label is. the guide clamps itself half of this
|
|
8
8
|
// from either border, and a rubberband label that would hang off a border goes
|
|
@@ -51,7 +51,21 @@ const useStyles = makeStyles()(theme => ({
|
|
|
51
51
|
insideRightEdge: {
|
|
52
52
|
right: 0,
|
|
53
53
|
},
|
|
54
|
+
guideLine: {
|
|
55
|
+
pointerEvents: 'none',
|
|
56
|
+
height: '100%',
|
|
57
|
+
width: 1,
|
|
58
|
+
position: 'absolute',
|
|
59
|
+
left: 0,
|
|
60
|
+
background: 'red',
|
|
61
|
+
zIndex: 1001,
|
|
62
|
+
},
|
|
54
63
|
}));
|
|
64
|
+
/** A red vertical line at `coordX` with `children` labelled above it. */
|
|
65
|
+
export function VerticalGuideLine({ coordX, viewWidth, stickyTop, children, }) {
|
|
66
|
+
const { classes } = useStyles();
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx(GuideLabel, { coordX: coordX, viewWidth: viewWidth, stickyTop: stickyTop, children: children }), _jsx("div", { className: classes.guideLine, style: { transform: `translateX(${coordX}px)` } })] }));
|
|
68
|
+
}
|
|
55
69
|
function anchorStyle(stickyTop) {
|
|
56
70
|
return stickyTop === undefined
|
|
57
71
|
? { position: 'relative' }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface DragEnd {
|
|
3
|
+
startX: number;
|
|
4
|
+
endX: number;
|
|
5
|
+
isClick: boolean;
|
|
6
|
+
clientX: number;
|
|
7
|
+
clientY: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Follow a press that began at `startX` (px from `ref`'s left edge) through
|
|
11
|
+
* window events until it ends, so the pointer can leave the element mid-drag.
|
|
12
|
+
* A release within 3px of the press is a click. Undefined `startX` is no drag.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useWindowDrag(ref: React.RefObject<HTMLElement | null>, startX: number | undefined, handlers: {
|
|
15
|
+
onMove: (x: number) => void;
|
|
16
|
+
onEnd: (end: DragEnd) => void;
|
|
17
|
+
onCancel: () => void;
|
|
18
|
+
}): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useEventCallback } from '@jbrowse/core/util/useEventCallback';
|
|
3
|
+
/**
|
|
4
|
+
* Follow a press that began at `startX` (px from `ref`'s left edge) through
|
|
5
|
+
* window events until it ends, so the pointer can leave the element mid-drag.
|
|
6
|
+
* A release within 3px of the press is a click. Undefined `startX` is no drag.
|
|
7
|
+
*/
|
|
8
|
+
export function useWindowDrag(ref, startX, handlers) {
|
|
9
|
+
const onMove = useEventCallback(handlers.onMove);
|
|
10
|
+
const onEnd = useEventCallback(handlers.onEnd);
|
|
11
|
+
const onCancel = useEventCallback(handlers.onCancel);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const el = ref.current;
|
|
14
|
+
if (startX === undefined || !el) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { left } = el.getBoundingClientRect();
|
|
18
|
+
const mouseMove = (event) => {
|
|
19
|
+
onMove(event.clientX - left);
|
|
20
|
+
};
|
|
21
|
+
const mouseUp = ({ clientX, clientY }) => {
|
|
22
|
+
const endX = clientX - left;
|
|
23
|
+
onEnd({
|
|
24
|
+
startX,
|
|
25
|
+
endX,
|
|
26
|
+
isClick: Math.abs(endX - startX) <= 3,
|
|
27
|
+
clientX,
|
|
28
|
+
clientY,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const keyDown = (event) => {
|
|
32
|
+
if (event.key === 'Escape') {
|
|
33
|
+
onCancel();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
window.addEventListener('mousemove', mouseMove);
|
|
37
|
+
window.addEventListener('mouseup', mouseUp);
|
|
38
|
+
window.addEventListener('keydown', keyDown);
|
|
39
|
+
return () => {
|
|
40
|
+
window.removeEventListener('mousemove', mouseMove);
|
|
41
|
+
window.removeEventListener('mouseup', mouseUp);
|
|
42
|
+
window.removeEventListener('keydown', keyDown);
|
|
43
|
+
};
|
|
44
|
+
}, [ref, startX, onMove, onEnd, onCancel]);
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-linear-genome-view",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 linear genome view",
|
|
6
6
|
"keywords": [
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
25
|
-
"@mui/icons-material": "^9.
|
|
26
|
-
"@mui/material": "^9.
|
|
27
|
-
"mobx": "^7.0.
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.7.0",
|
|
25
|
+
"@mui/icons-material": "^9.4.0",
|
|
26
|
+
"@mui/material": "^9.4.0",
|
|
27
|
+
"mobx": "^7.0.5",
|
|
28
28
|
"mobx-react": "^10.0.2",
|
|
29
|
-
"@jbrowse/
|
|
30
|
-
"@jbrowse/
|
|
31
|
-
"@jbrowse/
|
|
32
|
-
"@jbrowse/display-
|
|
29
|
+
"@jbrowse/display-kit": "^5.0.0-beta.11",
|
|
30
|
+
"@jbrowse/core": "^5.0.0-beta.11",
|
|
31
|
+
"@jbrowse/render-core": "^5.0.0-beta.11",
|
|
32
|
+
"@jbrowse/display-ui": "^5.0.0-beta.11"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": ">=19.0.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"sideEffects": false,
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@jbrowse/display-test-utils": "^5.0.0-beta.
|
|
43
|
+
"@jbrowse/display-test-utils": "^5.0.0-beta.11"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"prebuild": "pnpm clean",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { TrackOverlayPortal } from '@jbrowse/display-ui';
|
|
3
|
-
import { observer } from 'mobx-react';
|
|
4
|
-
// Lifts an SVG legend / chrome above the inter-region padding masks: a
|
|
5
|
-
// full-track, pointer-events:none <svg> portaled into the TrackContainer overlay
|
|
6
|
-
// node (see TrackOverlayPortal). The SVG counterpart to the HTML overlays
|
|
7
|
-
// (FloatingLegend, HicOverlayPanel) — used where the same legend
|
|
8
|
-
// markup is also drawn by an SVG-export path, so it must stay <svg>.
|
|
9
|
-
//
|
|
10
|
-
// Children draw in <svg> user space; the wrapping <svg> stays pointer-events:none
|
|
11
|
-
// so it doesn't eat canvas events, so any interactive sub-content (a dismiss
|
|
12
|
-
// "×", clickable swatches) must set pointer-events:auto on its own element.
|
|
13
|
-
// fallbackInline={false}: outside a TrackContainer it renders nothing, matching
|
|
14
|
-
// callers whose export path draws its own inline legend.
|
|
15
|
-
export const FloatingSvgOverlay = observer(function FloatingSvgOverlay({ width, height, children, }) {
|
|
16
|
-
return (_jsx(TrackOverlayPortal, { fallbackInline: false, children: _jsx("svg", { style: {
|
|
17
|
-
position: 'absolute',
|
|
18
|
-
top: 0,
|
|
19
|
-
left: 0,
|
|
20
|
-
width,
|
|
21
|
-
height,
|
|
22
|
-
pointerEvents: 'none',
|
|
23
|
-
overflow: 'hidden',
|
|
24
|
-
}, children: children }) }));
|
|
25
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function partitionLaunchKeys(spec: object, viewPropKeys: ReadonlySet<string>): {
|
|
2
|
-
init: Record<string, unknown>;
|
|
3
|
-
viewProps: Record<string, unknown>;
|
|
4
|
-
unknown: Record<string, unknown>;
|
|
5
|
-
};
|
|
6
|
-
export declare function linearGenomeViewPropKeys(pluginManager: {
|
|
7
|
-
getViewType: (name: string) => {
|
|
8
|
-
stateModel: {
|
|
9
|
-
properties: object;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
}): ReadonlySet<string>;
|
|
13
|
-
export declare function warnUnknownLaunchKeys(surface: string, unknown: object): void;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// The init keys are derived from their interface so they can't drift: the
|
|
2
|
-
// Record requires exactly one entry per key, so adding a field without listing
|
|
3
|
-
// it here is a compile error rather than a key that silently warns as "unknown".
|
|
4
|
-
//
|
|
5
|
-
// There is no matching list for the plain view props, and there must not be:
|
|
6
|
-
// they are whatever the state model declares, read off it by the caller and
|
|
7
|
-
// passed in as `viewPropKeys`. A hand-written eight is what left `hideHeader`,
|
|
8
|
-
// `showGridlines`, `showCytobands` and five others declared, menu-settable and
|
|
9
|
-
// silently unreachable from any spec — the same failure LinearSyntenyView's
|
|
10
|
-
// `drawLocationMarkers` had.
|
|
11
|
-
//
|
|
12
|
-
// #launchKeys LinearGenomeView — the URL parameters page renders this list
|
|
13
|
-
// rather than restating it; each of these keys has its own `¶m=` section
|
|
14
|
-
// there, which is why no description is attached here.
|
|
15
|
-
const knownInitKeyMap = {
|
|
16
|
-
loc: true,
|
|
17
|
-
grow: true,
|
|
18
|
-
assembly: true,
|
|
19
|
-
displayedRegionNames: true,
|
|
20
|
-
tracks: true,
|
|
21
|
-
tracklist: true,
|
|
22
|
-
nav: true,
|
|
23
|
-
highlight: true,
|
|
24
|
-
};
|
|
25
|
-
const knownInitKeys = new Set(Object.keys(knownInitKeyMap));
|
|
26
|
-
// Never settable from a spec, whatever the model declares: `type` picks the
|
|
27
|
-
// view and `id` is passed top-level so MST's optional identifier honors it.
|
|
28
|
-
const RESERVED = new Set(['id', 'type', 'init']);
|
|
29
|
-
// The viewport keys from before it was stored as a window. They are no longer
|
|
30
|
-
// declared properties, so the model's own property list does not contain them
|
|
31
|
-
// and they would partition as typos — reported as unknown and dropped. They are
|
|
32
|
-
// not typos: `preProcessSnapshot` still converts them, so they belong in the
|
|
33
|
-
// snapshot bucket, and a URL or saved spec that names them goes on working.
|
|
34
|
-
//
|
|
35
|
-
// Deletable with `legacyBpPerPx` in the model, and for the same reason.
|
|
36
|
-
const LEGACY_VIEWPORT_PROPS = new Set(['bpPerPx', 'offsetPx']);
|
|
37
|
-
// Partition launch keys three ways, once, for every caller that needs to know
|
|
38
|
-
// which is which:
|
|
39
|
-
//
|
|
40
|
-
// - `init`: resolution keys (loc, tracks, highlight, …) with no direct MST
|
|
41
|
-
// representation, which afterAttach applies on attach and then discards
|
|
42
|
-
// - `viewProps`: plain persisted props MST restores natively, so they belong on
|
|
43
|
-
// the view snapshot rather than in the blob
|
|
44
|
-
// - `unknown`: neither, i.e. a typo. MST silently drops unknown snapshot keys and
|
|
45
|
-
// `init` is a frozen blob, so nothing else would notice
|
|
46
|
-
//
|
|
47
|
-
// The two callers differ only in what they do with each bucket: the launcher
|
|
48
|
-
// spreads a flat spec across the new view's snapshot and warns about the typos,
|
|
49
|
-
// while afterAttach — which only ever sees an already-built blob — warns about
|
|
50
|
-
// both a typo and a view prop that ended up inside it.
|
|
51
|
-
export function partitionLaunchKeys(spec,
|
|
52
|
-
// The view's declared property names. Callers with a live node read it off
|
|
53
|
-
// `getPropertyMembers(self).properties`; callers building a snapshot read it
|
|
54
|
-
// off the registered view type's `stateModel.properties` — see
|
|
55
|
-
// `linearGenomeViewPropKeys`.
|
|
56
|
-
viewPropKeys) {
|
|
57
|
-
const init = {};
|
|
58
|
-
const viewProps = {};
|
|
59
|
-
const unknown = {};
|
|
60
|
-
for (const [key, value] of Object.entries(spec)) {
|
|
61
|
-
const bucket = knownInitKeys.has(key)
|
|
62
|
-
? init
|
|
63
|
-
: (viewPropKeys.has(key) || LEGACY_VIEWPORT_PROPS.has(key)) &&
|
|
64
|
-
!RESERVED.has(key)
|
|
65
|
-
? viewProps
|
|
66
|
-
: unknown;
|
|
67
|
-
bucket[key] = value;
|
|
68
|
-
}
|
|
69
|
-
return { init, viewProps, unknown };
|
|
70
|
-
}
|
|
71
|
-
// The registered LinearGenomeView's declared property names, for a caller that
|
|
72
|
-
// has a PluginManager but no view yet (a launcher, or a synteny row being built).
|
|
73
|
-
// A caller holding a live node reads the same thing off
|
|
74
|
-
// `getPropertyMembers(self).properties` instead.
|
|
75
|
-
//
|
|
76
|
-
// `getViewType` THROWS on an unregistered name, and that is left alone: this is
|
|
77
|
-
// only ever called from code that is about to add a LinearGenomeView, so an
|
|
78
|
-
// absent view type is a broken plugin manager rather than a case to degrade
|
|
79
|
-
// through — and degrading would mean silently dropping every property the
|
|
80
|
-
// caller asked for, which is the failure this whole mechanism exists to end.
|
|
81
|
-
//
|
|
82
|
-
// Cached: the set is fixed for the life of the manager, and a launch that opens
|
|
83
|
-
// a stack of rows would otherwise rebuild it per row.
|
|
84
|
-
const propKeyCache = new WeakMap();
|
|
85
|
-
export function linearGenomeViewPropKeys(pluginManager) {
|
|
86
|
-
const cached = propKeyCache.get(pluginManager);
|
|
87
|
-
if (cached) {
|
|
88
|
-
return cached;
|
|
89
|
-
}
|
|
90
|
-
const { stateModel } = pluginManager.getViewType('LinearGenomeView');
|
|
91
|
-
const keys = new Set(Object.keys(stateModel.properties));
|
|
92
|
-
propKeyCache.set(pluginManager, keys);
|
|
93
|
-
return keys;
|
|
94
|
-
}
|
|
95
|
-
// Report the `unknown` bucket. Both callers say the same thing about the same
|
|
96
|
-
// mistake and differ only in which surface they name, so the wording lives next
|
|
97
|
-
// to the partition that produces the bucket rather than in two hand-written
|
|
98
|
-
// copies that drift.
|
|
99
|
-
export function warnUnknownLaunchKeys(surface, unknown) {
|
|
100
|
-
const keys = Object.keys(unknown);
|
|
101
|
-
if (keys.length) {
|
|
102
|
-
console.warn(`${surface} ignored unknown key(s): ${keys.join(', ')}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { stringify } from '@jbrowse/core/util';
|
|
3
|
-
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
-
import { observer } from 'mobx-react';
|
|
5
|
-
import { GuideLabel } from '../shared/coordLabels.js';
|
|
6
|
-
const useStyles = makeStyles()({
|
|
7
|
-
guide: {
|
|
8
|
-
pointerEvents: 'none',
|
|
9
|
-
height: '100%',
|
|
10
|
-
width: 1,
|
|
11
|
-
position: 'absolute',
|
|
12
|
-
background: 'red',
|
|
13
|
-
zIndex: 1001,
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
const VerticalGuide = observer(function VerticalGuide({ model, coordX, }) {
|
|
17
|
-
const { classes } = useStyles();
|
|
18
|
-
const { views } = model;
|
|
19
|
-
return (_jsxs(_Fragment, { children: [_jsx(GuideLabel, { coordX: coordX, viewWidth: Math.min(...views.map(view => view.width)), stickyTop: undefined, children: views.map(view => (_jsx("div", { children: stringify(view.pxToBp(coordX), true) }, view.id))) }), _jsx("div", { className: classes.guide, style: { left: coordX } })] }));
|
|
20
|
-
});
|
|
21
|
-
export default VerticalGuide;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { LinearGenomeViewModel } from './LinearGenomeView/index.ts';
|
|
2
|
-
export declare const LinearGenomeView: {
|
|
3
|
-
(props: {
|
|
4
|
-
model: LinearGenomeViewModel;
|
|
5
|
-
}): import("react").JSX.Element;
|
|
6
|
-
displayName: string;
|
|
7
|
-
};
|
|
8
|
-
export declare const SearchBox: {
|
|
9
|
-
(props: {
|
|
10
|
-
model: LinearGenomeViewModel;
|
|
11
|
-
showHelp?: boolean;
|
|
12
|
-
minWidth?: number;
|
|
13
|
-
maxWidth?: number;
|
|
14
|
-
style?: React.CSSProperties;
|
|
15
|
-
}): import("react").JSX.Element;
|
|
16
|
-
displayName: string;
|
|
17
|
-
};
|
|
18
|
-
export declare const ZoomControls: {
|
|
19
|
-
(props: {
|
|
20
|
-
model: LinearGenomeViewModel;
|
|
21
|
-
}): import("react").JSX.Element;
|
|
22
|
-
displayName: string;
|
|
23
|
-
};
|