react-cheminfo 0.37.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 (417) hide show
  1. package/README.md +77 -1
  2. package/bin/check-seo.mjs +43 -1
  3. package/lib/chart/ui/Axes.d.ts +19 -0
  4. package/lib/chart/ui/Axes.d.ts.map +1 -1
  5. package/lib/chart/ui/Axes.js +25 -5
  6. package/lib/chart/ui/Axes.js.map +1 -1
  7. package/lib/chrome/ui/NavMenuButton.d.ts.map +1 -1
  8. package/lib/chrome/ui/NavMenuButton.js +41 -11
  9. package/lib/chrome/ui/NavMenuButton.js.map +1 -1
  10. package/lib/chrome/ui/SiteHeader.js +24 -1
  11. package/lib/chrome/ui/SiteHeader.js.map +1 -1
  12. package/lib/chrome/ui/navItem.d.ts +7 -0
  13. package/lib/chrome/ui/navItem.d.ts.map +1 -1
  14. package/lib/chrome/ui/navItem.js.map +1 -1
  15. package/lib/citation/ui/entries.d.ts.map +1 -1
  16. package/lib/citation/ui/entries.js +6 -1
  17. package/lib/citation/ui/entries.js.map +1 -1
  18. package/lib/clipboard/ui/ClickToCopy.d.ts +11 -0
  19. package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
  20. package/lib/clipboard/ui/ClickToCopy.js +28 -10
  21. package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
  22. package/lib/clipboard/ui/CopyButton.d.ts +2 -1
  23. package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
  24. package/lib/clipboard/ui/CopyButton.js +2 -1
  25. package/lib/clipboard/ui/CopyButton.js.map +1 -1
  26. package/lib/clipboard/ui/CopyableValue.d.ts +24 -4
  27. package/lib/clipboard/ui/CopyableValue.d.ts.map +1 -1
  28. package/lib/clipboard/ui/CopyableValue.js +35 -3
  29. package/lib/clipboard/ui/CopyableValue.js.map +1 -1
  30. package/lib/clipboard/ui/index.d.ts +1 -1
  31. package/lib/clipboard/ui/index.d.ts.map +1 -1
  32. package/lib/clipboard/ui/index.js.map +1 -1
  33. package/lib/confirm/ui/ConfirmButton.d.ts.map +1 -1
  34. package/lib/confirm/ui/ConfirmButton.js +2 -1
  35. package/lib/confirm/ui/ConfirmButton.js.map +1 -1
  36. package/lib/core.d.ts +1 -0
  37. package/lib/core.d.ts.map +1 -1
  38. package/lib/core.js +1 -0
  39. package/lib/core.js.map +1 -1
  40. package/lib/delimited/ui/TableDataButton.d.ts.map +1 -1
  41. package/lib/delimited/ui/TableDataButton.js +2 -1
  42. package/lib/delimited/ui/TableDataButton.js.map +1 -1
  43. package/lib/download/core/figureSvg.d.ts.map +1 -1
  44. package/lib/download/core/figureSvg.js +37 -12
  45. package/lib/download/core/figureSvg.js.map +1 -1
  46. package/lib/help/ui/HelpToolbarButton.d.ts.map +1 -1
  47. package/lib/help/ui/HelpToolbarButton.js +2 -1
  48. package/lib/help/ui/HelpToolbarButton.js.map +1 -1
  49. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  50. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  51. package/lib/locales/de.json +48 -5
  52. package/lib/locales/en.json +48 -5
  53. package/lib/locales/es.json +48 -5
  54. package/lib/locales/fr.json +48 -5
  55. package/lib/locales/it.json +48 -5
  56. package/lib/number/core/index.d.ts +1 -1
  57. package/lib/number/core/index.d.ts.map +1 -1
  58. package/lib/number/core/index.js +1 -1
  59. package/lib/number/core/index.js.map +1 -1
  60. package/lib/number/core/numberText.d.ts +13 -0
  61. package/lib/number/core/numberText.d.ts.map +1 -1
  62. package/lib/number/core/numberText.js +20 -0
  63. package/lib/number/core/numberText.js.map +1 -1
  64. package/lib/number/ui/NumberInput.js +1 -1
  65. package/lib/number/ui/NumberInput.js.map +1 -1
  66. package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
  67. package/lib/overlay/ui/OverlayIconButton.js +1 -1
  68. package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
  69. package/lib/panel/ui/ClearButton.d.ts.map +1 -1
  70. package/lib/panel/ui/ClearButton.js +1 -1
  71. package/lib/panel/ui/ClearButton.js.map +1 -1
  72. package/lib/pedagogy/core/inlineMarks.d.ts +9 -5
  73. package/lib/pedagogy/core/inlineMarks.d.ts.map +1 -1
  74. package/lib/pedagogy/core/inlineMarks.js +14 -6
  75. package/lib/pedagogy/core/inlineMarks.js.map +1 -1
  76. package/lib/pedagogy/ui/GlossaryDefinition.d.ts +4 -10
  77. package/lib/pedagogy/ui/GlossaryDefinition.d.ts.map +1 -1
  78. package/lib/pedagogy/ui/GlossaryDefinition.js +7 -7
  79. package/lib/pedagogy/ui/GlossaryDefinition.js.map +1 -1
  80. package/lib/pedagogy/ui/GlossaryIndex.d.ts.map +1 -1
  81. package/lib/pedagogy/ui/GlossaryIndex.js +1 -1
  82. package/lib/pedagogy/ui/GlossaryIndex.js.map +1 -1
  83. package/lib/pedagogy/ui/GlossaryProvider.d.ts +7 -0
  84. package/lib/pedagogy/ui/GlossaryProvider.d.ts.map +1 -1
  85. package/lib/pedagogy/ui/GlossaryProvider.js +3 -2
  86. package/lib/pedagogy/ui/GlossaryProvider.js.map +1 -1
  87. package/lib/pedagogy/ui/GlossaryText.d.ts +7 -2
  88. package/lib/pedagogy/ui/GlossaryText.d.ts.map +1 -1
  89. package/lib/pedagogy/ui/GlossaryText.js +3 -2
  90. package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
  91. package/lib/pedagogy/ui/InlineText.d.ts +8 -2
  92. package/lib/pedagogy/ui/InlineText.d.ts.map +1 -1
  93. package/lib/pedagogy/ui/InlineText.js +7 -2
  94. package/lib/pedagogy/ui/InlineText.js.map +1 -1
  95. package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts +4 -1
  96. package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts.map +1 -1
  97. package/lib/pedagogy/ui/ReferenceSectionBlock.js +2 -1
  98. package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
  99. package/lib/pedagogy/ui/SyntaxTooltip.d.ts.map +1 -1
  100. package/lib/pedagogy/ui/SyntaxTooltip.js +11 -10
  101. package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
  102. package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
  103. package/lib/pedagogy/ui/TutorialStepStrip.js +18 -4
  104. package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
  105. package/lib/pedagogy/ui/glossaryContext.d.ts +7 -0
  106. package/lib/pedagogy/ui/glossaryContext.d.ts.map +1 -1
  107. package/lib/pedagogy/ui/glossaryContext.js.map +1 -1
  108. package/lib/pedagogy/ui/index.d.ts +1 -1
  109. package/lib/pedagogy/ui/index.d.ts.map +1 -1
  110. package/lib/pedagogy/ui/inlineSegments.d.ts +5 -0
  111. package/lib/pedagogy/ui/inlineSegments.d.ts.map +1 -1
  112. package/lib/pedagogy/ui/inlineSegments.js +2 -0
  113. package/lib/pedagogy/ui/inlineSegments.js.map +1 -1
  114. package/lib/pedagogy/ui/pedagogyStyle.d.ts +8 -9
  115. package/lib/pedagogy/ui/pedagogyStyle.d.ts.map +1 -1
  116. package/lib/pedagogy/ui/pedagogyStyle.js +10 -18
  117. package/lib/pedagogy/ui/pedagogyStyle.js.map +1 -1
  118. package/lib/range/core/index.d.ts +4 -0
  119. package/lib/range/core/index.d.ts.map +1 -0
  120. package/lib/range/core/index.js +3 -0
  121. package/lib/range/core/index.js.map +1 -0
  122. package/lib/range/core/rangeBounds.d.ts +79 -0
  123. package/lib/range/core/rangeBounds.d.ts.map +1 -0
  124. package/lib/range/core/rangeBounds.js +110 -0
  125. package/lib/range/core/rangeBounds.js.map +1 -0
  126. package/lib/range/core/rangeHistogram.d.ts +13 -0
  127. package/lib/range/core/rangeHistogram.d.ts.map +1 -0
  128. package/lib/range/core/rangeHistogram.js +39 -0
  129. package/lib/range/core/rangeHistogram.js.map +1 -0
  130. package/lib/range/ui/RangeHistogram.d.ts +20 -0
  131. package/lib/range/ui/RangeHistogram.d.ts.map +1 -0
  132. package/lib/range/ui/RangeHistogram.js +18 -0
  133. package/lib/range/ui/RangeHistogram.js.map +1 -0
  134. package/lib/range/ui/RangeSlider.d.ts +77 -0
  135. package/lib/range/ui/RangeSlider.d.ts.map +1 -0
  136. package/lib/range/ui/RangeSlider.js +60 -0
  137. package/lib/range/ui/RangeSlider.js.map +1 -0
  138. package/lib/range/ui/RangeValue.d.ts +41 -0
  139. package/lib/range/ui/RangeValue.d.ts.map +1 -0
  140. package/lib/range/ui/RangeValue.js +73 -0
  141. package/lib/range/ui/RangeValue.js.map +1 -0
  142. package/lib/range/ui/index.d.ts +3 -0
  143. package/lib/range/ui/index.d.ts.map +1 -0
  144. package/lib/range/ui/index.js +2 -0
  145. package/lib/range/ui/index.js.map +1 -0
  146. package/lib/seo/core/documentMeta.d.ts +16 -0
  147. package/lib/seo/core/documentMeta.d.ts.map +1 -1
  148. package/lib/seo/core/documentMeta.js +18 -0
  149. package/lib/seo/core/documentMeta.js.map +1 -1
  150. package/lib/seo/core/index.d.ts +3 -1
  151. package/lib/seo/core/index.d.ts.map +1 -1
  152. package/lib/seo/core/index.js +2 -1
  153. package/lib/seo/core/index.js.map +1 -1
  154. package/lib/seo/core/noscript.d.ts +8 -0
  155. package/lib/seo/core/noscript.d.ts.map +1 -1
  156. package/lib/seo/core/noscript.js +10 -3
  157. package/lib/seo/core/noscript.js.map +1 -1
  158. package/lib/seo/core/pageMeta.d.ts +10 -2
  159. package/lib/seo/core/pageMeta.d.ts.map +1 -1
  160. package/lib/seo/core/pageMeta.js +12 -3
  161. package/lib/seo/core/pageMeta.js.map +1 -1
  162. package/lib/seo/core/plainDescription.d.ts +2 -2
  163. package/lib/seo/core/plainDescription.js +5 -2
  164. package/lib/seo/core/plainDescription.js.map +1 -1
  165. package/lib/seo/core/siteFiles.d.ts +32 -3
  166. package/lib/seo/core/siteFiles.d.ts.map +1 -1
  167. package/lib/seo/core/siteFiles.js +13 -4
  168. package/lib/seo/core/siteFiles.js.map +1 -1
  169. package/lib/seo/core/startDocumentMeta.js +8 -7
  170. package/lib/seo/core/startDocumentMeta.js.map +1 -1
  171. package/lib/seo/vite/prerender.d.ts +26 -4
  172. package/lib/seo/vite/prerender.d.ts.map +1 -1
  173. package/lib/seo/vite/prerender.js +100 -30
  174. package/lib/seo/vite/prerender.js.map +1 -1
  175. package/lib/share/core/devices.d.ts +41 -0
  176. package/lib/share/core/devices.d.ts.map +1 -0
  177. package/lib/share/core/devices.js +58 -0
  178. package/lib/share/core/devices.js.map +1 -0
  179. package/lib/share/core/index.d.ts +4 -0
  180. package/lib/share/core/index.d.ts.map +1 -1
  181. package/lib/share/core/index.js +2 -0
  182. package/lib/share/core/index.js.map +1 -1
  183. package/lib/share/core/regions.d.ts +43 -0
  184. package/lib/share/core/regions.d.ts.map +1 -0
  185. package/lib/share/core/regions.js +71 -0
  186. package/lib/share/core/regions.js.map +1 -0
  187. package/lib/share/ui/HiddenPartsProvider.d.ts.map +1 -1
  188. package/lib/share/ui/HiddenPartsProvider.js +4 -1
  189. package/lib/share/ui/HiddenPartsProvider.js.map +1 -1
  190. package/lib/share/ui/PagePart.d.ts.map +1 -1
  191. package/lib/share/ui/PagePart.js +10 -1
  192. package/lib/share/ui/PagePart.js.map +1 -1
  193. package/lib/share/ui/ShareButton.d.ts.map +1 -1
  194. package/lib/share/ui/ShareButton.js +3 -4
  195. package/lib/share/ui/ShareButton.js.map +1 -1
  196. package/lib/share/ui/ShareConfigOptions.d.ts +6 -1
  197. package/lib/share/ui/ShareConfigOptions.d.ts.map +1 -1
  198. package/lib/share/ui/ShareConfigOptions.js +6 -13
  199. package/lib/share/ui/ShareConfigOptions.js.map +1 -1
  200. package/lib/share/ui/ShareDialog.d.ts +14 -0
  201. package/lib/share/ui/ShareDialog.d.ts.map +1 -1
  202. package/lib/share/ui/ShareDialog.js +3 -3
  203. package/lib/share/ui/ShareDialog.js.map +1 -1
  204. package/lib/share/ui/ShareDialogContent.d.ts +2 -2
  205. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  206. package/lib/share/ui/ShareDialogContent.js +36 -30
  207. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  208. package/lib/share/ui/ShareLinkBar.d.ts +21 -0
  209. package/lib/share/ui/ShareLinkBar.d.ts.map +1 -0
  210. package/lib/share/ui/ShareLinkBar.js +32 -0
  211. package/lib/share/ui/ShareLinkBar.js.map +1 -0
  212. package/lib/share/ui/SharePanes.d.ts +17 -0
  213. package/lib/share/ui/SharePanes.d.ts.map +1 -0
  214. package/lib/share/ui/SharePanes.js +27 -0
  215. package/lib/share/ui/SharePanes.js.map +1 -0
  216. package/lib/share/ui/SharePartOptions.d.ts +9 -3
  217. package/lib/share/ui/SharePartOptions.d.ts.map +1 -1
  218. package/lib/share/ui/SharePartOptions.js +12 -16
  219. package/lib/share/ui/SharePartOptions.js.map +1 -1
  220. package/lib/share/ui/SharePartRow.d.ts +37 -0
  221. package/lib/share/ui/SharePartRow.d.ts.map +1 -0
  222. package/lib/share/ui/SharePartRow.js +25 -0
  223. package/lib/share/ui/SharePartRow.js.map +1 -0
  224. package/lib/share/ui/SharePresetTabs.d.ts +10 -7
  225. package/lib/share/ui/SharePresetTabs.d.ts.map +1 -1
  226. package/lib/share/ui/SharePresetTabs.js +17 -17
  227. package/lib/share/ui/SharePresetTabs.js.map +1 -1
  228. package/lib/share/ui/SharePreview.d.ts +32 -0
  229. package/lib/share/ui/SharePreview.d.ts.map +1 -0
  230. package/lib/share/ui/SharePreview.js +104 -0
  231. package/lib/share/ui/SharePreview.js.map +1 -0
  232. package/lib/share/ui/SharePreviewDevices.d.ts +20 -0
  233. package/lib/share/ui/SharePreviewDevices.d.ts.map +1 -0
  234. package/lib/share/ui/SharePreviewDevices.js +40 -0
  235. package/lib/share/ui/SharePreviewDevices.js.map +1 -0
  236. package/lib/share/ui/index.d.ts +1 -0
  237. package/lib/share/ui/index.d.ts.map +1 -1
  238. package/lib/share/ui/index.js.map +1 -1
  239. package/lib/share/ui/shareMarking.d.ts +14 -0
  240. package/lib/share/ui/shareMarking.d.ts.map +1 -0
  241. package/lib/share/ui/shareMarking.js +17 -0
  242. package/lib/share/ui/shareMarking.js.map +1 -0
  243. package/lib/share/ui/useShareRegionReports.d.ts +12 -0
  244. package/lib/share/ui/useShareRegionReports.d.ts.map +1 -0
  245. package/lib/share/ui/useShareRegionReports.js +73 -0
  246. package/lib/share/ui/useShareRegionReports.js.map +1 -0
  247. package/lib/shared/ui/ActionTooltip.d.ts +42 -0
  248. package/lib/shared/ui/ActionTooltip.d.ts.map +1 -0
  249. package/lib/shared/ui/ActionTooltip.js +21 -0
  250. package/lib/shared/ui/ActionTooltip.js.map +1 -0
  251. package/lib/shared/ui/index.d.ts +4 -0
  252. package/lib/shared/ui/index.d.ts.map +1 -1
  253. package/lib/shared/ui/index.js +2 -0
  254. package/lib/shared/ui/index.js.map +1 -1
  255. package/lib/shared/ui/useActionTooltip.d.ts +48 -0
  256. package/lib/shared/ui/useActionTooltip.d.ts.map +1 -0
  257. package/lib/shared/ui/useActionTooltip.js +78 -0
  258. package/lib/shared/ui/useActionTooltip.js.map +1 -0
  259. package/lib/structure/core/index.d.ts +2 -0
  260. package/lib/structure/core/index.d.ts.map +1 -1
  261. package/lib/structure/core/index.js +1 -0
  262. package/lib/structure/core/index.js.map +1 -1
  263. package/lib/structure/core/sourceMolecule.d.ts +18 -0
  264. package/lib/structure/core/sourceMolecule.d.ts.map +1 -0
  265. package/lib/structure/core/sourceMolecule.js +26 -0
  266. package/lib/structure/core/sourceMolecule.js.map +1 -0
  267. package/lib/structure/core/structureNotations.d.ts +57 -0
  268. package/lib/structure/core/structureNotations.d.ts.map +1 -0
  269. package/lib/structure/core/structureNotations.js +74 -0
  270. package/lib/structure/core/structureNotations.js.map +1 -0
  271. package/lib/structure/core/structurePicture.d.ts +49 -0
  272. package/lib/structure/core/structurePicture.d.ts.map +1 -0
  273. package/lib/structure/core/structurePicture.js +83 -0
  274. package/lib/structure/core/structurePicture.js.map +1 -0
  275. package/lib/structure/ui/EditorExportButton.d.ts +23 -0
  276. package/lib/structure/ui/EditorExportButton.d.ts.map +1 -0
  277. package/lib/structure/ui/EditorExportButton.js +23 -0
  278. package/lib/structure/ui/EditorExportButton.js.map +1 -0
  279. package/lib/structure/ui/EditorHelpButton.d.ts +5 -2
  280. package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -1
  281. package/lib/structure/ui/EditorHelpButton.js +8 -9
  282. package/lib/structure/ui/EditorHelpButton.js.map +1 -1
  283. package/lib/structure/ui/StructureEditor.d.ts +8 -0
  284. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  285. package/lib/structure/ui/StructureEditor.js +26 -3
  286. package/lib/structure/ui/StructureEditor.js.map +1 -1
  287. package/lib/structure/ui/StructureExportDialog.d.ts +34 -0
  288. package/lib/structure/ui/StructureExportDialog.d.ts.map +1 -0
  289. package/lib/structure/ui/StructureExportDialog.js +43 -0
  290. package/lib/structure/ui/StructureExportDialog.js.map +1 -0
  291. package/lib/structure/ui/StructureExportPanel.d.ts +37 -0
  292. package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -0
  293. package/lib/structure/ui/StructureExportPanel.js +75 -0
  294. package/lib/structure/ui/StructureExportPanel.js.map +1 -0
  295. package/lib/structure/ui/StructureNotationRow.d.ts +22 -0
  296. package/lib/structure/ui/StructureNotationRow.d.ts.map +1 -0
  297. package/lib/structure/ui/StructureNotationRow.js +34 -0
  298. package/lib/structure/ui/StructureNotationRow.js.map +1 -0
  299. package/lib/structure/ui/StructurePicturePane.d.ts +23 -0
  300. package/lib/structure/ui/StructurePicturePane.d.ts.map +1 -0
  301. package/lib/structure/ui/StructurePicturePane.js +113 -0
  302. package/lib/structure/ui/StructurePicturePane.js.map +1 -0
  303. package/lib/structure/ui/StructureSvg.d.ts.map +1 -1
  304. package/lib/structure/ui/StructureSvg.js +2 -18
  305. package/lib/structure/ui/StructureSvg.js.map +1 -1
  306. package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -1
  307. package/lib/structure/ui/ToolbarTooltip.js +1 -2
  308. package/lib/structure/ui/ToolbarTooltip.js.map +1 -1
  309. package/lib/structure/ui/index.d.ts +7 -3
  310. package/lib/structure/ui/index.d.ts.map +1 -1
  311. package/lib/structure/ui/index.js +5 -3
  312. package/lib/structure/ui/index.js.map +1 -1
  313. package/lib/structure/ui/structureNotationText.d.ts +23 -0
  314. package/lib/structure/ui/structureNotationText.d.ts.map +1 -0
  315. package/lib/structure/ui/structureNotationText.js +42 -0
  316. package/lib/structure/ui/structureNotationText.js.map +1 -0
  317. package/lib/structure/ui/useEditorStructure.d.ts +37 -0
  318. package/lib/structure/ui/useEditorStructure.d.ts.map +1 -0
  319. package/lib/structure/ui/useEditorStructure.js +70 -0
  320. package/lib/structure/ui/useEditorStructure.js.map +1 -0
  321. package/lib/ui.d.ts +1 -0
  322. package/lib/ui.d.ts.map +1 -1
  323. package/lib/ui.js +1 -0
  324. package/lib/ui.js.map +1 -1
  325. package/package.json +1 -1
  326. package/src/chart/ui/Axes.tsx +53 -5
  327. package/src/chrome/ui/NavMenuButton.tsx +61 -18
  328. package/src/chrome/ui/SiteHeader.tsx +37 -1
  329. package/src/chrome/ui/navItem.ts +7 -0
  330. package/src/citation/ui/entries.tsx +10 -2
  331. package/src/clipboard/ui/ClickToCopy.tsx +47 -13
  332. package/src/clipboard/ui/CopyButton.tsx +4 -2
  333. package/src/clipboard/ui/CopyableValue.tsx +88 -13
  334. package/src/clipboard/ui/index.ts +5 -1
  335. package/src/confirm/ui/ConfirmButton.tsx +2 -0
  336. package/src/core.ts +1 -0
  337. package/src/delimited/ui/TableDataButton.tsx +2 -1
  338. package/src/download/core/figureSvg.ts +42 -10
  339. package/src/help/ui/HelpToolbarButton.tsx +2 -1
  340. package/src/locales/de.json +48 -5
  341. package/src/locales/en.json +48 -5
  342. package/src/locales/es.json +48 -5
  343. package/src/locales/fr.json +48 -5
  344. package/src/locales/it.json +48 -5
  345. package/src/number/core/index.ts +1 -0
  346. package/src/number/core/numberText.ts +20 -0
  347. package/src/number/ui/NumberInput.tsx +1 -1
  348. package/src/overlay/ui/OverlayIconButton.tsx +1 -0
  349. package/src/panel/ui/ClearButton.tsx +1 -0
  350. package/src/pedagogy/core/inlineMarks.ts +15 -7
  351. package/src/pedagogy/ui/GlossaryDefinition.tsx +8 -18
  352. package/src/pedagogy/ui/GlossaryIndex.tsx +0 -1
  353. package/src/pedagogy/ui/GlossaryProvider.tsx +10 -2
  354. package/src/pedagogy/ui/GlossaryText.tsx +10 -2
  355. package/src/pedagogy/ui/InlineText.tsx +21 -4
  356. package/src/pedagogy/ui/ReferenceSectionBlock.tsx +8 -2
  357. package/src/pedagogy/ui/SyntaxTooltip.tsx +11 -9
  358. package/src/pedagogy/ui/TutorialStepStrip.tsx +50 -18
  359. package/src/pedagogy/ui/glossaryContext.ts +7 -0
  360. package/src/pedagogy/ui/index.ts +1 -1
  361. package/src/pedagogy/ui/inlineSegments.tsx +7 -0
  362. package/src/pedagogy/ui/pedagogyStyle.ts +12 -24
  363. package/src/range/core/index.ts +11 -0
  364. package/src/range/core/rangeBounds.ts +143 -0
  365. package/src/range/core/rangeHistogram.ts +38 -0
  366. package/src/range/ui/RangeHistogram.tsx +49 -0
  367. package/src/range/ui/RangeSlider.tsx +209 -0
  368. package/src/range/ui/RangeValue.tsx +166 -0
  369. package/src/range/ui/index.ts +2 -0
  370. package/src/seo/core/documentMeta.ts +22 -0
  371. package/src/seo/core/index.ts +8 -1
  372. package/src/seo/core/noscript.ts +20 -2
  373. package/src/seo/core/pageMeta.ts +29 -5
  374. package/src/seo/core/plainDescription.ts +4 -2
  375. package/src/seo/core/siteFiles.ts +45 -10
  376. package/src/seo/core/startDocumentMeta.ts +9 -9
  377. package/src/seo/vite/prerender.ts +160 -36
  378. package/src/share/core/devices.ts +80 -0
  379. package/src/share/core/index.ts +14 -0
  380. package/src/share/core/regions.ts +87 -0
  381. package/src/share/ui/HiddenPartsProvider.tsx +6 -1
  382. package/src/share/ui/PagePart.tsx +12 -1
  383. package/src/share/ui/ShareButton.tsx +3 -6
  384. package/src/share/ui/ShareConfigOptions.tsx +25 -16
  385. package/src/share/ui/ShareDialog.tsx +17 -3
  386. package/src/share/ui/ShareDialogContent.tsx +80 -77
  387. package/src/share/ui/ShareLinkBar.tsx +70 -0
  388. package/src/share/ui/SharePanes.tsx +62 -0
  389. package/src/share/ui/SharePartOptions.tsx +25 -26
  390. package/src/share/ui/SharePartRow.tsx +78 -0
  391. package/src/share/ui/SharePresetTabs.tsx +36 -43
  392. package/src/share/ui/SharePreview.tsx +204 -0
  393. package/src/share/ui/SharePreviewDevices.tsx +70 -0
  394. package/src/share/ui/index.ts +1 -0
  395. package/src/share/ui/shareMarking.ts +18 -0
  396. package/src/share/ui/useShareRegionReports.ts +84 -0
  397. package/src/shared/ui/ActionTooltip.tsx +70 -0
  398. package/src/shared/ui/index.ts +7 -0
  399. package/src/shared/ui/useActionTooltip.ts +103 -0
  400. package/src/structure/core/index.ts +5 -0
  401. package/src/structure/core/sourceMolecule.ts +26 -0
  402. package/src/structure/core/structureNotations.ts +125 -0
  403. package/src/structure/core/structurePicture.ts +119 -0
  404. package/src/structure/ui/EditorExportButton.tsx +62 -0
  405. package/src/structure/ui/EditorHelpButton.tsx +26 -30
  406. package/src/structure/ui/StructureEditor.tsx +51 -5
  407. package/src/structure/ui/StructureExportDialog.tsx +90 -0
  408. package/src/structure/ui/StructureExportPanel.tsx +156 -0
  409. package/src/structure/ui/StructureNotationRow.tsx +74 -0
  410. package/src/structure/ui/StructurePicturePane.tsx +208 -0
  411. package/src/structure/ui/StructureSvg.tsx +3 -12
  412. package/src/structure/ui/ToolbarTooltip.tsx +1 -1
  413. package/src/structure/ui/index.ts +7 -3
  414. package/src/structure/ui/structureNotationText.ts +56 -0
  415. package/src/structure/ui/useEditorStructure.ts +119 -0
  416. package/src/ui.ts +1 -0
  417. package/styles/chrome.css +546 -11
@@ -52,18 +52,18 @@ export function startDocumentMeta(
52
52
  ): () => void {
53
53
  if (typeof document === 'undefined') return stopNothing;
54
54
 
55
+ // Everything the caller passed goes through, rather than the four fields this
56
+ // once named one by one: an option added to `PageMetaOptions` and forgotten
57
+ // here is dropped in silence. `languages` was, and every translated page took
58
+ // the English home page's title and canonical — type-checked, linted and unit
59
+ // tested all the way.
60
+ const { url, follow, ...page } = options;
61
+
55
62
  const write = (): void => {
56
- writeDocumentMeta(
57
- pageDocumentMeta({
58
- site: options.site,
59
- routes: options.routes,
60
- url: options.url(),
61
- origin: options.origin,
62
- }),
63
- );
63
+ writeDocumentMeta(pageDocumentMeta({ ...page, url: url() }));
64
64
  };
65
65
 
66
- return (options.follow ?? writeOnce)(write);
66
+ return (follow ?? writeOnce)(write);
67
67
  }
68
68
 
69
69
  function writeOnce(write: () => void): () => void {
@@ -23,6 +23,10 @@ import { dirname, join, resolve } from 'node:path';
23
23
  import type { Logger, Plugin } from 'vite';
24
24
 
25
25
  import type { SiteId, SiteRecord } from '../../ecosystem/core/sites.ts';
26
+ import { CHROME_CATALOG } from '../../i18n/core/chromeCatalog.ts';
27
+ import type { Language } from '../../i18n/core/languages.ts';
28
+ import { DEFAULT_LANGUAGE } from '../../i18n/core/languages.ts';
29
+ import { withLanguagePath } from '../../language/core/languagePath.ts';
26
30
  import { trimTrailingSlash } from '../../router/core/address.ts';
27
31
  import type { NoscriptText } from '../core/noscript.ts';
28
32
  import { noscriptIndex } from '../core/noscript.ts';
@@ -40,8 +44,17 @@ import { PAGE_BODY_MARKER, PAGE_HEAD_MARKER, fill } from '../core/template.ts';
40
44
  export interface PrerenderOptions {
41
45
  /** The site, named or passed. */
42
46
  site: SiteRecord | SiteId;
43
- /** Every address it answers, each with its title and description. */
44
- routes: readonly RouteMeta[];
47
+ /**
48
+ * Every address it answers, each with its title and description.
49
+ *
50
+ * A translated site passes a function instead: the build calls it once per
51
+ * language and writes that language's table, so the title and the description
52
+ * a search result shows are in the language of the page it points at. The
53
+ * paths are the same in every language — an id is never translated, or a
54
+ * progress key, a share link and a bookmark would all break on a language
55
+ * switch.
56
+ */
57
+ routes: readonly RouteMeta[] | ((language: Language) => readonly RouteMeta[]);
45
58
  /**
46
59
  * Where the site is served, mount path included. Every absolute address is
47
60
  * built on it, and `robots.txt` and the `noscript` index write their paths
@@ -91,7 +104,8 @@ export interface PrerenderOptions {
91
104
  * `<!--cheminfo:body-->` at all.
92
105
  * @default true
93
106
  */
94
- noscript?: boolean | NoscriptText;
107
+ noscript?:
108
+ boolean | NoscriptText | ((language: Language) => boolean | NoscriptText);
95
109
  /**
96
110
  * What each page says for itself, above the crawl path: its own heading, its
97
111
  * prose and the facts it would show anyway, read from the same data the app
@@ -103,7 +117,19 @@ export interface PrerenderOptions {
103
117
  * the bundle the browser downloads: only the build ever calls it.
104
118
  * @default undefined — every page carries the menu alone
105
119
  */
106
- content?: (route: RouteMeta) => PageContent | undefined;
120
+ content?: (route: RouteMeta, language: Language) => PageContent | undefined;
121
+ /**
122
+ * Every language the site is written in, the default one included.
123
+ *
124
+ * The build then writes one file per address per language — `/tutorial` and
125
+ * `/fr/tutorial` — each with its own `lang`, title, description and
126
+ * canonical, the `hreflang` set tying them together, and every variant in the
127
+ * sitemap. That is the whole of what makes a translation findable: a site
128
+ * keeping its language in a parameter or in storage serves five languages at
129
+ * one address, and a crawler indexes one.
130
+ * @default [the default language]
131
+ */
132
+ languages?: readonly Language[];
107
133
  }
108
134
 
109
135
  /**
@@ -114,21 +140,43 @@ export interface PrerenderOptions {
114
140
  * is not a path, or when the origin is not an absolute address.
115
141
  */
116
142
  export function cheminfoPrerender(options: PrerenderOptions): Plugin {
117
- const { site, routes, origin, robots = [] } = options;
143
+ const { site, origin, robots = [] } = options;
144
+ const languages = options.languages ?? [DEFAULT_LANGUAGE];
145
+ const tableFor = (language: Language): readonly RouteMeta[] =>
146
+ typeof options.routes === 'function'
147
+ ? options.routes(language)
148
+ : options.routes;
149
+
150
+ // Every language answers the same addresses, so each table is checked and
151
+ // they are checked against each other: a translation that renames a path or
152
+ // drops a page writes a file the sitemap of another language names, and the
153
+ // `hreflang` set would point at an address that is not there.
154
+ const routes = tableFor(DEFAULT_LANGUAGE);
118
155
  assertRoutes(routes);
119
- const structuredData = structuredDataOf(options);
156
+ for (const language of languages) {
157
+ if (language === DEFAULT_LANGUAGE) continue;
158
+ assertSamePaths(routes, tableFor(language), language);
159
+ }
160
+
161
+ const structuredData = structuredDataOf(options, routes);
120
162
 
121
163
  let out = 'dist';
122
164
  let serve = false;
123
165
  let logger: Logger | null = null;
124
166
 
125
- const page = (template: string, route: RouteMeta) => {
167
+ const page = (template: string, route: RouteMeta, language: Language) => {
126
168
  const head = fill(
127
- template,
169
+ withHtmlLang(template, language),
128
170
  PAGE_HEAD_MARKER,
129
- `${pageHeadTags({ site, routes, origin, url: route.path })}${structuredData}`,
171
+ `${pageHeadTags({
172
+ site,
173
+ routes: tableFor(language),
174
+ origin,
175
+ languages,
176
+ url: withLanguagePath(language, route.path),
177
+ })}${structuredData}`,
130
178
  );
131
- const crawlPath = crawlPathOf(options, route);
179
+ const crawlPath = crawlPathOf(options, route, language, tableFor(language));
132
180
  return crawlPath === '' ? head : fill(head, PAGE_BODY_MARKER, crawlPath);
133
181
  };
134
182
 
@@ -145,37 +193,60 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
145
193
  // from the home route rather than shipped with its markers showing.
146
194
  transformIndexHtml: {
147
195
  order: 'post',
148
- handler: (html: string) => (serve ? page(html, homeRoute(routes)) : html),
196
+ handler: (html: string) =>
197
+ serve ? page(html, homeRoute(routes), DEFAULT_LANGUAGE) : html,
149
198
  },
150
199
 
151
- closeBundle() {
200
+ async closeBundle() {
152
201
  if (serve) return;
202
+ // The crawl path names the family in the chrome's own words, and every
203
+ // language but the default arrives as a chunk of its own, so they are all
204
+ // fetched before a single file is written.
205
+ await Promise.all(
206
+ languages
207
+ .filter((language) => language !== DEFAULT_LANGUAGE)
208
+ .map((language) => CHROME_CATALOG.load(language)),
209
+ );
210
+
153
211
  const template = readFileSync(join(out, 'index.html'), 'utf8');
154
212
 
155
- const write = (route: RouteMeta, file: string) => {
213
+ const write = (route: RouteMeta, language: Language, file: string) => {
156
214
  mkdirSync(dirname(file), { recursive: true });
157
- writeFileSync(file, page(template, route));
215
+ writeFileSync(file, page(template, route, language));
158
216
  };
159
217
 
160
- let root = false;
161
- for (const route of routes) {
162
- const address = trimTrailingSlash(route.path);
163
- if (address === '/') root = true;
164
- write(
165
- route,
166
- address === '/'
167
- ? join(out, 'index.html')
168
- : join(out, address.slice(1), 'index.html'),
169
- );
218
+ let written = 0;
219
+ for (const language of languages) {
220
+ const table = tableFor(language);
221
+ let root = false;
222
+ for (const route of table) {
223
+ const address = trimTrailingSlash(
224
+ withLanguagePath(language, route.path),
225
+ );
226
+ if (address === '/') root = true;
227
+ write(
228
+ route,
229
+ language,
230
+ address === '/'
231
+ ? join(out, 'index.html')
232
+ : join(out, address.slice(1), 'index.html'),
233
+ );
234
+ written++;
235
+ }
236
+ // The file a static server hands out for the mount itself. A table
237
+ // naming no root would otherwise leave the template vite built, and
238
+ // ship a site whose front page carries its markers instead of a head.
239
+ // A language other than the default already has its own front page at
240
+ // `/<language>`, so only the unprefixed one is ever missing.
241
+ if (!root && language === DEFAULT_LANGUAGE) {
242
+ write(homeRoute(table), language, join(out, 'index.html'));
243
+ written++;
244
+ }
170
245
  }
171
- // The file a static server hands out for the mount itself. A table naming
172
- // no root would otherwise leave the template vite built, and ship a site
173
- // whose front page carries its markers instead of a head.
174
- if (!root) write(homeRoute(routes), join(out, 'index.html'));
175
246
 
176
247
  writeFileSync(
177
248
  join(out, 'sitemap.xml'),
178
- sitemapXml({ site, routes, origin }),
249
+ sitemapXml({ site, routes, origin, languages }),
179
250
  );
180
251
  if (robots !== false) {
181
252
  writeFileSync(
@@ -184,17 +255,22 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
184
255
  );
185
256
  }
186
257
 
258
+ const spoken =
259
+ languages.length > 1 ? ` in ${languages.length} languages` : '';
187
260
  logger?.info(
188
- `${routes.length} pages prerendered, and listed in sitemap.xml`,
261
+ `${written} pages prerendered${spoken}, and listed in sitemap.xml`,
189
262
  );
190
263
  },
191
264
  };
192
265
  }
193
266
 
194
- function structuredDataOf(options: PrerenderOptions): string {
267
+ function structuredDataOf(
268
+ options: PrerenderOptions,
269
+ routes: readonly RouteMeta[],
270
+ ): string {
195
271
  const { category, ...rest } = options;
196
272
  if (category === false) return '';
197
- const { site, routes, origin } = rest;
273
+ const { site, origin } = rest;
198
274
  const { operatingSystem, description, browserRequirements, currency } = rest;
199
275
  return `\n${structuredDataScript({
200
276
  site,
@@ -208,16 +284,64 @@ function structuredDataOf(options: PrerenderOptions): string {
208
284
  })}`;
209
285
  }
210
286
 
211
- function crawlPathOf(options: PrerenderOptions, route: RouteMeta): string {
212
- const { site, routes, origin, noscript = true } = options;
287
+ function crawlPathOf(
288
+ options: PrerenderOptions,
289
+ route: RouteMeta,
290
+ language: Language,
291
+ routes: readonly RouteMeta[],
292
+ ): string {
293
+ const { site, origin } = options;
294
+ // The crawl path of a translated page is that language's: its heading, its
295
+ // intro and the label under every link. Declared as a function of the
296
+ // language, the site writes each of them from its own catalog; declared as a
297
+ // record, it is the same words on every language, which is what a site that
298
+ // speaks one wants and is what it used to be.
299
+ const declared = options.noscript ?? true;
300
+ const noscript =
301
+ typeof declared === 'function' ? declared(language) : declared;
213
302
  if (noscript === false) return '';
214
- const content = options.content?.(route);
303
+ const content = options.content?.(route, language);
215
304
  const { routes: listed, ...prose } = noscript === true ? {} : noscript;
305
+ // The crawl path of a translated page links to that language's addresses: a
306
+ // French page listing the English ones is a crawler's only route out of it,
307
+ // and it would lead straight back out of the translation.
308
+ const menu = (listed ?? routes).map((entry) => ({
309
+ ...entry,
310
+ path: withLanguagePath(language, entry.path),
311
+ }));
216
312
  return noscriptIndex({
217
313
  site,
218
314
  origin,
219
315
  ...prose,
220
- routes: listed ?? routes,
316
+ routes: menu,
221
317
  content,
318
+ language,
222
319
  });
223
320
  }
321
+
322
+ // Two tables of one site answer the same addresses. A translated table that
323
+ // renames or drops a path leaves the `hreflang` set of every other language
324
+ // pointing at an address nothing wrote, which is a soft 404 on each of them.
325
+ function assertSamePaths(
326
+ source: readonly RouteMeta[],
327
+ translated: readonly RouteMeta[],
328
+ language: Language,
329
+ ): void {
330
+ const wanted = source.map((route) => route.path).join(' ');
331
+ const written = translated.map((route) => route.path).join(' ');
332
+ if (wanted !== written) {
333
+ throw new Error(
334
+ `the ${language} route table answers different addresses than the source one: an address is the same in every language`,
335
+ );
336
+ }
337
+ }
338
+
339
+ // The template is written in the default language, so each file says which
340
+ // language it is actually in. A page whose `lang` lies is read aloud wrong and
341
+ // offered to the wrong reader.
342
+ function withHtmlLang(template: string, language: Language): string {
343
+ return template.replace(
344
+ /<html(?<attributes>[^>]*)\slang="[^"]*"/i,
345
+ `<html$<attributes> lang="${language}"`,
346
+ );
347
+ }
@@ -0,0 +1,80 @@
1
+ /** The screens a link can be looked at on, before it is handed out. */
2
+ export type SharePreviewDeviceKey = 'window' | 'mobile' | 'laptop' | 'hd';
3
+
4
+ /** A screen the preview lays the page out at. */
5
+ export interface SharePreviewSize {
6
+ /** Width in CSS pixels. */
7
+ width: number;
8
+ /** Height in CSS pixels. */
9
+ height: number;
10
+ }
11
+
12
+ /**
13
+ * The screens offered, in the order the capsules read. `window` is the
14
+ * reader's own, and is resolved against the browser rather than written here.
15
+ */
16
+ export const SHARE_PREVIEW_DEVICES: readonly SharePreviewDeviceKey[] = [
17
+ 'window',
18
+ 'mobile',
19
+ 'laptop',
20
+ 'hd',
21
+ ];
22
+
23
+ const FIXED_SIZES: Record<
24
+ Exclude<SharePreviewDeviceKey, 'window'>,
25
+ SharePreviewSize
26
+ > = {
27
+ mobile: { width: 390, height: 844 },
28
+ laptop: { width: 1280, height: 800 },
29
+ hd: { width: 1920, height: 1080 },
30
+ };
31
+
32
+ /** What the preview falls back to where there is no window to measure. */
33
+ const FALLBACK_WINDOW: SharePreviewSize = { width: 1280, height: 800 };
34
+
35
+ /**
36
+ * The page size a device lays the preview out at.
37
+ * @param key - The device picked.
38
+ * @param window - The reader's own window, for the `window` device.
39
+ * @returns Its width and height in CSS pixels.
40
+ */
41
+ export function sharePreviewSize(
42
+ key: SharePreviewDeviceKey,
43
+ window?: Partial<SharePreviewSize>,
44
+ ): SharePreviewSize {
45
+ if (key !== 'window') return FIXED_SIZES[key];
46
+ const width = window?.width ?? 0;
47
+ const height = window?.height ?? 0;
48
+ return width > 0 && height > 0 ? { width, height } : FALLBACK_WINDOW;
49
+ }
50
+
51
+ /**
52
+ * How far the page is shrunk to fit the pane it is previewed in.
53
+ *
54
+ * It is never enlarged: a phone shown bigger than a phone is not what the
55
+ * reader is checking.
56
+ * @param pane - The room the preview pane has.
57
+ * @param page - The size the page is laid out at.
58
+ * @returns The scale, between 0 and 1.
59
+ */
60
+ export function sharePreviewScale(
61
+ pane: Partial<SharePreviewSize>,
62
+ page: SharePreviewSize,
63
+ ): number {
64
+ const width = pane.width ?? 0;
65
+ const height = pane.height ?? 0;
66
+ if (width <= 0 || height <= 0) return 1;
67
+ return Math.min(1, width / page.width, height / page.height);
68
+ }
69
+
70
+ /**
71
+ * The address as the preview's own bar writes it, without its scheme.
72
+ *
73
+ * What a reader checks there is the site and the query the link carries, and a
74
+ * bar as wide as a phone has no room for `https://`.
75
+ * @param url - The link the preview loads.
76
+ * @returns The address, scheme dropped.
77
+ */
78
+ export function sharePreviewAddress(url: string): string {
79
+ return url.replace(/^[a-z][\w+.-]*:\/\//i, '');
80
+ }
@@ -9,7 +9,21 @@ export {
9
9
  suggestedShareConfig,
10
10
  visibleShareParts,
11
11
  } from './config.ts';
12
+ export type { SharePreviewDeviceKey, SharePreviewSize } from './devices.ts';
13
+ export {
14
+ SHARE_PREVIEW_DEVICES,
15
+ sharePreviewAddress,
16
+ sharePreviewScale,
17
+ sharePreviewSize,
18
+ } from './devices.ts';
12
19
  export { escapeAttribute, escapeText } from './escape.ts';
20
+ export type { ShareRegion } from './regions.ts';
21
+ export {
22
+ SHARE_REGIONS_MESSAGE,
23
+ SHARE_REGIONS_REQUEST,
24
+ isShareRegionsRequest,
25
+ readShareRegions,
26
+ } from './regions.ts';
13
27
  export type {
14
28
  BooleanParamOptions,
15
29
  IntegerParamOptions,
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Where each part of a page sits, so a share dialog can show which region of
3
+ * the preview a row of its list names.
4
+ *
5
+ * The two sides are different documents — the dialog and the framed page — so
6
+ * they speak by message. Everything that arrives is somebody else's input: a
7
+ * framed page is messaged by whatever page frames it, and a dialog by whatever
8
+ * it frames, so both ends read rather than trust.
9
+ */
10
+
11
+ /** Asks a framed page to report where its parts are, and to keep reporting. */
12
+ export const SHARE_REGIONS_REQUEST = 'cheminfo:share-regions-request';
13
+
14
+ /** Carries the answer back to the dialog. */
15
+ export const SHARE_REGIONS_MESSAGE = 'cheminfo:share-regions';
16
+
17
+ /** The box one part of the page occupies, in the page's own coordinates. */
18
+ export interface ShareRegion {
19
+ /** The name the part takes in `?hide=`. */
20
+ part: string;
21
+ /** Distance from the left edge of the page, in CSS pixels. */
22
+ x: number;
23
+ /** Distance from the top edge of the page, in CSS pixels. */
24
+ y: number;
25
+ /** Width of the box, in CSS pixels. */
26
+ width: number;
27
+ /** Height of the box, in CSS pixels. */
28
+ height: number;
29
+ }
30
+
31
+ /**
32
+ * Whether a message asks this page to report where its parts are.
33
+ * @param data - What arrived on the message event.
34
+ * @returns Whether it is the request.
35
+ */
36
+ export function isShareRegionsRequest(data: unknown): boolean {
37
+ return messageType(data) === SHARE_REGIONS_REQUEST;
38
+ }
39
+
40
+ /**
41
+ * The regions a framed page reported, or `null` for anything else that arrived.
42
+ *
43
+ * A region with a name that is not a string, or a side that is not a finite
44
+ * number, is dropped rather than taken: a dialog drawing a box from a number it
45
+ * did not check draws it across the whole screen.
46
+ * @param data - What arrived on the message event.
47
+ * @returns The regions, or `null` when the message is not one.
48
+ */
49
+ export function readShareRegions(data: unknown): ShareRegion[] | null {
50
+ if (messageType(data) !== SHARE_REGIONS_MESSAGE) return null;
51
+ const { regions } = data as { regions?: unknown };
52
+ if (!Array.isArray(regions)) return null;
53
+
54
+ const read: ShareRegion[] = [];
55
+ for (const candidate of regions) {
56
+ const region = readRegion(candidate);
57
+ if (region !== null) read.push(region);
58
+ }
59
+ return read;
60
+ }
61
+
62
+ function readRegion(candidate: unknown): ShareRegion | null {
63
+ if (typeof candidate !== 'object' || candidate === null) return null;
64
+ const { part, x, y, width, height } = candidate as Record<string, unknown>;
65
+ if (typeof part !== 'string' || part === '') return null;
66
+ if (
67
+ !Number.isFinite(x) ||
68
+ !Number.isFinite(y) ||
69
+ !Number.isFinite(width) ||
70
+ !Number.isFinite(height)
71
+ ) {
72
+ return null;
73
+ }
74
+ return {
75
+ part,
76
+ x: x as number,
77
+ y: y as number,
78
+ width: width as number,
79
+ height: height as number,
80
+ };
81
+ }
82
+
83
+ function messageType(data: unknown): string | null {
84
+ if (typeof data !== 'object' || data === null) return null;
85
+ const { type } = data as { type?: unknown };
86
+ return typeof type === 'string' ? type : null;
87
+ }
@@ -2,6 +2,8 @@ import type { ReactElement, ReactNode } from 'react';
2
2
  import { useMemo } from 'react';
3
3
 
4
4
  import { HiddenPartsContext } from './hiddenParts.ts';
5
+ import { ShareMarkingContext } from './shareMarking.ts';
6
+ import { useShareRegionReports } from './useShareRegionReports.ts';
5
7
 
6
8
  export interface HiddenPartsProviderProps {
7
9
  /**
@@ -25,10 +27,13 @@ export function HiddenPartsProvider(
25
27
  ): ReactElement {
26
28
  const { hidden, children } = props;
27
29
  const parts = useMemo(() => new Set(hidden), [hidden]);
30
+ const marking = useShareRegionReports(parts);
28
31
 
29
32
  return (
30
33
  <HiddenPartsContext.Provider value={parts}>
31
- {children}
34
+ <ShareMarkingContext.Provider value={marking}>
35
+ {children}
36
+ </ShareMarkingContext.Provider>
32
37
  </HiddenPartsContext.Provider>
33
38
  );
34
39
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
 
3
3
  import { useIsHidden } from './hiddenParts.ts';
4
+ import { useShareMarking } from './shareMarking.ts';
4
5
 
5
6
  export interface PagePartProps {
6
7
  /** The name this region takes in `?hide=`. */
@@ -21,6 +22,16 @@ export interface PagePartProps {
21
22
  export function PagePart(props: PagePartProps): ReactNode {
22
23
  const { part, children } = props;
23
24
  const isHidden = useIsHidden();
25
+ const marking = useShareMarking();
24
26
 
25
- return isHidden(part) ? null : children;
27
+ if (isHidden(part)) return null;
28
+ if (!marking) return children;
29
+
30
+ // `display: contents` so the marker is only a name the dialog can find, and
31
+ // never a box between the page and the part it holds.
32
+ return (
33
+ <div data-share-part={part} style={{ display: 'contents' }}>
34
+ {children}
35
+ </div>
36
+ );
26
37
  }
@@ -2,6 +2,7 @@ import { Button, Icon } from '@blueprintjs/core';
2
2
  import type { ReactElement } from 'react';
3
3
 
4
4
  import { useChromeT } from '../../i18n/ui/useT.ts';
5
+ import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
5
6
 
6
7
  /** How the button is dressed for the bar it sits in. */
7
8
  export type ShareButtonVariant = 'nav-link' | 'blueprint';
@@ -61,7 +62,7 @@ export function ShareButton(props: ShareButtonProps): ReactElement {
61
62
  if (variant === 'blueprint') {
62
63
  return (
63
64
  <Button
64
- className={className}
65
+ className={joinClassNames('no-print', className)}
65
66
  variant="minimal"
66
67
  icon="share"
67
68
  text={text}
@@ -75,11 +76,7 @@ export function ShareButton(props: ShareButtonProps): ReactElement {
75
76
  return (
76
77
  <button
77
78
  type="button"
78
- className={
79
- className === undefined
80
- ? 'nav-link nav-link--icon'
81
- : `nav-link nav-link--icon ${className}`
82
- }
79
+ className={joinClassNames('nav-link nav-link--icon no-print', className)}
83
80
  title={title}
84
81
  aria-label={label}
85
82
  onClick={onClick}
@@ -1,18 +1,14 @@
1
- import { Checkbox, H6 } from '@blueprintjs/core';
1
+ import { H6 } from '@blueprintjs/core';
2
2
  import type { CSSProperties, ReactElement } from 'react';
3
3
 
4
4
  import { useChromeT } from '../../i18n/ui/useT.ts';
5
5
  import type { HideablePart } from '../core/index.ts';
6
6
 
7
7
  import { SharePartOptions } from './SharePartOptions.tsx';
8
+ import type { SharePartDescriptions } from './SharePartRow.tsx';
9
+ import { SharePartRow } from './SharePartRow.tsx';
8
10
 
9
- const SECTION_STYLE: CSSProperties = { marginBottom: 18 };
10
- const HINT_STYLE: CSSProperties = {
11
- display: 'block',
12
- marginLeft: 26,
13
- color: 'var(--text-muted, #5b6875)',
14
- fontSize: 12,
15
- };
11
+ const SECTION_STYLE: CSSProperties = { marginBottom: 14 };
16
12
 
17
13
  export interface ShareConfigOptionsProps {
18
14
  /** Whether the draft drops the site chrome. */
@@ -21,35 +17,46 @@ export interface ShareConfigOptionsProps {
21
17
  parts: readonly HideablePart[];
22
18
  /** The parts the draft switches off. */
23
19
  hidden: readonly string[];
20
+ /** Where each row says what switching it off does. */
21
+ descriptions: SharePartDescriptions;
24
22
  /** Called when the layout is switched. */
25
23
  onEmbedChange: (embed: boolean) => void;
26
24
  /** Called with the part and whether the link should switch it off. */
27
25
  onPartChange: (part: string, hidden: boolean) => void;
26
+ /** Called with the part the pointer is on, or `null` when it leaves. */
27
+ onPartPointed: (part: string | null) => void;
28
28
  }
29
29
 
30
30
  /**
31
- * The boxes of the share dialog: the layout, then one box per part of the page.
31
+ * The switches of the share dialog: the layout, then one per part of the page.
32
32
  * @param props - The draft's layout and parts, and how to change them.
33
33
  * @returns The two sections.
34
34
  */
35
35
  export function ShareConfigOptions(
36
36
  props: ShareConfigOptionsProps,
37
37
  ): ReactElement {
38
- const { embed, parts, hidden, onEmbedChange, onPartChange } = props;
38
+ const {
39
+ embed,
40
+ parts,
41
+ hidden,
42
+ descriptions,
43
+ onEmbedChange,
44
+ onPartChange,
45
+ onPartPointed,
46
+ } = props;
39
47
  const t = useChromeT();
40
48
 
41
49
  return (
42
50
  <>
43
51
  <section className="share-section" style={SECTION_STYLE}>
44
52
  <H6>{t('share.layout')}</H6>
45
- <Checkbox
46
- checked={embed}
53
+ <SharePartRow
47
54
  label={t('share.embed')}
48
- onChange={(event) => {
49
- onEmbedChange(event.currentTarget.checked);
50
- }}
55
+ description={t('share.embedHint')}
56
+ on={embed}
57
+ descriptions={descriptions}
58
+ onChange={onEmbedChange}
51
59
  />
52
- <span style={HINT_STYLE}>{t('share.embedHint')}</span>
53
60
  </section>
54
61
 
55
62
  {parts.length > 0 ? (
@@ -58,7 +65,9 @@ export function ShareConfigOptions(
58
65
  <SharePartOptions
59
66
  parts={parts}
60
67
  hidden={hidden}
68
+ descriptions={descriptions}
61
69
  onChange={onPartChange}
70
+ onPointed={onPartPointed}
62
71
  />
63
72
  </section>
64
73
  ) : null}