react-cheminfo 0.21.0 → 0.23.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/README.md +14 -4
- package/lib/build/vite/gitHead.d.ts.map +1 -1
- package/lib/build/vite/gitHead.js +17 -2
- package/lib/build/vite/gitHead.js.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.d.ts +69 -0
- package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -0
- package/lib/clipboard/ui/ClickToCopy.js +66 -0
- package/lib/clipboard/ui/ClickToCopy.js.map +1 -0
- package/lib/clipboard/ui/CopyButton.d.ts +1 -1
- package/lib/clipboard/ui/CopyButton.js +1 -1
- package/lib/clipboard/ui/CopyableValue.d.ts +3 -3
- package/lib/clipboard/ui/CopyableValue.d.ts.map +1 -1
- package/lib/clipboard/ui/CopyableValue.js +5 -5
- package/lib/clipboard/ui/CopyableValue.js.map +1 -1
- package/lib/clipboard/ui/index.d.ts +2 -0
- package/lib/clipboard/ui/index.d.ts.map +1 -1
- package/lib/clipboard/ui/index.js +1 -0
- package/lib/clipboard/ui/index.js.map +1 -1
- package/lib/ecosystem/core/groups.d.ts +4 -3
- package/lib/ecosystem/core/groups.d.ts.map +1 -1
- package/lib/ecosystem/core/groups.js +8 -2
- package/lib/ecosystem/core/groups.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +39 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +9 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/error/ui/ErrorFallback.d.ts.map +1 -1
- package/lib/error/ui/ErrorFallback.js +1 -1
- package/lib/error/ui/ErrorFallback.js.map +1 -1
- package/lib/overlay/core/index.d.ts +2 -2
- package/lib/overlay/core/index.d.ts.map +1 -1
- package/lib/overlay/core/index.js +1 -1
- package/lib/overlay/core/index.js.map +1 -1
- package/lib/overlay/core/overlayMarks.d.ts +20 -5
- package/lib/overlay/core/overlayMarks.d.ts.map +1 -1
- package/lib/overlay/core/overlayMarks.js +31 -5
- package/lib/overlay/core/overlayMarks.js.map +1 -1
- package/lib/overlay/ui/OverlayLegendMark.js +5 -0
- package/lib/overlay/ui/OverlayLegendMark.js.map +1 -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/projection/core/index.d.ts +4 -4
- package/lib/projection/core/index.d.ts.map +1 -1
- package/lib/projection/core/index.js +2 -2
- package/lib/projection/core/index.js.map +1 -1
- package/lib/projection/core/projectionCopy.d.ts +2 -0
- package/lib/projection/core/projectionCopy.d.ts.map +1 -1
- package/lib/projection/core/projectionCopy.js +1 -0
- package/lib/projection/core/projectionCopy.js.map +1 -1
- package/lib/projection/core/projectionGroupings.d.ts +44 -0
- package/lib/projection/core/projectionGroupings.d.ts.map +1 -0
- package/lib/projection/core/projectionGroupings.js +132 -0
- package/lib/projection/core/projectionGroupings.js.map +1 -0
- package/lib/projection/core/projectionOptions.d.ts +16 -6
- package/lib/projection/core/projectionOptions.d.ts.map +1 -1
- package/lib/projection/core/projectionOptions.js +7 -1
- package/lib/projection/core/projectionOptions.js.map +1 -1
- package/lib/projection/core/projectionPanelWords.d.ts +6 -0
- package/lib/projection/core/projectionPanelWords.d.ts.map +1 -1
- package/lib/projection/core/projectionPanelWords.js +4 -0
- package/lib/projection/core/projectionPanelWords.js.map +1 -1
- package/lib/projection/core/projectionSamples.d.ts +63 -32
- package/lib/projection/core/projectionSamples.d.ts.map +1 -1
- package/lib/projection/core/projectionSamples.js +1 -80
- package/lib/projection/core/projectionSamples.js.map +1 -1
- package/lib/projection/core/projectionStrings.d.ts +2 -0
- package/lib/projection/core/projectionStrings.d.ts.map +1 -1
- package/lib/projection/core/projectionStrings.js +10 -1
- package/lib/projection/core/projectionStrings.js.map +1 -1
- package/lib/projection/core/resolveProjectionOptions.d.ts +8 -3
- package/lib/projection/core/resolveProjectionOptions.d.ts.map +1 -1
- package/lib/projection/core/resolveProjectionOptions.js +58 -5
- package/lib/projection/core/resolveProjectionOptions.js.map +1 -1
- package/lib/projection/ui/ProjectionBar.d.ts +1 -1
- package/lib/projection/ui/ProjectionBar.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionBar.js +3 -1
- package/lib/projection/ui/ProjectionBar.js.map +1 -1
- package/lib/projection/ui/ProjectionGroupingPickers.d.ts +38 -0
- package/lib/projection/ui/ProjectionGroupingPickers.d.ts.map +1 -0
- package/lib/projection/ui/ProjectionGroupingPickers.js +33 -0
- package/lib/projection/ui/ProjectionGroupingPickers.js.map +1 -0
- package/lib/projection/ui/ProjectionMapControls.d.ts +6 -7
- package/lib/projection/ui/ProjectionMapControls.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionMapControls.js +11 -9
- package/lib/projection/ui/ProjectionMapControls.js.map +1 -1
- package/lib/projection/ui/ProjectionMapLegend.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionMapLegend.js +1 -1
- package/lib/projection/ui/ProjectionMapLegend.js.map +1 -1
- package/lib/projection/ui/ProjectionMapMore.d.ts +2 -2
- package/lib/projection/ui/ProjectionMapMore.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionMapMore.js +7 -5
- package/lib/projection/ui/ProjectionMapMore.js.map +1 -1
- package/lib/projection/ui/ProjectionMapTab.d.ts +15 -6
- package/lib/projection/ui/ProjectionMapTab.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionMapTab.js +11 -7
- package/lib/projection/ui/ProjectionMapTab.js.map +1 -1
- package/lib/projection/ui/ProjectionPairsMore.d.ts +3 -7
- package/lib/projection/ui/ProjectionPairsMore.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionPairsMore.js +4 -5
- package/lib/projection/ui/ProjectionPairsMore.js.map +1 -1
- package/lib/projection/ui/ProjectionPairsTab.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionPairsTab.js +2 -2
- package/lib/projection/ui/ProjectionPairsTab.js.map +1 -1
- package/lib/projection/ui/ProjectionPanel.d.ts +1 -1
- package/lib/projection/ui/ProjectionPanel.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionPanel.js +1 -1
- package/lib/projection/ui/ProjectionPanel.js.map +1 -1
- package/lib/projection/ui/ProjectionReadout.d.ts +7 -1
- package/lib/projection/ui/ProjectionReadout.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionReadout.js +19 -7
- package/lib/projection/ui/ProjectionReadout.js.map +1 -1
- package/lib/projection/ui/ProjectionSpaceControls.d.ts +3 -5
- package/lib/projection/ui/ProjectionSpaceControls.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionSpaceControls.js +9 -7
- package/lib/projection/ui/ProjectionSpaceControls.js.map +1 -1
- package/lib/projection/ui/ProjectionSpaceMore.d.ts +2 -2
- package/lib/projection/ui/ProjectionSpaceMore.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionSpaceMore.js +5 -4
- package/lib/projection/ui/ProjectionSpaceMore.js.map +1 -1
- package/lib/projection/ui/ProjectionSpaceTab.d.ts +11 -3
- package/lib/projection/ui/ProjectionSpaceTab.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionSpaceTab.js +7 -4
- package/lib/projection/ui/ProjectionSpaceTab.js.map +1 -1
- package/lib/projection/ui/ProjectionViewer.d.ts.map +1 -1
- package/lib/projection/ui/ProjectionViewer.js +1 -0
- package/lib/projection/ui/ProjectionViewer.js.map +1 -1
- package/lib/projection/ui/projectionBarReadings.d.ts +4 -1
- package/lib/projection/ui/projectionBarReadings.d.ts.map +1 -1
- package/lib/projection/ui/projectionBarReadings.js +14 -4
- package/lib/projection/ui/projectionBarReadings.js.map +1 -1
- package/lib/projection/ui/projectionBarSlots.d.ts +4 -2
- package/lib/projection/ui/projectionBarSlots.d.ts.map +1 -1
- package/lib/projection/ui/projectionBarSlots.js +3 -7
- package/lib/projection/ui/projectionBarSlots.js.map +1 -1
- package/lib/projection/ui/projectionMapChoices.d.ts +31 -6
- package/lib/projection/ui/projectionMapChoices.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapChoices.js +58 -9
- package/lib/projection/ui/projectionMapChoices.js.map +1 -1
- package/lib/projection/ui/projectionMapChrome.d.ts +3 -9
- package/lib/projection/ui/projectionMapChrome.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapChrome.js +41 -27
- package/lib/projection/ui/projectionMapChrome.js.map +1 -1
- package/lib/projection/ui/projectionMapCorner.d.ts +25 -0
- package/lib/projection/ui/projectionMapCorner.d.ts.map +1 -0
- package/lib/projection/ui/projectionMapCorner.js +46 -0
- package/lib/projection/ui/projectionMapCorner.js.map +1 -0
- package/lib/projection/ui/projectionMapModel.d.ts +9 -18
- package/lib/projection/ui/projectionMapModel.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapModel.js +14 -39
- package/lib/projection/ui/projectionMapModel.js.map +1 -1
- package/lib/projection/ui/projectionMapView.d.ts +3 -1
- package/lib/projection/ui/projectionMapView.d.ts.map +1 -1
- package/lib/projection/ui/projectionMapView.js +9 -4
- package/lib/projection/ui/projectionMapView.js.map +1 -1
- package/lib/projection/ui/projectionSelectionSentence.d.ts +10 -0
- package/lib/projection/ui/projectionSelectionSentence.d.ts.map +1 -0
- package/lib/projection/ui/projectionSelectionSentence.js +18 -0
- package/lib/projection/ui/projectionSelectionSentence.js.map +1 -0
- package/lib/projection/ui/projectionStateApi.d.ts +47 -0
- package/lib/projection/ui/projectionStateApi.d.ts.map +1 -0
- package/lib/projection/ui/projectionStateApi.js +9 -0
- package/lib/projection/ui/projectionStateApi.js.map +1 -0
- package/lib/projection/ui/projectionTabModels.js +1 -1
- package/lib/projection/ui/projectionTabModels.js.map +1 -1
- package/lib/projection/ui/useProjectionState.d.ts +1 -29
- package/lib/projection/ui/useProjectionState.d.ts.map +1 -1
- package/lib/projection/ui/useProjectionState.js +13 -5
- package/lib/projection/ui/useProjectionState.js.map +1 -1
- package/lib/scatter/core/index.d.ts +1 -0
- package/lib/scatter/core/index.d.ts.map +1 -1
- package/lib/scatter/core/index.js +1 -0
- package/lib/scatter/core/index.js.map +1 -1
- package/lib/scatter/core/pointShape.d.ts +18 -0
- package/lib/scatter/core/pointShape.d.ts.map +1 -0
- package/lib/scatter/core/pointShape.js +70 -0
- package/lib/scatter/core/pointShape.js.map +1 -0
- package/lib/scatter/ui/ScatterPlot.d.ts.map +1 -1
- package/lib/scatter/ui/ScatterPlot.js +3 -2
- package/lib/scatter/ui/ScatterPlot.js.map +1 -1
- package/lib/scatter/ui/ScatterPointLayer.d.ts +14 -1
- package/lib/scatter/ui/ScatterPointLayer.d.ts.map +1 -1
- package/lib/scatter/ui/ScatterPointLayer.js +15 -3
- package/lib/scatter/ui/ScatterPointLayer.js.map +1 -1
- package/lib/scatter/ui/scatterFigureProps.d.ts +13 -0
- package/lib/scatter/ui/scatterFigureProps.d.ts.map +1 -1
- package/lib/scatter/ui/scatterMarkGlyph.d.ts +2 -1
- package/lib/scatter/ui/scatterMarkGlyph.d.ts.map +1 -1
- package/lib/scatter/ui/scatterMarkGlyph.js +7 -1
- package/lib/scatter/ui/scatterMarkGlyph.js.map +1 -1
- package/lib/scatter3d/ui/ScatterCloud.d.ts.map +1 -1
- package/lib/scatter3d/ui/ScatterCloud.js +2 -1
- package/lib/scatter3d/ui/ScatterCloud.js.map +1 -1
- package/package.json +1 -1
- package/src/build/vite/gitHead.ts +17 -2
- package/src/clipboard/ui/ClickToCopy.tsx +190 -0
- package/src/clipboard/ui/CopyButton.tsx +2 -2
- package/src/clipboard/ui/CopyableValue.tsx +11 -6
- package/src/clipboard/ui/index.ts +2 -0
- package/src/ecosystem/core/groups.ts +10 -3
- package/src/ecosystem/core/sites.ts +39 -0
- package/src/ecosystem/core/types.ts +4 -1
- package/src/ecosystem/ui/glyphs.tsx +51 -0
- package/src/error/ui/ErrorFallback.tsx +3 -1
- package/src/overlay/core/index.ts +6 -2
- package/src/overlay/core/overlayMarks.ts +51 -6
- package/src/overlay/ui/OverlayLegendMark.tsx +5 -0
- package/src/pedagogy/ui/ReferenceSectionBlock.tsx +10 -1
- package/src/projection/core/index.ts +11 -3
- package/src/projection/core/projectionCopy.ts +3 -0
- package/src/projection/core/projectionGroupings.ts +174 -0
- package/src/projection/core/projectionOptions.ts +18 -7
- package/src/projection/core/projectionPanelWords.ts +10 -0
- package/src/projection/core/projectionSamples.ts +62 -120
- package/src/projection/core/projectionStrings.ts +12 -1
- package/src/projection/core/resolveProjectionOptions.ts +66 -4
- package/src/projection/ui/ProjectionBar.tsx +4 -2
- package/src/projection/ui/ProjectionGroupingPickers.tsx +91 -0
- package/src/projection/ui/ProjectionMapControls.tsx +29 -32
- package/src/projection/ui/ProjectionMapLegend.tsx +1 -1
- package/src/projection/ui/ProjectionMapMore.tsx +14 -4
- package/src/projection/ui/ProjectionMapTab.tsx +27 -9
- package/src/projection/ui/ProjectionPairsMore.tsx +12 -21
- package/src/projection/ui/ProjectionPairsTab.tsx +5 -2
- package/src/projection/ui/ProjectionPanel.tsx +7 -3
- package/src/projection/ui/ProjectionReadout.tsx +28 -8
- package/src/projection/ui/ProjectionSpaceControls.tsx +20 -27
- package/src/projection/ui/ProjectionSpaceMore.tsx +12 -3
- package/src/projection/ui/ProjectionSpaceTab.tsx +23 -6
- package/src/projection/ui/ProjectionViewer.tsx +1 -0
- package/src/projection/ui/projectionBarReadings.ts +24 -8
- package/src/projection/ui/projectionBarSlots.tsx +10 -9
- package/src/projection/ui/projectionMapChoices.ts +71 -14
- package/src/projection/ui/projectionMapChrome.ts +55 -30
- package/src/projection/ui/projectionMapCorner.ts +67 -0
- package/src/projection/ui/projectionMapModel.ts +20 -59
- package/src/projection/ui/projectionMapView.ts +15 -8
- package/src/projection/ui/projectionSelectionSentence.ts +22 -0
- package/src/projection/ui/projectionStateApi.ts +57 -0
- package/src/projection/ui/projectionTabModels.ts +1 -1
- package/src/projection/ui/useProjectionState.ts +33 -37
- package/src/scatter/core/index.ts +1 -0
- package/src/scatter/core/pointShape.ts +80 -0
- package/src/scatter/ui/ScatterPlot.tsx +4 -1
- package/src/scatter/ui/ScatterPointLayer.tsx +39 -11
- package/src/scatter/ui/scatterFigureProps.ts +13 -0
- package/src/scatter/ui/scatterMarkGlyph.tsx +9 -1
- package/src/scatter3d/ui/ScatterCloud.tsx +3 -0
- package/styles/chrome.css +163 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the one hook every tab shares hands out.
|
|
3
|
+
*
|
|
4
|
+
* The type lives beside the hook rather than inside it because the hook is
|
|
5
|
+
* short and its answer is not: a reader looking for how the state is kept
|
|
6
|
+
* should not have to scroll past a page of documented fields to find it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ScatterSelectionMode } from '../../scatter/core/scatterSelection.ts';
|
|
10
|
+
import type { SelectionChange } from '../../scatter/ui/useScatterSelection.ts';
|
|
11
|
+
import type { ProjectionOptions } from '../core/projectionOptions.ts';
|
|
12
|
+
import type {
|
|
13
|
+
ProjectionGrouping,
|
|
14
|
+
ResolvedProjectionGroups,
|
|
15
|
+
ResolvedProjectionShapes,
|
|
16
|
+
} from '../core/projectionSamples.ts';
|
|
17
|
+
import type { ProjectionTab } from '../core/projectionTabs.ts';
|
|
18
|
+
|
|
19
|
+
import type { ProjectionSelection } from './projectionSelection.ts';
|
|
20
|
+
|
|
21
|
+
/** What every tab reads, and the five ways any of them changes it. */
|
|
22
|
+
export interface ProjectionStateApi {
|
|
23
|
+
/** The tabs this result can fill, in reading order. */
|
|
24
|
+
tabs: readonly ProjectionTab[];
|
|
25
|
+
/** The one showing. */
|
|
26
|
+
tab: ProjectionTab;
|
|
27
|
+
/** Move to another tab. One the result cannot fill is ignored. */
|
|
28
|
+
setTab: (tab: ProjectionTab) => void;
|
|
29
|
+
/**
|
|
30
|
+
* The grouping the dots are coloured by, resolved once per change; empty
|
|
31
|
+
* while the colour stands for nothing.
|
|
32
|
+
*/
|
|
33
|
+
groups: ResolvedProjectionGroups;
|
|
34
|
+
/**
|
|
35
|
+
* The grouping the dots are shaped by, resolved once per change, or `null`
|
|
36
|
+
* while they are all discs.
|
|
37
|
+
*/
|
|
38
|
+
shapes: ResolvedProjectionShapes | null;
|
|
39
|
+
/** Every grouping the samples carry, for the pickers that choose between them. */
|
|
40
|
+
groupings: readonly ProjectionGrouping[];
|
|
41
|
+
/** Every option, already made safe against the result. */
|
|
42
|
+
options: ProjectionOptions;
|
|
43
|
+
/** Change some of them; the rest are carried over and checked again. */
|
|
44
|
+
setOptions: (patch: Partial<ProjectionOptions>) => void;
|
|
45
|
+
/** The selected rows, as indices, which is what a plot draws. */
|
|
46
|
+
selected: readonly number[];
|
|
47
|
+
/** Settle a selection a plot's own gesture produced. */
|
|
48
|
+
settleSelection: (change: SelectionChange) => void;
|
|
49
|
+
/** Settle one from elsewhere — a legend entry, a table beside the figure. */
|
|
50
|
+
selectRows: (
|
|
51
|
+
rows: readonly number[],
|
|
52
|
+
mode: ScatterSelectionMode,
|
|
53
|
+
source: ProjectionSelection['source'],
|
|
54
|
+
) => void;
|
|
55
|
+
/** Draw one pair of axes on the map, which is what a cell of the grid does. */
|
|
56
|
+
selectPair: (xAxis: number, yAxis: number) => void;
|
|
57
|
+
}
|
|
@@ -2,9 +2,13 @@ import { useCallback, useMemo, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { ScatterSelectionMode } from '../../scatter/core/scatterSelection.ts';
|
|
4
4
|
import type { SelectionChange } from '../../scatter/ui/useScatterSelection.ts';
|
|
5
|
+
import {
|
|
6
|
+
projectionGrouping,
|
|
7
|
+
resolveProjectionGroups,
|
|
8
|
+
resolveProjectionShapes,
|
|
9
|
+
} from '../core/projectionGroupings.ts';
|
|
5
10
|
import type { ProjectionOptions } from '../core/projectionOptions.ts';
|
|
6
|
-
import type {
|
|
7
|
-
import { resolveProjectionGroups } from '../core/projectionSamples.ts';
|
|
11
|
+
import type { ProjectionGrouping } from '../core/projectionSamples.ts';
|
|
8
12
|
import type { ProjectionTab } from '../core/projectionTabs.ts';
|
|
9
13
|
import { projectionTabs } from '../core/projectionTabs.ts';
|
|
10
14
|
import { resolveProjectionOptions } from '../core/resolveProjectionOptions.ts';
|
|
@@ -15,6 +19,7 @@ import {
|
|
|
15
19
|
projectionNamesOf,
|
|
16
20
|
projectionRowsOf,
|
|
17
21
|
} from './projectionSelection.ts';
|
|
22
|
+
import type { ProjectionStateApi } from './projectionStateApi.ts';
|
|
18
23
|
import type { ProjectionViewerProps } from './projectionViewerProps.ts';
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -41,34 +46,6 @@ type ProjectionStateOptions = Pick<
|
|
|
41
46
|
| 'onOptionsChange'
|
|
42
47
|
>;
|
|
43
48
|
|
|
44
|
-
/** What every tab reads, and the five ways any of them changes it. */
|
|
45
|
-
export interface ProjectionStateApi {
|
|
46
|
-
/** The tabs this result can fill, in reading order. */
|
|
47
|
-
tabs: readonly ProjectionTab[];
|
|
48
|
-
/** The one showing. */
|
|
49
|
-
tab: ProjectionTab;
|
|
50
|
-
/** Move to another tab. One the result cannot fill is ignored. */
|
|
51
|
-
setTab: (tab: ProjectionTab) => void;
|
|
52
|
-
/** The groups as every figure draws them, resolved once per change. */
|
|
53
|
-
groups: ResolvedProjectionGroups;
|
|
54
|
-
/** Every option, already made safe against the result. */
|
|
55
|
-
options: ProjectionOptions;
|
|
56
|
-
/** Change some of them; the rest are carried over and checked again. */
|
|
57
|
-
setOptions: (patch: Partial<ProjectionOptions>) => void;
|
|
58
|
-
/** The selected rows, as indices, which is what a plot draws. */
|
|
59
|
-
selected: readonly number[];
|
|
60
|
-
/** Settle a selection a plot's own gesture produced. */
|
|
61
|
-
settleSelection: (change: SelectionChange) => void;
|
|
62
|
-
/** Settle one from elsewhere — a legend entry, a table beside the figure. */
|
|
63
|
-
selectRows: (
|
|
64
|
-
rows: readonly number[],
|
|
65
|
-
mode: ScatterSelectionMode,
|
|
66
|
-
source: ProjectionSelection['source'],
|
|
67
|
-
) => void;
|
|
68
|
-
/** Draw one pair of axes on the map, which is what a cell of the grid does. */
|
|
69
|
-
selectPair: (xAxis: number, yAxis: number) => void;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
49
|
/**
|
|
73
50
|
* The tab, the pair of axes, the selection and the options, controlled or not.
|
|
74
51
|
*
|
|
@@ -121,13 +98,23 @@ export function useProjectionState(
|
|
|
121
98
|
() => defaultOptions ?? {},
|
|
122
99
|
);
|
|
123
100
|
|
|
101
|
+
const groupings = samples.groupings ?? NO_GROUPINGS;
|
|
102
|
+
const settled = useMemo(
|
|
103
|
+
() =>
|
|
104
|
+
resolveProjectionOptions(heldOptions ?? ownOptions, result, groupings),
|
|
105
|
+
[groupings, heldOptions, ownOptions, result],
|
|
106
|
+
);
|
|
107
|
+
const { colorBy, shapeBy } = settled;
|
|
108
|
+
const rowCount = result.scores.rows;
|
|
124
109
|
const groups = useMemo(
|
|
125
|
-
() =>
|
|
126
|
-
|
|
110
|
+
() =>
|
|
111
|
+
resolveProjectionGroups(projectionGrouping(samples, colorBy), rowCount),
|
|
112
|
+
[colorBy, rowCount, samples],
|
|
127
113
|
);
|
|
128
|
-
const
|
|
129
|
-
() =>
|
|
130
|
-
|
|
114
|
+
const shapes = useMemo(
|
|
115
|
+
() =>
|
|
116
|
+
resolveProjectionShapes(projectionGrouping(samples, shapeBy), rowCount),
|
|
117
|
+
[rowCount, samples, shapeBy],
|
|
131
118
|
);
|
|
132
119
|
|
|
133
120
|
const setTab = useCallback(
|
|
@@ -142,11 +129,15 @@ export function useProjectionState(
|
|
|
142
129
|
|
|
143
130
|
const setOptions = useCallback(
|
|
144
131
|
(patch: Partial<ProjectionOptions>) => {
|
|
145
|
-
const next = resolveProjectionOptions(
|
|
132
|
+
const next = resolveProjectionOptions(
|
|
133
|
+
{ ...settled, ...patch },
|
|
134
|
+
result,
|
|
135
|
+
groupings,
|
|
136
|
+
);
|
|
146
137
|
if (heldOptions === undefined) setOwnOptions(next);
|
|
147
138
|
onOptionsChange?.(next);
|
|
148
139
|
},
|
|
149
|
-
[heldOptions, onOptionsChange, result, settled],
|
|
140
|
+
[groupings, heldOptions, onOptionsChange, result, settled],
|
|
150
141
|
);
|
|
151
142
|
|
|
152
143
|
const selectRows = useCallback(
|
|
@@ -189,6 +180,8 @@ export function useProjectionState(
|
|
|
189
180
|
tab: inReach(tab, tabs) ?? inReach(ownTab, tabs) ?? tabs[0] ?? 'map',
|
|
190
181
|
setTab,
|
|
191
182
|
groups,
|
|
183
|
+
shapes,
|
|
184
|
+
groupings,
|
|
192
185
|
options: settled,
|
|
193
186
|
setOptions,
|
|
194
187
|
selected:
|
|
@@ -222,3 +215,6 @@ function inReach(
|
|
|
222
215
|
}
|
|
223
216
|
return undefined;
|
|
224
217
|
}
|
|
218
|
+
|
|
219
|
+
/** No groupings at all, shared so an absent list keeps one identity. */
|
|
220
|
+
const NO_GROUPINGS: readonly ProjectionGrouping[] = [];
|
|
@@ -38,6 +38,7 @@ export {
|
|
|
38
38
|
resetLassoPath,
|
|
39
39
|
} from './lassoPath.ts';
|
|
40
40
|
export { pointInPolygon, pointsInPolygon, polygonBounds } from './polygon.ts';
|
|
41
|
+
export { pointShapePath } from './pointShape.ts';
|
|
41
42
|
export type { ScatterGroupMoments } from './scatterGroupMoments.ts';
|
|
42
43
|
export type {
|
|
43
44
|
ScatterGroupSpread,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { chartRoundPixel } from '../../chart/core/chartScale.ts';
|
|
2
|
+
import type { OverlayMarkShape } from '../../overlay/core/overlayMarks.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The outline of a mark drawn as a filled polygon — a sample shaped by a
|
|
6
|
+
* second grouping, or a marker drawn in one of those shapes — as SVG path data.
|
|
7
|
+
*
|
|
8
|
+
* Every shape covers the area of the disc it stands in for. The eye weighs a
|
|
9
|
+
* mark by its ink, so a square drawn in the disc's own box would make its
|
|
10
|
+
* group read as the heavier one — which is a claim about the data that nothing
|
|
11
|
+
* in the data supports.
|
|
12
|
+
* @param shape - The shape.
|
|
13
|
+
* @param x - Horizontal centre, in pixels.
|
|
14
|
+
* @param y - Vertical centre, in pixels.
|
|
15
|
+
* @param radius - Radius of the disc the shape stands in for, in pixels.
|
|
16
|
+
* @returns The path data, or `null` for a disc, which is drawn as a circle,
|
|
17
|
+
* and for every mark that is not a filled polygon.
|
|
18
|
+
*/
|
|
19
|
+
export function pointShapePath(
|
|
20
|
+
shape: OverlayMarkShape,
|
|
21
|
+
x: number,
|
|
22
|
+
y: number,
|
|
23
|
+
radius: number,
|
|
24
|
+
): string | null {
|
|
25
|
+
if (shape === 'square') {
|
|
26
|
+
const half = SQUARE_HALF * radius;
|
|
27
|
+
return polygon([
|
|
28
|
+
[x - half, y - half],
|
|
29
|
+
[x + half, y - half],
|
|
30
|
+
[x + half, y + half],
|
|
31
|
+
[x - half, y + half],
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
if (shape === 'diamond') {
|
|
35
|
+
const reach = DIAMOND_REACH * radius;
|
|
36
|
+
return polygon([
|
|
37
|
+
[x, y - reach],
|
|
38
|
+
[x + reach, y],
|
|
39
|
+
[x, y + reach],
|
|
40
|
+
[x - reach, y],
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
if (shape === 'triangle' || shape === 'triangle-down') {
|
|
44
|
+
// The centroid sits on the point, so the triangle is balanced on the
|
|
45
|
+
// sample rather than hanging from it.
|
|
46
|
+
const tip = TRIANGLE_TIP * radius;
|
|
47
|
+
const half = TRIANGLE_HALF_BASE * radius;
|
|
48
|
+
const base = (TRIANGLE_TIP / 2) * radius;
|
|
49
|
+
const flip = shape === 'triangle' ? 1 : -1;
|
|
50
|
+
return polygon([
|
|
51
|
+
[x, y - flip * tip],
|
|
52
|
+
[x + half, y + flip * base],
|
|
53
|
+
[x - half, y + flip * base],
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function polygon(corners: ReadonlyArray<readonly [number, number]>): string {
|
|
60
|
+
let path = '';
|
|
61
|
+
for (const [x, y] of corners) {
|
|
62
|
+
path += `${path === '' ? 'M' : 'L'}${chartRoundPixel(x)} ${chartRoundPixel(y)}`;
|
|
63
|
+
}
|
|
64
|
+
return `${path}Z`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Half the side of a square as large as a disc of radius one: √π / 2. */
|
|
68
|
+
const SQUARE_HALF = Math.sqrt(Math.PI) / 2;
|
|
69
|
+
|
|
70
|
+
/** How far a diamond as large as that disc reaches from its centre: √(π / 2). */
|
|
71
|
+
const DIAMOND_REACH = Math.sqrt(Math.PI / 2);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The side of an equilateral triangle as large as that disc is √(4π / √3). Its
|
|
75
|
+
* tip stands side / √3 from the centroid, and its base half as far on the other
|
|
76
|
+
* side.
|
|
77
|
+
*/
|
|
78
|
+
const TRIANGLE_SIDE = Math.sqrt((4 * Math.PI) / Math.sqrt(3));
|
|
79
|
+
const TRIANGLE_TIP = TRIANGLE_SIDE / Math.sqrt(3);
|
|
80
|
+
const TRIANGLE_HALF_BASE = TRIANGLE_SIDE / 2;
|
|
@@ -32,7 +32,8 @@ import { useScatterLabels } from './useScatterLabels.ts';
|
|
|
32
32
|
*/
|
|
33
33
|
export function ScatterPlot(props: ScatterPlotProps): ReactElement {
|
|
34
34
|
const { x, y, width, height, xAxis, yAxis, groupOf, groups } = props;
|
|
35
|
-
const { mutedGroups,
|
|
35
|
+
const { mutedGroups, shapeOf, shapes } = props;
|
|
36
|
+
const { ellipse = null, ellipseMinimumPoints } = props;
|
|
36
37
|
const { ellipseFillOpacity, showGroupMeans, markers } = props;
|
|
37
38
|
const { pointRadius = 3.5, outlinedFrom, pointLabels } = props;
|
|
38
39
|
const { showGroupLabels = false, selected, defaultSelected } = props;
|
|
@@ -127,6 +128,8 @@ export function ScatterPlot(props: ScatterPlotProps): ReactElement {
|
|
|
127
128
|
groupOf={groupOf}
|
|
128
129
|
colors={ink.colors}
|
|
129
130
|
opacities={ink.opacities}
|
|
131
|
+
shapeOf={shapeOf}
|
|
132
|
+
shapes={shapes}
|
|
130
133
|
radius={pointRadius}
|
|
131
134
|
outlinedFrom={outlinedFrom}
|
|
132
135
|
/>
|
|
@@ -3,6 +3,8 @@ import { memo } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { chartGroupIndex } from '../../chart/core/chartGroups.ts';
|
|
5
5
|
import { chartRoundPixel } from '../../chart/core/chartScale.ts';
|
|
6
|
+
import type { OverlaySampleShape } from '../../overlay/core/overlayMarks.ts';
|
|
7
|
+
import { pointShapePath } from '../core/pointShape.ts';
|
|
6
8
|
import type { ScreenPoints } from '../core/screenPoints.ts';
|
|
7
9
|
|
|
8
10
|
/** What {@link ScatterPointLayer} draws. */
|
|
@@ -31,6 +33,17 @@ export interface ScatterPointLayerProps {
|
|
|
31
33
|
* @default undefined — every group is drawn at full strength
|
|
32
34
|
*/
|
|
33
35
|
opacities?: readonly number[];
|
|
36
|
+
/**
|
|
37
|
+
* Which shape each point takes, as an index into `shapes`. An entry of `-1`,
|
|
38
|
+
* or one outside the range, is a disc.
|
|
39
|
+
* @default undefined — every point is a disc
|
|
40
|
+
*/
|
|
41
|
+
shapeOf?: ArrayLike<number>;
|
|
42
|
+
/**
|
|
43
|
+
* The shapes, in the order `shapeOf` names them.
|
|
44
|
+
* @default undefined
|
|
45
|
+
*/
|
|
46
|
+
shapes?: readonly OverlaySampleShape[];
|
|
34
47
|
/**
|
|
35
48
|
* Colour of a point belonging to no group. It has to be the one ink on the
|
|
36
49
|
* plot that names nothing, or a reader counts it as one more group.
|
|
@@ -77,7 +90,8 @@ export interface ScatterPointLayerProps {
|
|
|
77
90
|
*
|
|
78
91
|
* A hollow dot is the same dot at the same radius with its colour moved from
|
|
79
92
|
* the fill to the stroke, so the two read as one kind of thing seen at two
|
|
80
|
-
* levels of confidence rather than as two unrelated marks.
|
|
93
|
+
* levels of confidence rather than as two unrelated marks. A shaped point
|
|
94
|
+
* follows the same rule, and covers the area of the disc it stands in for.
|
|
81
95
|
* @param props - See {@link ScatterPointLayerProps}.
|
|
82
96
|
* @returns The cloud.
|
|
83
97
|
*/
|
|
@@ -89,6 +103,8 @@ export const ScatterPointLayer = memo(function ScatterPointLayer(
|
|
|
89
103
|
groupOf,
|
|
90
104
|
colors,
|
|
91
105
|
opacities,
|
|
106
|
+
shapeOf,
|
|
107
|
+
shapes,
|
|
92
108
|
fallbackColor = 'var(--text-faint)',
|
|
93
109
|
radius = 3.5,
|
|
94
110
|
radii,
|
|
@@ -102,6 +118,7 @@ export const ScatterPointLayer = memo(function ScatterPointLayer(
|
|
|
102
118
|
const steps = order === undefined ? count : Math.min(count, order.length);
|
|
103
119
|
const hollowFrom = outlineStart(outlinedFrom, count);
|
|
104
120
|
const groups = colors?.length ?? 0;
|
|
121
|
+
const shapeCount = shapes?.length ?? 0;
|
|
105
122
|
const ink = groupInk(colors, opacities, fallbackColor);
|
|
106
123
|
|
|
107
124
|
const dots: ReactElement[] = [];
|
|
@@ -115,17 +132,28 @@ export const ScatterPointLayer = memo(function ScatterPointLayer(
|
|
|
115
132
|
const color = ink.colors[slot] ?? fallbackColor;
|
|
116
133
|
const hollow = index >= hollowFrom;
|
|
117
134
|
const size = radii?.[index];
|
|
135
|
+
const reach = size === undefined ? radius : chartRoundPixel(size);
|
|
136
|
+
const shape = shapes?.[chartGroupIndex(shapeOf, index, shapeCount)];
|
|
137
|
+
const outline =
|
|
138
|
+
shape === undefined ? null : pointShapePath(shape, x, y, reach);
|
|
139
|
+
const paint = {
|
|
140
|
+
fill: hollow ? 'none' : color,
|
|
141
|
+
stroke: hollow ? color : undefined,
|
|
142
|
+
strokeWidth: hollow ? OUTLINE_WIDTH : undefined,
|
|
143
|
+
opacity: ink.opacities[slot],
|
|
144
|
+
};
|
|
118
145
|
dots.push(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
outline === null ? (
|
|
147
|
+
<circle
|
|
148
|
+
key={index}
|
|
149
|
+
cx={chartRoundPixel(x)}
|
|
150
|
+
cy={chartRoundPixel(y)}
|
|
151
|
+
r={reach}
|
|
152
|
+
{...paint}
|
|
153
|
+
/>
|
|
154
|
+
) : (
|
|
155
|
+
<path key={index} d={outline} {...paint} />
|
|
156
|
+
),
|
|
129
157
|
);
|
|
130
158
|
}
|
|
131
159
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { ReactNode } from 'react';
|
|
11
11
|
|
|
12
|
+
import type { OverlaySampleShape } from '../../overlay/core/overlayMarks.ts';
|
|
12
13
|
import type { ScatterSelectionMode } from '../core/scatterSelection.ts';
|
|
13
14
|
|
|
14
15
|
import type { SelectionChange } from './useScatterSelection.ts';
|
|
@@ -61,6 +62,18 @@ export interface ScatterGroupProps {
|
|
|
61
62
|
* @default undefined — every group is drawn in full
|
|
62
63
|
*/
|
|
63
64
|
mutedGroups?: ReadonlySet<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Which shape each point takes, as an index into `shapes`: a second
|
|
67
|
+
* grouping of the same points, drawn while colour keeps the first. A point
|
|
68
|
+
* with `-1`, `NaN`, or an index outside the range, is a disc.
|
|
69
|
+
* @default undefined — every point is a disc
|
|
70
|
+
*/
|
|
71
|
+
shapeOf?: ArrayLike<number>;
|
|
72
|
+
/**
|
|
73
|
+
* The shapes, in the order `shapeOf` names them.
|
|
74
|
+
* @default undefined
|
|
75
|
+
*/
|
|
76
|
+
shapes?: readonly OverlaySampleShape[];
|
|
64
77
|
/**
|
|
65
78
|
* Radius of a dot, in pixels. A cloud draws the dot at the middle of its box
|
|
66
79
|
* at this size, one at the front larger and one at the back smaller.
|
|
@@ -10,6 +10,7 @@ import type { ReactElement } from 'react';
|
|
|
10
10
|
|
|
11
11
|
import { chartRoundPixel } from '../../chart/core/chartScale.ts';
|
|
12
12
|
import type { OverlayMarkShape } from '../../overlay/core/overlayMarks.ts';
|
|
13
|
+
import { pointShapePath } from '../core/pointShape.ts';
|
|
13
14
|
|
|
14
15
|
/** A point drawn over the cloud that is not a sample, already in pixels. */
|
|
15
16
|
export interface ScatterPixelMark {
|
|
@@ -32,7 +33,8 @@ export interface ScatterPixelMark {
|
|
|
32
33
|
* One mark, drawn as the glyph its shape asks for.
|
|
33
34
|
*
|
|
34
35
|
* A rule laid across the cloud names no point, so the two rule shapes are
|
|
35
|
-
* drawn as the default cross.
|
|
36
|
+
* drawn as the default cross. The three filled polygons are the shapes a
|
|
37
|
+
* sample can take, drawn as large as the disc they stand in for.
|
|
36
38
|
* @param mark - What to draw, already in the frame's pixels.
|
|
37
39
|
* @param size - Side of the square the glyph is drawn in, in pixels.
|
|
38
40
|
* @param key - What React reconciles the glyph by.
|
|
@@ -81,6 +83,12 @@ export function markGlyph(
|
|
|
81
83
|
/>
|
|
82
84
|
);
|
|
83
85
|
}
|
|
86
|
+
const polygon = pointShapePath(shape, x, y, half);
|
|
87
|
+
if (polygon !== null) {
|
|
88
|
+
return (
|
|
89
|
+
<path key={key} {...shared} d={polygon} fill={color} stroke="none" />
|
|
90
|
+
);
|
|
91
|
+
}
|
|
84
92
|
return (
|
|
85
93
|
<path
|
|
86
94
|
key={key}
|
|
@@ -47,6 +47,7 @@ import { useOrbitDrag } from './useOrbitDrag.ts';
|
|
|
47
47
|
export function ScatterCloud(props: ScatterCloudProps): ReactElement {
|
|
48
48
|
const { x, y, z, width, height, xLabel, yLabel, zLabel } = props;
|
|
49
49
|
const { groupOf, groups, mutedGroups, ellipsoid = null } = props;
|
|
50
|
+
const { shapeOf, shapes } = props;
|
|
50
51
|
const { ellipsoidMinimumPoints, ellipsoidFillOpacity } = props;
|
|
51
52
|
const { showGroupLabels = false, pointLabels, pointRadius = 3.5 } = props;
|
|
52
53
|
const { outlinedFrom, selected, defaultSelected, onSelectionChange } = props;
|
|
@@ -198,6 +199,8 @@ export function ScatterCloud(props: ScatterCloudProps): ReactElement {
|
|
|
198
199
|
groupOf={groupOf}
|
|
199
200
|
colors={ink.colors}
|
|
200
201
|
opacities={ink.opacities}
|
|
202
|
+
shapeOf={shapeOf}
|
|
203
|
+
shapes={shapes}
|
|
201
204
|
radius={pointRadius}
|
|
202
205
|
outlinedFrom={outlinedFrom}
|
|
203
206
|
/>
|
package/styles/chrome.css
CHANGED
|
@@ -321,6 +321,169 @@ button {
|
|
|
321
321
|
max-width: none;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/* ── Selection ───────────────────────────────────────────────────────────── */
|
|
325
|
+
|
|
326
|
+
/* A tool is clicked and dragged, not read: a drag across a viewer, a double
|
|
327
|
+
click on a control or a stray triple click must not paint the page. What is
|
|
328
|
+
worth taking away is copied with a click instead (`<ClickToCopy>`). Form
|
|
329
|
+
controls stay editable, and what a visitor reads, quotes or reports stays
|
|
330
|
+
selectable: the About page, the glossary, a code block, a reference, an
|
|
331
|
+
error, and any region a site marks with `text-selectable`. The regions are
|
|
332
|
+
reopened down to their descendants, so a site's own `* { user-select: none }`
|
|
333
|
+
cannot close them again. */
|
|
334
|
+
body {
|
|
335
|
+
-webkit-user-select: none;
|
|
336
|
+
user-select: none;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
input,
|
|
340
|
+
textarea,
|
|
341
|
+
select,
|
|
342
|
+
[contenteditable],
|
|
343
|
+
:is(
|
|
344
|
+
.text-selectable,
|
|
345
|
+
.about-page,
|
|
346
|
+
.glossary-index,
|
|
347
|
+
.code-block,
|
|
348
|
+
.citation-preview,
|
|
349
|
+
.error-fallback__message,
|
|
350
|
+
.bp6-callout.bp6-intent-danger
|
|
351
|
+
),
|
|
352
|
+
:is(
|
|
353
|
+
.text-selectable,
|
|
354
|
+
.about-page,
|
|
355
|
+
.glossary-index,
|
|
356
|
+
.code-block,
|
|
357
|
+
.citation-preview,
|
|
358
|
+
.error-fallback__message,
|
|
359
|
+
.bp6-callout.bp6-intent-danger
|
|
360
|
+
)
|
|
361
|
+
* {
|
|
362
|
+
-webkit-user-select: text;
|
|
363
|
+
user-select: text;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* ── Click to copy ───────────────────────────────────────────────────────── */
|
|
367
|
+
|
|
368
|
+
/* The copy cursor, the tint and the clipboard glyph are the promise; a tick
|
|
369
|
+
keeps it. The glyph is `clipboard`, never `duplicate`: two overlapping squares
|
|
370
|
+
read as "add another". Green and red are the confirmation's own colours,
|
|
371
|
+
the ones Blueprint gives success and danger. */
|
|
372
|
+
.click-to-copy {
|
|
373
|
+
position: relative;
|
|
374
|
+
border-radius: 4px;
|
|
375
|
+
cursor: copy;
|
|
376
|
+
transition: background-color 120ms ease;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.click-to-copy:focus-visible {
|
|
380
|
+
outline: 2px solid var(--accent);
|
|
381
|
+
outline-offset: 1px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.click-to-copy[data-copy='copied'] {
|
|
385
|
+
background-color: color-mix(in oklab, #238551 15%, transparent);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.click-to-copy[data-copy='failed'] {
|
|
389
|
+
background-color: color-mix(in oklab, #cd4246 15%, transparent);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.click-to-copy__icon {
|
|
393
|
+
position: absolute;
|
|
394
|
+
top: 50%;
|
|
395
|
+
color: var(--text-muted);
|
|
396
|
+
opacity: 0;
|
|
397
|
+
pointer-events: none;
|
|
398
|
+
transition: opacity 120ms ease;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* In a line of text the glyph floats just past the value, so the line keeps
|
|
402
|
+
its spacing; a block keeps room for it inside its right edge. */
|
|
403
|
+
.click-to-copy--inline .click-to-copy__icon {
|
|
404
|
+
left: 100%;
|
|
405
|
+
transform: translate(3px, -50%);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.click-to-copy--block {
|
|
409
|
+
padding-right: 22px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.click-to-copy--block .click-to-copy__icon {
|
|
413
|
+
right: 4px;
|
|
414
|
+
transform: translateY(-50%);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.click-to-copy:focus-visible .click-to-copy__icon,
|
|
418
|
+
.click-to-copy[data-copy] .click-to-copy__icon {
|
|
419
|
+
opacity: 1;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.click-to-copy[data-copy='copied'] .click-to-copy__icon {
|
|
423
|
+
color: #238551;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.click-to-copy[data-copy='failed'] .click-to-copy__icon {
|
|
427
|
+
color: #cd4246;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Only where there is a pointer to hover with: on a touch screen the tint
|
|
431
|
+
would stay on after the tap, over the tick. */
|
|
432
|
+
@media (hover: hover) {
|
|
433
|
+
.click-to-copy:not([data-copy]):hover {
|
|
434
|
+
background-color: color-mix(in oklab, var(--accent) 12%, transparent);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.click-to-copy:hover .click-to-copy__icon {
|
|
438
|
+
opacity: 1;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Blueprint's interactive and striped tables paint and point every cell from
|
|
443
|
+
three classes deep, so a copyable cell repeats its class to stay ahead. */
|
|
444
|
+
.click-to-copy.click-to-copy.click-to-copy.click-to-copy:is(td, th) {
|
|
445
|
+
border-radius: 0;
|
|
446
|
+
cursor: copy;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.click-to-copy.click-to-copy.click-to-copy.click-to-copy:is(
|
|
450
|
+
td,
|
|
451
|
+
th
|
|
452
|
+
)[data-copy='copied'] {
|
|
453
|
+
background-color: color-mix(in oklab, #238551 15%, transparent);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.click-to-copy.click-to-copy.click-to-copy.click-to-copy:is(
|
|
457
|
+
td,
|
|
458
|
+
th
|
|
459
|
+
)[data-copy='failed'] {
|
|
460
|
+
background-color: color-mix(in oklab, #cd4246 15%, transparent);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
@media (hover: hover) {
|
|
464
|
+
.click-to-copy.click-to-copy.click-to-copy.click-to-copy:is(td, th):not(
|
|
465
|
+
[data-copy]
|
|
466
|
+
):hover {
|
|
467
|
+
background-color: color-mix(in oklab, var(--accent) 12%, transparent);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* The glyph carries the confirmation to the eye; this carries it to a reader. */
|
|
472
|
+
.click-to-copy__status {
|
|
473
|
+
position: absolute;
|
|
474
|
+
width: 1px;
|
|
475
|
+
height: 1px;
|
|
476
|
+
overflow: hidden;
|
|
477
|
+
clip-path: inset(50%);
|
|
478
|
+
white-space: nowrap;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@media print {
|
|
482
|
+
.click-to-copy__icon {
|
|
483
|
+
display: none;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
324
487
|
/* ── Printing ────────────────────────────────────────────────────────────── */
|
|
325
488
|
|
|
326
489
|
/* Students print the cheatsheet. Let them, without the chrome. */
|