@genome-spy/core 0.67.0 → 0.68.0

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 (172) hide show
  1. package/dist/bundle/index.es.js +7641 -6313
  2. package/dist/bundle/index.js +115 -134
  3. package/dist/schema.json +534 -132
  4. package/dist/src/data/collector.d.ts +20 -0
  5. package/dist/src/data/collector.d.ts.map +1 -1
  6. package/dist/src/data/collector.js +148 -0
  7. package/dist/src/data/dataFlow.d.ts +6 -0
  8. package/dist/src/data/dataFlow.d.ts.map +1 -1
  9. package/dist/src/data/dataFlow.js +10 -0
  10. package/dist/src/data/flowInit.d.ts.map +1 -1
  11. package/dist/src/data/flowInit.js +2 -3
  12. package/dist/src/data/flowNode.d.ts +8 -0
  13. package/dist/src/data/flowNode.d.ts.map +1 -1
  14. package/dist/src/data/flowNode.js +18 -0
  15. package/dist/src/data/keyIndex.d.ts +18 -0
  16. package/dist/src/data/keyIndex.d.ts.map +1 -0
  17. package/dist/src/data/keyIndex.js +241 -0
  18. package/dist/src/data/keyIndex.test.d.ts +2 -0
  19. package/dist/src/data/keyIndex.test.d.ts.map +1 -0
  20. package/dist/src/data/sources/dataSource.d.ts.map +1 -1
  21. package/dist/src/data/sources/dataSource.js +5 -1
  22. package/dist/src/data/sources/dataSourceFactory.d.ts +14 -12
  23. package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
  24. package/dist/src/data/sources/dataSourceFactory.js +52 -16
  25. package/dist/src/data/sources/lazy/mockLazySource.d.ts +29 -0
  26. package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -0
  27. package/dist/src/data/sources/lazy/mockLazySource.js +44 -0
  28. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +22 -1
  29. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  30. package/dist/src/data/sources/lazy/singleAxisLazySource.js +34 -2
  31. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  32. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +15 -0
  33. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  34. package/dist/src/data/sources/lazy/tabixSource.js +15 -5
  35. package/dist/src/data/transforms/stack.d.ts.map +1 -1
  36. package/dist/src/data/transforms/stack.js +1 -0
  37. package/dist/src/encoder/accessor.d.ts +43 -0
  38. package/dist/src/encoder/accessor.d.ts.map +1 -1
  39. package/dist/src/encoder/accessor.js +164 -0
  40. package/dist/src/encoder/encoder.d.ts +11 -2
  41. package/dist/src/encoder/encoder.d.ts.map +1 -1
  42. package/dist/src/encoder/encoder.js +24 -4
  43. package/dist/src/encoder/metadataChannels.d.ts +15 -0
  44. package/dist/src/encoder/metadataChannels.d.ts.map +1 -0
  45. package/dist/src/encoder/metadataChannels.js +65 -0
  46. package/dist/src/encoder/metadataChannels.test.d.ts +2 -0
  47. package/dist/src/encoder/metadataChannels.test.d.ts.map +1 -0
  48. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  49. package/dist/src/genome/scaleLocus.js +14 -1
  50. package/dist/src/genomeSpy/containerUi.d.ts +0 -1
  51. package/dist/src/genomeSpy/containerUi.d.ts.map +1 -1
  52. package/dist/src/genomeSpy/containerUi.js +0 -14
  53. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +3 -7
  54. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -1
  55. package/dist/src/genomeSpy/loadingIndicatorManager.js +68 -20
  56. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts +52 -0
  57. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts.map +1 -0
  58. package/dist/src/genomeSpy/loadingStatusRegistry.js +86 -0
  59. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
  60. package/dist/src/genomeSpy/viewContextFactory.js +0 -1
  61. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
  62. package/dist/src/genomeSpy/viewDataInit.js +56 -11
  63. package/dist/src/genomeSpy.d.ts +0 -2
  64. package/dist/src/genomeSpy.d.ts.map +1 -1
  65. package/dist/src/genomeSpy.js +46 -26
  66. package/dist/src/marks/mark.d.ts.map +1 -1
  67. package/dist/src/marks/mark.js +18 -11
  68. package/dist/src/marks/markUtils.js +1 -1
  69. package/dist/src/scale/scale.d.ts +6 -1
  70. package/dist/src/scale/scale.d.ts.map +1 -1
  71. package/dist/src/scale/scale.js +83 -23
  72. package/dist/src/scales/axisResolution.d.ts.map +1 -1
  73. package/dist/src/scales/axisResolution.js +10 -0
  74. package/dist/src/scales/{scaleDomainAggregator.d.ts → domainPlanner.d.ts} +6 -3
  75. package/dist/src/scales/domainPlanner.d.ts.map +1 -0
  76. package/dist/src/scales/{scaleDomainAggregator.js → domainPlanner.js} +128 -10
  77. package/dist/src/scales/domainPlanner.test.d.ts +2 -0
  78. package/dist/src/scales/domainPlanner.test.d.ts.map +1 -0
  79. package/dist/src/scales/scaleInteractionController.d.ts +6 -0
  80. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  81. package/dist/src/scales/scaleInteractionController.js +41 -3
  82. package/dist/src/scales/scaleResolution.d.ts +19 -17
  83. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  84. package/dist/src/scales/scaleResolution.js +181 -70
  85. package/dist/src/scales/scaleResolution.test.d.ts.map +1 -1
  86. package/dist/src/selection/selection.d.ts +21 -0
  87. package/dist/src/selection/selection.d.ts.map +1 -1
  88. package/dist/src/selection/selection.js +82 -0
  89. package/dist/src/spec/channel.d.ts +52 -15
  90. package/dist/src/spec/data.d.ts +4 -0
  91. package/dist/src/spec/parameter.d.ts +16 -11
  92. package/dist/src/spec/testing.d.ts +12 -0
  93. package/dist/src/spec/testing.d.ts.map +1 -0
  94. package/dist/src/spec/testing.js +20 -0
  95. package/dist/src/spec/view.d.ts +45 -10
  96. package/dist/src/styles/genome-spy.css +3 -31
  97. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  98. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  99. package/dist/src/styles/genome-spy.css.js +0 -29
  100. package/dist/src/types/encoder.d.ts +37 -2
  101. package/dist/src/types/rendering.d.ts +4 -3
  102. package/dist/src/types/viewContext.d.ts +0 -14
  103. package/dist/src/utils/throttle.d.ts +4 -1
  104. package/dist/src/utils/throttle.d.ts.map +1 -1
  105. package/dist/src/utils/throttle.js +54 -23
  106. package/dist/src/utils/throttle.test.d.ts +2 -0
  107. package/dist/src/utils/throttle.test.d.ts.map +1 -0
  108. package/dist/src/utils/transition.d.ts +21 -0
  109. package/dist/src/utils/transition.d.ts.map +1 -1
  110. package/dist/src/utils/transition.js +28 -0
  111. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  112. package/dist/src/utils/ui/tooltip.js +7 -1
  113. package/dist/src/utils/ui/tooltip.test.d.ts +2 -0
  114. package/dist/src/utils/ui/tooltip.test.d.ts.map +1 -0
  115. package/dist/src/view/axisGridView.d.ts.map +1 -1
  116. package/dist/src/view/axisGridView.js +22 -5
  117. package/dist/src/view/axisView.d.ts.map +1 -1
  118. package/dist/src/view/axisView.js +20 -5
  119. package/dist/src/view/concatView.js +3 -3
  120. package/dist/src/view/containerMutationHelper.js +1 -1
  121. package/dist/src/view/containerView.d.ts +9 -5
  122. package/dist/src/view/containerView.d.ts.map +1 -1
  123. package/dist/src/view/containerView.js +34 -9
  124. package/dist/src/view/dataReadiness.d.ts +46 -0
  125. package/dist/src/view/dataReadiness.d.ts.map +1 -0
  126. package/dist/src/view/dataReadiness.js +267 -0
  127. package/dist/src/view/dataReadiness.test.d.ts +2 -0
  128. package/dist/src/view/dataReadiness.test.d.ts.map +1 -0
  129. package/dist/src/view/facetView.d.ts.map +1 -1
  130. package/dist/src/view/facetView.js +7 -5
  131. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  132. package/dist/src/view/flowBuilder.js +5 -1
  133. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  134. package/dist/src/view/gridView/gridChild.js +8 -0
  135. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  136. package/dist/src/view/gridView/gridView.js +119 -2
  137. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  138. package/dist/src/view/gridView/scrollbar.js +3 -0
  139. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  140. package/dist/src/view/gridView/selectionRect.js +20 -5
  141. package/dist/src/view/gridView/separatorView.d.ts +51 -0
  142. package/dist/src/view/gridView/separatorView.d.ts.map +1 -0
  143. package/dist/src/view/gridView/separatorView.js +275 -0
  144. package/dist/src/view/layerView.js +3 -3
  145. package/dist/src/view/layout/flexLayout.d.ts +0 -30
  146. package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
  147. package/dist/src/view/layout/flexLayout.js +0 -86
  148. package/dist/src/view/paramMediator.d.ts +19 -0
  149. package/dist/src/view/paramMediator.d.ts.map +1 -1
  150. package/dist/src/view/paramMediator.js +86 -19
  151. package/dist/src/view/testUtils.d.ts.map +1 -1
  152. package/dist/src/view/testUtils.js +6 -1
  153. package/dist/src/view/unitView.d.ts +8 -13
  154. package/dist/src/view/unitView.d.ts.map +1 -1
  155. package/dist/src/view/unitView.js +110 -41
  156. package/dist/src/view/view.d.ts +22 -14
  157. package/dist/src/view/view.d.ts.map +1 -1
  158. package/dist/src/view/view.js +93 -9
  159. package/dist/src/view/viewFactory.d.ts.map +1 -1
  160. package/dist/src/view/viewFactory.js +20 -1
  161. package/dist/src/view/viewSelectors.d.ts +148 -0
  162. package/dist/src/view/viewSelectors.d.ts.map +1 -0
  163. package/dist/src/view/viewSelectors.js +773 -0
  164. package/dist/src/view/viewSelectors.test.d.ts +2 -0
  165. package/dist/src/view/viewSelectors.test.d.ts.map +1 -0
  166. package/dist/src/view/viewUtils.d.ts +0 -8
  167. package/dist/src/view/viewUtils.d.ts.map +1 -1
  168. package/dist/src/view/viewUtils.js +1 -21
  169. package/package.json +3 -3
  170. package/dist/src/scales/scaleDomainAggregator.d.ts.map +0 -1
  171. package/dist/src/scales/scaleDomainAggregator.test.d.ts +0 -2
  172. package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +0 -1
@@ -13,7 +13,7 @@ import { configureDomain } from "../scale/scale.js";
13
13
 
14
14
  import ScaleInstanceManager from "./scaleInstanceManager.js";
15
15
  import { resolveScalePropsBase } from "./scalePropsResolver.js";
16
- import ScaleDomainAggregator from "./scaleDomainAggregator.js";
16
+ import DomainPlanner from "./domainPlanner.js";
17
17
  import ScaleInteractionController from "./scaleInteractionController.js";
18
18
  import {
19
19
  INDEX,
@@ -23,10 +23,10 @@ import {
23
23
  QUANTITATIVE,
24
24
  } from "./scaleResolutionConstants.js";
25
25
 
26
+ import { getAccessorDomainKey } from "../encoder/accessor.js";
26
27
  import { isSecondaryChannel } from "../encoder/encoder.js";
27
28
  import { NominalDomain } from "../utils/domainArray.js";
28
- import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
29
- import { VISIT_SKIP } from "../view/view.js";
29
+ import { shallowArrayEquals } from "../utils/arrayUtils.js";
30
30
  import createIndexer from "../utils/indexer.js";
31
31
 
32
32
  // Register scaleLocus to Vega-Scale.
@@ -44,7 +44,7 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
44
44
  * @prop {import("../view/unitView.js").default} view TODO: Get rid of the view reference
45
45
  * @prop {T} channel
46
46
  * @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
47
- * @prop {(channel: ChannelWithScale, type: import("../spec/channel.js").Type) => DomainArray} dataDomainSource
47
+ * @prop {boolean} contributesToDomain
48
48
  */
49
49
  /**
50
50
  * Resolves a shared scale for a channel by merging scale properties and domains
@@ -53,6 +53,16 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
53
53
  * notifications, while delegating domain aggregation, scale instance setup, and
54
54
  * interaction logic to focused helpers.
55
55
  *
56
+ * Documentation overview of current concerns this class (and its helpers) deal with:
57
+ * - Resolution membership and rules (shared/independent/forced/excluded, visibility, registration).
58
+ * - Scale property aggregation (merge props, channel overrides, unique scale names).
59
+ * - Domain computation and caching (configured/data unions, defaults, indexer stability, subscriptions).
60
+ * - Scale instance lifecycle (create, reconfigure props, apply domains, notify changes).
61
+ * - Interaction and zoom (zoom/pan/reset coordination, snapshots, zoom extents).
62
+ * - Rendering integration (range textures, axis sizing/positioning).
63
+ * - Locus-specific conversions (complex intervals, genome extent bindings).
64
+ * - Diagnostics and edge cases (ordinal unknown, nice/zero/padding, log warnings).
65
+ *
56
66
  * @implements {ScaleResolutionApi}
57
67
  */
58
68
  export default class ScaleResolution {
@@ -76,6 +86,9 @@ export default class ScaleResolution {
76
86
  /** @type {Set<ScaleResolutionMember>} The involved views */
77
87
  #members = new Set();
78
88
 
89
+ /** @type {Set<ScaleResolutionMember>} */
90
+ #dataDomainMembers = new Set();
91
+
79
92
  /**
80
93
  * @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
81
94
  */
@@ -87,7 +100,7 @@ export default class ScaleResolution {
87
100
  /** @type {ScaleInstanceManager} */
88
101
  #scaleManager;
89
102
 
90
- /** @type {ScaleDomainAggregator} */
103
+ /** @type {DomainPlanner} */
91
104
  #domainAggregator;
92
105
 
93
106
  /** @type {ScaleInteractionController} */
@@ -109,8 +122,10 @@ export default class ScaleResolution {
109
122
  /** @type {string} An optional unique identifier for the scale */
110
123
  this.name = undefined;
111
124
 
112
- this.#domainAggregator = new ScaleDomainAggregator({
125
+ this.#domainAggregator = new DomainPlanner({
113
126
  getMembers: () => this.#getActiveMembers(),
127
+ getDataMembers: () =>
128
+ this.#getActiveMembers(this.#dataDomainMembers),
114
129
  getType: () => this.type,
115
130
  getLocusExtent: () => this.#getLocusExtent(),
116
131
  fromComplexInterval: this.fromComplexInterval.bind(this),
@@ -146,10 +161,13 @@ export default class ScaleResolution {
146
161
  return first.view;
147
162
  }
148
163
 
149
- #getActiveMembers() {
164
+ /**
165
+ * @param {Set<ScaleResolutionMember>} [members]
166
+ */
167
+ #getActiveMembers(members = this.#members) {
150
168
  /** @type {Set<ScaleResolutionMember>} */
151
169
  const active = new Set();
152
- for (const member of this.#members) {
170
+ for (const member of members) {
153
171
  const view = member.view;
154
172
  if (!view.isConfiguredVisible()) {
155
173
  continue;
@@ -289,6 +307,10 @@ export default class ScaleResolution {
289
307
  }
290
308
 
291
309
  this.#members.add(newMember);
310
+ if (newMember.contributesToDomain) {
311
+ this.#dataDomainMembers.add(newMember);
312
+ }
313
+ this.#domainAggregator.invalidateConfiguredDomain();
292
314
  }
293
315
 
294
316
  /**
@@ -299,10 +321,62 @@ export default class ScaleResolution {
299
321
  this.#addMember(member);
300
322
  return () => {
301
323
  const removed = this.#members.delete(member);
324
+ if (removed) {
325
+ this.#dataDomainMembers.delete(member);
326
+ this.#domainAggregator.invalidateConfiguredDomain();
327
+ }
302
328
  return removed && this.#members.size === 0;
303
329
  };
304
330
  }
305
331
 
332
+ #hasRenderedMember() {
333
+ for (const member of this.#members) {
334
+ if (member.view.hasRendered()) {
335
+ return true;
336
+ }
337
+ }
338
+ return false;
339
+ }
340
+
341
+ /**
342
+ * @param {import("../data/collector.js").default} collector
343
+ * @param {Iterable<import("../types/encoder.js").ScaleAccessor>} accessors
344
+ * @returns {() => void}
345
+ */
346
+ registerCollectorSubscriptions(collector, accessors) {
347
+ /** @type {Set<string>} */
348
+ const domainKeys = new Set();
349
+
350
+ for (const accessor of accessors) {
351
+ if (accessor.channelDef.domainInert) {
352
+ continue;
353
+ }
354
+ domainKeys.add(getAccessorDomainKey(accessor, this.type));
355
+ }
356
+
357
+ if (domainKeys.size === 0) {
358
+ return () => undefined;
359
+ }
360
+
361
+ const listener = () => {
362
+ this.reconfigureDomain();
363
+ };
364
+
365
+ /** @type {(() => void)[]} */
366
+ const unregisters = [];
367
+ for (const domainKey of domainKeys) {
368
+ unregisters.push(
369
+ collector.subscribeDomainChanges(domainKey, listener)
370
+ );
371
+ }
372
+
373
+ return () => {
374
+ for (const unregister of unregisters) {
375
+ unregister();
376
+ }
377
+ };
378
+ }
379
+
306
380
  /**
307
381
  * Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
308
382
  */
@@ -432,36 +506,108 @@ export default class ScaleResolution {
432
506
  * or when scale properties are otherwise re-resolved from the view hierarchy.
433
507
  */
434
508
  reconfigure() {
435
- const props = this.#getScaleProps(true);
436
- this.#reconfigureWith(() => this.#scaleManager.reconfigureScale(props));
509
+ this.#domainAggregator.invalidateConfiguredDomain();
510
+ const state = this.#computeScaleState(true);
511
+ if (!state) {
512
+ return;
513
+ }
514
+ this.#applyReconfigure(state, (scale, props) =>
515
+ this.#scaleManager.reconfigureScale(props)
516
+ );
517
+ this.#finalizeReconfigure(state);
437
518
  }
438
519
 
439
520
  /**
440
521
  * Reconfigures only the effective domain (configured + data-derived).
441
522
  *
442
523
  * Use this when data changes but the scale membership and properties are stable.
524
+ *
443
525
  */
444
526
  reconfigureDomain() {
445
- const props = this.#getScaleProps(true);
446
- this.#reconfigureWith(() => {
447
- configureDomain(this.#scaleManager.scale, props);
448
- });
527
+ const state = this.#computeScaleState(true, true);
528
+ if (!state) {
529
+ return;
530
+ }
531
+ const { domainConfig, targetDomain } = state;
532
+ const domainMatches =
533
+ targetDomain != null &&
534
+ shallowArrayEquals(targetDomain, state.scale.domain());
535
+
536
+ if (targetDomain != null && !domainMatches) {
537
+ this.#applyReconfigure(state, (scale) => {
538
+ scale.domain(targetDomain);
539
+ if (domainConfig.applyOrdinalUnknown) {
540
+ // Keep ordinal unknown handling close to the domain write so
541
+ // domainImplicit semantics stay aligned with the applied domain.
542
+ /** @type {any} */ (scale).unknown(
543
+ domainConfig.ordinalUnknown
544
+ );
545
+ }
546
+ });
547
+ }
548
+ this.#finalizeReconfigure(state);
449
549
  }
450
550
 
451
551
  /**
452
- * @param {() => void} apply
552
+ * @param {boolean} extractDataDomain
553
+ * @param {boolean} [includeDomainConfig]
554
+ * @returns {{
555
+ * scale: ScaleWithProps,
556
+ * props: import("../spec/scale.js").Scale,
557
+ * previousDomain: any[],
558
+ * domainWasInitialized: boolean,
559
+ * domainConfig?: ReturnType<typeof configureDomain>,
560
+ * targetDomain?: any[] | null,
561
+ * } | undefined}
453
562
  */
454
- #reconfigureWith(apply) {
563
+ #computeScaleState(extractDataDomain, includeDomainConfig = false) {
455
564
  const scale = this.#scaleManager.scale;
456
565
 
457
566
  if (!scale || scale.type == "null") {
458
567
  return;
459
568
  }
460
569
 
461
- const domainWasInitialized = this.#isDomainInitialized();
462
- const previousDomain = scale.domain();
570
+ const state = {
571
+ scale,
572
+ props: this.#getScaleProps(extractDataDomain),
573
+ previousDomain: scale.domain(),
574
+ domainWasInitialized: this.#isDomainInitialized(),
575
+ };
576
+
577
+ if (includeDomainConfig) {
578
+ const domainConfig = configureDomain(scale, state.props);
579
+ return {
580
+ ...state,
581
+ domainConfig,
582
+ targetDomain: domainConfig.domain,
583
+ };
584
+ }
463
585
 
464
- this.#scaleManager.withDomainNotificationsSuppressed(apply);
586
+ return state;
587
+ }
588
+
589
+ /**
590
+ * @param {{
591
+ * scale: ScaleWithProps,
592
+ * props: import("../spec/scale.js").Scale,
593
+ * }} inputs
594
+ * @param {(scale: ScaleWithProps, props: import("../spec/scale.js").Scale) => void} apply
595
+ */
596
+ #applyReconfigure(inputs, apply) {
597
+ this.#scaleManager.withDomainNotificationsSuppressed(() => {
598
+ apply(inputs.scale, inputs.props);
599
+ });
600
+ }
601
+
602
+ /**
603
+ * @param {{
604
+ * scale: ScaleWithProps,
605
+ * previousDomain: any[],
606
+ * domainWasInitialized: boolean,
607
+ * }} inputs
608
+ */
609
+ #finalizeReconfigure(inputs) {
610
+ const { scale, previousDomain, domainWasInitialized } = inputs;
465
611
 
466
612
  if (
467
613
  this.#domainAggregator.captureInitialDomain(
@@ -475,13 +621,18 @@ export default class ScaleResolution {
475
621
  }
476
622
 
477
623
  const newDomain = scale.domain();
478
- if (!shallowArrayEquals(newDomain, previousDomain)) {
479
- if (this.isZoomable()) {
480
- // Don't mess with zoomed views, restore the previous domain
481
- this.#scaleManager.withDomainNotificationsSuppressed(() => {
482
- scale.domain(previousDomain);
483
- });
484
- } else if (this.#interactionController.isZoomingSupported()) {
624
+ const action = this.#interactionController.getDomainChangeAction(
625
+ previousDomain,
626
+ newDomain
627
+ );
628
+
629
+ if (action === "restore") {
630
+ // Don't mess with zoomed views, restore the previous domain
631
+ this.#scaleManager.withDomainNotificationsSuppressed(() => {
632
+ scale.domain(previousDomain);
633
+ });
634
+ } else if (action === "animate") {
635
+ if (this.#hasRenderedMember()) {
485
636
  // It can be zoomed, so lets make a smooth transition.
486
637
  // Restore the previous domain and zoom smoothly to the new domain.
487
638
  this.#scaleManager.withDomainNotificationsSuppressed(() => {
@@ -489,10 +640,12 @@ export default class ScaleResolution {
489
640
  });
490
641
  this.zoomTo(newDomain, 500); // TODO: Configurable duration
491
642
  } else {
492
- // Update immediately if the previous domain was the initial domain [0, 0]
493
- // Notifications were suppressed during reconfigure; notify explicitly.
494
643
  this.#notifyListeners("domain");
495
644
  }
645
+ } else if (action === "notify") {
646
+ // Update immediately if the previous domain was the initial domain [0, 0]
647
+ // Notifications were suppressed during reconfigure; notify explicitly.
648
+ this.#notifyListeners("domain");
496
649
  }
497
650
  }
498
651
 
@@ -688,45 +841,3 @@ export default class ScaleResolution {
688
841
  return /** @type {number[]} */ (interval);
689
842
  }
690
843
  }
691
-
692
- /**
693
- * Reconfigures scale domains for resolutions used by the given view(s).
694
- *
695
- * Use this for data-driven updates where only domains need refreshing.
696
- *
697
- * TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
698
- * for those views that get their data from the collector.
699
- *
700
- * TODO: This may reconfigure channels that are not affected by the change.
701
- * Causes performance issues with domains that are extracted from data.
702
- *
703
- * @param {import("../view/view.js").default | import("../view/view.js").default[]} fromViews
704
- * @param {(view: import("../view/view.js").default) => boolean} [viewFilter]
705
- */
706
- export function reconfigureScaleDomains(fromViews, viewFilter) {
707
- /** @type {Set<ScaleResolution>} */
708
- const uniqueResolutions = new Set();
709
-
710
- /** @param {import("../view/view.js").default} view */
711
- function collectResolutions(view) {
712
- for (const resolution of Object.values(view.resolutions.scale)) {
713
- uniqueResolutions.add(resolution);
714
- }
715
- }
716
-
717
- /** @type {import("../view/view.js").VisitorCallback} */
718
- function collectVisibleResolutions(view) {
719
- if (viewFilter && !viewFilter(view)) {
720
- return VISIT_SKIP;
721
- }
722
- if (view.options.contributesToScaleDomain) {
723
- collectResolutions(view);
724
- }
725
- }
726
-
727
- for (const fromView of asArray(fromViews)) {
728
- fromView.visit(collectVisibleResolutions);
729
- }
730
-
731
- uniqueResolutions.forEach((resolution) => resolution.reconfigureDomain());
732
- }
@@ -1 +1 @@
1
- {"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"sBAYa,OAAO,oBAAoB,EAAE,OAAO"}
1
+ {"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"sBAWa,OAAO,oBAAoB,EAAE,OAAO"}
@@ -8,6 +8,27 @@ export function createSinglePointSelection(datum: import("../data/flowNode.js").
8
8
  * @returns {import("../types/selectionTypes.js").MultiPointSelection}
9
9
  */
10
10
  export function createMultiPointSelection(data?: import("../data/flowNode.js").Datum[]): import("../types/selectionTypes.js").MultiPointSelection;
11
+ /**
12
+ * Returns key tuples for a point selection.
13
+ *
14
+ * @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
15
+ * @param {string[]} keyFields
16
+ * @returns {import("../spec/channel.js").Scalar[][] | undefined}
17
+ */
18
+ export function getPointSelectionKeyTuples(selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, keyFields: string[]): import("../spec/channel.js").Scalar[][] | undefined;
19
+ /**
20
+ * Resolves key tuples to a point selection value object.
21
+ *
22
+ * @param {"single" | "multi"} type
23
+ * @param {string[]} keyFields
24
+ * @param {import("../spec/channel.js").Scalar[][]} keyTuples
25
+ * @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
26
+ * @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
27
+ */
28
+ export function resolvePointSelectionFromKeyTuples(type: "single" | "multi", keyFields: string[], keyTuples: import("../spec/channel.js").Scalar[][], resolveDatum: (keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined): {
29
+ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection;
30
+ unresolved: import("../spec/channel.js").Scalar[][];
31
+ } | undefined;
11
32
  /**
12
33
  *
13
34
  * @param {import("../spec/channel.js").ChannelWithScale[]} channels
@@ -1 +1 @@
1
- {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH,kDAHW,OAAO,qBAAqB,EAAE,KAAK,GACjC,OAAO,4BAA4B,EAAE,oBAAoB,CAQrE;AAED;;;GAGG;AACH,iDAHW,OAAO,qBAAqB,EAAE,KAAK,EAAE,GACnC,OAAO,4BAA4B,EAAE,mBAAmB,CAQpE;AAED;;;;GAIG;AACH,kDAHW,OAAO,oBAAoB,EAAE,gBAAgB,EAAE,GAC7C,OAAO,4BAA4B,EAAE,iBAAiB,CASlE;AAED;;;;;;;GAOG;AACH,qDAJW,OAAO,4BAA4B,EAAE,mBAAmB,2BACxD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,QAAQ,CAAC,OAAO,qBAAqB,EAAE,KAAK,CAAC,CAAC,CAAC,GACzF,OAAO,4BAA4B,EAAE,mBAAmB,CA2BpE;AAED;;;;;GAKG;AACH,oDAHW,OAAO,sBAAsB,EAAE,qBAAqB,aACpD,OAAO,4BAA4B,EAAE,SAAS,UAgExD;AAED;;;GAGG;AACH,+CAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,iBAAiB,CAI/E;AAED;;;GAGG;AACH,kDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,oBAAoB,CAIlF;AAED;;;GAGG;AACH,iDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,mBAAmB,CAIjF;AAED;;;GAGG;AACH,gDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,kBAAkB,CAIhF;AAED;;;GAGG;AACH,gDAHW,OAAO,sBAAsB,EAAE,qBAAqB,GAClD,OAAO,sBAAsB,EAAE,eAAe,CA4B1D;AAED;;;GAGG;AACH,+CAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,oBAAoB,CAIzE;AAED;;;;GAIG;AACH,kDAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,uBAAuB,CAI5E;AAED;;GAEG;AACH,qDAFW,OAAO,4BAA4B,EAAE,iBAAiB,WAMhE;AAED;;;;;GAKG;AACH,kDAHW,iBAAiB,SACjB,aAAa,WAUvB;AAED;;;GAGG;AACH,yCAHW,OAAO,sBAAsB,EAAE,eAAe,CAAC,IAAI,CAAC,GAClD,OAAO,sBAAsB,EAAE,WAAW,CAsBtD;gCAvCY,OAAO,4BAA4B,EAAE,iBAAiB;4BACtD,OAAO,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,kDAHW,OAAO,qBAAqB,EAAE,KAAK,GACjC,OAAO,4BAA4B,EAAE,oBAAoB,CAQrE;AAED;;;GAGG;AACH,iDAHW,OAAO,qBAAqB,EAAE,KAAK,EAAE,GACnC,OAAO,4BAA4B,EAAE,mBAAmB,CAQpE;AAED;;;;;;GAMG;AACH,sDAJW,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,aACpH,MAAM,EAAE,GACN,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CA2B/D;AAED;;;;;;;;GAQG;AACH,yDANW,QAAQ,GAAG,OAAO,aAClB,MAAM,EAAE,aACR,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,gBACvC,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,KAAK,OAAO,qBAAqB,EAAE,KAAK,GAAG,SAAS,GACvH;IAAE,SAAS,EAAE,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,GAAG,SAAS,CAsChN;AAED;;;;GAIG;AACH,kDAHW,OAAO,oBAAoB,EAAE,gBAAgB,EAAE,GAC7C,OAAO,4BAA4B,EAAE,iBAAiB,CASlE;AAED;;;;;;;GAOG;AACH,qDAJW,OAAO,4BAA4B,EAAE,mBAAmB,2BACxD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,QAAQ,CAAC,OAAO,qBAAqB,EAAE,KAAK,CAAC,CAAC,CAAC,GACzF,OAAO,4BAA4B,EAAE,mBAAmB,CA2BpE;AAED;;;;;GAKG;AACH,oDAHW,OAAO,sBAAsB,EAAE,qBAAqB,aACpD,OAAO,4BAA4B,EAAE,SAAS,UAgExD;AAED;;;GAGG;AACH,+CAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,iBAAiB,CAI/E;AAED;;;GAGG;AACH,kDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,oBAAoB,CAIlF;AAED;;;GAGG;AACH,iDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,mBAAmB,CAIjF;AAED;;;GAGG;AACH,gDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,kBAAkB,CAIhF;AAED;;;GAGG;AACH,gDAHW,OAAO,sBAAsB,EAAE,qBAAqB,GAClD,OAAO,sBAAsB,EAAE,eAAe,CA4B1D;AAED;;;GAGG;AACH,+CAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,oBAAoB,CAIzE;AAED;;;;GAIG;AACH,kDAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,uBAAuB,CAI5E;AAED;;GAEG;AACH,qDAFW,OAAO,4BAA4B,EAAE,iBAAiB,WAMhE;AAED;;;;;GAKG;AACH,kDAHW,iBAAiB,SACjB,aAAa,WAUvB;AAED;;;GAGG;AACH,yCAHW,OAAO,sBAAsB,EAAE,eAAe,CAAC,IAAI,CAAC,GAClD,OAAO,sBAAsB,EAAE,WAAW,CAsBtD;gCAvCY,OAAO,4BAA4B,EAAE,iBAAiB;4BACtD,OAAO,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC"}
@@ -4,6 +4,7 @@ import {
4
4
  isPrimaryPositionalChannel,
5
5
  } from "../encoder/encoder.js";
6
6
  import { validateParameterName } from "../view/paramMediator.js";
7
+ import { field } from "../utils/field.js";
7
8
 
8
9
  /**
9
10
  * @param {import("../data/flowNode.js").Datum} datum
@@ -29,6 +30,87 @@ export function createMultiPointSelection(data) {
29
30
  };
30
31
  }
31
32
 
33
+ /**
34
+ * Returns key tuples for a point selection.
35
+ *
36
+ * @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
37
+ * @param {string[]} keyFields
38
+ * @returns {import("../spec/channel.js").Scalar[][] | undefined}
39
+ */
40
+ export function getPointSelectionKeyTuples(selection, keyFields) {
41
+ if (!keyFields || keyFields.length === 0) {
42
+ return;
43
+ }
44
+
45
+ const accessors = keyFields.map((fieldName) => field(fieldName));
46
+ const toTuple = (
47
+ /** @type {import("../data/flowNode.js").Datum} */ datum
48
+ ) => accessors.map((accessor) => accessor(datum));
49
+
50
+ if (isSinglePointSelection(selection)) {
51
+ if (!selection.datum) {
52
+ return [];
53
+ }
54
+
55
+ return [toTuple(selection.datum)];
56
+ }
57
+
58
+ if (isMultiPointSelection(selection)) {
59
+ return [...selection.data.values()].map(toTuple);
60
+ }
61
+
62
+ throw new Error(
63
+ `Expected a point selection, got: ${JSON.stringify(selection)}`
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Resolves key tuples to a point selection value object.
69
+ *
70
+ * @param {"single" | "multi"} type
71
+ * @param {string[]} keyFields
72
+ * @param {import("../spec/channel.js").Scalar[][]} keyTuples
73
+ * @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
74
+ * @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
75
+ */
76
+ export function resolvePointSelectionFromKeyTuples(
77
+ type,
78
+ keyFields,
79
+ keyTuples,
80
+ resolveDatum
81
+ ) {
82
+ if (!keyFields || keyFields.length === 0) {
83
+ return;
84
+ }
85
+
86
+ if (type === "single" && keyTuples.length > 1) {
87
+ throw new Error(
88
+ "Single point selections expect at most one key tuple."
89
+ );
90
+ }
91
+
92
+ /** @type {import("../data/flowNode.js").Datum[]} */
93
+ const datums = [];
94
+ /** @type {import("../spec/channel.js").Scalar[][]} */
95
+ const unresolved = [];
96
+
97
+ for (const tuple of keyTuples) {
98
+ const datum = resolveDatum(keyFields, tuple);
99
+ if (datum) {
100
+ datums.push(datum);
101
+ } else {
102
+ unresolved.push(tuple);
103
+ }
104
+ }
105
+
106
+ const selection =
107
+ type === "single"
108
+ ? createSinglePointSelection(datums[0] ?? null)
109
+ : createMultiPointSelection(datums);
110
+
111
+ return { selection, unresolved };
112
+ }
113
+
32
114
  /**
33
115
  *
34
116
  * @param {import("../spec/channel.js").ChannelWithScale[]} channels
@@ -46,6 +46,7 @@ export type ChannelWithoutScale =
46
46
  | "uniqueId"
47
47
  | "search"
48
48
  | "text"
49
+ | "key"
49
50
  | "facetIndex"
50
51
  | "semanticScore"
51
52
  | "uniqueId"
@@ -121,9 +122,13 @@ export type TypedFieldDef<T extends Type = Type> = FieldDefBase &
121
122
  TitleMixins &
122
123
  TypeMixins<T>;
123
124
 
124
- export type ScaleFieldDef<T extends Type> = TypedFieldDef<T> & ScaleMixins;
125
+ export type ScaleFieldDef<T extends Type> = TypedFieldDef<T> &
126
+ ScaleMixins &
127
+ DomainContributionMixins;
125
128
 
126
129
  export type FieldDefWithoutScale = FieldDefBase & TitleMixins;
130
+ export type KeyDef = FieldDefWithoutScale | FieldDefWithoutScale[];
131
+ export type SearchDef = FieldDefWithoutScale | FieldDefWithoutScale[];
127
132
 
128
133
  export interface ScaleMixins {
129
134
  /**
@@ -145,15 +150,18 @@ export interface ScaleMixins {
145
150
  * @internal
146
151
  */
147
152
  resolutionChannel?: ChannelWithScale;
153
+ }
148
154
 
155
+ export interface DomainContributionMixins {
149
156
  /**
150
- * Whether the field or evaluated expr should be included in the scale's domain.
157
+ * Whether the field or evaluated expr should be excluded from the scale's domain.
158
+ * Prefer the view-level `domainInert` when an entire subtree should be excluded.
151
159
  *
152
- * **Default value:** `true`
160
+ * **Default value:** `false`
153
161
  *
154
162
  * @internal
155
163
  */
156
- contributesToScaleDomain?: boolean;
164
+ domainInert?: boolean;
157
165
  }
158
166
 
159
167
  export interface ValueDefBase<V extends Value = Scalar> {
@@ -190,6 +198,7 @@ export type MarkPropFieldDef<T extends Type = Type> = ScaleFieldDef<T> &
190
198
  export type MarkPropExprDef<T extends Type = Type> = ExprDef &
191
199
  TypeMixins<T> &
192
200
  ScaleMixins &
201
+ DomainContributionMixins &
193
202
  TitleMixins;
194
203
 
195
204
  export type MarkPropDatumDef<T extends Type> = LegendMixins &
@@ -279,16 +288,18 @@ export type MarkPropFieldOrDatumOrExprDef<T extends Type = Type> =
279
288
 
280
289
  export type MarkPropDef<V extends Value, T extends Type = Type> =
281
290
  | FieldOrDatumDefWithCondition<MarkPropFieldDef<T>, V>
282
- | FieldOrDatumDefWithCondition<DatumDef, V>
291
+ | FieldOrDatumDefWithCondition<ScaleDatumDef, V>
283
292
  | ValueDefWithCondition<V>;
284
293
 
285
294
  export type ColorDef = MarkPropDef<string | null>;
286
295
 
287
- export type SecondaryFieldDef = FieldDefBase & TitleMixins;
296
+ export type SecondaryFieldDef = FieldDefBase &
297
+ TitleMixins &
298
+ DomainContributionMixins;
288
299
 
289
300
  export type NumericValueDef = ValueDef<number>;
290
301
 
291
- export type ScaleDatumDef = ScaleMixins & DatumDef;
302
+ export type ScaleDatumDef = ScaleMixins & DatumDef & DomainContributionMixins;
292
303
 
293
304
  export type PositionDatumDefBase = ScaleDatumDef & TypeMixins<Type>;
294
305
 
@@ -299,7 +310,8 @@ export type PositionDatumDef = PositionDatumDefBase & PositionMixins;
299
310
  export type PositionExprDef = ExprDef &
300
311
  PositionMixins &
301
312
  BandMixins &
302
- TypeMixins<Type>;
313
+ TypeMixins<Type> &
314
+ DomainContributionMixins;
303
315
 
304
316
  export type PositionValueDef = NumericValueDef;
305
317
 
@@ -344,7 +356,8 @@ export interface ChromPosDefBase extends BandMixins {
344
356
 
345
357
  export type SecondaryChromPosDef = ChromPosDefBase &
346
358
  TitleMixins &
347
- PositionMixins;
359
+ PositionMixins &
360
+ DomainContributionMixins;
348
361
 
349
362
  export type ChromPosDef = SecondaryChromPosDef &
350
363
  TypeMixins<"locus"> &
@@ -360,8 +373,8 @@ export type PositionDef =
360
373
  export type Position2Def =
361
374
  | (SecondaryFieldDef & BandMixins)
362
375
  | SecondaryChromPosDef
363
- | (DatumDef & BandMixins)
364
- | (ExprDef & BandMixins)
376
+ | (ScaleDatumDef & BandMixins)
377
+ | (ExprDef & BandMixins & DomainContributionMixins)
365
378
  | PositionValueDef;
366
379
 
367
380
  export type NumericMarkPropDef = MarkPropDef<number>;
@@ -372,10 +385,15 @@ export interface StringFieldDef extends FieldDefWithoutScale, FormatMixins {}
372
385
 
373
386
  export type TextDef = StringFieldDef | StringDatumDef | ExprDef; // TODO: Conditions
374
387
 
375
- export type ChannelDef = Encoding[keyof Encoding];
388
+ export type ChannelDef = Exclude<
389
+ Encoding[keyof Encoding],
390
+ FieldDefWithoutScale[]
391
+ >;
376
392
 
377
393
  // TODO: Does this make sense?
378
- export type ChannelDefWithScale = ScaleMixins & TypeMixins<Type>;
394
+ export type ChannelDefWithScale = ScaleMixins &
395
+ TypeMixins<Type> &
396
+ DomainContributionMixins;
379
397
 
380
398
  export interface XIndexDef {
381
399
  /**
@@ -497,14 +515,33 @@ export interface Encoding {
497
515
  */
498
516
  sample?: FieldDefWithoutScale;
499
517
 
518
+ /**
519
+ * One or more data fields that uniquely identify data objects for stable
520
+ * point selections and bookmarking across sessions. Unlike `uniqueId` (an
521
+ * implicit surrogate key), key fields must be stable in the source data.
522
+ *
523
+ * Use a single field definition for simple keys, or an array of field
524
+ * definitions for composite keys. For composite keys, field order is
525
+ * significant.
526
+ */
527
+ key?: KeyDef;
528
+
500
529
  /**
501
530
  * For internal use
531
+ * @internal
502
532
  */
503
533
  // TODO: proper type
504
534
  uniqueId?: FieldDefWithoutScale;
505
535
 
506
- // TODO: proper type
507
- search?: FieldDefWithoutScale;
536
+ /**
537
+ * One or more fields used by the App's location/search input to match
538
+ * data objects in this view.
539
+ *
540
+ * Use a single field definition for simple search, or an array for
541
+ * matching against multiple fields. A datum matches when any configured
542
+ * search field matches the entered term.
543
+ */
544
+ search?: SearchDef;
508
545
 
509
546
  /**
510
547
  * For internal use
@@ -414,3 +414,7 @@ export interface Gff3Data extends TabixData {
414
414
  export interface VcfData extends TabixData {
415
415
  type: "vcf";
416
416
  }
417
+
418
+ /**
419
+ * Testing-only lazy data source. Not intended for production use.
420
+ */