react-cheminfo 0.50.0 → 0.52.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 +71 -16
- 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/host/core/connectHost.d.ts +75 -0
- package/lib/host/core/connectHost.d.ts.map +1 -0
- package/lib/host/core/connectHost.js +171 -0
- package/lib/host/core/connectHost.js.map +1 -0
- package/lib/host/core/hostStructure.d.ts +52 -0
- package/lib/host/core/hostStructure.d.ts.map +1 -0
- package/lib/host/core/hostStructure.js +106 -0
- package/lib/host/core/hostStructure.js.map +1 -0
- package/lib/host/core/index.d.ts +9 -0
- package/lib/host/core/index.d.ts.map +1 -0
- package/lib/host/core/index.js +5 -0
- package/lib/host/core/index.js.map +1 -0
- package/lib/host/core/kinds.d.ts +73 -0
- package/lib/host/core/kinds.d.ts.map +1 -0
- package/lib/host/core/kinds.js +88 -0
- package/lib/host/core/kinds.js.map +1 -0
- package/lib/host/core/protocol.d.ts +134 -0
- package/lib/host/core/protocol.d.ts.map +1 -0
- package/lib/host/core/protocol.js +92 -0
- package/lib/host/core/protocol.js.map +1 -0
- package/lib/host/testing/sciPeaks.d.ts +21 -0
- package/lib/host/testing/sciPeaks.d.ts.map +1 -0
- package/lib/host/testing/sciPeaks.js +62 -0
- package/lib/host/testing/sciPeaks.js.map +1 -0
- package/lib/host/testing/standInHost.d.ts +47 -0
- package/lib/host/testing/standInHost.d.ts.map +1 -0
- package/lib/host/testing/standInHost.js +85 -0
- package/lib/host/testing/standInHost.js.map +1 -0
- package/lib/host/ui/index.d.ts +3 -0
- package/lib/host/ui/index.d.ts.map +1 -0
- package/lib/host/ui/index.js +2 -0
- package/lib/host/ui/index.js.map +1 -0
- package/lib/host/ui/useHostConnection.d.ts +36 -0
- package/lib/host/ui/useHostConnection.d.ts.map +1 -0
- package/lib/host/ui/useHostConnection.js +62 -0
- package/lib/host/ui/useHostConnection.js.map +1 -0
- package/lib/host-testing.d.ts +8 -0
- package/lib/host-testing.d.ts.map +1 -0
- package/lib/host-testing.js +7 -0
- package/lib/host-testing.js.map +1 -0
- package/lib/host.d.ts +11 -0
- package/lib/host.d.ts.map +1 -0
- package/lib/host.js +11 -0
- package/lib/host.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 +4 -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/host/core/connectHost.ts +240 -0
- package/src/host/core/hostStructure.ts +129 -0
- package/src/host/core/index.ts +46 -0
- package/src/host/core/kinds.ts +128 -0
- package/src/host/core/protocol.ts +197 -0
- package/src/host/testing/sciPeaks.ts +66 -0
- package/src/host/testing/standInHost.ts +116 -0
- package/src/host/ui/index.ts +2 -0
- package/src/host/ui/useHostConnection.ts +85 -0
- package/src/host-testing.ts +18 -0
- package/src/host.ts +11 -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
|
@@ -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',
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Slider } from '@blueprintjs/core';
|
|
2
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
import { formatPercent } from '../../format/core/numbers.ts';
|
|
6
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
7
|
+
import type { ColorStop } from '../core/interpolate.ts';
|
|
8
|
+
import type { ScaleTone } from '../core/tone.ts';
|
|
9
|
+
import { scaleTone, setScaleTone } from '../core/tone.ts';
|
|
10
|
+
|
|
11
|
+
const STEP = 0.01;
|
|
12
|
+
// Above zero: a grey or a black anchor has no hue left to bring back.
|
|
13
|
+
const MINIMUM_SATURATION = 0.1;
|
|
14
|
+
const MINIMUM_VALUE = 0.2;
|
|
15
|
+
|
|
16
|
+
/** What {@link ColorScaleTone} edits. */
|
|
17
|
+
export interface ColorScaleToneProps {
|
|
18
|
+
/** The anchors of the scale being edited. */
|
|
19
|
+
stops: readonly ColorStop[];
|
|
20
|
+
/** Called with the recoloured anchors on every move of either slider. */
|
|
21
|
+
onChange: (stops: ColorStop[]) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Two sliders setting the saturation and the brightness of every anchor at
|
|
26
|
+
* once, each anchor keeping its hue.
|
|
27
|
+
* @param props - See {@link ColorScaleToneProps}.
|
|
28
|
+
* @returns The two sliders, each with its value.
|
|
29
|
+
*/
|
|
30
|
+
export function ColorScaleTone(props: ColorScaleToneProps): ReactElement {
|
|
31
|
+
const { stops, onChange } = props;
|
|
32
|
+
const t = useChromeT();
|
|
33
|
+
const tone = scaleTone(stops);
|
|
34
|
+
// The anchors as a drag found them, so their hues are read once per drag
|
|
35
|
+
// rather than off colours already rounded by the previous move.
|
|
36
|
+
const base = useRef<readonly ColorStop[] | null>(null);
|
|
37
|
+
|
|
38
|
+
function set(next: Partial<ScaleTone>): void {
|
|
39
|
+
base.current ??= stops;
|
|
40
|
+
onChange(setScaleTone(base.current, next));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function release(): void {
|
|
44
|
+
base.current = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div style={GRID_STYLE} title={t('color.toneHelp')}>
|
|
49
|
+
<span style={LABEL_STYLE}>{t('color.saturation')}</span>
|
|
50
|
+
<span style={SLIDER_STYLE}>
|
|
51
|
+
<Slider
|
|
52
|
+
min={MINIMUM_SATURATION}
|
|
53
|
+
max={1}
|
|
54
|
+
stepSize={STEP}
|
|
55
|
+
labelRenderer={false}
|
|
56
|
+
value={Math.max(MINIMUM_SATURATION, tone.saturation)}
|
|
57
|
+
onChange={(saturation) => {
|
|
58
|
+
set({ saturation });
|
|
59
|
+
}}
|
|
60
|
+
onRelease={release}
|
|
61
|
+
/>
|
|
62
|
+
</span>
|
|
63
|
+
<span style={VALUE_STYLE}>{formatPercent(tone.saturation, 0)}</span>
|
|
64
|
+
<span style={LABEL_STYLE}>{t('color.brightness')}</span>
|
|
65
|
+
<span style={SLIDER_STYLE}>
|
|
66
|
+
<Slider
|
|
67
|
+
min={MINIMUM_VALUE}
|
|
68
|
+
max={1}
|
|
69
|
+
stepSize={STEP}
|
|
70
|
+
labelRenderer={false}
|
|
71
|
+
value={Math.max(MINIMUM_VALUE, tone.value)}
|
|
72
|
+
onChange={(value) => {
|
|
73
|
+
set({ value });
|
|
74
|
+
}}
|
|
75
|
+
onRelease={release}
|
|
76
|
+
/>
|
|
77
|
+
</span>
|
|
78
|
+
<span style={VALUE_STYLE}>{formatPercent(tone.value, 0)}</span>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const GRID_STYLE = {
|
|
84
|
+
display: 'grid',
|
|
85
|
+
gridTemplateColumns: 'max-content minmax(0, 1fr) 3.5em',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
columnGap: 10,
|
|
88
|
+
rowGap: 4,
|
|
89
|
+
} as const satisfies CSSProperties;
|
|
90
|
+
|
|
91
|
+
const LABEL_STYLE = { fontSize: 12 } as const satisfies CSSProperties;
|
|
92
|
+
|
|
93
|
+
/** Room on both sides, so the handle is never clipped at either end. */
|
|
94
|
+
const SLIDER_STYLE = {
|
|
95
|
+
display: 'block',
|
|
96
|
+
paddingInline: 8,
|
|
97
|
+
} as const satisfies CSSProperties;
|
|
98
|
+
|
|
99
|
+
const VALUE_STYLE = {
|
|
100
|
+
fontSize: 12,
|
|
101
|
+
fontVariantNumeric: 'tabular-nums',
|
|
102
|
+
textAlign: 'right',
|
|
103
|
+
} as const satisfies CSSProperties;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CSSProperties,
|
|
3
|
+
KeyboardEvent,
|
|
4
|
+
PointerEvent,
|
|
5
|
+
ReactElement,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { useRef } from 'react';
|
|
8
|
+
|
|
9
|
+
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
10
|
+
import type { ColorScale, ColorStop } from '../core/interpolate.ts';
|
|
11
|
+
import { addColorStop, moveColorStop } from '../core/stops.ts';
|
|
12
|
+
|
|
13
|
+
import { ColorScaleBar } from './ColorScaleBar.tsx';
|
|
14
|
+
|
|
15
|
+
const STRIP_HEIGHT = 28;
|
|
16
|
+
const STRIP_SAMPLES = 64;
|
|
17
|
+
const SWATCH_SIZE = 14;
|
|
18
|
+
const KEY_STEP = 0.01;
|
|
19
|
+
const KEY_STEP_LARGE = 0.1;
|
|
20
|
+
|
|
21
|
+
/** What {@link ColorScaleTrack} draws and hands back. */
|
|
22
|
+
export interface ColorScaleTrackProps {
|
|
23
|
+
/** The scale being edited. */
|
|
24
|
+
scale: ColorScale;
|
|
25
|
+
/** The anchor whose colour and position the editor shows. */
|
|
26
|
+
selected: number;
|
|
27
|
+
/** The anchor pointed at, which Backspace would remove. */
|
|
28
|
+
hovered: number | null;
|
|
29
|
+
/** Called when an anchor is clicked, focused, dragged or added. */
|
|
30
|
+
onSelect: (index: number) => void;
|
|
31
|
+
/** Called when an anchor is pointed at, and with `null` when it is left. */
|
|
32
|
+
onHover: (index: number | null) => void;
|
|
33
|
+
/** Called with the anchors after every add or move. */
|
|
34
|
+
onChange: (stops: ColorStop[]) => void;
|
|
35
|
+
/** Called when an anchor asks to be removed, from the keyboard. */
|
|
36
|
+
onRemove: (index: number) => void;
|
|
37
|
+
/** Called when an anchor is double-clicked, to recolour it. */
|
|
38
|
+
onPick: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The scale as one strip with its anchors on it: a click on the strip adds an
|
|
43
|
+
* anchor there, and an anchor is dragged along it.
|
|
44
|
+
* @param props - See {@link ColorScaleTrackProps}.
|
|
45
|
+
* @returns The strip and its anchors.
|
|
46
|
+
*/
|
|
47
|
+
export function ColorScaleTrack(props: ColorScaleTrackProps): ReactElement {
|
|
48
|
+
const {
|
|
49
|
+
scale,
|
|
50
|
+
selected,
|
|
51
|
+
hovered,
|
|
52
|
+
onSelect,
|
|
53
|
+
onHover,
|
|
54
|
+
onChange,
|
|
55
|
+
onRemove,
|
|
56
|
+
onPick,
|
|
57
|
+
} = props;
|
|
58
|
+
const t = useChromeT();
|
|
59
|
+
// The anchors as the drag left them: a pointer can move twice before the
|
|
60
|
+
// page renders the first move, and the second must not undo it.
|
|
61
|
+
const drag = useRef<{ index: number; stops: ColorStop[] } | null>(null);
|
|
62
|
+
|
|
63
|
+
function start(event: PointerEvent<HTMLDivElement>): void {
|
|
64
|
+
if (event.button !== 0) return;
|
|
65
|
+
const anchor = (event.target as HTMLElement).closest<HTMLElement>(
|
|
66
|
+
'[data-anchor]',
|
|
67
|
+
);
|
|
68
|
+
let index = Number(anchor?.dataset.anchor);
|
|
69
|
+
let stops = [...scale.stops];
|
|
70
|
+
if (anchor === null) {
|
|
71
|
+
const added = addColorStop(scale, positionOf(event));
|
|
72
|
+
if (added === null) return;
|
|
73
|
+
({ index, stops } = added);
|
|
74
|
+
onChange(stops);
|
|
75
|
+
}
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
78
|
+
anchor?.focus({ focusVisible: false });
|
|
79
|
+
drag.current = { index, stops };
|
|
80
|
+
onSelect(index);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function move(event: PointerEvent<HTMLDivElement>): void {
|
|
84
|
+
if (drag.current === null) return;
|
|
85
|
+
const moved = moveColorStop(
|
|
86
|
+
drag.current.stops,
|
|
87
|
+
drag.current.index,
|
|
88
|
+
positionOf(event),
|
|
89
|
+
);
|
|
90
|
+
drag.current = moved;
|
|
91
|
+
onChange(moved.stops);
|
|
92
|
+
onSelect(moved.index);
|
|
93
|
+
onHover(moved.index);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function stop(): void {
|
|
97
|
+
drag.current = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function nudge(event: KeyboardEvent<HTMLSpanElement>, index: number): void {
|
|
101
|
+
if (event.key === 'Backspace' || event.key === 'Delete') {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
event.stopPropagation();
|
|
104
|
+
onRemove(index);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const step = event.shiftKey ? KEY_STEP_LARGE : KEY_STEP;
|
|
108
|
+
const delta =
|
|
109
|
+
event.key === 'ArrowRight' || event.key === 'ArrowUp'
|
|
110
|
+
? step
|
|
111
|
+
: event.key === 'ArrowLeft' || event.key === 'ArrowDown'
|
|
112
|
+
? -step
|
|
113
|
+
: 0;
|
|
114
|
+
if (delta === 0) return;
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
// From the keyboard an anchor stops at its neighbours, so focus stays on
|
|
117
|
+
// the anchor being moved.
|
|
118
|
+
const low = scale.stops[index - 1]?.position ?? 0;
|
|
119
|
+
const high = scale.stops[index + 1]?.position ?? 1;
|
|
120
|
+
const position = (scale.stops[index]?.position ?? 0) + delta;
|
|
121
|
+
const moved = moveColorStop(
|
|
122
|
+
scale.stops,
|
|
123
|
+
index,
|
|
124
|
+
Math.min(high, Math.max(low, position)),
|
|
125
|
+
);
|
|
126
|
+
onChange(moved.stops);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
style={TRACK_STYLE}
|
|
132
|
+
onPointerDown={start}
|
|
133
|
+
onPointerMove={move}
|
|
134
|
+
onPointerUp={stop}
|
|
135
|
+
onPointerCancel={stop}
|
|
136
|
+
>
|
|
137
|
+
<ColorScaleBar
|
|
138
|
+
scale={scale}
|
|
139
|
+
height={STRIP_HEIGHT}
|
|
140
|
+
samples={STRIP_SAMPLES}
|
|
141
|
+
label={t('color.scaleBeingEdited')}
|
|
142
|
+
/>
|
|
143
|
+
{scale.stops.map((anchor, index) => (
|
|
144
|
+
<span
|
|
145
|
+
// An anchor has no identity but its rank, and keeping the element
|
|
146
|
+
// keeps the focus on it while it is dragged past a neighbour.
|
|
147
|
+
key={`anchor-${String(index)}`}
|
|
148
|
+
data-anchor={index}
|
|
149
|
+
role="slider"
|
|
150
|
+
tabIndex={0}
|
|
151
|
+
aria-label={t('color.anchorPosition', { index: index + 1 })}
|
|
152
|
+
aria-valuemin={0}
|
|
153
|
+
aria-valuemax={1}
|
|
154
|
+
aria-valuenow={anchor.position}
|
|
155
|
+
title={anchor.color}
|
|
156
|
+
style={{
|
|
157
|
+
...ANCHOR_STYLE,
|
|
158
|
+
left: `${String(anchor.position * 100)}%`,
|
|
159
|
+
zIndex: index === selected ? 2 : 1,
|
|
160
|
+
}}
|
|
161
|
+
onFocus={() => {
|
|
162
|
+
onSelect(index);
|
|
163
|
+
}}
|
|
164
|
+
onKeyDown={(event) => {
|
|
165
|
+
nudge(event, index);
|
|
166
|
+
}}
|
|
167
|
+
onPointerEnter={() => {
|
|
168
|
+
if (drag.current === null) onHover(index);
|
|
169
|
+
}}
|
|
170
|
+
onPointerLeave={() => {
|
|
171
|
+
if (drag.current === null) onHover(null);
|
|
172
|
+
}}
|
|
173
|
+
onDoubleClick={onPick}
|
|
174
|
+
>
|
|
175
|
+
<span style={NEEDLE_STYLE} />
|
|
176
|
+
<span
|
|
177
|
+
style={{
|
|
178
|
+
...SWATCH_STYLE,
|
|
179
|
+
background: anchor.color,
|
|
180
|
+
boxShadow:
|
|
181
|
+
index === selected
|
|
182
|
+
? SELECTED_RING
|
|
183
|
+
: index === hovered
|
|
184
|
+
? HOVERED_RING
|
|
185
|
+
: SWATCH_STYLE.boxShadow,
|
|
186
|
+
}}
|
|
187
|
+
/>
|
|
188
|
+
</span>
|
|
189
|
+
))}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function positionOf(event: PointerEvent<HTMLDivElement>): number {
|
|
195
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
196
|
+
return box.width === 0 ? 0 : (event.clientX - box.left) / box.width;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const SELECTED_RING = '0 0 0 2px var(--accent)';
|
|
200
|
+
const HOVERED_RING = '0 0 0 2px var(--text-muted)';
|
|
201
|
+
|
|
202
|
+
const TRACK_STYLE = {
|
|
203
|
+
position: 'relative',
|
|
204
|
+
margin: `0 ${String(SWATCH_SIZE / 2 + 2)}px`,
|
|
205
|
+
paddingBottom: SWATCH_SIZE + 4,
|
|
206
|
+
cursor: 'copy',
|
|
207
|
+
touchAction: 'none',
|
|
208
|
+
userSelect: 'none',
|
|
209
|
+
} as const satisfies CSSProperties;
|
|
210
|
+
|
|
211
|
+
const ANCHOR_STYLE = {
|
|
212
|
+
position: 'absolute',
|
|
213
|
+
top: -3,
|
|
214
|
+
bottom: 0,
|
|
215
|
+
display: 'flex',
|
|
216
|
+
width: SWATCH_SIZE + 4,
|
|
217
|
+
flexDirection: 'column',
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
cursor: 'ew-resize',
|
|
220
|
+
outline: 'none',
|
|
221
|
+
transform: 'translateX(-50%)',
|
|
222
|
+
} as const satisfies CSSProperties;
|
|
223
|
+
|
|
224
|
+
const NEEDLE_STYLE = {
|
|
225
|
+
width: 3,
|
|
226
|
+
height: STRIP_HEIGHT + 6,
|
|
227
|
+
border: '1px solid var(--text)',
|
|
228
|
+
borderRadius: 2,
|
|
229
|
+
background: 'var(--surface)',
|
|
230
|
+
} as const satisfies CSSProperties;
|
|
231
|
+
|
|
232
|
+
const SWATCH_STYLE = {
|
|
233
|
+
width: SWATCH_SIZE,
|
|
234
|
+
height: SWATCH_SIZE,
|
|
235
|
+
marginTop: -2,
|
|
236
|
+
border: '2px solid var(--surface)',
|
|
237
|
+
borderRadius: 4,
|
|
238
|
+
boxShadow: '0 0 0 1px var(--border-strong)',
|
|
239
|
+
} as const satisfies CSSProperties;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { writeBlobToClipboard } from '../../clipboard/core/writeBlobToClipboard.ts';
|
|
2
|
+
|
|
3
|
+
/** What the last copy came to, when it is worth a line of its own. */
|
|
4
|
+
export type FigureNotice = 'copied' | 'copyUnsupported';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Put a figure on the clipboard as a PNG.
|
|
8
|
+
*
|
|
9
|
+
* Call it from the click itself, with the picture still being painted: Safari
|
|
10
|
+
* only allows a clipboard write the click started.
|
|
11
|
+
* @param png - The picture being painted.
|
|
12
|
+
* @returns `copied`, or `copyUnsupported` where the clipboard takes no picture.
|
|
13
|
+
* @throws {Error} The painting's own error, when the picture could not be
|
|
14
|
+
* painted — a failure to report, not a browser that cannot copy.
|
|
15
|
+
*/
|
|
16
|
+
export async function copyFigure(png: Promise<Blob>): Promise<FigureNotice> {
|
|
17
|
+
if (await writeBlobToClipboard(png)) return 'copied';
|
|
18
|
+
await png;
|
|
19
|
+
return 'copyUnsupported';
|
|
20
|
+
}
|
|
@@ -89,6 +89,13 @@ export interface HtmlPaintDrawing {
|
|
|
89
89
|
x: number;
|
|
90
90
|
/** Its top edge, in pixels from the top of the part. */
|
|
91
91
|
y: number;
|
|
92
|
+
/**
|
|
93
|
+
* Its width and height as the browser drew it. A drawing sized by CSS
|
|
94
|
+
* carries no size of its own, and an `<svg>` without one fills the whole
|
|
95
|
+
* file.
|
|
96
|
+
* @default undefined — the markup is placed as it is
|
|
97
|
+
*/
|
|
98
|
+
size?: { width: number; height: number };
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
/** Everything inside an element the page drew faded. */
|
|
@@ -123,7 +130,7 @@ function paintMarkup(paint: HtmlPaint): string {
|
|
|
123
130
|
case 'text':
|
|
124
131
|
return textMarkup(paint);
|
|
125
132
|
case 'drawing':
|
|
126
|
-
return `<g transform="translate(${round(paint.x)} ${round(paint.y)})">${paint
|
|
133
|
+
return `<g transform="translate(${round(paint.x)} ${round(paint.y)})">${sizedDrawing(paint)}</g>`;
|
|
127
134
|
case 'group':
|
|
128
135
|
return `<g opacity="${round(paint.opacity)}">${figureHtmlMarkup(paint.children)}</g>`;
|
|
129
136
|
default:
|
|
@@ -131,6 +138,12 @@ function paintMarkup(paint: HtmlPaint): string {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
function sizedDrawing(drawing: HtmlPaintDrawing): string {
|
|
142
|
+
const { markup, size } = drawing;
|
|
143
|
+
if (size === undefined) return markup;
|
|
144
|
+
return `<svg width="${round(size.width)}" height="${round(size.height)}" overflow="visible">${markup}</svg>`;
|
|
145
|
+
}
|
|
146
|
+
|
|
134
147
|
function boxMarkup(box: HtmlPaintBox): string {
|
|
135
148
|
const { x, y, width, height, fill, stroke, dash } = box;
|
|
136
149
|
const { radius = 0, strokeWidth = 1 } = box;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type { FigureNotice } from './copyFigure.ts';
|
|
2
|
+
export { copyFigure } from './copyFigure.ts';
|
|
1
3
|
export { cssTokenNames, resolveCssTokens } from './cssTokens.ts';
|
|
2
4
|
export { downloadBlob } from './downloadBlob.ts';
|
|
3
5
|
export type { DownloadFigureOptions, FigureFormat } from './downloadFigure.ts';
|