react-cheminfo 0.38.0 → 0.39.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 (327) hide show
  1. package/lib/chart/ui/Axes.d.ts +19 -0
  2. package/lib/chart/ui/Axes.d.ts.map +1 -1
  3. package/lib/chart/ui/Axes.js +25 -5
  4. package/lib/chart/ui/Axes.js.map +1 -1
  5. package/lib/chrome/ui/NavMenuButton.d.ts.map +1 -1
  6. package/lib/chrome/ui/NavMenuButton.js +41 -11
  7. package/lib/chrome/ui/NavMenuButton.js.map +1 -1
  8. package/lib/chrome/ui/SiteHeader.js +24 -1
  9. package/lib/chrome/ui/SiteHeader.js.map +1 -1
  10. package/lib/chrome/ui/navItem.d.ts +7 -0
  11. package/lib/chrome/ui/navItem.d.ts.map +1 -1
  12. package/lib/chrome/ui/navItem.js.map +1 -1
  13. package/lib/citation/ui/entries.d.ts.map +1 -1
  14. package/lib/citation/ui/entries.js +6 -1
  15. package/lib/citation/ui/entries.js.map +1 -1
  16. package/lib/clipboard/ui/ClickToCopy.d.ts +11 -0
  17. package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
  18. package/lib/clipboard/ui/ClickToCopy.js +28 -10
  19. package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
  20. package/lib/clipboard/ui/CopyButton.d.ts +2 -1
  21. package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
  22. package/lib/clipboard/ui/CopyButton.js +2 -1
  23. package/lib/clipboard/ui/CopyButton.js.map +1 -1
  24. package/lib/clipboard/ui/CopyableValue.d.ts +24 -4
  25. package/lib/clipboard/ui/CopyableValue.d.ts.map +1 -1
  26. package/lib/clipboard/ui/CopyableValue.js +35 -3
  27. package/lib/clipboard/ui/CopyableValue.js.map +1 -1
  28. package/lib/clipboard/ui/index.d.ts +1 -1
  29. package/lib/clipboard/ui/index.d.ts.map +1 -1
  30. package/lib/clipboard/ui/index.js.map +1 -1
  31. package/lib/confirm/ui/ConfirmButton.d.ts.map +1 -1
  32. package/lib/confirm/ui/ConfirmButton.js +2 -1
  33. package/lib/confirm/ui/ConfirmButton.js.map +1 -1
  34. package/lib/delimited/ui/TableDataButton.d.ts.map +1 -1
  35. package/lib/delimited/ui/TableDataButton.js +2 -1
  36. package/lib/delimited/ui/TableDataButton.js.map +1 -1
  37. package/lib/download/core/figureSvg.d.ts.map +1 -1
  38. package/lib/download/core/figureSvg.js +37 -12
  39. package/lib/download/core/figureSvg.js.map +1 -1
  40. package/lib/help/ui/HelpToolbarButton.d.ts.map +1 -1
  41. package/lib/help/ui/HelpToolbarButton.js +2 -1
  42. package/lib/help/ui/HelpToolbarButton.js.map +1 -1
  43. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  44. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  45. package/lib/locales/de.json +39 -4
  46. package/lib/locales/en.json +39 -4
  47. package/lib/locales/es.json +39 -4
  48. package/lib/locales/fr.json +39 -4
  49. package/lib/locales/it.json +39 -4
  50. package/lib/number/ui/NumberInput.js +1 -1
  51. package/lib/number/ui/NumberInput.js.map +1 -1
  52. package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
  53. package/lib/overlay/ui/OverlayIconButton.js +1 -1
  54. package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
  55. package/lib/panel/ui/ClearButton.d.ts.map +1 -1
  56. package/lib/panel/ui/ClearButton.js +1 -1
  57. package/lib/panel/ui/ClearButton.js.map +1 -1
  58. package/lib/pedagogy/core/inlineMarks.d.ts +9 -5
  59. package/lib/pedagogy/core/inlineMarks.d.ts.map +1 -1
  60. package/lib/pedagogy/core/inlineMarks.js +14 -6
  61. package/lib/pedagogy/core/inlineMarks.js.map +1 -1
  62. package/lib/pedagogy/ui/GlossaryDefinition.d.ts +4 -10
  63. package/lib/pedagogy/ui/GlossaryDefinition.d.ts.map +1 -1
  64. package/lib/pedagogy/ui/GlossaryDefinition.js +7 -7
  65. package/lib/pedagogy/ui/GlossaryDefinition.js.map +1 -1
  66. package/lib/pedagogy/ui/GlossaryIndex.d.ts.map +1 -1
  67. package/lib/pedagogy/ui/GlossaryIndex.js +1 -1
  68. package/lib/pedagogy/ui/GlossaryIndex.js.map +1 -1
  69. package/lib/pedagogy/ui/GlossaryProvider.d.ts +7 -0
  70. package/lib/pedagogy/ui/GlossaryProvider.d.ts.map +1 -1
  71. package/lib/pedagogy/ui/GlossaryProvider.js +3 -2
  72. package/lib/pedagogy/ui/GlossaryProvider.js.map +1 -1
  73. package/lib/pedagogy/ui/GlossaryText.d.ts +7 -2
  74. package/lib/pedagogy/ui/GlossaryText.d.ts.map +1 -1
  75. package/lib/pedagogy/ui/GlossaryText.js +3 -2
  76. package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
  77. package/lib/pedagogy/ui/InlineText.d.ts +8 -2
  78. package/lib/pedagogy/ui/InlineText.d.ts.map +1 -1
  79. package/lib/pedagogy/ui/InlineText.js +7 -2
  80. package/lib/pedagogy/ui/InlineText.js.map +1 -1
  81. package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts +4 -1
  82. package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts.map +1 -1
  83. package/lib/pedagogy/ui/ReferenceSectionBlock.js +2 -1
  84. package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
  85. package/lib/pedagogy/ui/SyntaxTooltip.d.ts.map +1 -1
  86. package/lib/pedagogy/ui/SyntaxTooltip.js +11 -10
  87. package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
  88. package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
  89. package/lib/pedagogy/ui/TutorialStepStrip.js +18 -4
  90. package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
  91. package/lib/pedagogy/ui/glossaryContext.d.ts +7 -0
  92. package/lib/pedagogy/ui/glossaryContext.d.ts.map +1 -1
  93. package/lib/pedagogy/ui/glossaryContext.js.map +1 -1
  94. package/lib/pedagogy/ui/index.d.ts +1 -1
  95. package/lib/pedagogy/ui/index.d.ts.map +1 -1
  96. package/lib/pedagogy/ui/inlineSegments.d.ts +5 -0
  97. package/lib/pedagogy/ui/inlineSegments.d.ts.map +1 -1
  98. package/lib/pedagogy/ui/inlineSegments.js +2 -0
  99. package/lib/pedagogy/ui/inlineSegments.js.map +1 -1
  100. package/lib/pedagogy/ui/pedagogyStyle.d.ts +8 -9
  101. package/lib/pedagogy/ui/pedagogyStyle.d.ts.map +1 -1
  102. package/lib/pedagogy/ui/pedagogyStyle.js +10 -18
  103. package/lib/pedagogy/ui/pedagogyStyle.js.map +1 -1
  104. package/lib/seo/core/plainDescription.d.ts +2 -2
  105. package/lib/seo/core/plainDescription.js +5 -2
  106. package/lib/seo/core/plainDescription.js.map +1 -1
  107. package/lib/share/core/devices.d.ts +41 -0
  108. package/lib/share/core/devices.d.ts.map +1 -0
  109. package/lib/share/core/devices.js +58 -0
  110. package/lib/share/core/devices.js.map +1 -0
  111. package/lib/share/core/index.d.ts +4 -0
  112. package/lib/share/core/index.d.ts.map +1 -1
  113. package/lib/share/core/index.js +2 -0
  114. package/lib/share/core/index.js.map +1 -1
  115. package/lib/share/core/regions.d.ts +43 -0
  116. package/lib/share/core/regions.d.ts.map +1 -0
  117. package/lib/share/core/regions.js +71 -0
  118. package/lib/share/core/regions.js.map +1 -0
  119. package/lib/share/ui/HiddenPartsProvider.d.ts.map +1 -1
  120. package/lib/share/ui/HiddenPartsProvider.js +4 -1
  121. package/lib/share/ui/HiddenPartsProvider.js.map +1 -1
  122. package/lib/share/ui/PagePart.d.ts.map +1 -1
  123. package/lib/share/ui/PagePart.js +10 -1
  124. package/lib/share/ui/PagePart.js.map +1 -1
  125. package/lib/share/ui/ShareButton.d.ts.map +1 -1
  126. package/lib/share/ui/ShareButton.js +3 -4
  127. package/lib/share/ui/ShareButton.js.map +1 -1
  128. package/lib/share/ui/ShareConfigOptions.d.ts +6 -1
  129. package/lib/share/ui/ShareConfigOptions.d.ts.map +1 -1
  130. package/lib/share/ui/ShareConfigOptions.js +6 -13
  131. package/lib/share/ui/ShareConfigOptions.js.map +1 -1
  132. package/lib/share/ui/ShareDialog.d.ts +14 -0
  133. package/lib/share/ui/ShareDialog.d.ts.map +1 -1
  134. package/lib/share/ui/ShareDialog.js +3 -3
  135. package/lib/share/ui/ShareDialog.js.map +1 -1
  136. package/lib/share/ui/ShareDialogContent.d.ts +2 -2
  137. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  138. package/lib/share/ui/ShareDialogContent.js +36 -30
  139. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  140. package/lib/share/ui/ShareLinkBar.d.ts +21 -0
  141. package/lib/share/ui/ShareLinkBar.d.ts.map +1 -0
  142. package/lib/share/ui/ShareLinkBar.js +32 -0
  143. package/lib/share/ui/ShareLinkBar.js.map +1 -0
  144. package/lib/share/ui/SharePanes.d.ts +17 -0
  145. package/lib/share/ui/SharePanes.d.ts.map +1 -0
  146. package/lib/share/ui/SharePanes.js +27 -0
  147. package/lib/share/ui/SharePanes.js.map +1 -0
  148. package/lib/share/ui/SharePartOptions.d.ts +9 -3
  149. package/lib/share/ui/SharePartOptions.d.ts.map +1 -1
  150. package/lib/share/ui/SharePartOptions.js +12 -16
  151. package/lib/share/ui/SharePartOptions.js.map +1 -1
  152. package/lib/share/ui/SharePartRow.d.ts +37 -0
  153. package/lib/share/ui/SharePartRow.d.ts.map +1 -0
  154. package/lib/share/ui/SharePartRow.js +25 -0
  155. package/lib/share/ui/SharePartRow.js.map +1 -0
  156. package/lib/share/ui/SharePresetTabs.d.ts +10 -7
  157. package/lib/share/ui/SharePresetTabs.d.ts.map +1 -1
  158. package/lib/share/ui/SharePresetTabs.js +17 -17
  159. package/lib/share/ui/SharePresetTabs.js.map +1 -1
  160. package/lib/share/ui/SharePreview.d.ts +32 -0
  161. package/lib/share/ui/SharePreview.d.ts.map +1 -0
  162. package/lib/share/ui/SharePreview.js +104 -0
  163. package/lib/share/ui/SharePreview.js.map +1 -0
  164. package/lib/share/ui/SharePreviewDevices.d.ts +20 -0
  165. package/lib/share/ui/SharePreviewDevices.d.ts.map +1 -0
  166. package/lib/share/ui/SharePreviewDevices.js +40 -0
  167. package/lib/share/ui/SharePreviewDevices.js.map +1 -0
  168. package/lib/share/ui/index.d.ts +1 -0
  169. package/lib/share/ui/index.d.ts.map +1 -1
  170. package/lib/share/ui/index.js.map +1 -1
  171. package/lib/share/ui/shareMarking.d.ts +14 -0
  172. package/lib/share/ui/shareMarking.d.ts.map +1 -0
  173. package/lib/share/ui/shareMarking.js +17 -0
  174. package/lib/share/ui/shareMarking.js.map +1 -0
  175. package/lib/share/ui/useShareRegionReports.d.ts +12 -0
  176. package/lib/share/ui/useShareRegionReports.d.ts.map +1 -0
  177. package/lib/share/ui/useShareRegionReports.js +73 -0
  178. package/lib/share/ui/useShareRegionReports.js.map +1 -0
  179. package/lib/shared/ui/ActionTooltip.d.ts +42 -0
  180. package/lib/shared/ui/ActionTooltip.d.ts.map +1 -0
  181. package/lib/shared/ui/ActionTooltip.js +21 -0
  182. package/lib/shared/ui/ActionTooltip.js.map +1 -0
  183. package/lib/shared/ui/index.d.ts +4 -0
  184. package/lib/shared/ui/index.d.ts.map +1 -1
  185. package/lib/shared/ui/index.js +2 -0
  186. package/lib/shared/ui/index.js.map +1 -1
  187. package/lib/shared/ui/useActionTooltip.d.ts +48 -0
  188. package/lib/shared/ui/useActionTooltip.d.ts.map +1 -0
  189. package/lib/shared/ui/useActionTooltip.js +78 -0
  190. package/lib/shared/ui/useActionTooltip.js.map +1 -0
  191. package/lib/structure/core/index.d.ts +2 -0
  192. package/lib/structure/core/index.d.ts.map +1 -1
  193. package/lib/structure/core/index.js +1 -0
  194. package/lib/structure/core/index.js.map +1 -1
  195. package/lib/structure/core/sourceMolecule.d.ts +18 -0
  196. package/lib/structure/core/sourceMolecule.d.ts.map +1 -0
  197. package/lib/structure/core/sourceMolecule.js +26 -0
  198. package/lib/structure/core/sourceMolecule.js.map +1 -0
  199. package/lib/structure/core/structureNotations.d.ts +57 -0
  200. package/lib/structure/core/structureNotations.d.ts.map +1 -0
  201. package/lib/structure/core/structureNotations.js +74 -0
  202. package/lib/structure/core/structureNotations.js.map +1 -0
  203. package/lib/structure/core/structurePicture.d.ts +49 -0
  204. package/lib/structure/core/structurePicture.d.ts.map +1 -0
  205. package/lib/structure/core/structurePicture.js +83 -0
  206. package/lib/structure/core/structurePicture.js.map +1 -0
  207. package/lib/structure/ui/EditorExportButton.d.ts +23 -0
  208. package/lib/structure/ui/EditorExportButton.d.ts.map +1 -0
  209. package/lib/structure/ui/EditorExportButton.js +23 -0
  210. package/lib/structure/ui/EditorExportButton.js.map +1 -0
  211. package/lib/structure/ui/EditorHelpButton.d.ts +5 -2
  212. package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -1
  213. package/lib/structure/ui/EditorHelpButton.js +8 -9
  214. package/lib/structure/ui/EditorHelpButton.js.map +1 -1
  215. package/lib/structure/ui/StructureEditor.d.ts +8 -0
  216. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  217. package/lib/structure/ui/StructureEditor.js +26 -3
  218. package/lib/structure/ui/StructureEditor.js.map +1 -1
  219. package/lib/structure/ui/StructureExportDialog.d.ts +34 -0
  220. package/lib/structure/ui/StructureExportDialog.d.ts.map +1 -0
  221. package/lib/structure/ui/StructureExportDialog.js +43 -0
  222. package/lib/structure/ui/StructureExportDialog.js.map +1 -0
  223. package/lib/structure/ui/StructureExportPanel.d.ts +37 -0
  224. package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -0
  225. package/lib/structure/ui/StructureExportPanel.js +75 -0
  226. package/lib/structure/ui/StructureExportPanel.js.map +1 -0
  227. package/lib/structure/ui/StructureNotationRow.d.ts +22 -0
  228. package/lib/structure/ui/StructureNotationRow.d.ts.map +1 -0
  229. package/lib/structure/ui/StructureNotationRow.js +34 -0
  230. package/lib/structure/ui/StructureNotationRow.js.map +1 -0
  231. package/lib/structure/ui/StructurePicturePane.d.ts +23 -0
  232. package/lib/structure/ui/StructurePicturePane.d.ts.map +1 -0
  233. package/lib/structure/ui/StructurePicturePane.js +113 -0
  234. package/lib/structure/ui/StructurePicturePane.js.map +1 -0
  235. package/lib/structure/ui/StructureSvg.d.ts.map +1 -1
  236. package/lib/structure/ui/StructureSvg.js +2 -18
  237. package/lib/structure/ui/StructureSvg.js.map +1 -1
  238. package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -1
  239. package/lib/structure/ui/ToolbarTooltip.js +1 -2
  240. package/lib/structure/ui/ToolbarTooltip.js.map +1 -1
  241. package/lib/structure/ui/index.d.ts +7 -3
  242. package/lib/structure/ui/index.d.ts.map +1 -1
  243. package/lib/structure/ui/index.js +5 -3
  244. package/lib/structure/ui/index.js.map +1 -1
  245. package/lib/structure/ui/structureNotationText.d.ts +23 -0
  246. package/lib/structure/ui/structureNotationText.d.ts.map +1 -0
  247. package/lib/structure/ui/structureNotationText.js +42 -0
  248. package/lib/structure/ui/structureNotationText.js.map +1 -0
  249. package/lib/structure/ui/useEditorStructure.d.ts +37 -0
  250. package/lib/structure/ui/useEditorStructure.d.ts.map +1 -0
  251. package/lib/structure/ui/useEditorStructure.js +70 -0
  252. package/lib/structure/ui/useEditorStructure.js.map +1 -0
  253. package/package.json +1 -1
  254. package/src/chart/ui/Axes.tsx +53 -5
  255. package/src/chrome/ui/NavMenuButton.tsx +61 -18
  256. package/src/chrome/ui/SiteHeader.tsx +37 -1
  257. package/src/chrome/ui/navItem.ts +7 -0
  258. package/src/citation/ui/entries.tsx +10 -2
  259. package/src/clipboard/ui/ClickToCopy.tsx +47 -13
  260. package/src/clipboard/ui/CopyButton.tsx +4 -2
  261. package/src/clipboard/ui/CopyableValue.tsx +88 -13
  262. package/src/clipboard/ui/index.ts +5 -1
  263. package/src/confirm/ui/ConfirmButton.tsx +2 -0
  264. package/src/delimited/ui/TableDataButton.tsx +2 -1
  265. package/src/download/core/figureSvg.ts +42 -10
  266. package/src/help/ui/HelpToolbarButton.tsx +2 -1
  267. package/src/locales/de.json +39 -4
  268. package/src/locales/en.json +39 -4
  269. package/src/locales/es.json +39 -4
  270. package/src/locales/fr.json +39 -4
  271. package/src/locales/it.json +39 -4
  272. package/src/number/ui/NumberInput.tsx +1 -1
  273. package/src/overlay/ui/OverlayIconButton.tsx +1 -0
  274. package/src/panel/ui/ClearButton.tsx +1 -0
  275. package/src/pedagogy/core/inlineMarks.ts +15 -7
  276. package/src/pedagogy/ui/GlossaryDefinition.tsx +8 -18
  277. package/src/pedagogy/ui/GlossaryIndex.tsx +0 -1
  278. package/src/pedagogy/ui/GlossaryProvider.tsx +10 -2
  279. package/src/pedagogy/ui/GlossaryText.tsx +10 -2
  280. package/src/pedagogy/ui/InlineText.tsx +21 -4
  281. package/src/pedagogy/ui/ReferenceSectionBlock.tsx +8 -2
  282. package/src/pedagogy/ui/SyntaxTooltip.tsx +11 -9
  283. package/src/pedagogy/ui/TutorialStepStrip.tsx +50 -18
  284. package/src/pedagogy/ui/glossaryContext.ts +7 -0
  285. package/src/pedagogy/ui/index.ts +1 -1
  286. package/src/pedagogy/ui/inlineSegments.tsx +7 -0
  287. package/src/pedagogy/ui/pedagogyStyle.ts +12 -24
  288. package/src/seo/core/plainDescription.ts +4 -2
  289. package/src/share/core/devices.ts +80 -0
  290. package/src/share/core/index.ts +14 -0
  291. package/src/share/core/regions.ts +87 -0
  292. package/src/share/ui/HiddenPartsProvider.tsx +6 -1
  293. package/src/share/ui/PagePart.tsx +12 -1
  294. package/src/share/ui/ShareButton.tsx +3 -6
  295. package/src/share/ui/ShareConfigOptions.tsx +25 -16
  296. package/src/share/ui/ShareDialog.tsx +17 -3
  297. package/src/share/ui/ShareDialogContent.tsx +80 -77
  298. package/src/share/ui/ShareLinkBar.tsx +70 -0
  299. package/src/share/ui/SharePanes.tsx +62 -0
  300. package/src/share/ui/SharePartOptions.tsx +25 -26
  301. package/src/share/ui/SharePartRow.tsx +78 -0
  302. package/src/share/ui/SharePresetTabs.tsx +36 -43
  303. package/src/share/ui/SharePreview.tsx +204 -0
  304. package/src/share/ui/SharePreviewDevices.tsx +70 -0
  305. package/src/share/ui/index.ts +1 -0
  306. package/src/share/ui/shareMarking.ts +18 -0
  307. package/src/share/ui/useShareRegionReports.ts +84 -0
  308. package/src/shared/ui/ActionTooltip.tsx +70 -0
  309. package/src/shared/ui/index.ts +7 -0
  310. package/src/shared/ui/useActionTooltip.ts +103 -0
  311. package/src/structure/core/index.ts +5 -0
  312. package/src/structure/core/sourceMolecule.ts +26 -0
  313. package/src/structure/core/structureNotations.ts +125 -0
  314. package/src/structure/core/structurePicture.ts +119 -0
  315. package/src/structure/ui/EditorExportButton.tsx +62 -0
  316. package/src/structure/ui/EditorHelpButton.tsx +26 -30
  317. package/src/structure/ui/StructureEditor.tsx +51 -5
  318. package/src/structure/ui/StructureExportDialog.tsx +90 -0
  319. package/src/structure/ui/StructureExportPanel.tsx +156 -0
  320. package/src/structure/ui/StructureNotationRow.tsx +74 -0
  321. package/src/structure/ui/StructurePicturePane.tsx +208 -0
  322. package/src/structure/ui/StructureSvg.tsx +3 -12
  323. package/src/structure/ui/ToolbarTooltip.tsx +1 -1
  324. package/src/structure/ui/index.ts +7 -3
  325. package/src/structure/ui/structureNotationText.ts +56 -0
  326. package/src/structure/ui/useEditorStructure.ts +119 -0
  327. package/styles/chrome.css +414 -14
@@ -1,15 +1,9 @@
1
1
  import { Tab, Tabs } from '@blueprintjs/core';
2
- import type { CSSProperties, ReactElement, ReactNode } from 'react';
2
+ import type { ReactElement } from 'react';
3
3
 
4
4
  import { useChromeT } from '../../i18n/ui/useT.ts';
5
5
  import type { ShareParamCodecs, SharePreset } from '../core/index.ts';
6
6
 
7
- const SECTION_STYLE: CSSProperties = { marginBottom: 18 };
8
- const DESCRIPTION_STYLE: CSSProperties = {
9
- margin: 0,
10
- color: 'var(--text-muted, #5b6875)',
11
- };
12
-
13
7
  const CUSTOM_TAB = 'custom';
14
8
  const PRESET_TAB_PREFIX = 'preset-';
15
9
 
@@ -22,51 +16,50 @@ export interface SharePresetTabsProps<
22
16
  selected: string | null;
23
17
  /** Called with the key of the preset picked, `null` for the custom tab. */
24
18
  onSelect: (key: string | null) => void;
25
- /** What the custom tab holds: the boxes of the draft. */
26
- custom: ReactNode;
27
19
  }
28
20
 
29
21
  /**
30
- * One tab per preset, then a last Custom tab holding the boxes, which start
31
- * from whatever the preset before it set.
32
- * @param props - The presets, the open one, and the custom panel.
33
- * @returns The tab strip and the open panel.
22
+ * One tab per ready-made link, then a last Custom tab for building any other
23
+ * one.
24
+ *
25
+ * The strip runs the width of the dialog rather than sitting in the column of
26
+ * options, because the choice governs the preview as much as the switches: in
27
+ * a third of the width, four tabs wrap onto a second row and the last of them
28
+ * — the one holding every switch — reads as an afterthought.
29
+ * @param props - The presets, and which one is open.
30
+ * @returns The tab strip.
34
31
  */
35
32
  export function SharePresetTabs<
36
33
  Codecs extends ShareParamCodecs = Record<string, never>,
37
34
  >(props: SharePresetTabsProps<Codecs>): ReactElement {
38
- const { presets, selected, onSelect, custom } = props;
35
+ const { presets, selected, onSelect } = props;
39
36
  const t = useChromeT();
40
37
 
41
38
  return (
42
- <section className="share-section" style={SECTION_STYLE}>
43
- <Tabs
44
- id="share-presets"
45
- className="share-presets"
46
- animate={false}
47
- renderActiveTabPanelOnly
48
- selectedTabId={
49
- selected === null ? CUSTOM_TAB : `${PRESET_TAB_PREFIX}${selected}`
50
- }
51
- onChange={(tabId) => {
52
- const id = String(tabId);
53
- onSelect(
54
- id.startsWith(PRESET_TAB_PREFIX)
55
- ? id.slice(PRESET_TAB_PREFIX.length)
56
- : null,
57
- );
58
- }}
59
- >
60
- {presets.map((preset) => (
61
- <Tab
62
- key={preset.key}
63
- id={`${PRESET_TAB_PREFIX}${preset.key}`}
64
- title={preset.label}
65
- panel={<p style={DESCRIPTION_STYLE}>{preset.description}</p>}
66
- />
67
- ))}
68
- <Tab id={CUSTOM_TAB} title={t('share.custom')} panel={<>{custom}</>} />
69
- </Tabs>
70
- </section>
39
+ <Tabs
40
+ id="share-presets"
41
+ className="share-presets"
42
+ animate={false}
43
+ selectedTabId={
44
+ selected === null ? CUSTOM_TAB : `${PRESET_TAB_PREFIX}${selected}`
45
+ }
46
+ onChange={(tabId) => {
47
+ const id = String(tabId);
48
+ onSelect(
49
+ id.startsWith(PRESET_TAB_PREFIX)
50
+ ? id.slice(PRESET_TAB_PREFIX.length)
51
+ : null,
52
+ );
53
+ }}
54
+ >
55
+ {presets.map((preset) => (
56
+ <Tab
57
+ key={preset.key}
58
+ id={`${PRESET_TAB_PREFIX}${preset.key}`}
59
+ title={preset.label}
60
+ />
61
+ ))}
62
+ <Tab id={CUSTOM_TAB} title={t('share.custom')} />
63
+ </Tabs>
71
64
  );
72
65
  }
@@ -0,0 +1,204 @@
1
+ import { H6, Icon } from '@blueprintjs/core';
2
+ import type { CSSProperties, ReactElement } from 'react';
3
+ import { useEffect, useRef, useState } from 'react';
4
+
5
+ import { formatInteger } from '../../format/core/index.ts';
6
+ import type { HelpContent } from '../../help/ui/index.ts';
7
+ import { HelpIcon } from '../../help/ui/index.ts';
8
+ import { useContainerSize } from '../../hooks/ui/useContainerSize.ts';
9
+ import { useDebouncedValue } from '../../hooks/ui/useDebouncedValue.ts';
10
+ import { useChromeT } from '../../i18n/ui/useT.ts';
11
+ import type { SharePreviewDeviceKey, ShareRegion } from '../core/index.ts';
12
+ import {
13
+ SHARE_REGIONS_REQUEST,
14
+ readShareRegions,
15
+ sharePreviewAddress,
16
+ sharePreviewScale,
17
+ sharePreviewSize,
18
+ } from '../core/index.ts';
19
+
20
+ import { SharePreviewDevices } from './SharePreviewDevices.tsx';
21
+
22
+ /** Room the window's own bar takes, which the page is not scaled into. */
23
+ const CHROME_HEIGHT = 26;
24
+
25
+ export interface SharePreviewProps {
26
+ /** The address the frame loads — the link as it stands. */
27
+ url: string;
28
+ /** What a screen reader announces the frame as. */
29
+ title: string;
30
+ /**
31
+ * The part the reader is pointing at in the list, drawn on the page so the
32
+ * name in the list and the thing on the page are the same thing.
33
+ * @default null — nothing is pointed at
34
+ */
35
+ highlight?: { part: string; label: string } | null;
36
+ }
37
+
38
+ /**
39
+ * The page as the link hands it out, live, beside the options that write it.
40
+ *
41
+ * It is drawn as a window of its own — named above it, with the address it
42
+ * loads on its own bar — because a page scaled into a pane otherwise reads as
43
+ * the site itself, and a reader who takes the preview for the tool ticks a box
44
+ * in it and believes the link now carries it.
45
+ *
46
+ * The frame is laid out at the screen the reader picks — their own window, a
47
+ * phone, a laptop or a large display — and scaled into whatever room the pane
48
+ * has, so a course tile can be checked at the size a student will meet it.
49
+ * @param props - The address and the name of the frame.
50
+ * @returns The preview pane.
51
+ */
52
+ export function SharePreview(props: SharePreviewProps): ReactElement {
53
+ const { url, title, highlight = null } = props;
54
+ const t = useChromeT();
55
+ const stage = useRef<HTMLDivElement>(null);
56
+ const frame = useRef<HTMLIFrameElement>(null);
57
+ const pane = useContainerSize(stage);
58
+ const [device, setDevice] = useState<SharePreviewDeviceKey>('window');
59
+ const [regions, setRegions] = useState<readonly ShareRegion[]>(NO_REGIONS);
60
+ const mark = useRef<HTMLDivElement>(null);
61
+ // Every box ticked reloads the frame; a number typed into a tool's own field
62
+ // would otherwise reload it once per keystroke.
63
+ const src = useDebouncedValue(url, 400);
64
+
65
+ const windowSize = {
66
+ width: globalThis.innerWidth,
67
+ height: globalThis.innerHeight,
68
+ };
69
+ const page = sharePreviewSize(device, windowSize);
70
+ const scale = sharePreviewScale(
71
+ { width: pane.width, height: pane.height - CHROME_HEIGHT },
72
+ page,
73
+ );
74
+ const region =
75
+ highlight === null ? undefined : regionOf(regions, highlight.part);
76
+ const help: HelpContent = {
77
+ title: t('share.preview'),
78
+ body: [t('share.previewWhat'), t('share.previewLive')],
79
+ };
80
+
81
+ useEffect(() => {
82
+ function onMessage(event: MessageEvent): void {
83
+ if (event.source !== frame.current?.contentWindow) return;
84
+ const reported = readShareRegions(event.data);
85
+ if (reported !== null) setRegions(reported);
86
+ }
87
+
88
+ globalThis.addEventListener('message', onMessage);
89
+ return () => {
90
+ globalThis.removeEventListener('message', onMessage);
91
+ };
92
+ }, []);
93
+
94
+ // A region further down the page than the stage shows is brought into view,
95
+ // or pointing at a row would mark something the reader cannot see.
96
+ useEffect(() => {
97
+ mark.current?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
98
+ }, [highlight?.part]);
99
+
100
+ return (
101
+ <section className="share-preview" aria-label={t('share.preview')}>
102
+ <header className="share-preview__header">
103
+ <H6 className="share-preview__label">
104
+ <Icon icon="eye-open" size={13} />
105
+ {t('share.preview')}
106
+ <HelpIcon content={help} />
107
+ </H6>
108
+ <SharePreviewDevices
109
+ value={device}
110
+ onChange={setDevice}
111
+ windowSize={windowSize}
112
+ />
113
+ </header>
114
+ <div className="share-preview__stage" ref={stage}>
115
+ <div
116
+ className="share-preview__device"
117
+ style={{ width: page.width * scale }}
118
+ >
119
+ <div className="share-preview__chrome">
120
+ <Icon icon="eye-open" size={11} />
121
+ <span className="share-preview__address">
122
+ {sharePreviewAddress(url)}
123
+ </span>
124
+ <span className="share-preview__size">
125
+ {sizeNote(page.width, page.height, scale)}
126
+ </span>
127
+ </div>
128
+ <div
129
+ className="share-preview__page"
130
+ style={{ height: page.height * scale }}
131
+ >
132
+ <iframe
133
+ ref={frame}
134
+ className="share-preview__frame"
135
+ src={src}
136
+ title={title}
137
+ style={frameStyle(page.width, page.height, scale)}
138
+ onLoad={() => {
139
+ // What the page before it reported is where its own parts
140
+ // were, so it is dropped rather than drawn over this one.
141
+ setRegions(NO_REGIONS);
142
+ frame.current?.contentWindow?.postMessage(
143
+ { type: SHARE_REGIONS_REQUEST },
144
+ '*',
145
+ );
146
+ }}
147
+ />
148
+ {region === undefined || highlight === null ? null : (
149
+ <div
150
+ ref={mark}
151
+ className="share-preview__region"
152
+ style={regionStyle(region, scale)}
153
+ >
154
+ <span className="share-preview__region-name">
155
+ {highlight.label}
156
+ </span>
157
+ </div>
158
+ )}
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </section>
163
+ );
164
+ }
165
+
166
+ const NO_REGIONS: readonly ShareRegion[] = [];
167
+
168
+ function regionOf(
169
+ regions: readonly ShareRegion[],
170
+ part: string,
171
+ ): ShareRegion | undefined {
172
+ for (const region of regions) {
173
+ if (region.part === part) return region;
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ function regionStyle(region: ShareRegion, scale: number): CSSProperties {
179
+ return {
180
+ left: region.x * scale,
181
+ top: region.y * scale,
182
+ width: region.width * scale,
183
+ height: region.height * scale,
184
+ };
185
+ }
186
+
187
+ function frameStyle(
188
+ width: number,
189
+ height: number,
190
+ scale: number,
191
+ ): CSSProperties {
192
+ return {
193
+ width,
194
+ height,
195
+ border: 0,
196
+ transform: `scale(${scale})`,
197
+ transformOrigin: 'top left',
198
+ };
199
+ }
200
+
201
+ function sizeNote(width: number, height: number, scale: number): string {
202
+ const size = `${formatInteger(width)} × ${formatInteger(height)}`;
203
+ return scale === 1 ? size : `${size} · ${Math.round(scale * 100)}%`;
204
+ }
@@ -0,0 +1,70 @@
1
+ import type { IconName } from '@blueprintjs/core';
2
+ import { Button, ButtonGroup } from '@blueprintjs/core';
3
+ import type { ReactElement } from 'react';
4
+
5
+ import { useChromeT } from '../../i18n/ui/useT.ts';
6
+ import type { SharePreviewDeviceKey, SharePreviewSize } from '../core/index.ts';
7
+ import { SHARE_PREVIEW_DEVICES, sharePreviewSize } from '../core/index.ts';
8
+
9
+ /**
10
+ * The glyph each screen is picked by: a window of one's own, a phone, a
11
+ * monitor, a projected display. The row reads as four screen sizes at a
12
+ * glance, which four words never did.
13
+ */
14
+ const DEVICE_ICONS: Record<SharePreviewDeviceKey, IconName> = {
15
+ window: 'application',
16
+ mobile: 'mobile-phone',
17
+ laptop: 'desktop',
18
+ hd: 'presentation',
19
+ };
20
+
21
+ export interface SharePreviewDevicesProps {
22
+ /** The screen the preview is laid out at. */
23
+ value: SharePreviewDeviceKey;
24
+ /** Called with the screen picked. */
25
+ onChange: (value: SharePreviewDeviceKey) => void;
26
+ /** The reader's own window, which the first screen is measured from. */
27
+ windowSize: Partial<SharePreviewSize>;
28
+ }
29
+
30
+ /**
31
+ * The screens a link can be looked at on, as one row of glyphs.
32
+ *
33
+ * Each carries its name and its size, so a pointer resting on one says what it
34
+ * lays the page out at rather than leaving the reader to guess the icon.
35
+ * @param props - See {@link SharePreviewDevicesProps}.
36
+ * @returns The row of screen buttons.
37
+ */
38
+ export function SharePreviewDevices(
39
+ props: SharePreviewDevicesProps,
40
+ ): ReactElement {
41
+ const { value, onChange, windowSize } = props;
42
+ const t = useChromeT();
43
+
44
+ return (
45
+ <ButtonGroup className="share-preview__devices" variant="minimal">
46
+ {SHARE_PREVIEW_DEVICES.map((key) => {
47
+ const size = sharePreviewSize(key, windowSize);
48
+ const name = t(`share.device.${key}`);
49
+ const label = t('share.deviceSize', {
50
+ name,
51
+ width: size.width,
52
+ height: size.height,
53
+ });
54
+
55
+ return (
56
+ <Button
57
+ key={key}
58
+ icon={DEVICE_ICONS[key]}
59
+ active={key === value}
60
+ title={label}
61
+ aria-label={label}
62
+ onClick={() => {
63
+ onChange(key);
64
+ }}
65
+ />
66
+ );
67
+ })}
68
+ </ButtonGroup>
69
+ );
70
+ }
@@ -6,4 +6,5 @@ export { PagePart } from './PagePart.tsx';
6
6
  export type { ShareButtonProps, ShareButtonVariant } from './ShareButton.tsx';
7
7
  export { ShareButton } from './ShareButton.tsx';
8
8
  export type { ShareDialogProps } from './ShareDialog.tsx';
9
+ export type { SharePartDescriptions } from './SharePartRow.tsx';
9
10
  export { ShareDialog } from './ShareDialog.tsx';
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ /**
4
+ * Whether the page is being looked at through a share dialog's preview, and so
5
+ * marks each of its parts in the DOM for the dialog to find.
6
+ *
7
+ * A page nobody is framing marks nothing: the marker is a wrapper, and a
8
+ * wrapper changes what a `>` selector in a site's own stylesheet matches.
9
+ */
10
+ export const ShareMarkingContext = createContext(false);
11
+
12
+ /**
13
+ * Whether this page marks its parts for a share dialog looking at it.
14
+ * @returns Whether the markers are rendered.
15
+ */
16
+ export function useShareMarking(): boolean {
17
+ return useContext(ShareMarkingContext);
18
+ }
@@ -0,0 +1,84 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import type { ShareRegion } from '../core/index.ts';
4
+ import { SHARE_REGIONS_MESSAGE, isShareRegionsRequest } from '../core/index.ts';
5
+
6
+ /**
7
+ * Report where each part of this page sits, while a share dialog is looking at
8
+ * it through its preview.
9
+ *
10
+ * Nothing happens on a page nobody frames, and nothing happens in a frame
11
+ * whose host never asks — a page embedded in a course is framed too, and owes
12
+ * its host nothing.
13
+ * @param hidden - The parts the link switches off, so the boxes are measured again when they change.
14
+ * @returns Whether the page should mark its parts for the dialog to find.
15
+ */
16
+ export function useShareRegionReports(hidden: ReadonlySet<string>): boolean {
17
+ const [marking, setMarking] = useState(false);
18
+
19
+ useEffect(() => {
20
+ const host = globalThis.parent;
21
+ if (host === globalThis.self) return undefined;
22
+
23
+ function onMessage(event: MessageEvent): void {
24
+ if (isShareRegionsRequest(event.data)) setMarking(true);
25
+ }
26
+
27
+ globalThis.addEventListener('message', onMessage);
28
+ return () => {
29
+ globalThis.removeEventListener('message', onMessage);
30
+ };
31
+ }, []);
32
+
33
+ useEffect(() => {
34
+ if (!marking) return undefined;
35
+ const host = globalThis.parent;
36
+
37
+ function report(): void {
38
+ host.postMessage(
39
+ { type: SHARE_REGIONS_MESSAGE, regions: measureRegions() },
40
+ '*',
41
+ );
42
+ }
43
+
44
+ // A chart, a web font or a structure drawn after mount all move the boxes,
45
+ // so the page is measured again whenever anything in it resizes rather
46
+ // than once, on a deadline it would lose.
47
+ const observer = new ResizeObserver(report);
48
+ observer.observe(document.documentElement);
49
+ globalThis.addEventListener('resize', report);
50
+ report();
51
+
52
+ return () => {
53
+ observer.disconnect();
54
+ globalThis.removeEventListener('resize', report);
55
+ };
56
+ }, [marking, hidden]);
57
+
58
+ return marking;
59
+ }
60
+
61
+ function measureRegions(): ShareRegion[] {
62
+ const markers = document.querySelectorAll<HTMLElement>('[data-share-part]');
63
+ const range = document.createRange();
64
+ const regions: ShareRegion[] = [];
65
+
66
+ for (const marker of markers) {
67
+ const { sharePart: part } = marker.dataset;
68
+ if (part === undefined) continue;
69
+ // The marker lays out as `display: contents`, so it has no box of its own.
70
+ // A range over what it holds has the box its contents occupy.
71
+ range.selectNodeContents(marker);
72
+ const box = range.getBoundingClientRect();
73
+ if (box.width === 0 || box.height === 0) continue;
74
+ regions.push({
75
+ part,
76
+ x: box.x + globalThis.scrollX,
77
+ y: box.y + globalThis.scrollY,
78
+ width: box.width,
79
+ height: box.height,
80
+ });
81
+ }
82
+
83
+ return regions;
84
+ }
@@ -0,0 +1,70 @@
1
+ import type { Placement } from '@blueprintjs/core';
2
+ import { Tooltip } from '@blueprintjs/core';
3
+ import type { CSSProperties, ReactElement } from 'react';
4
+
5
+ import { useActionTooltip } from './useActionTooltip.ts';
6
+
7
+ /** What {@link ActionTooltip} explains, and what it steps aside for. */
8
+ export interface ActionTooltipProps {
9
+ /** What the control is for, read on hover. */
10
+ content: string | ReactElement;
11
+ /** The control. */
12
+ children: ReactElement;
13
+ /**
14
+ * Whether what the control opens is on screen. While it is, the tooltip
15
+ * stays shut.
16
+ * @default false
17
+ */
18
+ opened?: boolean;
19
+ /**
20
+ * Which side of the control the tooltip is drawn on.
21
+ * @default 'bottom'
22
+ */
23
+ placement?: Placement;
24
+ /**
25
+ * Class the popover carries, for a card that is not the chrome's own.
26
+ * @default 'help-tooltip'
27
+ */
28
+ popoverClassName?: string;
29
+ /**
30
+ * How long the pointer rests on the control before the card is drawn, in
31
+ * milliseconds.
32
+ * @default 250
33
+ */
34
+ delay?: number;
35
+ }
36
+
37
+ /**
38
+ * A tooltip on a control that opens something — a dialog, a popover, a menu.
39
+ *
40
+ * It is the wrapper form of {@link useActionTooltip}, which is where the whole
41
+ * argument is written; a tooltip whose target cannot take a wrapping element —
42
+ * a menu item, a table cell — uses the hook directly.
43
+ * @param props - See {@link ActionTooltipProps}.
44
+ * @returns The control, with its tooltip.
45
+ */
46
+ export function ActionTooltip(props: ActionTooltipProps): ReactElement {
47
+ const { content, children, opened = false } = props;
48
+ const { placement = 'bottom', popoverClassName = 'help-tooltip' } = props;
49
+ const { delay } = props;
50
+ const { isOpen, target } = useActionTooltip(opened, delay);
51
+
52
+ return (
53
+ <Tooltip
54
+ content={content}
55
+ placement={placement}
56
+ popoverClassName={popoverClassName}
57
+ isOpen={isOpen}
58
+ >
59
+ {/* The pointer is read here rather than on the control, so the control
60
+ keeps whatever handlers it was given. A box of its own, never
61
+ `display: contents`, which measures as an empty rectangle and leaves
62
+ the card positioned in the corner of the window. */}
63
+ <span style={WRAP_STYLE} {...target}>
64
+ {children}
65
+ </span>
66
+ </Tooltip>
67
+ );
68
+ }
69
+
70
+ const WRAP_STYLE = { display: 'inline-flex' } as const satisfies CSSProperties;
@@ -1,2 +1,9 @@
1
+ export type { ActionTooltipProps } from './ActionTooltip.tsx';
2
+ export { ActionTooltip } from './ActionTooltip.tsx';
1
3
  export type { HeaderButtonProps, MenuButtonProps } from './MenuButton.tsx';
2
4
  export { MenuButton } from './MenuButton.tsx';
5
+ export type {
6
+ ActionTooltipState,
7
+ ActionTooltipTarget,
8
+ } from './useActionTooltip.ts';
9
+ export { ACTION_TOOLTIP_DELAY, useActionTooltip } from './useActionTooltip.ts';
@@ -0,0 +1,103 @@
1
+ /**
2
+ * When a tooltip on a control that is pressed should be on screen.
3
+ *
4
+ * Such a control is where a hover tooltip goes wrong, in two ways that look
5
+ * like one. What it opens lands over the control, so the `mouseleave` that
6
+ * would close the card is never delivered and the card is left on screen, over
7
+ * the thing it was explaining. And the open is *scheduled*: a press within the
8
+ * hover delay cancels nothing, so the card appears after the press, outlives
9
+ * what was opened, and comes back when it is closed — with the pointer long
10
+ * gone.
11
+ *
12
+ * Blueprint's own hover handling cannot be told either of those, so the state
13
+ * is held here and Blueprint is told only when to draw.
14
+ *
15
+ * The card is a pointer affordance only, and deliberately does not open on
16
+ * focus: what a control opens hands the focus back as it closes, which would
17
+ * put the card on screen with the pointer long gone. Nothing is lost by it —
18
+ * the control carries the same words as its accessible name, which is what a
19
+ * screen reader reads.
20
+ */
21
+
22
+ import { useEffect, useRef, useState } from 'react';
23
+
24
+ /** The handlers the target carries, which decide when the card is drawn. */
25
+ export interface ActionTooltipTarget {
26
+ onPointerEnter: () => void;
27
+ onPointerLeave: () => void;
28
+ onPointerDownCapture: () => void;
29
+ onClickCapture: () => void;
30
+ onBlurCapture: () => void;
31
+ }
32
+
33
+ /** Whether the card is drawn, and what decides it. */
34
+ export interface ActionTooltipState {
35
+ /** What to give the tooltip as `isOpen`. */
36
+ isOpen: boolean;
37
+ /** What to give the target, or the element wrapping it. */
38
+ target: ActionTooltipTarget;
39
+ }
40
+
41
+ /** Long enough that sweeping the pointer across a row of controls opens none. */
42
+ export const ACTION_TOOLTIP_DELAY = 250;
43
+
44
+ /**
45
+ * Hold a tooltip's hover, so a press closes it and nothing reopens it until
46
+ * the pointer leaves the control and comes back.
47
+ * @param opened - Whether what the control opens is on screen; while it is,
48
+ * the card stays shut.
49
+ * @param delay - How long the pointer rests before the card is drawn, in
50
+ * milliseconds. A card heavier than a line of text asks for longer.
51
+ * @returns Whether to draw the card, and the handlers that decide it.
52
+ */
53
+ export function useActionTooltip(
54
+ opened = false,
55
+ delay = ACTION_TOOLTIP_DELAY,
56
+ ): ActionTooltipState {
57
+ const [shown, setShown] = useState(false);
58
+ const waiting = useRef<ReturnType<typeof setTimeout> | null>(null);
59
+ // Set by the press, and cleared only by the pointer actually leaving. It is
60
+ // what the pointer never leaving looks like: the dialog lands over the
61
+ // control, and removing it again makes the browser hand the control a fresh
62
+ // `pointerenter` — with the pointer never having moved. Clearing the latch
63
+ // there, or on the focus the dialog gives back as it closes, is what made
64
+ // the card reappear the moment the dialog was dismissed.
65
+ const pressed = useRef(false);
66
+
67
+ function cancel(): void {
68
+ if (waiting.current !== null) clearTimeout(waiting.current);
69
+ waiting.current = null;
70
+ }
71
+
72
+ function hide(): void {
73
+ cancel();
74
+ setShown(false);
75
+ }
76
+
77
+ function press(): void {
78
+ pressed.current = true;
79
+ hide();
80
+ }
81
+
82
+ useEffect(() => cancel, []);
83
+
84
+ return {
85
+ isOpen: shown && !opened,
86
+ target: {
87
+ onPointerEnter: () => {
88
+ if (pressed.current) return;
89
+ cancel();
90
+ waiting.current = setTimeout(() => setShown(true), delay);
91
+ },
92
+ onPointerLeave: () => {
93
+ pressed.current = false;
94
+ hide();
95
+ },
96
+ onPointerDownCapture: press,
97
+ // Enter and Space fire no pointer event, so a control worked from the
98
+ // keyboard is let go of here instead.
99
+ onClickCapture: press,
100
+ onBlurCapture: hide,
101
+ },
102
+ };
103
+ }
@@ -72,6 +72,11 @@ export type { ReadStructureResult, StructureKind } from './readStructure.ts';
72
72
  export { looksLikeSmarts, readStructure } from './readStructure.ts';
73
73
  export type { StructureError } from './structureError.ts';
74
74
  export { structureError } from './structureError.ts';
75
+ export type {
76
+ StructurePicture,
77
+ StructurePictureOptions,
78
+ } from './structurePicture.ts';
79
+ export { STRUCTURE_BOND_LENGTH, structurePicture } from './structurePicture.ts';
75
80
  export type {
76
81
  StructureSource,
77
82
  StructureSourceInput,