react-cheminfo 0.50.0 → 0.52.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 (314) hide show
  1. package/README.md +71 -16
  2. package/bin/check-seo.mjs +0 -0
  3. package/lib/clipboard/core/index.d.ts +0 -1
  4. package/lib/clipboard/core/index.d.ts.map +1 -1
  5. package/lib/clipboard/core/index.js +0 -1
  6. package/lib/clipboard/core/index.js.map +1 -1
  7. package/lib/clipboard/ui/CopyButton.d.ts +6 -0
  8. package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
  9. package/lib/clipboard/ui/CopyButton.js +5 -2
  10. package/lib/clipboard/ui/CopyButton.js.map +1 -1
  11. package/lib/color/core/index.d.ts +3 -0
  12. package/lib/color/core/index.d.ts.map +1 -1
  13. package/lib/color/core/index.js +2 -0
  14. package/lib/color/core/index.js.map +1 -1
  15. package/lib/color/core/interpolate.d.ts +4 -2
  16. package/lib/color/core/interpolate.d.ts.map +1 -1
  17. package/lib/color/core/interpolate.js +34 -12
  18. package/lib/color/core/interpolate.js.map +1 -1
  19. package/lib/color/core/stops.d.ts +43 -0
  20. package/lib/color/core/stops.d.ts.map +1 -0
  21. package/lib/color/core/stops.js +66 -0
  22. package/lib/color/core/stops.js.map +1 -0
  23. package/lib/color/core/tone.d.ts +25 -0
  24. package/lib/color/core/tone.d.ts.map +1 -0
  25. package/lib/color/core/tone.js +67 -0
  26. package/lib/color/core/tone.js.map +1 -0
  27. package/lib/color/core/uniform.d.ts +36 -0
  28. package/lib/color/core/uniform.d.ts.map +1 -0
  29. package/lib/color/core/uniform.js +61 -0
  30. package/lib/color/core/uniform.js.map +1 -0
  31. package/lib/color/ui/ColorScaleEditor.d.ts +5 -6
  32. package/lib/color/ui/ColorScaleEditor.d.ts.map +1 -1
  33. package/lib/color/ui/ColorScaleEditor.js +74 -84
  34. package/lib/color/ui/ColorScaleEditor.js.map +1 -1
  35. package/lib/color/ui/ColorScaleSelect.d.ts +16 -0
  36. package/lib/color/ui/ColorScaleSelect.d.ts.map +1 -1
  37. package/lib/color/ui/ColorScaleSelect.js +69 -17
  38. package/lib/color/ui/ColorScaleSelect.js.map +1 -1
  39. package/lib/color/ui/ColorScaleTone.d.ts +17 -0
  40. package/lib/color/ui/ColorScaleTone.d.ts.map +1 -0
  41. package/lib/color/ui/ColorScaleTone.js +55 -0
  42. package/lib/color/ui/ColorScaleTone.js.map +1 -0
  43. package/lib/color/ui/ColorScaleTrack.d.ts +29 -0
  44. package/lib/color/ui/ColorScaleTrack.d.ts.map +1 -0
  45. package/lib/color/ui/ColorScaleTrack.js +143 -0
  46. package/lib/color/ui/ColorScaleTrack.js.map +1 -0
  47. package/lib/download/core/copyFigure.d.ts +14 -0
  48. package/lib/download/core/copyFigure.d.ts.map +1 -0
  49. package/lib/download/core/copyFigure.js +18 -0
  50. package/lib/download/core/copyFigure.js.map +1 -0
  51. package/lib/download/core/figureHtml.js +1 -0
  52. package/lib/download/core/figureHtml.js.map +1 -1
  53. package/lib/download/core/figureHtmlMarkup.d.ts +10 -0
  54. package/lib/download/core/figureHtmlMarkup.d.ts.map +1 -1
  55. package/lib/download/core/figureHtmlMarkup.js +7 -1
  56. package/lib/download/core/figureHtmlMarkup.js.map +1 -1
  57. package/lib/download/core/index.d.ts +2 -0
  58. package/lib/download/core/index.d.ts.map +1 -1
  59. package/lib/download/core/index.js +1 -0
  60. package/lib/download/core/index.js.map +1 -1
  61. package/lib/download/ui/FigureDownloadPanel.d.ts +2 -3
  62. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  63. package/lib/download/ui/FigureDownloadPanel.js +1 -1
  64. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  65. package/lib/download/ui/figureDownloadHint.d.ts +1 -1
  66. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
  67. package/lib/download/ui/figureDownloadHint.js.map +1 -1
  68. package/lib/download/ui/index.d.ts +0 -1
  69. package/lib/download/ui/index.d.ts.map +1 -1
  70. package/lib/download/ui/useFigureActions.d.ts +2 -11
  71. package/lib/download/ui/useFigureActions.d.ts.map +1 -1
  72. package/lib/download/ui/useFigureActions.js +4 -23
  73. package/lib/download/ui/useFigureActions.js.map +1 -1
  74. package/lib/download/ui/useFigureTask.d.ts +21 -0
  75. package/lib/download/ui/useFigureTask.d.ts.map +1 -0
  76. package/lib/download/ui/useFigureTask.js +27 -0
  77. package/lib/download/ui/useFigureTask.js.map +1 -0
  78. package/lib/host/core/connectHost.d.ts +75 -0
  79. package/lib/host/core/connectHost.d.ts.map +1 -0
  80. package/lib/host/core/connectHost.js +171 -0
  81. package/lib/host/core/connectHost.js.map +1 -0
  82. package/lib/host/core/hostStructure.d.ts +52 -0
  83. package/lib/host/core/hostStructure.d.ts.map +1 -0
  84. package/lib/host/core/hostStructure.js +106 -0
  85. package/lib/host/core/hostStructure.js.map +1 -0
  86. package/lib/host/core/index.d.ts +9 -0
  87. package/lib/host/core/index.d.ts.map +1 -0
  88. package/lib/host/core/index.js +5 -0
  89. package/lib/host/core/index.js.map +1 -0
  90. package/lib/host/core/kinds.d.ts +73 -0
  91. package/lib/host/core/kinds.d.ts.map +1 -0
  92. package/lib/host/core/kinds.js +88 -0
  93. package/lib/host/core/kinds.js.map +1 -0
  94. package/lib/host/core/protocol.d.ts +134 -0
  95. package/lib/host/core/protocol.d.ts.map +1 -0
  96. package/lib/host/core/protocol.js +92 -0
  97. package/lib/host/core/protocol.js.map +1 -0
  98. package/lib/host/testing/sciPeaks.d.ts +21 -0
  99. package/lib/host/testing/sciPeaks.d.ts.map +1 -0
  100. package/lib/host/testing/sciPeaks.js +62 -0
  101. package/lib/host/testing/sciPeaks.js.map +1 -0
  102. package/lib/host/testing/standInHost.d.ts +47 -0
  103. package/lib/host/testing/standInHost.d.ts.map +1 -0
  104. package/lib/host/testing/standInHost.js +85 -0
  105. package/lib/host/testing/standInHost.js.map +1 -0
  106. package/lib/host/ui/index.d.ts +3 -0
  107. package/lib/host/ui/index.d.ts.map +1 -0
  108. package/lib/host/ui/index.js +2 -0
  109. package/lib/host/ui/index.js.map +1 -0
  110. package/lib/host/ui/useHostConnection.d.ts +36 -0
  111. package/lib/host/ui/useHostConnection.d.ts.map +1 -0
  112. package/lib/host/ui/useHostConnection.js +62 -0
  113. package/lib/host/ui/useHostConnection.js.map +1 -0
  114. package/lib/host-testing.d.ts +8 -0
  115. package/lib/host-testing.d.ts.map +1 -0
  116. package/lib/host-testing.js +7 -0
  117. package/lib/host-testing.js.map +1 -0
  118. package/lib/host.d.ts +11 -0
  119. package/lib/host.d.ts.map +1 -0
  120. package/lib/host.js +11 -0
  121. package/lib/host.js.map +1 -0
  122. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  123. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  124. package/lib/locales/de.json +28 -2
  125. package/lib/locales/en.json +28 -2
  126. package/lib/locales/es.json +28 -2
  127. package/lib/locales/fr.json +28 -2
  128. package/lib/locales/it.json +28 -2
  129. package/lib/molecule3d/core/index.d.ts +1 -1
  130. package/lib/molecule3d/core/index.d.ts.map +1 -1
  131. package/lib/molecule3d/core/index.js +1 -1
  132. package/lib/molecule3d/core/index.js.map +1 -1
  133. package/lib/molecule3d/core/settings.d.ts +15 -0
  134. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  135. package/lib/molecule3d/core/settings.js +25 -0
  136. package/lib/molecule3d/core/settings.js.map +1 -1
  137. package/lib/molecule3d/ui/Molecule3DExport.d.ts +3 -0
  138. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  139. package/lib/molecule3d/ui/Molecule3DExport.js +10 -16
  140. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  141. package/lib/molecule3d/ui/Molecule3DOptions.d.ts +3 -1
  142. package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
  143. package/lib/molecule3d/ui/Molecule3DOptions.js +22 -6
  144. package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
  145. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  146. package/lib/molecule3d/ui/MoleculeCanvas3D.js +4 -3
  147. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  148. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +9 -0
  149. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  150. package/lib/molecule3d/ui/exportMoleculeImage.js +15 -0
  151. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  152. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +2 -1
  153. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  154. package/lib/molecule3d/ui/useImageExport.d.ts +7 -1
  155. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  156. package/lib/molecule3d/ui/useImageExport.js +12 -3
  157. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  158. package/lib/overlay/ui/FigureBar.d.ts +41 -0
  159. package/lib/overlay/ui/FigureBar.d.ts.map +1 -0
  160. package/lib/overlay/ui/FigureBar.js +43 -0
  161. package/lib/overlay/ui/FigureBar.js.map +1 -0
  162. package/lib/overlay/ui/OverlayBar.d.ts +13 -4
  163. package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
  164. package/lib/overlay/ui/OverlayBar.js +2 -2
  165. package/lib/overlay/ui/OverlayBar.js.map +1 -1
  166. package/lib/overlay/ui/OverlayBarStrip.d.ts +2 -1
  167. package/lib/overlay/ui/OverlayBarStrip.d.ts.map +1 -1
  168. package/lib/overlay/ui/OverlayBarStrip.js +9 -5
  169. package/lib/overlay/ui/OverlayBarStrip.js.map +1 -1
  170. package/lib/overlay/ui/OverlayIconButton.d.ts +8 -2
  171. package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
  172. package/lib/overlay/ui/OverlayIconButton.js +2 -1
  173. package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
  174. package/lib/overlay/ui/index.d.ts +4 -0
  175. package/lib/overlay/ui/index.d.ts.map +1 -1
  176. package/lib/overlay/ui/index.js +2 -0
  177. package/lib/overlay/ui/index.js.map +1 -1
  178. package/lib/overlay/ui/overlayBarShape.d.ts +18 -0
  179. package/lib/overlay/ui/overlayBarShape.d.ts.map +1 -1
  180. package/lib/overlay/ui/overlayStyles.d.ts +30 -0
  181. package/lib/overlay/ui/overlayStyles.d.ts.map +1 -1
  182. package/lib/overlay/ui/overlayStyles.js +52 -0
  183. package/lib/overlay/ui/overlayStyles.js.map +1 -1
  184. package/lib/overlay/ui/useFigureBarAwake.d.ts +12 -0
  185. package/lib/overlay/ui/useFigureBarAwake.d.ts.map +1 -0
  186. package/lib/overlay/ui/useFigureBarAwake.js +51 -0
  187. package/lib/overlay/ui/useFigureBarAwake.js.map +1 -0
  188. package/lib/periodic/core/elements.d.ts +6 -3
  189. package/lib/periodic/core/elements.d.ts.map +1 -1
  190. package/lib/periodic/core/elements.js +9 -3
  191. package/lib/periodic/core/elements.js.map +1 -1
  192. package/lib/periodic/core/index.d.ts +2 -2
  193. package/lib/periodic/core/index.d.ts.map +1 -1
  194. package/lib/periodic/core/index.js +1 -1
  195. package/lib/periodic/core/index.js.map +1 -1
  196. package/lib/periodic/core/layout.d.ts +33 -3
  197. package/lib/periodic/core/layout.d.ts.map +1 -1
  198. package/lib/periodic/core/layout.js +14 -0
  199. package/lib/periodic/core/layout.js.map +1 -1
  200. package/lib/periodic/ui/BlockStrip.d.ts +20 -0
  201. package/lib/periodic/ui/BlockStrip.d.ts.map +1 -0
  202. package/lib/periodic/ui/BlockStrip.js +48 -0
  203. package/lib/periodic/ui/BlockStrip.js.map +1 -0
  204. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  205. package/lib/periodic/ui/ElementCell.js +9 -8
  206. package/lib/periodic/ui/ElementCell.js.map +1 -1
  207. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  208. package/lib/periodic/ui/PeriodicTable.js +3 -1
  209. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  210. package/lib/periodic/ui/PeriodicTableHelp.d.ts +33 -0
  211. package/lib/periodic/ui/PeriodicTableHelp.d.ts.map +1 -0
  212. package/lib/periodic/ui/PeriodicTableHelp.js +107 -0
  213. package/lib/periodic/ui/PeriodicTableHelp.js.map +1 -0
  214. package/lib/periodic/ui/cellStyles.d.ts +3 -2
  215. package/lib/periodic/ui/cellStyles.d.ts.map +1 -1
  216. package/lib/periodic/ui/cellStyles.js +3 -2
  217. package/lib/periodic/ui/cellStyles.js.map +1 -1
  218. package/lib/periodic/ui/index.d.ts +2 -0
  219. package/lib/periodic/ui/index.d.ts.map +1 -1
  220. package/lib/periodic/ui/index.js +1 -0
  221. package/lib/periodic/ui/index.js.map +1 -1
  222. package/lib/periodic/ui/periodicTableProps.d.ts +5 -4
  223. package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -1
  224. package/lib/periodic/ui/tableLayout.d.ts +3 -1
  225. package/lib/periodic/ui/tableLayout.d.ts.map +1 -1
  226. package/lib/periodic/ui/tableLayout.js +8 -3
  227. package/lib/periodic/ui/tableLayout.js.map +1 -1
  228. package/lib/share/ui/ShareDialogContent.d.ts +3 -2
  229. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  230. package/lib/share/ui/ShareDialogContent.js +5 -6
  231. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  232. package/lib/share/ui/ShareLinkBar.d.ts +8 -2
  233. package/lib/share/ui/ShareLinkBar.d.ts.map +1 -1
  234. package/lib/share/ui/ShareLinkBar.js +6 -5
  235. package/lib/share/ui/ShareLinkBar.js.map +1 -1
  236. package/lib/share/ui/SharePreview.d.ts.map +1 -1
  237. package/lib/share/ui/SharePreview.js +2 -2
  238. package/lib/share/ui/SharePreview.js.map +1 -1
  239. package/lib/structure/ui/StructurePicturePane.js +3 -3
  240. package/lib/structure/ui/StructurePicturePane.js.map +1 -1
  241. package/package.json +4 -1
  242. package/src/clipboard/core/index.ts +0 -1
  243. package/src/clipboard/ui/CopyButton.tsx +12 -1
  244. package/src/color/core/index.ts +9 -0
  245. package/src/color/core/interpolate.ts +51 -22
  246. package/src/color/core/stops.ts +98 -0
  247. package/src/color/core/tone.ts +79 -0
  248. package/src/color/core/uniform.ts +67 -0
  249. package/src/color/ui/ColorScaleEditor.tsx +115 -141
  250. package/src/color/ui/ColorScaleSelect.tsx +160 -36
  251. package/src/color/ui/ColorScaleTone.tsx +103 -0
  252. package/src/color/ui/ColorScaleTrack.tsx +239 -0
  253. package/src/download/core/copyFigure.ts +20 -0
  254. package/src/download/core/figureHtml.ts +1 -0
  255. package/src/download/core/figureHtmlMarkup.ts +14 -1
  256. package/src/download/core/index.ts +2 -0
  257. package/src/download/ui/FigureDownloadPanel.tsx +8 -11
  258. package/src/download/ui/figureDownloadHint.ts +1 -2
  259. package/src/download/ui/index.ts +0 -1
  260. package/src/download/ui/useFigureActions.ts +6 -42
  261. package/src/download/ui/useFigureTask.ts +46 -0
  262. package/src/host/core/connectHost.ts +240 -0
  263. package/src/host/core/hostStructure.ts +129 -0
  264. package/src/host/core/index.ts +46 -0
  265. package/src/host/core/kinds.ts +128 -0
  266. package/src/host/core/protocol.ts +197 -0
  267. package/src/host/testing/sciPeaks.ts +66 -0
  268. package/src/host/testing/standInHost.ts +116 -0
  269. package/src/host/ui/index.ts +2 -0
  270. package/src/host/ui/useHostConnection.ts +85 -0
  271. package/src/host-testing.ts +18 -0
  272. package/src/host.ts +11 -0
  273. package/src/locales/de.json +28 -2
  274. package/src/locales/en.json +28 -2
  275. package/src/locales/es.json +28 -2
  276. package/src/locales/fr.json +28 -2
  277. package/src/locales/it.json +28 -2
  278. package/src/molecule3d/core/index.ts +2 -0
  279. package/src/molecule3d/core/settings.ts +35 -0
  280. package/src/molecule3d/ui/Molecule3DExport.tsx +18 -17
  281. package/src/molecule3d/ui/Molecule3DOptions.tsx +51 -3
  282. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +11 -3
  283. package/src/molecule3d/ui/exportMoleculeImage.ts +20 -0
  284. package/src/molecule3d/ui/moleculeViewer3DProps.ts +2 -1
  285. package/src/molecule3d/ui/useImageExport.ts +25 -3
  286. package/src/overlay/ui/FigureBar.tsx +96 -0
  287. package/src/overlay/ui/OverlayBar.tsx +15 -5
  288. package/src/overlay/ui/OverlayBarStrip.tsx +34 -22
  289. package/src/overlay/ui/OverlayIconButton.tsx +10 -2
  290. package/src/overlay/ui/index.ts +4 -0
  291. package/src/overlay/ui/overlayBarShape.ts +19 -0
  292. package/src/overlay/ui/overlayStyles.ts +61 -0
  293. package/src/overlay/ui/useFigureBarAwake.ts +56 -0
  294. package/src/periodic/core/elements.ts +8 -3
  295. package/src/periodic/core/index.ts +8 -1
  296. package/src/periodic/core/layout.ts +45 -3
  297. package/src/periodic/ui/BlockStrip.tsx +81 -0
  298. package/src/periodic/ui/ElementCell.tsx +9 -8
  299. package/src/periodic/ui/PeriodicTable.tsx +4 -1
  300. package/src/periodic/ui/PeriodicTableHelp.tsx +205 -0
  301. package/src/periodic/ui/cellStyles.ts +3 -2
  302. package/src/periodic/ui/index.ts +5 -0
  303. package/src/periodic/ui/periodicTableProps.ts +5 -4
  304. package/src/periodic/ui/tableLayout.ts +9 -3
  305. package/src/share/ui/ShareDialogContent.tsx +9 -13
  306. package/src/share/ui/ShareLinkBar.tsx +16 -4
  307. package/src/share/ui/SharePreview.tsx +6 -2
  308. package/src/structure/ui/StructurePicturePane.tsx +3 -3
  309. package/styles/chrome.css +43 -32
  310. package/lib/clipboard/core/copyPng.d.ts +0 -13
  311. package/lib/clipboard/core/copyPng.d.ts.map +0 -1
  312. package/lib/clipboard/core/copyPng.js +0 -19
  313. package/lib/clipboard/core/copyPng.js.map +0 -1
  314. package/src/clipboard/core/copyPng.ts +0 -20
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The s, d, p and f labels, set in the cells no element occupies.
3
+ */
4
+
5
+ import type { CSSProperties, MouseEvent, ReactElement } from 'react';
6
+
7
+ import { useChromeT } from '../../i18n/ui/useT.ts';
8
+ import { TOKEN } from '../../tokens/core/familyTokens.ts';
9
+ import type { ElementRange } from '../core/layout.ts';
10
+ import { BLOCK_LABELS } from '../core/layout.ts';
11
+
12
+ import { headerButtonStyle, headerStyle } from './headerStyles.ts';
13
+ import { ofWidth } from './unit.ts';
14
+
15
+ /** What {@link BlockStrip} needs. */
16
+ interface BlockStripProps {
17
+ /** Called with the block whose label was clicked. */
18
+ onSelectRange: (range: ElementRange) => void;
19
+ }
20
+
21
+ /**
22
+ * A thin rule under each block with its letter on it, so the four blocks are a
23
+ * click each, like a period or a group, without a toolbar repeating the table.
24
+ * The f label stands beside the two rows it names, its rule running down them.
25
+ * @param props - See {@link BlockStripProps}.
26
+ * @returns One button per block, placed on the grid with the header offset.
27
+ */
28
+ export function BlockStrip(props: BlockStripProps): ReactElement {
29
+ const { onSelectRange } = props;
30
+ const t = useChromeT();
31
+ return (
32
+ <>
33
+ {BLOCK_LABELS.map(({ block, cell, columnSpan, rowSpan }) => {
34
+ const title = t('periodic.block', { block });
35
+ const vertical = rowSpan > 1;
36
+ return (
37
+ <button
38
+ key={block}
39
+ type="button"
40
+ aria-label={title}
41
+ title={title}
42
+ data-block={block}
43
+ onClick={(event: MouseEvent) => {
44
+ onSelectRange({
45
+ kind: 'block',
46
+ value: block,
47
+ additive: event.metaKey || event.ctrlKey,
48
+ });
49
+ }}
50
+ style={{
51
+ ...headerStyle,
52
+ ...headerButtonStyle,
53
+ gridColumn: `${String(cell.column + 1)} / span ${String(columnSpan)}`,
54
+ gridRow: `${String(cell.row + 1)} / span ${String(rowSpan)}`,
55
+ justifyContent: vertical ? 'flex-end' : 'center',
56
+ gap: '0.3em',
57
+ // Quieter than the period and group numbers: a block is a
58
+ // shortcut found on hover, not a label the table is read by.
59
+ color: TOKEN.textFaint,
60
+ fontSize: `max(0.4rem, ${ofWidth(1.05)})`,
61
+ }}
62
+ >
63
+ {vertical ? null : <span style={horizontalRuleStyle} />}
64
+ <span>{block}</span>
65
+ <span style={vertical ? verticalRuleStyle : horizontalRuleStyle} />
66
+ </button>
67
+ );
68
+ })}
69
+ </>
70
+ );
71
+ }
72
+
73
+ const horizontalRuleStyle = {
74
+ borderTop: `1px solid ${TOKEN.border}`,
75
+ flex: '1 1 0',
76
+ } as const satisfies CSSProperties;
77
+
78
+ const verticalRuleStyle = {
79
+ alignSelf: 'stretch',
80
+ borderLeft: `1px solid ${TOKEN.border}`,
81
+ } as const satisfies CSSProperties;
@@ -170,14 +170,15 @@ export function ElementCell(props: ElementCellProps): ReactElement {
170
170
  background: swatch.background,
171
171
  color: swatch.foreground,
172
172
  opacity: isIncluded ? 1 : 0.28,
173
- // An outline rather than a fill: a table coloured by a property must
174
- // keep saying what the value is while a cell is selected.
175
- outlineStyle: isSelected ? 'solid' : 'none',
176
- outlineColor: swatch.foreground,
177
- // Thick enough to be seen from the back of a room, and drawn inside
178
- // the cell so a neighbour never covers it.
179
- outlineWidth: `max(2px, ${ofWidth(0.3)})`,
180
- outlineOffset: `max(-3px, ${ofWidth(-0.4)})`,
173
+ // A ring rather than a fill: a table coloured by a property must keep
174
+ // saying what the value is while a cell is selected. Drawn as an inset
175
+ // shadow, never an outline: a click focuses the cell, and a site's
176
+ // focus-ring reset (`outline: none !important`) would erase it.
177
+ // Thick enough to be seen from the back of a room, and inside the cell
178
+ // so a neighbour never covers it.
179
+ boxShadow: isSelected
180
+ ? `inset 0 0 0 max(2px, ${ofWidth(0.3)}) ${swatch.foreground}`
181
+ : undefined,
181
182
  // The cell the pointer is on, for a class following a demonstration.
182
183
  filter: isHovered ? 'brightness(1.08)' : undefined,
183
184
  }}
@@ -21,6 +21,7 @@ import type { PeriodicElement } from '../core/elements.ts';
21
21
  import type { ElementPick } from '../core/layout.ts';
22
22
  import { elementByArrowKey, placedElements } from '../core/layout.ts';
23
23
 
24
+ import { BlockStrip } from './BlockStrip.tsx';
24
25
  import { CategoryLegend } from './CategoryLegend.tsx';
25
26
  import { ElementCell } from './ElementCell.tsx';
26
27
  import {
@@ -69,6 +70,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
69
70
  const gridRef = useRef<HTMLDivElement>(null);
70
71
  const cameFromKeyRef = useRef(false);
71
72
  const offset = headers ? 1 : 0;
73
+ const blocks = headers && onSelectRange !== undefined;
72
74
 
73
75
  // The drawing is read off this one number, so it is written straight onto
74
76
  // the element rather than held as state: a window being dragged would
@@ -132,7 +134,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
132
134
  role="grid"
133
135
  aria-label={t('periodic.table')}
134
136
  data-testid="periodic-table"
135
- style={gridStyle(headers)}
137
+ style={gridStyle(headers, blocks)}
136
138
  onKeyDown={handleKeyDown}
137
139
  >
138
140
  {headers ? (
@@ -141,6 +143,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
141
143
  onSelectAll={onSelectAll}
142
144
  />
143
145
  ) : null}
146
+ {blocks ? <BlockStrip onSelectRange={onSelectRange} /> : null}
144
147
  {inset === undefined ? null : (
145
148
  <div style={insetStyle(offset)}>{inset}</div>
146
149
  )}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * What the clicks on a periodic table do, behind one question mark.
3
+ *
4
+ * The table picks by row, by column, by block and by element, and the corner
5
+ * that takes everything back only shows itself when pointed at. None of that is
6
+ * written on the table, so the gestures are listed here, one to a line, the way
7
+ * the 3D viewer lists its own.
8
+ */
9
+
10
+ import { Classes } from '@blueprintjs/core';
11
+ import type { CSSProperties, ReactElement } from 'react';
12
+
13
+ import { HelpTooltip } from '../../help/ui/HelpTooltip.tsx';
14
+ import type { ChromeKey } from '../../i18n/core/chromeCatalog.ts';
15
+ import { useChromeT } from '../../i18n/ui/useT.ts';
16
+ import { OverlayIconButton } from '../../overlay/ui/OverlayIconButton.tsx';
17
+ import { TOKEN } from '../../tokens/core/familyTokens.ts';
18
+
19
+ /** What {@link PeriodicTableHelp} needs. */
20
+ export interface PeriodicTableHelpProps {
21
+ /**
22
+ * Which side the card opens on.
23
+ * @default 'bottom'
24
+ */
25
+ placement?: 'top' | 'right' | 'bottom' | 'left';
26
+ }
27
+
28
+ /**
29
+ * The question mark that explains how to pick elements on the table.
30
+ *
31
+ * It is the same button as `FigureDownload`'s, so the two sit side by side in a
32
+ * row of controls at one size and one ink.
33
+ * @param props - See {@link PeriodicTableHelpProps}.
34
+ * @returns The glyph, with its card.
35
+ */
36
+ export function PeriodicTableHelp(props: PeriodicTableHelpProps): ReactElement {
37
+ const { placement = 'bottom' } = props;
38
+ const t = useChromeT();
39
+ const title = t('periodic.help.title');
40
+
41
+ return (
42
+ <HelpTooltip
43
+ content={{ title, body: <PeriodicTableGestures /> }}
44
+ placement={placement}
45
+ width={CARD_WIDTH}
46
+ popoverClassName="help-tooltip--wide"
47
+ >
48
+ <OverlayIconButton
49
+ icon="help"
50
+ label={title}
51
+ untitled
52
+ testId="periodic-table-help"
53
+ />
54
+ </HelpTooltip>
55
+ );
56
+ }
57
+
58
+ /** One thing on the table, and what a plain and a modified click on it do. */
59
+ interface Target {
60
+ /** What is clicked; absent for the arrow keys, which are drawn as caps. */
61
+ name?: ChromeKey;
62
+ /** Keys drawn instead of a name. */
63
+ keys?: readonly string[];
64
+ click: ChromeKey;
65
+ /** What Cmd or Ctrl and a click does; absent when it does nothing more. */
66
+ modClick?: ChromeKey;
67
+ }
68
+
69
+ const TARGETS: readonly Target[] = [
70
+ {
71
+ name: 'periodic.help.element',
72
+ click: 'periodic.help.elementClick',
73
+ modClick: 'periodic.help.elementMod',
74
+ },
75
+ {
76
+ name: 'periodic.help.number',
77
+ click: 'periodic.help.numberClick',
78
+ modClick: 'periodic.help.runMod',
79
+ },
80
+ {
81
+ name: 'periodic.help.block',
82
+ click: 'periodic.help.blockClick',
83
+ modClick: 'periodic.help.runMod',
84
+ },
85
+ { name: 'periodic.help.corner', click: 'periodic.help.cornerClick' },
86
+ { keys: ['←', '↑', '→', '↓'], click: 'periodic.help.arrowsClick' },
87
+ ];
88
+
89
+ /**
90
+ * The gestures the table answers to: what is clicked, then what a plain click
91
+ * and a click with Cmd or Ctrl held each do to it.
92
+ * @returns The table of gestures, and the line saying what the dimming means.
93
+ */
94
+ export function PeriodicTableGestures(): ReactElement {
95
+ const t = useChromeT();
96
+ const modifier = isMacPlatform() ? '⌘' : t('periodic.help.ctrl');
97
+
98
+ return (
99
+ <div style={LIST_STYLE}>
100
+ <table style={TABLE_STYLE}>
101
+ <thead>
102
+ <tr>
103
+ <th aria-hidden="true" />
104
+ <th style={HEAD_STYLE}>{t('periodic.help.click')}</th>
105
+ <th style={HEAD_STYLE}>
106
+ <span style={KEYS_STYLE}>
107
+ <kbd className={Classes.KEY}>{modifier}</kbd>
108
+ {t('periodic.help.modClick')}
109
+ </span>
110
+ </th>
111
+ </tr>
112
+ </thead>
113
+ <tbody>
114
+ {TARGETS.map(({ name, keys, click, modClick }) => (
115
+ <tr key={click} style={ROW_STYLE}>
116
+ <th scope="row" style={TARGET_STYLE}>
117
+ {name === undefined ? (
118
+ <span style={KEYS_STYLE}>
119
+ {keys?.map((key) => (
120
+ <kbd key={key} className={Classes.KEY}>
121
+ {key}
122
+ </kbd>
123
+ ))}
124
+ </span>
125
+ ) : (
126
+ t(name)
127
+ )}
128
+ </th>
129
+ <td style={CELL_STYLE}>{t(click)}</td>
130
+ <td style={CELL_STYLE}>
131
+ {modClick === undefined ? (
132
+ <span className={Classes.TEXT_DISABLED}>—</span>
133
+ ) : (
134
+ t(modClick)
135
+ )}
136
+ </td>
137
+ </tr>
138
+ ))}
139
+ </tbody>
140
+ </table>
141
+ <p className={Classes.TEXT_MUTED} style={NOTE_STYLE}>
142
+ {t('periodic.help.note')}
143
+ </p>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Whether the page runs on a Mac.
150
+ * @returns True there, where the table reads ⌘ rather than Ctrl.
151
+ */
152
+ function isMacPlatform(): boolean {
153
+ return typeof navigator !== 'undefined' && navigator.platform.includes('Mac');
154
+ }
155
+
156
+ /** The wide card's measure, less its padding. */
157
+ const CARD_WIDTH = 436;
158
+
159
+ const LIST_STYLE = {
160
+ display: 'flex',
161
+ flexDirection: 'column',
162
+ gap: 10,
163
+ marginTop: 4,
164
+ } as const satisfies CSSProperties;
165
+
166
+ const TABLE_STYLE = {
167
+ borderCollapse: 'collapse',
168
+ width: '100%',
169
+ } as const satisfies CSSProperties;
170
+
171
+ const HEAD_STYLE = {
172
+ color: TOKEN.textMuted,
173
+ fontSize: 11.5,
174
+ fontWeight: 600,
175
+ padding: '0 8px 6px',
176
+ textAlign: 'left',
177
+ whiteSpace: 'nowrap',
178
+ } as const satisfies CSSProperties;
179
+
180
+ const ROW_STYLE = {
181
+ borderTop: `1px solid ${TOKEN.border}`,
182
+ } as const satisfies CSSProperties;
183
+
184
+ const TARGET_STYLE = {
185
+ fontWeight: 600,
186
+ padding: '6px 8px 6px 0',
187
+ textAlign: 'left',
188
+ verticalAlign: 'top',
189
+ width: '34%',
190
+ } as const satisfies CSSProperties;
191
+
192
+ const CELL_STYLE = {
193
+ padding: '6px 8px',
194
+ verticalAlign: 'top',
195
+ width: '33%',
196
+ } as const satisfies CSSProperties;
197
+
198
+ const KEYS_STYLE = {
199
+ alignItems: 'center',
200
+ display: 'inline-flex',
201
+ gap: 3,
202
+ whiteSpace: 'nowrap',
203
+ } as const satisfies CSSProperties;
204
+
205
+ const NOTE_STYLE = { margin: 0 } as const satisfies CSSProperties;
@@ -84,10 +84,11 @@ export const detailStyle = {
84
84
  /**
85
85
  * The electrons of each shell, innermost at the top, down the right-hand edge
86
86
  * of the whole cell: the stack is as tall as the three bands together, which
87
- * is what lets seven of them be read.
87
+ * is what lets seven of them be read. It hangs from the top, so the innermost
88
+ * shell sits at the same height in every cell whatever the period.
88
89
  */
89
90
  export const shellsStyle = {
90
- alignSelf: 'center',
91
+ alignSelf: 'start',
91
92
  gridColumn: 2,
92
93
  gridRow: '1 / span 3',
93
94
  fontVariantNumeric: 'tabular-nums',
@@ -4,3 +4,8 @@ export type { ElementCellProps } from './ElementCell.tsx';
4
4
  export { ElementCell } from './ElementCell.tsx';
5
5
  export type { PeriodicTableProps } from './PeriodicTable.tsx';
6
6
  export { PeriodicTable } from './PeriodicTable.tsx';
7
+ export type { PeriodicTableHelpProps } from './PeriodicTableHelp.tsx';
8
+ export {
9
+ PeriodicTableGestures,
10
+ PeriodicTableHelp,
11
+ } from './PeriodicTableHelp.tsx';
@@ -72,10 +72,11 @@ export interface PeriodicTableProps {
72
72
  */
73
73
  headers?: boolean;
74
74
  /**
75
- * Called when a whole group or period header is clicked. The run says
76
- * whether the click was additive — Cmd or Ctrl held — so a tool can let a
77
- * reader put two periods on one chart. Without it the strips are labels
78
- * rather than buttons.
75
+ * Called when a whole group or period header is clicked, or one of the block
76
+ * labels the gap under period 7 then carries. The run says whether the click
77
+ * was additive — Cmd or Ctrl held — so a tool can let a reader put two
78
+ * periods on one chart. Without it the strips are labels rather than
79
+ * buttons, and the blocks are not named.
79
80
  * @default undefined
80
81
  */
81
82
  onSelectRange?: (range: ElementRange) => void;
@@ -51,6 +51,9 @@ const ROW_HEIGHT = ofWidth(ROW_SHARE);
51
51
  /** The band the two inner-transition series were lifted out across. */
52
52
  const SERIES_GAP = `max(6px, ${ofWidth(1.1)})`;
53
53
 
54
+ /** The height of a header strip, and of the gap once it carries block labels. */
55
+ const STRIP_HEIGHT = `max(12px, ${ofWidth(2)})`;
56
+
54
57
  export const rootStyle = {
55
58
  display: 'flex',
56
59
  flexDirection: 'column',
@@ -76,11 +79,14 @@ const baseGridStyle = {
76
79
  * The grid the cells are placed on.
77
80
  * @param headers - Whether a leading column and row hold the period and group
78
81
  * numbers.
82
+ * @param blocks - Whether the gap under period 7 names the blocks, which needs
83
+ * it as tall as a header strip.
79
84
  * @returns The style of the grid.
80
85
  */
81
- export function gridStyle(headers: boolean): CSSProperties {
86
+ export function gridStyle(headers: boolean, blocks = false): CSSProperties {
82
87
  // The eighth row is the gap the inner-transition series are lifted out into.
83
- const rows = `repeat(7, ${ROW_HEIGHT}) ${SERIES_GAP} repeat(2, ${ROW_HEIGHT})`;
88
+ const gap = blocks ? STRIP_HEIGHT : SERIES_GAP;
89
+ const rows = `repeat(7, ${ROW_HEIGHT}) ${gap} repeat(2, ${ROW_HEIGHT})`;
84
90
  if (!headers) {
85
91
  return {
86
92
  ...baseGridStyle,
@@ -91,6 +97,6 @@ export function gridStyle(headers: boolean): CSSProperties {
91
97
  return {
92
98
  ...baseGridStyle,
93
99
  gridTemplateColumns: `max(14px, ${ofWidth(2.4)}) repeat(${String(COLUMN_COUNT)}, minmax(0, 1fr))`,
94
- gridTemplateRows: `max(12px, ${ofWidth(2)}) ${rows}`,
100
+ gridTemplateRows: `${STRIP_HEIGHT} ${rows}`,
95
101
  };
96
102
  }
@@ -1,9 +1,8 @@
1
- import { Button, DialogFooter } from '@blueprintjs/core';
1
+ import { DialogFooter } from '@blueprintjs/core';
2
2
  import type { CSSProperties, ReactElement } from 'react';
3
3
  import { useRef, useState } from 'react';
4
4
 
5
5
  import { useContainerSize } from '../../hooks/ui/useContainerSize.ts';
6
- import { useChromeT } from '../../i18n/ui/useT.ts';
7
6
  import type {
8
7
  HideablePart,
9
8
  ShareConfig,
@@ -44,8 +43,9 @@ export type ShareDialogContentProps<
44
43
  > = Omit<ShareDialogProps<Codecs>, 'isOpen' | 'usePortal'>;
45
44
 
46
45
  /**
47
- * The share dialog's body: what one does with the link above, never scrolled
48
- * away, then the options on the left and the page they write on the right.
46
+ * The share dialog's body: the options on the left and the page they write on
47
+ * the right, and what one does with the link in the footer, never scrolled
48
+ * away. A copy closes the dialog, since it was what the dialog was opened for.
49
49
  * @param props - What the site's links can say, how the page is named, and the extra section.
50
50
  * @returns The body and the footer of the dialog.
51
51
  */
@@ -66,7 +66,6 @@ export function ShareDialogContent<
66
66
  partDescriptions = 'inline',
67
67
  children,
68
68
  } = props;
69
- const t = useChromeT();
70
69
 
71
70
  const address = globalThis.location;
72
71
  const base = baseUrl ?? address?.href ?? '';
@@ -165,16 +164,15 @@ export function ShareDialogContent<
165
164
  return (
166
165
  <>
167
166
  <div className="share-dialog__body" ref={body}>
168
- <div className="share-dialog__top">
169
- {presets.length === 0 ? null : (
167
+ {presets.length === 0 ? null : (
168
+ <div className="share-dialog__top">
170
169
  <SharePresetTabs
171
170
  presets={presets}
172
171
  selected={presetKey}
173
172
  onSelect={selectPreset}
174
173
  />
175
- )}
176
- <ShareLinkBar url={url} frame={frame} />
177
- </div>
174
+ </div>
175
+ )}
178
176
 
179
177
  <SharePanes
180
178
  options={optionsPane}
@@ -183,9 +181,7 @@ export function ShareDialogContent<
183
181
  />
184
182
  </div>
185
183
  <DialogFooter
186
- actions={
187
- <Button intent="primary" text={t('share.done')} onClick={onClose} />
188
- }
184
+ actions={<ShareLinkBar url={url} frame={frame} onCopied={onClose} />}
189
185
  />
190
186
  </>
191
187
  );
@@ -10,12 +10,18 @@ export interface ShareLinkBarProps {
10
10
  url: string;
11
11
  /** The markup that frames the page in someone else's site. */
12
12
  frame: string;
13
+ /**
14
+ * Called once either copy has landed on the clipboard.
15
+ * @default undefined
16
+ */
17
+ onCopied?: () => void;
13
18
  }
14
19
 
15
20
  /**
16
- * The three things one does with a share link, kept above the options and
21
+ * The three things one does with a share link, in the dialog's footer and
17
22
  * never scrolled away: a dialog whose Copy button is below the fold is a dialog
18
- * one scrolls through every single time.
23
+ * one scrolls through every single time. A copy is what the dialog was opened
24
+ * for, so the dialog is told when one lands and closes on it.
19
25
  *
20
26
  * Neither the address nor the frame markup is printed. A teacher reads neither
21
27
  * before pasting it, and a hundred characters of query string set in monospace
@@ -25,7 +31,7 @@ export interface ShareLinkBarProps {
25
31
  * @returns The bar.
26
32
  */
27
33
  export function ShareLinkBar(props: ShareLinkBarProps): ReactElement {
28
- const { url, frame } = props;
34
+ const { url, frame, onCopied } = props;
29
35
  const t = useChromeT();
30
36
 
31
37
  return (
@@ -36,7 +42,12 @@ export function ShareLinkBar(props: ShareLinkBarProps): ReactElement {
36
42
  body: t('share.copyLinkHelp'),
37
43
  }}
38
44
  >
39
- <CopyButton content={url} label={t('share.copyLink')} title="" />
45
+ <CopyButton
46
+ content={url}
47
+ label={t('share.copyLink')}
48
+ title=""
49
+ onCopied={onCopied}
50
+ />
40
51
  </HelpTooltip>
41
52
  <HelpTooltip
42
53
  content={{
@@ -63,6 +74,7 @@ export function ShareLinkBar(props: ShareLinkBarProps): ReactElement {
63
74
  icon="code"
64
75
  label={t('share.copyIframe')}
65
76
  title=""
77
+ onCopied={onCopied}
66
78
  />
67
79
  </HelpTooltip>
68
80
  </div>
@@ -20,7 +20,7 @@ import {
20
20
  import { SharePreviewDevices } from './SharePreviewDevices.tsx';
21
21
 
22
22
  /** Room the window's own bar takes, which the page is not scaled into. */
23
- const CHROME_HEIGHT = 26;
23
+ const CHROME_HEIGHT = 31;
24
24
 
25
25
  /** How often the framed page is asked again while it has answered nothing. */
26
26
  const ASK_INTERVAL = 300;
@@ -148,7 +148,11 @@ export function SharePreview(props: SharePreviewProps): ReactElement {
148
148
  style={{ width: page.width * scale }}
149
149
  >
150
150
  <div className="share-preview__chrome">
151
- <Icon icon="eye-open" size={11} />
151
+ <span className="share-preview__lights" aria-hidden="true">
152
+ <span />
153
+ <span />
154
+ <span />
155
+ </span>
152
156
  <span className="share-preview__address">
153
157
  {sharePreviewAddress(url)}
154
158
  </span>
@@ -3,7 +3,7 @@ import type { Molecule } from 'openchemlib';
3
3
  import type { CSSProperties, ReactElement } from 'react';
4
4
  import { useMemo, useState } from 'react';
5
5
 
6
- import { writeImageToClipboard } from '../../clipboard/core/copyPng.ts';
6
+ import { copyFigure } from '../../download/core/copyFigure.ts';
7
7
  import { downloadBlob } from '../../download/core/downloadBlob.ts';
8
8
  import { downloadText } from '../../download/core/downloadText.ts';
9
9
  import { figurePng } from '../../download/core/figurePng.ts';
@@ -143,9 +143,9 @@ export function StructurePicturePane(
143
143
  onClick={() =>
144
144
  // Safari only allows a clipboard write the click itself started,
145
145
  // so the image is handed over as a promise rather than awaited
146
- // first — see `writeImageToClipboard`.
146
+ // first — see `copyFigure`.
147
147
  void run(async () =>
148
- (await writeImageToClipboard(png()))
148
+ (await copyFigure(png())) === 'copied'
149
149
  ? t('download.hintCopied', {
150
150
  pixels: formatFigurePixels(picture),
151
151
  })