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.
Files changed (229) hide show
  1. package/README.md +69 -0
  2. package/lib/chart/core/chartDomain.d.ts +14 -0
  3. package/lib/chart/core/chartDomain.d.ts.map +1 -1
  4. package/lib/chart/core/chartDomain.js +1 -0
  5. package/lib/chart/core/chartDomain.js.map +1 -1
  6. package/lib/chart/core/selectionGeometry.d.ts +4 -2
  7. package/lib/chart/core/selectionGeometry.d.ts.map +1 -1
  8. package/lib/chart/core/selectionGeometry.js +4 -2
  9. package/lib/chart/core/selectionGeometry.js.map +1 -1
  10. package/lib/chart/core/zoomDomain.d.ts +11 -1
  11. package/lib/chart/core/zoomDomain.d.ts.map +1 -1
  12. package/lib/chart/core/zoomDomain.js +25 -5
  13. package/lib/chart/core/zoomDomain.js.map +1 -1
  14. package/lib/chart/ui/chartGestures.d.ts +26 -1
  15. package/lib/chart/ui/chartGestures.d.ts.map +1 -1
  16. package/lib/chart/ui/chartGestures.js +21 -0
  17. package/lib/chart/ui/chartGestures.js.map +1 -1
  18. package/lib/chart/ui/index.d.ts +1 -1
  19. package/lib/chart/ui/index.d.ts.map +1 -1
  20. package/lib/chart/ui/index.js +1 -1
  21. package/lib/chart/ui/index.js.map +1 -1
  22. package/lib/chart/ui/useChartZoom.d.ts.map +1 -1
  23. package/lib/chart/ui/useChartZoom.js +17 -1
  24. package/lib/chart/ui/useChartZoom.js.map +1 -1
  25. package/lib/core.d.ts +1 -0
  26. package/lib/core.d.ts.map +1 -1
  27. package/lib/core.js +1 -0
  28. package/lib/core.js.map +1 -1
  29. package/lib/download/core/cssPaint.d.ts +27 -0
  30. package/lib/download/core/cssPaint.d.ts.map +1 -0
  31. package/lib/download/core/cssPaint.js +63 -0
  32. package/lib/download/core/cssPaint.js.map +1 -0
  33. package/lib/download/core/drawingMarkup.d.ts +11 -0
  34. package/lib/download/core/drawingMarkup.d.ts.map +1 -0
  35. package/lib/download/core/drawingMarkup.js +32 -0
  36. package/lib/download/core/drawingMarkup.js.map +1 -0
  37. package/lib/download/core/figureHtml.d.ts +25 -0
  38. package/lib/download/core/figureHtml.d.ts.map +1 -0
  39. package/lib/download/core/figureHtml.js +174 -0
  40. package/lib/download/core/figureHtml.js.map +1 -0
  41. package/lib/download/core/figureHtmlMarkup.d.ts +107 -0
  42. package/lib/download/core/figureHtmlMarkup.d.ts.map +1 -0
  43. package/lib/download/core/figureHtmlMarkup.js +75 -0
  44. package/lib/download/core/figureHtmlMarkup.js.map +1 -0
  45. package/lib/download/core/figureSvg.d.ts.map +1 -1
  46. package/lib/download/core/figureSvg.js +16 -35
  47. package/lib/download/core/figureSvg.js.map +1 -1
  48. package/lib/download/core/figureSvgDocument.d.ts +3 -3
  49. package/lib/download/core/figureSvgDocument.d.ts.map +1 -1
  50. package/lib/download/core/figureTarget.d.ts +14 -3
  51. package/lib/download/core/figureTarget.d.ts.map +1 -1
  52. package/lib/download/core/figureTarget.js +42 -3
  53. package/lib/download/core/figureTarget.js.map +1 -1
  54. package/lib/download/core/index.d.ts +1 -1
  55. package/lib/download/core/index.d.ts.map +1 -1
  56. package/lib/download/core/index.js +1 -1
  57. package/lib/download/core/index.js.map +1 -1
  58. package/lib/download/core/textLines.d.ts +27 -0
  59. package/lib/download/core/textLines.d.ts.map +1 -0
  60. package/lib/download/core/textLines.js +51 -0
  61. package/lib/download/core/textLines.js.map +1 -0
  62. package/lib/ecosystem/core/lookup.d.ts +2 -1
  63. package/lib/ecosystem/core/lookup.d.ts.map +1 -1
  64. package/lib/ecosystem/core/lookup.js +10 -3
  65. package/lib/ecosystem/core/lookup.js.map +1 -1
  66. package/lib/ecosystem/core/types.d.ts +2 -2
  67. package/lib/ecosystem/core/types.d.ts.map +1 -1
  68. package/lib/ecosystem/core/unlistedSites.d.ts +9 -0
  69. package/lib/ecosystem/core/unlistedSites.d.ts.map +1 -0
  70. package/lib/ecosystem/core/unlistedSites.js +35 -0
  71. package/lib/ecosystem/core/unlistedSites.js.map +1 -0
  72. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  73. package/lib/ecosystem/ui/glyphs.js +7 -0
  74. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  75. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  76. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  77. package/lib/ir/core/irMode.d.ts.map +1 -1
  78. package/lib/ir/core/irMode.js +6 -1
  79. package/lib/ir/core/irMode.js.map +1 -1
  80. package/lib/locales/de.json +56 -1
  81. package/lib/locales/en.json +56 -1
  82. package/lib/locales/es.json +56 -1
  83. package/lib/locales/fr.json +56 -1
  84. package/lib/locales/it.json +56 -1
  85. package/lib/periodic/core/index.d.ts +1 -1
  86. package/lib/periodic/core/index.d.ts.map +1 -1
  87. package/lib/periodic/core/layout.d.ts +11 -0
  88. package/lib/periodic/core/layout.d.ts.map +1 -1
  89. package/lib/periodic/core/layout.js.map +1 -1
  90. package/lib/periodic/ui/ElementCell.d.ts +6 -2
  91. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  92. package/lib/periodic/ui/ElementCell.js +2 -2
  93. package/lib/periodic/ui/ElementCell.js.map +1 -1
  94. package/lib/periodic/ui/PeriodicTable.d.ts +3 -98
  95. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  96. package/lib/periodic/ui/PeriodicTable.js +9 -84
  97. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  98. package/lib/periodic/ui/PeriodicTableChrome.d.ts +13 -3
  99. package/lib/periodic/ui/PeriodicTableChrome.d.ts.map +1 -1
  100. package/lib/periodic/ui/PeriodicTableChrome.js +10 -20
  101. package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
  102. package/lib/periodic/ui/SelectAllCorner.d.ts +23 -0
  103. package/lib/periodic/ui/SelectAllCorner.d.ts.map +1 -0
  104. package/lib/periodic/ui/SelectAllCorner.js +40 -0
  105. package/lib/periodic/ui/SelectAllCorner.js.map +1 -0
  106. package/lib/periodic/ui/headerStyles.d.ts +17 -0
  107. package/lib/periodic/ui/headerStyles.d.ts.map +1 -0
  108. package/lib/periodic/ui/headerStyles.js +19 -0
  109. package/lib/periodic/ui/headerStyles.js.map +1 -0
  110. package/lib/periodic/ui/periodicTableProps.d.ts +115 -0
  111. package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -0
  112. package/lib/periodic/ui/periodicTableProps.js +8 -0
  113. package/lib/periodic/ui/periodicTableProps.js.map +1 -0
  114. package/lib/periodic/ui/tableLayout.d.ts +26 -0
  115. package/lib/periodic/ui/tableLayout.d.ts.map +1 -0
  116. package/lib/periodic/ui/tableLayout.js +86 -0
  117. package/lib/periodic/ui/tableLayout.js.map +1 -0
  118. package/lib/split/ui/SplitColumn.d.ts +29 -0
  119. package/lib/split/ui/SplitColumn.d.ts.map +1 -0
  120. package/lib/split/ui/SplitColumn.js +19 -0
  121. package/lib/split/ui/SplitColumn.js.map +1 -0
  122. package/lib/split/ui/SplitRow.d.ts +14 -53
  123. package/lib/split/ui/SplitRow.d.ts.map +1 -1
  124. package/lib/split/ui/SplitRow.js +8 -90
  125. package/lib/split/ui/SplitRow.js.map +1 -1
  126. package/lib/split/ui/index.d.ts +3 -0
  127. package/lib/split/ui/index.d.ts.map +1 -1
  128. package/lib/split/ui/index.js +1 -0
  129. package/lib/split/ui/index.js.map +1 -1
  130. package/lib/split/ui/splitPanes.d.ts +84 -0
  131. package/lib/split/ui/splitPanes.d.ts.map +1 -0
  132. package/lib/split/ui/splitPanes.js +125 -0
  133. package/lib/split/ui/splitPanes.js.map +1 -0
  134. package/lib/taxonomy/core/index.d.ts +8 -0
  135. package/lib/taxonomy/core/index.d.ts.map +1 -0
  136. package/lib/taxonomy/core/index.js +5 -0
  137. package/lib/taxonomy/core/index.js.map +1 -0
  138. package/lib/taxonomy/core/lineage.d.ts +83 -0
  139. package/lib/taxonomy/core/lineage.d.ts.map +1 -0
  140. package/lib/taxonomy/core/lineage.js +126 -0
  141. package/lib/taxonomy/core/lineage.js.map +1 -0
  142. package/lib/taxonomy/core/ncbi.d.ts +8 -0
  143. package/lib/taxonomy/core/ncbi.d.ts.map +1 -0
  144. package/lib/taxonomy/core/ncbi.js +10 -0
  145. package/lib/taxonomy/core/ncbi.js.map +1 -0
  146. package/lib/taxonomy/core/organismName.d.ts +28 -0
  147. package/lib/taxonomy/core/organismName.d.ts.map +1 -0
  148. package/lib/taxonomy/core/organismName.js +188 -0
  149. package/lib/taxonomy/core/organismName.js.map +1 -0
  150. package/lib/taxonomy/core/organismWords.d.ts +20 -0
  151. package/lib/taxonomy/core/organismWords.d.ts.map +1 -0
  152. package/lib/taxonomy/core/organismWords.js +86 -0
  153. package/lib/taxonomy/core/organismWords.js.map +1 -0
  154. package/lib/taxonomy/core/ranks.d.ts +51 -0
  155. package/lib/taxonomy/core/ranks.d.ts.map +1 -0
  156. package/lib/taxonomy/core/ranks.js +123 -0
  157. package/lib/taxonomy/core/ranks.js.map +1 -0
  158. package/lib/taxonomy/ui/OrganismName.d.ts +28 -0
  159. package/lib/taxonomy/ui/OrganismName.d.ts.map +1 -0
  160. package/lib/taxonomy/ui/OrganismName.js +28 -0
  161. package/lib/taxonomy/ui/OrganismName.js.map +1 -0
  162. package/lib/taxonomy/ui/TaxonLineage.d.ts +67 -0
  163. package/lib/taxonomy/ui/TaxonLineage.d.ts.map +1 -0
  164. package/lib/taxonomy/ui/TaxonLineage.js +45 -0
  165. package/lib/taxonomy/ui/TaxonLineage.js.map +1 -0
  166. package/lib/taxonomy/ui/index.d.ts +6 -0
  167. package/lib/taxonomy/ui/index.d.ts.map +1 -0
  168. package/lib/taxonomy/ui/index.js +4 -0
  169. package/lib/taxonomy/ui/index.js.map +1 -0
  170. package/lib/taxonomy/ui/rankLabel.d.ts +17 -0
  171. package/lib/taxonomy/ui/rankLabel.d.ts.map +1 -0
  172. package/lib/taxonomy/ui/rankLabel.js +77 -0
  173. package/lib/taxonomy/ui/rankLabel.js.map +1 -0
  174. package/lib/ui.d.ts +1 -0
  175. package/lib/ui.d.ts.map +1 -1
  176. package/lib/ui.js +1 -0
  177. package/lib/ui.js.map +1 -1
  178. package/package.json +1 -1
  179. package/src/chart/core/chartDomain.ts +15 -0
  180. package/src/chart/core/selectionGeometry.ts +4 -2
  181. package/src/chart/core/zoomDomain.ts +37 -5
  182. package/src/chart/ui/chartGestures.ts +41 -1
  183. package/src/chart/ui/index.ts +1 -1
  184. package/src/chart/ui/useChartZoom.ts +25 -1
  185. package/src/core.ts +1 -0
  186. package/src/download/core/cssPaint.ts +71 -0
  187. package/src/download/core/drawingMarkup.ts +35 -0
  188. package/src/download/core/figureHtml.ts +214 -0
  189. package/src/download/core/figureHtmlMarkup.ts +187 -0
  190. package/src/download/core/figureSvg.ts +17 -38
  191. package/src/download/core/figureSvgDocument.ts +3 -3
  192. package/src/download/core/figureTarget.ts +41 -8
  193. package/src/download/core/index.ts +1 -0
  194. package/src/download/core/textLines.ts +67 -0
  195. package/src/ecosystem/core/lookup.ts +11 -3
  196. package/src/ecosystem/core/types.ts +4 -2
  197. package/src/ecosystem/core/unlistedSites.ts +37 -0
  198. package/src/ecosystem/ui/glyphs.tsx +44 -0
  199. package/src/ir/core/irMode.ts +6 -1
  200. package/src/locales/de.json +56 -1
  201. package/src/locales/en.json +56 -1
  202. package/src/locales/es.json +56 -1
  203. package/src/locales/fr.json +56 -1
  204. package/src/locales/it.json +56 -1
  205. package/src/periodic/core/index.ts +1 -1
  206. package/src/periodic/core/layout.ts +12 -0
  207. package/src/periodic/ui/ElementCell.tsx +8 -4
  208. package/src/periodic/ui/PeriodicTable.tsx +27 -197
  209. package/src/periodic/ui/PeriodicTableChrome.tsx +23 -20
  210. package/src/periodic/ui/SelectAllCorner.tsx +65 -0
  211. package/src/periodic/ui/headerStyles.ts +23 -0
  212. package/src/periodic/ui/periodicTableProps.ts +117 -0
  213. package/src/periodic/ui/tableLayout.ts +96 -0
  214. package/src/split/ui/SplitColumn.tsx +35 -0
  215. package/src/split/ui/SplitRow.tsx +16 -208
  216. package/src/split/ui/index.ts +3 -0
  217. package/src/split/ui/splitPanes.tsx +283 -0
  218. package/src/taxonomy/core/index.ts +20 -0
  219. package/src/taxonomy/core/lineage.ts +161 -0
  220. package/src/taxonomy/core/ncbi.ts +9 -0
  221. package/src/taxonomy/core/organismName.ts +217 -0
  222. package/src/taxonomy/core/organismWords.ts +86 -0
  223. package/src/taxonomy/core/ranks.ts +134 -0
  224. package/src/taxonomy/ui/OrganismName.tsx +59 -0
  225. package/src/taxonomy/ui/TaxonLineage.tsx +183 -0
  226. package/src/taxonomy/ui/index.ts +5 -0
  227. package/src/taxonomy/ui/rankLabel.ts +81 -0
  228. package/src/ui.ts +1 -0
  229. package/styles/chrome.css +135 -6
@@ -10,12 +10,7 @@
10
10
  * a site's own richer element record never has to cross into this component.
11
11
  */
12
12
 
13
- import type {
14
- CSSProperties,
15
- KeyboardEvent,
16
- ReactElement,
17
- ReactNode,
18
- } from 'react';
13
+ import type { KeyboardEvent, ReactElement } from 'react';
19
14
  import { useEffect, useRef } from 'react';
20
15
 
21
16
  import type { Swatch } from '../../color/core/interpolate.ts';
@@ -23,13 +18,8 @@ import { useResizeObserver } from '../../hooks/ui/useResizeObserver.ts';
23
18
  import { useChromeT } from '../../i18n/ui/useT.ts';
24
19
  import { categorySwatch } from '../core/categories.ts';
25
20
  import type { PeriodicElement } from '../core/elements.ts';
26
- import type { ElementRange } from '../core/layout.ts';
27
- import {
28
- COLUMN_COUNT,
29
- EMPTY_BLOCK,
30
- elementByArrowKey,
31
- placedElements,
32
- } from '../core/layout.ts';
21
+ import type { ElementPick } from '../core/layout.ts';
22
+ import { elementByArrowKey, placedElements } from '../core/layout.ts';
33
23
 
34
24
  import { CategoryLegend } from './CategoryLegend.tsx';
35
25
  import { ElementCell } from './ElementCell.tsx';
@@ -38,108 +28,16 @@ import {
38
28
  InnerTransitionMarkers,
39
29
  } from './PeriodicTableChrome.tsx';
40
30
  import {
41
- ROW_SHARE,
42
31
  SHELLS_RESERVED,
43
32
  detailType,
44
33
  shellFontSize,
45
34
  textWidthInEm,
46
35
  } from './cellType.ts';
47
- import { UNIT_PROPERTY, ofWidth, unitValue } from './unit.ts';
36
+ import type { PeriodicTableProps } from './periodicTableProps.ts';
37
+ import { gridStyle, insetStyle, rootStyle } from './tableLayout.ts';
38
+ import { UNIT_PROPERTY, unitValue } from './unit.ts';
48
39
 
49
- /** What {@link PeriodicTable} needs. */
50
- export interface PeriodicTableProps {
51
- /**
52
- * Symbol of the element the tools are pointed at.
53
- * @default undefined — none is
54
- */
55
- selected?: string;
56
- /**
57
- * Called with the symbol of the element that was clicked, and by the arrow
58
- * keys.
59
- * @default undefined — the table is a figure rather than a control
60
- */
61
- onSelect?: (symbol: string) => void;
62
- /**
63
- * The colour each cell takes.
64
- * @default the family colour
65
- */
66
- swatchOf?: (element: PeriodicElement) => Swatch;
67
- /**
68
- * What is written under the symbol; an empty string writes nothing.
69
- * @default nothing is written
70
- */
71
- detailOf?: (element: PeriodicElement) => string;
72
- /**
73
- * How an element is named, for the label a screen reader reads. The hook a
74
- * site translating the table writes its own names through.
75
- * @default the English name
76
- */
77
- nameOf?: (element: PeriodicElement) => string;
78
- /**
79
- * Electrons in each shell of an element, innermost first, written down the
80
- * right-hand edge of its cell. The energy levels, which a table is also read
81
- * for: none of ours knows them, so the tool that does says so here.
82
- * @default undefined — no cell writes any
83
- */
84
- shellsOf?: (element: PeriodicElement) => readonly number[] | undefined;
85
- /**
86
- * Whether an element is inside the set the tool is showing. Everything
87
- * outside it is dimmed rather than removed, so the table keeps its shape.
88
- * @default every element is
89
- */
90
- isIncluded?: (element: PeriodicElement) => boolean;
91
- /**
92
- * What is written in the block the table leaves empty — columns 3 to 12 of
93
- * the first three periods, in the middle of the top edge. A tool that reads
94
- * one element off the table puts what it says about it there, where the eye
95
- * already is, rather than under the grid.
96
- *
97
- * It is sized against the table, like everything else in the grid, so give
98
- * it type in `em` and it scales with the drawing.
99
- * @default undefined — the block stays empty
100
- */
101
- inset?: ReactNode;
102
- /**
103
- * Whether to draw the group and period strips.
104
- * @default false
105
- */
106
- headers?: boolean;
107
- /**
108
- * Called when a whole group or period header is clicked. The run says
109
- * whether the click was additive — Cmd or Ctrl held — so a tool can let a
110
- * reader put two periods on one chart. Without it the strips are labels
111
- * rather than buttons.
112
- * @default undefined
113
- */
114
- onSelectRange?: (range: ElementRange) => void;
115
- /**
116
- * Whether to draw the family legend under the grid.
117
- * @default false
118
- */
119
- legend?: boolean;
120
- /**
121
- * Whether the dashed markers stand where the two inner-transition series were
122
- * lifted out of the main block.
123
- * @default true
124
- */
125
- markers?: boolean;
126
- /**
127
- * Whether the arrow keys walk the grid: down from carbon is silicon, and
128
- * right from the end of a period is the start of the next.
129
- * @default true
130
- */
131
- keyboard?: boolean;
132
- /**
133
- * Called on pointer enter with the symbol, and on leave with `null`.
134
- * @default undefined
135
- */
136
- onHover?: (symbol: string | null) => void;
137
- /**
138
- * Class of the outermost element.
139
- * @default undefined
140
- */
141
- className?: string;
142
- }
40
+ export type { PeriodicTableProps } from './periodicTableProps.ts';
143
41
 
144
42
  /**
145
43
  * The 118 elements, laid out as the table.
@@ -158,6 +56,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
158
56
  inset,
159
57
  headers = false,
160
58
  onSelectRange,
59
+ onSelectAll,
161
60
  legend = false,
162
61
  markers = true,
163
62
  keyboard = true,
@@ -198,7 +97,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
198
97
  if (next === null) return;
199
98
  event.preventDefault();
200
99
  cameFromKeyRef.current = true;
201
- onSelect(next.symbol);
100
+ onSelect(next.symbol, PLAIN_PICK);
202
101
  }
203
102
 
204
103
  // One size for the whole table rather than one per cell: a column of values
@@ -219,7 +118,15 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
219
118
  const shellSize = shellFontSize(deepest);
220
119
 
221
120
  return (
222
- <div ref={rootRef} className={className} style={rootStyle}>
121
+ <div
122
+ ref={rootRef}
123
+ className={className}
124
+ // Drawn in HTML so every cell is a button, and saved by being painted
125
+ // from where the browser put it: a site saves the table by pointing
126
+ // `FigureDownload` at any box around it.
127
+ data-figure="html"
128
+ style={rootStyle}
129
+ >
223
130
  <div
224
131
  ref={gridRef}
225
132
  role="grid"
@@ -228,7 +135,12 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
228
135
  style={gridStyle(headers)}
229
136
  onKeyDown={handleKeyDown}
230
137
  >
231
- {headers ? <HeaderStrips onSelectRange={onSelectRange} /> : null}
138
+ {headers ? (
139
+ <HeaderStrips
140
+ onSelectRange={onSelectRange}
141
+ onSelectAll={onSelectAll}
142
+ />
143
+ ) : null}
232
144
  {inset === undefined ? null : (
233
145
  <div style={insetStyle(offset)}>{inset}</div>
234
146
  )}
@@ -258,30 +170,6 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
258
170
  );
259
171
  }
260
172
 
261
- /**
262
- * Where what the caller writes in the empty block is placed.
263
- * @param offset - 1 when the table draws its header strips, 0 otherwise.
264
- * @returns The style of the slot.
265
- */
266
- function insetStyle(offset: number): CSSProperties {
267
- return {
268
- alignItems: 'center',
269
- display: 'flex',
270
- // A column of air on each side, so the writing reads as sitting in the
271
- // block rather than as running into the cells beside it.
272
- padding: `0 ${ofWidth(2.4)}`,
273
- gridColumn: `${String(EMPTY_BLOCK.column + offset)} / span ${String(EMPTY_BLOCK.columnSpan)}`,
274
- gridRow: `${String(EMPTY_BLOCK.row + offset)} / span ${String(EMPTY_BLOCK.rowSpan)}`,
275
- // A share of the table, with a floor, exactly as a cell sizes its symbol:
276
- // the block holds the same three rows at every width, so what is written
277
- // in it has to shrink with them.
278
- fontSize: `max(0.6rem, ${ofWidth(1.8)})`,
279
- lineHeight: 1.35,
280
- minWidth: 0,
281
- overflow: 'hidden',
282
- };
283
- }
284
-
285
173
  function defaultSwatchOf(element: PeriodicElement): Swatch {
286
174
  return categorySwatch(element.category);
287
175
  }
@@ -290,68 +178,10 @@ function defaultNameOf(element: PeriodicElement): string {
290
178
  return element.name;
291
179
  }
292
180
 
181
+ /** The arrow keys walk the table to read it; they never add to a set. */
182
+ const PLAIN_PICK: ElementPick = { additive: false };
183
+
293
184
  function noop(): void {
294
185
  // A table with no `onSelect` is a figure; its cells stay buttons so the
295
186
  // keyboard and a screen reader still reach every element.
296
187
  }
297
-
298
- /** Narrowest the table is ever drawn; under it the type stops being readable. */
299
- const MIN_WIDTH = 280;
300
-
301
- /**
302
- * How tall one row of elements is, as a share of the table's width.
303
- *
304
- * A column is about 5.2% of that width, so a cell is a third taller than it is
305
- * wide — the proportion of a wall chart, and the room the three bands of a
306
- * cell need to be read from the back of a room. It is the same height in every
307
- * table: a cell keeps the band it writes a value in whether or not that table
308
- * writes one.
309
- */
310
- const ROW_HEIGHT = ofWidth(ROW_SHARE);
311
-
312
- /** The band the two inner-transition series were lifted out across. */
313
- const SERIES_GAP = `max(6px, ${ofWidth(1.1)})`;
314
-
315
- const rootStyle = {
316
- display: 'flex',
317
- flexDirection: 'column',
318
- gap: 8,
319
- minWidth: MIN_WIDTH,
320
- // Everything inside — the height of a row as much as the type in a cell — is
321
- // a share of this box rather than of the page, so the same table reads at
322
- // 280px beside a chart and fills a lecture-hall screen at twice that. The
323
- // share is measured (see `unit.ts`); the container is what the `1cqw`
324
- // fallback and a site's own `@container` rules are answered by, and it keeps
325
- // this box's width independent of what is written inside it.
326
- containerType: 'inline-size',
327
- } as const satisfies CSSProperties;
328
-
329
- const baseGridStyle = {
330
- display: 'grid',
331
- gap: 2,
332
- minWidth: MIN_WIDTH,
333
- width: '100%',
334
- } as const satisfies CSSProperties;
335
-
336
- /**
337
- * The grid the cells are placed on.
338
- * @param headers - Whether a leading column and row hold the period and group
339
- * numbers.
340
- * @returns The style of the grid.
341
- */
342
- function gridStyle(headers: boolean): CSSProperties {
343
- // The eighth row is the gap the inner-transition series are lifted out into.
344
- const rows = `repeat(7, ${ROW_HEIGHT}) ${SERIES_GAP} repeat(2, ${ROW_HEIGHT})`;
345
- if (!headers) {
346
- return {
347
- ...baseGridStyle,
348
- gridTemplateColumns: `repeat(${String(COLUMN_COUNT)}, minmax(0, 1fr))`,
349
- gridTemplateRows: rows,
350
- };
351
- }
352
- return {
353
- ...baseGridStyle,
354
- gridTemplateColumns: `max(14px, ${ofWidth(2.4)}) repeat(${String(COLUMN_COUNT)}, minmax(0, 1fr))`,
355
- gridTemplateRows: `max(12px, ${ofWidth(2)}) ${rows}`,
356
- };
357
- }
@@ -14,6 +14,8 @@ import {
14
14
  INNER_TRANSITION_ROWS,
15
15
  } from '../core/layout.ts';
16
16
 
17
+ import { SelectAllCorner } from './SelectAllCorner.tsx';
18
+ import { headerButtonStyle, headerStyle } from './headerStyles.ts';
17
19
  import { ofWidth } from './unit.ts';
18
20
 
19
21
  /** What {@link HeaderStrips} needs. */
@@ -24,18 +26,28 @@ interface HeaderStripsProps {
24
26
  * @default undefined
25
27
  */
26
28
  onSelectRange?: (range: ElementRange) => void;
29
+ /**
30
+ * Called when the corner where the two strips meet is clicked. The corner of
31
+ * a spreadsheet takes everything, and a table whose rows and columns are one
32
+ * click each owes the same for the whole of it. Without it the corner stays
33
+ * empty.
34
+ * @default undefined
35
+ */
36
+ onSelectAll?: () => void;
27
37
  }
28
38
 
29
39
  /**
30
- * The 1–18 strip along the top and the 1–7 strip down the left.
40
+ * The 1–18 strip along the top, the 1–7 strip down the left, and the corner
41
+ * where they meet.
31
42
  *
32
43
  * They are buttons when the table takes a range: "plot period 3" is one click
33
- * there and eight on the cells.
44
+ * there and eight on the cells. The corner takes the whole table, which is the
45
+ * gesture a spreadsheet has taught everybody.
34
46
  * @param props - See {@link HeaderStripsProps}.
35
- * @returns The two strips, placed on the grid.
47
+ * @returns The two strips, the corner, and the inner-transition period numbers.
36
48
  */
37
49
  export function HeaderStrips(props: HeaderStripsProps): ReactElement {
38
- const { onSelectRange } = props;
50
+ const { onSelectRange, onSelectAll } = props;
39
51
  const t = useChromeT();
40
52
  const groups: ReactElement[] = [];
41
53
  for (let group = 1; group <= COLUMN_COUNT; group++) {
@@ -84,6 +96,12 @@ export function HeaderStrips(props: HeaderStripsProps): ReactElement {
84
96
 
85
97
  return (
86
98
  <>
99
+ {onSelectAll === undefined ? null : (
100
+ <SelectAllCorner
101
+ title={t('periodic.allElements')}
102
+ onClick={onSelectAll}
103
+ />
104
+ )}
87
105
  {groups}
88
106
  {periods}
89
107
  {INNER_TRANSITION_ROWS.map(({ row, period }) => (
@@ -159,6 +177,7 @@ function HeaderCell(props: HeaderCellProps): ReactElement {
159
177
  <button
160
178
  type="button"
161
179
  aria-label={title}
180
+ title={title}
162
181
  onClick={onClick}
163
182
  style={{ ...style, ...headerButtonStyle }}
164
183
  >
@@ -167,22 +186,6 @@ function HeaderCell(props: HeaderCellProps): ReactElement {
167
186
  );
168
187
  }
169
188
 
170
- const headerStyle = {
171
- alignItems: 'center',
172
- color: TOKEN.textMuted,
173
- display: 'flex',
174
- fontSize: `max(0.45rem, ${ofWidth(1.35)})`,
175
- justifyContent: 'center',
176
- padding: 0,
177
- } as const satisfies CSSProperties;
178
-
179
- const headerButtonStyle = {
180
- background: 'none',
181
- border: 'none',
182
- cursor: 'pointer',
183
- font: 'inherit',
184
- } as const satisfies CSSProperties;
185
-
186
189
  const markerStyle = {
187
190
  alignItems: 'center',
188
191
  border: `1px dashed ${TOKEN.borderStrong}`,
@@ -0,0 +1,65 @@
1
+ import type { FocusEvent, ReactElement } from 'react';
2
+ import { useState } from 'react';
3
+
4
+ import { headerButtonStyle, headerStyle } from './headerStyles.ts';
5
+
6
+ /** What {@link SelectAllCorner} needs. */
7
+ interface SelectAllCornerProps {
8
+ /** What it does, for the pointer and for a screen reader. */
9
+ title: string;
10
+ /** Called when it is clicked. */
11
+ onClick: () => void;
12
+ }
13
+
14
+ /**
15
+ * The corner where the two header strips meet, which takes the whole table.
16
+ *
17
+ * It is a control and nothing else, so it stays out of sight until it is
18
+ * pointed at or reached from the keyboard, and it is left off a print and out
19
+ * of a saved figure. The glyph points into the grid it takes, as a
20
+ * spreadsheet's does; it is a mark rather than a word because the corner is a
21
+ * seventeenth of the table's width, and the word differs in every language the
22
+ * chrome speaks.
23
+ * @param props - See {@link SelectAllCornerProps}.
24
+ * @returns The corner.
25
+ */
26
+ export function SelectAllCorner(props: SelectAllCornerProps): ReactElement {
27
+ const { title, onClick } = props;
28
+ const [pointed, setPointed] = useState(false);
29
+ const [focused, setFocused] = useState(false);
30
+
31
+ return (
32
+ <button
33
+ type="button"
34
+ aria-label={title}
35
+ title={title}
36
+ onClick={onClick}
37
+ style={{
38
+ ...headerStyle,
39
+ gridColumn: 1,
40
+ gridRow: 1,
41
+ ...headerButtonStyle,
42
+ opacity: pointed || focused ? 1 : 0,
43
+ transition: 'opacity 120ms ease',
44
+ }}
45
+ className="no-print"
46
+ data-figure="chrome"
47
+ onPointerEnter={() => {
48
+ setPointed(true);
49
+ }}
50
+ onPointerLeave={() => {
51
+ setPointed(false);
52
+ }}
53
+ // Only a focus the keyboard gave it: a click focuses it too, and the
54
+ // glyph would otherwise stay up after the pointer has gone.
55
+ onFocus={(event: FocusEvent<HTMLButtonElement>) => {
56
+ setFocused(event.currentTarget.matches(':focus-visible'));
57
+ }}
58
+ onBlur={() => {
59
+ setFocused(false);
60
+ }}
61
+ >
62
+ ◢
63
+ </button>
64
+ );
65
+ }
@@ -0,0 +1,23 @@
1
+ import type { CSSProperties } from 'react';
2
+
3
+ import { TOKEN } from '../../tokens/core/familyTokens.ts';
4
+
5
+ import { ofWidth } from './unit.ts';
6
+
7
+ /** A group or period number, and the corner where the two strips meet. */
8
+ export const headerStyle = {
9
+ alignItems: 'center',
10
+ color: TOKEN.textMuted,
11
+ display: 'flex',
12
+ fontSize: `max(0.45rem, ${ofWidth(1.35)})`,
13
+ justifyContent: 'center',
14
+ padding: 0,
15
+ } as const satisfies CSSProperties;
16
+
17
+ /** The same, when it is a button: the type and the ground stay the strip's. */
18
+ export const headerButtonStyle = {
19
+ background: 'none',
20
+ border: 'none',
21
+ cursor: 'pointer',
22
+ font: 'inherit',
23
+ } as const satisfies CSSProperties;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * What a caller hands a `PeriodicTable`.
3
+ *
4
+ * The props live beside the component rather than inside it, so a reader
5
+ * looking for what the table does does not scroll past a page of options.
6
+ */
7
+
8
+ import type { ReactNode } from 'react';
9
+
10
+ import type { Swatch } from '../../color/core/interpolate.ts';
11
+ import type { PeriodicElement } from '../core/elements.ts';
12
+ import type { ElementPick, ElementRange } from '../core/layout.ts';
13
+
14
+ /** What `PeriodicTable` needs. */
15
+ export interface PeriodicTableProps {
16
+ /**
17
+ * Symbol of the element the tools are pointed at.
18
+ * @default undefined — none is
19
+ */
20
+ selected?: string;
21
+ /**
22
+ * Called with the symbol of the element that was clicked, and by the arrow
23
+ * keys. The pick says whether the click was additive — Cmd or Ctrl held — so
24
+ * a tool whose plain click already reads one element into a card can let the
25
+ * same table build a set of them.
26
+ * @default undefined — the table is a figure rather than a control
27
+ */
28
+ onSelect?: (symbol: string, pick: ElementPick) => void;
29
+ /**
30
+ * The colour each cell takes.
31
+ * @default the family colour
32
+ */
33
+ swatchOf?: (element: PeriodicElement) => Swatch;
34
+ /**
35
+ * What is written under the symbol; an empty string writes nothing.
36
+ * @default nothing is written
37
+ */
38
+ detailOf?: (element: PeriodicElement) => string;
39
+ /**
40
+ * How an element is named, for the label a screen reader reads. The hook a
41
+ * site translating the table writes its own names through.
42
+ * @default the English name
43
+ */
44
+ nameOf?: (element: PeriodicElement) => string;
45
+ /**
46
+ * Electrons in each shell of an element, innermost first, written down the
47
+ * right-hand edge of its cell. The energy levels, which a table is also read
48
+ * for: none of ours knows them, so the tool that does says so here.
49
+ * @default undefined — no cell writes any
50
+ */
51
+ shellsOf?: (element: PeriodicElement) => readonly number[] | undefined;
52
+ /**
53
+ * Whether an element is inside the set the tool is showing. Everything
54
+ * outside it is dimmed rather than removed, so the table keeps its shape.
55
+ * @default every element is
56
+ */
57
+ isIncluded?: (element: PeriodicElement) => boolean;
58
+ /**
59
+ * What is written in the block the table leaves empty — columns 3 to 12 of
60
+ * the first three periods, in the middle of the top edge. A tool that reads
61
+ * one element off the table puts what it says about it there, where the eye
62
+ * already is, rather than under the grid.
63
+ *
64
+ * It is sized against the table, like everything else in the grid, so give
65
+ * it type in `em` and it scales with the drawing.
66
+ * @default undefined — the block stays empty
67
+ */
68
+ inset?: ReactNode;
69
+ /**
70
+ * Whether to draw the group and period strips.
71
+ * @default false
72
+ */
73
+ headers?: boolean;
74
+ /**
75
+ * Called when a whole group or period header is clicked. The run says
76
+ * whether the click was additive — Cmd or Ctrl held — so a tool can let a
77
+ * reader put two periods on one chart. Without it the strips are labels
78
+ * rather than buttons.
79
+ * @default undefined
80
+ */
81
+ onSelectRange?: (range: ElementRange) => void;
82
+ /**
83
+ * Called when the corner where the two header strips meet is clicked, which
84
+ * is how a reader takes the whole table back. The corner shows its glyph only
85
+ * while it is pointed at or reached from the keyboard. Without it the corner
86
+ * stays empty.
87
+ * @default undefined
88
+ */
89
+ onSelectAll?: () => void;
90
+ /**
91
+ * Whether to draw the family legend under the grid.
92
+ * @default false
93
+ */
94
+ legend?: boolean;
95
+ /**
96
+ * Whether the dashed markers stand where the two inner-transition series were
97
+ * lifted out of the main block.
98
+ * @default true
99
+ */
100
+ markers?: boolean;
101
+ /**
102
+ * Whether the arrow keys walk the grid: down from carbon is silicon, and
103
+ * right from the end of a period is the start of the next.
104
+ * @default true
105
+ */
106
+ keyboard?: boolean;
107
+ /**
108
+ * Called on pointer enter with the symbol, and on leave with `null`.
109
+ * @default undefined
110
+ */
111
+ onHover?: (symbol: string | null) => void;
112
+ /**
113
+ * Class of the outermost element.
114
+ * @default undefined
115
+ */
116
+ className?: string;
117
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * How the table is laid out: the box it measures, the grid its cells sit on,
3
+ * and the block a caller may write in.
4
+ */
5
+
6
+ import type { CSSProperties } from 'react';
7
+
8
+ import { COLUMN_COUNT, EMPTY_BLOCK } from '../core/layout.ts';
9
+
10
+ import { ROW_SHARE } from './cellType.ts';
11
+ import { ofWidth } from './unit.ts';
12
+
13
+ /**
14
+ * Where what the caller writes in the empty block is placed.
15
+ * @param offset - 1 when the table draws its header strips, 0 otherwise.
16
+ * @returns The style of the slot.
17
+ */
18
+ export function insetStyle(offset: number): CSSProperties {
19
+ return {
20
+ alignItems: 'center',
21
+ display: 'flex',
22
+ // A column of air on each side, so the writing reads as sitting in the
23
+ // block rather than as running into the cells beside it.
24
+ padding: `0 ${ofWidth(2.4)}`,
25
+ gridColumn: `${String(EMPTY_BLOCK.column + offset)} / span ${String(EMPTY_BLOCK.columnSpan)}`,
26
+ gridRow: `${String(EMPTY_BLOCK.row + offset)} / span ${String(EMPTY_BLOCK.rowSpan)}`,
27
+ // A share of the table, with a floor, exactly as a cell sizes its symbol:
28
+ // the block holds the same three rows at every width, so what is written
29
+ // in it has to shrink with them.
30
+ fontSize: `max(0.6rem, ${ofWidth(1.8)})`,
31
+ lineHeight: 1.35,
32
+ minWidth: 0,
33
+ overflow: 'hidden',
34
+ };
35
+ }
36
+
37
+ /** Narrowest the table is ever drawn; under it the type stops being readable. */
38
+ const MIN_WIDTH = 280;
39
+
40
+ /**
41
+ * How tall one row of elements is, as a share of the table's width.
42
+ *
43
+ * A column is about 5.2% of that width, so a cell is a third taller than it is
44
+ * wide — the proportion of a wall chart, and the room the three bands of a
45
+ * cell need to be read from the back of a room. It is the same height in every
46
+ * table: a cell keeps the band it writes a value in whether or not that table
47
+ * writes one.
48
+ */
49
+ const ROW_HEIGHT = ofWidth(ROW_SHARE);
50
+
51
+ /** The band the two inner-transition series were lifted out across. */
52
+ const SERIES_GAP = `max(6px, ${ofWidth(1.1)})`;
53
+
54
+ export const rootStyle = {
55
+ display: 'flex',
56
+ flexDirection: 'column',
57
+ gap: 8,
58
+ minWidth: MIN_WIDTH,
59
+ // Everything inside — the height of a row as much as the type in a cell — is
60
+ // a share of this box rather than of the page, so the same table reads at
61
+ // 280px beside a chart and fills a lecture-hall screen at twice that. The
62
+ // share is measured (see `unit.ts`); the container is what the `1cqw`
63
+ // fallback and a site's own `@container` rules are answered by, and it keeps
64
+ // this box's width independent of what is written inside it.
65
+ containerType: 'inline-size',
66
+ } as const satisfies CSSProperties;
67
+
68
+ const baseGridStyle = {
69
+ display: 'grid',
70
+ gap: 2,
71
+ minWidth: MIN_WIDTH,
72
+ width: '100%',
73
+ } as const satisfies CSSProperties;
74
+
75
+ /**
76
+ * The grid the cells are placed on.
77
+ * @param headers - Whether a leading column and row hold the period and group
78
+ * numbers.
79
+ * @returns The style of the grid.
80
+ */
81
+ export function gridStyle(headers: boolean): CSSProperties {
82
+ // The eighth row is the gap the inner-transition series are lifted out into.
83
+ const rows = `repeat(7, ${ROW_HEIGHT}) ${SERIES_GAP} repeat(2, ${ROW_HEIGHT})`;
84
+ if (!headers) {
85
+ return {
86
+ ...baseGridStyle,
87
+ gridTemplateColumns: `repeat(${String(COLUMN_COUNT)}, minmax(0, 1fr))`,
88
+ gridTemplateRows: rows,
89
+ };
90
+ }
91
+ return {
92
+ ...baseGridStyle,
93
+ gridTemplateColumns: `max(14px, ${ofWidth(2.4)}) repeat(${String(COLUMN_COUNT)}, minmax(0, 1fr))`,
94
+ gridTemplateRows: `max(12px, ${ofWidth(2)}) ${rows}`,
95
+ };
96
+ }