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.
- package/lib/chart/ui/Axes.d.ts +19 -0
- package/lib/chart/ui/Axes.d.ts.map +1 -1
- package/lib/chart/ui/Axes.js +25 -5
- package/lib/chart/ui/Axes.js.map +1 -1
- package/lib/chrome/ui/NavMenuButton.d.ts.map +1 -1
- package/lib/chrome/ui/NavMenuButton.js +41 -11
- package/lib/chrome/ui/NavMenuButton.js.map +1 -1
- package/lib/chrome/ui/SiteHeader.js +24 -1
- package/lib/chrome/ui/SiteHeader.js.map +1 -1
- package/lib/chrome/ui/navItem.d.ts +7 -0
- package/lib/chrome/ui/navItem.d.ts.map +1 -1
- package/lib/chrome/ui/navItem.js.map +1 -1
- package/lib/citation/ui/entries.d.ts.map +1 -1
- package/lib/citation/ui/entries.js +6 -1
- package/lib/citation/ui/entries.js.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.d.ts +11 -0
- package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.js +28 -10
- package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
- package/lib/clipboard/ui/CopyButton.d.ts +2 -1
- package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
- package/lib/clipboard/ui/CopyButton.js +2 -1
- package/lib/clipboard/ui/CopyButton.js.map +1 -1
- package/lib/clipboard/ui/CopyableValue.d.ts +24 -4
- package/lib/clipboard/ui/CopyableValue.d.ts.map +1 -1
- package/lib/clipboard/ui/CopyableValue.js +35 -3
- package/lib/clipboard/ui/CopyableValue.js.map +1 -1
- package/lib/clipboard/ui/index.d.ts +1 -1
- package/lib/clipboard/ui/index.d.ts.map +1 -1
- package/lib/clipboard/ui/index.js.map +1 -1
- package/lib/confirm/ui/ConfirmButton.d.ts.map +1 -1
- package/lib/confirm/ui/ConfirmButton.js +2 -1
- package/lib/confirm/ui/ConfirmButton.js.map +1 -1
- package/lib/delimited/ui/TableDataButton.d.ts.map +1 -1
- package/lib/delimited/ui/TableDataButton.js +2 -1
- package/lib/delimited/ui/TableDataButton.js.map +1 -1
- package/lib/download/core/figureSvg.d.ts.map +1 -1
- package/lib/download/core/figureSvg.js +37 -12
- package/lib/download/core/figureSvg.js.map +1 -1
- package/lib/help/ui/HelpToolbarButton.d.ts.map +1 -1
- package/lib/help/ui/HelpToolbarButton.js +2 -1
- package/lib/help/ui/HelpToolbarButton.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +39 -4
- package/lib/locales/en.json +39 -4
- package/lib/locales/es.json +39 -4
- package/lib/locales/fr.json +39 -4
- package/lib/locales/it.json +39 -4
- package/lib/number/ui/NumberInput.js +1 -1
- package/lib/number/ui/NumberInput.js.map +1 -1
- package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayIconButton.js +1 -1
- package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
- package/lib/panel/ui/ClearButton.d.ts.map +1 -1
- package/lib/panel/ui/ClearButton.js +1 -1
- package/lib/panel/ui/ClearButton.js.map +1 -1
- package/lib/pedagogy/core/inlineMarks.d.ts +9 -5
- package/lib/pedagogy/core/inlineMarks.d.ts.map +1 -1
- package/lib/pedagogy/core/inlineMarks.js +14 -6
- package/lib/pedagogy/core/inlineMarks.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryDefinition.d.ts +4 -10
- package/lib/pedagogy/ui/GlossaryDefinition.d.ts.map +1 -1
- package/lib/pedagogy/ui/GlossaryDefinition.js +7 -7
- package/lib/pedagogy/ui/GlossaryDefinition.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.d.ts.map +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.js +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryProvider.d.ts +7 -0
- package/lib/pedagogy/ui/GlossaryProvider.d.ts.map +1 -1
- package/lib/pedagogy/ui/GlossaryProvider.js +3 -2
- package/lib/pedagogy/ui/GlossaryProvider.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryText.d.ts +7 -2
- package/lib/pedagogy/ui/GlossaryText.d.ts.map +1 -1
- package/lib/pedagogy/ui/GlossaryText.js +3 -2
- package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
- package/lib/pedagogy/ui/InlineText.d.ts +8 -2
- package/lib/pedagogy/ui/InlineText.d.ts.map +1 -1
- package/lib/pedagogy/ui/InlineText.js +7 -2
- package/lib/pedagogy/ui/InlineText.js.map +1 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts +4 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.d.ts.map +1 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.js +2 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.d.ts.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.js +11 -10
- package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js +18 -4
- package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
- package/lib/pedagogy/ui/glossaryContext.d.ts +7 -0
- package/lib/pedagogy/ui/glossaryContext.d.ts.map +1 -1
- package/lib/pedagogy/ui/glossaryContext.js.map +1 -1
- package/lib/pedagogy/ui/index.d.ts +1 -1
- package/lib/pedagogy/ui/index.d.ts.map +1 -1
- package/lib/pedagogy/ui/inlineSegments.d.ts +5 -0
- package/lib/pedagogy/ui/inlineSegments.d.ts.map +1 -1
- package/lib/pedagogy/ui/inlineSegments.js +2 -0
- package/lib/pedagogy/ui/inlineSegments.js.map +1 -1
- package/lib/pedagogy/ui/pedagogyStyle.d.ts +8 -9
- package/lib/pedagogy/ui/pedagogyStyle.d.ts.map +1 -1
- package/lib/pedagogy/ui/pedagogyStyle.js +10 -18
- package/lib/pedagogy/ui/pedagogyStyle.js.map +1 -1
- package/lib/seo/core/plainDescription.d.ts +2 -2
- package/lib/seo/core/plainDescription.js +5 -2
- package/lib/seo/core/plainDescription.js.map +1 -1
- package/lib/share/core/devices.d.ts +41 -0
- package/lib/share/core/devices.d.ts.map +1 -0
- package/lib/share/core/devices.js +58 -0
- package/lib/share/core/devices.js.map +1 -0
- package/lib/share/core/index.d.ts +4 -0
- package/lib/share/core/index.d.ts.map +1 -1
- package/lib/share/core/index.js +2 -0
- package/lib/share/core/index.js.map +1 -1
- package/lib/share/core/regions.d.ts +43 -0
- package/lib/share/core/regions.d.ts.map +1 -0
- package/lib/share/core/regions.js +71 -0
- package/lib/share/core/regions.js.map +1 -0
- package/lib/share/ui/HiddenPartsProvider.d.ts.map +1 -1
- package/lib/share/ui/HiddenPartsProvider.js +4 -1
- package/lib/share/ui/HiddenPartsProvider.js.map +1 -1
- package/lib/share/ui/PagePart.d.ts.map +1 -1
- package/lib/share/ui/PagePart.js +10 -1
- package/lib/share/ui/PagePart.js.map +1 -1
- package/lib/share/ui/ShareButton.d.ts.map +1 -1
- package/lib/share/ui/ShareButton.js +3 -4
- package/lib/share/ui/ShareButton.js.map +1 -1
- package/lib/share/ui/ShareConfigOptions.d.ts +6 -1
- package/lib/share/ui/ShareConfigOptions.d.ts.map +1 -1
- package/lib/share/ui/ShareConfigOptions.js +6 -13
- package/lib/share/ui/ShareConfigOptions.js.map +1 -1
- package/lib/share/ui/ShareDialog.d.ts +14 -0
- package/lib/share/ui/ShareDialog.d.ts.map +1 -1
- package/lib/share/ui/ShareDialog.js +3 -3
- package/lib/share/ui/ShareDialog.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts +2 -2
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +36 -30
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/ShareLinkBar.d.ts +21 -0
- package/lib/share/ui/ShareLinkBar.d.ts.map +1 -0
- package/lib/share/ui/ShareLinkBar.js +32 -0
- package/lib/share/ui/ShareLinkBar.js.map +1 -0
- package/lib/share/ui/SharePanes.d.ts +17 -0
- package/lib/share/ui/SharePanes.d.ts.map +1 -0
- package/lib/share/ui/SharePanes.js +27 -0
- package/lib/share/ui/SharePanes.js.map +1 -0
- package/lib/share/ui/SharePartOptions.d.ts +9 -3
- package/lib/share/ui/SharePartOptions.d.ts.map +1 -1
- package/lib/share/ui/SharePartOptions.js +12 -16
- package/lib/share/ui/SharePartOptions.js.map +1 -1
- package/lib/share/ui/SharePartRow.d.ts +37 -0
- package/lib/share/ui/SharePartRow.d.ts.map +1 -0
- package/lib/share/ui/SharePartRow.js +25 -0
- package/lib/share/ui/SharePartRow.js.map +1 -0
- package/lib/share/ui/SharePresetTabs.d.ts +10 -7
- package/lib/share/ui/SharePresetTabs.d.ts.map +1 -1
- package/lib/share/ui/SharePresetTabs.js +17 -17
- package/lib/share/ui/SharePresetTabs.js.map +1 -1
- package/lib/share/ui/SharePreview.d.ts +32 -0
- package/lib/share/ui/SharePreview.d.ts.map +1 -0
- package/lib/share/ui/SharePreview.js +104 -0
- package/lib/share/ui/SharePreview.js.map +1 -0
- package/lib/share/ui/SharePreviewDevices.d.ts +20 -0
- package/lib/share/ui/SharePreviewDevices.d.ts.map +1 -0
- package/lib/share/ui/SharePreviewDevices.js +40 -0
- package/lib/share/ui/SharePreviewDevices.js.map +1 -0
- package/lib/share/ui/index.d.ts +1 -0
- package/lib/share/ui/index.d.ts.map +1 -1
- package/lib/share/ui/index.js.map +1 -1
- package/lib/share/ui/shareMarking.d.ts +14 -0
- package/lib/share/ui/shareMarking.d.ts.map +1 -0
- package/lib/share/ui/shareMarking.js +17 -0
- package/lib/share/ui/shareMarking.js.map +1 -0
- package/lib/share/ui/useShareRegionReports.d.ts +12 -0
- package/lib/share/ui/useShareRegionReports.d.ts.map +1 -0
- package/lib/share/ui/useShareRegionReports.js +73 -0
- package/lib/share/ui/useShareRegionReports.js.map +1 -0
- package/lib/shared/ui/ActionTooltip.d.ts +42 -0
- package/lib/shared/ui/ActionTooltip.d.ts.map +1 -0
- package/lib/shared/ui/ActionTooltip.js +21 -0
- package/lib/shared/ui/ActionTooltip.js.map +1 -0
- package/lib/shared/ui/index.d.ts +4 -0
- package/lib/shared/ui/index.d.ts.map +1 -1
- package/lib/shared/ui/index.js +2 -0
- package/lib/shared/ui/index.js.map +1 -1
- package/lib/shared/ui/useActionTooltip.d.ts +48 -0
- package/lib/shared/ui/useActionTooltip.d.ts.map +1 -0
- package/lib/shared/ui/useActionTooltip.js +78 -0
- package/lib/shared/ui/useActionTooltip.js.map +1 -0
- package/lib/structure/core/index.d.ts +2 -0
- package/lib/structure/core/index.d.ts.map +1 -1
- package/lib/structure/core/index.js +1 -0
- package/lib/structure/core/index.js.map +1 -1
- package/lib/structure/core/sourceMolecule.d.ts +18 -0
- package/lib/structure/core/sourceMolecule.d.ts.map +1 -0
- package/lib/structure/core/sourceMolecule.js +26 -0
- package/lib/structure/core/sourceMolecule.js.map +1 -0
- package/lib/structure/core/structureNotations.d.ts +57 -0
- package/lib/structure/core/structureNotations.d.ts.map +1 -0
- package/lib/structure/core/structureNotations.js +74 -0
- package/lib/structure/core/structureNotations.js.map +1 -0
- package/lib/structure/core/structurePicture.d.ts +49 -0
- package/lib/structure/core/structurePicture.d.ts.map +1 -0
- package/lib/structure/core/structurePicture.js +83 -0
- package/lib/structure/core/structurePicture.js.map +1 -0
- package/lib/structure/ui/EditorExportButton.d.ts +23 -0
- package/lib/structure/ui/EditorExportButton.d.ts.map +1 -0
- package/lib/structure/ui/EditorExportButton.js +23 -0
- package/lib/structure/ui/EditorExportButton.js.map +1 -0
- package/lib/structure/ui/EditorHelpButton.d.ts +5 -2
- package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -1
- package/lib/structure/ui/EditorHelpButton.js +8 -9
- package/lib/structure/ui/EditorHelpButton.js.map +1 -1
- package/lib/structure/ui/StructureEditor.d.ts +8 -0
- package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
- package/lib/structure/ui/StructureEditor.js +26 -3
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructureExportDialog.d.ts +34 -0
- package/lib/structure/ui/StructureExportDialog.d.ts.map +1 -0
- package/lib/structure/ui/StructureExportDialog.js +43 -0
- package/lib/structure/ui/StructureExportDialog.js.map +1 -0
- package/lib/structure/ui/StructureExportPanel.d.ts +37 -0
- package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -0
- package/lib/structure/ui/StructureExportPanel.js +75 -0
- package/lib/structure/ui/StructureExportPanel.js.map +1 -0
- package/lib/structure/ui/StructureNotationRow.d.ts +22 -0
- package/lib/structure/ui/StructureNotationRow.d.ts.map +1 -0
- package/lib/structure/ui/StructureNotationRow.js +34 -0
- package/lib/structure/ui/StructureNotationRow.js.map +1 -0
- package/lib/structure/ui/StructurePicturePane.d.ts +23 -0
- package/lib/structure/ui/StructurePicturePane.d.ts.map +1 -0
- package/lib/structure/ui/StructurePicturePane.js +113 -0
- package/lib/structure/ui/StructurePicturePane.js.map +1 -0
- package/lib/structure/ui/StructureSvg.d.ts.map +1 -1
- package/lib/structure/ui/StructureSvg.js +2 -18
- package/lib/structure/ui/StructureSvg.js.map +1 -1
- package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -1
- package/lib/structure/ui/ToolbarTooltip.js +1 -2
- package/lib/structure/ui/ToolbarTooltip.js.map +1 -1
- package/lib/structure/ui/index.d.ts +7 -3
- package/lib/structure/ui/index.d.ts.map +1 -1
- package/lib/structure/ui/index.js +5 -3
- package/lib/structure/ui/index.js.map +1 -1
- package/lib/structure/ui/structureNotationText.d.ts +23 -0
- package/lib/structure/ui/structureNotationText.d.ts.map +1 -0
- package/lib/structure/ui/structureNotationText.js +42 -0
- package/lib/structure/ui/structureNotationText.js.map +1 -0
- package/lib/structure/ui/useEditorStructure.d.ts +37 -0
- package/lib/structure/ui/useEditorStructure.d.ts.map +1 -0
- package/lib/structure/ui/useEditorStructure.js +70 -0
- package/lib/structure/ui/useEditorStructure.js.map +1 -0
- package/package.json +1 -1
- package/src/chart/ui/Axes.tsx +53 -5
- package/src/chrome/ui/NavMenuButton.tsx +61 -18
- package/src/chrome/ui/SiteHeader.tsx +37 -1
- package/src/chrome/ui/navItem.ts +7 -0
- package/src/citation/ui/entries.tsx +10 -2
- package/src/clipboard/ui/ClickToCopy.tsx +47 -13
- package/src/clipboard/ui/CopyButton.tsx +4 -2
- package/src/clipboard/ui/CopyableValue.tsx +88 -13
- package/src/clipboard/ui/index.ts +5 -1
- package/src/confirm/ui/ConfirmButton.tsx +2 -0
- package/src/delimited/ui/TableDataButton.tsx +2 -1
- package/src/download/core/figureSvg.ts +42 -10
- package/src/help/ui/HelpToolbarButton.tsx +2 -1
- package/src/locales/de.json +39 -4
- package/src/locales/en.json +39 -4
- package/src/locales/es.json +39 -4
- package/src/locales/fr.json +39 -4
- package/src/locales/it.json +39 -4
- package/src/number/ui/NumberInput.tsx +1 -1
- package/src/overlay/ui/OverlayIconButton.tsx +1 -0
- package/src/panel/ui/ClearButton.tsx +1 -0
- package/src/pedagogy/core/inlineMarks.ts +15 -7
- package/src/pedagogy/ui/GlossaryDefinition.tsx +8 -18
- package/src/pedagogy/ui/GlossaryIndex.tsx +0 -1
- package/src/pedagogy/ui/GlossaryProvider.tsx +10 -2
- package/src/pedagogy/ui/GlossaryText.tsx +10 -2
- package/src/pedagogy/ui/InlineText.tsx +21 -4
- package/src/pedagogy/ui/ReferenceSectionBlock.tsx +8 -2
- package/src/pedagogy/ui/SyntaxTooltip.tsx +11 -9
- package/src/pedagogy/ui/TutorialStepStrip.tsx +50 -18
- package/src/pedagogy/ui/glossaryContext.ts +7 -0
- package/src/pedagogy/ui/index.ts +1 -1
- package/src/pedagogy/ui/inlineSegments.tsx +7 -0
- package/src/pedagogy/ui/pedagogyStyle.ts +12 -24
- package/src/seo/core/plainDescription.ts +4 -2
- package/src/share/core/devices.ts +80 -0
- package/src/share/core/index.ts +14 -0
- package/src/share/core/regions.ts +87 -0
- package/src/share/ui/HiddenPartsProvider.tsx +6 -1
- package/src/share/ui/PagePart.tsx +12 -1
- package/src/share/ui/ShareButton.tsx +3 -6
- package/src/share/ui/ShareConfigOptions.tsx +25 -16
- package/src/share/ui/ShareDialog.tsx +17 -3
- package/src/share/ui/ShareDialogContent.tsx +80 -77
- package/src/share/ui/ShareLinkBar.tsx +70 -0
- package/src/share/ui/SharePanes.tsx +62 -0
- package/src/share/ui/SharePartOptions.tsx +25 -26
- package/src/share/ui/SharePartRow.tsx +78 -0
- package/src/share/ui/SharePresetTabs.tsx +36 -43
- package/src/share/ui/SharePreview.tsx +204 -0
- package/src/share/ui/SharePreviewDevices.tsx +70 -0
- package/src/share/ui/index.ts +1 -0
- package/src/share/ui/shareMarking.ts +18 -0
- package/src/share/ui/useShareRegionReports.ts +84 -0
- package/src/shared/ui/ActionTooltip.tsx +70 -0
- package/src/shared/ui/index.ts +7 -0
- package/src/shared/ui/useActionTooltip.ts +103 -0
- package/src/structure/core/index.ts +5 -0
- package/src/structure/core/sourceMolecule.ts +26 -0
- package/src/structure/core/structureNotations.ts +125 -0
- package/src/structure/core/structurePicture.ts +119 -0
- package/src/structure/ui/EditorExportButton.tsx +62 -0
- package/src/structure/ui/EditorHelpButton.tsx +26 -30
- package/src/structure/ui/StructureEditor.tsx +51 -5
- package/src/structure/ui/StructureExportDialog.tsx +90 -0
- package/src/structure/ui/StructureExportPanel.tsx +156 -0
- package/src/structure/ui/StructureNotationRow.tsx +74 -0
- package/src/structure/ui/StructurePicturePane.tsx +208 -0
- package/src/structure/ui/StructureSvg.tsx +3 -12
- package/src/structure/ui/ToolbarTooltip.tsx +1 -1
- package/src/structure/ui/index.ts +7 -3
- package/src/structure/ui/structureNotationText.ts +56 -0
- package/src/structure/ui/useEditorStructure.ts +119 -0
- package/styles/chrome.css +414 -14
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { Tab, Tabs } from '@blueprintjs/core';
|
|
2
|
-
import type {
|
|
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
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
|
35
|
+
const { presets, selected, onSelect } = props;
|
|
39
36
|
const t = useChromeT();
|
|
40
37
|
|
|
41
38
|
return (
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
}
|
package/src/share/ui/index.ts
CHANGED
|
@@ -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;
|
package/src/shared/ui/index.ts
CHANGED
|
@@ -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,
|