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

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 (110) hide show
  1. package/README.md +278 -250
  2. package/esm/PluginManager.js +1 -1
  3. package/esm/ReExports/publicUi.d.ts +1 -1
  4. package/esm/ReExports/publicUi.js +1 -1
  5. package/esm/ReExports/publicUtil.d.ts +1 -1
  6. package/esm/ReExports/publicUtil.js +1 -1
  7. package/esm/ReExports/workerNamespaceNames.js +3 -9
  8. package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
  9. package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
  10. package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
  11. package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
  12. package/esm/configuration/configurationSchema.d.ts +1 -2
  13. package/esm/configuration/configurationSchema.js +3 -8
  14. package/esm/configuration/configurationSlot.d.ts +0 -47
  15. package/esm/configuration/configurationSlot.js +4 -54
  16. package/esm/configuration/fillLocations.d.ts +14 -0
  17. package/esm/configuration/fillLocations.js +14 -0
  18. package/esm/configuration/fullConfSnapshot.d.ts +0 -15
  19. package/esm/configuration/fullConfSnapshot.js +0 -28
  20. package/esm/configuration/getConf.d.ts +4 -34
  21. package/esm/configuration/getConf.js +3 -38
  22. package/esm/configuration/index.d.ts +3 -4
  23. package/esm/configuration/index.js +3 -2
  24. package/esm/configuration/schemaRegistry.d.ts +1 -2
  25. package/esm/configuration/schemaRegistry.js +1 -2
  26. package/esm/configuration/slotFacade.d.ts +1 -16
  27. package/esm/configuration/slotFacade.js +1 -2
  28. package/esm/configuration/tabixShorthand.js +3 -3
  29. package/esm/configuration/types.d.ts +4 -16
  30. package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
  31. package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
  32. package/esm/pluggableElementTypes/DisplayType.js +3 -1
  33. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
  34. package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
  35. package/esm/rpc/RpcRegistry.d.ts +7 -1
  36. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  37. package/esm/rpc/coreRpcMethods.js +1 -0
  38. package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
  39. package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
  40. package/esm/svg/svgId.d.ts +1 -1
  41. package/esm/svg/svgId.js +1 -1
  42. package/esm/ui/LegendSwatchGlyph.js +26 -21
  43. package/esm/ui/MenuTypes.d.ts +5 -52
  44. package/esm/ui/SizeSliderRow.d.ts +1 -3
  45. package/esm/ui/SizeSliderRow.js +4 -6
  46. package/esm/ui/SvgColorLegend.d.ts +9 -3
  47. package/esm/ui/SvgColorLegend.js +74 -33
  48. package/esm/ui/colorScale.d.ts +64 -0
  49. package/esm/ui/colorScale.js +41 -0
  50. package/esm/ui/colors.d.ts +11 -0
  51. package/esm/ui/colors.js +7 -0
  52. package/esm/ui/index.d.ts +5 -7
  53. package/esm/ui/index.js +3 -13
  54. package/esm/ui/legendMenuItem.d.ts +5 -16
  55. package/esm/ui/legendMenuItem.js +3 -14
  56. package/esm/ui/legendSpec.d.ts +11 -5
  57. package/esm/ui/legendSpec.js +8 -11
  58. package/esm/ui/makeSizeMenu.d.ts +0 -6
  59. package/esm/ui/makeSizeMenu.js +6 -30
  60. package/esm/ui/measureLegendText.js +1 -1
  61. package/esm/ui/menuItemAdornment.d.ts +9 -19
  62. package/esm/ui/menuItemAdornment.js +10 -31
  63. package/esm/ui/menuItems.d.ts +2 -6
  64. package/esm/ui/menuItems.js +0 -4
  65. package/esm/ui/palette.js +2 -1
  66. package/esm/ui/toggleMenuItems.d.ts +6 -13
  67. package/esm/ui/toggleMenuItems.js +13 -29
  68. package/esm/util/colorRamp.d.ts +3 -3
  69. package/esm/util/colorRamp.js +2 -2
  70. package/esm/util/featureTransforms.d.ts +15 -0
  71. package/esm/util/featureTransforms.js +342 -0
  72. package/esm/util/freezeDeep.d.ts +8 -10
  73. package/esm/util/freezeDeep.js +8 -10
  74. package/esm/util/glyphNames.d.ts +8 -0
  75. package/esm/util/glyphNames.js +12 -0
  76. package/esm/util/index.d.ts +0 -1
  77. package/esm/util/index.js +0 -1
  78. package/esm/util/markEncoding.d.ts +76 -0
  79. package/esm/util/markEncoding.js +436 -0
  80. package/esm/util/markEncodingTypes.d.ts +331 -0
  81. package/esm/util/markEncodingTypes.js +8 -0
  82. package/esm/util/renderToStaticMarkup.js +1 -1
  83. package/esm/util/starFusion.d.ts +26 -0
  84. package/esm/util/starFusion.js +37 -0
  85. package/esm/util/trackConfigDelta.d.ts +4 -8
  86. package/esm/util/trackConfigDelta.js +2 -4
  87. package/esm/util/tracks.d.ts +7 -1
  88. package/esm/util/tracks.js +72 -7
  89. package/esm/util/types/index.d.ts +12 -4
  90. package/esm/util/whenViewSettled.d.ts +9 -0
  91. package/esm/util/whenViewSettled.js +12 -4
  92. package/package.json +13 -3
  93. package/esm/configuration/promotableDefaults.d.ts +0 -234
  94. package/esm/configuration/promotableDefaults.js +0 -395
  95. package/esm/configuration/promotablePin.d.ts +0 -65
  96. package/esm/configuration/promotablePin.js +0 -1
  97. package/esm/configuration/promotableResolve.d.ts +0 -108
  98. package/esm/configuration/promotableResolve.js +0 -117
  99. package/esm/configuration/promotableSlots.d.ts +0 -12
  100. package/esm/configuration/promotableSlots.js +0 -45
  101. package/esm/configuration/slotShape.d.ts +0 -12
  102. package/esm/configuration/slotShape.js +0 -84
  103. package/esm/ui/PinAdornment.d.ts +0 -5
  104. package/esm/ui/PinAdornment.js +0 -67
  105. package/esm/ui/SvgGradientLegend.d.ts +0 -26
  106. package/esm/ui/SvgGradientLegend.js +0 -26
  107. package/esm/ui/promotablePinCoverage.d.ts +0 -58
  108. package/esm/ui/promotablePinCoverage.js +0 -124
  109. package/esm/util/openDisplays.d.ts +0 -23
  110. package/esm/util/openDisplays.js +0 -79
@@ -1,395 +0,0 @@
1
- /**
2
- * @module
3
- * Session-wide "promoted defaults" for display-type config slots — the UI /
4
- * control layer over the read-time cascade in `promotableResolve.ts`, whose
5
- * `SlotResolution` every function here reads a field off. The session store
6
- * (`get/setDisplayTypeDefault`) holds the promoted value.
7
- */
8
- import { getSnapshot, isAlive } from '@jbrowse/mobx-state-tree';
9
- import { deepEqual } from '../util/deepEqual.js';
10
- import { getSession } from '../util/mstUtils.js';
11
- import { isObject } from '../util/objectUtils.js';
12
- import { openPromotableDisplays } from '../util/openDisplays.js';
13
- import { pluralize } from '../util/stringUtils.js';
14
- import { fullConfSnapshot } from './fullConfSnapshot.js';
15
- import { setConf } from './getConf.js';
16
- import { cascadeContextFor, isPromotableValue, resolveSlot, resolveSlotIn, storedSlotValue, } from './promotableResolve.js';
17
- import { promotableSlotNames } from './promotableSlots.js';
18
- import { isConfigurationModel } from './schemaTypes.js';
19
- /**
20
- * #api core/configuration
21
- * Whether this track has customized the slot (holds a non-default value of its
22
- * own) rather than following the display type's default. The correct "reset to
23
- * default" predicate for a promotable slot: comparing the resolved value to the
24
- * base instead reads as at-default for a track merely *following* a non-base
25
- * promoted default, so the reset control lights up on a no-op.
26
- *
27
- * `SLOT` is constrained the way `getConf`'s is, and a widened `self` switches
28
- * the check off (`HostChecksSlotNames`). A slot name the schema does not
29
- * declare throws at the first read (`getSlotDefinition`), and a declared but
30
- * non-promotable one throws in `resolveSlotIn`.
31
- */
32
- export function isSlotCustomized(self, slot) {
33
- return resolveSlot(self, slot).customized;
34
- }
35
- /**
36
- * #api core/configuration
37
- * The display's full config snapshot with every `promotable`
38
- * slot overwritten by its resolved value in place. For building a worker payload:
39
- * a promotable slot serializes as its raw inherit sentinel (`undefined`, since
40
- * they're all `maybe*` types), which the worker can't interpret — it has no
41
- * session to resolve against. This hands it concrete values instead, with no per-slot
42
- * bookkeeping, so adding a promotable worker-consumed slot needs no rpcProps
43
- * change and can't silently ship a sentinel. Main-thread only (the cascade
44
- * consults the session). Display-only promotable slots the worker never reads
45
- * (e.g. displayMode) are still excluded by the caller — resolving them here is a
46
- * harmless no-op since they're dropped anyway.
47
- *
48
- * The return type is branded (`ResolvedConfigSnapshot`) so a payload builder can
49
- * demand a snapshot that has been through here. The assertion below is the one
50
- * place the brand is applied, and it sits on the line after the resolve.
51
- */
52
- export function getConfigSnapshotWithPromotables(self) {
53
- // the unresolved walk: this is the one place allowed to snapshot a promotable
54
- // config, because `resolvePromotablesInto` is what resolves every such slot
55
- const snap = fullConfSnapshot(self.configuration);
56
- resolvePromotablesInto(cascadeContextFor(self), snap);
57
- return snap;
58
- }
59
- /**
60
- * The subsystem's one resolve loop: every promotable slot of `ctx.config`
61
- * written into `snap` in place, returning the slots whose value came from a
62
- * promoted default rather than from the config. Both serialization boundaries
63
- * are this function over a different context — a display state node for the
64
- * worker payload, a bare display config for the About dialog's copy.
65
- */
66
- function resolvePromotablesInto(ctx, snap) {
67
- const inherited = [];
68
- for (const slot of promotableSlotNames(ctx.config)) {
69
- const res = resolveSlotIn(ctx, slot);
70
- snap[slot] = res.value;
71
- if (res.inherited) {
72
- inherited.push(slot);
73
- }
74
- }
75
- return inherited;
76
- }
77
- /**
78
- * #api core/configuration
79
- * See {@link TrackConfigWithPromotables}.
80
- */
81
- export function getTrackConfigWithPromotables(session, trackConfig) {
82
- const config = structuredClone(getSnapshot(trackConfig));
83
- const fromDisplayTypeDefaults = [];
84
- const displayConfigs = trackConfig.displays;
85
- const displaySnaps = config.displays;
86
- // a config with no `displays` (an assembly, a plain customized About config)
87
- // has no promotable slot to resolve — every one of them is display-level
88
- if (Array.isArray(displayConfigs) && Array.isArray(displaySnaps)) {
89
- for (const [i, displayConfig] of displayConfigs.entries()) {
90
- const snap = displaySnaps[i];
91
- if (!isConfigurationModel(displayConfig) || !isObject(snap)) {
92
- continue;
93
- }
94
- // off the live config node, not off `snap` — the snapshot is what this
95
- // function *writes*, so the key it resolves against shouldn't depend on
96
- // what survived `stripDefault` on the way out. A display schema is
97
- // `explicitlyTyped`, so the node always carries `type`; reading the
98
- // snapshot's copy meant a display whose `type` ever stopped being emitted
99
- // got skipped whole, and a skipped display is a copied config that
100
- // silently isn't flattened. Same source `cascadeContextFor` reads.
101
- const displayType = displayConfig.type;
102
- if (typeof displayType === 'string') {
103
- const ctx = { config: displayConfig, displayType, defaults: session };
104
- for (const slot of resolvePromotablesInto(ctx, snap)) {
105
- fromDisplayTypeDefaults.push(`${displayType}.${slot}`);
106
- }
107
- }
108
- }
109
- }
110
- return { config, fromDisplayTypeDefaults };
111
- }
112
- /**
113
- * Open displays of this display's type, **one entry per track** — the set the
114
- * pin's click writes. Module-internal (exercised by promotableDefaults.test.ts);
115
- * not part of the public barrel.
116
- *
117
- * A track open in two views is two display *models* over one display *config* —
118
- * `TrackConfigurationReference` resolves both through the hydration cache
119
- * (ADR-031) — so the raw walk yields it twice. That is the ordinary case in a
120
- * breakpoint-split view, which shows the same track in both halves and is one of
121
- * the composite shapes `openPromotableDisplays` recurses into. The count reaches
122
- * the user in the snackbar and the set gets written, and both
123
- * go wrong on a duplicate: the toast offered to act on "2 tracks" over a single
124
- * track, and the second write was a no-op against a node the first had already
125
- * set.
126
- *
127
- * Keying on the config node rather than on `trackId` is what keeps this
128
- * cast-free: the node is a member of `ResolvableDisplay`, and within one display
129
- * type it is 1:1 with the track.
130
- */
131
- export function openTracksOfType(self) {
132
- // seeded with the clicked display so it is in the set by construction rather
133
- // than by the walk happening to reach it. A display the walk misses used to
134
- // cost nothing — the click wrote only the session default — and would now be
135
- // the whole of the click. A duplicate from the walk lands on the same config
136
- // key, so this adds no entry.
137
- const byTrack = new Map([
138
- [self.configuration, self],
139
- ]);
140
- for (const display of openPromotableDisplays(getSession(self))) {
141
- if (display.type === self.type) {
142
- byTrack.set(display.configuration, display);
143
- }
144
- }
145
- return [...byTrack.values()];
146
- }
147
- /**
148
- * Whether `value` is the current session default for `slot`. The live state the
149
- * pin's filled/outline reflects — a session-wide fact, so it reads the raw
150
- * promoted default rather than what this display resolves to (a customized track
151
- * can be showing something else entirely). The named form of the comparison
152
- * {@link makePin} inlines off a resolution it already holds. Module-internal
153
- * (exercised by promotableDefaults.test.ts); not part of the public barrel.
154
- */
155
- export function isPromotableDefault(self, slot, value) {
156
- return deepEqual(resolveSlot(self, slot).promoted, value);
157
- }
158
- /**
159
- * Write `value` into each display's own config for `slot`, so the track *holds*
160
- * it rather than resolving it through the cascade. The pin's click, over every
161
- * open track of the display type.
162
- *
163
- * **A track already showing `value` still has to be written**, which is why this
164
- * compares the *stored* value and not the resolved one: a follower stores
165
- * nothing and is showing `value` only by way of some promoted default, so
166
- * skipping it would leave it to move again the moment that default changed.
167
- * Comparing the stored value is also what lets a `jexl:` value answer "is this
168
- * already what we would write?" without being evaluated.
169
- *
170
- * Skips dead displays, since the caller supplies the list and MST throws on any
171
- * read or write to a destroyed node.
172
- */
173
- export function applySlotToOpenTracks(displays, slot, value) {
174
- for (const display of displays.filter(display => isAlive(display))) {
175
- if (!deepEqual(storedSlotValue(display.configuration, slot), value)) {
176
- setConf(display, slot, value);
177
- }
178
- }
179
- }
180
- /**
181
- * The pin's click: write `value` into every open track of the type, and offer
182
- * the promotion as a snackbar action. Applying to the tracks in front of the
183
- * user is the click they mean far more often, so it is the one that needs no
184
- * second click; a default outlives the tracks it was set for and governs every
185
- * track of the type opened later, so it is the escalation (ADR-048).
186
- *
187
- * The apply is one operation over *every* open track, not a labeled pair over
188
- * the tracks that differ and the tracks that follow. Overwriting a customized
189
- * track is the same write as filling in a follower, and the distinction the two
190
- * actions drew is not one the user has any reason to see.
191
- *
192
- * The snackbar outlives the click that raised it, so the promotion closes over
193
- * the display TYPE — a string — rather than reaching back through `self`. A
194
- * promoted default is keyed by display type and governs tracks opened later, so
195
- * it has nothing to do with the clicked display still being open; guarding on
196
- * `self` instead meant a user who applied a value, closed the track, then took
197
- * the offer got a silent no-op. What is still guarded is the session, which owns
198
- * the map the write lands in. ADR-048 has the decisions this does not change:
199
- * no track set and no apply/promote decision may be closed over.
200
- *
201
- * **Promoting the slot's base value clears the default instead of storing it.**
202
- * The cascade resolves the two identically, but a stored base was a third
203
- * state the rest of the subsystem could see: the Preferences inventory listed a
204
- * row reading "from X to X", and the base option's radio pin drew filled. Now
205
- * "Set as the default" on the base row is the per-value way to undo a promoted
206
- * default, and a filled pin only ever means a non-base value is promoted. The
207
- * base is read here rather than at click time because it is a schema literal
208
- * and cannot change.
209
- */
210
- function applyAndOfferDefault(self, slot, value) {
211
- const session = getSession(self);
212
- const displayType = self.type;
213
- const { base } = resolveSlot(self, slot);
214
- const promoted = deepEqual(value, base) ? undefined : value;
215
- const open = openTracksOfType(self);
216
- applySlotToOpenTracks(open, slot, value);
217
- session.notify(`Applied to ${open.length} open ${pluralize(open.length, 'track')}`, 'info', {
218
- name: 'Set as the default',
219
- onClick: () => {
220
- if (isAlive(session)) {
221
- session.setDisplayTypeDefault(displayType, slot, promoted);
222
- }
223
- },
224
- });
225
- }
226
- /**
227
- * A value pin's click when its value is already the promoted default: clear
228
- * it, touching no track. The open tracks hold their values because the user
229
- * applied them, so reverting them here would make a toggle into a bulk discard.
230
- */
231
- function clearDefault(self, slot) {
232
- const session = getSession(self);
233
- session.setDisplayTypeDefault(self.type, slot, undefined);
234
- session.notify('Cleared the default', 'info');
235
- }
236
- /**
237
- * #api core/configuration
238
- * The pin on a radio or slider row: "apply this value to every open track of
239
- * this display type", and — via the snackbar it raises — "keep it as the
240
- * default for the ones opened later".
241
- *
242
- * `value` chooses between the subsystem's two meanings, which are otherwise
243
- * identical:
244
- *
245
- * - **Give it** for a per-value pin — "make *compact* the default" —
246
- * independent of what the track currently shows. Use on an always-visible pin
247
- * so it can never promote a meaningless value, and so two rows sharing one
248
- * slot (sashimi `'down'` vs `'auto'`) stay independent.
249
- * - **Omit it** for "whatever I'm showing", resolved through the cascade. Use for
250
- * a continuous setting where no fixed on-value makes sense (wiggle point size,
251
- * arc line width).
252
- *
253
- * A checkbox row takes neither: {@link makeTogglePin}.
254
- *
255
- * One function with an optional argument, rather than the two exported builders
256
- * it replaces — a per-value one and a `…CurrentValue…` one, the second of which
257
- * was exactly the first applied to `resolveSlot(self, slot).value`. The pair was
258
- * one function plus a doc section explaining which name to reach for; omitting
259
- * the argument now says what the longer name said.
260
- */
261
- export function makePin(self, slot, ...value) {
262
- // One walk of the cascade feeds both halves. The value-omitted form's
263
- // on-value IS the settled value, and `active` compares against the raw
264
- // promoted default of that same resolution — taking the two from separate
265
- // `resolveSlot` calls read as though they could disagree, and cost a second
266
- // walk per pin on a menu that builds a dozen of them.
267
- const res = resolveSlot(self, slot);
268
- // rest-tuple, not `value?: unknown`: the promote-current case has to stay
269
- // distinguishable from an explicit `undefined` rather than collapsing into it.
270
- const onValue = value.length ? value[0] : res.value;
271
- // The value-omitted form can't fail this — the cascade only ever settles on a
272
- // usable value — so this is entirely about a caller-supplied one. An on-value
273
- // the cascade would refuse builds a pin that is inert *and* silent: clicking
274
- // it stores a key `resolveSlotIn` then drops, so no track moves and the pin
275
- // draws outline forever. The reachable mistakes are the inherit sentinel
276
- // itself (`makePin(self, slot, undefined)`, which additionally reads as the
277
- // default the moment nothing is promoted, so it draws *filled* and does
278
- // nothing), a non-finite number, and a value outside a `maybeStringEnum`'s
279
- // vocabulary. The `value` parameter's type catches those wherever the slot
280
- // resolves to a real value type; what is left for runtime is the slot whose
281
- // schema widened to `any` — a `frozen`/`maybeFrozen` one by design, or a
282
- // display whose config model was not narrowed. Same bargain `ConfigSlot`
283
- // strikes over `promotedBase`, which is this gate at the other end.
284
- if (!isPromotableValue(self.configuration, slot, onValue)) {
285
- throw new Error(`cannot pin ${JSON.stringify(onValue)} as the default for config slot "${slot}": the cascade refuses it, so the pin could never light up`);
286
- }
287
- // `isPromotableDefault` off the resolution already in hand — same comparison,
288
- // same `deepEqual`, no second walk.
289
- const active = deepEqual(res.promoted, onValue);
290
- return {
291
- kind: 'value',
292
- slot,
293
- onValue,
294
- active,
295
- toggle: () => {
296
- if (active) {
297
- clearDefault(self, slot);
298
- }
299
- else {
300
- applyAndOfferDefault(self, slot, onValue);
301
- }
302
- },
303
- };
304
- }
305
- /**
306
- * #api core/configuration
307
- * The pin on a checkbox row: the row's own checkbox, acting on every open track
308
- * of the display type. `active` mirrors the row, so the pin draws filled
309
- * exactly when the box is ticked; a click flips the row's state on every open
310
- * track and offers the new state as the display type's default. It never
311
- * clears a default the way {@link makePin}'s filled pin does — flipping back
312
- * and taking the offer promotes the other value, and promoting the base value
313
- * clears the default (`applyAndOfferDefault`).
314
- *
315
- * Replaces a symmetric `makePin(self, slot)` on these rows, which carried the
316
- * row's current state: beside an unchecked box that applied *off* everywhere
317
- * and visibly did nothing. It also replaces the per-value `makePin` a checkbox
318
- * row over a shared enum slot used to carry, which gave two checkbox rows in
319
- * one submenu two different pins: one filled when its value was promoted and
320
- * clearing on a second click, the other filled when the box was ticked and
321
- * flipping. With `states`, a checkbox row is always the toggle kind.
322
- */
323
- export function makeTogglePin(self, slot,
324
- // rest-tuple, like `makePin`'s value: a boolean slot may omit the states, and
325
- // any other slot has to name them, so the type says which without a runtime
326
- // check that could only fire on a schema widened to `any`
327
- ...states) {
328
- const current = resolveSlot(self, slot).value;
329
- const [given] = states;
330
- if (!given && typeof current !== 'boolean') {
331
- throw new Error(`cannot build a toggle pin over config slot "${slot}" without naming its on/off states: it resolves to ${JSON.stringify(current)}, not a boolean`);
332
- }
333
- const { on, off } = given ?? { on: true, off: false };
334
- for (const state of [on, off]) {
335
- if (!isPromotableValue(self.configuration, slot, state)) {
336
- throw new Error(`cannot toggle config slot "${slot}" to ${JSON.stringify(state)}: the cascade refuses it, so the pin could never apply it`);
337
- }
338
- }
339
- const active = deepEqual(current, on);
340
- const onValue = active ? off : on;
341
- return {
342
- kind: 'toggle',
343
- slot,
344
- onValue,
345
- active,
346
- toggle: () => {
347
- applyAndOfferDefault(self, slot, onValue);
348
- },
349
- };
350
- }
351
- /**
352
- * #api core/configuration
353
- * Effective differences a track following the default inherits from session-wide
354
- * defaults, one per promotable slot whose inherited value differs from its schema
355
- * default. Drives the track-selector "affected by a session default" badge.
356
- */
357
- export function getDisplayTypeDefaultChanges(self) {
358
- const changes = [];
359
- for (const slot of promotableSlotNames(self.configuration)) {
360
- const res = resolveSlot(self, slot);
361
- if (res.inherited) {
362
- changes.push({
363
- path: [slot],
364
- // a cascade value is `unknown` here but JSON by contract — it has to
365
- // survive `postMessage` to a worker. Asserted per field rather than on
366
- // the whole entry, so `path` stays type-checked.
367
- from: res.base,
368
- to: res.value,
369
- });
370
- }
371
- }
372
- return changes;
373
- }
374
- /**
375
- * #api core/configuration
376
- * Clear the named promoted defaults for this display type, so every track
377
- * following one reverts to its own config value. Backs the badge's "clear
378
- * session default" action, which passes the slots it actually listed
379
- * (`getDisplayTypeDefaultChanges`).
380
- *
381
- * **`slots` is required, and an all-slots default is not the convenience it
382
- * looks like.** It reaches further than any list a dialog can have shown: a
383
- * promoted default the track *customized* over is `inherited: false` and so
384
- * appears in no row, yet still governs sibling tracks — so clearing it from a
385
- * dialog that never showed
386
- * it moves tracks other than the one whose badge was clicked. Clearing every
387
- * promoted default at once is a preferences-scope action, and Preferences →
388
- * "Reset to defaults" is where it lives (`clearPreferenceOverrides`).
389
- */
390
- export function clearPromotedDefaults(self, slots) {
391
- const session = getSession(self);
392
- for (const slot of slots) {
393
- session.setDisplayTypeDefault(self.type, slot, undefined);
394
- }
395
- }
@@ -1,65 +0,0 @@
1
- /**
2
- * Shared by both pin kinds: which promotable slot the pin writes, whether it
3
- * draws filled, and the one click it takes.
4
- *
5
- * `slot` is here so a *built menu* can be asked which promotable slots it offers
6
- * a pin for, which is the only way that question has an answer: declaring
7
- * `promotedBase` is a schema fact and the pin is a menu fact, and a display that
8
- * inherits the slot but never builds a row has a slot nothing can ever promote,
9
- * silently (`promotableSlotsWithoutPin`, guarded by
10
- * `products/jbrowse-web/src/tests/PromotablePinCoverage.test.ts`).
11
- *
12
- * **`toggle` rather than an `apply`/`clear` pair**, which was tried and dropped:
13
- * the sole renderer is a MUI `ToggleButton` whose `onChange` means exactly
14
- * "flip", so splitting it adds a member *and* a branch at the one call site that
15
- * never needed one. `active` is already public for a caller that wants to state
16
- * a direction.
17
- *
18
- * Lives here, alone and with no imports, rather than beside `makePin` in
19
- * `promotableDefaults.ts`: the menu types describe a pin without building one,
20
- * and `MenuTypes.ts` taking this from that module gave a React-free type file a
21
- * type closure of 374 files. See `agent-docs/ideas/barrels-block-extraction.md`
22
- * and `scripts/moduleClosure.ts`.
23
- */
24
- interface PinBase {
25
- slot: string;
26
- /** what `toggle` writes into every open track of the display type */
27
- onValue: unknown;
28
- active: boolean;
29
- toggle: () => void;
30
- }
31
- /**
32
- * #api core/configuration
33
- * The pin on a radio or slider row, built by {@link makePin}: `onValue` is the
34
- * row's own value, `active` means that value is the display type's promoted
35
- * default, and `toggle` on an outline pin applies the value to every open track
36
- * and offers it as the default, while on a filled pin it clears that default
37
- * and touches no track (`applyAndOfferDefault` / `clearDefault`).
38
- */
39
- export interface ValuePin extends PinBase {
40
- kind: 'value';
41
- }
42
- /**
43
- * #api core/configuration
44
- * The pin on a checkbox row, built by {@link makeTogglePin}: the row's own
45
- * checkbox over every open track of the display type. `active` mirrors the row,
46
- * `onValue` is the state a click applies (the row's opposite), and `toggle`
47
- * applies it everywhere and offers it as the default. It never clears a default
48
- * on its own — promoting the slot's base value is what clears one.
49
- */
50
- export interface TogglePin extends PinBase {
51
- kind: 'toggle';
52
- }
53
- /**
54
- * #api core/configuration
55
- * The "apply this to every open track of this type" affordance on a menu row —
56
- * the trailing `PushPin`, bundled so the row consumes it as one prop.
57
- *
58
- * `kind` is what the adornment words itself from and what the row builders
59
- * check: a checkbox row takes a {@link TogglePin}, a radio or slider row a
60
- * {@link ValuePin}. The two used to be told apart by `typeof onValue ===
61
- * 'boolean'`, which let a value pin over a boolean slot compile and read as a
62
- * toggle while clearing on its second click.
63
- */
64
- export type Pin = ValuePin | TogglePin;
65
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,108 +0,0 @@
1
- import type { AnyConfigurationModel } from './types.ts';
2
- import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
3
- /**
4
- * Everything the cascade needs to read a promotable slot: the display type it
5
- * keys the session-wide default on, and the config holding the track's own
6
- * value. Every entry point takes this — `resolveConf`, the control builders,
7
- * the worker snapshot, the badge diff — so each can take a display state node
8
- * directly rather than through a cast.
9
- *
10
- * Read-only, and there is no write-capable variant. A `PromotableDisplay` used
11
- * to exist for the one member the subsystem wrote (`setIgnorePromotedDefaults`,
12
- * the received-session opt-out); with that flag gone the subsystem writes
13
- * nothing to a display but a config slot, which `setConf` reaches through
14
- * `configuration`.
15
- *
16
- * **`CONF` is a parameter rather than always `AnyConfigurationModel` because
17
- * intersecting is not narrowing.** A caller wanting the cascade's members *and*
18
- * a concrete schema — a mixin casting to reach its host — writes
19
- * `ResolvableDisplay<XConfigModel>`. Spelling it
20
- * `ResolvableDisplay & { configuration: XConfigModel }` instead reads identical
21
- * and silently does the opposite: the intersected `configuration` is
22
- * `AnyConfigurationModel & XConfigModel`, `ConfigurationSchemaForModel` infers
23
- * the widened brand off it, and every slot name typechecks again. Two mixins
24
- * shipped that spelling and only a sabotage found it.
25
- */
26
- export type ResolvableDisplay<CONF extends AnyConfigurationModel = AnyConfigurationModel> = IStateTreeNode & {
27
- type: string;
28
- configuration: CONF;
29
- };
30
- /**
31
- * Where the session-wide tier of the cascade is read from. Narrowed to the one
32
- * method so the resolver doesn't depend on the whole session type — and exported
33
- * for the same reason, so an entry point taking the store directly
34
- * (`getTrackConfigWithPromotables`) can ask for what it uses instead of for a
35
- * whole session a test then has to fake.
36
- */
37
- export interface PromotedDefaultStore {
38
- getDisplayTypeDefault: (displayType: string, slot: string) => unknown;
39
- }
40
- /**
41
- * The cascade's inputs, stated directly rather than read off a display state
42
- * node. `ResolvableDisplay` is the usual way to supply them (see
43
- * `cascadeContextFor`), but nothing here *needs* a state node: the About
44
- * dialog's "Copy config" resolves a track's display configs directly, which is
45
- * what lets it answer "what would this render as" for a track that was never
46
- * opened. Reaching for the open display instead buys nothing — its
47
- * `configuration` is the same node, and its `type` is that config's own.
48
- */
49
- export interface CascadeContext {
50
- config: AnyConfigurationModel;
51
- displayType: string;
52
- defaults: PromotedDefaultStore;
53
- }
54
- export declare function cascadeContextFor(self: ResolvableDisplay): CascadeContext;
55
- /**
56
- * What the slot literally holds, unevaluated — a stray `jexl:` string yields the
57
- * raw string rather than running it. Both callers ask "is the slot set, and to
58
- * what kind of thing?", which has to be answerable with no feature context.
59
- *
60
- * Deliberately not `readConfObject`: for a promotable slot this is the whole
61
- * stored read, and both of that reader's extra behaviors are unwanted here — it
62
- * evaluates a `jexl:` string (which `isUsableValue` refuses anyway) and it
63
- * snapshots an MST-node value, which a `maybe*` slot never holds.
64
- */
65
- export declare function storedSlotValue(config: AnyConfigurationModel, slot: string): unknown;
66
- /**
67
- * Whether `value` could actually become this slot's promoted default — the same
68
- * `isUsableValue` gate the cascade puts both tiers through, asked ahead of time
69
- * about a value a caller is about to offer as a pin's on-value.
70
- *
71
- * Exists because a pin built over a value the gate refuses is inert in a way
72
- * nothing reports: `applyAndOfferDefault` writes it to the session store happily,
73
- * `resolveSlotIn` then drops it and every track keeps resolving to the tier
74
- * below, and `isPromotableDefault` compares the *raw* stored value, so the pin
75
- * draws outline forever while a dead key sits in the user's localStorage. See
76
- * `makePin`, which is the one caller.
77
- */
78
- export declare function isPromotableValue(config: AnyConfigurationModel, slot: string, value: unknown): boolean;
79
- /**
80
- * The outcome of walking the cascade for one slot: the two tiers that fed it,
81
- * whether the track customized it, and the settled value.
82
- *
83
- * There is deliberately **no callback case** — a `jexl:` value fails
84
- * `isUsableValue` like any other unusable value and degrades to the inherited
85
- * one, so `value` is always readable. See `isUsableValue`.
86
- */
87
- export interface SlotResolution {
88
- /** value a track following the default shows with nothing promoted (CSS `initial`) */
89
- base: unknown;
90
- /** the raw session-wide promoted default, if any */
91
- promoted: unknown;
92
- /** track holds its own value rather than following the default */
93
- customized: boolean;
94
- /**
95
- * the value came from the session-wide tier — the track follows the default
96
- * *and* that default moves it off `base`. The "this display picked something
97
- * up from the session" predicate, for the badge diff and the About dialog's
98
- * copy-config note. A field rather than a `!customized && !deepEqual(value,
99
- * base)` at each call site, because getting either half wrong is silent:
100
- * dropping `customized` reports a customized track as inheriting, dropping
101
- * the `base` compare flags a promoted default that changes nothing.
102
- */
103
- inherited: boolean;
104
- /** the final cascaded value (never the `undefined` inherit sentinel) */
105
- value: unknown;
106
- }
107
- export declare function resolveSlot(self: ResolvableDisplay, slot: string): SlotResolution;
108
- export declare function resolveSlotIn(ctx: CascadeContext, slot: string): SlotResolution;