react-cheminfo 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chart/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/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 +1 -0
- package/lib/locales/en.json +1 -0
- package/lib/locales/es.json +1 -0
- package/lib/locales/fr.json +1 -0
- package/lib/locales/it.json +1 -0
- 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 +12 -3
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +5 -3
- 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 +18 -6
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/split/ui/SplitColumn.d.ts +30 -0
- package/lib/split/ui/SplitColumn.d.ts.map +1 -0
- package/lib/split/ui/SplitColumn.js +20 -0
- package/lib/split/ui/SplitColumn.js.map +1 -0
- package/lib/split/ui/SplitRow.d.ts +15 -53
- package/lib/split/ui/SplitRow.d.ts.map +1 -1
- package/lib/split/ui/SplitRow.js +9 -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 +73 -0
- package/lib/split/ui/splitPanes.d.ts.map +1 -0
- package/lib/split/ui/splitPanes.js +128 -0
- package/lib/split/ui/splitPanes.js.map +1 -0
- 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/ir/core/irMode.ts +6 -1
- package/src/locales/de.json +1 -0
- package/src/locales/en.json +1 -0
- package/src/locales/es.json +1 -0
- package/src/locales/fr.json +1 -0
- package/src/locales/it.json +1 -0
- 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 +23 -5
- package/src/periodic/ui/PeriodicTableChrome.tsx +28 -4
- package/src/split/ui/SplitColumn.tsx +36 -0
- package/src/split/ui/SplitRow.tsx +17 -208
- package/src/split/ui/index.ts +3 -0
- package/src/split/ui/splitPanes.tsx +267 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SplitRow.js","sourceRoot":"","sources":["../../../src/split/ui/SplitRow.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"SplitRow.js","sourceRoot":"","sources":["../../../src/split/ui/SplitRow.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAkB,CAAC;AAQ9C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,OAAO,KAAC,UAAU,IAAC,SAAS,EAAC,YAAY,KAAK,KAAK,GAAI,CAAC;AAC1D,CAAC"}
|
package/lib/split/ui/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export type { SplitColumnProps } from './SplitColumn.tsx';
|
|
2
|
+
export { SplitColumn } from './SplitColumn.tsx';
|
|
1
3
|
export type { SplitRowProps } from './SplitRow.tsx';
|
|
2
4
|
export { SplitRow } from './SplitRow.tsx';
|
|
5
|
+
export type { SplitPanesProps } from './splitPanes.tsx';
|
|
3
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/split/ui/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/split/ui/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
package/lib/split/ui/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/split/ui/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/split/ui/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAgB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two figures with a splitter between them, and the share each takes written
|
|
3
|
+
* into the address. The body of `<SplitRow>` and `<SplitColumn>` both.
|
|
4
|
+
*
|
|
5
|
+
* How much room one half deserves beside the other is not ours to decide:
|
|
6
|
+
* somebody embedding the chart wants it nearly whole, somebody teaching off the
|
|
7
|
+
* picker wants the picker nearly whole, and both want the link they hand out to
|
|
8
|
+
* open the way they left it. So the splitter is dragged, the share it comes to
|
|
9
|
+
* rest at goes into `?split=`, and the address is the figure.
|
|
10
|
+
*
|
|
11
|
+
* Two things it cannot do, and must not pretend to: on a screen with no room
|
|
12
|
+
* for two the panes stack and no splitter is drawn; and a link that hid
|
|
13
|
+
* everything one pane holds leaves the other the whole of it rather than a
|
|
14
|
+
* share.
|
|
15
|
+
*
|
|
16
|
+
* The bar itself is react-science's `SplitPane`, and it keeps react-science's
|
|
17
|
+
* look — barely visible on purpose, found by the pointer that changes over it
|
|
18
|
+
* rather than by the eye. All `chrome.css` says about it is that eleven pixels
|
|
19
|
+
* is more gutter than two figures need.
|
|
20
|
+
*/
|
|
21
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
22
|
+
import type { SplitRange } from '../core/split.ts';
|
|
23
|
+
/** What both splitters take. */
|
|
24
|
+
export interface SplitPanesProps extends SplitRange {
|
|
25
|
+
/**
|
|
26
|
+
* Share of the box the first pane takes, as a whole percentage, or `null`
|
|
27
|
+
* while the page sits at {@link SplitPanesProps.defaultRatio}.
|
|
28
|
+
*/
|
|
29
|
+
ratio: number | null;
|
|
30
|
+
/** The share it opens at when the address names none. */
|
|
31
|
+
defaultRatio: number;
|
|
32
|
+
/**
|
|
33
|
+
* Take the share a drag came to rest at, or `null` when the splitter was
|
|
34
|
+
* double-clicked back to the default.
|
|
35
|
+
*/
|
|
36
|
+
onRatio: (ratio: number | null) => void;
|
|
37
|
+
/** The first pane — left, or top — or `null` when a link left nothing in it. */
|
|
38
|
+
start: ReactNode;
|
|
39
|
+
/** The second pane, or `null` when a link left nothing in it. */
|
|
40
|
+
end: ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* The smallest box that is still split, in pixels — a width for a row, a
|
|
43
|
+
* height for a column. Under it the two panes stack and no splitter is drawn.
|
|
44
|
+
* @default 860 across, 420 down
|
|
45
|
+
*/
|
|
46
|
+
stackBelow?: number;
|
|
47
|
+
/**
|
|
48
|
+
* The gutter each pane keeps beside the splitter, in pixels, so neither
|
|
49
|
+
* figure is read against its edge.
|
|
50
|
+
* @default 6
|
|
51
|
+
*/
|
|
52
|
+
gutter?: number;
|
|
53
|
+
/**
|
|
54
|
+
* The gap between the two panes once they have stacked, in pixels.
|
|
55
|
+
* @default 16
|
|
56
|
+
*/
|
|
57
|
+
stackedGap?: number;
|
|
58
|
+
}
|
|
59
|
+
/** What {@link SplitPanes} takes on top of the public props. */
|
|
60
|
+
interface InternalProps extends SplitPanesProps {
|
|
61
|
+
/** Across the box, or down it. */
|
|
62
|
+
direction: 'horizontal' | 'vertical';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The two panes, divided where the address says.
|
|
66
|
+
*
|
|
67
|
+
* @param props - See {@link SplitPanesProps}.
|
|
68
|
+
* @returns The panes with the splitter between them, the two stacked where
|
|
69
|
+
* there is no room for both, or whichever one a link left, alone.
|
|
70
|
+
*/
|
|
71
|
+
export declare function SplitPanes(props: InternalProps): ReactElement | null;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=splitPanes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitPanes.d.ts","sourceRoot":"","sources":["../../../src/split/ui/splitPanes.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,gCAAgC;AAChC,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,gFAAgF;IAChF,KAAK,EAAE,SAAS,CAAC;IACjB,iEAAiE;IACjE,GAAG,EAAE,SAAS,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gEAAgE;AAChE,UAAU,aAAc,SAAQ,eAAe;IAC7C,kCAAkC;IAClC,SAAS,EAAE,YAAY,GAAG,UAAU,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,GAAG,IAAI,CA6FpE"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { SplitPane } from 'react-science/ui';
|
|
4
|
+
import { useResizeObserver } from "../../hooks/ui/useResizeObserver.js";
|
|
5
|
+
import { clampSplit } from "../core/split.js";
|
|
6
|
+
/**
|
|
7
|
+
* The two panes, divided where the address says.
|
|
8
|
+
*
|
|
9
|
+
* @param props - See {@link SplitPanesProps}.
|
|
10
|
+
* @returns The panes with the splitter between them, the two stacked where
|
|
11
|
+
* there is no room for both, or whichever one a link left, alone.
|
|
12
|
+
*/
|
|
13
|
+
export function SplitPanes(props) {
|
|
14
|
+
const { direction, ratio, defaultRatio, onRatio, start, end, min, max, stackBelow = direction === 'horizontal' ? STACK_ACROSS : STACK_DOWN, gutter = GUTTER, stackedGap = STACKED_GAP, } = props;
|
|
15
|
+
const across = direction === 'horizontal';
|
|
16
|
+
const box = useRef(null);
|
|
17
|
+
const [size, setSize] = useState(() => viewportSize(across, stackBelow));
|
|
18
|
+
// The share under the pointer, which is not the one in the address: a drag
|
|
19
|
+
// reports every pixel it crosses, and the address is rewritten once, where
|
|
20
|
+
// the splitter is let go.
|
|
21
|
+
const [dragged, setDragged] = useState(null);
|
|
22
|
+
useResizeObserver(box, (measured) => {
|
|
23
|
+
setSize(across ? measured.width : measured.height);
|
|
24
|
+
});
|
|
25
|
+
if (start === null && end === null)
|
|
26
|
+
return null;
|
|
27
|
+
// A column divided by a share needs a box of a definite height to take a
|
|
28
|
+
// share of; in an ordinary page-height one it would measure zero and both
|
|
29
|
+
// panes would collapse, so it stacks instead and says nothing about it.
|
|
30
|
+
const stacked = start === null || end === null || size < stackBelow;
|
|
31
|
+
const startPane = start === null ? null : (_jsx("div", { "data-testid": "split-start", style: stacked ? STACKED_PANE : paneStyle(across, 'start', gutter), children: start }));
|
|
32
|
+
const endPane = end === null ? null : (_jsx("div", { "data-testid": "split-end", style: stacked ? STACKED_PANE : paneStyle(across, 'end', gutter), children: end }));
|
|
33
|
+
if (stacked) {
|
|
34
|
+
return (_jsxs("div", { ref: box, style: { ...STACK, gap: stackedGap }, children: [startPane, endPane] }));
|
|
35
|
+
}
|
|
36
|
+
const share = clampSplit(dragged ?? ratio ?? defaultRatio, { min, max });
|
|
37
|
+
return (_jsx("div", { ref: box, className: "split-row", style: across ? undefined : FILL, children: _jsxs(SplitPane, { direction: direction, controlledSide: "start", size: `${share}%`,
|
|
38
|
+
// Held open, so the only layout a visitor can reach is one the address
|
|
39
|
+
// describes: left to itself a double click collapses a pane, and a link
|
|
40
|
+
// shared from there would open with the pane back. The double click is
|
|
41
|
+
// answered here instead, by returning to the page's own share.
|
|
42
|
+
open: true, onOpenChange: () => {
|
|
43
|
+
setDragged(null);
|
|
44
|
+
onRatio(null);
|
|
45
|
+
}, onSizeChange: (asked) => {
|
|
46
|
+
setDragged(shareOf(asked, share, { min, max }));
|
|
47
|
+
}, onResize: (asked) => {
|
|
48
|
+
const rested = shareOf(asked, share, { min, max });
|
|
49
|
+
setDragged(null);
|
|
50
|
+
onRatio(rested);
|
|
51
|
+
}, children: [startPane, endPane] }) }));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The share a splitter asked for.
|
|
55
|
+
*
|
|
56
|
+
* @param asked - What it reported, a percentage because that is what it was
|
|
57
|
+
* given.
|
|
58
|
+
* @param current - The share in force, kept when the report is not a number.
|
|
59
|
+
* @param range - The range this box is divided in.
|
|
60
|
+
* @returns A whole percentage inside it.
|
|
61
|
+
*/
|
|
62
|
+
function shareOf(asked, current, range) {
|
|
63
|
+
const value = Number.parseFloat(asked);
|
|
64
|
+
return Number.isFinite(value) ? clampSplit(value, range) : current;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* How big the window is, which is what the box is taken to be until it has been
|
|
68
|
+
* measured. Reading it rather than starting at zero is what keeps a wide page
|
|
69
|
+
* from laying itself out stacked and splitting a frame later, which would build
|
|
70
|
+
* both figures twice on every load.
|
|
71
|
+
*
|
|
72
|
+
* @param across - Whether the box is divided across or down.
|
|
73
|
+
* @param fallback - What to answer where there is no window to read — a test,
|
|
74
|
+
* or a page rendered to a string.
|
|
75
|
+
* @returns The size in pixels.
|
|
76
|
+
*/
|
|
77
|
+
function viewportSize(across, fallback) {
|
|
78
|
+
const measure = across ? globalThis.innerWidth : globalThis.innerHeight;
|
|
79
|
+
return typeof measure === 'number' && measure > 0 ? measure : fallback;
|
|
80
|
+
}
|
|
81
|
+
/** The narrowest box still divided across, in pixels. */
|
|
82
|
+
const STACK_ACROSS = 860;
|
|
83
|
+
/**
|
|
84
|
+
* The shortest box still divided down, in pixels. Two figures sharing less than
|
|
85
|
+
* this have about two hundred pixels each, which is under what any of them
|
|
86
|
+
* reads at.
|
|
87
|
+
*/
|
|
88
|
+
const STACK_DOWN = 420;
|
|
89
|
+
/** The gutter a pane keeps beside the splitter, in pixels. */
|
|
90
|
+
const GUTTER = 6;
|
|
91
|
+
/** The gap between the panes once they have stacked, in pixels. */
|
|
92
|
+
const STACKED_GAP = 16;
|
|
93
|
+
const STACK = {
|
|
94
|
+
display: 'flex',
|
|
95
|
+
flexDirection: 'column',
|
|
96
|
+
minHeight: 0,
|
|
97
|
+
};
|
|
98
|
+
const STACKED_PANE = {
|
|
99
|
+
display: 'flex',
|
|
100
|
+
flexDirection: 'column',
|
|
101
|
+
minWidth: 0,
|
|
102
|
+
minHeight: 0,
|
|
103
|
+
};
|
|
104
|
+
/** A column takes the height it is given, so its share has something to divide. */
|
|
105
|
+
const FILL = {
|
|
106
|
+
display: 'flex',
|
|
107
|
+
flex: '1 1 0%',
|
|
108
|
+
flexDirection: 'column',
|
|
109
|
+
minHeight: 0,
|
|
110
|
+
};
|
|
111
|
+
function paneStyle(across, side, gutter) {
|
|
112
|
+
const edge = across
|
|
113
|
+
? side === 'start'
|
|
114
|
+
? 'paddingRight'
|
|
115
|
+
: 'paddingLeft'
|
|
116
|
+
: side === 'start'
|
|
117
|
+
? 'paddingBottom'
|
|
118
|
+
: 'paddingTop';
|
|
119
|
+
return {
|
|
120
|
+
display: 'flex',
|
|
121
|
+
flex: '1 1 0%',
|
|
122
|
+
flexDirection: 'column',
|
|
123
|
+
minWidth: 0,
|
|
124
|
+
minHeight: 0,
|
|
125
|
+
[edge]: gutter,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=splitPanes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splitPanes.js","sourceRoot":"","sources":["../../../src/split/ui/splitPanes.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-cheminfo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React components, hooks and framework-free helpers shared by the cheminfo sites: site chrome, citations, sharing, routing, pedagogy, charts, and the structure, orbital and 3D molecule viewers",
|
|
6
6
|
"author": "Luc Patiny",
|
|
@@ -163,12 +163,27 @@ export interface ZoomGestures {
|
|
|
163
163
|
* @default true
|
|
164
164
|
*/
|
|
165
165
|
wheel?: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Whether the chart answers `f` with a window fitted to everything drawn.
|
|
168
|
+
*
|
|
169
|
+
* The same thing the double click does, reachable without a pointer — and the
|
|
170
|
+
* one gesture a reader cannot discover by trying, which is why the key is the
|
|
171
|
+
* same `f` on every chart of the family rather than each one choosing.
|
|
172
|
+
*
|
|
173
|
+
* Turned off by a viewer that already owns its keys: the infrared shell runs
|
|
174
|
+
* `f` through its own command table, where the key is listed in a toolbar
|
|
175
|
+
* tooltip and in the documentation panel, and fits every pane of a stack
|
|
176
|
+
* rather than the one chart the pointer is on.
|
|
177
|
+
* @default true
|
|
178
|
+
*/
|
|
179
|
+
keys?: boolean;
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
/** What the gestures come to when a viewer states none of them. */
|
|
169
183
|
export const DEFAULT_ZOOM_GESTURES: Required<ZoomGestures> = {
|
|
170
184
|
drag: 'xAxis',
|
|
171
185
|
wheel: true,
|
|
186
|
+
keys: true,
|
|
172
187
|
};
|
|
173
188
|
|
|
174
189
|
/**
|
|
@@ -31,8 +31,10 @@ export interface SelectionRectangle {
|
|
|
31
31
|
/**
|
|
32
32
|
* The rectangle to draw for a drag being made.
|
|
33
33
|
*
|
|
34
|
-
* A drag that takes the y axis with it is drawn at the height
|
|
35
|
-
*
|
|
34
|
+
* A drag that takes the y axis with it is drawn at the height `zoomSelection`
|
|
35
|
+
* worked out — the window the release will take, which is the drag's own height
|
|
36
|
+
* stretched to any baseline the viewer holds — and one that asks for the x axis
|
|
37
|
+
* alone spans the whole plot.
|
|
36
38
|
* That is the gesture announcing itself before the button is let go: the
|
|
37
39
|
* rectangle changing shape — as the pointer crosses the baseline, or as a
|
|
38
40
|
* `dual` drag leaves the level it started on — is the only warning a user gets
|
|
@@ -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/ir/core/irMode.ts
CHANGED
|
@@ -168,7 +168,12 @@ export function zoomGestures(
|
|
|
168
168
|
tool: ZoomDragMode = 'xAxis',
|
|
169
169
|
): Required<ZoomGestures> {
|
|
170
170
|
const { boxZoomOnly, wheelZoom } = IR_MODE_RULES[mode];
|
|
171
|
-
|
|
171
|
+
// The shell owns `f`: its own command table runs `resetDomain`, which fits
|
|
172
|
+
// every pane of a stack rather than the one chart the caret is in, and the key
|
|
173
|
+
// is written down in a toolbar tooltip and in the documentation panel. A chart
|
|
174
|
+
// answering it as well would fit one pane and swallow the key before the
|
|
175
|
+
// shell heard it.
|
|
176
|
+
return { drag: boxZoomOnly ? 'box' : tool, wheel: wheelZoom, keys: false };
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
/**
|
package/src/locales/de.json
CHANGED
|
@@ -266,6 +266,7 @@
|
|
|
266
266
|
"periodic.table": "Periodensystem",
|
|
267
267
|
"periodic.group": "Gruppe {group}",
|
|
268
268
|
"periodic.period": "Periode {period}",
|
|
269
|
+
"periodic.allElements": "Alle Elemente",
|
|
269
270
|
"periodic.cellLabel": "{name} ({symbol}, Z = {atomicNumber})",
|
|
270
271
|
"periodic.category.alkali-metal": "Alkalimetall",
|
|
271
272
|
"periodic.category.alkaline-earth-metal": "Erdalkalimetall",
|
package/src/locales/en.json
CHANGED
|
@@ -412,6 +412,7 @@
|
|
|
412
412
|
"periodic.table": "Periodic table",
|
|
413
413
|
"periodic.group": "Group {group}",
|
|
414
414
|
"periodic.period": "Period {period}",
|
|
415
|
+
"periodic.allElements": "All elements",
|
|
415
416
|
"periodic.cellLabel": "{name} ({symbol}, Z = {atomicNumber})",
|
|
416
417
|
"periodic.category.alkali-metal": "Alkali metal",
|
|
417
418
|
"periodic.category.alkaline-earth-metal": "Alkaline earth",
|
package/src/locales/es.json
CHANGED
|
@@ -266,6 +266,7 @@
|
|
|
266
266
|
"periodic.table": "Tabla periódica",
|
|
267
267
|
"periodic.group": "Grupo {group}",
|
|
268
268
|
"periodic.period": "Periodo {period}",
|
|
269
|
+
"periodic.allElements": "Todos los elementos",
|
|
269
270
|
"periodic.cellLabel": "{name} ({symbol}, Z = {atomicNumber})",
|
|
270
271
|
"periodic.category.alkali-metal": "Metal alcalino",
|
|
271
272
|
"periodic.category.alkaline-earth-metal": "Alcalinotérreo",
|
package/src/locales/fr.json
CHANGED
|
@@ -266,6 +266,7 @@
|
|
|
266
266
|
"periodic.table": "Tableau périodique",
|
|
267
267
|
"periodic.group": "Groupe {group}",
|
|
268
268
|
"periodic.period": "Période {period}",
|
|
269
|
+
"periodic.allElements": "Tous les éléments",
|
|
269
270
|
"periodic.cellLabel": "{name} ({symbol}, Z = {atomicNumber})",
|
|
270
271
|
"periodic.category.alkali-metal": "Métal alcalin",
|
|
271
272
|
"periodic.category.alkaline-earth-metal": "Alcalino-terreux",
|
package/src/locales/it.json
CHANGED
|
@@ -412,6 +412,7 @@
|
|
|
412
412
|
"periodic.table": "Tavola periodica",
|
|
413
413
|
"periodic.group": "Gruppo {group}",
|
|
414
414
|
"periodic.period": "Periodo {period}",
|
|
415
|
+
"periodic.allElements": "Tutti gli elementi",
|
|
415
416
|
"periodic.cellLabel": "{name} ({symbol}, Z = {atomicNumber})",
|
|
416
417
|
"periodic.category.alkali-metal": "Metallo alcalino",
|
|
417
418
|
"periodic.category.alkaline-earth-metal": "Alcalino terroso",
|
|
@@ -28,6 +28,18 @@ export interface ElementRange {
|
|
|
28
28
|
additive: boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** One element, and what the click that picked it asked for. */
|
|
32
|
+
export interface ElementPick {
|
|
33
|
+
/**
|
|
34
|
+
* Whether the click asked for this element on top of what was already
|
|
35
|
+
* chosen — Cmd on a Mac, Ctrl elsewhere — rather than in place of it. A tool
|
|
36
|
+
* whose plain click already means something of its own — reading one element
|
|
37
|
+
* into a card — tells the two apart by it, so one table both reads an
|
|
38
|
+
* element and builds a set of them.
|
|
39
|
+
*/
|
|
40
|
+
additive: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
/** A cell of the grid, one-based as CSS grid lines are. */
|
|
32
44
|
export interface Cell {
|
|
33
45
|
/** Grid column, one-based. */
|
|
@@ -16,6 +16,7 @@ import { useState } from 'react';
|
|
|
16
16
|
|
|
17
17
|
import type { Swatch } from '../../color/core/interpolate.ts';
|
|
18
18
|
import { useChromeT } from '../../i18n/ui/useT.ts';
|
|
19
|
+
import type { ElementPick } from '../core/layout.ts';
|
|
19
20
|
|
|
20
21
|
import {
|
|
21
22
|
SHELLS_AIR,
|
|
@@ -53,8 +54,11 @@ export interface ElementCellProps {
|
|
|
53
54
|
column: number;
|
|
54
55
|
/** Row of the grid, one-based. */
|
|
55
56
|
row: number;
|
|
56
|
-
/**
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Called with the symbol when the cell is chosen, and with what the click
|
|
59
|
+
* asked for: a plain one, or an additive one — Cmd or Ctrl held.
|
|
60
|
+
*/
|
|
61
|
+
onSelect: (symbol: string, pick: ElementPick) => void;
|
|
58
62
|
/**
|
|
59
63
|
* Third line, under the symbol: the value the tool is showing.
|
|
60
64
|
* @default '' — nothing is written
|
|
@@ -143,8 +147,8 @@ export function ElementCell(props: ElementCellProps): ReactElement {
|
|
|
143
147
|
atomicNumber,
|
|
144
148
|
})}
|
|
145
149
|
aria-pressed={isSelected}
|
|
146
|
-
onClick={() => {
|
|
147
|
-
onSelect(symbol);
|
|
150
|
+
onClick={(event) => {
|
|
151
|
+
onSelect(symbol, { additive: event.metaKey || event.ctrlKey });
|
|
148
152
|
}}
|
|
149
153
|
onPointerEnter={() => {
|
|
150
154
|
setIsHovered(true);
|