@genome-spy/core 0.67.0 → 0.69.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 (290) hide show
  1. package/dist/bundle/index.es.js +15884 -13130
  2. package/dist/bundle/index.js +130 -149
  3. package/dist/schema.json +6498 -6191
  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 +20 -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 +33 -10
  13. package/dist/src/data/flowNode.d.ts.map +1 -1
  14. package/dist/src/data/flowNode.js +84 -13
  15. package/dist/src/data/flowTestUtils.d.ts +2 -2
  16. package/dist/src/data/flowTestUtils.d.ts.map +1 -1
  17. package/dist/src/data/flowTestUtils.js +5 -4
  18. package/dist/src/data/keyIndex.d.ts +18 -0
  19. package/dist/src/data/keyIndex.d.ts.map +1 -0
  20. package/dist/src/data/keyIndex.js +241 -0
  21. package/dist/src/data/keyIndex.test.d.ts +2 -0
  22. package/dist/src/data/keyIndex.test.d.ts.map +1 -0
  23. package/dist/src/data/sources/dataSource.d.ts.map +1 -1
  24. package/dist/src/data/sources/dataSource.js +7 -3
  25. package/dist/src/data/sources/dataSourceFactory.d.ts +14 -12
  26. package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
  27. package/dist/src/data/sources/dataSourceFactory.js +52 -16
  28. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  29. package/dist/src/data/sources/lazy/bigBedSource.js +11 -10
  30. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  31. package/dist/src/data/sources/lazy/bigWigSource.js +11 -10
  32. package/dist/src/data/sources/lazy/mockLazySource.d.ts +29 -0
  33. package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -0
  34. package/dist/src/data/sources/lazy/mockLazySource.js +44 -0
  35. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +22 -1
  36. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  37. package/dist/src/data/sources/lazy/singleAxisLazySource.js +34 -2
  38. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  39. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +16 -1
  40. package/dist/src/data/sources/lazy/tabixSource.d.ts +0 -1
  41. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  42. package/dist/src/data/sources/lazy/tabixSource.js +56 -16
  43. package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
  44. package/dist/src/data/sources/sequenceSource.js +5 -3
  45. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  46. package/dist/src/data/sources/urlSource.js +7 -3
  47. package/dist/src/data/transforms/filter.d.ts +4 -4
  48. package/dist/src/data/transforms/filter.d.ts.map +1 -1
  49. package/dist/src/data/transforms/filter.js +13 -7
  50. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  51. package/dist/src/data/transforms/filterScoredLabels.js +11 -6
  52. package/dist/src/data/transforms/filterScoredLabels.test.d.ts +2 -0
  53. package/dist/src/data/transforms/filterScoredLabels.test.d.ts.map +1 -0
  54. package/dist/src/data/transforms/formula.d.ts +4 -4
  55. package/dist/src/data/transforms/formula.d.ts.map +1 -1
  56. package/dist/src/data/transforms/formula.js +12 -6
  57. package/dist/src/data/transforms/measureText.d.ts +2 -2
  58. package/dist/src/data/transforms/measureText.d.ts.map +1 -1
  59. package/dist/src/data/transforms/measureText.js +16 -12
  60. package/dist/src/data/transforms/stack.d.ts.map +1 -1
  61. package/dist/src/data/transforms/stack.js +1 -0
  62. package/dist/src/data/transforms/transform.d.ts +2 -2
  63. package/dist/src/data/transforms/transform.d.ts.map +1 -1
  64. package/dist/src/data/transforms/transform.js +3 -3
  65. package/dist/src/encoder/accessor.d.ts +51 -4
  66. package/dist/src/encoder/accessor.d.ts.map +1 -1
  67. package/dist/src/encoder/accessor.js +174 -10
  68. package/dist/src/encoder/encoder.d.ts +11 -2
  69. package/dist/src/encoder/encoder.d.ts.map +1 -1
  70. package/dist/src/encoder/encoder.js +29 -9
  71. package/dist/src/encoder/metadataChannels.d.ts +15 -0
  72. package/dist/src/encoder/metadataChannels.d.ts.map +1 -0
  73. package/dist/src/encoder/metadataChannels.js +65 -0
  74. package/dist/src/encoder/metadataChannels.test.d.ts +2 -0
  75. package/dist/src/encoder/metadataChannels.test.d.ts.map +1 -0
  76. package/dist/src/genome/genome.d.ts +8 -0
  77. package/dist/src/genome/genome.d.ts.map +1 -1
  78. package/dist/src/genome/genome.js +16 -1
  79. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  80. package/dist/src/genome/scaleLocus.js +14 -1
  81. package/dist/src/genomeSpy/containerUi.d.ts +0 -1
  82. package/dist/src/genomeSpy/containerUi.d.ts.map +1 -1
  83. package/dist/src/genomeSpy/containerUi.js +0 -14
  84. package/dist/src/genomeSpy/inputBindingManager.js +1 -1
  85. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  86. package/dist/src/genomeSpy/interactionController.js +7 -1
  87. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +3 -7
  88. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -1
  89. package/dist/src/genomeSpy/loadingIndicatorManager.js +68 -20
  90. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts +52 -0
  91. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts.map +1 -0
  92. package/dist/src/genomeSpy/loadingStatusRegistry.js +86 -0
  93. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
  94. package/dist/src/genomeSpy/viewContextFactory.js +0 -1
  95. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
  96. package/dist/src/genomeSpy/viewDataInit.js +56 -11
  97. package/dist/src/genomeSpy.d.ts +0 -2
  98. package/dist/src/genomeSpy.d.ts.map +1 -1
  99. package/dist/src/genomeSpy.js +47 -27
  100. package/dist/src/gl/glslScaleGenerator.js +1 -1
  101. package/dist/src/marks/mark.d.ts.map +1 -1
  102. package/dist/src/marks/mark.js +40 -41
  103. package/dist/src/marks/markUtils.js +1 -1
  104. package/dist/src/marks/point.d.ts.map +1 -1
  105. package/dist/src/marks/point.js +4 -6
  106. package/dist/src/paramRuntime/expressionCompiler.d.ts +7 -0
  107. package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -0
  108. package/dist/src/paramRuntime/expressionCompiler.js +10 -0
  109. package/dist/src/paramRuntime/expressionRef.d.ts +20 -0
  110. package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -0
  111. package/dist/src/paramRuntime/expressionRef.js +95 -0
  112. package/dist/src/paramRuntime/expressionRef.test.d.ts +2 -0
  113. package/dist/src/paramRuntime/expressionRef.test.d.ts.map +1 -0
  114. package/dist/src/paramRuntime/graphRuntime.d.ts +176 -0
  115. package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -0
  116. package/dist/src/paramRuntime/graphRuntime.js +628 -0
  117. package/dist/src/paramRuntime/graphRuntime.test.d.ts +2 -0
  118. package/dist/src/paramRuntime/graphRuntime.test.d.ts.map +1 -0
  119. package/dist/src/paramRuntime/index.d.ts +9 -0
  120. package/dist/src/paramRuntime/index.d.ts.map +1 -0
  121. package/dist/src/paramRuntime/index.js +8 -0
  122. package/dist/src/paramRuntime/lifecycleRegistry.d.ts +27 -0
  123. package/dist/src/paramRuntime/lifecycleRegistry.d.ts.map +1 -0
  124. package/dist/src/paramRuntime/lifecycleRegistry.js +54 -0
  125. package/dist/src/paramRuntime/paramRuntime.d.ts +165 -0
  126. package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -0
  127. package/dist/src/paramRuntime/paramRuntime.js +222 -0
  128. package/dist/src/paramRuntime/paramRuntime.test.d.ts +2 -0
  129. package/dist/src/paramRuntime/paramRuntime.test.d.ts.map +1 -0
  130. package/dist/src/paramRuntime/paramStore.d.ts +68 -0
  131. package/dist/src/paramRuntime/paramStore.d.ts.map +1 -0
  132. package/dist/src/paramRuntime/paramStore.js +148 -0
  133. package/dist/src/paramRuntime/paramStore.test.d.ts +2 -0
  134. package/dist/src/paramRuntime/paramStore.test.d.ts.map +1 -0
  135. package/dist/src/paramRuntime/paramUtils.d.ts +86 -0
  136. package/dist/src/paramRuntime/paramUtils.d.ts.map +1 -0
  137. package/dist/src/paramRuntime/paramUtils.js +272 -0
  138. package/dist/src/paramRuntime/selectionStore.d.ts +6 -0
  139. package/dist/src/paramRuntime/selectionStore.d.ts.map +1 -0
  140. package/dist/src/paramRuntime/selectionStore.js +13 -0
  141. package/dist/src/paramRuntime/types.d.ts +16 -0
  142. package/dist/src/paramRuntime/types.d.ts.map +1 -0
  143. package/dist/src/paramRuntime/types.js +25 -0
  144. package/dist/src/paramRuntime/viewParamRuntime.d.ts +164 -0
  145. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -0
  146. package/dist/src/paramRuntime/viewParamRuntime.js +443 -0
  147. package/dist/src/scale/scale.d.ts +6 -1
  148. package/dist/src/scale/scale.d.ts.map +1 -1
  149. package/dist/src/scale/scale.js +83 -23
  150. package/dist/src/scales/axisResolution.d.ts.map +1 -1
  151. package/dist/src/scales/axisResolution.js +10 -0
  152. package/dist/src/scales/{scaleDomainAggregator.d.ts → domainPlanner.d.ts} +6 -3
  153. package/dist/src/scales/domainPlanner.d.ts.map +1 -0
  154. package/dist/src/scales/{scaleDomainAggregator.js → domainPlanner.js} +128 -10
  155. package/dist/src/scales/domainPlanner.test.d.ts +2 -0
  156. package/dist/src/scales/domainPlanner.test.d.ts.map +1 -0
  157. package/dist/src/scales/scaleInstanceManager.d.ts +6 -3
  158. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  159. package/dist/src/scales/scaleInstanceManager.js +17 -11
  160. package/dist/src/scales/scaleInteractionController.d.ts +6 -0
  161. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  162. package/dist/src/scales/scaleInteractionController.js +41 -3
  163. package/dist/src/scales/scaleResolution.d.ts +20 -17
  164. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  165. package/dist/src/scales/scaleResolution.js +188 -71
  166. package/dist/src/scales/scaleResolution.test.d.ts.map +1 -1
  167. package/dist/src/selection/selection.d.ts +21 -0
  168. package/dist/src/selection/selection.d.ts.map +1 -1
  169. package/dist/src/selection/selection.js +83 -1
  170. package/dist/src/spec/channel.d.ts +52 -15
  171. package/dist/src/spec/coreSchemaRoot.d.ts +53 -0
  172. package/dist/src/spec/data.d.ts +4 -0
  173. package/dist/src/spec/parameter.d.ts +16 -11
  174. package/dist/src/spec/root.d.ts +1 -1
  175. package/dist/src/spec/testing.d.ts +12 -0
  176. package/dist/src/spec/testing.d.ts.map +1 -0
  177. package/dist/src/spec/testing.js +20 -0
  178. package/dist/src/spec/view.d.ts +157 -41
  179. package/dist/src/styles/genome-spy.css +3 -31
  180. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  181. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  182. package/dist/src/styles/genome-spy.css.js +0 -29
  183. package/dist/src/tooltip/dataTooltipHandler.d.ts +1 -1
  184. package/dist/src/tooltip/dataTooltipHandler.js +23 -32
  185. package/dist/src/tooltip/dataTooltipHandler.test.d.ts +2 -0
  186. package/dist/src/tooltip/dataTooltipHandler.test.d.ts.map +1 -0
  187. package/dist/src/tooltip/flattenDatumRows.d.ts +13 -0
  188. package/dist/src/tooltip/flattenDatumRows.d.ts.map +1 -0
  189. package/dist/src/tooltip/flattenDatumRows.js +47 -0
  190. package/dist/src/tooltip/flattenDatumRows.test.d.ts +2 -0
  191. package/dist/src/tooltip/flattenDatumRows.test.d.ts.map +1 -0
  192. package/dist/src/tooltip/refseqGeneTooltipHandler.d.ts +1 -1
  193. package/dist/src/tooltip/refseqGeneTooltipHandler.js +7 -1
  194. package/dist/src/tooltip/tooltipContext.d.ts +13 -0
  195. package/dist/src/tooltip/tooltipContext.d.ts.map +1 -0
  196. package/dist/src/tooltip/tooltipContext.js +543 -0
  197. package/dist/src/tooltip/tooltipContext.test.d.ts +2 -0
  198. package/dist/src/tooltip/tooltipContext.test.d.ts.map +1 -0
  199. package/dist/src/tooltip/tooltipHandler.d.ts +40 -1
  200. package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
  201. package/dist/src/tooltip/tooltipHandler.ts +62 -1
  202. package/dist/src/types/encoder.d.ts +38 -3
  203. package/dist/src/types/rendering.d.ts +4 -3
  204. package/dist/src/types/viewContext.d.ts +0 -14
  205. package/dist/src/utils/inputBinding.d.ts +10 -2
  206. package/dist/src/utils/inputBinding.d.ts.map +1 -1
  207. package/dist/src/utils/inputBinding.js +12 -3
  208. package/dist/src/utils/throttle.d.ts +4 -1
  209. package/dist/src/utils/throttle.d.ts.map +1 -1
  210. package/dist/src/utils/throttle.js +54 -23
  211. package/dist/src/utils/throttle.test.d.ts +2 -0
  212. package/dist/src/utils/throttle.test.d.ts.map +1 -0
  213. package/dist/src/utils/transition.d.ts +21 -0
  214. package/dist/src/utils/transition.d.ts.map +1 -1
  215. package/dist/src/utils/transition.js +28 -0
  216. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  217. package/dist/src/utils/ui/tooltip.js +7 -1
  218. package/dist/src/utils/ui/tooltip.test.d.ts +2 -0
  219. package/dist/src/utils/ui/tooltip.test.d.ts.map +1 -0
  220. package/dist/src/view/axisGridView.d.ts.map +1 -1
  221. package/dist/src/view/axisGridView.js +22 -5
  222. package/dist/src/view/axisView.d.ts.map +1 -1
  223. package/dist/src/view/axisView.js +20 -5
  224. package/dist/src/view/concatView.js +3 -3
  225. package/dist/src/view/containerMutationHelper.js +1 -1
  226. package/dist/src/view/containerView.d.ts +9 -5
  227. package/dist/src/view/containerView.d.ts.map +1 -1
  228. package/dist/src/view/containerView.js +34 -9
  229. package/dist/src/view/dataReadiness.d.ts +46 -0
  230. package/dist/src/view/dataReadiness.d.ts.map +1 -0
  231. package/dist/src/view/dataReadiness.js +267 -0
  232. package/dist/src/view/dataReadiness.test.d.ts +2 -0
  233. package/dist/src/view/dataReadiness.test.d.ts.map +1 -0
  234. package/dist/src/view/facetView.d.ts.map +1 -1
  235. package/dist/src/view/facetView.js +7 -5
  236. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  237. package/dist/src/view/flowBuilder.js +17 -4
  238. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  239. package/dist/src/view/gridView/gridChild.js +16 -3
  240. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  241. package/dist/src/view/gridView/gridView.js +119 -2
  242. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  243. package/dist/src/view/gridView/scrollbar.js +3 -0
  244. package/dist/src/view/gridView/selectionRect.d.ts +6 -10
  245. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  246. package/dist/src/view/gridView/selectionRect.js +22 -24
  247. package/dist/src/view/gridView/separatorView.d.ts +51 -0
  248. package/dist/src/view/gridView/separatorView.d.ts.map +1 -0
  249. package/dist/src/view/gridView/separatorView.js +275 -0
  250. package/dist/src/view/layerView.d.ts.map +1 -1
  251. package/dist/src/view/layerView.js +7 -5
  252. package/dist/src/view/layout/flexLayout.d.ts +0 -30
  253. package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
  254. package/dist/src/view/layout/flexLayout.js +0 -86
  255. package/dist/src/view/multiscale.d.ts +35 -0
  256. package/dist/src/view/multiscale.d.ts.map +1 -0
  257. package/dist/src/view/multiscale.js +233 -0
  258. package/dist/src/view/multiscale.test.d.ts +2 -0
  259. package/dist/src/view/multiscale.test.d.ts.map +1 -0
  260. package/dist/src/view/testUtils.d.ts.map +1 -1
  261. package/dist/src/view/testUtils.js +6 -1
  262. package/dist/src/view/unitView.d.ts +8 -13
  263. package/dist/src/view/unitView.d.ts.map +1 -1
  264. package/dist/src/view/unitView.js +120 -45
  265. package/dist/src/view/view.d.ts +27 -18
  266. package/dist/src/view/view.d.ts.map +1 -1
  267. package/dist/src/view/view.js +298 -37
  268. package/dist/src/view/viewFactory.d.ts +0 -12
  269. package/dist/src/view/viewFactory.d.ts.map +1 -1
  270. package/dist/src/view/viewFactory.js +55 -25
  271. package/dist/src/view/viewParamRuntime.test.d.ts +2 -0
  272. package/dist/src/view/viewParamRuntime.test.d.ts.map +1 -0
  273. package/dist/src/view/viewSelectors.d.ts +148 -0
  274. package/dist/src/view/viewSelectors.d.ts.map +1 -0
  275. package/dist/src/view/viewSelectors.js +776 -0
  276. package/dist/src/view/viewSelectors.test.d.ts +2 -0
  277. package/dist/src/view/viewSelectors.test.d.ts.map +1 -0
  278. package/dist/src/view/viewUtils.d.ts +0 -8
  279. package/dist/src/view/viewUtils.d.ts.map +1 -1
  280. package/dist/src/view/viewUtils.js +1 -21
  281. package/package.json +4 -4
  282. package/dist/src/scales/scaleDomainAggregator.d.ts.map +0 -1
  283. package/dist/src/scales/scaleDomainAggregator.test.d.ts +0 -2
  284. package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +0 -1
  285. package/dist/src/spec/sampleView.d.ts +0 -197
  286. package/dist/src/view/paramMediator.d.ts +0 -149
  287. package/dist/src/view/paramMediator.d.ts.map +0 -1
  288. package/dist/src/view/paramMediator.js +0 -478
  289. package/dist/src/view/paramMediator.test.d.ts +0 -2
  290. package/dist/src/view/paramMediator.test.d.ts.map +0 -1
@@ -0,0 +1,776 @@
1
+ import { VISIT_SKIP, VISIT_STOP } from "./view.js";
2
+ import {
3
+ isSelectionParameter,
4
+ isVariableParameter,
5
+ } from "../paramRuntime/paramUtils.js";
6
+
7
+ /**
8
+ * Selectors identify views and parameters in a way that stays stable when the
9
+ * same template/import is instantiated multiple times. They combine a chain of
10
+ * named import instances (scope) with an explicit view or parameter name so
11
+ * bookmarkable state and visibility toggles do not rely on globally-unique
12
+ * names or runtime-only nodes.
13
+ */
14
+
15
+ /**
16
+ * @typedef {{ scope: string[], view: string }} ViewSelector
17
+ * @typedef {{ scope: string[], param: string }} ParamSelector
18
+ * @typedef {{ view: import("./view.js").default, param: import("../spec/parameter.js").Parameter, selector: ParamSelector }} BookmarkableParamEntry
19
+ * @typedef {{ message: string, scope: string[] }} SelectorValidationIssue
20
+ * @typedef {{ name: string | null }} ImportScopeInfo
21
+ * @typedef {"exclude" | "excludeSubtree"} AddressableOverride
22
+ * @typedef {{ skipSubtree?: boolean }} AddressableOptions
23
+ * @typedef {{ view: import("./view.js").default, param: import("../spec/parameter.js").Parameter }} ResolvedParam
24
+ */
25
+
26
+ export const PARAM_SELECTOR_KEY_PREFIX = "p:";
27
+
28
+ /** @type {WeakMap<import("./view.js").default, ImportScopeInfo>} */
29
+ const importScopes = new WeakMap();
30
+
31
+ /** @type {WeakMap<import("./view.js").default, AddressableOverride>} */
32
+ const addressableOverrides = new WeakMap();
33
+
34
+ /**
35
+ * Marks a view as the root of an import scope.
36
+ *
37
+ * @param {import("./view.js").default} view
38
+ * @param {string | null} scopeName
39
+ */
40
+ export function registerImportInstance(view, scopeName) {
41
+ if (scopeName !== null && typeof scopeName !== "string") {
42
+ throw new Error("Import scope name must be a string or null.");
43
+ }
44
+
45
+ importScopes.set(view, { name: scopeName });
46
+ }
47
+
48
+ /**
49
+ * Returns import scope info for a view, if it is an import root.
50
+ *
51
+ * @param {import("./view.js").default} view
52
+ * @returns {ImportScopeInfo | undefined}
53
+ */
54
+ export function getImportScopeInfo(view) {
55
+ return importScopes.get(view);
56
+ }
57
+
58
+ /**
59
+ * Marks a view as non-addressable for selector resolution.
60
+ *
61
+ * @param {import("./view.js").default} view
62
+ * @param {AddressableOptions} [options]
63
+ */
64
+ export function markViewAsNonAddressable(view, options = {}) {
65
+ const skipSubtree = options.skipSubtree ?? false;
66
+ const behavior = skipSubtree ? "excludeSubtree" : "exclude";
67
+ addressableOverrides.set(view, behavior);
68
+ }
69
+
70
+ /**
71
+ * Returns the import scope chain for a view, using named import instances.
72
+ *
73
+ * @param {import("./view.js").default} view
74
+ * @returns {string[]}
75
+ */
76
+ export function getViewScopeChain(view) {
77
+ const ancestors = view.getDataAncestors();
78
+
79
+ /** @type {string[]} */
80
+ const chain = [];
81
+
82
+ for (let i = ancestors.length - 1; i >= 0; i -= 1) {
83
+ const info = importScopes.get(ancestors[i]);
84
+ if (info && typeof info.name === "string") {
85
+ chain.push(info.name);
86
+ }
87
+ }
88
+
89
+ return chain;
90
+ }
91
+
92
+ /**
93
+ * Returns a view selector for a view with an explicit name.
94
+ *
95
+ * @param {import("./view.js").default} view
96
+ * @returns {ViewSelector}
97
+ */
98
+ export function getViewSelector(view) {
99
+ const explicitName = view.explicitName;
100
+ if (!explicitName) {
101
+ throw new Error("Cannot build a selector for a view without a name.");
102
+ }
103
+
104
+ return {
105
+ scope: getViewScopeChain(view),
106
+ view: explicitName,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Returns a parameter selector for a parameter registered in a view.
112
+ *
113
+ * @param {import("./view.js").default} view
114
+ * @param {string} paramName
115
+ * @returns {ParamSelector}
116
+ */
117
+ export function getParamSelector(view, paramName) {
118
+ if (!paramName) {
119
+ throw new Error(
120
+ "Cannot build a selector for a parameter without a name."
121
+ );
122
+ }
123
+
124
+ return {
125
+ scope: getViewScopeChain(view),
126
+ param: paramName,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Returns a stable key for a parameter selector.
132
+ *
133
+ * @param {ParamSelector} selector
134
+ * @returns {string}
135
+ */
136
+ export function makeParamSelectorKey(selector) {
137
+ validateParamSelector(selector);
138
+ return (
139
+ PARAM_SELECTOR_KEY_PREFIX +
140
+ JSON.stringify({ scope: selector.scope, param: selector.param })
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Enumerates views that can be addressed by selectors.
146
+ *
147
+ * @param {import("./view.js").default} root
148
+ * @returns {import("./view.js").default[]}
149
+ */
150
+ export function getAddressableViews(root) {
151
+ /** @type {import("./view.js").default[]} */
152
+ const views = [];
153
+
154
+ visitAddressableViews(root, (view) => {
155
+ views.push(view);
156
+ });
157
+
158
+ return views;
159
+ }
160
+
161
+ /**
162
+ * Visits all addressable views in the hierarchy.
163
+ *
164
+ * @param {import("./view.js").default} root
165
+ * @param {import("./view.js").Visitor} visitor
166
+ */
167
+ export function visitAddressableViews(root, visitor) {
168
+ root.visit((view) => {
169
+ const behavior = addressableOverrides.get(view);
170
+ if (behavior === "excludeSubtree") {
171
+ return VISIT_SKIP;
172
+ }
173
+
174
+ if (behavior === "exclude") {
175
+ return;
176
+ }
177
+
178
+ return visitor(view);
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Resolves a view selector to a unique view within the matching scope.
184
+ *
185
+ * @param {import("./view.js").default} root
186
+ * @param {ViewSelector} selector
187
+ * @returns {import("./view.js").default | undefined}
188
+ */
189
+ export function resolveViewSelector(root, selector) {
190
+ validateViewSelector(selector);
191
+
192
+ const scopeRoot = resolveScopeRoot(root, selector.scope);
193
+ if (!scopeRoot) {
194
+ return;
195
+ }
196
+
197
+ /** @type {import("./view.js").default[]} */
198
+ const matches = [];
199
+
200
+ visitViewsInScope(
201
+ scopeRoot,
202
+ (view) => {
203
+ if (view.explicitName === selector.view) {
204
+ matches.push(view);
205
+ }
206
+ },
207
+ { includeNamedImportRoots: true }
208
+ );
209
+
210
+ if (matches.length === 1) {
211
+ return matches[0];
212
+ } else if (matches.length === 0) {
213
+ return;
214
+ }
215
+
216
+ throw new Error(
217
+ 'View selector is ambiguous for view "' +
218
+ selector.view +
219
+ '" in scope ' +
220
+ JSON.stringify(selector.scope)
221
+ );
222
+ }
223
+
224
+ /**
225
+ * Resolves a parameter selector to a unique bookmarkable parameter.
226
+ *
227
+ * @param {import("./view.js").default} root
228
+ * @param {ParamSelector} selector
229
+ * @returns {ResolvedParam | undefined}
230
+ */
231
+ export function resolveParamSelector(root, selector) {
232
+ validateParamSelector(selector);
233
+
234
+ const scopeRoot = resolveScopeRoot(root, selector.scope);
235
+ if (!scopeRoot) {
236
+ return;
237
+ }
238
+
239
+ /** @type {ResolvedParam[]} */
240
+ const matches = [];
241
+
242
+ visitViewsInScope(scopeRoot, (view) => {
243
+ for (const [name, param] of view.paramRuntime.paramConfigs) {
244
+ if (name !== selector.param) {
245
+ continue;
246
+ }
247
+
248
+ if (!isBookmarkableParam(param)) {
249
+ continue;
250
+ }
251
+
252
+ matches.push({ view, param });
253
+ }
254
+ });
255
+
256
+ if (matches.length === 1) {
257
+ return matches[0];
258
+ } else if (matches.length === 0) {
259
+ return;
260
+ }
261
+
262
+ throw new Error(
263
+ 'Param selector is ambiguous for param "' +
264
+ selector.param +
265
+ '" in scope ' +
266
+ JSON.stringify(selector.scope)
267
+ );
268
+ }
269
+
270
+ /**
271
+ * Visits bookmarkable parameters in the view hierarchy.
272
+ *
273
+ * @param {import("./view.js").default} root
274
+ * @param {(entry: BookmarkableParamEntry) => void} visitor
275
+ */
276
+ export function visitBookmarkableParams(root, visitor) {
277
+ root.visit((view) => {
278
+ const behavior = addressableOverrides.get(view);
279
+ if (behavior === "excludeSubtree") {
280
+ return VISIT_SKIP;
281
+ }
282
+
283
+ if (behavior === "exclude") {
284
+ return;
285
+ }
286
+
287
+ for (const [name, param] of view.paramRuntime.paramConfigs) {
288
+ if (!isBookmarkableParam(param)) {
289
+ continue;
290
+ }
291
+
292
+ visitor({
293
+ view,
294
+ param,
295
+ selector: getParamSelector(view, name),
296
+ });
297
+ }
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Returns bookmarkable parameters in the view hierarchy.
303
+ *
304
+ * @param {import("./view.js").default} root
305
+ * @returns {BookmarkableParamEntry[]}
306
+ */
307
+ export function getBookmarkableParams(root) {
308
+ /** @type {BookmarkableParamEntry[]} */
309
+ const entries = [];
310
+ visitBookmarkableParams(root, (entry) => entries.push(entry));
311
+ return entries;
312
+ }
313
+
314
+ /**
315
+ * Validates naming and scoping constraints for addressable views and parameters.
316
+ *
317
+ * @param {import("./view.js").default} root
318
+ * @returns {SelectorValidationIssue[]}
319
+ */
320
+ export function validateSelectorConstraints(root) {
321
+ /** @type {SelectorValidationIssue[]} */
322
+ const issues = [];
323
+
324
+ for (const scopeRoot of collectScopeRoots(root)) {
325
+ const scope = getScopeChainForRoot(scopeRoot);
326
+ validateViewNamesInScope(scopeRoot, scope, issues);
327
+ validateParamNamesInScope(scopeRoot, scope, issues);
328
+ validateImportInstanceNames(scopeRoot, scope, issues);
329
+ }
330
+
331
+ return issues;
332
+ }
333
+
334
+ /**
335
+ * Validates the structural shape of a parameter selector.
336
+ *
337
+ * @param {ParamSelector} selector
338
+ */
339
+ function validateParamSelector(selector) {
340
+ if (!selector || !Array.isArray(selector.scope)) {
341
+ throw new Error("Param selector scope must be an array.");
342
+ }
343
+
344
+ if (typeof selector.param !== "string" || !selector.param.length) {
345
+ throw new Error("Param selector param must be a non-empty string.");
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Validates the structural shape of a view selector.
351
+ *
352
+ * @param {ViewSelector} selector
353
+ */
354
+ function validateViewSelector(selector) {
355
+ if (!selector || !Array.isArray(selector.scope)) {
356
+ throw new Error("View selector scope must be an array.");
357
+ }
358
+
359
+ if (typeof selector.view !== "string" || !selector.view.length) {
360
+ throw new Error("View selector view must be a non-empty string.");
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Returns the effective configurableVisibility value for a view.
366
+ *
367
+ * @param {import("./view.js").default} view
368
+ * @returns {boolean}
369
+ */
370
+ function isConfigurableVisibility(view) {
371
+ const explicit = view.spec.configurableVisibility;
372
+ if (explicit !== undefined) {
373
+ return explicit;
374
+ }
375
+
376
+ return !(
377
+ view.layoutParent &&
378
+ view.layoutParent.spec &&
379
+ "layer" in view.layoutParent.spec
380
+ );
381
+ }
382
+
383
+ /**
384
+ * Returns true for parameters that are persisted in bookmarks.
385
+ *
386
+ * @param {import("../spec/parameter.js").Parameter} param
387
+ * @returns {boolean}
388
+ */
389
+ function isBookmarkableParam(param) {
390
+ if (param.persist === false) {
391
+ return false;
392
+ }
393
+
394
+ if (isSelectionParameter(param)) {
395
+ return true;
396
+ }
397
+
398
+ if (isVariableParameter(param)) {
399
+ return Boolean(param.bind);
400
+ }
401
+
402
+ return false;
403
+ }
404
+
405
+ /**
406
+ * Collects scope roots for all named import instances and the top-level root.
407
+ *
408
+ * @param {import("./view.js").default} root
409
+ * @returns {import("./view.js").default[]}
410
+ */
411
+ function collectScopeRoots(root) {
412
+ /** @type {Set<import("./view.js").default>} */
413
+ const roots = new Set([root]);
414
+
415
+ root.visit((view) => {
416
+ const info = importScopes.get(view);
417
+ if (info && typeof info.name === "string") {
418
+ roots.add(view);
419
+ }
420
+ });
421
+
422
+ return Array.from(roots);
423
+ }
424
+
425
+ /**
426
+ * Builds the full scope chain for a scope root, including its own name.
427
+ *
428
+ * @param {import("./view.js").default} scopeRoot
429
+ * @returns {string[]}
430
+ */
431
+ function getScopeChainForRoot(scopeRoot) {
432
+ const chain = getViewScopeChain(scopeRoot);
433
+ const info = importScopes.get(scopeRoot);
434
+ if (info && typeof info.name === "string") {
435
+ return [...chain, info.name];
436
+ }
437
+
438
+ return chain;
439
+ }
440
+
441
+ /**
442
+ * Formats a scope chain for diagnostics.
443
+ *
444
+ * @param {string[]} scope
445
+ * @returns {string}
446
+ */
447
+ function formatScope(scope) {
448
+ if (!scope.length) {
449
+ return "import scope (root)";
450
+ }
451
+
452
+ return "import scope [" + scope.join(" / ") + "]";
453
+ }
454
+
455
+ /**
456
+ * Checks configurable view names for required explicit and unique naming.
457
+ *
458
+ * @param {import("./view.js").default} scopeRoot
459
+ * @param {string[]} scope
460
+ * @param {SelectorValidationIssue[]} issues
461
+ */
462
+ function validateViewNamesInScope(scopeRoot, scope, issues) {
463
+ /** @type {Map<string, import("./view.js").default[]>} */
464
+ const names = new Map();
465
+
466
+ visitViewsInScope(
467
+ scopeRoot,
468
+ (view) => {
469
+ const explicitName = view.explicitName;
470
+ const isConfigurable = isConfigurableVisibility(view);
471
+ const isExplicitlyConfigurable =
472
+ view.spec.configurableVisibility === true;
473
+
474
+ if (!isConfigurable) {
475
+ return;
476
+ }
477
+
478
+ if (!explicitName) {
479
+ if (!isExplicitlyConfigurable) {
480
+ return;
481
+ }
482
+
483
+ issues.push({
484
+ message:
485
+ "Configurable view must have an explicit name in " +
486
+ formatScope(scope) +
487
+ ".",
488
+ scope,
489
+ });
490
+ return;
491
+ }
492
+
493
+ const matches = names.get(explicitName);
494
+ if (matches) {
495
+ matches.push(view);
496
+ } else {
497
+ names.set(explicitName, [view]);
498
+ }
499
+ },
500
+ { includeNamedImportRoots: true }
501
+ );
502
+
503
+ for (const [name, matches] of names) {
504
+ if (matches.length <= 1) {
505
+ continue;
506
+ }
507
+
508
+ const paths = matches.map((view) => view.getPathString()).join(", ");
509
+
510
+ issues.push({
511
+ message:
512
+ 'Configurable view name "' +
513
+ name +
514
+ '" is not unique within ' +
515
+ formatScope(scope) +
516
+ ". Found in: " +
517
+ paths +
518
+ ".",
519
+ scope,
520
+ });
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Checks bookmarkable parameter names for uniqueness within a scope.
526
+ *
527
+ * @param {import("./view.js").default} scopeRoot
528
+ * @param {string[]} scope
529
+ * @param {SelectorValidationIssue[]} issues
530
+ */
531
+ function validateParamNamesInScope(scopeRoot, scope, issues) {
532
+ /** @type {Map<string, import("./view.js").default[]>} */
533
+ const names = new Map();
534
+
535
+ visitViewsInScope(scopeRoot, (view) => {
536
+ for (const [name, param] of view.paramRuntime.paramConfigs) {
537
+ if (!isBookmarkableParam(param)) {
538
+ continue;
539
+ }
540
+
541
+ const matches = names.get(name);
542
+ if (matches) {
543
+ matches.push(view);
544
+ } else {
545
+ names.set(name, [view]);
546
+ }
547
+ }
548
+ });
549
+
550
+ for (const [name, matches] of names) {
551
+ if (matches.length <= 1) {
552
+ continue;
553
+ }
554
+
555
+ const paths = matches.map((view) => view.getPathString()).join(", ");
556
+
557
+ issues.push({
558
+ message:
559
+ 'Bookmarkable parameter "' +
560
+ name +
561
+ '" is not unique within ' +
562
+ formatScope(scope) +
563
+ ". Found in: " +
564
+ paths +
565
+ ".",
566
+ scope,
567
+ });
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Ensures addressable import instances are uniquely named in a scope.
573
+ *
574
+ * @param {import("./view.js").default} scopeRoot
575
+ * @param {string[]} scope
576
+ * @param {SelectorValidationIssue[]} issues
577
+ */
578
+ function validateImportInstanceNames(scopeRoot, scope, issues) {
579
+ const importRoots = collectImmediateImportRoots(scopeRoot);
580
+ if (!importRoots.length) {
581
+ return;
582
+ }
583
+
584
+ const addressableRoots = importRoots.filter((view) =>
585
+ hasAddressableFeatures(view)
586
+ );
587
+
588
+ if (addressableRoots.length <= 1) {
589
+ return;
590
+ }
591
+
592
+ /** @type {Map<string, number>} */
593
+ const counts = new Map();
594
+
595
+ for (const view of addressableRoots) {
596
+ const info = importScopes.get(view);
597
+ const name = info ? info.name : undefined;
598
+ if (typeof name !== "string" || !name.length) {
599
+ continue;
600
+ }
601
+
602
+ counts.set(name, (counts.get(name) ?? 0) + 1);
603
+ }
604
+
605
+ for (const [name, count] of counts) {
606
+ if (count > 1) {
607
+ issues.push({
608
+ message:
609
+ 'Import instance name "' +
610
+ name +
611
+ '" is used multiple times for addressable instances in ' +
612
+ formatScope(scope) +
613
+ ".",
614
+ scope,
615
+ });
616
+ }
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Collects direct import roots under a scope root.
622
+ *
623
+ * @param {import("./view.js").default} scopeRoot
624
+ * @returns {import("./view.js").default[]}
625
+ */
626
+ function collectImmediateImportRoots(scopeRoot) {
627
+ /** @type {import("./view.js").default[]} */
628
+ const roots = [];
629
+
630
+ scopeRoot.visit((view) => {
631
+ if (view === scopeRoot) {
632
+ return;
633
+ }
634
+
635
+ const behavior = addressableOverrides.get(view);
636
+ if (behavior === "excludeSubtree") {
637
+ return VISIT_SKIP;
638
+ }
639
+
640
+ const info = importScopes.get(view);
641
+ if (info) {
642
+ roots.push(view);
643
+ return VISIT_SKIP;
644
+ }
645
+ });
646
+
647
+ return roots;
648
+ }
649
+
650
+ /**
651
+ * Detects whether a subtree exposes configurable views or bookmarkable params.
652
+ *
653
+ * @param {import("./view.js").default} root
654
+ * @returns {boolean}
655
+ */
656
+ function hasAddressableFeatures(root) {
657
+ let found = false;
658
+
659
+ root.visit((view) => {
660
+ const behavior = addressableOverrides.get(view);
661
+ if (behavior === "excludeSubtree") {
662
+ return VISIT_SKIP;
663
+ }
664
+
665
+ if (behavior !== "exclude") {
666
+ const isConfigurable = isConfigurableVisibility(view);
667
+ const isExplicitlyConfigurable =
668
+ view.spec.configurableVisibility === true;
669
+
670
+ if (
671
+ isConfigurable &&
672
+ (view.explicitName || isExplicitlyConfigurable)
673
+ ) {
674
+ found = true;
675
+ return VISIT_STOP;
676
+ }
677
+
678
+ for (const param of view.paramRuntime.paramConfigs.values()) {
679
+ if (isBookmarkableParam(param)) {
680
+ found = true;
681
+ return VISIT_STOP;
682
+ }
683
+ }
684
+ }
685
+ });
686
+
687
+ return found;
688
+ }
689
+
690
+ /**
691
+ * Resolves a scope chain to its scope root.
692
+ *
693
+ * @param {import("./view.js").default} root
694
+ * @param {string[]} scope
695
+ * @returns {import("./view.js").default | undefined}
696
+ */
697
+ function resolveScopeRoot(root, scope) {
698
+ /** @type {import("./view.js").default} */
699
+ let current = root;
700
+
701
+ for (const name of scope) {
702
+ if (typeof name !== "string" || !name.length) {
703
+ throw new Error("Scope names must be non-empty strings.");
704
+ }
705
+
706
+ /** @type {import("./view.js").default | undefined} */
707
+ let match;
708
+ let hasDuplicate = false;
709
+
710
+ visitViewsInScope(
711
+ current,
712
+ (view) => {
713
+ const info = importScopes.get(view);
714
+ if (info && info.name === name) {
715
+ if (match) {
716
+ hasDuplicate = true;
717
+ return VISIT_STOP;
718
+ }
719
+ match = view;
720
+ }
721
+ },
722
+ { includeNamedImportRoots: true }
723
+ );
724
+
725
+ if (hasDuplicate) {
726
+ throw new Error(
727
+ 'Multiple import instances named "' + name + '" in scope.'
728
+ );
729
+ }
730
+
731
+ if (match) {
732
+ current = match;
733
+ } else {
734
+ return;
735
+ }
736
+ }
737
+
738
+ return current;
739
+ }
740
+
741
+ /**
742
+ * Visits addressable views within a scope, skipping nested named import roots.
743
+ *
744
+ * @param {import("./view.js").default} scopeRoot
745
+ * @param {import("./view.js").Visitor} visitor
746
+ * @param {{ includeNamedImportRoots?: boolean }} [options]
747
+ */
748
+ function visitViewsInScope(scopeRoot, visitor, options = {}) {
749
+ const includeNamedImportRoots = options.includeNamedImportRoots ?? false;
750
+
751
+ scopeRoot.visit((view) => {
752
+ const behavior = addressableOverrides.get(view);
753
+ if (behavior === "excludeSubtree") {
754
+ return VISIT_SKIP;
755
+ }
756
+
757
+ const info = importScopes.get(view);
758
+ const isNamedImportRoot =
759
+ view !== scopeRoot && info && typeof info.name === "string";
760
+
761
+ if (isNamedImportRoot) {
762
+ if (behavior !== "exclude" && includeNamedImportRoots) {
763
+ const result = visitor(view);
764
+ if (result === VISIT_STOP) {
765
+ return result;
766
+ }
767
+ }
768
+
769
+ return VISIT_SKIP;
770
+ }
771
+
772
+ if (behavior !== "exclude") {
773
+ return visitor(view);
774
+ }
775
+ });
776
+ }