react-cheminfo 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/check-seo.mjs +0 -0
- package/lib/clipboard/core/index.d.ts +0 -1
- package/lib/clipboard/core/index.d.ts.map +1 -1
- package/lib/clipboard/core/index.js +0 -1
- package/lib/clipboard/core/index.js.map +1 -1
- package/lib/clipboard/ui/CopyButton.d.ts +6 -0
- package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
- package/lib/clipboard/ui/CopyButton.js +5 -2
- package/lib/clipboard/ui/CopyButton.js.map +1 -1
- package/lib/color/core/index.d.ts +3 -0
- package/lib/color/core/index.d.ts.map +1 -1
- package/lib/color/core/index.js +2 -0
- package/lib/color/core/index.js.map +1 -1
- package/lib/color/core/interpolate.d.ts +4 -2
- package/lib/color/core/interpolate.d.ts.map +1 -1
- package/lib/color/core/interpolate.js +34 -12
- package/lib/color/core/interpolate.js.map +1 -1
- package/lib/color/core/stops.d.ts +43 -0
- package/lib/color/core/stops.d.ts.map +1 -0
- package/lib/color/core/stops.js +66 -0
- package/lib/color/core/stops.js.map +1 -0
- package/lib/color/core/tone.d.ts +25 -0
- package/lib/color/core/tone.d.ts.map +1 -0
- package/lib/color/core/tone.js +67 -0
- package/lib/color/core/tone.js.map +1 -0
- package/lib/color/core/uniform.d.ts +36 -0
- package/lib/color/core/uniform.d.ts.map +1 -0
- package/lib/color/core/uniform.js +61 -0
- package/lib/color/core/uniform.js.map +1 -0
- package/lib/color/ui/ColorScaleEditor.d.ts +5 -6
- package/lib/color/ui/ColorScaleEditor.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleEditor.js +74 -84
- package/lib/color/ui/ColorScaleEditor.js.map +1 -1
- package/lib/color/ui/ColorScaleSelect.d.ts +16 -0
- package/lib/color/ui/ColorScaleSelect.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleSelect.js +69 -17
- package/lib/color/ui/ColorScaleSelect.js.map +1 -1
- package/lib/color/ui/ColorScaleTone.d.ts +17 -0
- package/lib/color/ui/ColorScaleTone.d.ts.map +1 -0
- package/lib/color/ui/ColorScaleTone.js +55 -0
- package/lib/color/ui/ColorScaleTone.js.map +1 -0
- package/lib/color/ui/ColorScaleTrack.d.ts +29 -0
- package/lib/color/ui/ColorScaleTrack.d.ts.map +1 -0
- package/lib/color/ui/ColorScaleTrack.js +143 -0
- package/lib/color/ui/ColorScaleTrack.js.map +1 -0
- package/lib/download/core/copyFigure.d.ts +14 -0
- package/lib/download/core/copyFigure.d.ts.map +1 -0
- package/lib/download/core/copyFigure.js +18 -0
- package/lib/download/core/copyFigure.js.map +1 -0
- package/lib/download/core/figureHtml.js +1 -0
- package/lib/download/core/figureHtml.js.map +1 -1
- package/lib/download/core/figureHtmlMarkup.d.ts +10 -0
- package/lib/download/core/figureHtmlMarkup.d.ts.map +1 -1
- package/lib/download/core/figureHtmlMarkup.js +7 -1
- package/lib/download/core/figureHtmlMarkup.js.map +1 -1
- package/lib/download/core/index.d.ts +2 -0
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +1 -0
- package/lib/download/core/index.js.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.d.ts +2 -3
- package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.js +1 -1
- package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
- package/lib/download/ui/figureDownloadHint.d.ts +1 -1
- package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
- package/lib/download/ui/figureDownloadHint.js.map +1 -1
- package/lib/download/ui/index.d.ts +0 -1
- package/lib/download/ui/index.d.ts.map +1 -1
- package/lib/download/ui/useFigureActions.d.ts +2 -11
- package/lib/download/ui/useFigureActions.d.ts.map +1 -1
- package/lib/download/ui/useFigureActions.js +4 -23
- package/lib/download/ui/useFigureActions.js.map +1 -1
- package/lib/download/ui/useFigureTask.d.ts +21 -0
- package/lib/download/ui/useFigureTask.d.ts.map +1 -0
- package/lib/download/ui/useFigureTask.js +27 -0
- package/lib/download/ui/useFigureTask.js.map +1 -0
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +28 -2
- package/lib/locales/en.json +28 -2
- package/lib/locales/es.json +28 -2
- package/lib/locales/fr.json +28 -2
- package/lib/locales/it.json +28 -2
- package/lib/molecule3d/core/index.d.ts +1 -1
- package/lib/molecule3d/core/index.d.ts.map +1 -1
- package/lib/molecule3d/core/index.js +1 -1
- package/lib/molecule3d/core/index.js.map +1 -1
- package/lib/molecule3d/core/settings.d.ts +15 -0
- package/lib/molecule3d/core/settings.d.ts.map +1 -1
- package/lib/molecule3d/core/settings.js +25 -0
- package/lib/molecule3d/core/settings.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DExport.d.ts +3 -0
- package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DExport.js +10 -16
- package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DOptions.d.ts +3 -1
- package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DOptions.js +22 -6
- package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
- package/lib/molecule3d/ui/MoleculeCanvas3D.js +4 -3
- package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
- package/lib/molecule3d/ui/exportMoleculeImage.d.ts +9 -0
- package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
- package/lib/molecule3d/ui/exportMoleculeImage.js +15 -0
- package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +2 -1
- package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
- package/lib/molecule3d/ui/useImageExport.d.ts +7 -1
- package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
- package/lib/molecule3d/ui/useImageExport.js +12 -3
- package/lib/molecule3d/ui/useImageExport.js.map +1 -1
- package/lib/overlay/ui/FigureBar.d.ts +41 -0
- package/lib/overlay/ui/FigureBar.d.ts.map +1 -0
- package/lib/overlay/ui/FigureBar.js +43 -0
- package/lib/overlay/ui/FigureBar.js.map +1 -0
- package/lib/overlay/ui/OverlayBar.d.ts +13 -4
- package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayBar.js +2 -2
- package/lib/overlay/ui/OverlayBar.js.map +1 -1
- package/lib/overlay/ui/OverlayBarStrip.d.ts +2 -1
- package/lib/overlay/ui/OverlayBarStrip.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayBarStrip.js +9 -5
- package/lib/overlay/ui/OverlayBarStrip.js.map +1 -1
- package/lib/overlay/ui/OverlayIconButton.d.ts +8 -2
- package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayIconButton.js +2 -1
- package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
- package/lib/overlay/ui/index.d.ts +4 -0
- package/lib/overlay/ui/index.d.ts.map +1 -1
- package/lib/overlay/ui/index.js +2 -0
- package/lib/overlay/ui/index.js.map +1 -1
- package/lib/overlay/ui/overlayBarShape.d.ts +18 -0
- package/lib/overlay/ui/overlayBarShape.d.ts.map +1 -1
- package/lib/overlay/ui/overlayStyles.d.ts +30 -0
- package/lib/overlay/ui/overlayStyles.d.ts.map +1 -1
- package/lib/overlay/ui/overlayStyles.js +52 -0
- package/lib/overlay/ui/overlayStyles.js.map +1 -1
- package/lib/overlay/ui/useFigureBarAwake.d.ts +12 -0
- package/lib/overlay/ui/useFigureBarAwake.d.ts.map +1 -0
- package/lib/overlay/ui/useFigureBarAwake.js +51 -0
- package/lib/overlay/ui/useFigureBarAwake.js.map +1 -0
- package/lib/periodic/core/elements.d.ts +6 -3
- package/lib/periodic/core/elements.d.ts.map +1 -1
- package/lib/periodic/core/elements.js +9 -3
- package/lib/periodic/core/elements.js.map +1 -1
- package/lib/periodic/core/index.d.ts +2 -2
- package/lib/periodic/core/index.d.ts.map +1 -1
- package/lib/periodic/core/index.js +1 -1
- package/lib/periodic/core/index.js.map +1 -1
- package/lib/periodic/core/layout.d.ts +33 -3
- package/lib/periodic/core/layout.d.ts.map +1 -1
- package/lib/periodic/core/layout.js +14 -0
- package/lib/periodic/core/layout.js.map +1 -1
- package/lib/periodic/ui/BlockStrip.d.ts +20 -0
- package/lib/periodic/ui/BlockStrip.d.ts.map +1 -0
- package/lib/periodic/ui/BlockStrip.js +48 -0
- package/lib/periodic/ui/BlockStrip.js.map +1 -0
- package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
- package/lib/periodic/ui/ElementCell.js +9 -8
- package/lib/periodic/ui/ElementCell.js.map +1 -1
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +3 -1
- package/lib/periodic/ui/PeriodicTable.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableHelp.d.ts +33 -0
- package/lib/periodic/ui/PeriodicTableHelp.d.ts.map +1 -0
- package/lib/periodic/ui/PeriodicTableHelp.js +107 -0
- package/lib/periodic/ui/PeriodicTableHelp.js.map +1 -0
- package/lib/periodic/ui/cellStyles.d.ts +3 -2
- package/lib/periodic/ui/cellStyles.d.ts.map +1 -1
- package/lib/periodic/ui/cellStyles.js +3 -2
- package/lib/periodic/ui/cellStyles.js.map +1 -1
- package/lib/periodic/ui/index.d.ts +2 -0
- package/lib/periodic/ui/index.d.ts.map +1 -1
- package/lib/periodic/ui/index.js +1 -0
- package/lib/periodic/ui/index.js.map +1 -1
- package/lib/periodic/ui/periodicTableProps.d.ts +5 -4
- package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -1
- package/lib/periodic/ui/tableLayout.d.ts +3 -1
- package/lib/periodic/ui/tableLayout.d.ts.map +1 -1
- package/lib/periodic/ui/tableLayout.js +8 -3
- package/lib/periodic/ui/tableLayout.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts +3 -2
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +5 -6
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/ShareLinkBar.d.ts +8 -2
- package/lib/share/ui/ShareLinkBar.d.ts.map +1 -1
- package/lib/share/ui/ShareLinkBar.js +6 -5
- package/lib/share/ui/ShareLinkBar.js.map +1 -1
- package/lib/share/ui/SharePreview.d.ts.map +1 -1
- package/lib/share/ui/SharePreview.js +2 -2
- package/lib/share/ui/SharePreview.js.map +1 -1
- package/lib/structure/ui/StructurePicturePane.js +3 -3
- package/lib/structure/ui/StructurePicturePane.js.map +1 -1
- package/package.json +1 -1
- package/src/clipboard/core/index.ts +0 -1
- package/src/clipboard/ui/CopyButton.tsx +12 -1
- package/src/color/core/index.ts +9 -0
- package/src/color/core/interpolate.ts +51 -22
- package/src/color/core/stops.ts +98 -0
- package/src/color/core/tone.ts +79 -0
- package/src/color/core/uniform.ts +67 -0
- package/src/color/ui/ColorScaleEditor.tsx +115 -141
- package/src/color/ui/ColorScaleSelect.tsx +160 -36
- package/src/color/ui/ColorScaleTone.tsx +103 -0
- package/src/color/ui/ColorScaleTrack.tsx +239 -0
- package/src/download/core/copyFigure.ts +20 -0
- package/src/download/core/figureHtml.ts +1 -0
- package/src/download/core/figureHtmlMarkup.ts +14 -1
- package/src/download/core/index.ts +2 -0
- package/src/download/ui/FigureDownloadPanel.tsx +8 -11
- package/src/download/ui/figureDownloadHint.ts +1 -2
- package/src/download/ui/index.ts +0 -1
- package/src/download/ui/useFigureActions.ts +6 -42
- package/src/download/ui/useFigureTask.ts +46 -0
- package/src/locales/de.json +28 -2
- package/src/locales/en.json +28 -2
- package/src/locales/es.json +28 -2
- package/src/locales/fr.json +28 -2
- package/src/locales/it.json +28 -2
- package/src/molecule3d/core/index.ts +2 -0
- package/src/molecule3d/core/settings.ts +35 -0
- package/src/molecule3d/ui/Molecule3DExport.tsx +18 -17
- package/src/molecule3d/ui/Molecule3DOptions.tsx +51 -3
- package/src/molecule3d/ui/MoleculeCanvas3D.tsx +11 -3
- package/src/molecule3d/ui/exportMoleculeImage.ts +20 -0
- package/src/molecule3d/ui/moleculeViewer3DProps.ts +2 -1
- package/src/molecule3d/ui/useImageExport.ts +25 -3
- package/src/overlay/ui/FigureBar.tsx +96 -0
- package/src/overlay/ui/OverlayBar.tsx +15 -5
- package/src/overlay/ui/OverlayBarStrip.tsx +34 -22
- package/src/overlay/ui/OverlayIconButton.tsx +10 -2
- package/src/overlay/ui/index.ts +4 -0
- package/src/overlay/ui/overlayBarShape.ts +19 -0
- package/src/overlay/ui/overlayStyles.ts +61 -0
- package/src/overlay/ui/useFigureBarAwake.ts +56 -0
- package/src/periodic/core/elements.ts +8 -3
- package/src/periodic/core/index.ts +8 -1
- package/src/periodic/core/layout.ts +45 -3
- package/src/periodic/ui/BlockStrip.tsx +81 -0
- package/src/periodic/ui/ElementCell.tsx +9 -8
- package/src/periodic/ui/PeriodicTable.tsx +4 -1
- package/src/periodic/ui/PeriodicTableHelp.tsx +205 -0
- package/src/periodic/ui/cellStyles.ts +3 -2
- package/src/periodic/ui/index.ts +5 -0
- package/src/periodic/ui/periodicTableProps.ts +5 -4
- package/src/periodic/ui/tableLayout.ts +9 -3
- package/src/share/ui/ShareDialogContent.tsx +9 -13
- package/src/share/ui/ShareLinkBar.tsx +16 -4
- package/src/share/ui/SharePreview.tsx +6 -2
- package/src/structure/ui/StructurePicturePane.tsx +3 -3
- package/styles/chrome.css +43 -32
- package/lib/clipboard/core/copyPng.d.ts +0 -13
- package/lib/clipboard/core/copyPng.d.ts.map +0 -1
- package/lib/clipboard/core/copyPng.js +0 -19
- package/lib/clipboard/core/copyPng.js.map +0 -1
- package/src/clipboard/core/copyPng.ts +0 -20
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { Button, HTMLSelect } from '@blueprintjs/core';
|
|
2
2
|
import type { CSSProperties, ReactElement } from 'react';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
|
|
4
|
-
import { formatDecimal } from '../../format/core/numbers.ts';
|
|
5
5
|
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
6
|
+
import { NumberInput } from '../../number/ui/NumberInput.tsx';
|
|
6
7
|
import { normalizeHexColor } from '../core/hex.ts';
|
|
7
8
|
import type {
|
|
8
9
|
ColorInterpolation,
|
|
9
10
|
ColorScale,
|
|
10
11
|
ColorStop,
|
|
11
12
|
} from '../core/interpolate.ts';
|
|
12
|
-
import {
|
|
13
|
-
|
|
13
|
+
import {
|
|
14
|
+
MINIMUM_CUSTOM_STOPS,
|
|
15
|
+
moveColorStop,
|
|
16
|
+
recolorColorStop,
|
|
17
|
+
removeColorStop,
|
|
18
|
+
} from '../core/stops.ts';
|
|
14
19
|
|
|
15
|
-
import {
|
|
20
|
+
import { ColorScaleTone } from './ColorScaleTone.tsx';
|
|
21
|
+
import { ColorScaleTrack } from './ColorScaleTrack.tsx';
|
|
16
22
|
|
|
17
|
-
const MINIMUM_STOPS = 2;
|
|
18
23
|
const POSITION_STEP = 0.01;
|
|
19
|
-
const PREVIEW_HEIGHT = 16;
|
|
20
|
-
const PREVIEW_SAMPLES = 48;
|
|
21
|
-
const POSITION_DECIMALS = 2;
|
|
22
24
|
// A `type="color"` input only takes six hex digits; an anchor that is not a
|
|
23
25
|
// hex colour is shown as black until it is picked again.
|
|
24
26
|
const UNREADABLE_COLOR = '#000000';
|
|
@@ -44,88 +46,108 @@ export interface ColorScaleEditorProps {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
* A colour scale of the reader's own
|
|
48
|
-
* each one sits, and the path it takes between them.
|
|
49
|
+
* A colour scale of the reader's own, edited on the strip it draws.
|
|
49
50
|
*
|
|
50
|
-
* A
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* A click on the strip adds an anchor in the colour already there, an anchor
|
|
52
|
+
* is dragged along it, and an anchor pointed at goes with Backspace. The
|
|
53
|
+
* anchor last touched shows its colour and position below, to set exactly.
|
|
53
54
|
* @param props - See {@link ColorScaleEditorProps}.
|
|
54
|
-
* @returns The
|
|
55
|
+
* @returns The strip, the selected anchor, and the picker of the path between anchors.
|
|
55
56
|
*/
|
|
56
57
|
export function ColorScaleEditor(props: ColorScaleEditorProps): ReactElement {
|
|
57
58
|
const { className, value, onChange } = props;
|
|
58
59
|
const t = useChromeT();
|
|
59
60
|
const stops = value.stops;
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const [picked, setPicked] = useState(0);
|
|
62
|
+
const [hovered, setHovered] = useState<number | null>(null);
|
|
63
|
+
const colorInput = useRef<HTMLInputElement>(null);
|
|
64
|
+
const selected = Math.min(picked, stops.length - 1);
|
|
65
|
+
const stop = stops[selected];
|
|
66
|
+
const removable = stops.length > MINIMUM_CUSTOM_STOPS;
|
|
67
|
+
|
|
68
|
+
function write(next: ColorStop[]): void {
|
|
62
69
|
onChange({ stops: next, interpolation: value.interpolation });
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
function remove(index: number): void {
|
|
73
|
+
if (!removable) return;
|
|
74
|
+
write(removeColorStop(stops, index));
|
|
75
|
+
setHovered(null);
|
|
76
|
+
setPicked(Math.max(0, index - 1));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (hovered === null || !removable) return;
|
|
81
|
+
const index = hovered;
|
|
82
|
+
function onKeyDown(event: globalThis.KeyboardEvent): void {
|
|
83
|
+
if (event.key !== 'Backspace' && event.key !== 'Delete') return;
|
|
84
|
+
if (isEditable(event.target)) return;
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
onChange({
|
|
87
|
+
stops: removeColorStop(stops, index),
|
|
88
|
+
interpolation: value.interpolation,
|
|
89
|
+
});
|
|
90
|
+
setHovered(null);
|
|
91
|
+
setPicked(Math.max(0, index - 1));
|
|
92
|
+
}
|
|
93
|
+
globalThis.addEventListener('keydown', onKeyDown);
|
|
94
|
+
return () => {
|
|
95
|
+
globalThis.removeEventListener('keydown', onKeyDown);
|
|
96
|
+
};
|
|
97
|
+
}, [hovered, removable, stops, value.interpolation, onChange]);
|
|
98
|
+
|
|
65
99
|
return (
|
|
66
100
|
<div className={className} style={PANEL_STYLE}>
|
|
67
|
-
<
|
|
101
|
+
<ColorScaleTrack
|
|
68
102
|
scale={value}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
selected={selected}
|
|
104
|
+
hovered={hovered}
|
|
105
|
+
onSelect={setPicked}
|
|
106
|
+
onHover={setHovered}
|
|
107
|
+
onChange={write}
|
|
108
|
+
onRemove={remove}
|
|
109
|
+
onPick={() => {
|
|
110
|
+
colorInput.current?.showPicker();
|
|
111
|
+
}}
|
|
72
112
|
/>
|
|
73
113
|
|
|
74
|
-
<div style={
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
<span style={POSITION_STYLE}>
|
|
103
|
-
{formatDecimal(stop.position, POSITION_DECIMALS)}
|
|
104
|
-
</span>
|
|
105
|
-
<Button
|
|
106
|
-
icon="cross"
|
|
107
|
-
variant="minimal"
|
|
108
|
-
size="small"
|
|
109
|
-
aria-label={t('color.removeAnchor', { index: index + 1 })}
|
|
110
|
-
disabled={stops.length <= MINIMUM_STOPS}
|
|
111
|
-
onClick={() => {
|
|
112
|
-
write(stops.filter((_, at) => at !== index));
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
))}
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
<div style={FOOTER_STYLE}>
|
|
114
|
+
<div style={ROW_STYLE}>
|
|
115
|
+
<input
|
|
116
|
+
ref={colorInput}
|
|
117
|
+
type="color"
|
|
118
|
+
aria-label={t('color.anchorColour', { index: selected + 1 })}
|
|
119
|
+
value={normalizeHexColor(stop?.color ?? '') ?? UNREADABLE_COLOR}
|
|
120
|
+
style={SWATCH_STYLE}
|
|
121
|
+
onChange={(event) => {
|
|
122
|
+
write(recolorColorStop(stops, selected, event.target.value));
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
<NumberInput
|
|
126
|
+
ariaLabel={t('color.anchorPosition', { index: selected + 1 })}
|
|
127
|
+
value={stop?.position ?? 0}
|
|
128
|
+
min={0}
|
|
129
|
+
max={1}
|
|
130
|
+
step={POSITION_STEP}
|
|
131
|
+
size="small"
|
|
132
|
+
buttons={false}
|
|
133
|
+
style={POSITION_STYLE}
|
|
134
|
+
onChange={(position) => {
|
|
135
|
+
const moved = moveColorStop(stops, selected, position);
|
|
136
|
+
write(moved.stops);
|
|
137
|
+
setPicked(moved.index);
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
120
140
|
<Button
|
|
121
|
-
icon="
|
|
141
|
+
icon="trash"
|
|
142
|
+
variant="minimal"
|
|
122
143
|
size="small"
|
|
123
|
-
|
|
124
|
-
disabled={
|
|
144
|
+
aria-label={t('color.removeAnchor', { index: selected + 1 })}
|
|
145
|
+
disabled={!removable}
|
|
125
146
|
onClick={() => {
|
|
126
|
-
|
|
147
|
+
remove(selected);
|
|
127
148
|
}}
|
|
128
149
|
/>
|
|
150
|
+
<span style={SPACER_STYLE} />
|
|
129
151
|
<HTMLSelect
|
|
130
152
|
aria-label={t('color.path')}
|
|
131
153
|
value={value.interpolation}
|
|
@@ -141,59 +163,22 @@ export function ColorScaleEditor(props: ColorScaleEditorProps): ReactElement {
|
|
|
141
163
|
}}
|
|
142
164
|
/>
|
|
143
165
|
</div>
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
166
|
|
|
148
|
-
|
|
149
|
-
stops: readonly ColorStop[],
|
|
150
|
-
index: number,
|
|
151
|
-
patch: Partial<ColorStop>,
|
|
152
|
-
): ColorStop[] {
|
|
153
|
-
return stops.map((stop, at) => (at === index ? { ...stop, ...patch } : stop));
|
|
154
|
-
}
|
|
167
|
+
<ColorScaleTone stops={stops} onChange={write} />
|
|
155
168
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* @param stops - The anchors as they stand.
|
|
160
|
-
* @param index - Which of them is being moved.
|
|
161
|
-
* @param position - Where it is being moved to.
|
|
162
|
-
* @returns The position it is allowed to take.
|
|
163
|
-
*/
|
|
164
|
-
function hold(
|
|
165
|
-
stops: readonly ColorStop[],
|
|
166
|
-
index: number,
|
|
167
|
-
position: number,
|
|
168
|
-
): number {
|
|
169
|
-
if (!Number.isFinite(position)) return stops[index]?.position ?? 0;
|
|
170
|
-
const low = stops[index - 1]?.position ?? 0;
|
|
171
|
-
const high = stops[index + 1]?.position ?? 1;
|
|
172
|
-
return Math.min(high, Math.max(low, position));
|
|
169
|
+
<p style={HINT_STYLE}>{t('color.editorHint')}</p>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let widest = 0;
|
|
184
|
-
let at = 1;
|
|
185
|
-
for (let index = 1; index < stops.length; index++) {
|
|
186
|
-
const span =
|
|
187
|
-
(stops[index]?.position ?? 0) - (stops[index - 1]?.position ?? 0);
|
|
188
|
-
if (span > widest) {
|
|
189
|
-
widest = span;
|
|
190
|
-
at = index;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const position =
|
|
194
|
-
((stops[at]?.position ?? 1) + (stops[at - 1]?.position ?? 0)) / 2;
|
|
195
|
-
stops.splice(at, 0, { position, color: colorAt(scale, position) });
|
|
196
|
-
return stops;
|
|
174
|
+
function isEditable(target: EventTarget | null): boolean {
|
|
175
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
176
|
+
return (
|
|
177
|
+
target.isContentEditable ||
|
|
178
|
+
target instanceof HTMLInputElement ||
|
|
179
|
+
target instanceof HTMLTextAreaElement ||
|
|
180
|
+
target instanceof HTMLSelectElement
|
|
181
|
+
);
|
|
197
182
|
}
|
|
198
183
|
|
|
199
184
|
const PANEL_STYLE = {
|
|
@@ -201,18 +186,13 @@ const PANEL_STYLE = {
|
|
|
201
186
|
flexDirection: 'column',
|
|
202
187
|
gap: 10,
|
|
203
188
|
padding: 12,
|
|
204
|
-
minWidth:
|
|
205
|
-
} as const satisfies CSSProperties;
|
|
206
|
-
|
|
207
|
-
const ROWS_STYLE = {
|
|
208
|
-
display: 'flex',
|
|
209
|
-
flexDirection: 'column',
|
|
210
|
-
gap: 4,
|
|
189
|
+
minWidth: 320,
|
|
211
190
|
} as const satisfies CSSProperties;
|
|
212
191
|
|
|
213
192
|
const ROW_STYLE = {
|
|
214
193
|
display: 'flex',
|
|
215
194
|
alignItems: 'center',
|
|
195
|
+
flexWrap: 'wrap',
|
|
216
196
|
gap: 8,
|
|
217
197
|
} as const satisfies CSSProperties;
|
|
218
198
|
|
|
@@ -225,23 +205,17 @@ const SWATCH_STYLE = {
|
|
|
225
205
|
cursor: 'pointer',
|
|
226
206
|
} as const satisfies CSSProperties;
|
|
227
207
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
minWidth: 90,
|
|
231
|
-
accentColor: 'var(--accent, currentColor)',
|
|
208
|
+
const POSITION_STYLE = {
|
|
209
|
+
width: 64,
|
|
232
210
|
} as const satisfies CSSProperties;
|
|
233
211
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
fontVariantNumeric: 'tabular-nums',
|
|
237
|
-
width: 30,
|
|
238
|
-
textAlign: 'right',
|
|
212
|
+
const SPACER_STYLE = {
|
|
213
|
+
flex: '1 1 auto',
|
|
239
214
|
} as const satisfies CSSProperties;
|
|
240
215
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
justifyContent: 'space-between',
|
|
216
|
+
const HINT_STYLE = {
|
|
217
|
+
margin: 0,
|
|
218
|
+
color: 'var(--text-muted)',
|
|
219
|
+
fontSize: 12,
|
|
220
|
+
lineHeight: 1.4,
|
|
247
221
|
} as const satisfies CSSProperties;
|
|
@@ -9,15 +9,28 @@ import type { CSSProperties, ReactElement } from 'react';
|
|
|
9
9
|
import { useState } from 'react';
|
|
10
10
|
|
|
11
11
|
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
12
|
+
import { OverlayValueButton } from '../../overlay/ui/OverlayValueButton.tsx';
|
|
12
13
|
import { TOKEN } from '../../tokens/core/familyTokens.ts';
|
|
14
|
+
import { normalizeHexColor } from '../core/hex.ts';
|
|
15
|
+
import { sampleScale } from '../core/interpolate.ts';
|
|
13
16
|
import { formatColorScale, resolveColorScale } from '../core/scaleText.ts';
|
|
14
17
|
import { COLOR_SCALES } from '../core/scales.ts';
|
|
18
|
+
import {
|
|
19
|
+
UNIFORM_COLOR_SCALE_ID,
|
|
20
|
+
formatUniformColorScale,
|
|
21
|
+
isUniformColorScale,
|
|
22
|
+
uniformSwatch,
|
|
23
|
+
} from '../core/uniform.ts';
|
|
15
24
|
|
|
16
25
|
import { ColorScaleBar } from './ColorScaleBar.tsx';
|
|
17
26
|
import { ColorScaleEditor } from './ColorScaleEditor.tsx';
|
|
18
27
|
import { ColorScaleEntry } from './ColorScaleEntry.tsx';
|
|
19
28
|
|
|
20
29
|
const BUTTON_BAR_WIDTH = 72;
|
|
30
|
+
// Its two ends and its middle: the most dots `OverlaySwatchIcon` draws.
|
|
31
|
+
const BAR_SWATCHES = 3;
|
|
32
|
+
// A `type="color"` input only takes six hex digits.
|
|
33
|
+
const UNREADABLE_COLOR = '#000000';
|
|
21
34
|
|
|
22
35
|
/** What {@link ColorScaleSelect} chooses. */
|
|
23
36
|
export interface ColorScaleSelectProps {
|
|
@@ -38,6 +51,22 @@ export interface ColorScaleSelectProps {
|
|
|
38
51
|
* @default true
|
|
39
52
|
*/
|
|
40
53
|
allowCustom?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the menu opens on one colour for every value, which a figure that
|
|
56
|
+
* needs no key — a plain table to print — is drawn with. Chosen, it writes
|
|
57
|
+
* {@link UNIFORM_COLOR_SCALE_ID}; its swatch picks the colour, written as
|
|
58
|
+
* `formatUniformColorScale` does.
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
allowUniform?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether it is a form field — a caption over a full-width button — or a
|
|
64
|
+
* setting on a figure's bar, writing its name and its scale on one compact
|
|
65
|
+
* line as `OverlayValueButton` does. The bar form needs an `OverlayLayer` or
|
|
66
|
+
* a `FigureBar` above it for its measurements.
|
|
67
|
+
* @default 'field'
|
|
68
|
+
*/
|
|
69
|
+
appearance?: 'field' | 'bar';
|
|
41
70
|
/**
|
|
42
71
|
* Value of the `data-testid` attribute of the button, for the end-to-end tests.
|
|
43
72
|
* @default undefined
|
|
@@ -67,11 +96,15 @@ export function ColorScaleSelect(props: ColorScaleSelectProps): ReactElement {
|
|
|
67
96
|
onChange,
|
|
68
97
|
label = '',
|
|
69
98
|
allowCustom = true,
|
|
99
|
+
allowUniform = false,
|
|
100
|
+
appearance = 'field',
|
|
70
101
|
testId,
|
|
71
102
|
} = props;
|
|
72
103
|
const t = useChromeT();
|
|
73
104
|
const [isOpen, setOpen] = useState(false);
|
|
74
105
|
const [isEditing, setEditing] = useState(false);
|
|
106
|
+
const uniform = allowUniform && isUniformColorScale(value);
|
|
107
|
+
const oneColor = uniformSwatch(uniform ? value : undefined).background;
|
|
75
108
|
const resolved = resolveColorScale(value);
|
|
76
109
|
|
|
77
110
|
const content = isEditing ? (
|
|
@@ -104,6 +137,36 @@ export function ColorScaleSelect(props: ColorScaleSelectProps): ReactElement {
|
|
|
104
137
|
</div>
|
|
105
138
|
) : (
|
|
106
139
|
<Menu style={MENU_STYLE}>
|
|
140
|
+
{allowUniform ? (
|
|
141
|
+
<MenuItem
|
|
142
|
+
roleStructure="listoption"
|
|
143
|
+
selected={uniform}
|
|
144
|
+
htmlTitle={t('color.uniformDescription')}
|
|
145
|
+
text={
|
|
146
|
+
<span style={UNIFORM_ITEM_STYLE}>
|
|
147
|
+
<span>{t('color.uniform')}</span>
|
|
148
|
+
<input
|
|
149
|
+
type="color"
|
|
150
|
+
aria-label={t('color.uniformColour')}
|
|
151
|
+
title={t('color.uniformColour')}
|
|
152
|
+
value={normalizeHexColor(oneColor) ?? UNREADABLE_COLOR}
|
|
153
|
+
style={UNIFORM_INPUT_STYLE}
|
|
154
|
+
// The swatch picks the colour; the rest of the row chooses
|
|
155
|
+
// one colour and closes the menu.
|
|
156
|
+
onClick={(event) => {
|
|
157
|
+
event.stopPropagation();
|
|
158
|
+
}}
|
|
159
|
+
onChange={(event) => {
|
|
160
|
+
onChange(formatUniformColorScale(event.target.value));
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
</span>
|
|
164
|
+
}
|
|
165
|
+
onClick={() => {
|
|
166
|
+
onChange(uniform ? value : UNIFORM_COLOR_SCALE_ID);
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
) : null}
|
|
107
170
|
{COLOR_SCALES.map((entry, index) => (
|
|
108
171
|
<ColorScaleEntry
|
|
109
172
|
key={entry.id}
|
|
@@ -112,7 +175,7 @@ export function ColorScaleSelect(props: ColorScaleSelectProps): ReactElement {
|
|
|
112
175
|
kind={entry.kind}
|
|
113
176
|
description={entry.description}
|
|
114
177
|
previous={COLOR_SCALES[index - 1]?.kind}
|
|
115
|
-
selected={entry.id === resolved.id}
|
|
178
|
+
selected={!uniform && entry.id === resolved.id}
|
|
116
179
|
onChange={onChange}
|
|
117
180
|
/>
|
|
118
181
|
))}
|
|
@@ -122,7 +185,9 @@ export function ColorScaleSelect(props: ColorScaleSelectProps): ReactElement {
|
|
|
122
185
|
<MenuItem
|
|
123
186
|
icon="edit"
|
|
124
187
|
text={t('color.customEllipsis')}
|
|
125
|
-
label={
|
|
188
|
+
label={
|
|
189
|
+
resolved.id === null && !uniform ? t('color.inUse') : undefined
|
|
190
|
+
}
|
|
126
191
|
shouldDismissPopover={false}
|
|
127
192
|
onClick={() => {
|
|
128
193
|
setEditing(true);
|
|
@@ -133,44 +198,79 @@ export function ColorScaleSelect(props: ColorScaleSelectProps): ReactElement {
|
|
|
133
198
|
</Menu>
|
|
134
199
|
);
|
|
135
200
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
201
|
+
const scaleName = uniform
|
|
202
|
+
? t('color.uniform')
|
|
203
|
+
: resolved.id === null
|
|
204
|
+
? t('color.custom')
|
|
205
|
+
: t.or(`color.scale.${resolved.id}.label`, resolved.label);
|
|
206
|
+
const name = label === '' ? t('color.colourScale') : label;
|
|
207
|
+
|
|
208
|
+
const target =
|
|
209
|
+
appearance === 'bar' ? (
|
|
210
|
+
<OverlayValueButton
|
|
211
|
+
label={name}
|
|
212
|
+
value={scaleName}
|
|
213
|
+
swatches={
|
|
214
|
+
uniform ? [oneColor] : sampleScale(resolved.scale, BAR_SWATCHES)
|
|
215
|
+
}
|
|
216
|
+
active={isOpen}
|
|
217
|
+
opensMenu
|
|
218
|
+
testId={testId}
|
|
219
|
+
/>
|
|
220
|
+
) : (
|
|
221
|
+
<Button
|
|
222
|
+
alignText="start"
|
|
223
|
+
aria-label={name}
|
|
224
|
+
data-testid={testId}
|
|
225
|
+
endIcon="caret-down"
|
|
226
|
+
fill
|
|
227
|
+
text={
|
|
228
|
+
<span style={BUTTON_TEXT_STYLE}>
|
|
229
|
+
<span>{scaleName}</span>
|
|
230
|
+
{uniform ? (
|
|
231
|
+
<span
|
|
232
|
+
style={{
|
|
233
|
+
...UNIFORM_BAR_STYLE,
|
|
234
|
+
width: BUTTON_BAR_WIDTH,
|
|
235
|
+
background: oneColor,
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
) : (
|
|
166
239
|
<ColorScaleBar
|
|
167
240
|
scale={resolved.scale}
|
|
168
241
|
style={{ width: BUTTON_BAR_WIDTH }}
|
|
169
242
|
/>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
243
|
+
)}
|
|
244
|
+
</span>
|
|
245
|
+
}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const popover = (
|
|
250
|
+
<PopoverNext
|
|
251
|
+
// A fade, like the pickers a site puts beside this one: the default
|
|
252
|
+
// grows the panel from a third of its size over about a second, which
|
|
253
|
+
// reads as the row behaving differently control by control.
|
|
254
|
+
animation="minimal"
|
|
255
|
+
arrow={false}
|
|
256
|
+
placement="bottom-start"
|
|
257
|
+
isOpen={isOpen}
|
|
258
|
+
content={content}
|
|
259
|
+
onInteraction={(next) => {
|
|
260
|
+
setOpen(next);
|
|
261
|
+
if (!next) setEditing(false);
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
{target}
|
|
265
|
+
</PopoverNext>
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (appearance === 'bar') return popover;
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<label className={className} style={FIELD_STYLE}>
|
|
272
|
+
{label === '' ? null : <span style={CAPTION_STYLE}>{label}</span>}
|
|
273
|
+
{popover}
|
|
174
274
|
</label>
|
|
175
275
|
);
|
|
176
276
|
}
|
|
@@ -197,6 +297,30 @@ const BUTTON_TEXT_STYLE = {
|
|
|
197
297
|
width: '100%',
|
|
198
298
|
} as const satisfies CSSProperties;
|
|
199
299
|
|
|
300
|
+
// The lines of the menu, so one colour reads as one more choice among them.
|
|
301
|
+
const UNIFORM_ITEM_STYLE = {
|
|
302
|
+
display: 'grid',
|
|
303
|
+
gridTemplateColumns: '7rem 1fr',
|
|
304
|
+
alignItems: 'center',
|
|
305
|
+
gap: 10,
|
|
306
|
+
minWidth: 220,
|
|
307
|
+
} as const satisfies CSSProperties;
|
|
308
|
+
|
|
309
|
+
const UNIFORM_BAR_STYLE = {
|
|
310
|
+
display: 'inline-block',
|
|
311
|
+
height: 12,
|
|
312
|
+
borderRadius: 2,
|
|
313
|
+
} as const satisfies CSSProperties;
|
|
314
|
+
|
|
315
|
+
const UNIFORM_INPUT_STYLE = {
|
|
316
|
+
width: '100%',
|
|
317
|
+
height: 18,
|
|
318
|
+
padding: 0,
|
|
319
|
+
border: 0,
|
|
320
|
+
background: 'none',
|
|
321
|
+
cursor: 'pointer',
|
|
322
|
+
} as const satisfies CSSProperties;
|
|
323
|
+
|
|
200
324
|
const MENU_STYLE = {
|
|
201
325
|
maxHeight: '60vh',
|
|
202
326
|
overflowY: 'auto',
|