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

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 (142) 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/whenViewSettled.d.ts +31 -6
  137. package/esm/util/whenViewSettled.js +33 -5
  138. package/esm/util/withLaunchInput.d.ts +47 -2
  139. package/esm/util/withLaunchInput.js +56 -11
  140. package/package.json +10 -5
  141. package/esm/ui/promotableMenuItems.d.ts +0 -19
  142. package/esm/ui/promotableMenuItems.js +0 -91
@@ -41,6 +41,18 @@ export function defineLaunchKeys() {
41
41
  // is ever a setting and neither can be lifted off the snapshot.
42
42
  const IDENTITY = new Set(['id', 'type']);
43
43
  const LAUNCH = 'launch';
44
+ /**
45
+ * The part of a launch spec that belongs on the snapshot: everything but the
46
+ * launch keys, and the identity and `launch` keys no spec may set. For a caller
47
+ * that applies the launch itself and builds the view from a snapshot — a
48
+ * synteny row, whose parent navigates it and opens its tracks — so what it
49
+ * hands MST is settings only, and the view's own partition sorts those into
50
+ * properties and reported typos.
51
+ */
52
+ export function snapshotSettings(spec, registration) {
53
+ const keys = registration?.keys ?? {};
54
+ return Object.fromEntries(Object.entries(spec).filter(([key]) => !(key in keys) && !IDENTITY.has(key) && key !== LAUNCH));
55
+ }
44
56
  function isObject(value) {
45
57
  return !!value && typeof value === 'object';
46
58
  }
@@ -80,6 +92,31 @@ function splitEntries(kind, value) {
80
92
  state: built.length ? (Array.isArray(value) ? built : value) : undefined,
81
93
  };
82
94
  }
95
+ // The properties a snapshot's keys are classified against are the REGISTERED
96
+ // view type's, read at preprocess time: `extendViewType` composes onto the
97
+ // model after `withLaunchInput` wrapped it, and a set frozen at wrap time sent
98
+ // every added property to `unknown` — restored at its default, with a warning
99
+ // naming it a typo. The wrapped model's own properties are the answer when no
100
+ // registry is given, or the snapshot names a type it does not hold, which is
101
+ // how a factory-built model in a test behaves.
102
+ function knownPropsResolver(model, registry) {
103
+ let cachedProps;
104
+ let cachedKeys = new Set();
105
+ return (snap) => {
106
+ const name = snap.type;
107
+ const registered = registry && typeof name === 'string' && registry.viewTypes.has(name)
108
+ ? registry.getViewType(name)
109
+ : undefined;
110
+ const props = registered?.isStateModelLoaded
111
+ ? registered.stateModel.properties
112
+ : model.properties;
113
+ if (props !== cachedProps) {
114
+ cachedProps = props;
115
+ cachedKeys = new Set(Object.keys(props));
116
+ }
117
+ return cachedKeys;
118
+ };
119
+ }
83
120
  function classify(snap, { keys, passThrough }, known, out) {
84
121
  for (const [key, value] of Object.entries(snap)) {
85
122
  const spec = keys[key];
@@ -140,6 +177,11 @@ function legacyInitSnapshot(init, { keys }) {
140
177
  * are about to be rejected. `afterAttach` — reached only by a snapshot that won
141
178
  * — reports what the partition captured.
142
179
  *
180
+ * `registry` is the PluginManager. With it the partition classifies against
181
+ * the view type as registered, `extendViewType`'s added properties included;
182
+ * without it, against the model as wrapped here. `materialized` draws the line
183
+ * past which the blob is no longer persisted; see `LaunchInputOptions`.
184
+ *
143
185
  * ORDER: MST runs preprocessors in the reverse of the order they were added, so
144
186
  * this belongs on the chain BEFORE a view's own legacy-key preprocessor, where
145
187
  * it partitions the snapshot MST finally consumes rather than a key that remap
@@ -156,13 +198,14 @@ function legacyInitSnapshot(init, { keys }) {
156
198
  * without a word, and `ViewType.stateModel` and every `.properties`
157
199
  * introspection site break with it.
158
200
  */
159
- export function withLaunchInput(model, registration) {
160
- const known = new Set(Object.keys(model.properties));
201
+ export function withLaunchInput(model, registration, { registry, materialized }) {
202
+ const knownProps = knownPropsResolver(model, registry);
161
203
  const partitioned = model
162
204
  .preProcessSnapshot((snap) => {
163
205
  if (!isObject(snap)) {
164
206
  return snap;
165
207
  }
208
+ const known = knownProps(snap);
166
209
  const { init, ...rest } = snap;
167
210
  const out = {
168
211
  state: {},
@@ -207,20 +250,22 @@ export function withLaunchInput(model, registration) {
207
250
  reportMalformedRows(self, Object.keys(launch?.malformed ?? {}));
208
251
  },
209
252
  }))
210
- // `legacyInit` names the SPELLING of the snapshot this view was opened from,
211
- // and a saved one does not use it — so persisting the flag makes every later
212
- // restore report nesting the snapshot it is reading does not contain,
213
- // forever, since nothing clears it (`pendingLaunch` excludes the report
214
- // keys, so the launch autorun never runs). `unknown` and `malformed` stay:
215
- // those name content that was DISCARDED, which is still true of the saved
216
- // snapshot, and the blob is the only record of it.
253
+ // Whole while the view has yet to materialize, since the pending launch is
254
+ // then the only thing a reload can rebuild the view from, and gone once it
255
+ // has — the report keys with it, so a saved session never carries a
256
+ // typo report about content it no longer holds. `legacyInit` goes either
257
+ // way: it names the SPELLING of the snapshot this view was opened from,
258
+ // and the saved one does not use it, so persisting it would report nesting
259
+ // on every restore forever (`pendingLaunch` excludes the report keys, so
260
+ // no launch run ever clears it).
217
261
  .postProcessSnapshot((snap) => {
218
262
  const launch = snap[LAUNCH];
219
- if (!isObject(launch) || !launch.legacyInit) {
263
+ if (!isObject(launch)) {
220
264
  return snap;
221
265
  }
222
266
  const { legacyInit, ...kept } = launch;
223
- return Object.keys(kept).length
267
+ return Object.keys(kept).length &&
268
+ !materialized(snap)
224
269
  ? { ...snap, [LAUNCH]: kept }
225
270
  : Object.fromEntries(Object.entries(snap).filter(([key]) => key !== LAUNCH));
226
271
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/core",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0-beta.4",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 core libraries used by plugins",
6
6
  "keywords": [
@@ -46,6 +46,7 @@
46
46
  "./data_adapters/BaseAdapter/getAdapterId": "./esm/data_adapters/BaseAdapter/getAdapterId.js",
47
47
  "./data_adapters/BaseAdapter/stats": "./esm/data_adapters/BaseAdapter/stats.js",
48
48
  "./data_adapters/BaseAdapter/types": "./esm/data_adapters/BaseAdapter/types.js",
49
+ "./data_adapters/adapterSessionRefcount": "./esm/data_adapters/adapterSessionRefcount.js",
49
50
  "./data_adapters/dataAdapterCache": "./esm/data_adapters/dataAdapterCache.js",
50
51
  "./data_adapters/getFeatureAdapter": "./esm/data_adapters/getFeatureAdapter.js",
51
52
  "./data_adapters/getSequenceSubAdapter": "./esm/data_adapters/getSequenceSubAdapter.js",
@@ -145,6 +146,7 @@
145
146
  "./util/SvgCanvas": "./esm/util/SvgCanvas.js",
146
147
  "./util/TimeTraveller": "./esm/util/TimeTraveller.js",
147
148
  "./util/aborting": "./esm/util/aborting.js",
149
+ "./util/adapterConfigKey": "./esm/util/adapterConfigKey.js",
148
150
  "./util/adapterMetadata": "./esm/util/adapterMetadata.js",
149
151
  "./util/addRelativeUris": "./esm/util/addRelativeUris.js",
150
152
  "./util/analytics": "./esm/util/analytics.js",
@@ -185,6 +187,7 @@
185
187
  "./util/installClearHoverOnSurfaceMove": "./esm/util/installClearHoverOnSurfaceMove.js",
186
188
  "./util/installFetch": "./esm/util/installFetch.js",
187
189
  "./util/installInitAutorun": "./esm/util/installInitAutorun.js",
190
+ "./util/installPrerequisiteFetch": "./esm/util/installPrerequisiteFetch.js",
188
191
  "./util/io": "./esm/util/io/index.js",
189
192
  "./util/io/RemoteFileWithRangeCache": "./esm/util/io/RemoteFileWithRangeCache.js",
190
193
  "./util/isDataCurrent": "./esm/util/isDataCurrent.js",
@@ -200,10 +203,12 @@
200
203
  "./util/librpc": "./esm/util/librpc.js",
201
204
  "./util/localStorage": "./esm/util/localStorage.js",
202
205
  "./util/makeAssembly": "./esm/util/makeAssembly.js",
206
+ "./util/mst-reflection": "./esm/util/mst-reflection.js",
203
207
  "./util/mstUtils": "./esm/util/mstUtils.js",
204
208
  "./util/nanoid": "./esm/util/nanoid.js",
205
209
  "./util/numericUtils": "./esm/util/numericUtils.js",
206
210
  "./util/offscreenCanvasPonyfill": "./esm/util/offscreenCanvasPonyfill.js",
211
+ "./util/openViews": "./esm/util/openViews.js",
207
212
  "./util/paintLayer": "./esm/util/paintLayer.js",
208
213
  "./util/parseLineByLine": "./esm/util/parseLineByLine.js",
209
214
  "./util/progress": "./esm/util/progress.js",
@@ -259,10 +264,10 @@
259
264
  "@mui/icons-material": "^9.4.0",
260
265
  "@mui/material": "^9.4.0",
261
266
  "@mui/system": "^9.4.0",
262
- "@mui/x-data-grid": "^9.12.0",
267
+ "@mui/x-data-grid": "^9.13.0",
263
268
  "canvas-sequencer-ts": "^3.1.3",
264
269
  "compare-versions": "^6.1.1",
265
- "dompurify": "^3.4.14",
270
+ "dompurify": "^3.4.15",
266
271
  "generic-filehandle2": "^2.4.0",
267
272
  "idb": "^8.0.3",
268
273
  "mobx": "^7.0.3",
@@ -270,8 +275,8 @@
270
275
  "pako-esm2": "^2.0.2",
271
276
  "rxjs": "^7.8.2",
272
277
  "source-map-js": "^1.2.1",
273
- "@jbrowse/add-track-core": "^5.0.0-beta.2",
274
- "@jbrowse/render-core": "^5.0.0-beta.2"
278
+ "@jbrowse/add-track-core": "^5.0.0-beta.4",
279
+ "@jbrowse/render-core": "^5.0.0-beta.4"
275
280
  },
276
281
  "peerDependencies": {
277
282
  "react": ">=19.0.0",
@@ -1,19 +0,0 @@
1
- import type { Pin } from '../configuration/promotablePin.ts';
2
- import type { CheckboxMenuItem, RadioMenuItem } from './MenuTypes.ts';
3
- import type { RadioOption, SettingRowOptions } from './toggleMenuItems.ts';
4
- /** #menuBuilder promotableToggleItem | `checkboxItem` plus an apply-to-open-tracks pin */
5
- export declare function promotableToggleItem({ label, checked, onToggle, pin, ...opts }: {
6
- label: string;
7
- checked: boolean;
8
- onToggle: () => void;
9
- pin: Pin;
10
- } & SettingRowOptions): CheckboxMenuItem;
11
- /** #menuBuilder promotableRadioItem | `radioItem` plus an apply-to-open-tracks pin */
12
- export declare function promotableRadioItem({ label, checked, onClick, pin, ...opts }: {
13
- label: string;
14
- checked: boolean;
15
- onClick: () => void;
16
- pin?: Pin;
17
- } & SettingRowOptions): RadioMenuItem;
18
- /** #menuBuilder promotableRadioItems | `radioItems` plus a pin per option, from a factory over the value */
19
- export declare function promotableRadioItems<T extends string>(options: readonly RadioOption<T>[], current: T | undefined, setMode: (m: T) => void, pin: (value: T) => Pin): RadioMenuItem[];
@@ -1,91 +0,0 @@
1
- import { checkboxItem, radioItem, radioItems } from './toggleMenuItems.js';
2
- // Every builder here describes the trailing pin (`pin`) rather than
3
- // rendering it. That is what keeps this module — and therefore every state
4
- // model and menu file that calls it — free of React and of MUI's
5
- // ToggleButton/Tooltip. `menuItemAdornment.tsx` turns the description into
6
- // `PinAdornment` at the point the menu is drawn. See
7
- // reference/EAGER_BUNDLE.md; `menuItems.purity.test.ts` holds it.
8
- //
9
- // Each is the plain builder in `toggleMenuItems.ts` plus a pin, and each is
10
- // written that way — `checkboxItem` / `radioItems` build the row — so the
11
- // promotable and plain forms of a row can differ only by the pin.
12
- // A promotable setting as one native checkbox menu row: the value toggles the
13
- // track (inheriting native hover/sizing/keyboard), and a trailing pin applies
14
- // this value to every open track of the display type, offering the display-type
15
- // default as its snackbar's one action. Always shown so the capability is
16
- // discoverable.
17
- //
18
- // **The row itself is `checkboxItem`'s**, so the two builders can only ever
19
- // differ by the pin. They used to be two literals, and had already drifted: this
20
- // one accepted `helpText` and `keepMenuOpen` while silently dropping `disabled`
21
- // and `disabledHelpText`, so a promotable row could not be greyed out the way
22
- // its plain sibling could. Sharing the body is also what makes a future
23
- // change to what a checkbox row *is* reach both — the failure `checkboxItem`'s
24
- // own comment records is a menu regressing by omission.
25
- //
26
- // `keepMenuOpen` is left unset by default, so `CascadingMenu` keeps the row open
27
- // by its checkbox type: these rows only write a setting, users flip several in
28
- // one visit, and the menu is an observer so the ticks and pins move live. A row
29
- // whose click opens a dialog instead (colorBy's "Tag...") passes false.
30
- /** #menuBuilder promotableToggleItem | `checkboxItem` plus an apply-to-open-tracks pin */
31
- export function promotableToggleItem({ label, checked, onToggle, pin, ...opts }) {
32
- return {
33
- ...checkboxItem(label, checked, onToggle, opts),
34
- pin: { control: pin, label },
35
- };
36
- }
37
- // ONE radio row of a promotable-value group — the escape hatch, for a group the
38
- // plural builder below can't express. **Reach for `promotableRadioItems` first**:
39
- // every option in a group has to get a pin, and hand-naming the rows is what
40
- // leaves one without.
41
- //
42
- // The rows that genuinely need this: `pin` is optional here, for a row with no
43
- // single value to promote yet (the colorBy "Tag..." row before a tag is picked)
44
- // or a display whose slot isn't promotable at all (the shared colorBy menu on
45
- // gwas/variants); and the alignments size presets, whose group is gated row by
46
- // row (`needsContent`) and mixes in a non-promotable "Custom..." peer.
47
- //
48
- // **The row itself is `radioItem`'s**, for the reason `promotableToggleItem`'s
49
- // is `checkboxItem`'s: it was a second literal, and it had drifted the same way
50
- // its checkbox sibling had — naming `helpText`/`keepMenuOpen` by hand and so
51
- // silently dropping `disabled` and `disabledHelpText`, which the
52
- // alignments size presets have to bolt back on afterwards (`needsContent`).
53
- /** #menuBuilder promotableRadioItem | `radioItem` plus an apply-to-open-tracks pin */
54
- export function promotableRadioItem({ label, checked, onClick, pin, ...opts }) {
55
- return {
56
- ...radioItem(label, checked, onClick, opts),
57
- ...(pin && {
58
- pin: { control: pin, label },
59
- }),
60
- };
61
- }
62
- // A whole promotable radio group: `radioItems` plus one pin per option. The
63
- // plural form of the builder above, and the one to reach for on a group whose
64
- // options differ only in their value — which is all five of them today
65
- // (`heightMode`, `displayMode`, `showLabels`, `subfeatureLabels`,
66
- // `sashimiArcsMode`; the two label groups reach it through canvas's
67
- // `inlineRadioGroup`).
68
- //
69
- // **Every option in a group gets a pin, the `promotedBase` value included** —
70
- // once a non-base value is promoted, pinning the base back is the only per-value
71
- // way to undo it from its own row, and a radio group with one row silently
72
- // missing its trailing control reads as a bug. Building the group here is what
73
- // makes that structural instead of a rule to remember: `sashimiArcsMode`'s base
74
- // looked unpinnable precisely because each row had been named by hand. Prefer
75
- // this over `.map(promotableRadioItem)` whenever the pin is a function of the
76
- // option's value.
77
- //
78
- // `pin` is a factory rather than an array so it can't be a row short. Pass
79
- // `value => makePin(self, slot, value)`, or a model member of that shape where
80
- // the menu module is handed a duck-typed model (alignments).
81
- //
82
- // The rows are `radioItems`' own, so the promotable and plain groups can't drift
83
- // on anything but the pin. `radioItems` maps `options` in order, which is what
84
- // lets each row be paired back to the option that built it.
85
- /** #menuBuilder promotableRadioItems | `radioItems` plus a pin per option, from a factory over the value */
86
- export function promotableRadioItems(options, current, setMode, pin) {
87
- return radioItems(options, current, setMode).map((item, i) => {
88
- const { value, label } = options[i];
89
- return { ...item, pin: { control: pin(value), label } };
90
- });
91
- }