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

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 (115) hide show
  1. package/README.md +42 -3
  2. package/esm/BaseFeatureWidget/SequenceFeatureDetails/test_data/NCDN.d.ts +2 -2
  3. package/esm/PluginLoader.d.ts +1 -0
  4. package/esm/PluginLoader.js +52 -8
  5. package/esm/PluginManager.d.ts +70 -17
  6. package/esm/PluginManager.js +151 -20
  7. package/esm/ReExports/modules.js +3 -53
  8. package/esm/ReExports/publicUtil.d.ts +1 -1
  9. package/esm/ReExports/publicUtil.js +1 -1
  10. package/esm/ReExports/sharedModules.d.ts +96 -0
  11. package/esm/ReExports/sharedModules.js +59 -0
  12. package/esm/ReExports/uiStub.d.ts +1 -0
  13. package/esm/ReExports/uiStub.js +13 -0
  14. package/esm/ReExports/workerModules.d.ts +2 -0
  15. package/esm/ReExports/workerModules.js +10 -0
  16. package/esm/configuration/configurationSchema.d.ts +5 -38
  17. package/esm/configuration/configurationSchema.js +53 -40
  18. package/esm/configuration/promotableDefaults.js +12 -6
  19. package/esm/configuration/promotableResolve.d.ts +1 -1
  20. package/esm/configuration/promotableResolve.js +1 -1
  21. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.d.ts +12 -0
  22. package/esm/data_adapters/BaseAdapter/BaseFeatureDataAdapter.js +39 -0
  23. package/esm/data_adapters/BaseAdapter/featureDensity.d.ts +29 -0
  24. package/esm/data_adapters/BaseAdapter/featureDensity.js +17 -0
  25. package/esm/data_adapters/BaseAdapter/index.d.ts +1 -0
  26. package/esm/data_adapters/BaseAdapter/index.js +1 -0
  27. package/esm/data_adapters/BaseAdapter/stats.d.ts +33 -1
  28. package/esm/data_adapters/BaseAdapter/stats.js +52 -13
  29. package/esm/data_adapters/BaseAdapter/types.d.ts +1 -0
  30. package/esm/pluggableElementTypes/DisplayType.d.ts +9 -10
  31. package/esm/pluggableElementTypes/DisplayType.js +43 -8
  32. package/esm/pluggableElementTypes/ViewType.d.ts +25 -2
  33. package/esm/pluggableElementTypes/ViewType.js +75 -2
  34. package/esm/pluggableElementTypes/extendElementType.js +8 -3
  35. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +41 -0
  36. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +74 -1
  37. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +21 -0
  38. package/esm/pluggableElementTypes/models/BaseTrackModel.js +37 -2
  39. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +49 -0
  40. package/esm/pluggableElementTypes/models/BaseViewModel.js +39 -0
  41. package/esm/pluggableElementTypes/models/assertDisplayContract.d.ts +10 -7
  42. package/esm/pluggableElementTypes/models/assertDisplayContract.js +23 -30
  43. package/esm/rpc/RpcRegistry.d.ts +10 -0
  44. package/esm/rpc/byteBudget.d.ts +9 -0
  45. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  46. package/esm/rpc/coreRpcMethods.js +1 -0
  47. package/esm/rpc/methods/CoreGetFeatureDensity.d.ts +11 -0
  48. package/esm/rpc/methods/CoreGetFeatureDensity.js +22 -0
  49. package/esm/svg/svgReady.d.ts +1 -0
  50. package/esm/ui/CascadingMenu.js +30 -8
  51. package/esm/ui/LoadingOverlay.d.ts +14 -0
  52. package/esm/ui/LoadingOverlay.js +1 -1
  53. package/esm/ui/MenuTypes.d.ts +11 -2
  54. package/esm/ui/MenuTypes.js +3 -0
  55. package/esm/ui/PaletteContext.d.ts +11 -4
  56. package/esm/ui/PaletteContext.js +34 -5
  57. package/esm/ui/PinAdornment.js +12 -6
  58. package/esm/ui/index.d.ts +1 -1
  59. package/esm/ui/index.js +1 -1
  60. package/esm/ui/launchViewMenu.js +7 -1
  61. package/esm/ui/legendMenuItem.d.ts +5 -5
  62. package/esm/ui/legendMenuItem.js +5 -5
  63. package/esm/ui/makeSizeMenu.js +1 -1
  64. package/esm/ui/menuItems.d.ts +1 -1
  65. package/esm/ui/menuItems.js +1 -1
  66. package/esm/ui/promotableMenuItems.js +8 -6
  67. package/esm/ui/promotablePinCoverage.js +2 -1
  68. package/esm/ui/toggleMenuItems.d.ts +1 -3
  69. package/esm/util/addAndShowTrack.d.ts +1 -1
  70. package/esm/util/addAndShowTrack.js +5 -1
  71. package/esm/util/addTrackFromWidget.js +2 -2
  72. package/esm/util/cacheBudgets.d.ts +22 -19
  73. package/esm/util/cacheBudgets.js +22 -19
  74. package/esm/util/createStopTokenRotation.d.ts +1 -0
  75. package/esm/util/createStopTokenRotation.js +7 -27
  76. package/esm/util/fetchContext.d.ts +2 -2
  77. package/esm/util/fetchContext.js +2 -2
  78. package/esm/util/fetchPhases.d.ts +9 -9
  79. package/esm/util/hooks.d.ts +8 -0
  80. package/esm/util/hooks.js +27 -0
  81. package/esm/util/index.d.ts +2 -3
  82. package/esm/util/index.js +2 -3
  83. package/esm/util/installFetch.d.ts +49 -23
  84. package/esm/util/installFetch.js +49 -21
  85. package/esm/util/installInitAutorun.d.ts +8 -8
  86. package/esm/util/installInitAutorun.js +15 -15
  87. package/esm/util/lazyAfterAttach.d.ts +13 -0
  88. package/esm/util/lazyAfterAttach.js +31 -0
  89. package/esm/util/legendCandidates.d.ts +50 -0
  90. package/esm/util/legendCandidates.js +87 -0
  91. package/esm/util/mstUtils.d.ts +12 -1
  92. package/esm/util/mstUtils.js +20 -2
  93. package/esm/util/pluginStore.d.ts +1 -0
  94. package/esm/util/pluginStore.js +8 -1
  95. package/esm/util/range.d.ts +13 -9
  96. package/esm/util/range.js +17 -8
  97. package/esm/util/rxjs.d.ts +16 -0
  98. package/esm/util/rxjs.js +49 -0
  99. package/esm/util/tabix.d.ts +10 -4
  100. package/esm/util/tabix.js +11 -5
  101. package/esm/util/tracks.d.ts +48 -0
  102. package/esm/util/tracks.js +192 -66
  103. package/esm/util/types/index.d.ts +48 -5
  104. package/esm/util/types/index.js +3 -0
  105. package/esm/util/unknownSnapshotKeys.d.ts +36 -0
  106. package/esm/util/unknownSnapshotKeys.js +69 -0
  107. package/esm/util/viewStatus.d.ts +23 -0
  108. package/esm/util/viewStatus.js +17 -0
  109. package/esm/util/whenViewSettled.d.ts +17 -4
  110. package/esm/util/whenViewSettled.js +19 -6
  111. package/esm/util/withLaunchInput.d.ts +123 -0
  112. package/esm/util/withLaunchInput.js +228 -0
  113. package/package.json +12 -9
  114. package/esm/util/applyInitSettings.d.ts +0 -79
  115. package/esm/util/applyInitSettings.js +0 -91
@@ -4,10 +4,10 @@
4
4
  * remembered.
5
5
  *
6
6
  * The shared `installFetch` skeleton takes this shape, and so does the LGV
7
- * global family (`installGlobalFetchAutorun`, over `FetchMixin.runFetch`). It
8
- * was the same contract declared twice, with the same three rules explained
9
- * twice, which is a drift axis over rules whose whole value is being the same
10
- * everywhere.
7
+ * global family (`installGlobalFetchAutorun`, a declaration over that same
8
+ * skeleton). It was the same contract declared twice, with the same three rules
9
+ * explained twice, which is a drift axis over rules whose whole value is being
10
+ * the same everywhere.
11
11
  *
12
12
  * `TCtx` is what a family hands its `run`: a `FetchContext` for most of them, a
13
13
  * `ComparativeFetchContext` (that plus adapter config and refName rename) for
@@ -56,11 +56,11 @@ export interface FetchPhases<TArgs, TResult, TCtx> {
56
56
  * **Nothing it reads is tracked**, so the payload it assembles cannot widen
57
57
  * the autorun's dependency set the way calling `rpcProps()` in the body would.
58
58
  * Being async is not what buys that — `run` is *called* synchronously, so its
59
- * own prefix down to its first `await` runs wherever the caller was. Each
60
- * family arranges it: the global one reaches `run` through
61
- * `FetchMixin.runFetch`, an MST flow and so an action, which MobX runs
62
- * untracked; the comparative one calls its lifecycle inside `untracked`
63
- * because it has no action to hide behind.
59
+ * own prefix down to its first `await` runs wherever the caller was. The
60
+ * `installFetch` skeleton calls it inside `untracked`, which covers the
61
+ * global and comparative families with it; the per-region family reaches its
62
+ * work through `FetchMixin.runFetch`, an MST flow and so an action, which
63
+ * MobX runs untracked.
64
64
  */
65
65
  run: (args: TArgs, ctx: TCtx) => Promise<TResult | undefined>;
66
66
  /**
@@ -20,6 +20,14 @@ export declare function useWidthSetter(view: {
20
20
  * "avoiding recreating the ref contents" pattern.
21
21
  */
22
22
  export declare function useCreateOnce<T>(create: () => T): T;
23
+ /**
24
+ * `useCreateOnce` for an asynchronous factory: returns undefined until
25
+ * `create` resolves, and rethrows a rejection from render. The value can
26
+ * resolve after the component is gone — a caller whose value is expensive to
27
+ * leak should use product-core's `useAsyncEngineLifecycle`, which discards
28
+ * that late arrival.
29
+ */
30
+ export declare function useCreateOnceAsync<T>(create: () => Promise<T>): T | undefined;
23
31
  /**
24
32
  * Run `cleanup` when the component *really* unmounts.
25
33
  *
package/esm/util/hooks.js CHANGED
@@ -112,6 +112,33 @@ export function useCreateOnce(create) {
112
112
  ref.current ??= { value: create() };
113
113
  return ref.current.value;
114
114
  }
115
+ /**
116
+ * `useCreateOnce` for an asynchronous factory: returns undefined until
117
+ * `create` resolves, and rethrows a rejection from render. The value can
118
+ * resolve after the component is gone — a caller whose value is expensive to
119
+ * leak should use product-core's `useAsyncEngineLifecycle`, which discards
120
+ * that late arrival.
121
+ */
122
+ export function useCreateOnceAsync(create) {
123
+ const started = useRef(false);
124
+ const [state, setState] = useState();
125
+ useEffect(() => {
126
+ if (!started.current) {
127
+ started.current = true;
128
+ create().then(value => {
129
+ setState({ value });
130
+ }, (error) => {
131
+ setState({ error });
132
+ });
133
+ }
134
+ // create is read on the first effect only, as in useCreateOnce
135
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
136
+ }, []);
137
+ if (state && 'error' in state) {
138
+ throw state.error;
139
+ }
140
+ return state?.value;
141
+ }
115
142
  /**
116
143
  * Run `cleanup` when the component *really* unmounts.
117
144
  *
@@ -3,7 +3,7 @@ export { capitalizeFirst, pluralize, shorten, shorten2, truncateMiddle, } from '
3
3
  export { formatBytes } from './formatBytes.ts';
4
4
  export { getFillProps, getStrokeProps, stripAlpha } from './svgColorProps.ts';
5
5
  export { fetchAndMaybeUnzip, fetchAndMaybeUnzipText, isGzip, } from './fetchAndMaybeUnzip.ts';
6
- export { type AbstractDisplayModel, type AbstractMenuManager, type AbstractRootModel, type AbstractSessionModel, type AbstractTrackModel, type AbstractViewContainer, type AbstractViewModel, type AnimationMode, type AnyReactComponentType, type AppRootModel, type AssemblyHost, type AssemblyManager, type AugmentedRegion, AuthNeededError, type BasePlugin, type BlobLocation, type ClassReturnedBy, type ConnectionInstance, type DialogComponentType, type DialogHost, type DrawerPosition, type FileHandleLocation, type FileLocation, type InstanceTypeRestrictive, type JBrowsePlugin, type JBrowsePluginVersion, type LocalPathLocation, type NoAssemblyRegion, type NotificationLevel, type NotificationSink, type PaletteHost, type PreBlobLocation, type PreFileHandleLocation, type PreFileLocation, type PreLocalPathLocation, type PreUriLocation, type Region, type RenderingServices, type RpcCaller, type RpcHost, type RootModelWithInternetAccounts, type SelectionContainer, type SessionServices, type SessionWithAddAssembly, type SessionWithAddSessionTrack, type SessionWithPublishTrackConf, type SessionWithConfigEditing, type SessionWithConnectionEditing, type SessionWithConnections, type SessionWithDeleteTrackConf, type SessionWithDrawerWidgets, type SessionWithFocusedViewAndDrawerWidgets, type SessionWithPermanentPlugins, type SessionWithSessionPlugins, type SessionWithShareURL, type SessionWithViewReplacement, type SessionWithWidgets, type SnackAction, type TrackActionView, type TrackCatalog, type TrackContainer, type TrackViewModel, type TypeTestedByPredicate, type UriLocation, type Widget, type WidgetMap, addOrReplaceView, canReplaceView, isAbstractMenuManager, isAppRootModel, isAuthNeededException, isBlobLocation, isDisplayModel, isFileHandleLocation, isLocalPathLocation, isRootModelWithInternetAccounts, isSelectionContainer, isSessionModel, isSessionServices, isSessionModelWithConfigEditing, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddAssembly, isSessionWithAddSessionTrack, isSessionWithAddTracks, isSessionWithPublishTrackConf, isSessionWithDeleteTrackConf, isSessionWithPermanentPlugins, isSessionWithSessionPlugins, isSessionWithSessionTracks, isSessionWithShareURL, isSessionWithViewReplacement, isTrackModel, isTrackViewModel, isUriLocation, isViewContainer, isViewModel, } from './types/index.ts';
6
+ export { type AbstractDisplayModel, type AbstractMenuManager, type AbstractRootModel, type AbstractSessionModel, type AbstractTrackModel, type AbstractViewContainer, type AbstractViewModel, type AnimationMode, type AnyReactComponentType, type AppRootModel, type AssemblyHost, type AssemblyManager, type AugmentedRegion, AuthNeededError, type BasePlugin, type BlobLocation, type ClassReturnedBy, type ConnectionInstance, type DialogComponentType, type DialogHost, type DrawerPosition, type FileHandleLocation, type FileLocation, type InstanceTypeRestrictive, type JBrowsePlugin, type JBrowsePluginVersion, type LocalPathLocation, type NoAssemblyRegion, type NotificationLevel, type NotificationSink, type PaletteHost, type PreBlobLocation, type PreFileHandleLocation, type PreFileLocation, type PreLocalPathLocation, type PreUriLocation, type Region, type RenderingServices, type RpcCaller, type RpcHost, type RootModelWithInternetAccounts, type SelectionContainer, type SessionServices, type SessionWithAddAssembly, type SessionWithAddSessionTrack, type SessionWithPublishTrackConf, type SessionWithConfigEditing, type SessionWithConnectionEditing, type SessionWithConnections, type SessionWithDeleteTrackConf, type SessionWithDrawerWidgets, type SessionWithFocusedViewAndDrawerWidgets, type SessionWithPermanentPlugins, type SessionWithSessionPlugins, type SessionWithShareURL, type SessionWithViewReplacement, type SessionWithWidgets, type SnackAction, type TrackActionView, type TrackCatalog, type TrackContainer, type TrackViewModel, type TypeTestedByPredicate, type UriLocation, type Widget, type WidgetMap, addOrReplaceView, canReplaceView, isAbstractMenuManager, isAppRootModel, isAuthNeededException, isBlobLocation, isDisplayModel, isFileHandleLocation, isLocalPathLocation, isRootModelWithInternetAccounts, isSelectionContainer, isSessionModel, isSessionServices, isSessionModelWithConfigEditing, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddAssembly, isSessionWithAddSessionTrack, isSessionWithAddTracks, isSessionWithPublishTrackConf, isSessionWithDeleteTrackConf, isSessionWithMultipleViews, isSessionWithPermanentPlugins, isSessionWithSessionPlugins, isSessionWithSessionTracks, isSessionWithShareURL, isSessionWithViewReplacement, isTrackModel, isTrackViewModel, isUriLocation, isViewContainer, isViewModel, } from './types/index.ts';
7
7
  export { type PluginUpdate, type ResolvedPlugin, type StorePluginFailure, type StorePluginResolution, getPluginUpdate, installablePlugins, installedVersionFromUrl, isPluginInstalled, resolvePlugin, resolveStorePluginRefs, resolveStoreRefs, } from './pluginStore.ts';
8
8
  export { type PhaseFailure, type ProgressReporter, type RpcStatus, type StatusCallback, type StatusPhase, type StatusStream, type StatusWindow, type StatusWithProgress, createProgressReporter, createStatusFanOut, createStatusWindow, downloadStatus, progressLabel, statusFraction, statusMessageText, statusProgressLabel, statusReading, statusSource, throttleStatusEmits, updateStatus, withProgress, } from './progress.ts';
9
9
  export { calculateRedispatchRange, doesIntersect2, intersection2, } from './range.ts';
@@ -46,7 +46,7 @@ export { isRpcResult } from './rpc.ts';
46
46
  export { aesDecrypt, aesEncrypt, sha256, sha256Base64, sha256Base64Url, toBase64, toBase64Url, } from './crypto.ts';
47
47
  export { type FeatureWidgetTypeRef, notifyFeatureDetailsMiss, openFeatureWidget, } from './openFeatureWidget.ts';
48
48
  export { withFeatureDetails } from './withFeatureDetails.ts';
49
- export { canonicalizeViewRefName, findParentThat, findParentThatIs, getAssemblyHost, getContainingDisplay, getContainingTrack, getContainingView, getEnv, getRenderingServices, getSession, hashCode, objectHash, } from './mstUtils.ts';
49
+ export { canonicalizeViewRefName, findParentThat, findParentThatIs, getAssemblyHost, getContainingDisplay, getContainingTrack, getContainingView, getEnv, getRenderingServices, launchOrReplaceView, getSession, hashCode, objectHash, } from './mstUtils.ts';
50
50
  export { getDialogHost, getNotificationSink, getPaletteHost, getRpcHost, getSessionServices, } from './sessionServices.ts';
51
51
  /**
52
52
  * Convert a browser File (from a drop zone or file input) into a FileLocation:
@@ -56,7 +56,6 @@ export declare function fileToLocation(file: File): FileLocation;
56
56
  export { type Frame, complement, complementTable, defaultStarts, getFrame, revcom, reverse, stitch, } from './seqUtils.ts';
57
57
  export { revlist } from './revlist.ts';
58
58
  export { useCreateOnce, useDebounce, useFinalUnmount, useLocalStorage, useWidthSetter, } from './hooks.ts';
59
- export { renderToStaticMarkup } from './renderToStaticMarkup.ts';
60
59
  export { codonTable, defaultCodonTable, generateCodonTable, } from './geneticCodes.ts';
61
60
  export { IUPAC_MOTIF_REGEX, isPalindromic, iupacToRegex, reverseComplementIupac, } from './iupac.ts';
62
61
  export { type MotifListParse, type MotifParseError, type ParsedMotif, parseMotifList, } from './parseMotifList.ts';
package/esm/util/index.js CHANGED
@@ -12,7 +12,7 @@ export { capitalizeFirst, pluralize, shorten, shorten2, truncateMiddle, } from '
12
12
  export { formatBytes } from './formatBytes.js';
13
13
  export { getFillProps, getStrokeProps, stripAlpha } from './svgColorProps.js';
14
14
  export { fetchAndMaybeUnzip, fetchAndMaybeUnzipText, isGzip, } from './fetchAndMaybeUnzip.js';
15
- export { AuthNeededError, addOrReplaceView, canReplaceView, isAbstractMenuManager, isAppRootModel, isAuthNeededException, isBlobLocation, isDisplayModel, isFileHandleLocation, isLocalPathLocation, isRootModelWithInternetAccounts, isSelectionContainer, isSessionModel, isSessionServices, isSessionModelWithConfigEditing, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddAssembly, isSessionWithAddSessionTrack, isSessionWithAddTracks, isSessionWithPublishTrackConf, isSessionWithDeleteTrackConf, isSessionWithPermanentPlugins, isSessionWithSessionPlugins, isSessionWithSessionTracks, isSessionWithShareURL, isSessionWithViewReplacement, isTrackModel, isTrackViewModel, isUriLocation, isViewContainer, isViewModel, } from './types/index.js';
15
+ export { AuthNeededError, addOrReplaceView, canReplaceView, isAbstractMenuManager, isAppRootModel, isAuthNeededException, isBlobLocation, isDisplayModel, isFileHandleLocation, isLocalPathLocation, isRootModelWithInternetAccounts, isSelectionContainer, isSessionModel, isSessionServices, isSessionModelWithConfigEditing, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddAssembly, isSessionWithAddSessionTrack, isSessionWithAddTracks, isSessionWithPublishTrackConf, isSessionWithDeleteTrackConf, isSessionWithMultipleViews, isSessionWithPermanentPlugins, isSessionWithSessionPlugins, isSessionWithSessionTracks, isSessionWithShareURL, isSessionWithViewReplacement, isTrackModel, isTrackViewModel, isUriLocation, isViewContainer, isViewModel, } from './types/index.js';
16
16
  export { getPluginUpdate, installablePlugins, installedVersionFromUrl, isPluginInstalled, resolvePlugin, resolveStorePluginRefs, resolveStoreRefs, } from './pluginStore.js';
17
17
  export { createProgressReporter, createStatusFanOut, createStatusWindow, downloadStatus, progressLabel, statusFraction, statusMessageText, statusProgressLabel, statusReading, statusSource, throttleStatusEmits, updateStatus, withProgress, } from './progress.js';
18
18
  export { calculateRedispatchRange, doesIntersect2, intersection2, } from './range.js';
@@ -53,7 +53,7 @@ export { isRpcResult } from './rpc.js';
53
53
  export { aesDecrypt, aesEncrypt, sha256, sha256Base64, sha256Base64Url, toBase64, toBase64Url, } from './crypto.js';
54
54
  export { notifyFeatureDetailsMiss, openFeatureWidget, } from './openFeatureWidget.js';
55
55
  export { withFeatureDetails } from './withFeatureDetails.js';
56
- export { canonicalizeViewRefName, findParentThat, findParentThatIs, getAssemblyHost, getContainingDisplay, getContainingTrack, getContainingView, getEnv, getRenderingServices, getSession, hashCode, objectHash, } from './mstUtils.js';
56
+ export { canonicalizeViewRefName, findParentThat, findParentThatIs, getAssemblyHost, getContainingDisplay, getContainingTrack, getContainingView, getEnv, getRenderingServices, launchOrReplaceView, getSession, hashCode, objectHash, } from './mstUtils.js';
57
57
  export { getDialogHost, getNotificationSink, getPaletteHost, getRpcHost, getSessionServices, } from './sessionServices.js';
58
58
  // this is recommended in a later comment in
59
59
  // https://github.com/electron/electron/issues/2288 for detecting electron in a
@@ -91,7 +91,6 @@ export { revlist } from './revlist.js';
91
91
  // without it that is `(0, PR.useLocalStorage) is not a function` when the panel
92
92
  // renders. The implementations never went anywhere -- only these lines did.
93
93
  export { useCreateOnce, useDebounce, useFinalUnmount, useLocalStorage, useWidthSetter, } from './hooks.js';
94
- export { renderToStaticMarkup } from './renderToStaticMarkup.js';
95
94
  export { codonTable, defaultCodonTable, generateCodonTable, } from './geneticCodes.js';
96
95
  export { IUPAC_MOTIF_REGEX, isPalindromic, iupacToRegex, reverseComplementIupac, } from './iupac.js';
97
96
  export { parseMotifList, } from './parseMotifList.js';
@@ -1,6 +1,7 @@
1
- import type { ActiveFetch, StatusReporter } from './createStopTokenRotation.ts';
1
+ import type { ActiveFetch, StatusReporter, StopTokenRotation } from './createStopTokenRotation.ts';
2
2
  import type { FetchContext } from './fetchContext.ts';
3
3
  import type { FetchPhases } from './fetchPhases.ts';
4
+ import type { StopToken } from './stopToken.ts';
4
5
  import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
5
6
  /**
6
7
  * The bookkeeping a family hangs off one fetch, beside the phases. Each of the
@@ -27,8 +28,13 @@ export interface FetchLifecycle {
27
28
  * the comparative family's `fetching`), and anything a display blanks
28
29
  * because it has no freshness signature to keep stale data honest with
29
30
  * (chord's refName map).
31
+ *
32
+ * Takes the token rather than the whole {@link ActiveFetch}: publishing it is
33
+ * the only thing any implementation does with the fetch, and the guard and
34
+ * the status slot are the skeleton's to hand out — through `ctx`, where a
35
+ * `run` gets them pre-wired.
30
36
  */
31
- onBegin?: (active: ActiveFetch) => void;
37
+ onBegin?: (stopToken: StopToken) => void;
32
38
  /**
33
39
  * Runs in the `finally`, before the rotation's `end()`. `current` is this
34
40
  * run's own guard, already evaluated: a **superseded** run must not clear the
@@ -80,7 +86,7 @@ export interface FetchSkeletonHost extends IStateTreeNode {
80
86
  reloadCounter: number;
81
87
  /**
82
88
  * The states where this host deliberately fetches nothing, read only by the
83
- * dev-only retry check. A decline there is not a dead Retry.
89
+ * retry contract check. A decline there is not a dead Retry.
84
90
  */
85
91
  fetchInert: boolean;
86
92
  /**
@@ -97,19 +103,11 @@ export interface FetchSkeletonHost extends IStateTreeNode {
97
103
  */
98
104
  fetchCanceled?: boolean;
99
105
  }
100
- export interface InstallFetchOptions<TArgs, TResult> extends FetchPhases<TArgs, TResult, FetchContext>, FetchLifecycle {
106
+ interface InstallFetchOptionsBase<TArgs, TResult> extends FetchPhases<TArgs, TResult, FetchContext>, FetchLifecycle {
101
107
  /** autorun name, for the MobX devtools/spy stream */
102
108
  name: string;
103
109
  /** how long to coalesce reruns once the body has started work once */
104
110
  delay: number;
105
- /**
106
- * Where this fetch's status lands. A display passes itself — its status
107
- * fields are part of its own API, and one composing `FetchMixin` lends the
108
- * model-wide window so this fetch takes a slot on the one field beside the
109
- * display's other operations rather than opening a second writer over it.
110
- * Anything with one operation to narrate passes a `createStatusChannel`.
111
- */
112
- report: StatusReporter;
113
111
  /**
114
112
  * An extra skip above `prepare`, reported as `gated` rather than `declined`:
115
113
  * the skeleton decided before the family's own gate was consulted, so it is
@@ -161,7 +159,7 @@ export interface InstallFetchOptions<TArgs, TResult> extends FetchPhases<TArgs,
161
159
  */
162
160
  committedKey?: () => string | undefined;
163
161
  /**
164
- * Install the two dev-only display-contract checks under this name. Omitted
162
+ * Install the two display-contract checks under this name. Omitted
165
163
  * by a **secondary** fetch on a host whose primary foundation already
166
164
  * installed them: `assertDisplayContract` would report the second install as
167
165
  * the double-attach it is built to catch, and a second retry check would
@@ -169,19 +167,46 @@ export interface InstallFetchOptions<TArgs, TResult> extends FetchPhases<TArgs,
169
167
  */
170
168
  contract?: string;
171
169
  }
170
+ /**
171
+ * The rotation half is either/or: `report` says where a rotation this installer
172
+ * creates for itself reports, and `rotation` lends the host's own instead — a
173
+ * display composing `FetchMixin` passes its `fetchRotation`, so `cancelFetch`
174
+ * and `cancelFetchByUser` reach the fetch this installs rather than a second
175
+ * rotation they cannot see. A lent rotation already carries its status window,
176
+ * so `report` would be dead beside it — the same either/or `StatusReporter`
177
+ * itself is a union for.
178
+ */
179
+ export type InstallFetchOptions<TArgs, TResult> = InstallFetchOptionsBase<TArgs, TResult> & ({
180
+ /**
181
+ * Where this fetch's status lands. A display passes itself — its status
182
+ * fields are part of its own API, and one composing `FetchMixin` lends
183
+ * the model-wide window so this fetch takes a slot on the one field
184
+ * beside the display's other operations rather than opening a second
185
+ * writer over it. Anything with one operation to narrate passes a
186
+ * `createStatusChannel`.
187
+ */
188
+ report: StatusReporter;
189
+ rotation?: never;
190
+ } | {
191
+ /** the host's own latest-wins rotation, lent whole — see above */
192
+ rotation: StopTokenRotation;
193
+ report?: never;
194
+ });
172
195
  /**
173
196
  * A whole fetch, installed: the rotation, the leading-edge autorun, the
174
197
  * unconditional trigger reads, the gates, and {@link runFetchOnce} under them.
175
198
  *
176
- * Every fetch in the tree is this shape except one — the prerequisite reads
177
- * (HiC's header, the multi-sample sample list), both comparative displays, the
178
- * circular view's chord fetch, the breakpoint overlay fetch and the multi-way
179
- * synteny display's two DEPENDENT fetches (lane genes, lane links, each gated on
180
- * the key its own commit stamps) all take it. The
181
- * exception is `FetchMixin.runFetch`, the viewport fetch of an LGV display: its
182
- * rotation is a member rather than a closure (so `cancelFetch` can reach it)
183
- * and its trigger list is the per-region or global foundation's, so it holds
184
- * `runFetchOnce` directly and the autorun above it is that foundation's.
199
+ * Every fetch in the tree is this shape except one family — the prerequisite
200
+ * reads (HiC's header, the multi-sample sample list), the LGV global family
201
+ * (`installGlobalFetchAutorun`, which lends `FetchMixin`'s rotation through the
202
+ * `rotation` option so `cancelFetch` reaches the fetch it installs), both
203
+ * comparative displays, the circular view's chord fetch, the breakpoint overlay
204
+ * fetch and the multi-way synteny display's two DEPENDENT fetches (lane genes,
205
+ * lane links, each gated on the key its own commit stamps) all take it. The
206
+ * exception is the per-region family's `FetchMixin.runFetch`: its trigger is
207
+ * `planRegionFetch`'s autorun and its commits stream per region through
208
+ * `ctx.commitRegion`, so it holds `runFetchOnce` directly and the autorun above
209
+ * it is that foundation's.
185
210
  *
186
211
  * What a caller supplies is exactly what differs between them: which reads wake
187
212
  * it (`prepare`, plus `gate`), what a round trip commits, where the loading
@@ -191,4 +216,5 @@ export interface InstallFetchOptions<TArgs, TResult> extends FetchPhases<TArgs,
191
216
  * Cancel button: the flag alone is not a cancel — nothing rotates the token, the
192
217
  * stopped RPC stays current, and it commits over the load the user just stopped.
193
218
  */
194
- export declare function installFetch<TArgs, TResult>(self: FetchSkeletonHost, { name, delay, report, gate, contract, fetchKey, committedKey, prepare, run, commit, setError, onBegin, onEnd, }: InstallFetchOptions<TArgs, TResult>): () => void;
219
+ export declare function installFetch<TArgs, TResult>(self: FetchSkeletonHost, opts: InstallFetchOptions<TArgs, TResult>): () => void;
220
+ export {};
@@ -33,7 +33,7 @@ export async function runFetchOnce(self, active, args, { run, commit, setError,
33
33
  const { stopToken, isCurrent, statusCallback, end } = active;
34
34
  // synchronous, so it lands while this run is by construction the current one
35
35
  setError(undefined);
36
- onBegin?.(active);
36
+ onBegin?.(stopToken);
37
37
  try {
38
38
  const result = await run(args, makeFetchContext(self, {
39
39
  stopToken,
@@ -65,7 +65,7 @@ export async function runFetchOnce(self, active, args, { run, commit, setError,
65
65
  }
66
66
  }
67
67
  /**
68
- * The two dev-only checks every fetch installer owes, together because they are
68
+ * The two contract checks every fetch installer owes, together because they are
69
69
  * the same doctrine: one reports a foundation attached twice, the other reports
70
70
  * a `reload()` the gate still declines — the dead Retry button.
71
71
  */
@@ -73,19 +73,33 @@ function installContractChecks(self, contract) {
73
73
  assertDisplayContract(self, contract);
74
74
  return makeRetryContractCheck(self);
75
75
  }
76
+ /**
77
+ * The rotation for an installation that was lent none: created here, so
78
+ * disposed here — a lent one is the host's, and resetting its status window
79
+ * would drop a write the host's other operations are still owed.
80
+ */
81
+ function ownRotation(self, report) {
82
+ const rotation = createStopTokenRotation(self, report);
83
+ addDisposer(self, () => {
84
+ rotation.dispose();
85
+ });
86
+ return rotation;
87
+ }
76
88
  /**
77
89
  * A whole fetch, installed: the rotation, the leading-edge autorun, the
78
90
  * unconditional trigger reads, the gates, and {@link runFetchOnce} under them.
79
91
  *
80
- * Every fetch in the tree is this shape except one — the prerequisite reads
81
- * (HiC's header, the multi-sample sample list), both comparative displays, the
82
- * circular view's chord fetch, the breakpoint overlay fetch and the multi-way
83
- * synteny display's two DEPENDENT fetches (lane genes, lane links, each gated on
84
- * the key its own commit stamps) all take it. The
85
- * exception is `FetchMixin.runFetch`, the viewport fetch of an LGV display: its
86
- * rotation is a member rather than a closure (so `cancelFetch` can reach it)
87
- * and its trigger list is the per-region or global foundation's, so it holds
88
- * `runFetchOnce` directly and the autorun above it is that foundation's.
92
+ * Every fetch in the tree is this shape except one family — the prerequisite
93
+ * reads (HiC's header, the multi-sample sample list), the LGV global family
94
+ * (`installGlobalFetchAutorun`, which lends `FetchMixin`'s rotation through the
95
+ * `rotation` option so `cancelFetch` reaches the fetch it installs), both
96
+ * comparative displays, the circular view's chord fetch, the breakpoint overlay
97
+ * fetch and the multi-way synteny display's two DEPENDENT fetches (lane genes,
98
+ * lane links, each gated on the key its own commit stamps) all take it. The
99
+ * exception is the per-region family's `FetchMixin.runFetch`: its trigger is
100
+ * `planRegionFetch`'s autorun and its commits stream per region through
101
+ * `ctx.commitRegion`, so it holds `runFetchOnce` directly and the autorun above
102
+ * it is that foundation's.
89
103
  *
90
104
  * What a caller supplies is exactly what differs between them: which reads wake
91
105
  * it (`prepare`, plus `gate`), what a round trip commits, where the loading
@@ -95,16 +109,23 @@ function installContractChecks(self, contract) {
95
109
  * Cancel button: the flag alone is not a cancel — nothing rotates the token, the
96
110
  * stopped RPC stays current, and it commits over the load the user just stopped.
97
111
  */
98
- export function installFetch(self, { name, delay, report, gate, contract, fetchKey, committedKey, prepare, run, commit, setError, onBegin, onEnd, }) {
112
+ export function installFetch(self, opts) {
113
+ const { name, delay, gate, contract, fetchKey, committedKey, prepare, run, commit, setError, onBegin, onEnd, } = opts;
99
114
  const noteFetchAutorunRun = contract === undefined ? undefined : installContractChecks(self, contract);
100
- const rotation = createStopTokenRotation(self, report);
115
+ const lent = opts.rotation;
116
+ const rotation = lent === undefined ? ownRotation(self, opts.report) : lent;
101
117
  // The `reloadCounter` this installation last issued a fetch at. Per
102
118
  // installation and per instance, so a host with several fetches gives each its
103
119
  // own — and a closure rather than model state, because nothing outside reads
104
120
  // it and a volatile would be one more thing a host has to declare.
105
121
  let issuedEpoch;
106
- const stamp = committedKey ? undefined : observable.box();
107
- const heldKey = committedKey ?? (() => stamp.get());
122
+ // The skeleton's own stamp exists only where there is a key to stamp — a
123
+ // fetch with no `fetchKey` never reads it, so allocating one would only feed
124
+ // it `undefined` on every commit.
125
+ const stamp = committedKey === undefined && fetchKey !== undefined
126
+ ? observable.box()
127
+ : undefined;
128
+ const heldKey = committedKey ?? (() => stamp?.get());
108
129
  const commitAndStamp = stamp
109
130
  ? (result, args) => {
110
131
  runInAction(() => {
@@ -113,9 +134,6 @@ export function installFetch(self, { name, delay, report, gate, contract, fetchK
113
134
  });
114
135
  }
115
136
  : commit;
116
- addDisposer(self, () => {
117
- rotation.dispose();
118
- });
119
137
  leadingEdgeAutorun(self,
120
138
  // #region voidTracking
121
139
  () => {
@@ -128,13 +146,23 @@ export function installFetch(self, { name, delay, report, gate, contract, fetchK
128
146
  // Cancel would be a one-way door with a Retry button on it. Order, not
129
147
  // just position: counter first, then this, then the gates.
130
148
  const canceled = self.fetchCanceled === true;
149
+ // Above the gate, not only above `prepare`: teardown mutates the
150
+ // observables this body reads before the disposers run, and every `gate`
151
+ // but the breakpoint view's reaches the containing view or track through a
152
+ // parent walk (`host.initialized`, `isMinimized`, `getContainingView`),
153
+ // which throws once the node has left the tree. This catches the destroyed
154
+ // node; the detached-and-still-alive window (ADR-069) is covered only by
155
+ // the walk's own cache. Nothing is reported for a dead node — there is no
156
+ // Retry button left to be dead, and the check would read three more
157
+ // members of a corpse to decide it.
158
+ if (!isAlive(self)) {
159
+ return false;
160
+ }
131
161
  if (canceled || gate?.() === false) {
132
162
  noteFetchAutorunRun?.('gated');
133
163
  return false;
134
164
  }
135
- // Teardown mutates observables `prepare` reads before the disposers run,
136
- // and getContainingView on a detached node warns then throws.
137
- const args = isAlive(self) ? prepare() : undefined;
165
+ const args = prepare();
138
166
  if (args === undefined) {
139
167
  noteFetchAutorunRun?.('declined');
140
168
  return false;
@@ -1,13 +1,13 @@
1
1
  import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
2
2
  interface InitAutorunHost<T> extends IStateTreeNode {
3
- init?: T;
4
- setInit: (init?: T) => void;
3
+ pendingLaunch?: T;
4
+ setLaunch: (launch?: T) => void;
5
5
  setError: (error: unknown) => void;
6
6
  }
7
7
  export interface InitApplyContext {
8
8
  /**
9
9
  * True once this init can no longer be the one being applied: the node is
10
- * gone, or a newer `setInit` has replaced it. Any mid-apply wait that can
10
+ * gone, or a newer `setLaunch` has replaced it. Any mid-apply wait that can
11
11
  * park indefinitely must fold this into its condition — an unbounded wait on
12
12
  * state that never arrives holds the drain open, and the newer init that
13
13
  * would have superseded it is exactly what gets stranded. This is the causal
@@ -35,7 +35,7 @@ interface InitAutorunOptions<T> {
35
35
  apply: (init: T, ctx: InitApplyContext) => Promise<void>;
36
36
  }
37
37
  /**
38
- * The init state machine shared by the views that take a declarative `init`
38
+ * The launch state machine shared by the views that take a declarative launch
39
39
  * blob (LinearGenomeView, DotplotView, LinearSyntenyView): the re-entry guard,
40
40
  * the readiness gate, the serialized drain, the identity-checked clear, the
41
41
  * isAlive guards, and one failure policy.
@@ -43,18 +43,18 @@ interface InitAutorunOptions<T> {
43
43
  * The failure policy keys off `materialized`, the same line each view's
44
44
  * postProcessSnapshot already draws for persistence:
45
45
  *
46
- * - Not materialized — nothing is on screen and `init` is still persisted, so
46
+ * - Not materialized — nothing is on screen and `launch` is still persisted, so
47
47
  * keep it for a reload retry and `setError`. That flips the view onto its
48
48
  * import form, which renders the error in its own banner; a snackbar would
49
49
  * state the same failure twice and the banner is the one that persists.
50
50
  * - Materialized — the view works, so a later failure is one sub-step's
51
51
  * problem and must not take the view down with it. `setError` would, since
52
52
  * showImportForm keys off `error` alone and would discard rows that loaded
53
- * fine, so report it as a snackbar instead. Clearing `init` is also what
53
+ * fine, so report it as a snackbar instead. Clearing `launch` is also what
54
54
  * disarms the re-fire: `ready` usually folds in the measured width, so
55
- * leaving it set lets any resize re-run `apply` over a half-applied init.
55
+ * leaving it set lets any resize re-run `apply` over a half-applied blob.
56
56
  *
57
- * `apply` therefore never clears `init` and never catches for reporting — it
57
+ * `apply` therefore never clears `launch` and never catches for reporting — it
58
58
  * catches only the failures it wants to keep going through (a bad locstring in
59
59
  * one row), and everything it lets escape is fatal-as-of-that-point.
60
60
  */
@@ -1,19 +1,19 @@
1
1
  import { addDisposer, isAlive } from '@jbrowse/mobx-state-tree';
2
2
  import { autorun } from 'mobx';
3
3
  import { getNotificationSink } from './sessionServices.js';
4
- // Clear only when `init` is still the exact object we applied. A setInit that
5
- // lands while `apply` is awaiting (React StrictMode remounts, a programmatic
6
- // re-launch) swaps in a new object; clearing unconditionally would silently
7
- // drop that pending init. Leaving it lets the drain loop apply it next.
4
+ // Clear only when the pending blob is still the exact object we applied. A
5
+ // setLaunch that lands while `apply` is awaiting (React StrictMode remounts, a
6
+ // programmatic re-launch) swaps in a new object; clearing unconditionally would
7
+ // silently drop it. Leaving it lets the drain loop apply it next.
8
8
  function clearIfUnchanged(self, init) {
9
- if (isAlive(self) && self.init === init) {
10
- self.setInit(undefined);
9
+ if (isAlive(self) && self.pendingLaunch === init) {
10
+ self.setLaunch(undefined);
11
11
  }
12
12
  }
13
13
  async function applyInitOnce(self, init, { apply, materialized }) {
14
14
  try {
15
15
  await apply(init, {
16
- superseded: () => !isAlive(self) || self.init !== init,
16
+ superseded: () => !isAlive(self) || self.pendingLaunch !== init,
17
17
  });
18
18
  clearIfUnchanged(self, init);
19
19
  }
@@ -34,7 +34,7 @@ async function applyInitOnce(self, init, { apply, materialized }) {
34
34
  }
35
35
  }
36
36
  function currentInit(self, { ready }) {
37
- return isAlive(self) && ready() ? self.init : undefined;
37
+ return isAlive(self) && ready() ? self.pendingLaunch : undefined;
38
38
  }
39
39
  // Apply pending inits one at a time until none remain. Serializing (rather
40
40
  // than letting the autorun run `apply` concurrently) is what keeps re-entrant
@@ -47,13 +47,13 @@ async function drainInit(self, opts) {
47
47
  while (pending !== undefined) {
48
48
  await applyInitOnce(self, pending, opts);
49
49
  const next = currentInit(self, opts);
50
- // a fatal failure keeps `init` for a reload retry; the same object still
50
+ // a fatal failure keeps `launch` for a reload retry; the same object still
51
51
  // sitting there means it was not consumed, so stop rather than spin on it
52
52
  pending = next === pending ? undefined : next;
53
53
  }
54
54
  }
55
55
  /**
56
- * The init state machine shared by the views that take a declarative `init`
56
+ * The launch state machine shared by the views that take a declarative launch
57
57
  * blob (LinearGenomeView, DotplotView, LinearSyntenyView): the re-entry guard,
58
58
  * the readiness gate, the serialized drain, the identity-checked clear, the
59
59
  * isAlive guards, and one failure policy.
@@ -61,18 +61,18 @@ async function drainInit(self, opts) {
61
61
  * The failure policy keys off `materialized`, the same line each view's
62
62
  * postProcessSnapshot already draws for persistence:
63
63
  *
64
- * - Not materialized — nothing is on screen and `init` is still persisted, so
64
+ * - Not materialized — nothing is on screen and `launch` is still persisted, so
65
65
  * keep it for a reload retry and `setError`. That flips the view onto its
66
66
  * import form, which renders the error in its own banner; a snackbar would
67
67
  * state the same failure twice and the banner is the one that persists.
68
68
  * - Materialized — the view works, so a later failure is one sub-step's
69
69
  * problem and must not take the view down with it. `setError` would, since
70
70
  * showImportForm keys off `error` alone and would discard rows that loaded
71
- * fine, so report it as a snackbar instead. Clearing `init` is also what
71
+ * fine, so report it as a snackbar instead. Clearing `launch` is also what
72
72
  * disarms the re-fire: `ready` usually folds in the measured width, so
73
- * leaving it set lets any resize re-run `apply` over a half-applied init.
73
+ * leaving it set lets any resize re-run `apply` over a half-applied blob.
74
74
  *
75
- * `apply` therefore never clears `init` and never catches for reporting — it
75
+ * `apply` therefore never clears `launch` and never catches for reporting — it
76
76
  * catches only the failures it wants to keep going through (a bad locstring in
77
77
  * one row), and everything it lets escape is fatal-as-of-that-point.
78
78
  */
@@ -93,7 +93,7 @@ export function installInitAutorun(self, opts) {
93
93
  // reading a detached node throws — here into an unawaited promise.
94
94
  const alive = isAlive(self);
95
95
  const isReady = alive && opts.ready();
96
- const init = alive ? self.init : undefined;
96
+ const init = alive ? self.pendingLaunch : undefined;
97
97
  if (isReady && init !== undefined && !draining) {
98
98
  draining = true;
99
99
  try {
@@ -0,0 +1,13 @@
1
+ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
2
+ /**
3
+ * #api core/util
4
+ * Run a display's `afterAttach` body from a module loaded on demand, so the
5
+ * autorun installers stay out of the display's eager bundle.
6
+ *
7
+ * One policy for the gap the `await` opens: a node torn down before the module
8
+ * lands installs nothing, and a module that fails to load is reported where the
9
+ * user can see it rather than onto the display's own error slot — that slot is
10
+ * what `reload()` clears, and nothing would re-run the install behind it.
11
+ * Three displays hand-rolled this IIFE and each drew the lines differently.
12
+ */
13
+ export declare function runLazyAfterAttach<Self extends IStateTreeNode>(self: Self, load: () => Promise<(self: Self) => void>): void;
@@ -0,0 +1,31 @@
1
+ import { isAlive } from '@jbrowse/mobx-state-tree';
2
+ import { getNotificationSink } from './sessionServices.js';
3
+ /**
4
+ * #api core/util
5
+ * Run a display's `afterAttach` body from a module loaded on demand, so the
6
+ * autorun installers stay out of the display's eager bundle.
7
+ *
8
+ * One policy for the gap the `await` opens: a node torn down before the module
9
+ * lands installs nothing, and a module that fails to load is reported where the
10
+ * user can see it rather than onto the display's own error slot — that slot is
11
+ * what `reload()` clears, and nothing would re-run the install behind it.
12
+ * Three displays hand-rolled this IIFE and each drew the lines differently.
13
+ */
14
+ export function runLazyAfterAttach(self, load) {
15
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
16
+ ;
17
+ (async () => {
18
+ try {
19
+ const install = await load();
20
+ if (isAlive(self)) {
21
+ install(self);
22
+ }
23
+ }
24
+ catch (e) {
25
+ if (isAlive(self)) {
26
+ console.error(e);
27
+ getNotificationSink(self).notifyError(`${e}`, e);
28
+ }
29
+ }
30
+ })();
31
+ }
@@ -0,0 +1,50 @@
1
+ export interface LegendCandidate {
2
+ rowIndex: number;
3
+ label: string;
4
+ color: number;
5
+ }
6
+ export interface LegendEntry {
7
+ label: string;
8
+ color: number;
9
+ }
10
+ export declare const MAX_LEGEND_ENTRIES = 30;
11
+ export declare const MAX_LEGEND_CANDIDATES = 1024;
12
+ /**
13
+ * Accumulator for the distinct (row, name, color) combinations a packer walks
14
+ * past, in first-seen order and bounded — the small list `unionLegendCandidates`
15
+ * derives a key from, so the main thread never re-walks the features. Fed scalar
16
+ * by scalar, from the loop that already holds the three values.
17
+ *
18
+ * Deduped on the whole triple rather than on (row, color): the union takes the
19
+ * first NAME it sees for a color, so where that name is already spoken for, the
20
+ * second name on that color has to still be in the list to carry it.
21
+ *
22
+ * A nameless feature contributes nothing, which is the union's rule too — so a
23
+ * track whose features have no names ships an empty list rather than one entry
24
+ * per feature.
25
+ */
26
+ export declare function createLegendCandidateCollector(maxCandidates?: number): {
27
+ candidates: LegendCandidate[];
28
+ add(rowIndex: number, label: string, color: number): void;
29
+ };
30
+ export interface LegendCandidateSource {
31
+ candidates: readonly LegendCandidate[];
32
+ rowPaintsCandidateColor: (rowIndex: number) => boolean;
33
+ }
34
+ /**
35
+ * The categorical color key over any number of packed regions: one entry per
36
+ * distinct color, labeled by the first name seen in that color, in first-seen
37
+ * order.
38
+ *
39
+ * Keyed by color rather than by name, so each key row is 1:1 with a color — a
40
+ * row is normally a toggle and hiding a category hides features BY color, so two
41
+ * names sharing one color must collapse to a single row. A name reused across
42
+ * two colors keeps its first-seen color.
43
+ *
44
+ * `[]` when the data carries no categorical signal: nothing named, or more than
45
+ * `maxEntries` distinct colors.
46
+ *
47
+ * `regions` is consumed lazily, so a caller able to answer without reading any
48
+ * of them can pass a generator and have none of them touched.
49
+ */
50
+ export declare function unionLegendCandidates<T>(regions: Iterable<T>, resolve: (region: T) => LegendCandidateSource, maxEntries?: number): LegendEntry[];