@jbrowse/core 5.0.0-beta.2 → 5.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +70 -40
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/FeatureDetails.d.ts +2 -3
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.d.ts +4 -0
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/ParentFeatureLine.js +17 -0
  5. package/esm/BaseFeatureWidget/BaseFeatureDetail/index.js +4 -3
  6. package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.d.ts +2 -2
  7. package/esm/BaseFeatureWidget/index.d.ts +1 -1
  8. package/esm/BaseFeatureWidget/stateModelFactory.d.ts +8 -1
  9. package/esm/BaseFeatureWidget/stateModelFactory.js +6 -0
  10. package/esm/BaseFeatureWidget/types.d.ts +10 -2
  11. package/esm/PluginManager.d.ts +30 -1
  12. package/esm/PluginManager.js +107 -70
  13. package/esm/ReExports/BaseFeatureDetails.js +1 -1
  14. package/esm/ReExports/MuiDataGridReExports.d.ts +1 -0
  15. package/esm/ReExports/MuiDataGridReExports.js +2 -2
  16. package/esm/ReExports/barrelOnlyNames.d.ts +5 -0
  17. package/esm/ReExports/barrelOnlyNames.js +63 -0
  18. package/esm/ReExports/modules.js +28 -56
  19. package/esm/ReExports/publicTracks.d.ts +1 -0
  20. package/esm/ReExports/publicTracks.js +14 -0
  21. package/esm/ReExports/publicUi.d.ts +4 -0
  22. package/esm/ReExports/publicUi.js +33 -0
  23. package/esm/ReExports/publicUtil.d.ts +1 -1
  24. package/esm/ReExports/publicUtil.js +8 -4
  25. package/esm/ReExports/sharedModules.d.ts +1 -1
  26. package/esm/ReExports/sharedModules.js +8 -1
  27. package/esm/ReExports/uiStub.d.ts +1 -0
  28. package/esm/ReExports/uiStub.js +6 -8
  29. package/esm/ReExports/workerModules.js +9 -4
  30. package/esm/ReExports/workerNamespaceNames.d.ts +11 -0
  31. package/esm/ReExports/workerNamespaceNames.js +281 -0
  32. package/esm/configuration/configurationSchema.js +11 -0
  33. package/esm/configuration/index.d.ts +2 -2
  34. package/esm/configuration/index.js +1 -1
  35. package/esm/configuration/promotableDefaults.d.ts +54 -19
  36. package/esm/configuration/promotableDefaults.js +108 -45
  37. package/esm/configuration/promotablePin.d.ts +50 -40
  38. package/esm/configuration/promotableResolve.d.ts +1 -1
  39. package/esm/configuration/promotableResolve.js +1 -1
  40. package/esm/configuration/tabixIndexFields.d.ts +2 -2
  41. package/esm/configuration/tabixIndexFields.js +2 -2
  42. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +4 -12
  43. package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +12 -0
  44. package/esm/data_adapters/BaseAdapter/featureDensity.js +15 -0
  45. package/esm/data_adapters/BaseAdapter/index.d.ts +2 -2
  46. package/esm/data_adapters/BaseAdapter/index.js +1 -1
  47. package/esm/data_adapters/BaseAdapter/types.d.ts +2 -8
  48. package/esm/pluggableElementTypes/AdapterType.d.ts +2 -1
  49. package/esm/pluggableElementTypes/AdapterType.js +1 -1
  50. package/esm/pluggableElementTypes/DisplayType.d.ts +3 -10
  51. package/esm/pluggableElementTypes/DisplayType.js +3 -45
  52. package/esm/pluggableElementTypes/LazyStateModelElement.d.ts +27 -0
  53. package/esm/pluggableElementTypes/LazyStateModelElement.js +71 -0
  54. package/esm/pluggableElementTypes/ViewType.d.ts +3 -10
  55. package/esm/pluggableElementTypes/ViewType.js +6 -51
  56. package/esm/pluggableElementTypes/extendElementType.d.ts +1 -2
  57. package/esm/pluggableElementTypes/extendElementType.js +1 -2
  58. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +3 -3
  59. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +1 -1
  60. package/esm/pluggableElementTypes/models/BaseTrackModel.js +1 -4
  61. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +34 -34
  62. package/esm/pluggableElementTypes/models/BaseViewModel.js +19 -30
  63. package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +4 -3
  64. package/esm/pluggableElementTypes/models/assertDisplayContract.js +2 -2
  65. package/esm/pluggableElementTypes/models/baseTrackConfig.js +7 -1
  66. package/esm/ui/BaseExportSvgDialog.d.ts +1 -1
  67. package/esm/ui/ErrorMessageStackTraceDialog.js +5 -6
  68. package/esm/ui/ExportSvgDialog.d.ts +1 -1
  69. package/esm/ui/MenuTypes.d.ts +3 -3
  70. package/esm/ui/PinAdornment.js +22 -40
  71. package/esm/ui/ScrollChrome.d.ts +16 -0
  72. package/esm/ui/ScrollChrome.js +12 -0
  73. package/esm/ui/ScrollZoomToggle.d.ts +4 -3
  74. package/esm/ui/ScrollZoomToggle.js +4 -3
  75. package/esm/ui/SizeSliderRow.js +1 -1
  76. package/esm/ui/index.d.ts +2 -2
  77. package/esm/ui/index.js +3 -3
  78. package/esm/ui/legendMenuItem.d.ts +9 -15
  79. package/esm/ui/legendMenuItem.js +8 -19
  80. package/esm/ui/menuItemAdornment.d.ts +2 -3
  81. package/esm/ui/menuItemAdornment.js +2 -3
  82. package/esm/ui/menuItems.d.ts +1 -2
  83. package/esm/ui/menuItems.js +0 -1
  84. package/esm/ui/palette.d.ts +11 -11
  85. package/esm/ui/palette.js +11 -11
  86. package/esm/ui/promotablePinCoverage.d.ts +16 -2
  87. package/esm/ui/promotablePinCoverage.js +38 -2
  88. package/esm/ui/theme.js +5 -0
  89. package/esm/ui/toggleMenuItems.d.ts +13 -6
  90. package/esm/ui/toggleMenuItems.js +32 -15
  91. package/esm/util/Base1DUtils.d.ts +4 -2
  92. package/esm/util/Base1DUtils.js +12 -4
  93. package/esm/util/SvgCanvas.js +4 -3
  94. package/esm/util/adapterConfigKey.d.ts +12 -0
  95. package/esm/util/adapterConfigKey.js +14 -0
  96. package/esm/util/adapterMetadata.js +2 -1
  97. package/esm/util/analytics.js +4 -5
  98. package/esm/util/clampToContig.d.ts +17 -0
  99. package/esm/util/clampToContig.js +19 -0
  100. package/esm/util/colorBits.d.ts +2 -1
  101. package/esm/util/colorBits.js +2 -5
  102. package/esm/util/createStopTokenRotation.d.ts +8 -7
  103. package/esm/util/featureTypes.d.ts +24 -0
  104. package/esm/util/featureTypes.js +35 -0
  105. package/esm/util/fetchPhases.d.ts +1 -1
  106. package/esm/util/index.d.ts +4 -2
  107. package/esm/util/index.js +3 -1
  108. package/esm/util/installFetch.d.ts +29 -18
  109. package/esm/util/installFetch.js +17 -17
  110. package/esm/util/installInitAutorun.d.ts +6 -5
  111. package/esm/util/installInitAutorun.js +2 -2
  112. package/esm/util/installPrerequisiteFetch.d.ts +86 -0
  113. package/esm/util/installPrerequisiteFetch.js +73 -0
  114. package/esm/util/openFeatureWidget.d.ts +5 -0
  115. package/esm/util/openFeatureWidget.js +1 -0
  116. package/esm/util/openViews.d.ts +16 -0
  117. package/esm/util/openViews.js +22 -0
  118. package/esm/util/parentWalk.d.ts +1 -1
  119. package/esm/util/parentWalk.js +1 -1
  120. package/esm/util/renderToStaticMarkup.js +10 -0
  121. package/esm/util/scheduleDetachedDestroy.d.ts +18 -0
  122. package/esm/util/scheduleDetachedDestroy.js +24 -0
  123. package/esm/util/scrollLatch.d.ts +1 -0
  124. package/esm/util/scrollLatch.js +8 -0
  125. package/esm/util/stopToken.d.ts +6 -5
  126. package/esm/util/trackConfigDelta.d.ts +6 -6
  127. package/esm/util/trackConfigDelta.js +3 -3
  128. package/esm/util/types/index.d.ts +25 -20
  129. package/esm/util/usePanelVirtualScroll.d.ts +6 -2
  130. package/esm/util/usePanelVirtualScroll.js +6 -2
  131. package/esm/util/useRowVirtualScroll.d.ts +1 -1
  132. package/esm/util/useRowVirtualScroll.js +1 -1
  133. package/esm/util/useVirtualScrollWheel.js +11 -1
  134. package/esm/util/wheelZoom.d.ts +3 -1
  135. package/esm/util/wheelZoom.js +39 -14
  136. package/esm/util/withLaunchInput.d.ts +47 -2
  137. package/esm/util/withLaunchInput.js +56 -11
  138. package/package.json +10 -5
  139. package/esm/ui/promotableMenuItems.d.ts +0 -19
  140. package/esm/ui/promotableMenuItems.js +0 -91
@@ -161,7 +161,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
161
161
  * pointing at" channel a plugin can subscribe to.
162
162
  *
163
163
  * Declared here because a cross-display consumer can only read a name the
164
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
164
+ * base declares — the same reason `FetchMixin.fetchInert` is a
165
165
  * hook rather than a getter each display invents. The container used to
166
166
  * read `featureUnderMouse`, which only the wiggle, alignments and
167
167
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -382,7 +382,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
382
382
  * pointing at" channel a plugin can subscribe to.
383
383
  *
384
384
  * Declared here because a cross-display consumer can only read a name the
385
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
385
+ * base declares — the same reason `FetchMixin.fetchInert` is a
386
386
  * hook rather than a getter each display invents. The container used to
387
387
  * read `featureUnderMouse`, which only the wiggle, alignments and
388
388
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -595,7 +595,7 @@ export declare const BaseDisplay: import("@jbrowse/mobx-state-tree").IModelType<
595
595
  * pointing at" channel a plugin can subscribe to.
596
596
  *
597
597
  * Declared here because a cross-display consumer can only read a name the
598
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
598
+ * base declares — the same reason `FetchMixin.fetchInert` is a
599
599
  * hook rather than a getter each display invents. The container used to
600
600
  * read `featureUnderMouse`, which only the wiggle, alignments and
601
601
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -94,7 +94,7 @@ function stateModelFactory() {
94
94
  * pointing at" channel a plugin can subscribe to.
95
95
  *
96
96
  * Declared here because a cross-display consumer can only read a name the
97
- * base declares — the same reason `SyntenyFetchStateMixin.fetchInert` is a
97
+ * base declares — the same reason `FetchMixin.fetchInert` is a
98
98
  * hook rather than a getter each display invents. The container used to
99
99
  * read `featureUnderMouse`, which only the wiggle, alignments and
100
100
  * Manhattan families spelled that way — canvas said `hoveredFeature`,
@@ -277,10 +277,7 @@ export function createBaseTrackModel(pm, trackType, baseTrackConfig) {
277
277
  const displayConf = getDisplayConf(displays, newDisplayId);
278
278
  // same interception showTrackGeneric makes: a registered-but-unloaded
279
279
  // display would otherwise fail as an opaque union mismatch below
280
- const displayRecord = pm.resolveDisplayTypeRecord(displayConf.type);
281
- if (displayRecord && !displayRecord.isStateModelLoaded) {
282
- throw new Error(`state model for display type ${displayConf.type} is not loaded yet — await getDisplayType('${displayConf.type}').loadStateModel() first`);
283
- }
280
+ pm.resolveDisplayTypeRecord(displayConf.type)?.assertStateModelLoaded();
284
281
  self.displays[idx] = {
285
282
  ...initialSnapshot,
286
283
  type: displayConf.type,
@@ -69,28 +69,28 @@ declare const BaseViewModel: import("@jbrowse/mobx-state-tree").IModelType<{
69
69
  menuItems(): MenuItem[];
70
70
  /**
71
71
  * #getter
72
- * Every track open on this view itself: its own `tracks` array plus any
73
- * track containers it owns instead (the synteny view keeps one list per
74
- * band on `trackContainers` and its own `tracks` empty). Tracks on nested
75
- * views are `allTracks`'s, not this getter's.
72
+ * The tracks this view puts in the census — its own, not a nested view's.
73
+ *
74
+ * Empty here, and overridden by the views that have any: the base cannot
75
+ * read `self.tracks` for them, because what a view keeps under that name
76
+ * is the view's business. react-msaview's holds its MSA annotation rows —
77
+ * `{ReactComponent, model}` objects with no configuration and no displays
78
+ * — and a base that helped itself to them handed them to the readiness
79
+ * marker as tracks, which error-paged every session holding an MSA view.
76
80
  */
77
81
  readonly ownTracks: AbstractTrackModel[];
78
82
  /**
79
83
  * #getter
80
- * This view and every view nested inside it, to any depth — a synteny
81
- * stack's genome rows, a breakpoint split view's panels. Each view answers
82
- * for its own children, so a consumer never walks the nesting itself:
83
- * before this getter, four consumers each carried a copy of the walk and
84
- * two of them had drifted (one read `levels`, one read `trackContainers`,
85
- * and each was blind to the other's spelling).
86
- */
87
- readonly allViews: AbstractViewModel[];
88
- } & {
89
- /**
90
- * #getter
91
- * Every track open on this view or any view nested inside it.
84
+ * The views nested directly inside this one, which the census counts as
85
+ * views in their own right — a synteny stack's genome rows, a breakpoint
86
+ * split view's panels.
87
+ *
88
+ * Empty here for the same reason, and the dotplot is why it has to be a
89
+ * declaration rather than a walk of `views`: that prop name holds its two
90
+ * 1D *axis* models, which are view-shaped and are not views the user
91
+ * opened. No structural test separates the two — only the view knows.
92
92
  */
93
- readonly allTracks: AbstractTrackModel[];
93
+ readonly ownViews: AbstractViewModel[];
94
94
  } & {
95
95
  /**
96
96
  * #action
@@ -199,28 +199,28 @@ export declare const BaseViewModelWithDisplayedRegions: import("@jbrowse/mobx-st
199
199
  menuItems(): MenuItem[];
200
200
  /**
201
201
  * #getter
202
- * Every track open on this view itself: its own `tracks` array plus any
203
- * track containers it owns instead (the synteny view keeps one list per
204
- * band on `trackContainers` and its own `tracks` empty). Tracks on nested
205
- * views are `allTracks`'s, not this getter's.
202
+ * The tracks this view puts in the census — its own, not a nested view's.
203
+ *
204
+ * Empty here, and overridden by the views that have any: the base cannot
205
+ * read `self.tracks` for them, because what a view keeps under that name
206
+ * is the view's business. react-msaview's holds its MSA annotation rows —
207
+ * `{ReactComponent, model}` objects with no configuration and no displays
208
+ * — and a base that helped itself to them handed them to the readiness
209
+ * marker as tracks, which error-paged every session holding an MSA view.
206
210
  */
207
211
  readonly ownTracks: AbstractTrackModel[];
208
212
  /**
209
213
  * #getter
210
- * This view and every view nested inside it, to any depth — a synteny
211
- * stack's genome rows, a breakpoint split view's panels. Each view answers
212
- * for its own children, so a consumer never walks the nesting itself:
213
- * before this getter, four consumers each carried a copy of the walk and
214
- * two of them had drifted (one read `levels`, one read `trackContainers`,
215
- * and each was blind to the other's spelling).
216
- */
217
- readonly allViews: AbstractViewModel[];
218
- } & {
219
- /**
220
- * #getter
221
- * Every track open on this view or any view nested inside it.
214
+ * The views nested directly inside this one, which the census counts as
215
+ * views in their own right — a synteny stack's genome rows, a breakpoint
216
+ * split view's panels.
217
+ *
218
+ * Empty here for the same reason, and the dotplot is why it has to be a
219
+ * declaration rather than a walk of `views`: that prop name holds its two
220
+ * 1D *axis* models, which are view-shaped and are not views the user
221
+ * opened. No structural test separates the two — only the view knows.
222
222
  */
223
- readonly allTracks: AbstractTrackModel[];
223
+ readonly ownViews: AbstractViewModel[];
224
224
  } & {
225
225
  /**
226
226
  * #action
@@ -96,42 +96,31 @@ const BaseViewModel = types
96
96
  },
97
97
  /**
98
98
  * #getter
99
- * Every track open on this view itself: its own `tracks` array plus any
100
- * track containers it owns instead (the synteny view keeps one list per
101
- * band on `trackContainers` and its own `tracks` empty). Tracks on nested
102
- * views are `allTracks`'s, not this getter's.
99
+ * The tracks this view puts in the census — its own, not a nested view's.
100
+ *
101
+ * Empty here, and overridden by the views that have any: the base cannot
102
+ * read `self.tracks` for them, because what a view keeps under that name
103
+ * is the view's business. react-msaview's holds its MSA annotation rows —
104
+ * `{ReactComponent, model}` objects with no configuration and no displays
105
+ * — and a base that helped itself to them handed them to the readiness
106
+ * marker as tracks, which error-paged every session holding an MSA view.
103
107
  */
104
108
  get ownTracks() {
105
- const s = self;
106
- return [
107
- ...(s.tracks ?? []),
108
- ...(s.trackContainers ?? []).flatMap(c => c.tracks ?? []),
109
- ];
110
- },
111
- /**
112
- * #getter
113
- * This view and every view nested inside it, to any depth — a synteny
114
- * stack's genome rows, a breakpoint split view's panels. Each view answers
115
- * for its own children, so a consumer never walks the nesting itself:
116
- * before this getter, four consumers each carried a copy of the walk and
117
- * two of them had drifted (one read `levels`, one read `trackContainers`,
118
- * and each was blind to the other's spelling).
119
- */
120
- get allViews() {
121
- const s = self;
122
- return [
123
- self,
124
- ...(s.views ?? []).flatMap(v => v.allViews ?? []),
125
- ];
109
+ return [];
126
110
  },
127
- }))
128
- .views(self => ({
129
111
  /**
130
112
  * #getter
131
- * Every track open on this view or any view nested inside it.
113
+ * The views nested directly inside this one, which the census counts as
114
+ * views in their own right — a synteny stack's genome rows, a breakpoint
115
+ * split view's panels.
116
+ *
117
+ * Empty here for the same reason, and the dotplot is why it has to be a
118
+ * declaration rather than a walk of `views`: that prop name holds its two
119
+ * 1D *axis* models, which are view-shaped and are not views the user
120
+ * opened. No structural test separates the two — only the view knows.
132
121
  */
133
- get allTracks() {
134
- return self.allViews.flatMap(v => v.ownTracks);
122
+ get ownViews() {
123
+ return [];
135
124
  },
136
125
  }))
137
126
  .actions(self => ({
@@ -60,8 +60,9 @@ interface RetryContractHost {
60
60
  reloadCounter: number;
61
61
  fetchInert: boolean;
62
62
  /**
63
- * Optional: the comparative family has no two-stage fetch, so nothing there
64
- * declares it. A family that grows one declares it and gets the deferral.
63
+ * Optional: chord and the breakpoint overlay have no two-stage fetch, so
64
+ * neither declares it. A fetch that grows one declares it and gets the
65
+ * deferral.
65
66
  */
66
67
  awaitingPrerequisite?: boolean;
67
68
  /**
@@ -92,7 +93,7 @@ export declare function takeFetchStarted(self: object): boolean;
92
93
  * has changed. Arc shipped exactly that: a `prepare` declining on `dataCurrent`,
93
94
  * with the error clearing on click and no arcs ever coming back. The fix this
94
95
  * message asks for is what `GlobalFetchMixin.reload()` now does for that whole
95
- * family — drop `loadedFetchSignature`, so `dataCurrent` goes false and the
96
+ * family — drop `loadedFetchKey`, so `dataCurrent` goes false and the
96
97
  * gate opens.
97
98
  *
98
99
  * Detected at the moment it happens rather than statically, because the relation
@@ -109,7 +109,7 @@ export function takeFetchStarted(self) {
109
109
  * has changed. Arc shipped exactly that: a `prepare` declining on `dataCurrent`,
110
110
  * with the error clearing on click and no arcs ever coming back. The fix this
111
111
  * message asks for is what `GlobalFetchMixin.reload()` now does for that whole
112
- * family — drop `loadedFetchSignature`, so `dataCurrent` goes false and the
112
+ * family — drop `loadedFetchKey`, so `dataCurrent` goes false and the
113
113
  * gate opens.
114
114
  *
115
115
  * Detected at the moment it happens rather than statically, because the relation
@@ -222,7 +222,7 @@ export function makeRetryContractCheck(self) {
222
222
  `fetch autorun's gate still declines, so Retry is a dead ` +
223
223
  `button — it clears the error and nothing refetches. reload() has ` +
224
224
  `to invalidate whatever that gate reads, not just bump the ` +
225
- `counter (GlobalFetchMixin.reload drops loadedFetchSignature so ` +
225
+ `counter (GlobalFetchMixin.reload drops loadedFetchKey so ` +
226
226
  `dataCurrent goes false). If this display is deliberately not ` +
227
227
  `fetching, say so with fetchInert — the loading scrim and the SVG ` +
228
228
  `export read it too; if this run declined only because a ` +
@@ -61,7 +61,13 @@ export function preprocessTrackConfigSnapshot(pluginManager, snapshot) {
61
61
  const canonical = displayAliasMap.get(d.type);
62
62
  return canonical ? { ...d, type: canonical } : d;
63
63
  })
64
- .filter(d => knownDisplayTypes.has(d.type))
64
+ .filter(d => {
65
+ const known = knownDisplayTypes.has(d.type);
66
+ if (!known) {
67
+ console.warn(`Dropping display of unknown type "${d.type}" from track "${snap.trackId}": no registered display type has that name`);
68
+ }
69
+ return known;
70
+ })
65
71
  .filter(d => {
66
72
  const dup = seenTypes.has(d.type);
67
73
  seenTypes.add(d.type);
@@ -9,7 +9,7 @@ export interface BaseExportSvgOptions {
9
9
  declare const _default: ({ model, handleClose, exportSvg, children, checkboxes, }: {
10
10
  model: IAnyStateTreeNode;
11
11
  handleClose: () => void;
12
- exportSvg: (opts: BaseExportSvgOptions) => Promise<void>;
12
+ exportSvg: (opts: BaseExportSvgOptions) => Promise<unknown>;
13
13
  children?: React.ReactNode;
14
14
  checkboxes?: React.ReactNode;
15
15
  }) => import("react").JSX.Element;
@@ -15,12 +15,11 @@ export default function ErrorMessageStackTraceDialog({ error, onClose, extra, })
15
15
  const errorText = error ? `${error}` : '';
16
16
  const stackTrace = formatErrorStack(error);
17
17
  const { data: mappedStackTrace, isLoading } = useFetch(['mappedStackTrace', stackTrace], () => mapStackTrace(stackTrace));
18
- // The rung actually drawing, not a list of the ones available: it already says
19
- // which rungs exist, since Canvas2D means neither and WebGL2 means no WebGPU.
20
- // It accounts for a `?renderer=` pin and for the GPU banner's "disable GPU",
21
- // which matters most in exactly this dialog — a user reporting a rendering bug
22
- // has often already clicked that button, and until this moved to render-core
23
- // the report they sent still said WebGL2.
18
+ // The rung createGpuHal built, not a list of the ones available: it already
19
+ // says which rungs exist, since Canvas2D means neither and WebGL2 means no
20
+ // WebGPU. It also honours the GPU banner's "disable GPU", which matters most
21
+ // in exactly this dialog — a user reporting a rendering bug has often already
22
+ // clicked it.
24
23
  const graphicsInfo = graphicsCapabilities
25
24
  ? `Graphics: ${effectiveRenderer(graphicsCapabilities)}`
26
25
  : '';
@@ -1,7 +1,7 @@
1
1
  import type { BaseExportSvgOptions } from './BaseExportSvgDialog.tsx';
2
2
  export default function ExportSvgDialog({ model, handleClose, }: {
3
3
  model: {
4
- exportSvg(opts: BaseExportSvgOptions): Promise<void>;
4
+ exportSvg(opts: BaseExportSvgOptions): Promise<unknown>;
5
5
  };
6
6
  handleClose: () => void;
7
7
  }): import("react").JSX.Element;
@@ -66,7 +66,7 @@ export interface BaseMenuItem {
66
66
  */
67
67
  endAdornment?: React.ReactNode;
68
68
  /**
69
- * #menuField the "apply this to all open tracks of this type" pin; set it with a promotable builder
69
+ * #menuField the "apply this to all open tracks of this type" pin; set it through a row builder's `pin` option
70
70
  *
71
71
  * The trailing "default for all tracks of this type" pin, as a **description**
72
72
  * rather than an element — the renderer builds `PinAdornment` from
@@ -76,8 +76,8 @@ export interface BaseMenuItem {
76
76
  * into every host's first paint. It did, until 2026-08-05; see
77
77
  * reference/EAGER_BUNDLE.md.
78
78
  *
79
- * Set it through `promotableToggleItem` / `promotableRadioItems` (or
80
- * `promotableRadioItem` for a lone row), not by hand.
79
+ * Set it through the `pin` option of `checkboxItem` / `toggleItem` /
80
+ * `radioItem` / `radioItems`, not by hand.
81
81
  *
82
82
  * A `type: 'custom'` row (`makePromotableSizeMenu`) draws its own pin inside
83
83
  * its rendered content and still sets this — the declaration is what lets a
@@ -18,40 +18,26 @@ const useStyles = makeStyles()(theme => ({
18
18
  display: 'inline-flex',
19
19
  },
20
20
  }));
21
- // How the pin names what it acts on. Two shapes, decided by the on-value:
22
- //
23
- // - **A boolean on-value carries a state**, and the row's label names the
24
- // setting rather than a value ("Show legend", "Show soft clipping"). A pin on
25
- // an unchecked such row applies the setting *off*, so the value-shaped copy
26
- // below stated the opposite of what the click does — and then, once filled,
27
- // claimed the setting was on by default when it had just been turned off.
28
- // - **Everything else IS what the label says**: a radio option ("Compact"), a
29
- // size row whose caller folds the value into the label ("Line width (2px)").
30
- //
31
- // One `typeof` rather than a flag on the row, because the two forms of
32
- // `makePin` are exactly this distinction: a symmetric pin over a `maybeBoolean`
33
- // slot is the only way a pin's value and its label can disagree.
34
- function pinPredicate(onValue) {
35
- return typeof onValue === 'boolean'
36
- ? `${onValue ? 'on' : 'off'} by default`
37
- : 'the default';
38
- }
39
- // The click, which is not the state the pin draws: an outline pin applies the
40
- // value to the open tracks, a filled one clears the default it stands for.
21
+ // A toggle pin sits on a checkbox row: the copy names the state the click
22
+ // applies, and the fill mirrors the checkbox. A value pin's on-value IS what
23
+ // the label says — a radio option ("Compact"), a size row whose caller folds
24
+ // the value into the label ("Line width (2px)") — and there the fill means the
25
+ // value is the promoted default, so a click clears it.
41
26
  //
42
27
  // The aria-label carries "of this type" wherever the tooltip does: it used to
43
28
  // stop at "for all open tracks", stating a wider blast radius than the control
44
29
  // has.
45
- function pinCopy(label, onValue, isDefault) {
46
- return isDefault
30
+ function pinCopy(label, control) {
31
+ const { kind, active } = control;
32
+ return kind === 'toggle'
47
33
  ? {
48
- title: `${label} is ${pinPredicate(onValue)} for all tracks of this type (click to clear)`,
49
- ariaLabel: `clear the default for ${label} for all tracks of this type`,
34
+ title: `Turn ${label} ${active ? 'off' : 'on'} for all open tracks of this type`,
35
+ ariaLabel: `turn ${label} ${active ? 'off' : 'on'} for all open tracks of this type`,
50
36
  }
51
- : typeof onValue === 'boolean'
37
+ : active
52
38
  ? {
53
- title: `Turn ${label} ${onValue ? 'on' : 'off'} for all open tracks of this type`,
54
- ariaLabel: `turn ${label} ${onValue ? 'on' : 'off'} for all open tracks of this type`,
39
+ title: `${label} is the default for all tracks of this type (click to clear)`,
40
+ ariaLabel: `clear the default for ${label} for all tracks of this type`,
55
41
  }
56
42
  : {
57
43
  title: `Apply ${label} to all open tracks of this type`,
@@ -64,22 +50,18 @@ function pinCopy(label, onValue, isDefault) {
64
50
  // ToggleButton (native button a11y + a clear selected tint) with a pin —
65
51
  // distinct from the value checkbox.
66
52
  //
67
- // **The click and the state are two different things.** One click writes the
68
- // value into every open track of this display type and raises a snackbar
69
- // offering to keep it as the display type's default; the filled pin means that
70
- // default is in place, so a click on a filled pin clears it and touches no
71
- // track. A default therefore takes two deliberate clicks, which is what it costs
72
- // to govern every track of the type opened later (ADR-048). Always shown so the
73
- // capability is discoverable. stopPropagation keeps the click off the row value
74
- // / menu dismissal. "of this type" because a promoted default is scoped to the
75
- // display type (e.g. every LinearAlignmentsDisplay), not literally all tracks.
53
+ // One click writes the value into every open track of this display type and
54
+ // raises a snackbar offering to keep it as the display type's default, so a
55
+ // default takes two deliberate clicks (ADR-048). Always shown so the capability
56
+ // is discoverable. stopPropagation keeps the click off the row value / menu
57
+ // dismissal. "of this type" because a promoted default is scoped to the display
58
+ // type (e.g. every LinearAlignmentsDisplay), not literally all tracks.
76
59
  export function PinAdornment({ pin, disabled, }) {
77
60
  const { classes } = useStyles();
78
61
  const { label, control } = pin;
79
- const isDefault = control.active;
80
- const { title, ariaLabel } = pinCopy(label, control.onValue, isDefault);
81
- return (_jsx(Tooltip, { title: title, children: _jsx("span", { className: classes.wrapper, children: _jsx(ToggleButton, { className: classes.button, value: "default", disabled: disabled, selected: isDefault, color: "primary", size: "small", "aria-label": ariaLabel, onChange: e => {
62
+ const { title, ariaLabel } = pinCopy(label, control);
63
+ return (_jsx(Tooltip, { title: title, children: _jsx("span", { className: classes.wrapper, children: _jsx(ToggleButton, { className: classes.button, value: "default", disabled: disabled, selected: control.active, color: "primary", size: "small", "aria-label": ariaLabel, onChange: e => {
82
64
  e.stopPropagation();
83
65
  control.toggle();
84
- }, children: isDefault ? (_jsx(PushPinIcon, { fontSize: "small" })) : (_jsx(PushPinOutlinedIcon, { fontSize: "small" })) }) }) }));
66
+ }, children: control.active ? (_jsx(PushPinIcon, { fontSize: "small" })) : (_jsx(PushPinOutlinedIcon, { fontSize: "small" })) }) }) }));
85
67
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The chrome a display that scrolls its content virtually mounts over its
3
+ * viewport: `ScrollEdgeShadow` saying content is hidden past an edge, and
4
+ * `VerticalScrollbar` to reach it. Both read the same three numbers, so a
5
+ * display hands them over once here. Renders nothing when the content fits.
6
+ */
7
+ export default function ScrollChrome({ scrollTop, setScrollTop, viewportHeight, contentHeight, controlsId, top, }: {
8
+ scrollTop: number;
9
+ setScrollTop: (n: number) => void;
10
+ viewportHeight: number;
11
+ contentHeight: number;
12
+ /** `id` of the scrolled viewport element, for `aria-controls`. */
13
+ controlsId: string;
14
+ /** Viewport offset from the top, for a display with a sticky band above it. */
15
+ top?: number;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import ScrollEdgeShadow from './ScrollEdgeShadow.js';
3
+ import VerticalScrollbar from './VerticalScrollbar.js';
4
+ /**
5
+ * The chrome a display that scrolls its content virtually mounts over its
6
+ * viewport: `ScrollEdgeShadow` saying content is hidden past an edge, and
7
+ * `VerticalScrollbar` to reach it. Both read the same three numbers, so a
8
+ * display hands them over once here. Renders nothing when the content fits.
9
+ */
10
+ export default function ScrollChrome({ scrollTop, setScrollTop, viewportHeight, contentHeight, controlsId, top = 0, }) {
11
+ return (_jsxs(_Fragment, { children: [_jsx(ScrollEdgeShadow, { scrollTop: scrollTop, viewportHeight: viewportHeight, contentHeight: contentHeight, top: top }), _jsx(VerticalScrollbar, { scrollTop: scrollTop, setScrollTop: setScrollTop, viewportHeight: viewportHeight, contentHeight: contentHeight, controlsId: controlsId, top: top })] }));
12
+ }
@@ -27,9 +27,10 @@ export interface ScrollZoomToggleModel {
27
27
  * of this button pulse with it, which is the preference being session-wide made
28
28
  * visible.
29
29
  *
30
- * `iconOnly` is for a header where this sits inside a run of icon buttons (the
31
- * comparative views), where a single labelled button reads as a mistake rather
32
- * than as emphasis. The tooltip carries the same words either way.
30
+ * `iconOnly` is for a header with no room for the words, or one where this sits
31
+ * inside a run of icon buttons with nothing else labelled — a single labelled
32
+ * button there reads as a mistake rather than as emphasis. The tooltip carries
33
+ * the same words either way.
33
34
  */
34
35
  declare const ScrollZoomToggle: ({ model, iconOnly, }: {
35
36
  model: ScrollZoomToggleModel;
@@ -67,9 +67,10 @@ const useStyles = makeStyles()(theme => {
67
67
  * of this button pulse with it, which is the preference being session-wide made
68
68
  * visible.
69
69
  *
70
- * `iconOnly` is for a header where this sits inside a run of icon buttons (the
71
- * comparative views), where a single labelled button reads as a mistake rather
72
- * than as emphasis. The tooltip carries the same words either way.
70
+ * `iconOnly` is for a header with no room for the words, or one where this sits
71
+ * inside a run of icon buttons with nothing else labelled — a single labelled
72
+ * button there reads as a mistake rather than as emphasis. The tooltip carries
73
+ * the same words either way.
73
74
  */
74
75
  const ScrollZoomToggle = observer(function ScrollZoomToggle({ model, iconOnly, }) {
75
76
  const { classes } = useStyles();
@@ -33,7 +33,7 @@ export const SizeSliderRow = observer(function SizeSliderRow({ title, getValue,
33
33
  const slug = title.toLowerCase().replaceAll(' ', '-');
34
34
  return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.header, children: [_jsxs(Typography, { variant: "caption", color: "textSecondary", className: classes.label, children: [title, ": ", format(value)] }), _jsx(ResetToDefaultButton, { disabled: isDefault, onClick: () => {
35
35
  onReset();
36
- } }), pin ? (_jsx(PinAdornment, { pin: { control: pin, label: `${title} (${format(value)})` } })) : null] }), _jsx(SingleSlider, { value: toSlider(value), min: toSlider(min), max: toSlider(max), step: sliderStep ?? step, size: "small", "aria-label": title.toLowerCase(), "data-testid": `${slug}-slider`, valueLabelDisplay: "auto", valueLabelFormat: (v) => format(fromSlider(v)), sx: { py: 0.5, display: 'block' }, onChange: v => {
36
+ } }), pin ? (_jsx(PinAdornment, { pin: { control: pin, label: `${title} (${format(modelValue)})` } })) : null] }), _jsx(SingleSlider, { value: toSlider(value), min: toSlider(min), max: toSlider(max), step: sliderStep ?? step, size: "small", "aria-label": title.toLowerCase(), "data-testid": `${slug}-slider`, valueLabelDisplay: "auto", valueLabelFormat: (v) => format(fromSlider(v)), sx: { py: 0.5, display: 'block' }, onChange: v => {
37
37
  const n = fromSlider(v);
38
38
  if (commitOnRelease) {
39
39
  setDragValue(n);
package/esm/ui/index.d.ts CHANGED
@@ -48,6 +48,7 @@ export { default as StatusProgressBar } from './StatusProgressBar.tsx';
48
48
  export { default as Menu } from './Menu.tsx';
49
49
  export { default as PrerenderedCanvas } from './PrerenderedCanvas.tsx';
50
50
  export { default as ResizeHandle } from './ResizeHandle.tsx';
51
+ export { default as ScrollChrome } from './ScrollChrome.tsx';
51
52
  export { default as ScrollEdgeShadow } from './ScrollEdgeShadow.tsx';
52
53
  export { default as VerticalScrollbar } from './VerticalScrollbar.tsx';
53
54
  export { default as SubmitDialog } from './SubmitDialog.tsx';
@@ -78,8 +79,7 @@ export { hoverBoxStyle } from './hoverBoxStyle.ts';
78
79
  export { useMouseState, useMouseTracking } from './useMouseTracking.ts';
79
80
  export type { MouseState, MouseTracker } from './useMouseTracking.ts';
80
81
  export { PinAdornment } from './PinAdornment.tsx';
81
- export { promotableRadioItem, promotableRadioItems, promotableToggleItem, } from './promotableMenuItems.ts';
82
- export { displayTypesWithPromotableSlots, promotableSlotsWithoutPin, } from './promotablePinCoverage.ts';
82
+ export { displayTypesWithPromotableSlots, misKindedPins, promotableSlotsWithoutPin, } from './promotablePinCoverage.ts';
83
83
  export declare const VIEW_HEADER_HEIGHT = 28;
84
84
  export { GRADIENT_LEGEND_HEIGHT, GRADIENT_LEGEND_SVG_AREA_WIDTH, GRADIENT_LEGEND_WIDTH, default as SvgGradientLegend, } from './SvgGradientLegend.tsx';
85
85
  export type { GradientLabel, GradientStop } from './SvgGradientLegend.tsx';
package/esm/ui/index.js CHANGED
@@ -44,6 +44,7 @@ export { default as StatusProgressBar } from './StatusProgressBar.js';
44
44
  export { default as Menu } from './Menu.js';
45
45
  export { default as PrerenderedCanvas } from './PrerenderedCanvas.js';
46
46
  export { default as ResizeHandle } from './ResizeHandle.js';
47
+ export { default as ScrollChrome } from './ScrollChrome.js';
47
48
  export { default as ScrollEdgeShadow } from './ScrollEdgeShadow.js';
48
49
  export { default as VerticalScrollbar } from './VerticalScrollbar.js';
49
50
  export { default as SubmitDialog } from './SubmitDialog.js';
@@ -72,8 +73,7 @@ export { default as Crosshairs } from './Crosshairs.js';
72
73
  export { hoverBoxStyle } from './hoverBoxStyle.js';
73
74
  export { useMouseState, useMouseTracking } from './useMouseTracking.js';
74
75
  export { PinAdornment } from './PinAdornment.js';
75
- export { promotableRadioItem, promotableRadioItems, promotableToggleItem, } from './promotableMenuItems.js';
76
- // Not on the React-free `menuItems.ts` entry beside the two builders above: this
76
+ // Not on the React-free `menuItems.ts` entry beside the row builders: this
77
77
  // reads a config's slot table, so it pulls `configuration/promotableSlots.ts`
78
78
  // in, and that entry exists precisely to stay out of every plugin's eager graph.
79
79
  //
@@ -81,7 +81,7 @@ export { promotableRadioItem, promotableRadioItems, promotableToggleItem, } from
81
81
  // caller has is "which promotable slots does this menu miss", and answering the
82
82
  // half of it that lists what a menu *does* offer needs the schema's slot table
83
83
  // to mean anything.
84
- export { displayTypesWithPromotableSlots, promotableSlotsWithoutPin, } from './promotablePinCoverage.js';
84
+ export { displayTypesWithPromotableSlots, misKindedPins, promotableSlotsWithoutPin, } from './promotablePinCoverage.js';
85
85
  export const VIEW_HEADER_HEIGHT = 28;
86
86
  export { GRADIENT_LEGEND_HEIGHT, GRADIENT_LEGEND_SVG_AREA_WIDTH, GRADIENT_LEGEND_WIDTH, default as SvgGradientLegend, } from './SvgGradientLegend.js';
87
87
  export { LEGEND_ROW_HEIGHT, LEGEND_SWATCH, default as SvgColorLegend, } from './SvgColorLegend.js';
@@ -1,5 +1,5 @@
1
- import type { Pin } from '../configuration/promotablePin.ts';
2
1
  import type { MenuItem } from './MenuTypes.ts';
2
+ import type { CheckboxRowOptions } from './toggleMenuItems.ts';
3
3
  /**
4
4
  * The "Show legend" checkbox, which seven displays build by hand — Hi-C, LD,
5
5
  * alignments, LGVSynteny, multi-wiggle, multi-row features, both multi-sample
@@ -14,25 +14,19 @@ import type { MenuItem } from './MenuTypes.ts';
14
14
  * values legitimately differ (a Hi-C color scale is off by default, a variant
15
15
  * genotype key on), and their descriptions describe genuinely different legends.
16
16
  * What they now share is being *promotable* — pass `opts.pin` and the row gains
17
- * the pin that applies the current state to every open track of this display
18
- * type, offering the display-type default as its snackbar's one action. Every
17
+ * the pin that toggles the legend on every open track of this display type,
18
+ * offering the new state as the display-type default in its snackbar. Every
19
19
  * display whose legend is backed by a config slot passes one, and gets it from
20
- * `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling `makePin`
21
- * itself: the slot is the per-display half, the accessors over it are not.
20
+ * `LegendMixin`'s `showLegendDisplayTypeDefault` rather than calling
21
+ * `makeTogglePin` itself: the slot is the per-display half, the accessors over
22
+ * it are not.
22
23
  *
23
24
  * `pin` is optional because two callers have no slot to promote: the Manhattan
24
25
  * plot's LD legend and `LinearBasicDisplay`'s color key are a volatile and a
25
26
  * per-legend `dismissed` flag respectively, neither of which is config at all.
26
- * They get the plain row, which is what `checkboxItem` builds either way — the
27
- * promotable form is that same row plus the pin, per `promotableToggleItem`.
28
27
  *
29
- * Deliberately not used by `synteny-core`'s "Show color legend", which sits
30
- * inside a "Color by..." submenu where the bare word would not say which legend.
28
+ * `synteny-core`'s color-by legend has no toggle at all: it comes up with the
29
+ * modes that have a key and is dismissed from its own close button.
31
30
  */
32
31
  /** #menuBuilder showLegendCheckboxItem | the shared "Show legend" checkbox */
33
- export declare function showLegendCheckboxItem(checked: boolean, onToggle: () => void, opts?: {
34
- helpText?: string;
35
- disabled?: boolean;
36
- disabledHelpText?: string;
37
- pin?: Pin;
38
- }): MenuItem;
32
+ export declare function showLegendCheckboxItem(checked: boolean, onToggle: () => void, opts?: CheckboxRowOptions): MenuItem;