react-cheminfo 0.46.0 → 0.48.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 +69 -0
- package/lib/chart/core/chartDomain.d.ts +14 -0
- package/lib/chart/core/chartDomain.d.ts.map +1 -1
- package/lib/chart/core/chartDomain.js +1 -0
- package/lib/chart/core/chartDomain.js.map +1 -1
- package/lib/chart/core/selectionGeometry.d.ts +4 -2
- package/lib/chart/core/selectionGeometry.d.ts.map +1 -1
- package/lib/chart/core/selectionGeometry.js +4 -2
- package/lib/chart/core/selectionGeometry.js.map +1 -1
- package/lib/chart/core/zoomDomain.d.ts +11 -1
- package/lib/chart/core/zoomDomain.d.ts.map +1 -1
- package/lib/chart/core/zoomDomain.js +25 -5
- package/lib/chart/core/zoomDomain.js.map +1 -1
- package/lib/chart/ui/chartGestures.d.ts +26 -1
- package/lib/chart/ui/chartGestures.d.ts.map +1 -1
- package/lib/chart/ui/chartGestures.js +21 -0
- package/lib/chart/ui/chartGestures.js.map +1 -1
- package/lib/chart/ui/index.d.ts +1 -1
- package/lib/chart/ui/index.d.ts.map +1 -1
- package/lib/chart/ui/index.js +1 -1
- package/lib/chart/ui/index.js.map +1 -1
- package/lib/chart/ui/useChartZoom.d.ts.map +1 -1
- package/lib/chart/ui/useChartZoom.js +17 -1
- package/lib/chart/ui/useChartZoom.js.map +1 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/download/core/cssPaint.d.ts +27 -0
- package/lib/download/core/cssPaint.d.ts.map +1 -0
- package/lib/download/core/cssPaint.js +63 -0
- package/lib/download/core/cssPaint.js.map +1 -0
- package/lib/download/core/drawingMarkup.d.ts +11 -0
- package/lib/download/core/drawingMarkup.d.ts.map +1 -0
- package/lib/download/core/drawingMarkup.js +32 -0
- package/lib/download/core/drawingMarkup.js.map +1 -0
- package/lib/download/core/figureHtml.d.ts +25 -0
- package/lib/download/core/figureHtml.d.ts.map +1 -0
- package/lib/download/core/figureHtml.js +174 -0
- package/lib/download/core/figureHtml.js.map +1 -0
- package/lib/download/core/figureHtmlMarkup.d.ts +107 -0
- package/lib/download/core/figureHtmlMarkup.d.ts.map +1 -0
- package/lib/download/core/figureHtmlMarkup.js +75 -0
- package/lib/download/core/figureHtmlMarkup.js.map +1 -0
- package/lib/download/core/figureSvg.d.ts.map +1 -1
- package/lib/download/core/figureSvg.js +16 -35
- package/lib/download/core/figureSvg.js.map +1 -1
- package/lib/download/core/figureSvgDocument.d.ts +3 -3
- package/lib/download/core/figureSvgDocument.d.ts.map +1 -1
- package/lib/download/core/figureTarget.d.ts +14 -3
- package/lib/download/core/figureTarget.d.ts.map +1 -1
- package/lib/download/core/figureTarget.js +42 -3
- package/lib/download/core/figureTarget.js.map +1 -1
- package/lib/download/core/index.d.ts +1 -1
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +1 -1
- package/lib/download/core/index.js.map +1 -1
- package/lib/download/core/textLines.d.ts +27 -0
- package/lib/download/core/textLines.d.ts.map +1 -0
- package/lib/download/core/textLines.js +51 -0
- package/lib/download/core/textLines.js.map +1 -0
- package/lib/ecosystem/core/lookup.d.ts +2 -1
- package/lib/ecosystem/core/lookup.d.ts.map +1 -1
- package/lib/ecosystem/core/lookup.js +10 -3
- package/lib/ecosystem/core/lookup.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +2 -2
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/core/unlistedSites.d.ts +9 -0
- package/lib/ecosystem/core/unlistedSites.d.ts.map +1 -0
- package/lib/ecosystem/core/unlistedSites.js +35 -0
- package/lib/ecosystem/core/unlistedSites.js.map +1 -0
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +7 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/ir/core/irMode.d.ts.map +1 -1
- package/lib/ir/core/irMode.js +6 -1
- package/lib/ir/core/irMode.js.map +1 -1
- package/lib/locales/de.json +56 -1
- package/lib/locales/en.json +56 -1
- package/lib/locales/es.json +56 -1
- package/lib/locales/fr.json +56 -1
- package/lib/locales/it.json +56 -1
- package/lib/periodic/core/index.d.ts +1 -1
- package/lib/periodic/core/index.d.ts.map +1 -1
- package/lib/periodic/core/layout.d.ts +11 -0
- package/lib/periodic/core/layout.d.ts.map +1 -1
- package/lib/periodic/core/layout.js.map +1 -1
- package/lib/periodic/ui/ElementCell.d.ts +6 -2
- package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
- package/lib/periodic/ui/ElementCell.js +2 -2
- package/lib/periodic/ui/ElementCell.js.map +1 -1
- package/lib/periodic/ui/PeriodicTable.d.ts +3 -98
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +9 -84
- package/lib/periodic/ui/PeriodicTable.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.d.ts +13 -3
- package/lib/periodic/ui/PeriodicTableChrome.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.js +10 -20
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/periodic/ui/SelectAllCorner.d.ts +23 -0
- package/lib/periodic/ui/SelectAllCorner.d.ts.map +1 -0
- package/lib/periodic/ui/SelectAllCorner.js +40 -0
- package/lib/periodic/ui/SelectAllCorner.js.map +1 -0
- package/lib/periodic/ui/headerStyles.d.ts +17 -0
- package/lib/periodic/ui/headerStyles.d.ts.map +1 -0
- package/lib/periodic/ui/headerStyles.js +19 -0
- package/lib/periodic/ui/headerStyles.js.map +1 -0
- package/lib/periodic/ui/periodicTableProps.d.ts +115 -0
- package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -0
- package/lib/periodic/ui/periodicTableProps.js +8 -0
- package/lib/periodic/ui/periodicTableProps.js.map +1 -0
- package/lib/periodic/ui/tableLayout.d.ts +26 -0
- package/lib/periodic/ui/tableLayout.d.ts.map +1 -0
- package/lib/periodic/ui/tableLayout.js +86 -0
- package/lib/periodic/ui/tableLayout.js.map +1 -0
- package/lib/split/ui/SplitColumn.d.ts +29 -0
- package/lib/split/ui/SplitColumn.d.ts.map +1 -0
- package/lib/split/ui/SplitColumn.js +19 -0
- package/lib/split/ui/SplitColumn.js.map +1 -0
- package/lib/split/ui/SplitRow.d.ts +14 -53
- package/lib/split/ui/SplitRow.d.ts.map +1 -1
- package/lib/split/ui/SplitRow.js +8 -90
- package/lib/split/ui/SplitRow.js.map +1 -1
- package/lib/split/ui/index.d.ts +3 -0
- package/lib/split/ui/index.d.ts.map +1 -1
- package/lib/split/ui/index.js +1 -0
- package/lib/split/ui/index.js.map +1 -1
- package/lib/split/ui/splitPanes.d.ts +84 -0
- package/lib/split/ui/splitPanes.d.ts.map +1 -0
- package/lib/split/ui/splitPanes.js +125 -0
- package/lib/split/ui/splitPanes.js.map +1 -0
- package/lib/taxonomy/core/index.d.ts +8 -0
- package/lib/taxonomy/core/index.d.ts.map +1 -0
- package/lib/taxonomy/core/index.js +5 -0
- package/lib/taxonomy/core/index.js.map +1 -0
- package/lib/taxonomy/core/lineage.d.ts +83 -0
- package/lib/taxonomy/core/lineage.d.ts.map +1 -0
- package/lib/taxonomy/core/lineage.js +126 -0
- package/lib/taxonomy/core/lineage.js.map +1 -0
- package/lib/taxonomy/core/ncbi.d.ts +8 -0
- package/lib/taxonomy/core/ncbi.d.ts.map +1 -0
- package/lib/taxonomy/core/ncbi.js +10 -0
- package/lib/taxonomy/core/ncbi.js.map +1 -0
- package/lib/taxonomy/core/organismName.d.ts +28 -0
- package/lib/taxonomy/core/organismName.d.ts.map +1 -0
- package/lib/taxonomy/core/organismName.js +188 -0
- package/lib/taxonomy/core/organismName.js.map +1 -0
- package/lib/taxonomy/core/organismWords.d.ts +20 -0
- package/lib/taxonomy/core/organismWords.d.ts.map +1 -0
- package/lib/taxonomy/core/organismWords.js +86 -0
- package/lib/taxonomy/core/organismWords.js.map +1 -0
- package/lib/taxonomy/core/ranks.d.ts +51 -0
- package/lib/taxonomy/core/ranks.d.ts.map +1 -0
- package/lib/taxonomy/core/ranks.js +123 -0
- package/lib/taxonomy/core/ranks.js.map +1 -0
- package/lib/taxonomy/ui/OrganismName.d.ts +28 -0
- package/lib/taxonomy/ui/OrganismName.d.ts.map +1 -0
- package/lib/taxonomy/ui/OrganismName.js +28 -0
- package/lib/taxonomy/ui/OrganismName.js.map +1 -0
- package/lib/taxonomy/ui/TaxonLineage.d.ts +67 -0
- package/lib/taxonomy/ui/TaxonLineage.d.ts.map +1 -0
- package/lib/taxonomy/ui/TaxonLineage.js +45 -0
- package/lib/taxonomy/ui/TaxonLineage.js.map +1 -0
- package/lib/taxonomy/ui/index.d.ts +6 -0
- package/lib/taxonomy/ui/index.d.ts.map +1 -0
- package/lib/taxonomy/ui/index.js +4 -0
- package/lib/taxonomy/ui/index.js.map +1 -0
- package/lib/taxonomy/ui/rankLabel.d.ts +17 -0
- package/lib/taxonomy/ui/rankLabel.d.ts.map +1 -0
- package/lib/taxonomy/ui/rankLabel.js +77 -0
- package/lib/taxonomy/ui/rankLabel.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/chart/core/chartDomain.ts +15 -0
- package/src/chart/core/selectionGeometry.ts +4 -2
- package/src/chart/core/zoomDomain.ts +37 -5
- package/src/chart/ui/chartGestures.ts +41 -1
- package/src/chart/ui/index.ts +1 -1
- package/src/chart/ui/useChartZoom.ts +25 -1
- package/src/core.ts +1 -0
- package/src/download/core/cssPaint.ts +71 -0
- package/src/download/core/drawingMarkup.ts +35 -0
- package/src/download/core/figureHtml.ts +214 -0
- package/src/download/core/figureHtmlMarkup.ts +187 -0
- package/src/download/core/figureSvg.ts +17 -38
- package/src/download/core/figureSvgDocument.ts +3 -3
- package/src/download/core/figureTarget.ts +41 -8
- package/src/download/core/index.ts +1 -0
- package/src/download/core/textLines.ts +67 -0
- package/src/ecosystem/core/lookup.ts +11 -3
- package/src/ecosystem/core/types.ts +4 -2
- package/src/ecosystem/core/unlistedSites.ts +37 -0
- package/src/ecosystem/ui/glyphs.tsx +44 -0
- package/src/ir/core/irMode.ts +6 -1
- package/src/locales/de.json +56 -1
- package/src/locales/en.json +56 -1
- package/src/locales/es.json +56 -1
- package/src/locales/fr.json +56 -1
- package/src/locales/it.json +56 -1
- package/src/periodic/core/index.ts +1 -1
- package/src/periodic/core/layout.ts +12 -0
- package/src/periodic/ui/ElementCell.tsx +8 -4
- package/src/periodic/ui/PeriodicTable.tsx +27 -197
- package/src/periodic/ui/PeriodicTableChrome.tsx +23 -20
- package/src/periodic/ui/SelectAllCorner.tsx +65 -0
- package/src/periodic/ui/headerStyles.ts +23 -0
- package/src/periodic/ui/periodicTableProps.ts +117 -0
- package/src/periodic/ui/tableLayout.ts +96 -0
- package/src/split/ui/SplitColumn.tsx +35 -0
- package/src/split/ui/SplitRow.tsx +16 -208
- package/src/split/ui/index.ts +3 -0
- package/src/split/ui/splitPanes.tsx +283 -0
- package/src/taxonomy/core/index.ts +20 -0
- package/src/taxonomy/core/lineage.ts +161 -0
- package/src/taxonomy/core/ncbi.ts +9 -0
- package/src/taxonomy/core/organismName.ts +217 -0
- package/src/taxonomy/core/organismWords.ts +86 -0
- package/src/taxonomy/core/ranks.ts +134 -0
- package/src/taxonomy/ui/OrganismName.tsx +59 -0
- package/src/taxonomy/ui/TaxonLineage.tsx +183 -0
- package/src/taxonomy/ui/index.ts +5 -0
- package/src/taxonomy/ui/rankLabel.ts +81 -0
- package/src/ui.ts +1 -0
- package/styles/chrome.css +135 -6
|
@@ -55,7 +55,17 @@ export interface DragBox {
|
|
|
55
55
|
toY: number;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* The drag as it is being made, for the preview rectangle to be drawn from.
|
|
60
|
+
*
|
|
61
|
+
* Its **vertical corners are the window the release will take**, not the two
|
|
62
|
+
* places the hand passed through. The two differ by the baseline a viewer's
|
|
63
|
+
* rules hold: on a mirrored chart zero runs across the middle of the plot, so a
|
|
64
|
+
* drag made wholly inside the reflected half crosses the baseline without ever
|
|
65
|
+
* reaching it, and the release stretches the window back to zero. A rectangle
|
|
66
|
+
* drawn at the height of the hand would promise a band between two intensities
|
|
67
|
+
* that letting go does not give.
|
|
68
|
+
*/
|
|
59
69
|
export interface ZoomSelection extends DragBox {
|
|
60
70
|
/**
|
|
61
71
|
* Whether letting go here would take the y axis with it, which is what the
|
|
@@ -88,15 +98,22 @@ export function zoomSelection(
|
|
|
88
98
|
if (drag === null) return null;
|
|
89
99
|
const {
|
|
90
100
|
baseline = DEFAULT_Y_AXIS_RULES.baseline,
|
|
101
|
+
keepBaseline = DEFAULT_Y_AXIS_RULES.keepBaseline,
|
|
91
102
|
pointsDown = DEFAULT_Y_AXIS_RULES.pointsDown,
|
|
92
103
|
drag: mode = 'xAxis',
|
|
93
104
|
} = rules;
|
|
105
|
+
const zoomsYAxis = takesYAxis(drag, mode, yScale, baseline, pointsDown);
|
|
106
|
+
// Stretched in pixels rather than in values because the rectangle is drawn in
|
|
107
|
+
// pixels and the mapping is monotonic, so including the baseline's own pixel
|
|
108
|
+
// in the span is including the baseline in the window.
|
|
109
|
+
const held = zoomsYAxis && holdsBaseline(mode, keepBaseline);
|
|
110
|
+
const atBaseline = chartPixel(yScale, baseline);
|
|
94
111
|
return {
|
|
95
112
|
fromX: drag.fromX,
|
|
96
|
-
fromY: drag.fromY,
|
|
113
|
+
fromY: held ? Math.min(drag.fromY, drag.toY, atBaseline) : drag.fromY,
|
|
97
114
|
toX: drag.toX,
|
|
98
|
-
toY: drag.toY,
|
|
99
|
-
zoomsYAxis
|
|
115
|
+
toY: held ? Math.max(drag.fromY, drag.toY, atBaseline) : drag.toY,
|
|
116
|
+
zoomsYAxis,
|
|
100
117
|
};
|
|
101
118
|
}
|
|
102
119
|
|
|
@@ -213,7 +230,7 @@ export function zoomedDomain(
|
|
|
213
230
|
// tool or by a `dual` drag that left its level, is a promise about exactly
|
|
214
231
|
// that rectangle, and a viewer whose axis rules keep its baseline would
|
|
215
232
|
// otherwise see a taller window than the one drawn.
|
|
216
|
-
const holdBaseline =
|
|
233
|
+
const holdBaseline = holdsBaseline(mode, keepBaseline);
|
|
217
234
|
const bottom = holdBaseline
|
|
218
235
|
? Math.min(baseline, fromY, toY)
|
|
219
236
|
: Math.min(fromY, toY);
|
|
@@ -272,6 +289,21 @@ export function scaledYAxis(
|
|
|
272
289
|
* @param pointsDown - Whether the data hangs below that baseline.
|
|
273
290
|
* @returns Whether the height comes along.
|
|
274
291
|
*/
|
|
292
|
+
/**
|
|
293
|
+
* Whether the window keeps the baseline whatever the drag asked for, which the
|
|
294
|
+
* preview and the release must agree on as surely as they agree on the height.
|
|
295
|
+
*
|
|
296
|
+
* Only the reading gesture holds it. A rectangle dragged out deliberately — by
|
|
297
|
+
* the box tool, or by a `dual` drag that left its level — is a promise about
|
|
298
|
+
* exactly that rectangle.
|
|
299
|
+
* @param mode - What a drag on this chart asks for.
|
|
300
|
+
* @param keepBaseline - Whether the viewer's rules keep it.
|
|
301
|
+
* @returns Whether the baseline stays in the window.
|
|
302
|
+
*/
|
|
303
|
+
function holdsBaseline(mode: DragMode, keepBaseline: boolean): boolean {
|
|
304
|
+
return keepBaseline && mode === 'xAxis';
|
|
305
|
+
}
|
|
306
|
+
|
|
275
307
|
function takesYAxis(
|
|
276
308
|
drag: DragBox,
|
|
277
309
|
mode: DragMode,
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
* gesture reporting a range and a click for the same drag.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
KeyboardEvent as ReactKeyboardEvent,
|
|
19
|
+
PointerEvent as ReactPointerEvent,
|
|
20
|
+
} from 'react';
|
|
18
21
|
|
|
22
|
+
import { isEditingField } from '../../panel/core/isEditingField.ts';
|
|
19
23
|
import type { ChartDomain, ZoomRules } from '../core/chartDomain.ts';
|
|
20
24
|
import type { PlotRect } from '../core/chartGeometry.ts';
|
|
21
25
|
import type { ChartScale } from '../core/chartScale.ts';
|
|
@@ -29,6 +33,7 @@ import { isInsidePlot } from './useChartPointer.ts';
|
|
|
29
33
|
|
|
30
34
|
/** One gesture on the SVG, as React hands it over. */
|
|
31
35
|
type PointerHandler = (event: ReactPointerEvent<SVGSVGElement>) => void;
|
|
36
|
+
type KeyboardHandler = (event: ReactKeyboardEvent<SVGSVGElement>) => void;
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Which keys were held as a click was let go.
|
|
@@ -56,6 +61,41 @@ export interface ChartZoomHandlers {
|
|
|
56
61
|
onPointerUp: PointerHandler;
|
|
57
62
|
onPointerCancel: PointerHandler;
|
|
58
63
|
onDoubleClick: () => void;
|
|
64
|
+
onKeyDown: KeyboardHandler;
|
|
65
|
+
/**
|
|
66
|
+
* So the chart can hold the caret, which is what lets a key reach it.
|
|
67
|
+
*
|
|
68
|
+
* In the tab order rather than merely focusable by a click, because the chart
|
|
69
|
+
* is* the widget here: a reader who navigates by keyboard has no other way to
|
|
70
|
+
* reach it, and a chart nobody can focus answers no key at all. A viewer that
|
|
71
|
+
* answers none takes itself back out of the order.
|
|
72
|
+
*/
|
|
73
|
+
tabIndex: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The key that fits the window to everything drawn, on every chart. */
|
|
77
|
+
export const FIT_KEY = 'f';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Whether a key press asks for the window to be fitted.
|
|
81
|
+
*
|
|
82
|
+
* A key pressed with a modifier belongs to the browser or to the page — Ctrl+F
|
|
83
|
+
* is a search, and a chart that swallowed it to fit itself would be a chart
|
|
84
|
+
* people stop typing near — and a key delivered to a field is part of what is
|
|
85
|
+
* being written. Compared case-insensitively, so a capital arriving from a held
|
|
86
|
+
* shift still fits.
|
|
87
|
+
* @param event - The key press, as React delivers it or as the DOM does.
|
|
88
|
+
* @returns Whether the chart should fit its window.
|
|
89
|
+
*/
|
|
90
|
+
export function asksToFit(
|
|
91
|
+
event: Pick<
|
|
92
|
+
KeyboardEvent,
|
|
93
|
+
'key' | 'ctrlKey' | 'metaKey' | 'altKey' | 'target'
|
|
94
|
+
>,
|
|
95
|
+
): boolean {
|
|
96
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return false;
|
|
97
|
+
if (isEditingField(event.target)) return false;
|
|
98
|
+
return event.key.toLowerCase() === FIT_KEY;
|
|
59
99
|
}
|
|
60
100
|
|
|
61
101
|
/** What a host asks to be told, beyond the window it is shown anyway. */
|
package/src/chart/ui/index.ts
CHANGED
|
@@ -47,7 +47,7 @@ export { SelectionRect } from './SelectionRect.tsx';
|
|
|
47
47
|
export type { LegendEntry, TraceLegendProps } from './TraceLegend.tsx';
|
|
48
48
|
export { TraceLegend } from './TraceLegend.tsx';
|
|
49
49
|
export type { ChartClickModifiers } from './chartGestures.ts';
|
|
50
|
-
export { NO_MODIFIERS } from './chartGestures.ts';
|
|
50
|
+
export { FIT_KEY, NO_MODIFIERS, asksToFit } from './chartGestures.ts';
|
|
51
51
|
export { chartSurfaceStyle } from './chartSurface.ts';
|
|
52
52
|
export type { ChartPointer, ChartPointerOptions } from './useChartPointer.ts';
|
|
53
53
|
export {
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
* answer each viewer hands over.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import type {
|
|
31
|
+
import type {
|
|
32
|
+
KeyboardEvent as ReactKeyboardEvent,
|
|
33
|
+
PointerEvent as ReactPointerEvent,
|
|
34
|
+
RefObject,
|
|
35
|
+
} from 'react';
|
|
32
36
|
import { useCallback, useMemo, useState } from 'react';
|
|
33
37
|
|
|
34
38
|
import type {
|
|
@@ -50,6 +54,7 @@ import type {
|
|
|
50
54
|
} from './chartGestures.ts';
|
|
51
55
|
import {
|
|
52
56
|
answeredRelease,
|
|
57
|
+
asksToFit,
|
|
53
58
|
draggedTo,
|
|
54
59
|
releaseDrag,
|
|
55
60
|
startedDrag,
|
|
@@ -147,6 +152,7 @@ export function useChartZoom(options: ChartZoomOptions): ChartZoom {
|
|
|
147
152
|
const pointsDown = yAxis?.pointsDown;
|
|
148
153
|
const dragMode = gestures?.drag ?? DEFAULT_ZOOM_GESTURES.drag;
|
|
149
154
|
const wheel = gestures?.wheel ?? DEFAULT_ZOOM_GESTURES.wheel;
|
|
155
|
+
const keys = gestures?.keys ?? DEFAULT_ZOOM_GESTURES.keys;
|
|
150
156
|
const rules = useMemo<ZoomRules>(
|
|
151
157
|
() => ({ baseline, keepBaseline, pointsDown, drag: dragMode }),
|
|
152
158
|
[baseline, keepBaseline, pointsDown, dragMode],
|
|
@@ -236,6 +242,19 @@ export function useChartZoom(options: ChartZoomOptions): ChartZoom {
|
|
|
236
242
|
[drag, yScale, rules],
|
|
237
243
|
);
|
|
238
244
|
|
|
245
|
+
const onKeyDown = useCallback(
|
|
246
|
+
(event: ReactKeyboardEvent<SVGSVGElement>) => {
|
|
247
|
+
if (!keys || !asksToFit(event)) return;
|
|
248
|
+
// Taken rather than passed on, so the page under a chart does not scroll
|
|
249
|
+
// to its own next heading as well — and so a second chart mounted beside
|
|
250
|
+
// this one is not fitted by a key aimed at this one.
|
|
251
|
+
event.preventDefault();
|
|
252
|
+
event.stopPropagation();
|
|
253
|
+
reset();
|
|
254
|
+
},
|
|
255
|
+
[keys, reset],
|
|
256
|
+
);
|
|
257
|
+
|
|
239
258
|
return {
|
|
240
259
|
domain,
|
|
241
260
|
xScale,
|
|
@@ -248,6 +267,11 @@ export function useChartZoom(options: ChartZoomOptions): ChartZoom {
|
|
|
248
267
|
onPointerUp,
|
|
249
268
|
onPointerCancel,
|
|
250
269
|
onDoubleClick: reset,
|
|
270
|
+
onKeyDown,
|
|
271
|
+
// Out of the tab order again on a chart that answers no key: a stop on the
|
|
272
|
+
// way through a page that does nothing when it is reached is worse than no
|
|
273
|
+
// stop at all.
|
|
274
|
+
tabIndex: keys ? 0 : -1,
|
|
251
275
|
},
|
|
252
276
|
};
|
|
253
277
|
}
|
package/src/core.ts
CHANGED
|
@@ -35,5 +35,6 @@ export * from './state/core/index.ts';
|
|
|
35
35
|
// Framework-free molfile and editor-value helpers, so a worker can read a
|
|
36
36
|
// molfile without loading the editor that `react-cheminfo/structure` exports.
|
|
37
37
|
export * from './structure/core/index.ts';
|
|
38
|
+
export * from './taxonomy/core/index.ts';
|
|
38
39
|
export * from './tokens/core/index.ts';
|
|
39
40
|
export * from './worker/core/index.ts';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a computed style says is painted, read the way a figure file needs it.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A colour, or nothing where the page paints nothing with it.
|
|
7
|
+
* @param color - A computed colour, as `getComputedStyle` writes it.
|
|
8
|
+
* @returns The colour, or undefined where it is transparent.
|
|
9
|
+
*/
|
|
10
|
+
export function visibleColor(color: string): string | undefined {
|
|
11
|
+
const value = color.trim();
|
|
12
|
+
if (value === '' || value === 'transparent') return undefined;
|
|
13
|
+
if (ZERO_ALPHA_COMMAS.test(value) || ZERO_ALPHA_SLASH.test(value)) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The dashes a CSS line style is drawn with.
|
|
21
|
+
* @param style - The computed `border-*-style` or `outline-style`.
|
|
22
|
+
* @param width - The line's width, in pixels.
|
|
23
|
+
* @returns The dashes and gaps in pixels — empty for a solid line — or
|
|
24
|
+
* undefined where no line is drawn. `auto` is a focus ring, which is the
|
|
25
|
+
* state of the pointer rather than a part of the figure.
|
|
26
|
+
*/
|
|
27
|
+
export function lineDash(
|
|
28
|
+
style: string,
|
|
29
|
+
width: number,
|
|
30
|
+
): readonly number[] | undefined {
|
|
31
|
+
if (!(width > 0)) return undefined;
|
|
32
|
+
switch (style) {
|
|
33
|
+
case 'none':
|
|
34
|
+
case 'hidden':
|
|
35
|
+
case 'auto':
|
|
36
|
+
case '':
|
|
37
|
+
return undefined;
|
|
38
|
+
case 'dashed':
|
|
39
|
+
return [width * 3, width * 3];
|
|
40
|
+
case 'dotted':
|
|
41
|
+
return [width, width];
|
|
42
|
+
default:
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A corner radius in pixels.
|
|
49
|
+
* @param radius - The computed `border-*-radius`, in pixels or as a share.
|
|
50
|
+
* @param width - The box's width, which a share is taken of.
|
|
51
|
+
* @param height - Its height.
|
|
52
|
+
* @returns The radius, in pixels.
|
|
53
|
+
*/
|
|
54
|
+
export function cornerRadius(
|
|
55
|
+
radius: string,
|
|
56
|
+
width: number,
|
|
57
|
+
height: number,
|
|
58
|
+
): number {
|
|
59
|
+
const value = Number.parseFloat(radius);
|
|
60
|
+
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
61
|
+
if (radius.trim().endsWith('%')) {
|
|
62
|
+
return (Math.min(width, height) * value) / 100;
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `rgba(r, g, b, 0)`, which is what a transparent ground computes to. */
|
|
68
|
+
const ZERO_ALPHA_COMMAS = /^rgba\((?:[^,]+,){3}\s*0(?:\.0*)?\)$/;
|
|
69
|
+
|
|
70
|
+
/** `rgb(r g b / 0)` and `color(srgb r g b / 0)`. */
|
|
71
|
+
const ZERO_ALPHA_SLASH = /\/\s*0(?:\.0*)?%?\)$/;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cssTokenNames, resolveCssTokens } from './cssTokens.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One drawing, serialized with every token it names written out.
|
|
5
|
+
*
|
|
6
|
+
* The copy is taken first and read from the original, because the values are a
|
|
7
|
+
* property of where the figure sits on the page: a detached clone inherits
|
|
8
|
+
* nothing and would resolve every token to an empty string.
|
|
9
|
+
* @param drawing - The drawing, as it sits on the page.
|
|
10
|
+
* @returns Its markup.
|
|
11
|
+
*/
|
|
12
|
+
export function drawingMarkup(drawing: SVGSVGElement): string {
|
|
13
|
+
const clone = drawing.cloneNode(true) as SVGSVGElement;
|
|
14
|
+
const markup = new XMLSerializer().serializeToString(clone);
|
|
15
|
+
return resolveCssTokens(markup, tokenValues(drawing, cssTokenNames(markup)));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What each token the markup names is worth where the figure is drawn.
|
|
20
|
+
* @param drawing - The drawing, as it sits on the page.
|
|
21
|
+
* @param names - The tokens it asks for.
|
|
22
|
+
* @returns The values, keyed by token name.
|
|
23
|
+
*/
|
|
24
|
+
function tokenValues(
|
|
25
|
+
drawing: SVGSVGElement,
|
|
26
|
+
names: readonly string[],
|
|
27
|
+
): Record<string, string> {
|
|
28
|
+
const values: Record<string, string> = {};
|
|
29
|
+
if (names.length === 0) return values;
|
|
30
|
+
const styles = window.getComputedStyle(drawing);
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
values[name] = styles.getPropertyValue(name);
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A part of a figure the page draws in HTML, painted from where the browser put
|
|
3
|
+
* it.
|
|
4
|
+
*
|
|
5
|
+
* Some figures are not drawings: a periodic table is a grid of buttons, because
|
|
6
|
+
* every cell has to be reachable by Tab and read by a screen reader. Saved as
|
|
7
|
+
* SVG it still has to look like the table the reader saw, so each box, each
|
|
8
|
+
* line round it and each line of words is measured off the page and painted
|
|
9
|
+
* again — never re-derived from the styles that made it, so the copy cannot
|
|
10
|
+
* drift from the screen.
|
|
11
|
+
*
|
|
12
|
+
* What is painted: a box's ground, its border (as drawn on its top edge), its
|
|
13
|
+
* corner, its outline, its opacity, every line of its words in their colour,
|
|
14
|
+
* size, weight and style, and any `<svg>` inside it. What is not: images,
|
|
15
|
+
* gradients, shadows, filters and generated `::before` / `::after` content.
|
|
16
|
+
* Chrome inside the part — `data-figure="chrome"` — is left behind.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { cornerRadius, lineDash, visibleColor } from './cssPaint.ts';
|
|
20
|
+
import { drawingMarkup } from './drawingMarkup.ts';
|
|
21
|
+
import type { HtmlPaint } from './figureHtmlMarkup.ts';
|
|
22
|
+
import { figureHtmlMarkup } from './figureHtmlMarkup.ts';
|
|
23
|
+
import { lineBoxes } from './textLines.ts';
|
|
24
|
+
|
|
25
|
+
/** Chrome inside the part, which is not part of the picture. */
|
|
26
|
+
const CHROME = '[data-figure="chrome"]';
|
|
27
|
+
|
|
28
|
+
/** What every measurement of one part is taken against. */
|
|
29
|
+
interface PaintContext {
|
|
30
|
+
/** The part's own box: every position is written from its corner. */
|
|
31
|
+
origin: DOMRect;
|
|
32
|
+
/** The type the document is set in, which a run need not repeat. */
|
|
33
|
+
documentFont: string;
|
|
34
|
+
/** One range, moved over each character in turn. */
|
|
35
|
+
range: Range;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One part of a figure drawn in HTML, as SVG markup.
|
|
40
|
+
* @param part - The element the part is drawn in.
|
|
41
|
+
* @param documentFont - The font family the saved document is set in.
|
|
42
|
+
* @returns The markup, in the part's own coordinates: the caller places it.
|
|
43
|
+
*/
|
|
44
|
+
export function figureHtml(part: Element, documentFont: string): string {
|
|
45
|
+
const context: PaintContext = {
|
|
46
|
+
origin: part.getBoundingClientRect(),
|
|
47
|
+
documentFont,
|
|
48
|
+
range: document.createRange(),
|
|
49
|
+
};
|
|
50
|
+
const paints: HtmlPaint[] = [];
|
|
51
|
+
paintElement(part, context, paints);
|
|
52
|
+
return figureHtmlMarkup(paints);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function paintElement(
|
|
56
|
+
element: Element,
|
|
57
|
+
context: PaintContext,
|
|
58
|
+
into: HtmlPaint[],
|
|
59
|
+
): void {
|
|
60
|
+
if (element.matches(CHROME)) return;
|
|
61
|
+
const styles = window.getComputedStyle(element);
|
|
62
|
+
if (styles.display === 'none') return;
|
|
63
|
+
const visible = styles.visibility === 'visible';
|
|
64
|
+
const box = element.getBoundingClientRect();
|
|
65
|
+
|
|
66
|
+
if (element instanceof SVGSVGElement) {
|
|
67
|
+
if (!visible || box.width <= 0 || box.height <= 0) return;
|
|
68
|
+
into.push({
|
|
69
|
+
kind: 'drawing',
|
|
70
|
+
markup: drawingMarkup(element),
|
|
71
|
+
x: box.left - context.origin.left,
|
|
72
|
+
y: box.top - context.origin.top,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const own: HtmlPaint[] = [];
|
|
78
|
+
if (visible) paintGround(box, styles, context, own);
|
|
79
|
+
for (const child of element.childNodes) {
|
|
80
|
+
if (child instanceof Text) {
|
|
81
|
+
if (visible) paintText(child, styles, context, own);
|
|
82
|
+
} else if (child instanceof Element) {
|
|
83
|
+
paintElement(child, context, own);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (visible) paintOutline(box, styles, context, own);
|
|
87
|
+
if (own.length === 0) return;
|
|
88
|
+
|
|
89
|
+
const opacity = Number.parseFloat(styles.opacity);
|
|
90
|
+
if (Number.isFinite(opacity) && opacity < 1) {
|
|
91
|
+
into.push({ kind: 'group', opacity, children: own });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const paint of own) into.push(paint);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function paintGround(
|
|
98
|
+
box: DOMRect,
|
|
99
|
+
styles: CSSStyleDeclaration,
|
|
100
|
+
context: PaintContext,
|
|
101
|
+
into: HtmlPaint[],
|
|
102
|
+
): void {
|
|
103
|
+
if (box.width <= 0 || box.height <= 0) return;
|
|
104
|
+
const x = box.left - context.origin.left;
|
|
105
|
+
const y = box.top - context.origin.top;
|
|
106
|
+
const radius = cornerRadius(
|
|
107
|
+
styles.borderTopLeftRadius,
|
|
108
|
+
box.width,
|
|
109
|
+
box.height,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const fill = visibleColor(styles.backgroundColor);
|
|
113
|
+
if (fill !== undefined) {
|
|
114
|
+
into.push({
|
|
115
|
+
kind: 'box',
|
|
116
|
+
x,
|
|
117
|
+
y,
|
|
118
|
+
width: box.width,
|
|
119
|
+
height: box.height,
|
|
120
|
+
radius,
|
|
121
|
+
fill,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A border lies inside the box, where a stroke straddles its line: the
|
|
126
|
+
// stroke is drawn half a width in, over the ground the page drew under it.
|
|
127
|
+
const width = Number.parseFloat(styles.borderTopWidth);
|
|
128
|
+
const dash = lineDash(styles.borderTopStyle, width);
|
|
129
|
+
const stroke = visibleColor(styles.borderTopColor);
|
|
130
|
+
if (dash === undefined || stroke === undefined) return;
|
|
131
|
+
const half = width / 2;
|
|
132
|
+
into.push({
|
|
133
|
+
kind: 'box',
|
|
134
|
+
x: x + half,
|
|
135
|
+
y: y + half,
|
|
136
|
+
width: box.width - width,
|
|
137
|
+
height: box.height - width,
|
|
138
|
+
radius: Math.max(0, radius - half),
|
|
139
|
+
stroke,
|
|
140
|
+
strokeWidth: width,
|
|
141
|
+
dash,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function paintOutline(
|
|
146
|
+
box: DOMRect,
|
|
147
|
+
styles: CSSStyleDeclaration,
|
|
148
|
+
context: PaintContext,
|
|
149
|
+
into: HtmlPaint[],
|
|
150
|
+
): void {
|
|
151
|
+
const width = Number.parseFloat(styles.outlineWidth);
|
|
152
|
+
const dash = lineDash(styles.outlineStyle, width);
|
|
153
|
+
const stroke = visibleColor(styles.outlineColor);
|
|
154
|
+
if (dash === undefined || stroke === undefined) return;
|
|
155
|
+
const offset = Number.parseFloat(styles.outlineOffset) || 0;
|
|
156
|
+
const grow = offset + width / 2;
|
|
157
|
+
const radius = cornerRadius(
|
|
158
|
+
styles.borderTopLeftRadius,
|
|
159
|
+
box.width,
|
|
160
|
+
box.height,
|
|
161
|
+
);
|
|
162
|
+
into.push({
|
|
163
|
+
kind: 'box',
|
|
164
|
+
x: box.left - context.origin.left - grow,
|
|
165
|
+
y: box.top - context.origin.top - grow,
|
|
166
|
+
width: box.width + grow * 2,
|
|
167
|
+
height: box.height + grow * 2,
|
|
168
|
+
radius: radius > 0 ? Math.max(0, radius + grow) : 0,
|
|
169
|
+
stroke,
|
|
170
|
+
strokeWidth: width,
|
|
171
|
+
dash,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function paintText(
|
|
176
|
+
node: Text,
|
|
177
|
+
styles: CSSStyleDeclaration,
|
|
178
|
+
context: PaintContext,
|
|
179
|
+
into: HtmlPaint[],
|
|
180
|
+
): void {
|
|
181
|
+
const color = visibleColor(styles.color);
|
|
182
|
+
if (color === undefined || node.data.trim() === '') return;
|
|
183
|
+
const fontSize = Number.parseFloat(styles.fontSize);
|
|
184
|
+
const fontStyle =
|
|
185
|
+
styles.fontStyle === 'normal' ? undefined : styles.fontStyle;
|
|
186
|
+
const fontFamily =
|
|
187
|
+
styles.fontFamily === context.documentFont ? undefined : styles.fontFamily;
|
|
188
|
+
|
|
189
|
+
for (const line of lineBoxes(node, context.range)) {
|
|
190
|
+
const words = transformed(
|
|
191
|
+
node.data.slice(line.start, line.end).replaceAll(/\s+/g, ' ').trim(),
|
|
192
|
+
styles.textTransform,
|
|
193
|
+
);
|
|
194
|
+
if (words === '') continue;
|
|
195
|
+
into.push({
|
|
196
|
+
kind: 'text',
|
|
197
|
+
text: words,
|
|
198
|
+
x: line.left - context.origin.left,
|
|
199
|
+
y: (line.top + line.bottom) / 2 - context.origin.top,
|
|
200
|
+
width: line.right - line.left,
|
|
201
|
+
color,
|
|
202
|
+
fontSize,
|
|
203
|
+
fontWeight: styles.fontWeight,
|
|
204
|
+
fontStyle,
|
|
205
|
+
fontFamily,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function transformed(words: string, transform: string): string {
|
|
211
|
+
if (transform === 'uppercase') return words.toUpperCase();
|
|
212
|
+
if (transform === 'lowercase') return words.toLowerCase();
|
|
213
|
+
return words;
|
|
214
|
+
}
|