react-msaview 6.5.0 → 8.0.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 (414) hide show
  1. package/README.md +13 -11
  2. package/bundle/index.js +122 -112
  3. package/bundle/index.js.map +4 -4
  4. package/dist/colorSchemes.js +1 -2
  5. package/dist/colorSchemes.js.map +1 -1
  6. package/dist/columnStats.d.ts +28 -0
  7. package/dist/columnStats.js +34 -0
  8. package/dist/columnStats.js.map +1 -0
  9. package/dist/components/DragHandle.d.ts +3 -2
  10. package/dist/components/DragHandle.js +11 -5
  11. package/dist/components/DragHandle.js.map +1 -1
  12. package/dist/components/Loading.js +2 -2
  13. package/dist/components/Loading.js.map +1 -1
  14. package/dist/components/MSAView.js +17 -6
  15. package/dist/components/MSAView.js.map +1 -1
  16. package/dist/components/MSAViewer.d.ts +69 -4
  17. package/dist/components/MSAViewer.js +201 -8
  18. package/dist/components/MSAViewer.js.map +1 -1
  19. package/dist/components/PortalTooltip.d.ts +11 -0
  20. package/dist/components/PortalTooltip.js +29 -0
  21. package/dist/components/PortalTooltip.js.map +1 -0
  22. package/dist/components/ResizeHandles.d.ts +3 -3
  23. package/dist/components/ResizeHandles.js +13 -24
  24. package/dist/components/ResizeHandles.js.map +1 -1
  25. package/dist/components/SequenceTextArea.js +3 -3
  26. package/dist/components/SequenceTextArea.js.map +1 -1
  27. package/dist/components/Track.js +41 -22
  28. package/dist/components/Track.js.map +1 -1
  29. package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
  30. package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
  31. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
  32. package/dist/components/dialogs/ExportSVGDialog.js +5 -3
  33. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  34. package/dist/components/dialogs/FeatureDialog.js +13 -9
  35. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  36. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  37. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  38. package/dist/components/header/GappynessSlider.js +6 -11
  39. package/dist/components/header/GappynessSlider.js.map +1 -1
  40. package/dist/components/header/Header.js +7 -0
  41. package/dist/components/header/Header.js.map +1 -1
  42. package/dist/components/header/HeaderStatusArea.js +0 -2
  43. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  44. package/dist/components/header/LoadWarnings.d.ts +6 -0
  45. package/dist/components/header/LoadWarnings.js +35 -0
  46. package/dist/components/header/LoadWarnings.js.map +1 -0
  47. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  48. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  49. package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
  50. package/dist/components/header/ScrollZoomIcons.js +35 -0
  51. package/dist/components/header/ScrollZoomIcons.js.map +1 -0
  52. package/dist/components/header/TreeSettingsMenu.js +7 -1
  53. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  54. package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
  55. package/dist/components/header/UnshareableDataWarning.js +40 -0
  56. package/dist/components/header/UnshareableDataWarning.js.map +1 -0
  57. package/dist/components/header/ZoomControls.js +49 -6
  58. package/dist/components/header/ZoomControls.js.map +1 -1
  59. package/dist/components/header/getAnnotationsMenu.js +11 -14
  60. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  61. package/dist/components/header/settingsMenuItems.js +10 -7
  62. package/dist/components/header/settingsMenuItems.js.map +1 -1
  63. package/dist/components/import/ImportForm.js +1 -1
  64. package/dist/components/import/ImportForm.js.map +1 -1
  65. package/dist/components/import/ImportFormExamples.js +2 -6
  66. package/dist/components/import/ImportFormExamples.js.map +1 -1
  67. package/dist/components/import/util.js +2 -0
  68. package/dist/components/import/util.js.map +1 -1
  69. package/dist/components/minimap/Minimap.js +1 -2
  70. package/dist/components/minimap/Minimap.js.map +1 -1
  71. package/dist/components/msa/AnnotationLegend.js +27 -19
  72. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  73. package/dist/components/msa/MSACanvas.js +1 -1
  74. package/dist/components/msa/MSACanvas.js.map +1 -1
  75. package/dist/components/msa/MSACanvasBlock.js +17 -20
  76. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  77. package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
  78. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  79. package/dist/components/msa/domainBandCursor.d.ts +7 -11
  80. package/dist/components/msa/domainBandCursor.js +7 -11
  81. package/dist/components/msa/domainBandCursor.js.map +1 -1
  82. package/dist/components/msa/drawFeatureSpans.d.ts +53 -0
  83. package/dist/components/msa/drawFeatureSpans.js +78 -0
  84. package/dist/components/msa/drawFeatureSpans.js.map +1 -0
  85. package/dist/components/msa/legendRows.d.ts +8 -0
  86. package/dist/components/msa/legendRows.js +16 -0
  87. package/dist/components/msa/legendRows.js.map +1 -0
  88. package/dist/components/msa/msaRaster.d.ts +30 -27
  89. package/dist/components/msa/msaRaster.js +193 -90
  90. package/dist/components/msa/msaRaster.js.map +1 -1
  91. package/dist/components/msa/packDomainLanes.d.ts +20 -0
  92. package/dist/components/msa/packDomainLanes.js +33 -0
  93. package/dist/components/msa/packDomainLanes.js.map +1 -0
  94. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
  95. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +51 -87
  96. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  97. package/dist/components/msa/renderHighlights.d.ts +44 -4
  98. package/dist/components/msa/renderHighlights.js +99 -11
  99. package/dist/components/msa/renderHighlights.js.map +1 -1
  100. package/dist/components/msa/renderMSABlock.d.ts +1 -2
  101. package/dist/components/msa/renderMSABlock.js +28 -31
  102. package/dist/components/msa/renderMSABlock.js.map +1 -1
  103. package/dist/components/msa/renderMSAMouseover.js +9 -30
  104. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  105. package/dist/components/msa/tileColor.js +4 -7
  106. package/dist/components/msa/tileColor.js.map +1 -1
  107. package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
  108. package/dist/components/msa/useMsaBlockMouse.js +6 -6
  109. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  110. package/dist/components/renderCtx.d.ts +1 -1
  111. package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
  112. package/dist/components/rowpanels/RowPanelBlock.js +86 -0
  113. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
  114. package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
  115. package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
  116. package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
  117. package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
  118. package/dist/components/rowpanels/RowPanels.js +32 -0
  119. package/dist/components/rowpanels/RowPanels.js.map +1 -0
  120. package/dist/components/rowpanels/headerLayout.d.ts +1 -0
  121. package/dist/components/rowpanels/headerLayout.js +5 -0
  122. package/dist/components/rowpanels/headerLayout.js.map +1 -0
  123. package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
  124. package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
  125. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
  126. package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
  127. package/dist/components/rowpanels/renderRowPanel.js +22 -0
  128. package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
  129. package/dist/components/rowpanels/renderStrip.d.ts +20 -0
  130. package/dist/components/rowpanels/renderStrip.js +26 -0
  131. package/dist/components/rowpanels/renderStrip.js.map +1 -0
  132. package/dist/components/tracks/TrackBlocks.js +13 -16
  133. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  134. package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
  135. package/dist/components/tracks/TrackTooltipContent.js +109 -0
  136. package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
  137. package/dist/components/tracks/drawTracks.d.ts +30 -12
  138. package/dist/components/tracks/drawTracks.js +127 -34
  139. package/dist/components/tracks/drawTracks.js.map +1 -1
  140. package/dist/components/tracks/useTrackHover.d.ts +22 -0
  141. package/dist/components/tracks/useTrackHover.js +30 -0
  142. package/dist/components/tracks/useTrackHover.js.map +1 -0
  143. package/dist/components/tree/CladeLabels.d.ts +12 -0
  144. package/dist/components/tree/CladeLabels.js +48 -0
  145. package/dist/components/tree/CladeLabels.js.map +1 -0
  146. package/dist/components/tree/TreeCanvas.js +6 -4
  147. package/dist/components/tree/TreeCanvas.js.map +1 -1
  148. package/dist/components/tree/TreeCanvasBlock.js +6 -21
  149. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  150. package/dist/components/tree/TreeOverview.d.ts +14 -0
  151. package/dist/components/tree/TreeOverview.js +75 -0
  152. package/dist/components/tree/TreeOverview.js.map +1 -0
  153. package/dist/components/tree/TreeRuler.js +11 -5
  154. package/dist/components/tree/TreeRuler.js.map +1 -1
  155. package/dist/components/tree/cladeBrackets.d.ts +25 -0
  156. package/dist/components/tree/cladeBrackets.js +51 -0
  157. package/dist/components/tree/cladeBrackets.js.map +1 -0
  158. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
  159. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
  160. package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
  161. package/dist/components/tree/renderTreeCanvas.js +221 -47
  162. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  163. package/dist/components/tree/renderTreeOverview.d.ts +52 -0
  164. package/dist/components/tree/renderTreeOverview.js +138 -0
  165. package/dist/components/tree/renderTreeOverview.js.map +1 -0
  166. package/dist/components/tree/scaleBar.d.ts +10 -0
  167. package/dist/components/tree/scaleBar.js +24 -0
  168. package/dist/components/tree/scaleBar.js.map +1 -0
  169. package/dist/components/tree/useTreeHover.js +2 -4
  170. package/dist/components/tree/useTreeHover.js.map +1 -1
  171. package/dist/constants.d.ts +18 -1
  172. package/dist/constants.js +51 -8
  173. package/dist/constants.js.map +1 -1
  174. package/dist/createPaletteMap.d.ts +7 -6
  175. package/dist/createPaletteMap.js +10 -9
  176. package/dist/createPaletteMap.js.map +1 -1
  177. package/dist/element.d.ts +13 -0
  178. package/dist/element.js +269 -0
  179. package/dist/element.js.map +1 -0
  180. package/dist/featureFields.d.ts +11 -0
  181. package/dist/featureFields.js +17 -0
  182. package/dist/featureFields.js.map +1 -0
  183. package/dist/fetchUtils.d.ts +7 -7
  184. package/dist/fetchUtils.js +4 -32
  185. package/dist/fetchUtils.js.map +1 -1
  186. package/dist/flatToTree.js +1 -2
  187. package/dist/flatToTree.js.map +1 -1
  188. package/dist/ggplotPalettes.d.ts +10 -0
  189. package/dist/ggplotPalettes.js +58 -0
  190. package/dist/ggplotPalettes.js.map +1 -1
  191. package/dist/hierarchy.d.ts +29 -1
  192. package/dist/hierarchy.js +82 -19
  193. package/dist/hierarchy.js.map +1 -1
  194. package/dist/index.d.ts +10 -1
  195. package/dist/index.js +10 -2
  196. package/dist/index.js.map +1 -1
  197. package/dist/model/DataModel.js +4 -4
  198. package/dist/model/DataModel.js.map +1 -1
  199. package/dist/model/msaModel.d.ts +3 -2
  200. package/dist/model/msaModel.js +3 -2
  201. package/dist/model/msaModel.js.map +1 -1
  202. package/dist/model/treeModel.d.ts +30 -0
  203. package/dist/model/treeModel.js +37 -1
  204. package/dist/model/treeModel.js.map +1 -1
  205. package/dist/model.d.ts +792 -259
  206. package/dist/model.js +2092 -675
  207. package/dist/model.js.map +1 -1
  208. package/dist/mount.d.ts +11 -0
  209. package/dist/mount.js +22 -0
  210. package/dist/mount.js.map +1 -0
  211. package/dist/neighborJoining.js +8 -32
  212. package/dist/neighborJoining.js.map +1 -1
  213. package/dist/renderToStaticMarkup.d.ts +15 -13
  214. package/dist/renderToStaticMarkup.js +37 -25
  215. package/dist/renderToStaticMarkup.js.map +1 -1
  216. package/dist/renderToSvg.js +198 -68
  217. package/dist/renderToSvg.js.map +1 -1
  218. package/dist/rowCoordinateCalculations.d.ts +26 -28
  219. package/dist/rowCoordinateCalculations.js +13 -39
  220. package/dist/rowCoordinateCalculations.js.map +1 -1
  221. package/dist/scales.d.ts +22 -0
  222. package/dist/scales.js +30 -0
  223. package/dist/scales.js.map +1 -0
  224. package/dist/seqPosToGlobalCol.d.ts +2 -3
  225. package/dist/seqPosToGlobalCol.js +5 -4
  226. package/dist/seqPosToGlobalCol.js.map +1 -1
  227. package/dist/sequenceLogo.d.ts +6 -11
  228. package/dist/sequenceLogo.js +6 -11
  229. package/dist/sequenceLogo.js.map +1 -1
  230. package/dist/stripDefault.d.ts +7 -14
  231. package/dist/stripDefault.js +7 -14
  232. package/dist/stripDefault.js.map +1 -1
  233. package/dist/svgTestUtil.d.ts +306 -124
  234. package/dist/types.d.ts +295 -3
  235. package/dist/umd.d.ts +2 -0
  236. package/dist/umd.js +4 -2
  237. package/dist/umd.js.map +1 -1
  238. package/dist/useCanvasAutorun.d.ts +5 -10
  239. package/dist/useCanvasAutorun.js +5 -10
  240. package/dist/useCanvasAutorun.js.map +1 -1
  241. package/dist/useMsaSvgFigure.d.ts +20 -0
  242. package/dist/useMsaSvgFigure.js +46 -0
  243. package/dist/useMsaSvgFigure.js.map +1 -0
  244. package/dist/util.d.ts +10 -4
  245. package/dist/util.js +37 -10
  246. package/dist/util.js.map +1 -1
  247. package/dist/version.d.ts +1 -1
  248. package/dist/version.js +1 -1
  249. package/dist/wuss.d.ts +14 -0
  250. package/dist/wuss.js +55 -0
  251. package/dist/wuss.js.map +1 -0
  252. package/package.json +23 -14
  253. package/src/colorSchemes.ts +1 -2
  254. package/src/columnStats.ts +58 -0
  255. package/src/components/DragHandle.tsx +18 -7
  256. package/src/components/Loading.tsx +14 -8
  257. package/src/components/MSAView.tsx +19 -5
  258. package/src/components/MSAViewer.tsx +326 -10
  259. package/src/components/PortalTooltip.tsx +47 -0
  260. package/src/components/ResizeHandles.tsx +21 -36
  261. package/src/components/SequenceTextArea.tsx +3 -3
  262. package/src/components/Track.tsx +55 -18
  263. package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
  264. package/src/components/dialogs/ExportSVGDialog.tsx +7 -2
  265. package/src/components/dialogs/FeatureDialog.tsx +13 -9
  266. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  267. package/src/components/header/GappynessSlider.tsx +6 -11
  268. package/src/components/header/Header.tsx +10 -0
  269. package/src/components/header/HeaderStatusArea.tsx +0 -5
  270. package/src/components/header/LoadWarnings.tsx +48 -0
  271. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  272. package/src/components/header/ScrollZoomIcons.tsx +52 -0
  273. package/src/components/header/TreeSettingsMenu.tsx +7 -1
  274. package/src/components/header/UnshareableDataWarning.tsx +56 -0
  275. package/src/components/header/ZoomControls.tsx +74 -14
  276. package/src/components/header/getAnnotationsMenu.ts +13 -17
  277. package/src/components/header/settingsMenuItems.ts +11 -6
  278. package/src/components/import/ImportForm.tsx +2 -2
  279. package/src/components/import/ImportFormExamples.tsx +4 -8
  280. package/src/components/import/util.ts +2 -0
  281. package/src/components/minimap/Minimap.tsx +1 -2
  282. package/src/components/msa/AnnotationLegend.tsx +48 -38
  283. package/src/components/msa/MSACanvas.tsx +3 -5
  284. package/src/components/msa/MSACanvasBlock.tsx +17 -21
  285. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
  286. package/src/components/msa/domainBandCursor.ts +7 -11
  287. package/src/components/msa/drawFeatureSpans.ts +150 -0
  288. package/src/components/msa/legendRows.ts +24 -0
  289. package/src/components/msa/msaRaster.ts +236 -88
  290. package/src/components/msa/packDomainLanes.ts +36 -0
  291. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +69 -123
  292. package/src/components/msa/renderHighlights.ts +141 -10
  293. package/src/components/msa/renderMSABlock.ts +29 -38
  294. package/src/components/msa/renderMSAMouseover.ts +9 -39
  295. package/src/components/msa/tileColor.ts +4 -7
  296. package/src/components/msa/useMsaBlockMouse.ts +6 -6
  297. package/src/components/renderCtx.ts +1 -0
  298. package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
  299. package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
  300. package/src/components/rowpanels/RowPanels.tsx +77 -0
  301. package/src/components/rowpanels/headerLayout.ts +5 -0
  302. package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
  303. package/src/components/rowpanels/renderRowPanel.ts +41 -0
  304. package/src/components/rowpanels/renderStrip.ts +46 -0
  305. package/src/components/tracks/TrackBlocks.tsx +13 -18
  306. package/src/components/tracks/TrackTooltipContent.tsx +156 -0
  307. package/src/components/tracks/drawTracks.ts +169 -38
  308. package/src/components/tracks/useTrackHover.ts +42 -0
  309. package/src/components/tree/CladeLabels.tsx +77 -0
  310. package/src/components/tree/TreeCanvas.tsx +4 -3
  311. package/src/components/tree/TreeCanvasBlock.tsx +10 -32
  312. package/src/components/tree/TreeOverview.tsx +106 -0
  313. package/src/components/tree/TreeRuler.tsx +29 -5
  314. package/src/components/tree/cladeBrackets.ts +73 -0
  315. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
  316. package/src/components/tree/renderTreeCanvas.ts +312 -48
  317. package/src/components/tree/renderTreeOverview.ts +212 -0
  318. package/src/components/tree/scaleBar.ts +23 -0
  319. package/src/components/tree/useTreeHover.ts +2 -4
  320. package/src/constants.ts +61 -8
  321. package/src/createPaletteMap.ts +10 -9
  322. package/src/element.ts +321 -0
  323. package/src/featureFields.ts +19 -0
  324. package/src/fetchUtils.ts +7 -40
  325. package/src/flatToTree.ts +1 -2
  326. package/src/ggplotPalettes.ts +60 -0
  327. package/src/hierarchy.ts +97 -20
  328. package/src/index.ts +46 -6
  329. package/src/model/DataModel.ts +5 -4
  330. package/src/model/msaModel.ts +3 -2
  331. package/src/model/treeModel.ts +42 -0
  332. package/src/model.ts +2492 -793
  333. package/src/mount.tsx +32 -0
  334. package/src/neighborJoining.ts +8 -32
  335. package/src/rawImports.d.ts +9 -0
  336. package/src/renderToStaticMarkup.ts +41 -25
  337. package/src/renderToSvg.tsx +365 -92
  338. package/src/rowCoordinateCalculations.ts +16 -62
  339. package/src/scales.ts +47 -0
  340. package/src/seqPosToGlobalCol.ts +5 -4
  341. package/src/sequenceLogo.ts +6 -11
  342. package/src/stripDefault.ts +7 -14
  343. package/src/types.ts +352 -12
  344. package/src/umd.ts +4 -2
  345. package/src/useCanvasAutorun.ts +5 -10
  346. package/src/useMsaSvgFigure.ts +72 -0
  347. package/src/util.ts +40 -14
  348. package/src/version.ts +1 -1
  349. package/src/wuss.ts +62 -0
  350. package/dist/components/dialogs/InterProScanDialog.js +0 -137
  351. package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
  352. package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
  353. package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
  354. package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
  355. package/dist/components/msa/ColumnStats.js +0 -42
  356. package/dist/components/msa/ColumnStats.js.map +0 -1
  357. package/dist/launchInterProScan.d.ts +0 -13
  358. package/dist/launchInterProScan.js +0 -110
  359. package/dist/launchInterProScan.js.map +0 -1
  360. package/dist/useColorContrast.d.ts +0 -10
  361. package/dist/useColorContrast.js +0 -13
  362. package/dist/useColorContrast.js.map +0 -1
  363. package/src/clustalX.test.ts +0 -74
  364. package/src/colSpace.test.ts +0 -67
  365. package/src/collapseLogic.test.ts +0 -115
  366. package/src/colorSchemes.docs.test.ts +0 -57
  367. package/src/columnCounts.test.ts +0 -73
  368. package/src/columnStats.test.ts +0 -94
  369. package/src/columnTracks.test.ts +0 -97
  370. package/src/components/dialogs/InterProScanDialog.tsx +0 -214
  371. package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
  372. package/src/components/minimap/minimapLayout.test.ts +0 -71
  373. package/src/components/msa/ColumnStats.tsx +0 -54
  374. package/src/components/msa/msaRaster.test.ts +0 -145
  375. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
  376. package/src/components/tree/renderTreeCanvas.test.ts +0 -296
  377. package/src/dataInitialized.test.ts +0 -42
  378. package/src/domainBands.test.ts +0 -122
  379. package/src/domainLetterColor.test.ts +0 -80
  380. package/src/exportFileName.test.ts +0 -40
  381. package/src/fetchUtils.test.ts +0 -83
  382. package/src/flatToTree.test.ts +0 -51
  383. package/src/hierarchy.test.ts +0 -228
  384. package/src/highlights.test.ts +0 -70
  385. package/src/impgRender.test.tsx +0 -68
  386. package/src/largeTree.test.ts +0 -67
  387. package/src/launchInterProScan.ts +0 -167
  388. package/src/loadErrorDisplay.test.tsx +0 -102
  389. package/src/modelFilehandleLoaders.test.ts +0 -137
  390. package/src/modelReset.test.ts +0 -120
  391. package/src/neighborJoining.test.ts +0 -164
  392. package/src/parseAsn1.test.ts +0 -36
  393. package/src/propertyConservation.test.ts +0 -36
  394. package/src/renderColumnTracksSvg.test.tsx +0 -108
  395. package/src/renderDomainsSvg.test.tsx +0 -118
  396. package/src/renderHighlightsSvg.test.tsx +0 -85
  397. package/src/renderMinimapSvg.test.tsx +0 -62
  398. package/src/renderRasterSvg.test.tsx +0 -144
  399. package/src/renderSequenceLogoSvg.test.tsx +0 -165
  400. package/src/renderToSvg.test.tsx +0 -65
  401. package/src/reset.test.ts +0 -61
  402. package/src/rowCoordinateCalculations.test.ts +0 -211
  403. package/src/rowSpace.test.ts +0 -46
  404. package/src/segmentOverlay.test.ts +0 -70
  405. package/src/seqPosToGlobalCol.test.ts +0 -59
  406. package/src/sequenceLogo.test.ts +0 -88
  407. package/src/setFontSize.test.ts +0 -33
  408. package/src/stripDefault.test.ts +0 -48
  409. package/src/stripDefaultSnapshot.test.ts +0 -87
  410. package/src/treeMetadata.test.ts +0 -39
  411. package/src/useCanvasAutorun.test.tsx +0 -98
  412. package/src/useColorContrast.ts +0 -18
  413. package/src/viewChrome.test.tsx +0 -134
  414. package/src/wheelZoom.test.ts +0 -46
@@ -0,0 +1,212 @@
1
+ import { descendants } from '../../hierarchy.ts'
2
+ import { getNodeX } from './renderTreeCanvas.ts'
3
+
4
+ import type { HierarchyNode } from '../../hierarchy.ts'
5
+ import type { MsaViewModel } from '../../model.ts'
6
+ import type { RasterCanvas } from '../msa/msaRaster.ts'
7
+ import type { RenderCtx } from '../renderCtx.ts'
8
+ import type { Theme } from '@mui/material'
9
+
10
+ // the tree draws this far inside the band, so a box around the first or the
11
+ // last row keeps both of its edges
12
+ const pad = 1
13
+
14
+ interface OverviewArgs {
15
+ model: MsaViewModel
16
+ ctx: RenderCtx
17
+ theme: Theme
18
+ width: number
19
+ height: number
20
+ }
21
+
22
+ function rowScale(numTips: number, height: number) {
23
+ const inner = height - pad * 2
24
+ return (row: number) => pad + (row / numTips) * inner
25
+ }
26
+
27
+ /**
28
+ * The whole tree at the size of the overview band, with the clade highlights
29
+ * under it.
30
+ *
31
+ * A branch that moves less than a pixel on both axes is dropped. The 230k-tip
32
+ * COVID tree has 460k of them over a 120px band, and the ones landing on the
33
+ * pixel their parent already covers cost a path segment each and change no
34
+ * pixel; the test in renderTreeOverview.test.ts measures a 200k-tip tree.
35
+ */
36
+ export function drawTreeOverview({
37
+ model,
38
+ ctx,
39
+ theme,
40
+ width,
41
+ height,
42
+ }: OverviewArgs) {
43
+ const layout = model.treeOverviewLayout
44
+ if (!layout) {
45
+ return
46
+ }
47
+ const { root, numTips, maxDepthToLeaf, showBranchLen } = layout
48
+ const innerWidth = width - pad * 2
49
+ const rowY = rowScale(numTips, height)
50
+ const nodeX = (node: HierarchyNode) =>
51
+ pad + (getNodeX(node, showBranchLen, 1, maxDepthToLeaf) ?? 0) * innerWidth
52
+
53
+ for (const { rows, color } of model.treeOverviewClades) {
54
+ ctx.fillStyle = color
55
+ ctx.fillRect(0, rowY(rows[0]), width, rowY(rows[1] + 1) - rowY(rows[0]))
56
+ }
57
+
58
+ ctx.strokeStyle = theme.palette.text.primary
59
+ ctx.beginPath()
60
+ for (const source of descendants(root)) {
61
+ if (!source.children) {
62
+ continue
63
+ }
64
+ const sx = nodeX(source)
65
+ const sy = rowY(source.x!)
66
+ for (const target of source.children) {
67
+ const tx = nodeX(target)
68
+ const ty = rowY(target.x!)
69
+ if (Math.abs(tx - sx) < 1 && Math.abs(ty - sy) < 1) {
70
+ continue
71
+ }
72
+ ctx.moveTo(sx, sy)
73
+ ctx.lineTo(sx, ty)
74
+ ctx.lineTo(tx, ty)
75
+ }
76
+ }
77
+ ctx.stroke()
78
+ }
79
+
80
+ /**
81
+ * A box across the overview around a run of tip rows: the focused subtree's
82
+ * extent, or the one a hovered pixel would focus.
83
+ */
84
+ export function drawTreeOverviewBox({
85
+ model,
86
+ ctx,
87
+ color,
88
+ rows,
89
+ width,
90
+ height,
91
+ }: Omit<OverviewArgs, 'theme'> & {
92
+ color: string
93
+ rows: [number, number]
94
+ }) {
95
+ const layout = model.treeOverviewLayout
96
+ if (!layout) {
97
+ return
98
+ }
99
+ const rowY = rowScale(layout.numTips, height)
100
+ const top = rowY(rows[0])
101
+ ctx.strokeStyle = color
102
+ ctx.strokeRect(0.5, top, width - 1, Math.max(2, rowY(rows[1] + 1) - top))
103
+ }
104
+
105
+ /**
106
+ * The overview as the figure carries it: the tree, the clade highlights and
107
+ * the focus box. The SVG export draws it straight onto a svgcanvas Context,
108
+ * and the live canvas blits `treeOverviewImage` in place of the first two.
109
+ */
110
+ export function renderTreeOverview(args: OverviewArgs) {
111
+ drawTreeOverview(args)
112
+ const rows = args.model.treeOverviewFocusRows
113
+ if (rows) {
114
+ drawTreeOverviewBox({
115
+ ...args,
116
+ rows,
117
+ color: args.theme.palette.text.primary,
118
+ })
119
+ }
120
+ }
121
+
122
+ interface OverviewCache {
123
+ keys: unknown[]
124
+ canvas: RasterCanvas | undefined
125
+ }
126
+
127
+ const caches = new WeakMap<object, OverviewCache>()
128
+
129
+ /**
130
+ * Everything the drawn tree depends on. `treeOverviewLayout` and
131
+ * `treeOverviewClades` are computed views, which recompute on every read
132
+ * outside a reaction, so the key holds the values behind them instead.
133
+ */
134
+ function overviewKeys({
135
+ model,
136
+ theme,
137
+ width,
138
+ height,
139
+ scale,
140
+ }: {
141
+ model: MsaViewModel
142
+ theme: Theme
143
+ width: number
144
+ height: number
145
+ scale: number
146
+ }) {
147
+ return [
148
+ model.data.tree,
149
+ model.data.msa,
150
+ model.currentAlignment,
151
+ model.collapsed.join(','),
152
+ JSON.stringify(model.clades),
153
+ model.showBranchLen,
154
+ width,
155
+ height,
156
+ scale,
157
+ theme.palette.text.primary,
158
+ ]
159
+ }
160
+
161
+ function makeCanvas(width: number, height: number) {
162
+ const canvas =
163
+ typeof OffscreenCanvas !== 'undefined'
164
+ ? new OffscreenCanvas(width, height)
165
+ : typeof document !== 'undefined'
166
+ ? Object.assign(document.createElement('canvas'), { width, height })
167
+ : undefined
168
+ const ctx = canvas?.getContext('2d')
169
+ return ctx && typeof (ctx as CanvasRenderingContext2D).stroke === 'function'
170
+ ? { canvas: canvas as RasterCanvas, ctx: ctx as CanvasRenderingContext2D }
171
+ : undefined
172
+ }
173
+
174
+ /**
175
+ * The tree and its clade highlights on an offscreen canvas, drawn once per
176
+ * change of anything they depend on and blitted every frame after that. A
177
+ * 230k-branch tree takes hundreds of milliseconds to walk and cannot be redrawn
178
+ * as the focus box follows the pointer.
179
+ *
180
+ * Returns undefined where a canvas has no 2d context (jsdom), and the caller
181
+ * then draws the tree itself.
182
+ */
183
+ export function treeOverviewImage({
184
+ model,
185
+ theme,
186
+ width,
187
+ height,
188
+ scale,
189
+ }: {
190
+ model: MsaViewModel
191
+ theme: Theme
192
+ width: number
193
+ height: number
194
+ scale: number
195
+ }) {
196
+ const keys = overviewKeys({ model, theme, width, height, scale })
197
+ const prev = caches.get(model)
198
+ if (prev && keys.every((key, i) => key === prev.keys[i])) {
199
+ return prev.canvas
200
+ }
201
+ const made = makeCanvas(
202
+ Math.max(1, Math.round(width * scale)),
203
+ Math.max(1, Math.round(height * scale)),
204
+ )
205
+ if (made) {
206
+ made.ctx.scale(scale, scale)
207
+ drawTreeOverview({ model, ctx: made.ctx, theme, width, height })
208
+ }
209
+ const next = { keys, canvas: made?.canvas }
210
+ caches.set(model, next)
211
+ return next.canvas
212
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A round branch-length step and its width in pixels: the largest 1/2/5 x 10^n
3
+ * substitutions-per-site that fits in `maxPx`, aiming at about half of it.
4
+ * Returns undefined in cladogram mode, where the x-positions carry no length.
5
+ */
6
+ export function scaleBarLength(pxPerUnit: number, maxPx: number) {
7
+ if (pxPerUnit <= 0 || maxPx < 20) {
8
+ return undefined
9
+ }
10
+ const target = maxPx / 2 / pxPerUnit
11
+ const magnitude = 10 ** Math.floor(Math.log10(target))
12
+ const mantissa = target / magnitude
13
+ const step = (mantissa >= 5 ? 5 : mantissa >= 2 ? 2 : 1) * magnitude
14
+ const px = step * pxPerUnit
15
+ return px > maxPx
16
+ ? undefined
17
+ : {
18
+ step,
19
+ px,
20
+ // a length of 0.02 reads as 0.02, not 0.020000000000000004
21
+ label: `${Number(step.toPrecision(2))}`,
22
+ }
23
+ }
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from 'react'
1
+ import { useState } from 'react'
2
2
 
3
3
  import { ClickMapIndex } from './clickMap.ts'
4
4
 
@@ -24,9 +24,7 @@ export function useTreeHover({
24
24
  model: MsaViewModel
25
25
  offsetY: number
26
26
  }) {
27
- const clickMapRef = useRef<ClickMapIndex>(null)
28
- clickMapRef.current ??= new ClickMapIndex()
29
- const clickMap = clickMapRef.current
27
+ const [clickMap] = useState(() => new ClickMapIndex())
30
28
  const [hovered, setHovered] = useState<TreeHoverTarget>()
31
29
 
32
30
  // leaf labels win over the branch/bubble targets they overlap, so a click on a
package/src/constants.ts CHANGED
@@ -5,18 +5,22 @@ export const defaultHeight = 550
5
5
  export const defaultScrollX = 0
6
6
  export const defaultScrollY = 0
7
7
  export const defaultCurrentAlignment = 0
8
- export const defaultShowDomains = false
8
+ // Only a hidden overlay is stored, so a link that never mentions it opens
9
+ // showing its annotations.
10
+ export const defaultShowDomains = true
9
11
  export const defaultShowDomainLegend = true
10
12
  export const defaultHideGaps = true
11
13
  export const defaultAllowedGappyness = 100
12
14
  export const defaultSubFeatureRows = false
13
- // row height for each stacked feature when subFeatureRows is on
15
+ // height of one sub-row when subFeatureRows is on, unless the row has more
16
+ // lanes than fit at this height (see renderBoxFeatureCanvasBlock)
14
17
  export const subFeatureRowHeight = 4
18
+ // height of the bar the overlay draws under a row in letter-color mode
19
+ export const domainUnderlineHeight = 3
15
20
 
16
- // Feature types that are ordinal *segments* of a single transcript (exons and
17
- // the like) rather than categorical domains. Their identity is their position,
18
- // so the overlay alternates two shades to mark boundaries and labels them by
19
- // number, instead of assigning every one a distinct hue + legend row.
21
+ // Feature types that are ordinal segments of one transcript. The overlay
22
+ // alternates two shades across them and labels them by number, where a domain
23
+ // type gets its own hue and legend row.
20
24
  export const segmentFeatureTypes = new Set([
21
25
  'exon',
22
26
  'CDS',
@@ -31,9 +35,16 @@ export const segmentShades = ['#9fb6d4', '#d4dcea']
31
35
  export const defaultDrawMsaLetters = true
32
36
  export const defaultScrollZoom = false
33
37
 
38
+ // Which cell dimensions a wheel zoom scales. 'horizontal' holds the row height,
39
+ // which fixes the label and letter font size while the columns compress.
40
+ // 'vertical' does the reverse.
41
+ export const scrollZoomAxes = ['both', 'horizontal', 'vertical'] as const
42
+ export type ScrollZoomAxis = (typeof scrollZoomAxes)[number]
43
+ export const defaultScrollZoomAxis: ScrollZoomAxis = 'both'
44
+
34
45
  // Cell size floors for drawing residue letters and tree labels. Below these a
35
- // 500px block holds thousands of glyphs, and fillText -- which no sprite atlas
36
- // beats, measured -- dominates every zoom frame.
46
+ // 500px block holds thousands of glyphs, and fillText dominates every zoom
47
+ // frame. A sprite atlas measured slower.
37
48
  export const minLetterRowHeight = 8
38
49
  export const minLetterColWidth = 5
39
50
 
@@ -61,3 +72,45 @@ export const defaultTreeWidth = 300
61
72
  export const defaultShowBranchLen = true
62
73
  export const defaultDrawTree = true
63
74
  export const defaultDrawNodeBubbles = true
75
+ export const defaultDrawNodeLabels = false
76
+ export const defaultShowTreeOverview = false
77
+ export const defaultOverviewHeight = 120
78
+
79
+ // Neighbor joining does not run above this many rows. The distance matrix is
80
+ // O(n^2*L) and the join loop O(n^3), both on the main thread with no progress
81
+ // and no cancel: 400 rows measured 1.5s, 800 rows 10s, and 1600 would be about
82
+ // seventy. See agent-docs/ideas/neighbor-joining-scaling.md.
83
+ export const maxNeighborJoiningRows = 500
84
+
85
+ // The opacity a `rowTint` encoding draws its scale's color at, so the residues
86
+ // under the wash stay readable. A color the producer already gave an alpha
87
+ // keeps it.
88
+ export const rowTintAlpha = 0.25
89
+
90
+ // The fill a `clades` record with the `highlight` mark draws behind its rows,
91
+ // and the opacity a producer's own color takes when it carries none.
92
+ export const cladeHighlightColor = '#fff3c4'
93
+ export const cladeHighlightAlpha = 0.6
94
+
95
+ // The largest inline document the snapshot carries. A larger pasted or
96
+ // locally-opened file stays in the live model and out of the snapshot, keeping
97
+ // shared URLs sendable. `unshareableData` warns using the same number.
98
+ export const maxInlineSnapshotBytes = 50_000
99
+
100
+ // Height of the band the `rowPanels` headers draw in, above the strips in the
101
+ // top area beside the tree ruler and the minimap. A panel wider than the band
102
+ // is tall writes its header across, where it has more room.
103
+ export const rowPanelHeaderHeight = 56
104
+
105
+ // Height of the branch-length scale bar above the tree.
106
+ export const treeScaleBarHeight = 22
107
+
108
+ // Width of a `features` row panel that names none, wide enough for a gene
109
+ // neighborhood of a few arrows to read.
110
+ export const defaultFeaturePanelWidth = 200
111
+
112
+ // The alignment's overlay canvas draws the mouseover, the persisted highlights
113
+ // and the row tints over the residues, and the legend key floats above that,
114
+ // so a tint reaching the top right corner leaves the key readable.
115
+ export const msaOverlayZIndex = 1000
116
+ export const legendZIndex = msaOverlayZIndex + 1
@@ -3,17 +3,18 @@ import { colord } from 'colord'
3
3
  import palettes from './ggplotPalettes.ts'
4
4
 
5
5
  /**
6
- * Creates a map from keys to colors. Up to the largest ggplot palette (8 keys)
7
- * we use the hand-picked ggplot colors; beyond that — e.g. a gene-structure
8
- * overlay with one feature per exon — we generate evenly spaced HSL hues so
9
- * every key still gets a distinct, stable color instead of running off the end
10
- * of the palette.
6
+ * Creates a map from keys to colors. `palette` names the colors to take in
7
+ * order, such as the list a scale asked for by name; without it the keys take
8
+ * the ggplot palette of their own size. Beyond the colors available — a
9
+ * gene-structure overlay with one feature per exon, or nine keys on an
10
+ * eight-color palette — every key gets an evenly spaced HSL hue instead, so no
11
+ * two keys share a color.
11
12
  */
12
- export function createPaletteMap(keys: string[]) {
13
+ export function createPaletteMap(keys: string[], palette?: readonly string[]) {
13
14
  const n = keys.length
14
- if (n <= palettes.length) {
15
- const palette = palettes[n - 1]!
16
- return Object.fromEntries(keys.map((key, i) => [key, palette[i]!]))
15
+ const colors = palette ?? palettes[n - 1]
16
+ if (colors && n <= colors.length) {
17
+ return Object.fromEntries(keys.map((key, i) => [key, colors[i]!]))
17
18
  }
18
19
  return Object.fromEntries(
19
20
  keys.map((key, i) => [
package/src/element.ts ADDED
@@ -0,0 +1,321 @@
1
+ import { when } from 'mobx'
2
+
3
+ import { mount } from './mount.tsx'
4
+
5
+ import type { MSAViewerProps } from './components/MSAViewer.tsx'
6
+ import type { MsaViewModel } from './model.ts'
7
+ import type { MountedViewer } from './mount.tsx'
8
+ import type {
9
+ Cell,
10
+ ColumnTrackSpec,
11
+ Highlight,
12
+ ResidueMapping,
13
+ Viewport,
14
+ } from './types.ts'
15
+
16
+ type ElementData = Pick<
17
+ MSAViewerProps,
18
+ 'msa' | 'tree' | 'gff' | 'highlights' | 'columnTracks' | 'residueMappings'
19
+ >
20
+
21
+ interface NightingaleManager extends HTMLElement {
22
+ register: (element: HTMLElement) => void
23
+ unregister: (element: HTMLElement) => void
24
+ }
25
+
26
+ const ownAttributes = [
27
+ 'msa-url',
28
+ 'tree-url',
29
+ 'gff-url',
30
+ 'color-scheme',
31
+ 'height',
32
+ 'hide-header',
33
+ 'theme',
34
+ 'reference-row',
35
+ 'tree-area-width',
36
+ ] as const
37
+
38
+ // the attributes a <nightingale-manager> writes on the elements it holds
39
+ const nightingaleAttributes = ['display-start', 'display-end', 'highlight']
40
+
41
+ function location(uri: string | null) {
42
+ return uri ? { uri, locationType: 'UriLocation' as const } : undefined
43
+ }
44
+
45
+ function numberAttribute(value: string | null | undefined) {
46
+ const n = value ? Number(value) : Number.NaN
47
+ return Number.isFinite(n) ? n : undefined
48
+ }
49
+
50
+ // Nightingale writes a highlight as "start:end,start:end", 1-based inclusive
51
+ function parseHighlight(value: string | null, row?: string): Highlight[] {
52
+ return (value ?? '').split(',').flatMap(span => {
53
+ const [start, end] = span.split(':').map(numberAttribute)
54
+ return start === undefined ? [] : [{ row, start, end: end ?? start }]
55
+ })
56
+ }
57
+
58
+ function residueAtColumn(model: MsaViewModel, row: string, column: number) {
59
+ const visible = model.globalColToVisibleCol(column - 1)
60
+ return visible === undefined
61
+ ? undefined
62
+ : model.visibleColToSeqPosOneBased(row, visible)
63
+ }
64
+
65
+ // the first and last residues of `row` inside the viewport's columns
66
+ function residueRange(model: MsaViewModel, row: string, viewport: Viewport) {
67
+ let start: number | undefined
68
+ let end: number | undefined
69
+ for (let c = viewport.startColumn; c <= viewport.endColumn && !start; c++) {
70
+ start = residueAtColumn(model, row, c)
71
+ }
72
+ for (let c = viewport.endColumn; c >= viewport.startColumn && !end; c--) {
73
+ end = residueAtColumn(model, row, c)
74
+ }
75
+ return start && end ? { start, end } : undefined
76
+ }
77
+
78
+ /**
79
+ * Registers a custom element that renders MSAViewer, for pages with no React.
80
+ * Attributes: msa-url, tree-url, gff-url, color-scheme, height, hide-header,
81
+ * theme, tree-area-width and reference-row. Properties: msa, tree and gff text, highlights,
82
+ * columnTracks and residueMappings. Events: cell-hover, cell-click and
83
+ * viewport-change, each with the MSAViewer callback's value as detail.
84
+ *
85
+ * Inside a <nightingale-manager>, the element registers with it and speaks its
86
+ * protocol: it follows the display-start, display-end and highlight attributes
87
+ * the manager writes, and reports its own range and hovered residue as change
88
+ * events. Positions are residues of reference-row, or columns without one.
89
+ */
90
+ export function defineMsaElement(tagName = 'jbrowse-msa') {
91
+ const existing = customElements.get(tagName)
92
+ if (existing) {
93
+ return existing
94
+ }
95
+
96
+ class MsaElement extends HTMLElement {
97
+ static observedAttributes = [...ownAttributes, ...nightingaleAttributes]
98
+
99
+ #viewer?: MountedViewer
100
+ #model?: MsaViewModel
101
+ #manager?: NightingaleManager
102
+ #cancelRange?: () => void
103
+ #rangePending = false
104
+ #reported?: { start: number; end: number }
105
+ #data: ElementData = {}
106
+
107
+ get msa() {
108
+ return this.#data.msa
109
+ }
110
+ set msa(value: string | undefined) {
111
+ this.#setData({ msa: value })
112
+ }
113
+ get tree() {
114
+ return this.#data.tree
115
+ }
116
+ set tree(value: string | undefined) {
117
+ this.#setData({ tree: value })
118
+ }
119
+ get gff() {
120
+ return this.#data.gff
121
+ }
122
+ set gff(value: string | undefined) {
123
+ this.#setData({ gff: value })
124
+ }
125
+ get highlights() {
126
+ return this.#data.highlights
127
+ }
128
+ set highlights(value: Highlight[] | undefined) {
129
+ this.#setData({ highlights: value })
130
+ }
131
+ get columnTracks() {
132
+ return this.#data.columnTracks
133
+ }
134
+ set columnTracks(value: ColumnTrackSpec[] | undefined) {
135
+ this.#setData({ columnTracks: value })
136
+ }
137
+ get residueMappings() {
138
+ return this.#data.residueMappings
139
+ }
140
+ set residueMappings(value: ResidueMapping[] | undefined) {
141
+ this.#setData({ residueMappings: value })
142
+ }
143
+
144
+ get #referenceRow() {
145
+ return this.getAttribute('reference-row') ?? undefined
146
+ }
147
+
148
+ #setData(data: ElementData) {
149
+ this.#data = { ...this.#data, ...data }
150
+ this.#viewer?.update(this.#props())
151
+ }
152
+
153
+ #props(): MSAViewerProps {
154
+ const theme = this.getAttribute('theme')
155
+ return {
156
+ ...this.#data,
157
+ msaFilehandle: location(this.getAttribute('msa-url')),
158
+ treeFilehandle: location(this.getAttribute('tree-url')),
159
+ gffFilehandle: location(this.getAttribute('gff-url')),
160
+ colorScheme: this.getAttribute('color-scheme') ?? undefined,
161
+ height: numberAttribute(this.getAttribute('height')),
162
+ treeAreaWidth: numberAttribute(this.getAttribute('tree-area-width')),
163
+ hideHeader: this.hasAttribute('hide-header'),
164
+ theme: theme === 'dark' ? 'dark' : 'light',
165
+ onCellHover: cell => {
166
+ this.#emit('cell-hover', cell)
167
+ this.#reportHover(cell)
168
+ },
169
+ onCellClick: cell => {
170
+ this.#emit('cell-click', cell)
171
+ },
172
+ onViewportChange: viewport => {
173
+ this.#emit('viewport-change', viewport)
174
+ this.#reportRange(viewport)
175
+ },
176
+ onModel: model => {
177
+ this.#model = model
178
+ this.#followRange()
179
+ this.#followHighlight()
180
+ },
181
+ }
182
+ }
183
+
184
+ #emit(type: string, detail: unknown) {
185
+ this.dispatchEvent(new CustomEvent(type, { detail }))
186
+ }
187
+
188
+ #nightingaleChange(detail: Record<string, unknown>) {
189
+ if (this.#manager) {
190
+ this.dispatchEvent(
191
+ new CustomEvent('change', {
192
+ detail,
193
+ bubbles: true,
194
+ cancelable: true,
195
+ }),
196
+ )
197
+ }
198
+ }
199
+
200
+ #reportHover(cell: Cell | undefined) {
201
+ const model = this.#model
202
+ const row = this.#referenceRow
203
+ const position =
204
+ cell && model && row
205
+ ? residueAtColumn(model, row, cell.column)
206
+ : cell?.column
207
+ this.#nightingaleChange({
208
+ type: 'highlight',
209
+ value: position === undefined ? null : `${position}:${position}`,
210
+ })
211
+ }
212
+
213
+ #reportRange(viewport: Viewport | undefined) {
214
+ const model = this.#model
215
+ const row = this.#referenceRow
216
+ if (!viewport || !model) {
217
+ return
218
+ }
219
+ const range = row
220
+ ? residueRange(model, row, viewport)
221
+ : { start: viewport.startColumn, end: viewport.endColumn }
222
+ const shownStart = numberAttribute(this.getAttribute('display-start'))
223
+ const shownEnd = numberAttribute(this.getAttribute('display-end'))
224
+ // a range the manager just wrote comes back within a column of itself,
225
+ // since the viewer shows whole columns
226
+ const echo =
227
+ range &&
228
+ shownStart !== undefined &&
229
+ shownEnd !== undefined &&
230
+ Math.abs(range.start - shownStart) <= 1 &&
231
+ Math.abs(range.end - shownEnd) <= 1
232
+ if (range && !echo) {
233
+ this.#reported = range
234
+ this.#nightingaleChange({
235
+ 'display-start': range.start,
236
+ 'display-end': range.end,
237
+ })
238
+ }
239
+ }
240
+
241
+ #followRange() {
242
+ const model = this.#model
243
+ const start = numberAttribute(this.getAttribute('display-start'))
244
+ const end = numberAttribute(this.getAttribute('display-end'))
245
+ const reported = this.#reported
246
+ // the manager writes the range this element reported back onto it, and
247
+ // zooming to it again would snap the reader's own zoom to whole residues
248
+ if (reported && reported.start === start && reported.end === end) {
249
+ return
250
+ }
251
+ this.#cancelRange?.()
252
+ if (model && start !== undefined && end !== undefined) {
253
+ const row = this.#referenceRow
254
+ this.#cancelRange = when(
255
+ () => model.viewInitialized && model.numColumns > 0,
256
+ () => {
257
+ model.zoomToRegion({ row, start, end })
258
+ },
259
+ )
260
+ }
261
+ }
262
+
263
+ #followHighlight() {
264
+ const spans = parseHighlight(
265
+ this.getAttribute('highlight'),
266
+ this.#referenceRow,
267
+ )
268
+ if (spans.length) {
269
+ this.#model?.applyHighlight('nightingale', spans)
270
+ } else {
271
+ this.#model?.clearHighlight('nightingale')
272
+ }
273
+ }
274
+
275
+ connectedCallback() {
276
+ if (!this.style.display) {
277
+ this.style.display = 'block'
278
+ }
279
+ this.#viewer = mount(this, this.#props())
280
+ const manager = this.closest<NightingaleManager>('nightingale-manager')
281
+ if (manager) {
282
+ void customElements.whenDefined('nightingale-manager').then(() => {
283
+ if (this.isConnected) {
284
+ this.#manager = manager
285
+ manager.register(this)
286
+ }
287
+ })
288
+ }
289
+ }
290
+
291
+ disconnectedCallback() {
292
+ this.#manager?.unregister(this)
293
+ this.#manager = undefined
294
+ this.#cancelRange?.()
295
+ this.#viewer?.destroy()
296
+ this.#viewer = undefined
297
+ this.#model = undefined
298
+ }
299
+
300
+ attributeChangedCallback(name: string) {
301
+ if (name === 'display-start' || name === 'display-end') {
302
+ // the manager writes the two attributes one after the other, and a
303
+ // zoom between them reports a range that overwrites the second
304
+ if (!this.#rangePending) {
305
+ this.#rangePending = true
306
+ queueMicrotask(() => {
307
+ this.#rangePending = false
308
+ this.#followRange()
309
+ })
310
+ }
311
+ } else if (name === 'highlight') {
312
+ this.#followHighlight()
313
+ } else {
314
+ this.#viewer?.update(this.#props())
315
+ }
316
+ }
317
+ }
318
+
319
+ customElements.define(tagName, MsaElement)
320
+ return MsaElement
321
+ }