@czi-sds/data-viz 2.4.1 → 2.4.3

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.
@@ -1052,8 +1052,8 @@ const HOVER_THROTTLE_MS = 3 * 16.666;
1052
1052
  const COPIED_FEEDBACK_MS = 2e3;
1053
1053
  //#endregion
1054
1054
  //#region src/core/ProteinStructureViewer/components/SequenceView/style.ts
1055
- /** Height of the fade that masks residues scrolling under the panel header. */
1056
- const SCROLL_FADE_HEIGHT = 8;
1055
+ /** Height of the fades that mask residues scrolling past the panel's edges. */
1056
+ const SCROLL_FADE_HEIGHT = 10;
1057
1057
  const SequencePanel = (0, _emotion_styled.default)("div")`
1058
1058
  display: flex;
1059
1059
  flex-direction: column;
@@ -1167,8 +1167,9 @@ const ResidueCountValue = (0, _emotion_styled.default)("span")`
1167
1167
  `;
1168
1168
  /**
1169
1169
  * Scroll region for the residue grid. The `::before` fade masks residues as
1170
- * they scroll up under the header, so it has to start from whatever the panel
1171
- * is painted with or it leaves a mismatched strip.
1170
+ * they scroll up under the header and the `::after` fade masks them running off
1171
+ * the panel's bottom edge, so both have to start from whatever the panel is
1172
+ * painted with or they leave a mismatched strip.
1172
1173
  */
1173
1174
  const SequenceScroller = (0, _emotion_styled.default)("div")`
1174
1175
  position: relative;
@@ -1178,16 +1179,26 @@ const SequenceScroller = (0, _emotion_styled.default)("div")`
1178
1179
  ${(props) => {
1179
1180
  var _props$backgroundColo3, _semanticColors$base10;
1180
1181
  const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
1182
+ const surface = (_props$backgroundColo3 = props.backgroundColor) !== null && _props$backgroundColo3 !== void 0 ? _props$backgroundColo3 : semanticColors === null || semanticColors === void 0 || (_semanticColors$base10 = semanticColors.base) === null || _semanticColors$base10 === void 0 ? void 0 : _semanticColors$base10.surfacePrimary;
1181
1183
  return `
1182
- &::before {
1184
+ &::before,
1185
+ &::after {
1183
1186
  content: "";
1184
1187
  position: absolute;
1185
1188
  inset-inline: 0;
1186
- top: 0;
1187
1189
  z-index: 10;
1188
1190
  height: ${SCROLL_FADE_HEIGHT}px;
1189
1191
  pointer-events: none;
1190
- background: linear-gradient(to bottom, ${(_props$backgroundColo3 = props.backgroundColor) !== null && _props$backgroundColo3 !== void 0 ? _props$backgroundColo3 : semanticColors === null || semanticColors === void 0 || (_semanticColors$base10 = semanticColors.base) === null || _semanticColors$base10 === void 0 ? void 0 : _semanticColors$base10.surfacePrimary}, transparent);
1192
+ }
1193
+
1194
+ &::before {
1195
+ top: 0;
1196
+ background: linear-gradient(to bottom, ${surface}, transparent);
1197
+ }
1198
+
1199
+ &::after {
1200
+ bottom: 0;
1201
+ background: linear-gradient(to top, ${surface}, transparent);
1191
1202
  }
1192
1203
  `;
1193
1204
  }}
@@ -4153,6 +4164,35 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4153
4164
  };
4154
4165
  }
4155
4166
  //#endregion
4167
+ //#region src/core/ProteinStructureViewer/hooks/useSelection.ts
4168
+ /**
4169
+ * Reconciles the selection the consumer passes with the one the viewer keeps.
4170
+ *
4171
+ * Selection is controlled when `selection` is passed and owned here when it is
4172
+ * not, the same split `useChains` makes for visibility. The camera only ever
4173
+ * follows the selection, so without an owner a click on a viewer nobody holds
4174
+ * state for would mark the residue and never zoom in on it.
4175
+ * `onSelectionChange` fires either way: a consumer that only wants to follow
4176
+ * along should not have to take ownership to hear about it.
4177
+ */
4178
+ function useSelection({ onSelectionChange, selection, structure }) {
4179
+ const isControlled = selection !== void 0;
4180
+ const [ownSelection, setOwnSelection] = (0, react.useState)(null);
4181
+ const onSelectionChangeRef = (0, react.useRef)(onSelectionChange);
4182
+ onSelectionChangeRef.current = onSelectionChange;
4183
+ (0, react.useEffect)(() => {
4184
+ setOwnSelection(null);
4185
+ }, [structure]);
4186
+ return {
4187
+ changeSelection: (0, react.useCallback)((next) => {
4188
+ var _onSelectionChangeRef;
4189
+ if (!isControlled) setOwnSelection(next);
4190
+ (_onSelectionChangeRef = onSelectionChangeRef.current) === null || _onSelectionChangeRef === void 0 || _onSelectionChangeRef.call(onSelectionChangeRef, next);
4191
+ }, [isControlled]),
4192
+ selection: isControlled ? selection : ownSelection
4193
+ };
4194
+ }
4195
+ //#endregion
4156
4196
  //#region src/core/ProteinStructureViewer/utils/focusRepresentation.ts
4157
4197
  /**
4158
4198
  * What Mol* draws around a focused selection, as ball-and-stick over the
@@ -4271,10 +4311,11 @@ function focusWithShell(plugin, loci, includeNeighbours, currentShell, pluginRef
4271
4311
  * chains around them instead, in `useChainHighlight`.
4272
4312
  *
4273
4313
  * The click subscription only reports the selection it was given, so a click
4274
- * moves the camera only once the consumer accepts it and echoes it back -
4275
- * which is what the prop being controlled is supposed to mean. Clicking still
4276
- * marks immediately, through Mol*'s own focus behavior, so the structure
4277
- * responds to a click whether or not the consumer wires the prop up.
4314
+ * moves the camera only once whoever owns the selection accepts it: the
4315
+ * consumer echoing it back when `selection` is controlled, the viewer itself
4316
+ * when it is not. Clicking still marks immediately, through Mol*'s own focus
4317
+ * behavior, so the structure responds to a click even when a consumer
4318
+ * controlling the selection declines it.
4278
4319
  */
4279
4320
  function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setClipRatio }) {
4280
4321
  var _selection$chains, _selection$residues;
@@ -4564,14 +4605,16 @@ function resolveScaleProps(overlay, hasPlddt) {
4564
4605
  * Interactive 3D protein structure viewer built on Mol*, with a sequence panel,
4565
4606
  * pLDDT confidence coloring, per-residue value overlays, and a stats legend.
4566
4607
  *
4567
- * The viewer is controlled for selection: `selectedResidue` drives the camera
4568
- * (clicking a residue zooms in on it, clearing the selection zooms back out),
4569
- * while hover state is owned internally so the legend can show a live readout
4570
- * without the consumer round-tripping every pointer move.
4608
+ * The selection drives the camera: clicking a residue zooms in on it, and
4609
+ * clearing the selection zooms back out. It is controlled when `selection` is
4610
+ * passed and owned internally when it is not, so a click zooms without the
4611
+ * consumer holding any state. Hover state is always owned internally, so the
4612
+ * legend can show a live readout without the consumer round-tripping every
4613
+ * pointer move.
4571
4614
  */
4572
4615
  const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4573
4616
  var _selection$chains;
4574
- const { backgroundColor, chainColors: chainColorOverrides, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, onChainVisibilityChange, onChainsChange, molstarSpec, onResidueClick, onResidueHover, onSelectionChange, structure, plddt, residueOverlay, selection = null, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
4617
+ const { backgroundColor, chainColors: chainColorOverrides, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, onChainVisibilityChange, onChainsChange, molstarSpec, onResidueClick, onResidueHover, onSelectionChange, structure, plddt, residueOverlay, selection: selectionProp, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
4575
4618
  const theme = (0, _mui_material_styles.useTheme)();
4576
4619
  const mode = (0, _czi_sds_components.getMode)({ theme });
4577
4620
  const semanticColors = (0, _czi_sds_components.getSemanticColors)({ theme });
@@ -4599,9 +4642,14 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4599
4642
  return themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.fillSelected, FALLBACK_HIGHLIGHT_COLOR);
4600
4643
  }, [semanticColors]);
4601
4644
  const { handleResidueHover, hoveredResidue } = useResidueHoverState(onResidueHover);
4645
+ const { changeSelection, selection } = useSelection({
4646
+ onSelectionChange,
4647
+ selection: selectionProp,
4648
+ structure
4649
+ });
4602
4650
  const handleSelectionClear = (0, react.useCallback)(() => {
4603
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(null);
4604
- }, [onSelectionChange]);
4651
+ changeSelection(null);
4652
+ }, [changeSelection]);
4605
4653
  /**
4606
4654
  * Chains the selection covers whole, which is what makes a chain caption a
4607
4655
  * toggle rather than a one-way switch. Keyed on its contents because the
@@ -4621,9 +4669,8 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4621
4669
  */
4622
4670
  const handleChainSelect = (0, react.useCallback)((chainId) => {
4623
4671
  var _selection$chains2, _selection$residues;
4624
- const isOnlyThisChain = (selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length);
4625
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(isOnlyThisChain ? null : { chains: [chainId] });
4626
- }, [onSelectionChange, selection]);
4672
+ changeSelection((selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length) ? null : { chains: [chainId] });
4673
+ }, [changeSelection, selection]);
4627
4674
  /**
4628
4675
  * Dims every other chain while a chain's name is pointed at, in the legend
4629
4676
  * or in the sequence panel's captions, and leaves the selected chain lit
@@ -4662,7 +4709,7 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4662
4709
  onChainToggle: toggleChain,
4663
4710
  onResidueClick,
4664
4711
  onResidueHover: handleResidueHover,
4665
- onSelectionChange,
4712
+ onSelectionChange: changeSelection,
4666
4713
  onSelectionClear: handleSelectionClear,
4667
4714
  selectedChains,
4668
4715
  sequenceViewerBackgroundColor,
@@ -1051,8 +1051,8 @@ const HOVER_THROTTLE_MS = 3 * 16.666;
1051
1051
  const COPIED_FEEDBACK_MS = 2e3;
1052
1052
  //#endregion
1053
1053
  //#region src/core/ProteinStructureViewer/components/SequenceView/style.ts
1054
- /** Height of the fade that masks residues scrolling under the panel header. */
1055
- const SCROLL_FADE_HEIGHT = 8;
1054
+ /** Height of the fades that mask residues scrolling past the panel's edges. */
1055
+ const SCROLL_FADE_HEIGHT = 10;
1056
1056
  const SequencePanel = styled("div")`
1057
1057
  display: flex;
1058
1058
  flex-direction: column;
@@ -1166,8 +1166,9 @@ const ResidueCountValue = styled("span")`
1166
1166
  `;
1167
1167
  /**
1168
1168
  * Scroll region for the residue grid. The `::before` fade masks residues as
1169
- * they scroll up under the header, so it has to start from whatever the panel
1170
- * is painted with or it leaves a mismatched strip.
1169
+ * they scroll up under the header and the `::after` fade masks them running off
1170
+ * the panel's bottom edge, so both have to start from whatever the panel is
1171
+ * painted with or they leave a mismatched strip.
1171
1172
  */
1172
1173
  const SequenceScroller = styled("div")`
1173
1174
  position: relative;
@@ -1177,16 +1178,26 @@ const SequenceScroller = styled("div")`
1177
1178
  ${(props) => {
1178
1179
  var _props$backgroundColo3, _semanticColors$base10;
1179
1180
  const semanticColors = getSemanticColors(props);
1181
+ const surface = (_props$backgroundColo3 = props.backgroundColor) !== null && _props$backgroundColo3 !== void 0 ? _props$backgroundColo3 : semanticColors === null || semanticColors === void 0 || (_semanticColors$base10 = semanticColors.base) === null || _semanticColors$base10 === void 0 ? void 0 : _semanticColors$base10.surfacePrimary;
1180
1182
  return `
1181
- &::before {
1183
+ &::before,
1184
+ &::after {
1182
1185
  content: "";
1183
1186
  position: absolute;
1184
1187
  inset-inline: 0;
1185
- top: 0;
1186
1188
  z-index: 10;
1187
1189
  height: ${SCROLL_FADE_HEIGHT}px;
1188
1190
  pointer-events: none;
1189
- background: linear-gradient(to bottom, ${(_props$backgroundColo3 = props.backgroundColor) !== null && _props$backgroundColo3 !== void 0 ? _props$backgroundColo3 : semanticColors === null || semanticColors === void 0 || (_semanticColors$base10 = semanticColors.base) === null || _semanticColors$base10 === void 0 ? void 0 : _semanticColors$base10.surfacePrimary}, transparent);
1191
+ }
1192
+
1193
+ &::before {
1194
+ top: 0;
1195
+ background: linear-gradient(to bottom, ${surface}, transparent);
1196
+ }
1197
+
1198
+ &::after {
1199
+ bottom: 0;
1200
+ background: linear-gradient(to top, ${surface}, transparent);
1190
1201
  }
1191
1202
  `;
1192
1203
  }}
@@ -4152,6 +4163,35 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4152
4163
  };
4153
4164
  }
4154
4165
  //#endregion
4166
+ //#region src/core/ProteinStructureViewer/hooks/useSelection.ts
4167
+ /**
4168
+ * Reconciles the selection the consumer passes with the one the viewer keeps.
4169
+ *
4170
+ * Selection is controlled when `selection` is passed and owned here when it is
4171
+ * not, the same split `useChains` makes for visibility. The camera only ever
4172
+ * follows the selection, so without an owner a click on a viewer nobody holds
4173
+ * state for would mark the residue and never zoom in on it.
4174
+ * `onSelectionChange` fires either way: a consumer that only wants to follow
4175
+ * along should not have to take ownership to hear about it.
4176
+ */
4177
+ function useSelection({ onSelectionChange, selection, structure }) {
4178
+ const isControlled = selection !== void 0;
4179
+ const [ownSelection, setOwnSelection] = useState(null);
4180
+ const onSelectionChangeRef = useRef(onSelectionChange);
4181
+ onSelectionChangeRef.current = onSelectionChange;
4182
+ useEffect(() => {
4183
+ setOwnSelection(null);
4184
+ }, [structure]);
4185
+ return {
4186
+ changeSelection: useCallback((next) => {
4187
+ var _onSelectionChangeRef;
4188
+ if (!isControlled) setOwnSelection(next);
4189
+ (_onSelectionChangeRef = onSelectionChangeRef.current) === null || _onSelectionChangeRef === void 0 || _onSelectionChangeRef.call(onSelectionChangeRef, next);
4190
+ }, [isControlled]),
4191
+ selection: isControlled ? selection : ownSelection
4192
+ };
4193
+ }
4194
+ //#endregion
4155
4195
  //#region src/core/ProteinStructureViewer/utils/focusRepresentation.ts
4156
4196
  /**
4157
4197
  * What Mol* draws around a focused selection, as ball-and-stick over the
@@ -4270,10 +4310,11 @@ function focusWithShell(plugin, loci, includeNeighbours, currentShell, pluginRef
4270
4310
  * chains around them instead, in `useChainHighlight`.
4271
4311
  *
4272
4312
  * The click subscription only reports the selection it was given, so a click
4273
- * moves the camera only once the consumer accepts it and echoes it back -
4274
- * which is what the prop being controlled is supposed to mean. Clicking still
4275
- * marks immediately, through Mol*'s own focus behavior, so the structure
4276
- * responds to a click whether or not the consumer wires the prop up.
4313
+ * moves the camera only once whoever owns the selection accepts it: the
4314
+ * consumer echoing it back when `selection` is controlled, the viewer itself
4315
+ * when it is not. Clicking still marks immediately, through Mol*'s own focus
4316
+ * behavior, so the structure responds to a click even when a consumer
4317
+ * controlling the selection declines it.
4277
4318
  */
4278
4319
  function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setClipRatio }) {
4279
4320
  var _selection$chains, _selection$residues;
@@ -4561,7 +4602,7 @@ function resolveScaleProps(overlay, hasPlddt) {
4561
4602
  }
4562
4603
  var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4563
4604
  var _selection$chains;
4564
- const { backgroundColor, chainColors: chainColorOverrides, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, onChainVisibilityChange, onChainsChange, molstarSpec, onResidueClick, onResidueHover, onSelectionChange, structure, plddt, residueOverlay, selection = null, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = _objectWithoutProperties(props, _excluded);
4605
+ const { backgroundColor, chainColors: chainColorOverrides, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, onChainVisibilityChange, onChainsChange, molstarSpec, onResidueClick, onResidueHover, onSelectionChange, structure, plddt, residueOverlay, selection: selectionProp, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = _objectWithoutProperties(props, _excluded);
4565
4606
  const theme = useTheme();
4566
4607
  const mode = getMode({ theme });
4567
4608
  const semanticColors = getSemanticColors({ theme });
@@ -4589,9 +4630,14 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4589
4630
  return themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.fillSelected, FALLBACK_HIGHLIGHT_COLOR);
4590
4631
  }, [semanticColors]);
4591
4632
  const { handleResidueHover, hoveredResidue } = useResidueHoverState(onResidueHover);
4633
+ const { changeSelection, selection } = useSelection({
4634
+ onSelectionChange,
4635
+ selection: selectionProp,
4636
+ structure
4637
+ });
4592
4638
  const handleSelectionClear = useCallback(() => {
4593
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(null);
4594
- }, [onSelectionChange]);
4639
+ changeSelection(null);
4640
+ }, [changeSelection]);
4595
4641
  /**
4596
4642
  * Chains the selection covers whole, which is what makes a chain caption a
4597
4643
  * toggle rather than a one-way switch. Keyed on its contents because the
@@ -4611,9 +4657,8 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4611
4657
  */
4612
4658
  const handleChainSelect = useCallback((chainId) => {
4613
4659
  var _selection$chains2, _selection$residues;
4614
- const isOnlyThisChain = (selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length);
4615
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(isOnlyThisChain ? null : { chains: [chainId] });
4616
- }, [onSelectionChange, selection]);
4660
+ changeSelection((selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length) ? null : { chains: [chainId] });
4661
+ }, [changeSelection, selection]);
4617
4662
  /**
4618
4663
  * Dims every other chain while a chain's name is pointed at, in the legend
4619
4664
  * or in the sequence panel's captions, and leaves the selected chain lit
@@ -4652,7 +4697,7 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4652
4697
  onChainToggle: toggleChain,
4653
4698
  onResidueClick,
4654
4699
  onResidueHover: handleResidueHover,
4655
- onSelectionChange,
4700
+ onSelectionChange: changeSelection,
4656
4701
  onSelectionClear: handleSelectionClear,
4657
4702
  selectedChains,
4658
4703
  sequenceViewerBackgroundColor,
@@ -50,12 +50,12 @@ interface ResidueValueOverlay {
50
50
  * address it.
51
51
  *
52
52
  * `index` is the viewer's own key: the position in `plddt` and in
53
- * `residueOverlay`'s map, and what `selectedResidue` takes. `chainId`, `seqId`
54
- * and `insCode` are what the file says, which is what the sequence panel shows
55
- * and what a system that supplied the structure will recognise. They differ
56
- * from `index` whenever the file does not number a single chain from 1 -- a
57
- * crop, or a complex -- so address a residue outside the viewer with those
58
- * three, not with `index`.
53
+ * `residueOverlay`'s map, and what `selection.residues` takes. `chainId`,
54
+ * `seqId` and `insCode` are what the file says, which is what the sequence
55
+ * panel shows and what a system that supplied the structure will recognise.
56
+ * They differ from `index` whenever the file does not number a single chain
57
+ * from 1 -- a crop, or a complex -- so address a residue outside the viewer
58
+ * with those three, not with `index`.
59
59
  *
60
60
  * `insCode` is rarely set but is part of the address when it is: `10` and `10A`
61
61
  * are different residues on the same chain, so chain and number alone do not
@@ -208,13 +208,15 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
208
208
  /** Per-residue values that override pLDDT coloring while set. */
209
209
  residueOverlay?: ResidueValueOverlay | null;
210
210
  /**
211
- * What is selected, or null when nothing is. Controlled: the camera frames
212
- * whatever the selection covers, and clearing it zooms back out to the
213
- * default view.
211
+ * What is selected, or null when nothing is. Leave undefined to let the
212
+ * viewer own the selection, so clicking a residue zooms in on it with no
213
+ * state on the consumer's side; passing it takes that over, and a click then
214
+ * moves the camera only once `onSelectionChange` is echoed back here.
214
215
  *
215
- * Residues are drawn in ball-and-stick over the cartoon; whole chains are
216
- * left as they are and the chains around them dim, the same as hovering a
217
- * chain's name.
216
+ * Either way the camera frames whatever the selection covers, and clearing
217
+ * it zooms back out to the default view. Residues are drawn in
218
+ * ball-and-stick over the cartoon; whole chains are left as they are and the
219
+ * chains around them dim, the same as hovering a chain's name.
218
220
  */
219
221
  selection?: StructureSelection | null;
220
222
  /**
@@ -286,6 +288,8 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
286
288
  *
287
289
  * A whole-chain selection arrives as `{ chains: [id] }` rather than as every
288
290
  * index on it, so echoing it straight back into `selection` costs nothing.
291
+ * Fires whether or not `selection` is controlled, so a consumer can follow
292
+ * the viewer's own selection without owning it.
289
293
  */
290
294
  onSelectionChange?: (selection: StructureSelection | null) => void;
291
295
  /**
@@ -52,12 +52,12 @@ interface ResidueValueOverlay {
52
52
  * address it.
53
53
  *
54
54
  * `index` is the viewer's own key: the position in `plddt` and in
55
- * `residueOverlay`'s map, and what `selectedResidue` takes. `chainId`, `seqId`
56
- * and `insCode` are what the file says, which is what the sequence panel shows
57
- * and what a system that supplied the structure will recognise. They differ
58
- * from `index` whenever the file does not number a single chain from 1 -- a
59
- * crop, or a complex -- so address a residue outside the viewer with those
60
- * three, not with `index`.
55
+ * `residueOverlay`'s map, and what `selection.residues` takes. `chainId`,
56
+ * `seqId` and `insCode` are what the file says, which is what the sequence
57
+ * panel shows and what a system that supplied the structure will recognise.
58
+ * They differ from `index` whenever the file does not number a single chain
59
+ * from 1 -- a crop, or a complex -- so address a residue outside the viewer
60
+ * with those three, not with `index`.
61
61
  *
62
62
  * `insCode` is rarely set but is part of the address when it is: `10` and `10A`
63
63
  * are different residues on the same chain, so chain and number alone do not
@@ -210,13 +210,15 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
210
210
  /** Per-residue values that override pLDDT coloring while set. */
211
211
  residueOverlay?: ResidueValueOverlay | null;
212
212
  /**
213
- * What is selected, or null when nothing is. Controlled: the camera frames
214
- * whatever the selection covers, and clearing it zooms back out to the
215
- * default view.
213
+ * What is selected, or null when nothing is. Leave undefined to let the
214
+ * viewer own the selection, so clicking a residue zooms in on it with no
215
+ * state on the consumer's side; passing it takes that over, and a click then
216
+ * moves the camera only once `onSelectionChange` is echoed back here.
216
217
  *
217
- * Residues are drawn in ball-and-stick over the cartoon; whole chains are
218
- * left as they are and the chains around them dim, the same as hovering a
219
- * chain's name.
218
+ * Either way the camera frames whatever the selection covers, and clearing
219
+ * it zooms back out to the default view. Residues are drawn in
220
+ * ball-and-stick over the cartoon; whole chains are left as they are and the
221
+ * chains around them dim, the same as hovering a chain's name.
220
222
  */
221
223
  selection?: StructureSelection | null;
222
224
  /**
@@ -288,6 +290,8 @@ interface ProteinStructureViewerProps extends Omit<HTMLAttributes<HTMLDivElement
288
290
  *
289
291
  * A whole-chain selection arrives as `{ chains: [id] }` rather than as every
290
292
  * index on it, so echoing it straight back into `selection` costs nothing.
293
+ * Fires whether or not `selection` is controlled, so a consumer can follow
294
+ * the viewer's own selection without owning it.
291
295
  */
292
296
  onSelectionChange?: (selection: StructureSelection | null) => void;
293
297
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@czi-sds/data-viz",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "main": "dist/index.cjs.js",
5
5
  "types": "dist/index.cjs.d.ts",
6
6
  "module": "dist/index.esm.js",
@@ -147,5 +147,5 @@
147
147
  "echarts": "^6.0.0",
148
148
  "lodash": "^4.17.21"
149
149
  },
150
- "gitHead": "a4587a068a8e457bb068a1b170951c8f4f396bd5"
150
+ "gitHead": "52f0027db4ead40a4a311a27aefd762ed8823d47"
151
151
  }