react-cheminfo 0.29.0 → 0.31.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.
Files changed (126) hide show
  1. package/README.md +62 -2
  2. package/lib/chrome/ui/AccountButton.d.ts +76 -0
  3. package/lib/chrome/ui/AccountButton.d.ts.map +1 -0
  4. package/lib/chrome/ui/AccountButton.js +66 -0
  5. package/lib/chrome/ui/AccountButton.js.map +1 -0
  6. package/lib/chrome/ui/index.d.ts +2 -0
  7. package/lib/chrome/ui/index.d.ts.map +1 -1
  8. package/lib/chrome/ui/index.js +1 -0
  9. package/lib/chrome/ui/index.js.map +1 -1
  10. package/lib/clipboard/ui/ClickToCopy.d.ts +6 -0
  11. package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
  12. package/lib/clipboard/ui/ClickToCopy.js +3 -3
  13. package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
  14. package/lib/download/core/downloadFigure.d.ts +7 -5
  15. package/lib/download/core/downloadFigure.d.ts.map +1 -1
  16. package/lib/download/core/downloadFigure.js +7 -6
  17. package/lib/download/core/downloadFigure.js.map +1 -1
  18. package/lib/download/core/figureLayout.d.ts +55 -0
  19. package/lib/download/core/figureLayout.d.ts.map +1 -0
  20. package/lib/download/core/figureLayout.js +89 -0
  21. package/lib/download/core/figureLayout.js.map +1 -0
  22. package/lib/download/core/figureScale.d.ts +9 -0
  23. package/lib/download/core/figureScale.d.ts.map +1 -1
  24. package/lib/download/core/figureScale.js +10 -0
  25. package/lib/download/core/figureScale.js.map +1 -1
  26. package/lib/download/core/figureSettle.d.ts +51 -0
  27. package/lib/download/core/figureSettle.d.ts.map +1 -0
  28. package/lib/download/core/figureSettle.js +81 -0
  29. package/lib/download/core/figureSettle.js.map +1 -0
  30. package/lib/download/core/figureSvg.d.ts +7 -1
  31. package/lib/download/core/figureSvg.d.ts.map +1 -1
  32. package/lib/download/core/figureSvg.js +1 -0
  33. package/lib/download/core/figureSvg.js.map +1 -1
  34. package/lib/download/core/figureSvgDocument.d.ts +7 -0
  35. package/lib/download/core/figureSvgDocument.d.ts.map +1 -1
  36. package/lib/download/core/figureSvgDocument.js +11 -2
  37. package/lib/download/core/figureSvgDocument.js.map +1 -1
  38. package/lib/download/core/index.d.ts +5 -1
  39. package/lib/download/core/index.d.ts.map +1 -1
  40. package/lib/download/core/index.js +3 -1
  41. package/lib/download/core/index.js.map +1 -1
  42. package/lib/download/ui/FigureDownload.d.ts +18 -4
  43. package/lib/download/ui/FigureDownload.d.ts.map +1 -1
  44. package/lib/download/ui/FigureDownload.js +35 -9
  45. package/lib/download/ui/FigureDownload.js.map +1 -1
  46. package/lib/download/ui/FigureDownloadPanel.d.ts +17 -7
  47. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  48. package/lib/download/ui/FigureDownloadPanel.js +30 -22
  49. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  50. package/lib/download/ui/FigureSizeField.d.ts +27 -0
  51. package/lib/download/ui/FigureSizeField.d.ts.map +1 -0
  52. package/lib/download/ui/FigureSizeField.js +87 -0
  53. package/lib/download/ui/FigureSizeField.js.map +1 -0
  54. package/lib/download/ui/FigureSizeRows.d.ts +34 -0
  55. package/lib/download/ui/FigureSizeRows.d.ts.map +1 -0
  56. package/lib/download/ui/FigureSizeRows.js +54 -0
  57. package/lib/download/ui/FigureSizeRows.js.map +1 -0
  58. package/lib/download/ui/index.d.ts +2 -0
  59. package/lib/download/ui/index.d.ts.map +1 -1
  60. package/lib/download/ui/index.js.map +1 -1
  61. package/lib/download/ui/useFigureRedraw.d.ts +27 -0
  62. package/lib/download/ui/useFigureRedraw.d.ts.map +1 -0
  63. package/lib/download/ui/useFigureRedraw.js +78 -0
  64. package/lib/download/ui/useFigureRedraw.js.map +1 -0
  65. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  66. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  67. package/lib/locales/de.json +13 -2
  68. package/lib/locales/en.json +13 -2
  69. package/lib/locales/es.json +13 -2
  70. package/lib/locales/fr.json +13 -2
  71. package/lib/overlay/ui/OverlayValueMenu.d.ts.map +1 -1
  72. package/lib/overlay/ui/OverlayValueMenu.js +7 -1
  73. package/lib/overlay/ui/OverlayValueMenu.js.map +1 -1
  74. package/lib/structure/core/conformerBoltzmann.d.ts +56 -0
  75. package/lib/structure/core/conformerBoltzmann.d.ts.map +1 -0
  76. package/lib/structure/core/conformerBoltzmann.js +96 -0
  77. package/lib/structure/core/conformerBoltzmann.js.map +1 -0
  78. package/lib/structure/core/conformerEnergy.d.ts +69 -0
  79. package/lib/structure/core/conformerEnergy.d.ts.map +1 -0
  80. package/lib/structure/core/conformerEnergy.js +47 -0
  81. package/lib/structure/core/conformerEnergy.js.map +1 -0
  82. package/lib/structure/core/index.d.ts +3 -0
  83. package/lib/structure/core/index.d.ts.map +1 -1
  84. package/lib/structure/core/index.js +2 -0
  85. package/lib/structure/core/index.js.map +1 -1
  86. package/lib/structure/ui/ConformerTable.d.ts +73 -0
  87. package/lib/structure/ui/ConformerTable.d.ts.map +1 -0
  88. package/lib/structure/ui/ConformerTable.js +259 -0
  89. package/lib/structure/ui/ConformerTable.js.map +1 -0
  90. package/lib/structure/ui/conformerColumns.d.ts +30 -0
  91. package/lib/structure/ui/conformerColumns.d.ts.map +1 -0
  92. package/lib/structure/ui/conformerColumns.js +37 -0
  93. package/lib/structure/ui/conformerColumns.js.map +1 -0
  94. package/lib/ui.d.ts +4 -0
  95. package/lib/ui.d.ts.map +1 -1
  96. package/lib/ui.js +2 -0
  97. package/lib/ui.js.map +1 -1
  98. package/package.json +1 -1
  99. package/src/chrome/ui/AccountButton.tsx +185 -0
  100. package/src/chrome/ui/index.ts +2 -0
  101. package/src/clipboard/ui/ClickToCopy.tsx +9 -1
  102. package/src/download/core/downloadFigure.ts +12 -9
  103. package/src/download/core/figureLayout.ts +114 -0
  104. package/src/download/core/figureScale.ts +13 -0
  105. package/src/download/core/figureSettle.ts +112 -0
  106. package/src/download/core/figureSvg.ts +8 -1
  107. package/src/download/core/figureSvgDocument.ts +19 -2
  108. package/src/download/core/index.ts +13 -0
  109. package/src/download/ui/FigureDownload.tsx +88 -35
  110. package/src/download/ui/FigureDownloadPanel.tsx +45 -22
  111. package/src/download/ui/FigureSizeField.tsx +153 -0
  112. package/src/download/ui/FigureSizeRows.tsx +108 -0
  113. package/src/download/ui/index.ts +2 -0
  114. package/src/download/ui/useFigureRedraw.tsx +115 -0
  115. package/src/locales/de.json +13 -2
  116. package/src/locales/en.json +13 -2
  117. package/src/locales/es.json +13 -2
  118. package/src/locales/fr.json +13 -2
  119. package/src/overlay/ui/OverlayValueMenu.tsx +7 -1
  120. package/src/structure/core/conformerBoltzmann.ts +102 -0
  121. package/src/structure/core/conformerEnergy.ts +97 -0
  122. package/src/structure/core/index.ts +15 -0
  123. package/src/structure/ui/ConformerTable.tsx +429 -0
  124. package/src/structure/ui/conformerColumns.ts +55 -0
  125. package/src/ui.ts +12 -0
  126. package/styles/chrome.css +55 -0
@@ -0,0 +1,259 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A conformer set, most stable first, as a table one row of which is selected.
4
+ *
5
+ * Two sites read the same set for different reasons — one picks the geometry a
6
+ * calculation runs on, the other reads the ranking itself — so the columns are
7
+ * a prop rather than a variant. Everything else is the same table: rows ordered
8
+ * by energy, one selected, the arrows walking it.
9
+ *
10
+ * The population column is the reason the ranking matters. A share is
11
+ * exponential in the energy gap, so it inherits the error of whatever produced
12
+ * the energies and magnifies it; the caption under the table says which method
13
+ * that was, and `boltzmannConfidence` says what its error is worth. A table
14
+ * that prints a force field's populations without that line is stating a
15
+ * percentage it cannot support.
16
+ */
17
+ import { HTMLTable } from '@blueprintjs/core';
18
+ import { useEffect, useMemo, useRef } from 'react';
19
+ import { ClickToCopy } from "../../clipboard/ui/ClickToCopy.js";
20
+ import { formatPercent } from "../../format/core/numbers.js";
21
+ import { handleListNavigationKey } from "../../hooks/ui/listNavigation.js";
22
+ import { ROOM_TEMPERATURE, boltzmannShares, } from "../core/conformerBoltzmann.js";
23
+ import { relativeEnergyOf, totalEnergyOf } from "../core/conformerEnergy.js";
24
+ import { CONFORMER_COLUMN_LABELS, NUMERIC_CONFORMER_COLUMNS, PICKER_COLUMNS, } from "./conformerColumns.js";
25
+ /**
26
+ * The conformers of a set, as a selectable table.
27
+ *
28
+ * The arrows walk the rows once the table has the focus, rather than from
29
+ * anywhere on the page: a conformer list usually sits beside a drawing surface
30
+ * or a 3D canvas, both of which read the arrows themselves.
31
+ * @param props - See {@link ConformerTableProps}.
32
+ * @returns The table, or `null` when there is nothing to show.
33
+ */
34
+ export function ConformerTable(props) {
35
+ const { conformers, columns = PICKER_COLUMNS, ranking = 'force-field', selectedId = null, onSelect, temperature = ROOM_TEMPERATURE, label = 'Conformers, most stable first', rowName = String, columnLabels, } = props;
36
+ const bodyRef = useRef(null);
37
+ const shares = useMemo(() => {
38
+ if (!columns.includes('population'))
39
+ return [];
40
+ const energies = new Array(conformers.length);
41
+ for (let index = 0; index < conformers.length; index++) {
42
+ const conformer = conformers[index];
43
+ energies[index] =
44
+ conformer === undefined ? null : relativeEnergyOf(conformer, ranking);
45
+ }
46
+ return boltzmannShares(energies, temperature);
47
+ }, [conformers, columns, ranking, temperature]);
48
+ const largestShare = useMemo(() => {
49
+ let largest = 0;
50
+ for (const share of shares) {
51
+ if (share != null && share > largest)
52
+ largest = share;
53
+ }
54
+ return largest;
55
+ }, [shares]);
56
+ // The picked row is brought into view rather than left under the fold, which
57
+ // is what makes walking a long set with the arrows usable.
58
+ useEffect(() => {
59
+ if (bodyRef.current === null || selectedId === null)
60
+ return;
61
+ bodyRef.current
62
+ .querySelector('[aria-selected="true"]')
63
+ ?.scrollIntoView({ block: 'nearest' });
64
+ }, [selectedId]);
65
+ if (conformers.length === 0)
66
+ return null;
67
+ function onKeyDown(event) {
68
+ if (onSelect === undefined)
69
+ return;
70
+ handleListNavigationKey(event, {
71
+ length: conformers.length,
72
+ selectedIndex: conformers.findIndex((conformer) => conformer.id === selectedId),
73
+ onSelect: (index) => {
74
+ const conformer = conformers[index];
75
+ if (conformer !== undefined)
76
+ onSelect(conformer.id);
77
+ },
78
+ });
79
+ }
80
+ return (_jsxs(HTMLTable, { compact: true, interactive: true, striped: true, style: tableStyle, children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column) => (_jsx("th", { style: headerStyle(column), children: columnLabels?.[column] ?? CONFORMER_COLUMN_LABELS[column] }, column))) }) }), _jsx("tbody", { ref: bodyRef, onKeyDown: onKeyDown,
81
+ // Reached by Tab and then walked with the arrows: the surfaces beside a
82
+ // conformer list read the arrows themselves.
83
+ tabIndex: onSelect === undefined ? undefined : 0, role: "listbox", "aria-label": label, children: conformers.map((conformer, index) => {
84
+ const active = conformer.id === selectedId;
85
+ const copyableHere = onSelect === undefined || active;
86
+ return (_jsx("tr", { "data-testid": "conformer-row", "data-conformer-id": String(conformer.id), role: "option", "aria-selected": active, "data-selected": active ? 'true' : 'false', style: rowStyle(active, onSelect !== undefined), onClick: () => onSelect?.(conformer.id), children: columns.map((column) => {
87
+ const content = cellContent(column, conformer, ranking, shares[index] ?? null, largestShare, rowName);
88
+ // An energy is a value a reader takes away; a rank and a bar
89
+ // are not. A copy stops the click at the cell, so only the row
90
+ // already selected offers one: on any other row the click has
91
+ // to reach the row and select it, which is what a reader means
92
+ // by clicking a conformer they are not on.
93
+ return copyableHere &&
94
+ COPY_LABELS[column] !== undefined &&
95
+ typeof content === 'string' &&
96
+ content !== '—' ? (_jsx(ClickToCopy, { as: "td", style: cellStyle(column), value: content, label: COPY_LABELS[column], testId: CELL_TEST_IDS[column], children: content }, column)) : (_jsx("td", { style: cellStyle(column), "data-testid": CELL_TEST_IDS[column], children: content }, column));
97
+ }) }, conformer.id));
98
+ }) })] }));
99
+ }
100
+ /**
101
+ * What a copyable column is called in the hover title, and which columns are
102
+ * copyable at all.
103
+ *
104
+ * Only the relative energy: it is the number a chemist reads off the set and
105
+ * quotes elsewhere. A total force-field energy is an internal quantity nobody
106
+ * pastes, and a rank and a share are read on the page rather than taken away.
107
+ */
108
+ const COPY_LABELS = {
109
+ relative: 'relative energy in kcal/mol',
110
+ };
111
+ /**
112
+ * A stable hook per cell, so a suite can read one column without counting
113
+ * table cells — which changes the moment a column is added.
114
+ */
115
+ const CELL_TEST_IDS = {
116
+ id: 'conformer-id',
117
+ population: 'conformer-population',
118
+ relative: 'conformer-relative-energy',
119
+ total: 'conformer-total-energy',
120
+ forceFieldRelative: 'conformer-force-field-relative',
121
+ forceFieldRank: 'conformer-force-field-rank',
122
+ };
123
+ /**
124
+ * The share of a conformer, drawn as a bar.
125
+ *
126
+ * It takes its colour from the row it sits in — `currentColor` — so it is
127
+ * legible on a plain row and on the selected one without a rule per state.
128
+ *
129
+ * It is drawn against the largest share rather than against the whole: the
130
+ * column is narrow, and against the whole a set of twelve near-equal conformers
131
+ * would be twelve bars of three pixels.
132
+ * @param props - The share to draw, against the largest one in the set.
133
+ * @param props.share - The share from 0 to 1, or `null` when there is no energy.
134
+ * @param props.largest - The largest share in the set, which is the full width.
135
+ * @returns The bar.
136
+ */
137
+ function ShareBar(props) {
138
+ const { share, largest } = props;
139
+ const width = share === null || largest <= 0
140
+ ? 0
141
+ : Math.max(0, Math.min(1, share / largest)) * 100;
142
+ return (_jsx("span", { style: barTrackStyle, "aria-hidden": "true", children: _jsx("span", { style: { ...barFillStyle, width: `${width}%` } }) }));
143
+ }
144
+ function cellContent(column, conformer, ranking, share, largest, rowName) {
145
+ switch (column) {
146
+ case 'id':
147
+ return rowName(conformer.id);
148
+ case 'population':
149
+ return (_jsxs("span", { style: populationStyle, children: [_jsx(ShareBar, { share: share, largest: largest }), _jsx("span", { children: shareLabel(share) })] }));
150
+ case 'relative':
151
+ return energyLabel(relativeEnergyOf(conformer, ranking));
152
+ case 'total':
153
+ return energyLabel(totalEnergyOf(conformer, ranking));
154
+ case 'forceFieldRelative':
155
+ return energyLabel(conformer.relativeEnergy);
156
+ case 'forceFieldRank':
157
+ return conformer.refinement == null
158
+ ? '—'
159
+ : String(conformer.refinement.forceFieldId);
160
+ default:
161
+ return '—';
162
+ }
163
+ }
164
+ /**
165
+ * The share of molecules in a conformer, as a percentage.
166
+ *
167
+ * A share that rounds to zero is written as `<0.1 %` instead: a conformer the
168
+ * search found and kept is populated by something, and `0 %` says it is not.
169
+ * @param share - The share from 0 to 1, or `null` when there is no energy.
170
+ * @returns The percentage, or a dash when it cannot be worked out.
171
+ */
172
+ function shareLabel(share) {
173
+ if (share === null)
174
+ return '—';
175
+ if (share > 0 && share < 0.001)
176
+ return '<0.1 %';
177
+ return formatPercent(share);
178
+ }
179
+ /**
180
+ * The energy to two decimals, or a dash when there is none.
181
+ *
182
+ * The unit is in the column header rather than in every cell, so the numbers
183
+ * line up and a row stays readable in a narrow panel.
184
+ * @param energy - kcal/mol, or `null`.
185
+ * @returns The label.
186
+ */
187
+ function energyLabel(energy) {
188
+ if (energy === null)
189
+ return '—';
190
+ if (Math.abs(energy) < ENERGY_RESOLUTION)
191
+ return '0.00';
192
+ return energy > 0 ? `+${energy.toFixed(2)}` : energy.toFixed(2);
193
+ }
194
+ /**
195
+ * Below this, two force-field energies are the same number to anyone reading
196
+ * them, and the duplicate-minimum test upstream has already decided they are
197
+ * distinct shapes.
198
+ */
199
+ const ENERGY_RESOLUTION = 0.005;
200
+ function headerStyle(column) {
201
+ return NUMERIC_CONFORMER_COLUMNS.has(column) ? numericStyle : undefined;
202
+ }
203
+ function cellStyle(column) {
204
+ return NUMERIC_CONFORMER_COLUMNS.has(column) ? numericStyle : undefined;
205
+ }
206
+ const tableStyle = { width: '100%' };
207
+ const numericStyle = {
208
+ textAlign: 'right',
209
+ fontVariantNumeric: 'tabular-nums',
210
+ };
211
+ /**
212
+ * The selected row is tinted with the site's own accent, so the table belongs
213
+ * to whichever site draws it; a row is a pointer only where a click does
214
+ * something.
215
+ * @param active - Whether this row is the selected one.
216
+ * @param selectable - Whether a click selects a row at all.
217
+ * @returns The row's inline style, or `undefined` when it needs none.
218
+ */
219
+ function rowStyle(active, selectable) {
220
+ if (active)
221
+ return selectable ? SELECTED_ROW : SELECTED_ROW_STATIC;
222
+ return selectable ? SELECTABLE_ROW : undefined;
223
+ }
224
+ const SELECTED_ROW_STATIC = {
225
+ background: 'color-mix(in oklab, var(--accent) 12%, white)',
226
+ fontWeight: 600,
227
+ };
228
+ const SELECTED_ROW = {
229
+ ...SELECTED_ROW_STATIC,
230
+ cursor: 'pointer',
231
+ };
232
+ const SELECTABLE_ROW = { cursor: 'pointer' };
233
+ const populationStyle = {
234
+ display: 'flex',
235
+ alignItems: 'center',
236
+ gap: 6,
237
+ fontVariantNumeric: 'tabular-nums',
238
+ };
239
+ /**
240
+ * The track is tinted with `color-mix` rather than `opacity`, which the fill
241
+ * would inherit: an opacity on the track composites its child too, so the bar
242
+ * would be as faint as the groove it sits in.
243
+ */
244
+ const barTrackStyle = {
245
+ display: 'block',
246
+ flex: '1 1 auto',
247
+ minWidth: 24,
248
+ height: 5,
249
+ borderRadius: 3,
250
+ backgroundColor: 'color-mix(in srgb, currentColor 22%, transparent)',
251
+ overflow: 'hidden',
252
+ };
253
+ const barFillStyle = {
254
+ display: 'block',
255
+ height: '100%',
256
+ borderRadius: 3,
257
+ backgroundColor: 'currentColor',
258
+ };
259
+ //# sourceMappingURL=ConformerTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConformerTable.js","sourceRoot":"","sources":["../../../src/structure/ui/ConformerTable.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The columns a conformer table can draw, and the two sets a site usually
3
+ * wants.
4
+ *
5
+ * Their own module so `ConformerTable.tsx` exports a component and nothing
6
+ * else, which is what keeps fast refresh working on the sites that render it.
7
+ */
8
+ /** One column of a conformer table. */
9
+ export type ConformerColumn =
10
+ /** The 1-based rank, which is also the conformer's id. */
11
+ 'id'
12
+ /** The Boltzmann share, as a bar and a percentage. */
13
+ | 'population'
14
+ /** Energy above the most stable conformer, in the active ranking. */
15
+ | 'relative'
16
+ /** Total energy, in the active ranking. */
17
+ | 'total'
18
+ /** What the force field said before a refinement reordered the set. */
19
+ | 'forceFieldRelative'
20
+ /** The rank the conformer held before a refinement reordered the set. */
21
+ | 'forceFieldRank';
22
+ /** The columns a picker needs: which shape, how much of it, how far up. */
23
+ export declare const PICKER_COLUMNS: readonly ConformerColumn[];
24
+ /** The columns a reader of the ranking itself needs. */
25
+ export declare const RANKING_COLUMNS: readonly ConformerColumn[];
26
+ /** The heading of each column. The unit lives here, never in a cell. */
27
+ export declare const CONFORMER_COLUMN_LABELS: Record<ConformerColumn, string>;
28
+ /** The columns whose cells are numbers, and so are right-aligned and tabular. */
29
+ export declare const NUMERIC_CONFORMER_COLUMNS: ReadonlySet<ConformerColumn>;
30
+ //# sourceMappingURL=conformerColumns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformerColumns.d.ts","sourceRoot":"","sources":["../../../src/structure/ui/conformerColumns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uCAAuC;AACvC,MAAM,MAAM,eAAe;AACzB,0DAA0D;AACxD,IAAI;AACN,sDAAsD;GACpD,YAAY;AACd,qEAAqE;GACnE,UAAU;AACZ,2CAA2C;GACzC,OAAO;AACT,uEAAuE;GACrE,oBAAoB;AACtB,yEAAyE;GACvE,gBAAgB,CAAC;AAErB,2EAA2E;AAC3E,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EAIpD,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAKrD,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAOnE,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,eAAe,CAKjE,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The columns a conformer table can draw, and the two sets a site usually
3
+ * wants.
4
+ *
5
+ * Their own module so `ConformerTable.tsx` exports a component and nothing
6
+ * else, which is what keeps fast refresh working on the sites that render it.
7
+ */
8
+ /** The columns a picker needs: which shape, how much of it, how far up. */
9
+ export const PICKER_COLUMNS = [
10
+ 'id',
11
+ 'population',
12
+ 'relative',
13
+ ];
14
+ /** The columns a reader of the ranking itself needs. */
15
+ export const RANKING_COLUMNS = [
16
+ 'id',
17
+ 'population',
18
+ 'relative',
19
+ 'total',
20
+ ];
21
+ /** The heading of each column. The unit lives here, never in a cell. */
22
+ export const CONFORMER_COLUMN_LABELS = {
23
+ id: '#',
24
+ population: 'share',
25
+ relative: 'ΔE kcal/mol',
26
+ total: 'total kcal/mol',
27
+ forceFieldRelative: 'force field ΔE',
28
+ forceFieldRank: 'force field rank',
29
+ };
30
+ /** The columns whose cells are numbers, and so are right-aligned and tabular. */
31
+ export const NUMERIC_CONFORMER_COLUMNS = new Set([
32
+ 'relative',
33
+ 'total',
34
+ 'forceFieldRelative',
35
+ 'forceFieldRank',
36
+ ]);
37
+ //# sourceMappingURL=conformerColumns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformerColumns.js","sourceRoot":"","sources":["../../../src/structure/ui/conformerColumns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,IAAI;IACJ,YAAY;IACZ,UAAU;CACX,CAAC;AAEF,wDAAwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAA+B;IACzD,IAAI;IACJ,YAAY;IACZ,UAAU;IACV,OAAO;CACR,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,uBAAuB,GAAoC;IACtE,EAAE,EAAE,GAAG;IACP,UAAU,EAAE,OAAO;IACnB,QAAQ,EAAE,aAAa;IACvB,KAAK,EAAE,gBAAgB;IACvB,kBAAkB,EAAE,gBAAgB;IACpC,cAAc,EAAE,kBAAkB;CACnC,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,yBAAyB,GAAiC,IAAI,GAAG,CAAC;IAC7E,UAAU;IACV,OAAO;IACP,oBAAoB;IACpB,gBAAgB;CACjB,CAAC,CAAC"}
package/lib/ui.d.ts CHANGED
@@ -27,4 +27,8 @@ export * from './scatter/ui/index.ts';
27
27
  export * from './scatter3d/ui/index.ts';
28
28
  export * from './share/ui/index.ts';
29
29
  export * from './shared/ui/index.ts';
30
+ export type { ConformerTableProps } from './structure/ui/ConformerTable.tsx';
31
+ export { ConformerTable } from './structure/ui/ConformerTable.tsx';
32
+ export type { ConformerColumn } from './structure/ui/conformerColumns.ts';
33
+ export { CONFORMER_COLUMN_LABELS, PICKER_COLUMNS, RANKING_COLUMNS, } from './structure/ui/conformerColumns.ts';
30
34
  //# sourceMappingURL=ui.d.ts.map
package/lib/ui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AAKrC,YAAY,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
package/lib/ui.js CHANGED
@@ -27,4 +27,6 @@ export * from "./scatter/ui/index.js";
27
27
  export * from "./scatter3d/ui/index.js";
28
28
  export * from "./share/ui/index.js";
29
29
  export * from "./shared/ui/index.js";
30
+ export { ConformerTable } from "./structure/ui/ConformerTable.js";
31
+ export { CONFORMER_COLUMN_LABELS, PICKER_COLUMNS, RANKING_COLUMNS, } from "./structure/ui/conformerColumns.js";
30
32
  //# sourceMappingURL=ui.js.map
package/lib/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AAMrC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAmC,CAAC;AAEnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,eAAe,GAChB,MAAM,oCAAoC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-cheminfo",
3
- "version": "0.29.0",
3
+ "version": "0.31.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",
@@ -0,0 +1,185 @@
1
+ import { Menu, MenuDivider, MenuItem, PopoverNext } from '@blueprintjs/core';
2
+ import type { ReactElement } from 'react';
3
+
4
+ import { useChromeT } from '../../i18n/ui/useT.ts';
5
+
6
+ import { NavLink } from './NavLink.tsx';
7
+ import type { NavItem } from './navItem.ts';
8
+ import { isModifiedClick } from './navItem.ts';
9
+
10
+ /** Who the site is answering to. */
11
+ export interface AccountIdentity {
12
+ /** What the person is called on screen. */
13
+ name: string;
14
+ /**
15
+ * What tells the account apart under that name — the address it is held at,
16
+ * the handle it was opened with.
17
+ * @default undefined
18
+ */
19
+ detail?: string;
20
+ /**
21
+ * The letters the mark is drawn with.
22
+ * @default the first letter of each of the first two words of `name`
23
+ */
24
+ initials?: string;
25
+ }
26
+
27
+ export interface AccountButtonProps {
28
+ /** Who is signed in, or `null` when nobody is. */
29
+ identity: AccountIdentity | null;
30
+ /**
31
+ * Whether the site has not heard back yet. Neither state is drawn until it
32
+ * has: a bar that offers to sign in and then turns into a name has told the
33
+ * visitor something false, and it is the state they act on first.
34
+ * @default false
35
+ */
36
+ loading?: boolean;
37
+ /**
38
+ * The address signing in happens at, for a site with a page of its own for
39
+ * it. Writing it keeps the entry a real link, so a middle click opens a tab.
40
+ * @default undefined
41
+ */
42
+ signInHref?: string;
43
+ /**
44
+ * What the site does when the invitation is picked — route to that page, or
45
+ * open a credentials dialog for a site that has no page.
46
+ * @default undefined
47
+ */
48
+ onSignIn?: () => void;
49
+ /**
50
+ * What the site does when Sign out is picked.
51
+ * @default undefined
52
+ */
53
+ onSignOut?: () => void;
54
+ /**
55
+ * What the invitation reads, for a site whose accounts are for one kind of
56
+ * person — "Teacher sign in". Two words: it is written out in the bar rather
57
+ * than folded into a glyph.
58
+ * @default the chrome's own word for it, in the language of the page
59
+ */
60
+ signInLabel?: string;
61
+ /**
62
+ * The account's own pages, listed above Sign out — a profile, a settings
63
+ * page.
64
+ * @default undefined
65
+ */
66
+ items?: readonly NavItem[];
67
+ }
68
+
69
+ /**
70
+ * The one utility of the bar that says who the site is answering to: an
71
+ * invitation when nobody is signed in, and the account's mark when somebody is.
72
+ *
73
+ * The two states are told apart by whether an identity is on screen, never by
74
+ * the direction of an arrow. Blueprint's `log-in` and `log-out` are the same
75
+ * arrow either side of the same door, and at 14 px among four other monochrome
76
+ * glyphs nobody reads which side it is on — a site drawing one reports the
77
+ * visitor as signed in while they are signed out. So the invitation is the
78
+ * words `Sign in`, which no mark can be mistaken for, and being signed in is
79
+ * the person's own initials.
80
+ * @param props - Who is signed in, what signing in and out do, and the pages
81
+ * the account's menu lists.
82
+ * @returns The entry, or nothing while the site is still asking.
83
+ */
84
+ export function AccountButton(props: AccountButtonProps): ReactElement | null {
85
+ const {
86
+ identity,
87
+ loading = false,
88
+ signInHref,
89
+ onSignIn,
90
+ onSignOut,
91
+ signInLabel,
92
+ items = [],
93
+ } = props;
94
+ const t = useChromeT();
95
+
96
+ if (loading) {
97
+ // The width of the mark, so the bar does not jump when the answer lands.
98
+ return <span className="account-button__pending" aria-hidden="true" />;
99
+ }
100
+
101
+ if (identity === null) {
102
+ return (
103
+ <NavLink
104
+ className="account-button account-button--out"
105
+ item={{
106
+ id: 'account',
107
+ label: signInLabel ?? t('account.signIn'),
108
+ href: signInHref,
109
+ onSelect: onSignIn,
110
+ }}
111
+ />
112
+ );
113
+ }
114
+
115
+ const { name, detail, initials = initialsOf(name) } = identity;
116
+
117
+ return (
118
+ <PopoverNext
119
+ placement="bottom-end"
120
+ content={
121
+ <Menu className="nav-menu account-menu">
122
+ <li className="account-menu__identity">
123
+ <span className="account-menu__name">{name}</span>
124
+ {detail === undefined ? null : (
125
+ <span className="account-menu__detail">{detail}</span>
126
+ )}
127
+ </li>
128
+ <MenuDivider />
129
+ {items.map((item) => (
130
+ <MenuItem
131
+ key={item.id}
132
+ icon={item.icon}
133
+ text={item.label}
134
+ href={item.href}
135
+ target={item.external ? '_blank' : undefined}
136
+ onClick={(event) => {
137
+ if (item.onSelect === undefined || isModifiedClick(event)) {
138
+ return;
139
+ }
140
+ event.preventDefault();
141
+ item.onSelect();
142
+ }}
143
+ />
144
+ ))}
145
+ {items.length === 0 ? null : <MenuDivider />}
146
+ <MenuItem
147
+ icon="log-out"
148
+ text={t('account.signOut')}
149
+ onClick={onSignOut}
150
+ />
151
+ </Menu>
152
+ }
153
+ >
154
+ <button
155
+ type="button"
156
+ className="nav-link nav-link--icon account-button account-button--in"
157
+ title={t('account.signedInAs', { name })}
158
+ aria-label={t('account.signedInAs', { name })}
159
+ >
160
+ <span className="account-mark">{initials}</span>
161
+ </button>
162
+ </PopoverNext>
163
+ );
164
+ }
165
+
166
+ /**
167
+ * The letters a name is marked with: the first of each of its first two words,
168
+ * or the first two letters of a name written as one word.
169
+ * @param name - What the person is called.
170
+ * @returns One or two letters, in capitals.
171
+ */
172
+ function initialsOf(name: string): string {
173
+ const [first, second] = name.split(/\s+/).filter((word) => word !== '');
174
+ if (first === undefined) return '?';
175
+ if (second === undefined) return firstWordInitials(first);
176
+ return `${first.charAt(0)}${second.charAt(0)}`.toUpperCase();
177
+ }
178
+
179
+ // A single word is usually a handle or an address, where the two letters say
180
+ // more than one does — but never the part after the `@`, which every account
181
+ // on the same domain would share.
182
+ function firstWordInitials(word: string): string {
183
+ const [local = ''] = word.split('@');
184
+ return (local === '' ? word : local).slice(0, 2).toUpperCase();
185
+ }
@@ -1,3 +1,5 @@
1
+ export type { AccountButtonProps, AccountIdentity } from './AccountButton.tsx';
2
+ export { AccountButton } from './AccountButton.tsx';
1
3
  export type { HeaderToggleOption, HeaderToggleProps } from './HeaderToggle.tsx';
2
4
  export { HeaderToggle } from './HeaderToggle.tsx';
3
5
  export type { NavItem } from './navItem.ts';
@@ -71,6 +71,12 @@ export interface ClickToCopyProps {
71
71
  * @default undefined
72
72
  */
73
73
  style?: CSSProperties;
74
+ /**
75
+ * `data-testid` the element carries, so an end-to-end suite can select the
76
+ * value without depending on the text inside it.
77
+ * @default undefined
78
+ */
79
+ testId?: string;
74
80
  }
75
81
 
76
82
  /** Past this length the value is left out of the title: a molfile is no tooltip. */
@@ -106,13 +112,14 @@ export function ClickToCopy(props: ClickToCopyProps): ReactElement {
106
112
  disabled = false,
107
113
  className,
108
114
  style,
115
+ testId,
109
116
  } = props;
110
117
  const t = useChromeT();
111
118
  const { copied, failed, copy } = useCopyToClipboard();
112
119
 
113
120
  if (disabled) {
114
121
  return (
115
- <Element className={className} style={style}>
122
+ <Element className={className} style={style} data-testid={testId}>
116
123
  {children}
117
124
  </Element>
118
125
  );
@@ -138,6 +145,7 @@ export function ClickToCopy(props: ClickToCopyProps): ReactElement {
138
145
  className,
139
146
  )}
140
147
  style={style}
148
+ data-testid={testId}
141
149
  role={focusable && !CELLS.has(Element) ? 'button' : undefined}
142
150
  tabIndex={focusable ? 0 : undefined}
143
151
  title={title ?? copyTitle(value, label, t)}