react-cheminfo 0.44.0 → 0.46.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 +16 -5
- package/lib/chart/core/chartDomain.d.ts.map +1 -1
- package/lib/chart/core/chartDomain.js.map +1 -1
- package/lib/chart/core/index.d.ts +1 -1
- package/lib/chart/core/index.d.ts.map +1 -1
- package/lib/chart/core/index.js +1 -1
- package/lib/chart/core/index.js.map +1 -1
- package/lib/chart/core/selectionGeometry.d.ts +4 -3
- package/lib/chart/core/selectionGeometry.d.ts.map +1 -1
- package/lib/chart/core/selectionGeometry.js +4 -3
- package/lib/chart/core/selectionGeometry.js.map +1 -1
- package/lib/chart/core/zoomDomain.d.ts +28 -3
- package/lib/chart/core/zoomDomain.d.ts.map +1 -1
- package/lib/chart/core/zoomDomain.js +67 -19
- package/lib/chart/core/zoomDomain.js.map +1 -1
- package/lib/chart/ui/SelectionRect.d.ts.map +1 -1
- package/lib/chart/ui/SelectionRect.js +1 -1
- package/lib/chart/ui/SelectionRect.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/split/core/index.d.ts +3 -0
- package/lib/split/core/index.d.ts.map +1 -0
- package/lib/split/core/index.js +2 -0
- package/lib/split/core/index.js.map +1 -0
- package/lib/split/core/split.d.ts +52 -0
- package/lib/split/core/split.d.ts.map +1 -0
- package/lib/split/core/split.js +45 -0
- package/lib/split/core/split.js.map +1 -0
- package/lib/split/ui/SplitRow.d.ts +66 -0
- package/lib/split/ui/SplitRow.d.ts.map +1 -0
- package/lib/split/ui/SplitRow.js +99 -0
- package/lib/split/ui/SplitRow.js.map +1 -0
- package/lib/split/ui/index.d.ts +3 -0
- package/lib/split/ui/index.d.ts.map +1 -0
- package/lib/split/ui/index.js +2 -0
- package/lib/split/ui/index.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 +16 -5
- package/src/chart/core/index.ts +2 -0
- package/src/chart/core/selectionGeometry.ts +4 -3
- package/src/chart/core/zoomDomain.ts +74 -23
- package/src/chart/ui/SelectionRect.tsx +4 -0
- package/src/core.ts +1 -0
- package/src/split/core/index.ts +8 -0
- package/src/split/core/split.ts +67 -0
- package/src/split/ui/SplitRow.tsx +225 -0
- package/src/split/ui/index.ts +2 -0
- package/src/ui.ts +1 -0
- package/styles/chrome.css +18 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two figures side by side, with a splitter between them and the share each
|
|
3
|
+
* takes written into the address.
|
|
4
|
+
*
|
|
5
|
+
* How much room the tool deserves beside the figure it feeds is not ours to
|
|
6
|
+
* decide: somebody embedding the chart wants it nearly whole, somebody
|
|
7
|
+
* teaching off the picker wants the picker nearly whole, and both want the
|
|
8
|
+
* link they hand out to open the way they left it. So the splitter is dragged,
|
|
9
|
+
* the share it comes to rest at goes into `?split=`, and the address is the
|
|
10
|
+
* figure.
|
|
11
|
+
*
|
|
12
|
+
* Two things it cannot do, and must not pretend to: on a narrow screen there
|
|
13
|
+
* is no room for two columns at all, so the panes stack and no splitter is
|
|
14
|
+
* drawn; and a link that hid everything one pane holds leaves the other the
|
|
15
|
+
* whole width rather than a share of it.
|
|
16
|
+
*
|
|
17
|
+
* The bar itself is react-science's `SplitPane`, and it keeps react-science's
|
|
18
|
+
* look — barely visible on purpose, found by the pointer that changes over it
|
|
19
|
+
* rather than by the eye. All `chrome.css` says about it is that eleven pixels
|
|
20
|
+
* is more gutter than two figures need.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
24
|
+
import { useRef, useState } from 'react';
|
|
25
|
+
import type { SplitPaneSize } from 'react-science/ui';
|
|
26
|
+
import { SplitPane } from 'react-science/ui';
|
|
27
|
+
|
|
28
|
+
import { useResizeObserver } from '../../hooks/ui/useResizeObserver.ts';
|
|
29
|
+
import type { SplitRange } from '../core/split.ts';
|
|
30
|
+
import { clampSplit } from '../core/split.ts';
|
|
31
|
+
|
|
32
|
+
/** Props of {@link SplitRow}. */
|
|
33
|
+
export interface SplitRowProps extends SplitRange {
|
|
34
|
+
/**
|
|
35
|
+
* Share of the row the first pane takes, as a whole percentage, or `null`
|
|
36
|
+
* while the page sits at {@link SplitRowProps.defaultRatio}.
|
|
37
|
+
*/
|
|
38
|
+
ratio: number | null;
|
|
39
|
+
/** The share the row opens at when the address names none. */
|
|
40
|
+
defaultRatio: number;
|
|
41
|
+
/**
|
|
42
|
+
* Take the share a drag came to rest at, or `null` when the splitter was
|
|
43
|
+
* double-clicked back to the default.
|
|
44
|
+
*/
|
|
45
|
+
onRatio: (ratio: number | null) => void;
|
|
46
|
+
/** The left-hand pane, or `null` when a link left nothing in it. */
|
|
47
|
+
start: ReactNode;
|
|
48
|
+
/** The right-hand pane, or `null` when a link left nothing in it. */
|
|
49
|
+
end: ReactNode;
|
|
50
|
+
/**
|
|
51
|
+
* The narrowest row that is still split; under it the two panes stack.
|
|
52
|
+
* @default 860
|
|
53
|
+
*/
|
|
54
|
+
stackBelow?: number;
|
|
55
|
+
/**
|
|
56
|
+
* The gutter each pane keeps beside the splitter, in pixels, so neither
|
|
57
|
+
* figure is read against its edge.
|
|
58
|
+
* @default 6
|
|
59
|
+
*/
|
|
60
|
+
gutter?: number;
|
|
61
|
+
/**
|
|
62
|
+
* The gap between the two panes once they have stacked, in pixels.
|
|
63
|
+
* @default 16
|
|
64
|
+
*/
|
|
65
|
+
stackedGap?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Two panes sharing a row, divided where the address says.
|
|
70
|
+
* @param props - See {@link SplitRowProps}.
|
|
71
|
+
* @returns The two panes with the splitter between them, the two stacked on a
|
|
72
|
+
* narrow screen, or whichever one a link left on the page, alone.
|
|
73
|
+
*/
|
|
74
|
+
export function SplitRow(props: SplitRowProps): ReactElement | null {
|
|
75
|
+
const {
|
|
76
|
+
ratio,
|
|
77
|
+
defaultRatio,
|
|
78
|
+
onRatio,
|
|
79
|
+
start,
|
|
80
|
+
end,
|
|
81
|
+
min,
|
|
82
|
+
max,
|
|
83
|
+
stackBelow = STACK_BELOW,
|
|
84
|
+
gutter = GUTTER,
|
|
85
|
+
stackedGap = STACKED_GAP,
|
|
86
|
+
} = props;
|
|
87
|
+
const row = useRef<HTMLDivElement>(null);
|
|
88
|
+
const [width, setWidth] = useState(windowWidth);
|
|
89
|
+
// The share under the pointer, which is not the one in the address: a drag
|
|
90
|
+
// reports every pixel it crosses, and the address is rewritten once, where
|
|
91
|
+
// the splitter is let go.
|
|
92
|
+
const [dragged, setDragged] = useState<number | null>(null);
|
|
93
|
+
|
|
94
|
+
useResizeObserver(row, (size) => {
|
|
95
|
+
setWidth(size.width);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (start === null && end === null) return null;
|
|
99
|
+
|
|
100
|
+
const stacked = start === null || end === null || width < stackBelow;
|
|
101
|
+
const startPane =
|
|
102
|
+
start === null ? null : (
|
|
103
|
+
<div
|
|
104
|
+
data-testid="split-start"
|
|
105
|
+
style={stacked ? STACKED_PANE : paneStyle('start', gutter)}
|
|
106
|
+
>
|
|
107
|
+
{start}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
const endPane =
|
|
111
|
+
end === null ? null : (
|
|
112
|
+
<div
|
|
113
|
+
data-testid="split-end"
|
|
114
|
+
style={stacked ? STACKED_PANE : paneStyle('end', gutter)}
|
|
115
|
+
>
|
|
116
|
+
{end}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (stacked) {
|
|
121
|
+
return (
|
|
122
|
+
<div ref={row} style={{ ...STACK, gap: stackedGap }}>
|
|
123
|
+
{startPane}
|
|
124
|
+
{endPane}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const share = clampSplit(dragged ?? ratio ?? defaultRatio, { min, max });
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
// `split-row` is what `chrome.css` reaches the bar through: it is
|
|
133
|
+
// react-science's own element and carries no class of ours.
|
|
134
|
+
<div ref={row} className="split-row">
|
|
135
|
+
<SplitPane
|
|
136
|
+
direction="horizontal"
|
|
137
|
+
controlledSide="start"
|
|
138
|
+
size={`${share}%`}
|
|
139
|
+
// Held open, so the only layout a visitor can reach is one the address
|
|
140
|
+
// describes: left to itself a double click collapses a pane, and a link
|
|
141
|
+
// shared from there would open with the pane back. The double click is
|
|
142
|
+
// answered here instead, by returning to the page's own share.
|
|
143
|
+
open
|
|
144
|
+
onOpenChange={() => {
|
|
145
|
+
setDragged(null);
|
|
146
|
+
onRatio(null);
|
|
147
|
+
}}
|
|
148
|
+
onSizeChange={(asked) => {
|
|
149
|
+
setDragged(shareOf(asked, share, { min, max }));
|
|
150
|
+
}}
|
|
151
|
+
onResize={(asked) => {
|
|
152
|
+
const rested = shareOf(asked, share, { min, max });
|
|
153
|
+
setDragged(null);
|
|
154
|
+
onRatio(rested);
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
{startPane}
|
|
158
|
+
{endPane}
|
|
159
|
+
</SplitPane>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The share a splitter asked for.
|
|
166
|
+
* @param asked - What it reported, a percentage because that is what it was
|
|
167
|
+
* given.
|
|
168
|
+
* @param current - The share in force, kept when the report is not a number.
|
|
169
|
+
* @param range - The range this row is divided in.
|
|
170
|
+
* @returns A whole percentage inside it.
|
|
171
|
+
*/
|
|
172
|
+
function shareOf(
|
|
173
|
+
asked: SplitPaneSize,
|
|
174
|
+
current: number,
|
|
175
|
+
range: SplitRange,
|
|
176
|
+
): number {
|
|
177
|
+
const value = Number.parseFloat(asked);
|
|
178
|
+
return Number.isFinite(value) ? clampSplit(value, range) : current;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* How wide the window is, which is what the row is taken to be until it has
|
|
183
|
+
* been measured. Reading it rather than starting at zero is what keeps a wide
|
|
184
|
+
* page from laying itself out stacked and splitting a frame later, which would
|
|
185
|
+
* build both figures twice on every load.
|
|
186
|
+
* @returns The width in pixels, or the threshold itself where there is no
|
|
187
|
+
* window to read — a test, or a page rendered to a string — which is the
|
|
188
|
+
* narrowest a row is still split at.
|
|
189
|
+
*/
|
|
190
|
+
function windowWidth(): number {
|
|
191
|
+
const { innerWidth } = globalThis;
|
|
192
|
+
return typeof innerWidth === 'number' && innerWidth > 0
|
|
193
|
+
? innerWidth
|
|
194
|
+
: STACK_BELOW;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The narrowest row that is still split, in pixels. */
|
|
198
|
+
const STACK_BELOW = 860;
|
|
199
|
+
|
|
200
|
+
/** The gutter a pane keeps beside the splitter, in pixels. */
|
|
201
|
+
const GUTTER = 6;
|
|
202
|
+
|
|
203
|
+
/** The gap between the panes once they have stacked, in pixels. */
|
|
204
|
+
const STACKED_GAP = 16;
|
|
205
|
+
|
|
206
|
+
const STACK = {
|
|
207
|
+
display: 'flex',
|
|
208
|
+
flexDirection: 'column',
|
|
209
|
+
} as const satisfies CSSProperties;
|
|
210
|
+
|
|
211
|
+
const STACKED_PANE = {
|
|
212
|
+
display: 'flex',
|
|
213
|
+
flexDirection: 'column',
|
|
214
|
+
minWidth: 0,
|
|
215
|
+
} as const satisfies CSSProperties;
|
|
216
|
+
|
|
217
|
+
function paneStyle(side: 'start' | 'end', gutter: number): CSSProperties {
|
|
218
|
+
return {
|
|
219
|
+
display: 'flex',
|
|
220
|
+
flex: '1 1 0%',
|
|
221
|
+
flexDirection: 'column',
|
|
222
|
+
minWidth: 0,
|
|
223
|
+
[side === 'start' ? 'paddingRight' : 'paddingLeft']: gutter,
|
|
224
|
+
};
|
|
225
|
+
}
|
package/src/ui.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './scatter/ui/index.ts';
|
|
|
30
30
|
export * from './scatter3d/ui/index.ts';
|
|
31
31
|
export * from './share/ui/index.ts';
|
|
32
32
|
export * from './shared/ui/index.ts';
|
|
33
|
+
export * from './split/ui/index.ts';
|
|
33
34
|
// The conformer table, from the structure module's file rather than its
|
|
34
35
|
// barrel: `structure/ui/index.ts` is the door that must never reach react-ocl,
|
|
35
36
|
// and this component does not, but `react-cheminfo/conformers` is imported by
|
package/styles/chrome.css
CHANGED
|
@@ -1173,6 +1173,24 @@ select,
|
|
|
1173
1173
|
color: var(--accent);
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
+
/* ── The splitter ───────────────────────────────────────────────────────── */
|
|
1177
|
+
|
|
1178
|
+
/*
|
|
1179
|
+
The bar between the two figures of a `<SplitRow>` is react-science's, and it
|
|
1180
|
+
keeps react-science's look — the pale track with the grip written on it that
|
|
1181
|
+
NMRium and every other tool of ours is divided by. It is barely visible on
|
|
1182
|
+
purpose: what is read is the two figures, and the bar is found by the pointer
|
|
1183
|
+
that changes over it rather than by the eye.
|
|
1184
|
+
|
|
1185
|
+
All this says is that eleven pixels is more gutter than two figures need.
|
|
1186
|
+
`:nth-child(2)` is the bar: a split is only mounted with a pane on each side
|
|
1187
|
+
of it, so the three children are always side, bar, side.
|
|
1188
|
+
*/
|
|
1189
|
+
.split-row > div > div:nth-child(2) {
|
|
1190
|
+
width: 7px;
|
|
1191
|
+
min-width: 7px;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1176
1194
|
/* ── Printing ────────────────────────────────────────────────────────────── */
|
|
1177
1195
|
|
|
1178
1196
|
/*
|