react-cheminfo 0.49.0 → 0.51.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 (269) hide show
  1. package/README.md +23 -0
  2. package/bin/check-seo.mjs +27 -2
  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/i18n/core/chromeCatalog.d.ts +1 -1
  79. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  80. package/lib/locales/de.json +28 -2
  81. package/lib/locales/en.json +28 -2
  82. package/lib/locales/es.json +28 -2
  83. package/lib/locales/fr.json +28 -2
  84. package/lib/locales/it.json +28 -2
  85. package/lib/molecule3d/core/index.d.ts +1 -1
  86. package/lib/molecule3d/core/index.d.ts.map +1 -1
  87. package/lib/molecule3d/core/index.js +1 -1
  88. package/lib/molecule3d/core/index.js.map +1 -1
  89. package/lib/molecule3d/core/settings.d.ts +15 -0
  90. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  91. package/lib/molecule3d/core/settings.js +25 -0
  92. package/lib/molecule3d/core/settings.js.map +1 -1
  93. package/lib/molecule3d/ui/Molecule3DExport.d.ts +3 -0
  94. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  95. package/lib/molecule3d/ui/Molecule3DExport.js +10 -16
  96. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  97. package/lib/molecule3d/ui/Molecule3DOptions.d.ts +3 -1
  98. package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
  99. package/lib/molecule3d/ui/Molecule3DOptions.js +22 -6
  100. package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
  101. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  102. package/lib/molecule3d/ui/MoleculeCanvas3D.js +4 -3
  103. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  104. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +9 -0
  105. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  106. package/lib/molecule3d/ui/exportMoleculeImage.js +15 -0
  107. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  108. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +2 -1
  109. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  110. package/lib/molecule3d/ui/useImageExport.d.ts +7 -1
  111. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  112. package/lib/molecule3d/ui/useImageExport.js +12 -3
  113. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  114. package/lib/overlay/ui/FigureBar.d.ts +41 -0
  115. package/lib/overlay/ui/FigureBar.d.ts.map +1 -0
  116. package/lib/overlay/ui/FigureBar.js +43 -0
  117. package/lib/overlay/ui/FigureBar.js.map +1 -0
  118. package/lib/overlay/ui/OverlayBar.d.ts +13 -4
  119. package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
  120. package/lib/overlay/ui/OverlayBar.js +2 -2
  121. package/lib/overlay/ui/OverlayBar.js.map +1 -1
  122. package/lib/overlay/ui/OverlayBarStrip.d.ts +2 -1
  123. package/lib/overlay/ui/OverlayBarStrip.d.ts.map +1 -1
  124. package/lib/overlay/ui/OverlayBarStrip.js +9 -5
  125. package/lib/overlay/ui/OverlayBarStrip.js.map +1 -1
  126. package/lib/overlay/ui/OverlayIconButton.d.ts +8 -2
  127. package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
  128. package/lib/overlay/ui/OverlayIconButton.js +2 -1
  129. package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
  130. package/lib/overlay/ui/index.d.ts +4 -0
  131. package/lib/overlay/ui/index.d.ts.map +1 -1
  132. package/lib/overlay/ui/index.js +2 -0
  133. package/lib/overlay/ui/index.js.map +1 -1
  134. package/lib/overlay/ui/overlayBarShape.d.ts +18 -0
  135. package/lib/overlay/ui/overlayBarShape.d.ts.map +1 -1
  136. package/lib/overlay/ui/overlayStyles.d.ts +30 -0
  137. package/lib/overlay/ui/overlayStyles.d.ts.map +1 -1
  138. package/lib/overlay/ui/overlayStyles.js +52 -0
  139. package/lib/overlay/ui/overlayStyles.js.map +1 -1
  140. package/lib/overlay/ui/useFigureBarAwake.d.ts +12 -0
  141. package/lib/overlay/ui/useFigureBarAwake.d.ts.map +1 -0
  142. package/lib/overlay/ui/useFigureBarAwake.js +51 -0
  143. package/lib/overlay/ui/useFigureBarAwake.js.map +1 -0
  144. package/lib/pages.d.ts +3 -0
  145. package/lib/pages.d.ts.map +1 -0
  146. package/lib/pages.js +2 -0
  147. package/lib/pages.js.map +1 -0
  148. package/lib/periodic/core/elements.d.ts +6 -3
  149. package/lib/periodic/core/elements.d.ts.map +1 -1
  150. package/lib/periodic/core/elements.js +9 -3
  151. package/lib/periodic/core/elements.js.map +1 -1
  152. package/lib/periodic/core/index.d.ts +2 -2
  153. package/lib/periodic/core/index.d.ts.map +1 -1
  154. package/lib/periodic/core/index.js +1 -1
  155. package/lib/periodic/core/index.js.map +1 -1
  156. package/lib/periodic/core/layout.d.ts +33 -3
  157. package/lib/periodic/core/layout.d.ts.map +1 -1
  158. package/lib/periodic/core/layout.js +14 -0
  159. package/lib/periodic/core/layout.js.map +1 -1
  160. package/lib/periodic/ui/BlockStrip.d.ts +20 -0
  161. package/lib/periodic/ui/BlockStrip.d.ts.map +1 -0
  162. package/lib/periodic/ui/BlockStrip.js +48 -0
  163. package/lib/periodic/ui/BlockStrip.js.map +1 -0
  164. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  165. package/lib/periodic/ui/ElementCell.js +9 -8
  166. package/lib/periodic/ui/ElementCell.js.map +1 -1
  167. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  168. package/lib/periodic/ui/PeriodicTable.js +3 -1
  169. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  170. package/lib/periodic/ui/PeriodicTableHelp.d.ts +33 -0
  171. package/lib/periodic/ui/PeriodicTableHelp.d.ts.map +1 -0
  172. package/lib/periodic/ui/PeriodicTableHelp.js +107 -0
  173. package/lib/periodic/ui/PeriodicTableHelp.js.map +1 -0
  174. package/lib/periodic/ui/cellStyles.d.ts +3 -2
  175. package/lib/periodic/ui/cellStyles.d.ts.map +1 -1
  176. package/lib/periodic/ui/cellStyles.js +3 -2
  177. package/lib/periodic/ui/cellStyles.js.map +1 -1
  178. package/lib/periodic/ui/index.d.ts +2 -0
  179. package/lib/periodic/ui/index.d.ts.map +1 -1
  180. package/lib/periodic/ui/index.js +1 -0
  181. package/lib/periodic/ui/index.js.map +1 -1
  182. package/lib/periodic/ui/periodicTableProps.d.ts +5 -4
  183. package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -1
  184. package/lib/periodic/ui/tableLayout.d.ts +3 -1
  185. package/lib/periodic/ui/tableLayout.d.ts.map +1 -1
  186. package/lib/periodic/ui/tableLayout.js +8 -3
  187. package/lib/periodic/ui/tableLayout.js.map +1 -1
  188. package/lib/seo/core/trackingMiddleware.d.ts +27 -0
  189. package/lib/seo/core/trackingMiddleware.d.ts.map +1 -0
  190. package/lib/seo/core/trackingMiddleware.js +33 -0
  191. package/lib/seo/core/trackingMiddleware.js.map +1 -0
  192. package/lib/share/ui/ShareDialogContent.d.ts +3 -2
  193. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  194. package/lib/share/ui/ShareDialogContent.js +5 -6
  195. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  196. package/lib/share/ui/ShareLinkBar.d.ts +8 -2
  197. package/lib/share/ui/ShareLinkBar.d.ts.map +1 -1
  198. package/lib/share/ui/ShareLinkBar.js +6 -5
  199. package/lib/share/ui/ShareLinkBar.js.map +1 -1
  200. package/lib/share/ui/SharePreview.d.ts.map +1 -1
  201. package/lib/share/ui/SharePreview.js +2 -2
  202. package/lib/share/ui/SharePreview.js.map +1 -1
  203. package/lib/structure/ui/StructurePicturePane.js +3 -3
  204. package/lib/structure/ui/StructurePicturePane.js.map +1 -1
  205. package/package.json +2 -1
  206. package/src/clipboard/core/index.ts +0 -1
  207. package/src/clipboard/ui/CopyButton.tsx +12 -1
  208. package/src/color/core/index.ts +9 -0
  209. package/src/color/core/interpolate.ts +51 -22
  210. package/src/color/core/stops.ts +98 -0
  211. package/src/color/core/tone.ts +79 -0
  212. package/src/color/core/uniform.ts +67 -0
  213. package/src/color/ui/ColorScaleEditor.tsx +115 -141
  214. package/src/color/ui/ColorScaleSelect.tsx +160 -36
  215. package/src/color/ui/ColorScaleTone.tsx +103 -0
  216. package/src/color/ui/ColorScaleTrack.tsx +239 -0
  217. package/src/download/core/copyFigure.ts +20 -0
  218. package/src/download/core/figureHtml.ts +1 -0
  219. package/src/download/core/figureHtmlMarkup.ts +14 -1
  220. package/src/download/core/index.ts +2 -0
  221. package/src/download/ui/FigureDownloadPanel.tsx +8 -11
  222. package/src/download/ui/figureDownloadHint.ts +1 -2
  223. package/src/download/ui/index.ts +0 -1
  224. package/src/download/ui/useFigureActions.ts +6 -42
  225. package/src/download/ui/useFigureTask.ts +46 -0
  226. package/src/locales/de.json +28 -2
  227. package/src/locales/en.json +28 -2
  228. package/src/locales/es.json +28 -2
  229. package/src/locales/fr.json +28 -2
  230. package/src/locales/it.json +28 -2
  231. package/src/molecule3d/core/index.ts +2 -0
  232. package/src/molecule3d/core/settings.ts +35 -0
  233. package/src/molecule3d/ui/Molecule3DExport.tsx +18 -17
  234. package/src/molecule3d/ui/Molecule3DOptions.tsx +51 -3
  235. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +11 -3
  236. package/src/molecule3d/ui/exportMoleculeImage.ts +20 -0
  237. package/src/molecule3d/ui/moleculeViewer3DProps.ts +2 -1
  238. package/src/molecule3d/ui/useImageExport.ts +25 -3
  239. package/src/overlay/ui/FigureBar.tsx +96 -0
  240. package/src/overlay/ui/OverlayBar.tsx +15 -5
  241. package/src/overlay/ui/OverlayBarStrip.tsx +34 -22
  242. package/src/overlay/ui/OverlayIconButton.tsx +10 -2
  243. package/src/overlay/ui/index.ts +4 -0
  244. package/src/overlay/ui/overlayBarShape.ts +19 -0
  245. package/src/overlay/ui/overlayStyles.ts +61 -0
  246. package/src/overlay/ui/useFigureBarAwake.ts +56 -0
  247. package/src/pages.ts +2 -0
  248. package/src/periodic/core/elements.ts +8 -3
  249. package/src/periodic/core/index.ts +8 -1
  250. package/src/periodic/core/layout.ts +45 -3
  251. package/src/periodic/ui/BlockStrip.tsx +81 -0
  252. package/src/periodic/ui/ElementCell.tsx +9 -8
  253. package/src/periodic/ui/PeriodicTable.tsx +4 -1
  254. package/src/periodic/ui/PeriodicTableHelp.tsx +205 -0
  255. package/src/periodic/ui/cellStyles.ts +3 -2
  256. package/src/periodic/ui/index.ts +5 -0
  257. package/src/periodic/ui/periodicTableProps.ts +5 -4
  258. package/src/periodic/ui/tableLayout.ts +9 -3
  259. package/src/seo/core/trackingMiddleware.ts +48 -0
  260. package/src/share/ui/ShareDialogContent.tsx +9 -13
  261. package/src/share/ui/ShareLinkBar.tsx +16 -4
  262. package/src/share/ui/SharePreview.tsx +6 -2
  263. package/src/structure/ui/StructurePicturePane.tsx +3 -3
  264. package/styles/chrome.css +43 -32
  265. package/lib/clipboard/core/copyPng.d.ts +0 -13
  266. package/lib/clipboard/core/copyPng.d.ts.map +0 -1
  267. package/lib/clipboard/core/copyPng.js +0 -19
  268. package/lib/clipboard/core/copyPng.js.map +0 -1
  269. package/src/clipboard/core/copyPng.ts +0 -20
@@ -1,11 +1,16 @@
1
1
  import type { RefObject } from 'react';
2
2
  import { useCallback } from 'react';
3
3
 
4
+ import type { FigureNotice } from '../../download/core/copyFigure.ts';
5
+ import { copyFigure } from '../../download/core/copyFigure.ts';
4
6
  import type { FigureFormat } from '../../download/core/downloadFigure.ts';
5
7
  import type { FigurePixels } from '../../download/core/figureScale.ts';
6
8
 
7
9
  import type { SceneCapture } from './exportMoleculeImage.ts';
8
- import { exportMoleculeImage } from './exportMoleculeImage.ts';
10
+ import {
11
+ captureMoleculePng,
12
+ exportMoleculeImage,
13
+ } from './exportMoleculeImage.ts';
9
14
 
10
15
  /** What {@link useImageExport} hands the export panel. */
11
16
  export interface ImageExport {
@@ -13,10 +18,15 @@ export interface ImageExport {
13
18
  canvasSize: () => FigurePixels | null;
14
19
  /** Render the scene at `scale` and download it as `format`. */
15
20
  exportImage: (format: FigureFormat, scale: number) => Promise<void>;
21
+ /**
22
+ * Render the scene at `scale` and put it on the clipboard as a PNG. Call it
23
+ * from the click itself: Safari only allows a write the click started.
24
+ */
25
+ copyImage: (scale: number) => Promise<FigureNotice>;
16
26
  }
17
27
 
18
28
  /**
19
- * The two callbacks the export panel needs, bound to the canvas and its viewer.
29
+ * The callbacks the export panel needs, bound to the canvas and its viewer.
20
30
  * @param container - The element molstar draws into.
21
31
  * @param viewerRef - The viewer, once created.
22
32
  * @param fileName - File name without its extension.
@@ -46,5 +56,17 @@ export function useImageExport(
46
56
  [canvasSize, viewerRef, fileName],
47
57
  );
48
58
 
49
- return { canvasSize, exportImage };
59
+ const copyImage = useCallback(
60
+ (scale: number) => {
61
+ const viewer = viewerRef.current;
62
+ const size = canvasSize();
63
+ if (viewer === null || size === null) {
64
+ return Promise.reject(new Error('There is no picture to copy yet.'));
65
+ }
66
+ return copyFigure(captureMoleculePng(viewer, scale, size));
67
+ },
68
+ [canvasSize, viewerRef],
69
+ );
70
+
71
+ return { canvasSize, exportImage, copyImage };
50
72
  }
@@ -0,0 +1,96 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import { useMemo, useRef } from 'react';
3
+
4
+ import { useContainerSize } from '../../hooks/ui/useContainerSize.ts';
5
+ import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
6
+ import type { OverlayDensity } from '../core/overlayMetrics.ts';
7
+ import { overlayMetrics } from '../core/overlayMetrics.ts';
8
+
9
+ import type { OverlayBarProps } from './OverlayBar.tsx';
10
+ import { OverlayBar } from './OverlayBar.tsx';
11
+ import type { OverlaySurface } from './overlaySurface.ts';
12
+ import {
13
+ OverlaySurfaceContext,
14
+ useCoarsePointer,
15
+ useOverlaySurface,
16
+ } from './overlaySurface.ts';
17
+ import { useFigureBarAwake } from './useFigureBarAwake.ts';
18
+
19
+ /** What {@link FigureBar} needs. */
20
+ export interface FigureBarProps extends Omit<
21
+ OverlayBarProps,
22
+ 'placement' | 'restingOpacity' | 'children'
23
+ > {
24
+ /**
25
+ * The controls at the start of the row — how the figure is drawn, such as
26
+ * its axis scales. They never fold.
27
+ * @default undefined — the start of the row is empty
28
+ */
29
+ children?: ReactNode;
30
+ /**
31
+ * How tightly the bar packs its controls. A coarse pointer wins over this,
32
+ * whatever it says.
33
+ * @default 'compact'
34
+ */
35
+ density?: OverlayDensity;
36
+ /**
37
+ * Class the bar carries, in addition to `figure-bar no-print`.
38
+ * @default undefined
39
+ */
40
+ className?: string;
41
+ }
42
+
43
+ /**
44
+ * The controls of a figure, in one bar spanning the width above it.
45
+ *
46
+ * Settings read from the left edge — `children`, which never fold, then `end`,
47
+ * which folds behind a cog on a narrow figure — and the glyphs keep the right
48
+ * one: the `?` in `info`, saving and printing in `tools`. Every figure of the
49
+ * family is laid out this way, so the reader finds both in the same place.
50
+ *
51
+ * Put it right before the figure, never inside its box, and give the two a
52
+ * parent of their own: it is in the flow, so it covers neither the data nor
53
+ * the axes, and with `rest` it wakes while that parent is pointed at. It measures its own width to decide
54
+ * what folds, is left out of a printed page, and is marked as chrome so that
55
+ * saving a figure it sits inside saves the picture without it.
56
+ * @param props - See {@link FigureBarProps}.
57
+ * @returns The bar.
58
+ */
59
+ export function FigureBar(props: FigureBarProps): ReactElement {
60
+ const { children, density = 'compact', className, ...bar } = props;
61
+ const { rest = 'visible' } = props;
62
+ const box = useRef<HTMLDivElement>(null);
63
+ const { width } = useContainerSize(box);
64
+ const above = useOverlaySurface();
65
+ const pointer = useCoarsePointer() ? 'coarse' : 'fine';
66
+ // A finger has nothing to point with until it touches, so it never rests.
67
+ const awake = useFigureBarAwake(
68
+ box,
69
+ rest !== 'visible' && pointer === 'fine',
70
+ );
71
+
72
+ const surface = useMemo<OverlaySurface>(
73
+ () => ({
74
+ ...above,
75
+ metrics: overlayMetrics(density, pointer),
76
+ awake,
77
+ pointer,
78
+ width,
79
+ }),
80
+ [above, density, awake, pointer, width],
81
+ );
82
+
83
+ return (
84
+ <div
85
+ ref={box}
86
+ className={joinClassNames('figure-bar no-print', className)}
87
+ data-figure="chrome"
88
+ >
89
+ <OverlaySurfaceContext.Provider value={surface}>
90
+ <OverlayBar {...bar} placement="stretch">
91
+ {children}
92
+ </OverlayBar>
93
+ </OverlaySurfaceContext.Provider>
94
+ </div>
95
+ );
96
+ }
@@ -9,6 +9,7 @@ import type { OverlayPlacement } from '../core/overlayPlacement.ts';
9
9
  import { OverlayBarCard } from './OverlayBarCard.tsx';
10
10
  import { OverlayBarStrip } from './OverlayBarStrip.tsx';
11
11
  import type { OverlayPanel } from './OverlayPanel.tsx';
12
+ import type { OverlayBarRest } from './overlayBarShape.ts';
12
13
  import { OVERLAY_RESTING_OPACITY } from './overlayStyles.ts';
13
14
  import { useOverlaySurface } from './overlaySurface.ts';
14
15
 
@@ -21,10 +22,11 @@ export interface OverlayBarProps {
21
22
  */
22
23
  children: ReactNode;
23
24
  /**
24
- * What sits at the far end of the row: the controls belonging to whatever
25
- * the figure is currently showing. They are the first thing to go when the
26
- * figure is too narrow for both ends, folding in behind the button.
27
- * @default undefined — the bar's end holds only its glyphs
25
+ * The controls belonging to whatever the figure is currently showing. On a
26
+ * card they close the row; on a stretched bar they follow the start, left
27
+ * aligned, and only the glyphs keep the right edge. They are the first
28
+ * thing to go when the figure is too narrow, folding in behind the button.
29
+ * @default undefined — the bar holds only its start and its glyphs
28
30
  */
29
31
  end?: ReactNode;
30
32
  /**
@@ -73,6 +75,13 @@ export interface OverlayBarProps {
73
75
  * @default 0.74
74
76
  */
75
77
  restingOpacity?: number;
78
+ /**
79
+ * How a stretched bar's controls are drawn while nothing points at the
80
+ * figure: at full strength, faded, or not at all. The bar keeps its height
81
+ * whichever it is. A card ignores it: its rest is `restingOpacity`.
82
+ * @default 'visible'
83
+ */
84
+ rest?: OverlayBarRest;
76
85
  /**
77
86
  * Whether the bar is reduced to a button opening the same controls in a
78
87
  * popover. Left out, it folds on its own once the figure is narrower than
@@ -135,7 +144,7 @@ export function OverlayBar(props: OverlayBarProps): ReactElement {
135
144
  const name = label ?? t('overlay.options');
136
145
  const { collapsed, defaultCollapsed = false } = props;
137
146
  const { collapseBelow = 420, moreIcon = 'cog', testId } = props;
138
- const { morePadded = true } = props;
147
+ const { morePadded = true, rest } = props;
139
148
  const { width } = useOverlaySurface();
140
149
  const [startedFolded] = useState(defaultCollapsed);
141
150
 
@@ -158,6 +167,7 @@ export function OverlayBar(props: OverlayBarProps): ReactElement {
158
167
  moreIcon={moreIcon}
159
168
  morePadded={morePadded}
160
169
  folded={folded}
170
+ rest={rest}
161
171
  testId={testId}
162
172
  >
163
173
  {children}
@@ -3,15 +3,18 @@ import type { ReactElement } from 'react';
3
3
  import { OverlayBarButton } from './OverlayBarButton.tsx';
4
4
  import type { OverlayBarShapeProps } from './overlayBarShape.ts';
5
5
  import {
6
- OVERLAY_BAR_GROUND_STYLE,
7
6
  overlayBarContentStyle,
7
+ overlayBarGroundStyle,
8
+ overlayBarGroupStyle,
9
+ overlayBarRestStyle,
8
10
  overlayBarSideStyle,
9
11
  overlayCardStyle,
10
12
  } from './overlayStyles.ts';
11
13
  import { useOverlaySurface } from './overlaySurface.ts';
12
14
 
13
15
  /**
14
- * The bar that spans the width of the figure, its two ends pushed apart.
16
+ * The bar that spans the width of the figure: the settings from the left edge,
17
+ * the glyphs — help, saving, the button the rest folds into — at the right.
15
18
  *
16
19
  * It is in the flow above the picture rather than on it, and its ground is
17
20
  * solid rather than translucent, because a strip reaching from one edge of the
@@ -26,35 +29,44 @@ import { useOverlaySurface } from './overlaySurface.ts';
26
29
  */
27
30
  export function OverlayBarStrip(props: OverlayBarShapeProps): ReactElement {
28
31
  const { children, end, tools, info, more, placement, label } = props;
29
- const { moreIcon, morePadded, folded, testId } = props;
30
- const { metrics } = useOverlaySurface();
32
+ const { moreIcon, morePadded, folded, testId, rest = 'visible' } = props;
33
+ const { metrics, awake } = useOverlaySurface();
31
34
 
32
35
  const opens = more !== undefined || (folded && end !== undefined);
33
36
 
34
37
  return (
35
38
  <div style={overlayCardStyle(placement, metrics)} data-testid={testId}>
36
- <div style={OVERLAY_BAR_GROUND_STYLE} />
37
- <div style={overlayBarContentStyle(metrics)}>
38
- <div style={overlayBarSideStyle(metrics)}>{children}</div>
39
+ <div style={overlayBarGroundStyle(rest, awake)} />
40
+ <div
41
+ style={{
42
+ ...overlayBarContentStyle(metrics),
43
+ ...overlayBarRestStyle(rest, awake),
44
+ }}
45
+ >
46
+ {children === null || children === undefined ? null : (
47
+ <div style={overlayBarSideStyle(metrics)}>{children}</div>
48
+ )}
39
49
  <div
40
50
  role="group"
41
51
  aria-label={label}
42
- style={overlayBarSideStyle(metrics)}
52
+ style={overlayBarGroupStyle(metrics)}
43
53
  >
44
- {folded ? null : end}
45
- {tools}
46
- {info}
47
- {opens ? (
48
- <OverlayBarButton
49
- placement={placement}
50
- label={label}
51
- icon={moreIcon}
52
- padded={morePadded}
53
- >
54
- {folded ? end : null}
55
- {more}
56
- </OverlayBarButton>
57
- ) : null}
54
+ <div style={overlayBarSideStyle(metrics)}>{folded ? null : end}</div>
55
+ <div style={overlayBarSideStyle(metrics)}>
56
+ {tools}
57
+ {info}
58
+ {opens ? (
59
+ <OverlayBarButton
60
+ placement={placement}
61
+ label={label}
62
+ icon={moreIcon}
63
+ padded={morePadded}
64
+ >
65
+ {folded ? end : null}
66
+ {more}
67
+ </OverlayBarButton>
68
+ ) : null}
69
+ </div>
58
70
  </div>
59
71
  </div>
60
72
  </div>
@@ -7,7 +7,7 @@ import { useOverlaySurface } from './overlaySurface.ts';
7
7
  import { useOverlayInteraction } from './useOverlayInteraction.ts';
8
8
 
9
9
  /** What {@link OverlayIconButton} needs. */
10
- interface OverlayIconButtonProps {
10
+ export interface OverlayIconButtonProps {
11
11
  /**
12
12
  * The glyph: a Blueprint icon name, or an element of the caller's own — an
13
13
  * inline `<svg>` drawing the very mark the figure draws, which is the one
@@ -59,6 +59,13 @@ interface OverlayIconButtonProps {
59
59
  * @default undefined — nothing of its own happens
60
60
  */
61
61
  onClick?: () => void;
62
+ /**
63
+ * Whether the pointer is left without the browser's own tooltip, for a
64
+ * button that already opens a card of its own on hover: the two would
65
+ * otherwise stack. The screen reader is still told the name.
66
+ * @default false
67
+ */
68
+ untitled?: boolean;
62
69
  /**
63
70
  * Value of the `data-testid` attribute.
64
71
  * @default undefined
@@ -81,6 +88,7 @@ interface OverlayIconButtonProps {
81
88
  export function OverlayIconButton(props: OverlayIconButtonProps): ReactElement {
82
89
  const { icon, label, value, active = false, disabled = false } = props;
83
90
  const { disabledReason, opensMenu = false, onClick, testId } = props;
91
+ const { untitled = false } = props;
84
92
  const { metrics } = useOverlaySurface();
85
93
  const { hovered, focused, handlers } = useOverlayInteraction();
86
94
 
@@ -91,7 +99,7 @@ export function OverlayIconButton(props: OverlayIconButtonProps): ReactElement {
91
99
  <button
92
100
  type="button"
93
101
  className="no-print"
94
- title={told}
102
+ title={untitled ? undefined : told}
95
103
  aria-label={name}
96
104
  aria-haspopup={opensMenu ? 'menu' : undefined}
97
105
  aria-expanded={opensMenu ? active : undefined}
@@ -9,6 +9,8 @@
9
9
  * belong to the same tool.
10
10
  */
11
11
 
12
+ export type { FigureBarProps } from './FigureBar.tsx';
13
+ export { FigureBar } from './FigureBar.tsx';
12
14
  export type { OverlayActionProps } from './OverlayAction.tsx';
13
15
  export { OverlayAction } from './OverlayAction.tsx';
14
16
  export type { OverlayBarProps } from './OverlayBar.tsx';
@@ -20,6 +22,8 @@ export { OverlayChip } from './OverlayChip.tsx';
20
22
  export { OverlayDivider } from './OverlayDivider.tsx';
21
23
  export type { OverlayGroupProps } from './OverlayGroup.tsx';
22
24
  export { OverlayGroup } from './OverlayGroup.tsx';
25
+ export type { OverlayIconButtonProps } from './OverlayIconButton.tsx';
26
+ export { OverlayIconButton } from './OverlayIconButton.tsx';
23
27
  export type { OverlayInfoProps } from './OverlayInfo.tsx';
24
28
  export { OverlayInfo } from './OverlayInfo.tsx';
25
29
  export type { OverlayLayerProps } from './OverlayLayer.tsx';
@@ -12,6 +12,19 @@ import type { ReactNode } from 'react';
12
12
 
13
13
  import type { OverlayPlacement } from '../core/overlayPlacement.ts';
14
14
 
15
+ /**
16
+ * How a stretched bar's controls are drawn while nothing points at the figure
17
+ * it belongs to. The bar keeps its height in every case, so nothing under it
18
+ * moves when it wakes.
19
+ *
20
+ * - `visible` — at full strength, always.
21
+ * - `quiet` — faded, so the bar still says how the figure is drawn without
22
+ * drawing the eye; full strength on pointing or focus.
23
+ * - `hidden` — an empty strip until the figure is pointed at or a control
24
+ * holds the focus.
25
+ */
26
+ export type OverlayBarRest = 'visible' | 'quiet' | 'hidden';
27
+
15
28
  /** What both shapes of a bar are drawn from, with every default resolved. */
16
29
  export interface OverlayBarShapeProps {
17
30
  /** The controls at the start of the row. */
@@ -46,6 +59,12 @@ export interface OverlayBarShapeProps {
46
59
  morePadded: boolean;
47
60
  /** Whether the bar has folded its controls away behind that button. */
48
61
  folded: boolean;
62
+ /**
63
+ * How a stretched bar's controls are drawn while nothing points at the
64
+ * figure.
65
+ * @default 'visible'
66
+ */
67
+ rest?: OverlayBarRest;
49
68
  /**
50
69
  * Value of the `data-testid` attribute of the bar.
51
70
  * @default undefined
@@ -204,6 +204,67 @@ export function overlayBarContentStyle(metrics: OverlayMetrics): CSSProperties {
204
204
  };
205
205
  }
206
206
 
207
+ /** How strong a quiet bar's controls are drawn while it rests. */
208
+ export const OVERLAY_QUIET_OPACITY = 0.45;
209
+
210
+ /**
211
+ * How much of a resting bar's ground is left: almost none, so the strip reads
212
+ * as the page behind it until the figure is pointed at.
213
+ */
214
+ export const OVERLAY_RESTING_GROUND_OPACITY = 0.08;
215
+
216
+ /**
217
+ * The opacity of a stretched bar's controls.
218
+ * @param rest - How the bar rests.
219
+ * @param awake - Whether the figure is pointed at or a control holds the focus.
220
+ * @returns The rules fading the controls, never the ground under them.
221
+ */
222
+ export function overlayBarRestStyle(
223
+ rest: 'visible' | 'quiet' | 'hidden',
224
+ awake: boolean,
225
+ ): CSSProperties {
226
+ if (rest === 'visible') return {};
227
+ const opacity = awake ? 1 : rest === 'quiet' ? OVERLAY_QUIET_OPACITY : 0;
228
+ return { opacity, transition: 'opacity 150ms ease-out' };
229
+ }
230
+
231
+ /**
232
+ * The ground of a stretched bar — its surface and the hairline under it —
233
+ * faded with its controls, so a resting bar is the page's own grey.
234
+ * @param rest - How the bar rests.
235
+ * @param awake - Whether the figure is pointed at or a control holds the focus.
236
+ * @returns The ground's rules.
237
+ */
238
+ export function overlayBarGroundStyle(
239
+ rest: 'visible' | 'quiet' | 'hidden',
240
+ awake: boolean,
241
+ ): CSSProperties {
242
+ if (rest === 'visible') return OVERLAY_BAR_GROUND_STYLE;
243
+ return {
244
+ ...OVERLAY_BAR_GROUND_STYLE,
245
+ opacity: awake ? 1 : OVERLAY_RESTING_GROUND_OPACITY,
246
+ transition: 'opacity 150ms ease-out',
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Everything on a stretched bar after its start: the settings, then the glyphs
252
+ * pushed to the right edge, so settings always read from the left and the `?`
253
+ * and the save glyph are always found in the same corner.
254
+ * @param metrics - The measurements the bar is drawn from.
255
+ * @returns The group's rules.
256
+ */
257
+ export function overlayBarGroupStyle(metrics: OverlayMetrics): CSSProperties {
258
+ return {
259
+ display: 'flex',
260
+ flex: '1 1 auto',
261
+ alignItems: 'center',
262
+ justifyContent: 'space-between',
263
+ gap: metrics.gap,
264
+ minWidth: 0,
265
+ };
266
+ }
267
+
207
268
  /**
208
269
  * One end of a stretched bar.
209
270
  *
@@ -0,0 +1,56 @@
1
+ import type { RefObject } from 'react';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ /** How long the bar stays awake after the pointer leaves, so a reader moving to a menu does not watch it fade. */
5
+ const SLEEP_DELAY = 300;
6
+
7
+ /**
8
+ * Whether a figure's bar is awake: the box holding the bar and its figure is
9
+ * pointed at, or the focus is in the bar or in a popover one of its controls
10
+ * opened.
11
+ * @param bar - The bar; its parent is the box holding the figure it configures.
12
+ * @param watching - Whether to watch at all. A bar that never rests is always
13
+ * awake and listens to nothing.
14
+ * @returns Whether the bar is awake.
15
+ */
16
+ export function useFigureBarAwake(
17
+ bar: RefObject<HTMLElement | null>,
18
+ watching: boolean,
19
+ ): boolean {
20
+ const [pointed, setPointed] = useState(false);
21
+ const [focused, setFocused] = useState(false);
22
+
23
+ useEffect(() => {
24
+ const figure = bar.current?.parentElement;
25
+ if (!watching || figure === null || figure === undefined) return;
26
+ let timer: ReturnType<typeof setTimeout> | undefined;
27
+ const enter = () => {
28
+ clearTimeout(timer);
29
+ setPointed(true);
30
+ };
31
+ const leave = () => {
32
+ clearTimeout(timer);
33
+ timer = setTimeout(() => setPointed(false), SLEEP_DELAY);
34
+ };
35
+ // A menu opened from the bar is portalled out of it; the focus inside it
36
+ // still belongs to the bar, so the trigger does not vanish under it.
37
+ const focusIn = (event: FocusEvent) => {
38
+ const target = event.target;
39
+ if (!(target instanceof Element)) return;
40
+ const inBar = bar.current?.contains(target) ?? false;
41
+ const inPortal = target.closest('.bp6-portal') !== null;
42
+ setFocused((was) => inBar || (was && inPortal));
43
+ };
44
+ figure.addEventListener('pointerenter', enter);
45
+ figure.addEventListener('pointerleave', leave);
46
+ document.addEventListener('focusin', focusIn);
47
+ return () => {
48
+ clearTimeout(timer);
49
+ figure.removeEventListener('pointerenter', enter);
50
+ figure.removeEventListener('pointerleave', leave);
51
+ document.removeEventListener('focusin', focusIn);
52
+ };
53
+ }, [bar, watching]);
54
+
55
+ return !watching || pointed || focused;
56
+ }
package/src/pages.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { TrackingMiddlewareContext } from './seo/core/trackingMiddleware.ts';
2
+ export { trackingScriptMiddleware as onRequest } from './seo/core/trackingMiddleware.ts';
@@ -9,9 +9,12 @@
9
9
  * they still agree with it.
10
10
  *
11
11
  * `block` follows the element's *position* in the table, which is what a
12
- * student reads off it. The configuration is the authority on which subshell is
13
- * actually filling, and the two disagree for a handful of elements — lanthanum
14
- * fills 5d, not 4f. See `ELEMENT_ANOMALIES` in the orbital module.
12
+ * student reads off it, with two exceptions where the table's shape and the
13
+ * chemistry part ways: helium is s, and lutetium and lawrencium close the d
14
+ * block in group 3 (IUPAC), so the f block is fourteen wide. The configuration
15
+ * is the authority on which subshell is actually filling, and the two disagree
16
+ * for a handful of elements — lanthanum fills 5d, not 4f. See
17
+ * `ELEMENT_ANOMALIES` in the orbital module.
15
18
  */
16
19
 
17
20
  /** Region of the table an element sits in. */
@@ -210,6 +213,8 @@ export function elementByAtomicNumber(
210
213
  }
211
214
 
212
215
  function blockOf(atomicNumber: number, group: number | null): ElementBlock {
216
+ // Lutetium and lawrencium are drawn in the f rows but belong to group 3.
217
+ if (atomicNumber === 71 || atomicNumber === 103) return 'd';
213
218
  if (group === null) return 'f';
214
219
  // Helium sits above the p block but its only electrons are 1s.
215
220
  if (atomicNumber === 2) return 's';
@@ -15,8 +15,15 @@ export {
15
15
  UNKNOWN_SWATCH,
16
16
  categorySwatch,
17
17
  } from './categories.ts';
18
- export type { Cell, ElementPick, ElementRange } from './layout.ts';
18
+ export type {
19
+ BlockRange,
20
+ Cell,
21
+ ElementPick,
22
+ ElementRange,
23
+ NumberedRange,
24
+ } from './layout.ts';
19
25
  export {
26
+ BLOCK_LABELS,
20
27
  COLUMN_COUNT,
21
28
  EMPTY_BLOCK,
22
29
  INNER_TRANSITION_MARKERS,
@@ -11,11 +11,23 @@
11
11
  * by one; nothing in this module needs to know that.
12
12
  */
13
13
 
14
- import type { ElementCategory, PeriodicElement } from './elements.ts';
14
+ import type {
15
+ ElementBlock,
16
+ ElementCategory,
17
+ PeriodicElement,
18
+ } from './elements.ts';
15
19
  import { PERIODIC_ELEMENTS, elementBySymbol } from './elements.ts';
16
20
 
17
- /** A whole run of the table: one group, or one period. */
18
- export interface ElementRange {
21
+ /**
22
+ * A whole run of the table: one group, one period, or one block.
23
+ *
24
+ * Narrow on `kind` before reading `value`: a group or a period is numbered, a
25
+ * block is lettered.
26
+ */
27
+ export type ElementRange = NumberedRange | BlockRange;
28
+
29
+ /** One group or one period. */
30
+ export interface NumberedRange {
19
31
  /** Whether the run is a group or a period. */
20
32
  kind: 'group' | 'period';
21
33
  /** Its number: a group from 1 to 18, a period from 1 to 7. */
@@ -28,6 +40,16 @@ export interface ElementRange {
28
40
  additive: boolean;
29
41
  }
30
42
 
43
+ /** One block: the elements whose last electron enters the same subshell. */
44
+ export interface BlockRange {
45
+ /** A block. */
46
+ kind: 'block';
47
+ /** Its letter. */
48
+ value: ElementBlock;
49
+ /** As for {@link NumberedRange.additive}. */
50
+ additive: boolean;
51
+ }
52
+
31
53
  /** One element, and what the click that picked it asked for. */
32
54
  export interface ElementPick {
33
55
  /**
@@ -137,6 +159,26 @@ export const INNER_TRANSITION_MARKERS: ReadonlyArray<{
137
159
  { cell: { column: 3, row: 7 }, label: '89–103', category: 'actinoid' },
138
160
  ];
139
161
 
162
+ /**
163
+ * Where each block is named, in the cells no element occupies: s, d and p
164
+ * along the spacer under period 7, each under the columns it holds, and f in
165
+ * the two cells left of the inner-transition rows.
166
+ *
167
+ * Helium is an s element standing over the p block; a label follows the
168
+ * columns, and the element its electrons.
169
+ */
170
+ export const BLOCK_LABELS: ReadonlyArray<{
171
+ block: ElementBlock;
172
+ cell: Cell;
173
+ columnSpan: number;
174
+ rowSpan: number;
175
+ }> = [
176
+ { block: 's', cell: { column: 1, row: 8 }, columnSpan: 2, rowSpan: 1 },
177
+ { block: 'd', cell: { column: 3, row: 8 }, columnSpan: 10, rowSpan: 1 },
178
+ { block: 'p', cell: { column: 13, row: 8 }, columnSpan: 6, rowSpan: 1 },
179
+ { block: 'f', cell: { column: 1, row: 9 }, columnSpan: 2, rowSpan: 2 },
180
+ ];
181
+
140
182
  /** The period each inner-transition row belongs to, for its row label. */
141
183
  export const INNER_TRANSITION_ROWS: ReadonlyArray<{
142
184
  row: number;