react-cheminfo 0.66.0 → 0.67.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 (291) hide show
  1. package/README.md +22 -11
  2. package/lib/download/ui/FigureDownloadPanel.d.ts +7 -1
  3. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  4. package/lib/download/ui/FigureDownloadPanel.js +2 -2
  5. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  6. package/lib/ecosystem/core/sites.js +3 -3
  7. package/lib/ecosystem/core/sites.js.map +1 -1
  8. package/lib/ecosystem/core/types.d.ts +1 -1
  9. package/lib/ecosystem/core/types.d.ts.map +1 -1
  10. package/lib/ecosystem/ui/glyphs.js +1 -1
  11. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  12. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  13. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  14. package/lib/i18n/core/messageCatalog.d.ts +2 -2
  15. package/lib/i18n/vite/localeCatalog.d.ts +1 -1
  16. package/lib/ir/core/index.d.ts +2 -2
  17. package/lib/ir/core/index.d.ts.map +1 -1
  18. package/lib/ir/core/index.js.map +1 -1
  19. package/lib/ir/core/irEditorApi.d.ts +12 -0
  20. package/lib/ir/core/irEditorApi.d.ts.map +1 -1
  21. package/lib/ir/core/irReducer.d.ts +2 -2
  22. package/lib/ir/core/irReducer.d.ts.map +1 -1
  23. package/lib/ir/core/irSidePanels.d.ts +7 -1
  24. package/lib/ir/core/irSidePanels.d.ts.map +1 -1
  25. package/lib/ir/core/irState.d.ts +8 -5
  26. package/lib/ir/core/irState.d.ts.map +1 -1
  27. package/lib/ir/core/irState.js.map +1 -1
  28. package/lib/ir/ui/IrCanvas.d.ts +7 -0
  29. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  30. package/lib/ir/ui/IrCanvas.js +8 -3
  31. package/lib/ir/ui/IrCanvas.js.map +1 -1
  32. package/lib/ir/ui/IrChart.d.ts.map +1 -1
  33. package/lib/ir/ui/IrChart.js +5 -1
  34. package/lib/ir/ui/IrChart.js.map +1 -1
  35. package/lib/ir/ui/IrEditor.d.ts +22 -4
  36. package/lib/ir/ui/IrEditor.d.ts.map +1 -1
  37. package/lib/ir/ui/IrEditor.js.map +1 -1
  38. package/lib/ir/ui/IrShell.d.ts +9 -1
  39. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  40. package/lib/ir/ui/IrShell.js +4 -19
  41. package/lib/ir/ui/IrShell.js.map +1 -1
  42. package/lib/ir/ui/IrSidePanel.d.ts +11 -0
  43. package/lib/ir/ui/IrSidePanel.d.ts.map +1 -1
  44. package/lib/ir/ui/IrSidePanel.js +12 -3
  45. package/lib/ir/ui/IrSidePanel.js.map +1 -1
  46. package/lib/ir/ui/RamanEditor.d.ts +22 -4
  47. package/lib/ir/ui/RamanEditor.d.ts.map +1 -1
  48. package/lib/ir/ui/RamanEditor.js.map +1 -1
  49. package/lib/ir/ui/VibrationalEditor.d.ts +18 -4
  50. package/lib/ir/ui/VibrationalEditor.d.ts.map +1 -1
  51. package/lib/ir/ui/VibrationalEditor.js +4 -2
  52. package/lib/ir/ui/VibrationalEditor.js.map +1 -1
  53. package/lib/ir/ui/index.d.ts +1 -0
  54. package/lib/ir/ui/index.d.ts.map +1 -1
  55. package/lib/ir/ui/index.js.map +1 -1
  56. package/lib/ir/ui/irActions.d.ts +3 -3
  57. package/lib/ir/ui/irActions.d.ts.map +1 -1
  58. package/lib/ir/ui/irChartProps.d.ts +7 -0
  59. package/lib/ir/ui/irChartProps.d.ts.map +1 -1
  60. package/lib/ir/ui/irHostPanels.d.ts +53 -0
  61. package/lib/ir/ui/irHostPanels.d.ts.map +1 -0
  62. package/lib/ir/ui/irHostPanels.js +41 -0
  63. package/lib/ir/ui/irHostPanels.js.map +1 -0
  64. package/lib/ir/ui/useIrSide.d.ts +27 -0
  65. package/lib/ir/ui/useIrSide.d.ts.map +1 -0
  66. package/lib/ir/ui/useIrSide.js +42 -0
  67. package/lib/ir/ui/useIrSide.js.map +1 -0
  68. package/lib/ir/ui/useIrZoom.d.ts +8 -0
  69. package/lib/ir/ui/useIrZoom.d.ts.map +1 -1
  70. package/lib/ir/ui/useIrZoom.js +2 -1
  71. package/lib/ir/ui/useIrZoom.js.map +1 -1
  72. package/lib/locales/de.json +9 -1
  73. package/lib/locales/en.json +9 -1
  74. package/lib/locales/es.json +9 -1
  75. package/lib/locales/fr.json +9 -1
  76. package/lib/locales/it.json +9 -1
  77. package/lib/molecule3d/core/animatedPng.d.ts +25 -0
  78. package/lib/molecule3d/core/animatedPng.d.ts.map +1 -0
  79. package/lib/molecule3d/core/animatedPng.js +59 -0
  80. package/lib/molecule3d/core/animatedPng.js.map +1 -0
  81. package/lib/molecule3d/core/file.d.ts +24 -0
  82. package/lib/molecule3d/core/file.d.ts.map +1 -0
  83. package/lib/molecule3d/core/file.js +2 -0
  84. package/lib/molecule3d/core/file.js.map +1 -0
  85. package/lib/molecule3d/core/index.d.ts +4 -1
  86. package/lib/molecule3d/core/index.d.ts.map +1 -1
  87. package/lib/molecule3d/core/index.js +1 -0
  88. package/lib/molecule3d/core/index.js.map +1 -1
  89. package/lib/molecule3d/core/settings.d.ts +0 -7
  90. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  91. package/lib/molecule3d/core/settings.js.map +1 -1
  92. package/lib/molecule3d/ui/Molecule3DExport.d.ts +10 -2
  93. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  94. package/lib/molecule3d/ui/Molecule3DExport.js +15 -2
  95. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  96. package/lib/molecule3d/ui/Molecule3DToolbar.js +9 -2
  97. package/lib/molecule3d/ui/Molecule3DToolbar.js.map +1 -1
  98. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  99. package/lib/molecule3d/ui/MoleculeCanvas3D.js +23 -41
  100. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  101. package/lib/molecule3d/ui/drawScene.d.ts +2 -1
  102. package/lib/molecule3d/ui/drawScene.d.ts.map +1 -1
  103. package/lib/molecule3d/ui/drawScene.js.map +1 -1
  104. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +15 -1
  105. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  106. package/lib/molecule3d/ui/exportMoleculeImage.js +14 -2
  107. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  108. package/lib/molecule3d/ui/moleculeFrames.d.ts +47 -0
  109. package/lib/molecule3d/ui/moleculeFrames.d.ts.map +1 -0
  110. package/lib/molecule3d/ui/moleculeFrames.js +65 -0
  111. package/lib/molecule3d/ui/moleculeFrames.js.map +1 -0
  112. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +14 -1
  113. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  114. package/lib/molecule3d/ui/renderMolecule.d.ts +3 -3
  115. package/lib/molecule3d/ui/renderMolecule.d.ts.map +1 -1
  116. package/lib/molecule3d/ui/renderMolecule.js +2 -1
  117. package/lib/molecule3d/ui/renderMolecule.js.map +1 -1
  118. package/lib/molecule3d/ui/sceneDrawer.d.ts +2 -1
  119. package/lib/molecule3d/ui/sceneDrawer.d.ts.map +1 -1
  120. package/lib/molecule3d/ui/sceneDrawer.js +9 -1
  121. package/lib/molecule3d/ui/sceneDrawer.js.map +1 -1
  122. package/lib/molecule3d/ui/useImageExport.d.ts +8 -3
  123. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  124. package/lib/molecule3d/ui/useImageExport.js +13 -4
  125. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  126. package/lib/molecule3d/ui/useMoleculeScene.d.ts +2 -1
  127. package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -1
  128. package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -1
  129. package/lib/molecule3d/ui/useMoleculeViewer.d.ts +16 -0
  130. package/lib/molecule3d/ui/useMoleculeViewer.d.ts.map +1 -0
  131. package/lib/molecule3d/ui/useMoleculeViewer.js +37 -0
  132. package/lib/molecule3d/ui/useMoleculeViewer.js.map +1 -0
  133. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts +3 -3
  134. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts.map +1 -1
  135. package/lib/molecule3d/ui/usePolarSurfaceArea.js +4 -3
  136. package/lib/molecule3d/ui/usePolarSurfaceArea.js.map +1 -1
  137. package/lib/molecule3d/ui/viewer.d.ts +4 -1
  138. package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
  139. package/lib/molecule3d/ui/viewer.js +21 -19
  140. package/lib/molecule3d/ui/viewer.js.map +1 -1
  141. package/lib/molecule3d/ui/viewerTypes.d.ts +6 -0
  142. package/lib/molecule3d/ui/viewerTypes.d.ts.map +1 -1
  143. package/lib/molstar/core/captureScene.d.ts +19 -1
  144. package/lib/molstar/core/captureScene.d.ts.map +1 -1
  145. package/lib/molstar/core/captureScene.js +57 -2
  146. package/lib/molstar/core/captureScene.js.map +1 -1
  147. package/lib/molstar/core/index.d.ts +5 -2
  148. package/lib/molstar/core/index.d.ts.map +1 -1
  149. package/lib/molstar/core/index.js +4 -2
  150. package/lib/molstar/core/index.js.map +1 -1
  151. package/lib/molstar/core/trajectory.d.ts +56 -0
  152. package/lib/molstar/core/trajectory.d.ts.map +1 -0
  153. package/lib/molstar/core/trajectory.js +96 -0
  154. package/lib/molstar/core/trajectory.js.map +1 -0
  155. package/lib/panel/ui/PanelRail.d.ts.map +1 -1
  156. package/lib/panel/ui/PanelRail.js +1 -1
  157. package/lib/panel/ui/PanelRail.js.map +1 -1
  158. package/lib/raman.d.ts +2 -1
  159. package/lib/raman.d.ts.map +1 -1
  160. package/lib/raman.js.map +1 -1
  161. package/lib/router/core/basePath.d.ts +2 -2
  162. package/lib/router/core/basePath.js +2 -2
  163. package/lib/router/core/siteAddresses.d.ts +1 -1
  164. package/lib/router/core/siteAddresses.js +1 -1
  165. package/lib/seo/core/siteFiles.js +1 -1
  166. package/lib/seo/core/siteFiles.js.map +1 -1
  167. package/lib/server/answerAddresses.d.ts +3 -3
  168. package/lib/server/answerAddresses.js +3 -3
  169. package/lib/structure/core/index.d.ts +2 -0
  170. package/lib/structure/core/index.d.ts.map +1 -1
  171. package/lib/structure/core/index.js +1 -0
  172. package/lib/structure/core/index.js.map +1 -1
  173. package/lib/structure/core/structureTransfer.d.ts +29 -0
  174. package/lib/structure/core/structureTransfer.d.ts.map +1 -0
  175. package/lib/structure/core/structureTransfer.js +26 -0
  176. package/lib/structure/core/structureTransfer.js.map +1 -0
  177. package/lib/structure/ui/EditorImportButton.d.ts +20 -0
  178. package/lib/structure/ui/EditorImportButton.d.ts.map +1 -0
  179. package/lib/structure/ui/EditorImportButton.js +24 -0
  180. package/lib/structure/ui/EditorImportButton.js.map +1 -0
  181. package/lib/structure/ui/EditorImportProblem.d.ts +17 -0
  182. package/lib/structure/ui/EditorImportProblem.d.ts.map +1 -0
  183. package/lib/structure/ui/EditorImportProblem.js +32 -0
  184. package/lib/structure/ui/EditorImportProblem.js.map +1 -0
  185. package/lib/structure/ui/StructureDrop.d.ts +39 -0
  186. package/lib/structure/ui/StructureDrop.d.ts.map +1 -0
  187. package/lib/structure/ui/StructureDrop.js +19 -0
  188. package/lib/structure/ui/StructureDrop.js.map +1 -0
  189. package/lib/structure/ui/StructureDropOverlay.d.ts +16 -0
  190. package/lib/structure/ui/StructureDropOverlay.d.ts.map +1 -0
  191. package/lib/structure/ui/StructureDropOverlay.js +29 -0
  192. package/lib/structure/ui/StructureDropOverlay.js.map +1 -0
  193. package/lib/structure/ui/StructureEditor.d.ts +3 -80
  194. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  195. package/lib/structure/ui/StructureEditor.js +21 -33
  196. package/lib/structure/ui/StructureEditor.js.map +1 -1
  197. package/lib/structure/ui/importedStructure.d.ts +25 -0
  198. package/lib/structure/ui/importedStructure.d.ts.map +1 -0
  199. package/lib/structure/ui/importedStructure.js +74 -0
  200. package/lib/structure/ui/importedStructure.js.map +1 -0
  201. package/lib/structure/ui/index.d.ts +2 -0
  202. package/lib/structure/ui/index.d.ts.map +1 -1
  203. package/lib/structure/ui/index.js +1 -0
  204. package/lib/structure/ui/index.js.map +1 -1
  205. package/lib/structure/ui/structureEditorProps.d.ts +98 -0
  206. package/lib/structure/ui/structureEditorProps.d.ts.map +1 -0
  207. package/lib/structure/ui/structureEditorProps.js +2 -0
  208. package/lib/structure/ui/structureEditorProps.js.map +1 -0
  209. package/lib/structure/ui/useDebouncedChange.d.ts +16 -0
  210. package/lib/structure/ui/useDebouncedChange.d.ts.map +1 -0
  211. package/lib/structure/ui/useDebouncedChange.js +30 -0
  212. package/lib/structure/ui/useDebouncedChange.js.map +1 -0
  213. package/lib/structure/ui/useStructureDrop.d.ts +30 -0
  214. package/lib/structure/ui/useStructureDrop.d.ts.map +1 -0
  215. package/lib/structure/ui/useStructureDrop.js +49 -0
  216. package/lib/structure/ui/useStructureDrop.js.map +1 -0
  217. package/lib/structure/ui/useStructureImport.d.ts +45 -0
  218. package/lib/structure/ui/useStructureImport.d.ts.map +1 -0
  219. package/lib/structure/ui/useStructureImport.js +62 -0
  220. package/lib/structure/ui/useStructureImport.js.map +1 -0
  221. package/package.json +4 -1
  222. package/src/download/ui/FigureDownloadPanel.tsx +9 -2
  223. package/src/ecosystem/core/sites.ts +3 -3
  224. package/src/ecosystem/core/types.ts +1 -1
  225. package/src/ecosystem/ui/glyphs.tsx +1 -1
  226. package/src/i18n/core/messageCatalog.ts +2 -2
  227. package/src/i18n/vite/localeCatalog.ts +1 -1
  228. package/src/ir/core/index.ts +6 -2
  229. package/src/ir/core/irEditorApi.ts +13 -0
  230. package/src/ir/core/irReducer.ts +2 -2
  231. package/src/ir/core/irSidePanels.ts +8 -1
  232. package/src/ir/core/irState.ts +8 -5
  233. package/src/ir/ui/IrCanvas.tsx +27 -2
  234. package/src/ir/ui/IrChart.tsx +11 -0
  235. package/src/ir/ui/IrEditor.tsx +22 -4
  236. package/src/ir/ui/IrShell.tsx +21 -22
  237. package/src/ir/ui/IrSidePanel.tsx +36 -10
  238. package/src/ir/ui/RamanEditor.tsx +22 -4
  239. package/src/ir/ui/VibrationalEditor.tsx +24 -5
  240. package/src/ir/ui/index.ts +1 -0
  241. package/src/ir/ui/irActions.ts +3 -3
  242. package/src/ir/ui/irChartProps.ts +7 -0
  243. package/src/ir/ui/irHostPanels.ts +76 -0
  244. package/src/ir/ui/useIrSide.ts +60 -0
  245. package/src/ir/ui/useIrZoom.ts +10 -0
  246. package/src/locales/de.json +9 -1
  247. package/src/locales/en.json +9 -1
  248. package/src/locales/es.json +9 -1
  249. package/src/locales/fr.json +9 -1
  250. package/src/locales/it.json +9 -1
  251. package/src/molecule3d/core/animatedPng.ts +79 -0
  252. package/src/molecule3d/core/file.ts +23 -0
  253. package/src/molecule3d/core/index.ts +3 -1
  254. package/src/molecule3d/core/settings.ts +0 -8
  255. package/src/molecule3d/ui/Molecule3DExport.tsx +43 -5
  256. package/src/molecule3d/ui/Molecule3DToolbar.tsx +9 -2
  257. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +24 -38
  258. package/src/molecule3d/ui/drawScene.ts +2 -1
  259. package/src/molecule3d/ui/exportMoleculeImage.ts +28 -3
  260. package/src/molecule3d/ui/moleculeFrames.ts +88 -0
  261. package/src/molecule3d/ui/moleculeViewer3DProps.ts +14 -5
  262. package/src/molecule3d/ui/renderMolecule.ts +5 -3
  263. package/src/molecule3d/ui/sceneDrawer.ts +14 -2
  264. package/src/molecule3d/ui/useImageExport.ts +22 -5
  265. package/src/molecule3d/ui/useMoleculeScene.ts +2 -1
  266. package/src/molecule3d/ui/useMoleculeViewer.ts +46 -0
  267. package/src/molecule3d/ui/usePolarSurfaceArea.ts +6 -4
  268. package/src/molecule3d/ui/viewer.ts +34 -27
  269. package/src/molecule3d/ui/viewerTypes.ts +6 -0
  270. package/src/molstar/core/captureScene.ts +85 -2
  271. package/src/molstar/core/index.ts +13 -2
  272. package/src/molstar/core/trajectory.ts +117 -0
  273. package/src/panel/ui/PanelRail.tsx +3 -0
  274. package/src/raman.ts +2 -1
  275. package/src/router/core/basePath.ts +2 -2
  276. package/src/router/core/siteAddresses.ts +1 -1
  277. package/src/seo/core/siteFiles.ts +1 -1
  278. package/src/server/answerAddresses.ts +3 -3
  279. package/src/structure/core/index.ts +8 -0
  280. package/src/structure/core/structureTransfer.ts +37 -0
  281. package/src/structure/ui/EditorImportButton.tsx +59 -0
  282. package/src/structure/ui/EditorImportProblem.tsx +63 -0
  283. package/src/structure/ui/StructureDrop.tsx +66 -0
  284. package/src/structure/ui/StructureDropOverlay.tsx +50 -0
  285. package/src/structure/ui/StructureEditor.tsx +49 -134
  286. package/src/structure/ui/importedStructure.ts +83 -0
  287. package/src/structure/ui/index.ts +2 -0
  288. package/src/structure/ui/structureEditorProps.ts +103 -0
  289. package/src/structure/ui/useDebouncedChange.ts +41 -0
  290. package/src/structure/ui/useStructureDrop.ts +76 -0
  291. package/src/structure/ui/useStructureImport.ts +122 -0
@@ -1,6 +1,7 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
2
 
3
3
  import type { IrBand } from '../core/irBand.ts';
4
+ import type { IrPlotClick } from '../core/irEditorApi.ts';
4
5
  import type { IrDomain } from '../core/irSpectrum.ts';
5
6
 
6
7
  import { IrChart } from './IrChart.tsx';
@@ -32,6 +33,12 @@ export interface IrCanvasProps {
32
33
  * @default undefined
33
34
  */
34
35
  chartId?: string;
36
+ /**
37
+ * Told of a click on the plot — a press let go without travelling, never the
38
+ * end of a drag — with the spectrum selected at that moment.
39
+ * @default undefined
40
+ */
41
+ onPlotClick?: (click: IrPlotClick) => void;
35
42
  }
36
43
 
37
44
  /**
@@ -50,7 +57,13 @@ export interface IrCanvasProps {
50
57
  * @returns The canvas.
51
58
  */
52
59
  export function IrCanvas(props: IrCanvasProps) {
53
- const { highlight = null, onHighlight, attachChart, chartId } = props;
60
+ const {
61
+ highlight = null,
62
+ onHighlight,
63
+ attachChart,
64
+ chartId,
65
+ onPlotClick,
66
+ } = props;
54
67
  const { state, visibleSpectra, assigned } = useIrEditorState();
55
68
  const actions = useIrActions();
56
69
  const { domain, tool, selectedId } = state.view;
@@ -63,6 +76,17 @@ export function IrCanvas(props: IrCanvasProps) {
63
76
  [actions],
64
77
  );
65
78
 
79
+ // Which spectrum was selected is the viewer's to say, not the chart's: the
80
+ // chart only knows where the press landed.
81
+ const onClickAt = useMemo(
82
+ () =>
83
+ onPlotClick === undefined
84
+ ? undefined
85
+ : (wavenumber: number) =>
86
+ onPlotClick({ wavenumber, spectrumId: selectedId }),
87
+ [onPlotClick, selectedId],
88
+ );
89
+
66
90
  return (
67
91
  <div style={rootStyle}>
68
92
  <div ref={attachChart} id={chartId} style={chartStyle}>
@@ -79,6 +103,7 @@ export function IrCanvas(props: IrCanvasProps) {
79
103
  onDomainChange={onDomainChange}
80
104
  drag={tool === 'box' ? 'box' : 'xAxis'}
81
105
  emphasisedId={selectedId}
106
+ onPlotClick={onClickAt}
82
107
  />
83
108
  </div>
84
109
  <IrStatusBar />
@@ -3,6 +3,7 @@ import { useCallback, useId, useLayoutEffect, useMemo, useRef } from 'react';
3
3
 
4
4
  import { sameChartDomain } from '../../chart/core/chartDomain.ts';
5
5
  import { MARGIN, plotRect } from '../../chart/core/chartGeometry.ts';
6
+ import type { ChartPoint } from '../../chart/core/svgPoint.ts';
6
7
  import { Axes } from '../../chart/ui/Axes.tsx';
7
8
  import { SelectionRect } from '../../chart/ui/SelectionRect.tsx';
8
9
  import { chartSurfaceStyle } from '../../chart/ui/chartSurface.ts';
@@ -56,6 +57,7 @@ export function IrChart(props: IrChartProps): ReactElement {
56
57
  onHighlight,
57
58
  domain = null,
58
59
  onDomainChange,
60
+ onPlotClick,
59
61
  drag = 'xAxis',
60
62
  emphasisedId = null,
61
63
  width,
@@ -81,6 +83,14 @@ export function IrChart(props: IrChartProps): ReactElement {
81
83
  const frameWidth = plot.right + MARGIN.right;
82
84
  const frameHeight = plot.bottom + MARGIN.bottom;
83
85
 
86
+ const onClick = useMemo(
87
+ () =>
88
+ onPlotClick === undefined
89
+ ? undefined
90
+ : (point: ChartPoint) => onPlotClick(point.x),
91
+ [onPlotClick],
92
+ );
93
+
84
94
  // The host's window goes *into* the zoom rather than over it, so a drag is
85
95
  // always measured against what is on screen.
86
96
  const zoom = useIrZoom({
@@ -91,6 +101,7 @@ export function IrChart(props: IrChartProps): ReactElement {
91
101
  plot,
92
102
  domain,
93
103
  drag,
104
+ onClick,
94
105
  });
95
106
 
96
107
  // The scales the zoom measured its gestures against — the x one running right
@@ -1,10 +1,11 @@
1
1
  import type { Ref } from 'react';
2
2
 
3
- import type { IrEditorHandle } from '../core/irEditorApi.ts';
4
- import type { IrSidePanelId } from '../core/irSidePanels.ts';
3
+ import type { IrEditorHandle, IrPlotClick } from '../core/irEditorApi.ts';
4
+ import type { IrPanelId } from '../core/irSidePanels.ts';
5
5
  import type { IrMode, IrSpectrum } from '../core/irSpectrum.ts';
6
6
 
7
7
  import { VibrationalEditor } from './VibrationalEditor.tsx';
8
+ import type { IrHostPanel } from './irHostPanels.ts';
8
9
 
9
10
  export interface IrEditorProps {
10
11
  /**
@@ -32,10 +33,27 @@ export interface IrEditorProps {
32
33
  */
33
34
  mode?: IrMode;
34
35
  /**
35
- * The side panels open to begin with.
36
+ * The side panels open to begin with: the viewer's own — `'spectra'`,
37
+ * `'bands'`, `'metadata'` — or the id of one of `panels`. An id no panel
38
+ * answers to is ignored.
36
39
  * @default DEFAULT_SIDE_PANELS
37
40
  */
38
- defaultPanelIds?: IrSidePanelId[];
41
+ defaultPanelIds?: readonly IrPanelId[];
42
+ /**
43
+ * Panels the host adds to the viewer's own — a structure to predict a
44
+ * spectrum from, say. Their icons come first in the activity bar, in the
45
+ * order given, and each then behaves as a built-in panel does: toggled on its
46
+ * own, stacked in the same column with a share of its height, folded and
47
+ * closed from its header.
48
+ * @default []
49
+ */
50
+ panels?: readonly IrHostPanel[];
51
+ /**
52
+ * The plot was clicked at this wavenumber (a press that did not travel, i.e.
53
+ * not a zoom drag), with the spectrum selected at that moment.
54
+ * @default undefined
55
+ */
56
+ onPlotClick?: (click: IrPlotClick) => void;
39
57
  /**
40
58
  * The handle a host drives the viewer through: take these spectra, frame this
41
59
  * wavenumber, select that one.
@@ -7,11 +7,9 @@ import { useFigureDownload } from '../../download/ui/useFigureDownload.tsx';
7
7
  import { PanelRail } from '../../panel/ui/PanelRail.tsx';
8
8
  import type { IrBand } from '../core/irBand.ts';
9
9
  import type { IrCommandHandlers } from '../core/irCommands.ts';
10
- import type { IrEditorHandle } from '../core/irEditorApi.ts';
10
+ import type { IrEditorHandle, IrPlotClick } from '../core/irEditorApi.ts';
11
11
  import { otherIrMode } from '../core/irMode.ts';
12
- import { irSidePanels } from '../core/irSidePanels.ts';
13
12
  import type { IrSpectrum } from '../core/irSpectrum.ts';
14
- import { DEFAULT_SIDE_PANEL } from '../core/irState.ts';
15
13
  import { irCommandOffered } from '../core/irTechniqueCommands.ts';
16
14
 
17
15
  import { IrAboutDialog } from './IrAboutDialog.tsx';
@@ -21,9 +19,11 @@ import { IrDocumentationDialog } from './IrDocumentationDialog.tsx';
21
19
  import { IrHeader } from './IrHeader.tsx';
22
20
  import { IrSidePanel } from './IrSidePanel.tsx';
23
21
  import { IrToolbar } from './IrToolbar.tsx';
22
+ import type { IrHostPanel } from './irHostPanels.ts';
24
23
  import { useIrActions, useIrEditorState } from './irStateContext.ts';
25
24
  import { useIrHandle } from './useIrHandle.ts';
26
25
  import { useIrShortcuts } from './useIrShortcuts.ts';
26
+ import { useIrSide } from './useIrSide.ts';
27
27
  import { useValueAxisZoom } from './useValueAxisZoom.ts';
28
28
 
29
29
  export interface IrShellProps {
@@ -32,6 +32,13 @@ export interface IrShellProps {
32
32
  * @default undefined
33
33
  */
34
34
  handle?: Ref<IrEditorHandle>;
35
+ /** The panels the host adds to the viewer's own, ahead of them. */
36
+ panels: readonly IrHostPanel[];
37
+ /**
38
+ * Told of a click on the plot, with the spectrum selected at that moment.
39
+ * @default undefined
40
+ */
41
+ onPlotClick?: (click: IrPlotClick) => void;
35
42
  }
36
43
 
37
44
  /**
@@ -49,12 +56,13 @@ export interface IrShellProps {
49
56
  * @returns The screen.
50
57
  */
51
58
  export function IrShell(props: IrShellProps) {
52
- const { handle } = props;
59
+ const { handle, panels, onPlotClick } = props;
53
60
 
54
61
  const { state, visibleSpectra, selectedSpectrum } = useIrEditorState();
55
62
  const actions = useIrActions();
56
63
  const { toggle: toggleFullScreen } = useFullscreen();
57
64
  const { openPanelIds, tool } = state.view;
65
+ const side = useIrSide(panels);
58
66
  const { mode, pickBands, showAssignments, technique } = state.settings;
59
67
 
60
68
  const [isAboutOpen, setIsAboutOpen] = useState(false);
@@ -122,20 +130,6 @@ export function IrShell(props: IrShellProps) {
122
130
  useIrShortcuts(root, commands);
123
131
  useIrHandle(handle);
124
132
 
125
- /**
126
- * Open or close the side, as the splitter is dragged shut and back open.
127
- * @param open - Whether the side is now open.
128
- */
129
- function setSideOpen(open: boolean): void {
130
- if (!open) {
131
- for (const panelId of openPanelIds) actions.togglePanel(panelId);
132
- return;
133
- }
134
- // Dragging the splitter back open must land on something, or it opens on an
135
- // empty column and reads as broken.
136
- if (openPanelIds.length === 0) actions.togglePanel(DEFAULT_SIDE_PANEL);
137
- }
138
-
139
133
  return (
140
134
  <IrCommandProvider handlers={commands} technique={technique}>
141
135
  {/* `tabIndex` so a click anywhere in the viewer makes this the target the
@@ -156,23 +150,28 @@ export function IrShell(props: IrShellProps) {
156
150
  direction="horizontal"
157
151
  controlledSide="end"
158
152
  defaultSize="360px"
159
- open={openPanelIds.length > 0}
160
- onOpenChange={setSideOpen}
153
+ open={side.isOpen}
154
+ onOpenChange={side.setOpen}
161
155
  >
162
156
  <IrCanvas
163
157
  highlight={highlight}
164
158
  onHighlight={setHighlight}
165
159
  chartId={chartId}
160
+ onPlotClick={onPlotClick}
166
161
  />
167
162
 
168
- <IrSidePanel highlight={highlight} onHighlight={setHighlight} />
163
+ <IrSidePanel
164
+ panels={panels}
165
+ highlight={highlight}
166
+ onHighlight={setHighlight}
167
+ />
169
168
  </SplitPane>
170
169
 
171
170
  {/* Outside the split, so the icons stay put against the right edge
172
171
  while the panel beside them is resized or closed. */}
173
172
  <div style={railStyle}>
174
173
  <PanelRail
175
- panels={irSidePanels}
174
+ panels={side.panels}
176
175
  openIds={openPanelIds}
177
176
  onToggle={actions.togglePanel}
178
177
  />
@@ -1,14 +1,24 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { panelStyle } from '../../panel/core/panelStyles.ts';
1
4
  import { SidePanelStack } from '../../panel/ui/SidePanelStack.tsx';
2
5
  import type { IrBand } from '../core/irBand.ts';
3
6
  import type { IrSidePanelId } from '../core/irSidePanels.ts';
4
- import { irSidePanels } from '../core/irSidePanels.ts';
5
7
 
6
8
  import { BandsPanel } from './BandsPanel.tsx';
7
9
  import { MetadataPanel } from './MetadataPanel.tsx';
8
10
  import { SpectraPanel } from './SpectraPanel.tsx';
11
+ import type { IrHostPanel } from './irHostPanels.ts';
12
+ import { irStackPanels, isIrHostPanel } from './irHostPanels.ts';
9
13
  import { useIrActions, useIrEditorState } from './irStateContext.ts';
10
14
 
11
15
  export interface IrSidePanelProps {
16
+ /**
17
+ * The panels the host adds to the viewer's own, stacked above them in the
18
+ * order given.
19
+ * @default []
20
+ */
21
+ panels?: readonly IrHostPanel[];
12
22
  /**
13
23
  * The band the pointer is resting on.
14
24
  * @default null
@@ -29,28 +39,41 @@ export interface IrSidePanelProps {
29
39
  * bands are is one question asked of two panels. The header carries the close
30
40
  * button and nothing else — a panel's own actions are a toolbar inside its body,
31
41
  * so they travel with it.
42
+ *
43
+ * A host's panel is stacked like the viewer's own, and its body is laid out in
44
+ * the same column a built-in panel fills, so it takes its share of the height
45
+ * and scrolls within it rather than pushing the stack.
32
46
  * @param props - Component props.
33
47
  * @returns The panel stack.
34
48
  */
35
49
  export function IrSidePanel(props: IrSidePanelProps) {
36
- const { highlight = null, onHighlight } = props;
50
+ const {
51
+ panels: hostPanels = NO_PANELS,
52
+ highlight = null,
53
+ onHighlight,
54
+ } = props;
37
55
  const { state } = useIrEditorState();
38
56
  const actions = useIrActions();
39
57
  const { openPanelIds } = state.view;
58
+ const panels = useMemo(() => irStackPanels(hostPanels), [hostPanels]);
40
59
 
41
60
  return (
42
61
  <SidePanelStack
43
- panels={irSidePanels}
62
+ panels={panels}
44
63
  openPanelIds={openPanelIds}
45
64
  onClose={actions.togglePanel}
46
65
  >
47
- {(panel) => (
48
- <PanelBody
49
- panelId={panel.id}
50
- highlight={highlight}
51
- onHighlight={onHighlight}
52
- />
53
- )}
66
+ {(panel) =>
67
+ isIrHostPanel(panel) ? (
68
+ <div style={panelStyle}>{panel.content}</div>
69
+ ) : (
70
+ <PanelBody
71
+ panelId={panel.id}
72
+ highlight={highlight}
73
+ onHighlight={onHighlight}
74
+ />
75
+ )
76
+ }
54
77
  </SidePanelStack>
55
78
  );
56
79
  }
@@ -76,3 +99,6 @@ function PanelBody(props: PanelBodyProps) {
76
99
  if (panelId === 'metadata') return <MetadataPanel />;
77
100
  return <BandsPanel highlight={highlight} onHighlight={onHighlight} />;
78
101
  }
102
+
103
+ /** What a stack the host adds nothing to holds, kept stable for the memo. */
104
+ const NO_PANELS: readonly IrHostPanel[] = [];
@@ -1,10 +1,11 @@
1
1
  import type { Ref } from 'react';
2
2
 
3
- import type { IrEditorHandle } from '../core/irEditorApi.ts';
4
- import type { IrSidePanelId } from '../core/irSidePanels.ts';
3
+ import type { IrEditorHandle, IrPlotClick } from '../core/irEditorApi.ts';
4
+ import type { IrPanelId } from '../core/irSidePanels.ts';
5
5
  import type { IrSpectrum } from '../core/irSpectrum.ts';
6
6
 
7
7
  import { VibrationalEditor } from './VibrationalEditor.tsx';
8
+ import type { IrHostPanel } from './irHostPanels.ts';
8
9
 
9
10
  /** The handle a host drives a Raman viewer through: the infrared one's. */
10
11
  export type RamanEditorHandle = IrEditorHandle;
@@ -25,10 +26,27 @@ export interface RamanEditorProps {
25
26
  */
26
27
  onChange?: (spectra: IrSpectrum[]) => void;
27
28
  /**
28
- * The side panels open to begin with.
29
+ * The side panels open to begin with: the viewer's own — `'spectra'`,
30
+ * `'bands'`, `'metadata'` — or the id of one of `panels`. An id no panel
31
+ * answers to is ignored.
29
32
  * @default DEFAULT_SIDE_PANELS
30
33
  */
31
- defaultPanelIds?: IrSidePanelId[];
34
+ defaultPanelIds?: readonly IrPanelId[];
35
+ /**
36
+ * Panels the host adds to the viewer's own. Their icons come first in the
37
+ * activity bar, in the order given, and each then behaves as a built-in panel
38
+ * does: toggled on its own, stacked in the same column with a share of its
39
+ * height, folded and closed from its header.
40
+ * @default []
41
+ */
42
+ panels?: readonly IrHostPanel[];
43
+ /**
44
+ * The plot was clicked at this Raman shift (a press that did not travel, i.e.
45
+ * not a zoom drag), with the spectrum selected at that moment. The shift is
46
+ * reported as `wavenumber`, the name both techniques share.
47
+ * @default undefined
48
+ */
49
+ onPlotClick?: (click: IrPlotClick) => void;
32
50
  /**
33
51
  * The handle a host drives the viewer through: take these spectra, frame this
34
52
  * Raman shift, select that one.
@@ -1,13 +1,14 @@
1
1
  import type { Ref } from 'react';
2
2
  import { RootLayout } from 'react-science/ui';
3
3
 
4
- import type { IrEditorHandle } from '../core/irEditorApi.ts';
5
- import type { IrSidePanelId } from '../core/irSidePanels.ts';
4
+ import type { IrEditorHandle, IrPlotClick } from '../core/irEditorApi.ts';
5
+ import type { IrPanelId } from '../core/irSidePanels.ts';
6
6
  import type { IrMode, IrSpectrum } from '../core/irSpectrum.ts';
7
7
  import type { IrTechnique } from '../core/irTechnique.ts';
8
8
 
9
9
  import { IrShell } from './IrShell.tsx';
10
10
  import { IrStateProvider } from './IrStateProvider.tsx';
11
+ import type { IrHostPanel } from './irHostPanels.ts';
11
12
  import { useIrEditor } from './useIrEditor.ts';
12
13
 
13
14
  export interface VibrationalEditorProps {
@@ -34,10 +35,23 @@ export interface VibrationalEditorProps {
34
35
  */
35
36
  mode?: IrMode;
36
37
  /**
37
- * The side panels open to begin with.
38
+ * The side panels open to begin with, the viewer's own or the host's. An id
39
+ * no panel answers to is ignored.
38
40
  * @default DEFAULT_SIDE_PANELS
39
41
  */
40
- defaultPanelIds?: IrSidePanelId[];
42
+ defaultPanelIds?: readonly IrPanelId[];
43
+ /**
44
+ * Panels the host adds to the viewer's own: their icons come first in the
45
+ * activity bar, in the order given, and each behaves as a built-in panel does.
46
+ * @default []
47
+ */
48
+ panels?: readonly IrHostPanel[];
49
+ /**
50
+ * The plot was clicked at this wavenumber (a press that did not travel, i.e.
51
+ * not a zoom drag), with the spectrum selected at that moment.
52
+ * @default undefined
53
+ */
54
+ onPlotClick?: (click: IrPlotClick) => void;
41
55
  /**
42
56
  * The handle a host drives the viewer through.
43
57
  * @default undefined
@@ -59,6 +73,8 @@ export function VibrationalEditor(props: VibrationalEditorProps) {
59
73
  onChange,
60
74
  mode,
61
75
  defaultPanelIds,
76
+ panels = NO_PANELS,
77
+ onPlotClick,
62
78
  ref,
63
79
  } = props;
64
80
 
@@ -77,7 +93,7 @@ export function VibrationalEditor(props: VibrationalEditorProps) {
77
93
  the backdrop white. Above the shell rather than inside it, so the shell
78
94
  can ask whether it is full screen and offer the way out. */}
79
95
  <RootLayout style={rootLayoutStyle}>
80
- <IrShell handle={ref} />
96
+ <IrShell handle={ref} panels={panels} onPlotClick={onPlotClick} />
81
97
  </RootLayout>
82
98
  </IrStateProvider>
83
99
  );
@@ -92,3 +108,6 @@ const rootLayoutStyle = { minHeight: 0 } as const;
92
108
 
93
109
  /** What a viewer opens on when it is handed nothing, kept stable for the memo. */
94
110
  const NO_SPECTRA: IrSpectrum[] = [];
111
+
112
+ /** What a viewer the host adds no panel to offers, kept stable for the memo. */
113
+ const NO_PANELS: readonly IrHostPanel[] = [];
@@ -47,6 +47,7 @@ export { createIrActions } from './irActions.ts';
47
47
  export type { IrChartProps } from './irChartProps.ts';
48
48
  export type { IrCommandContextValue } from './irCommandContext.tsx';
49
49
  export { useIrCommand, useIrCommandProps } from './irCommandContext.tsx';
50
+ export type { IrHostPanel } from './irHostPanels.ts';
50
51
  export { useIrActions, useIrEditorState } from './irStateContext.ts';
51
52
  export type { IrEditorApi, UseIrEditorOptions } from './useIrEditor.ts';
52
53
  export { useIrEditor } from './useIrEditor.ts';
@@ -9,7 +9,7 @@
9
9
  import type { Dispatch } from 'react';
10
10
 
11
11
  import type { IrAction } from '../core/irReducer.ts';
12
- import type { IrSidePanelId } from '../core/irSidePanels.ts';
12
+ import type { IrPanelId } from '../core/irSidePanels.ts';
13
13
  import type { IrDomain, IrSpectrum } from '../core/irSpectrum.ts';
14
14
  import type { IrSettings, IrTool, PartialIrState } from '../core/irState.ts';
15
15
 
@@ -35,8 +35,8 @@ export interface IrEditorActions {
35
35
  resetDomain: () => void;
36
36
  /** Run this tool on the next drag. */
37
37
  setTool: (tool: IrTool) => void;
38
- /** Open or close a side panel. */
39
- togglePanel: (panelId: IrSidePanelId) => void;
38
+ /** Open or close a side panel, the viewer's own or one a host added. */
39
+ togglePanel: (panelId: IrPanelId) => void;
40
40
  /** Say why something was refused, or that nothing is wrong any more. */
41
41
  setError: (error: string | null) => void;
42
42
  /** Change how the spectra are drawn and read. */
@@ -94,6 +94,13 @@ export interface IrChartProps {
94
94
  * @default undefined
95
95
  */
96
96
  onDomainChange?: (domain: IrDomain) => void;
97
+ /**
98
+ * Told the wavenumber a click on the plot landed at: a press let go over the
99
+ * plot without travelling. The end of a drag is never one, nor is a press let
100
+ * go over the axis labels, which inverts to a value the chart is not drawing.
101
+ * @default undefined
102
+ */
103
+ onPlotClick?: (wavenumber: number) => void;
97
104
  /**
98
105
  * What a drag across the plot asks for: the wavenumber axis alone, or exactly
99
106
  * the rectangle it sweeps out. A viewer offering a square-zoom tool switches
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The panels a host adds to the viewer's own, and the one list both make.
3
+ *
4
+ * A host panel is a panel like any other once it is in the list: the activity
5
+ * bar and the stack read the same list, so it toggles, folds and closes
6
+ * exactly as the spectra or the bands do. The host brings the body and nothing
7
+ * else — the header, its close button and the place in the column are the
8
+ * viewer's, which is what keeps every header reading the same.
9
+ */
10
+
11
+ import type { IconName } from '@blueprintjs/core';
12
+ import type { ReactNode } from 'react';
13
+
14
+ import { irSidePanels } from '../core/irSidePanels.ts';
15
+
16
+ /** A panel the host adds to the editor's own stack. */
17
+ export interface IrHostPanel {
18
+ /**
19
+ * Its identity in the activity bar and in `defaultPanelIds`; never a
20
+ * built-in id. One that is, or that an earlier host panel already took, is
21
+ * left out: one icon cannot open two panels.
22
+ */
23
+ id: string;
24
+ /** What its header and its activity-bar item say. */
25
+ title: string;
26
+ /** Its glyph in the activity bar (a Blueprint IconName). */
27
+ icon: IconName;
28
+ /**
29
+ * Its body. It brings its own toolbar as first row: the header carries the
30
+ * close button only.
31
+ *
32
+ * It is laid out in a column filling the panel's share of the height, so a
33
+ * toolbar row in `panelToolbarStyle` followed by a body in `panelBodyStyle`,
34
+ * both from `react-cheminfo/core`, keeps the toolbar in place while a tall
35
+ * body scrolls under it. It is rendered inside the viewer, so a component in
36
+ * it reaches the viewer it stands in through `useIrEditorState` and
37
+ * `useIrActions` — to overlay a predicted spectrum, say.
38
+ */
39
+ content: ReactNode;
40
+ }
41
+
42
+ /** One panel of the side: one of the viewer's own, or one a host added. */
43
+ export type IrStackPanel = (typeof irSidePanels)[number] | IrHostPanel;
44
+
45
+ /**
46
+ * Every panel the side offers, in the order the activity bar stacks them: the
47
+ * host's first, in the order given, then the viewer's own.
48
+ * @param hostPanels - What the host adds.
49
+ * @returns The panels; the viewer's own list itself when the host adds none.
50
+ */
51
+ export function irStackPanels(
52
+ hostPanels: readonly IrHostPanel[],
53
+ ): readonly IrStackPanel[] {
54
+ if (hostPanels.length === 0) return irSidePanels;
55
+ const taken = new Set<string>(BUILT_IN_IDS);
56
+ const panels: IrStackPanel[] = [];
57
+ for (const panel of hostPanels) {
58
+ if (taken.has(panel.id)) continue;
59
+ taken.add(panel.id);
60
+ panels.push(panel);
61
+ }
62
+ panels.push(...irSidePanels);
63
+ return panels;
64
+ }
65
+
66
+ /**
67
+ * Whether a panel of the side is one a host added, whose body it brings.
68
+ * @param panel - The panel.
69
+ * @returns True for a host's panel.
70
+ */
71
+ export function isIrHostPanel(panel: IrStackPanel): panel is IrHostPanel {
72
+ return 'content' in panel;
73
+ }
74
+
75
+ /** The ids no host panel may take. */
76
+ const BUILT_IN_IDS: readonly string[] = irSidePanels.map((panel) => panel.id);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The side of the viewer: the panels it offers, whether any of them is open,
3
+ * and what dragging the splitter shut and back open does to them.
4
+ *
5
+ * A hook of its own beside the shell, because the shell lays the side out and
6
+ * this decides what is on it.
7
+ */
8
+
9
+ import { useMemo } from 'react';
10
+
11
+ import { DEFAULT_SIDE_PANEL } from '../core/irState.ts';
12
+
13
+ import type { IrHostPanel, IrStackPanel } from './irHostPanels.ts';
14
+ import { irStackPanels } from './irHostPanels.ts';
15
+ import { useIrActions, useIrEditorState } from './irStateContext.ts';
16
+
17
+ /** The side, as the shell lays it out. */
18
+ export interface IrSide {
19
+ /** Every panel on offer, in the order their icons are stacked. */
20
+ panels: readonly IrStackPanel[];
21
+ /** Whether a panel on offer is open, which is whether the side is. */
22
+ isOpen: boolean;
23
+ /**
24
+ * Open or close the side, as the splitter is dragged shut and back open.
25
+ * @param open - Whether the side is now open.
26
+ */
27
+ setOpen: (open: boolean) => void;
28
+ }
29
+
30
+ /**
31
+ * The side of the viewer this is called inside.
32
+ * @param hostPanels - The panels the host adds to the viewer's own.
33
+ * @returns The panels on offer, whether the side is open, and how to open it.
34
+ */
35
+ export function useIrSide(hostPanels: readonly IrHostPanel[]): IrSide {
36
+ const { state } = useIrEditorState();
37
+ const actions = useIrActions();
38
+ const { openPanelIds } = state.view;
39
+
40
+ const panels = useMemo(() => irStackPanels(hostPanels), [hostPanels]);
41
+ // Read off the panels on offer rather than off the ids: an id no panel
42
+ // answers to opens nothing, and a side open on it alone is an empty column.
43
+ const isOpen = panels.some((panel) => openPanelIds.includes(panel.id));
44
+
45
+ /**
46
+ * Open or close the side.
47
+ * @param open - Whether the side is now open.
48
+ */
49
+ function setOpen(open: boolean): void {
50
+ if (!open) {
51
+ for (const panelId of openPanelIds) actions.togglePanel(panelId);
52
+ return;
53
+ }
54
+ // Dragging the splitter back open must land on something, or it opens on an
55
+ // empty column and reads as broken.
56
+ if (!isOpen) actions.togglePanel(DEFAULT_SIDE_PANEL);
57
+ }
58
+
59
+ return { panels, isOpen, setOpen };
60
+ }
@@ -16,6 +16,7 @@ import { useMemo } from 'react';
16
16
 
17
17
  import type { ZoomDragMode } from '../../chart/core/chartDomain.ts';
18
18
  import type { PlotRect } from '../../chart/core/chartGeometry.ts';
19
+ import type { ChartPoint } from '../../chart/core/svgPoint.ts';
19
20
  import type { ChartZoom } from '../../chart/ui/useChartZoom.ts';
20
21
  import { useChartZoom } from '../../chart/ui/useChartZoom.ts';
21
22
  import { fittedTo, fullDomain } from '../core/irDomain.ts';
@@ -52,6 +53,13 @@ export interface IrZoomOptions {
52
53
  * @default 'xAxis'
53
54
  */
54
55
  drag?: ZoomDragMode;
56
+ /**
57
+ * Told where a click on the plot landed — a press let go over it without
58
+ * travelling, never the end of a drag — as a wavenumber and a value in the
59
+ * mode drawn.
60
+ * @default undefined
61
+ */
62
+ onClick?: (point: ChartPoint) => void;
55
63
  }
56
64
 
57
65
  /**
@@ -72,6 +80,7 @@ export function useIrZoom(options: IrZoomOptions): ChartZoom {
72
80
  plot,
73
81
  domain = null,
74
82
  drag = 'xAxis',
83
+ onClick,
75
84
  } = options;
76
85
 
77
86
  const fitted = useMemo(() => fullDomain(spectra, mode), [spectra, mode]);
@@ -91,5 +100,6 @@ export function useIrZoom(options: IrZoomOptions): ChartZoom {
91
100
  domain,
92
101
  gestures,
93
102
  reverseX: IR_TECHNIQUE_RULES[technique].reverseX,
103
+ onClick,
94
104
  });
95
105
  }