@czi-sds/data-viz 2.4.2 → 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.
@@ -4164,6 +4164,35 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4164
4164
  };
4165
4165
  }
4166
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
4167
4196
  //#region src/core/ProteinStructureViewer/utils/focusRepresentation.ts
4168
4197
  /**
4169
4198
  * What Mol* draws around a focused selection, as ball-and-stick over the
@@ -4282,10 +4311,11 @@ function focusWithShell(plugin, loci, includeNeighbours, currentShell, pluginRef
4282
4311
  * chains around them instead, in `useChainHighlight`.
4283
4312
  *
4284
4313
  * The click subscription only reports the selection it was given, so a click
4285
- * moves the camera only once the consumer accepts it and echoes it back -
4286
- * which is what the prop being controlled is supposed to mean. Clicking still
4287
- * marks immediately, through Mol*'s own focus behavior, so the structure
4288
- * 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.
4289
4319
  */
4290
4320
  function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setClipRatio }) {
4291
4321
  var _selection$chains, _selection$residues;
@@ -4575,14 +4605,16 @@ function resolveScaleProps(overlay, hasPlddt) {
4575
4605
  * Interactive 3D protein structure viewer built on Mol*, with a sequence panel,
4576
4606
  * pLDDT confidence coloring, per-residue value overlays, and a stats legend.
4577
4607
  *
4578
- * The viewer is controlled for selection: `selectedResidue` drives the camera
4579
- * (clicking a residue zooms in on it, clearing the selection zooms back out),
4580
- * while hover state is owned internally so the legend can show a live readout
4581
- * 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.
4582
4614
  */
4583
4615
  const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4584
4616
  var _selection$chains;
4585
- 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);
4586
4618
  const theme = (0, _mui_material_styles.useTheme)();
4587
4619
  const mode = (0, _czi_sds_components.getMode)({ theme });
4588
4620
  const semanticColors = (0, _czi_sds_components.getSemanticColors)({ theme });
@@ -4610,9 +4642,14 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4610
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);
4611
4643
  }, [semanticColors]);
4612
4644
  const { handleResidueHover, hoveredResidue } = useResidueHoverState(onResidueHover);
4645
+ const { changeSelection, selection } = useSelection({
4646
+ onSelectionChange,
4647
+ selection: selectionProp,
4648
+ structure
4649
+ });
4613
4650
  const handleSelectionClear = (0, react.useCallback)(() => {
4614
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(null);
4615
- }, [onSelectionChange]);
4651
+ changeSelection(null);
4652
+ }, [changeSelection]);
4616
4653
  /**
4617
4654
  * Chains the selection covers whole, which is what makes a chain caption a
4618
4655
  * toggle rather than a one-way switch. Keyed on its contents because the
@@ -4632,9 +4669,8 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4632
4669
  */
4633
4670
  const handleChainSelect = (0, react.useCallback)((chainId) => {
4634
4671
  var _selection$chains2, _selection$residues;
4635
- 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);
4636
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(isOnlyThisChain ? null : { chains: [chainId] });
4637
- }, [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]);
4638
4674
  /**
4639
4675
  * Dims every other chain while a chain's name is pointed at, in the legend
4640
4676
  * or in the sequence panel's captions, and leaves the selected chain lit
@@ -4673,7 +4709,7 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4673
4709
  onChainToggle: toggleChain,
4674
4710
  onResidueClick,
4675
4711
  onResidueHover: handleResidueHover,
4676
- onSelectionChange,
4712
+ onSelectionChange: changeSelection,
4677
4713
  onSelectionClear: handleSelectionClear,
4678
4714
  selectedChains,
4679
4715
  sequenceViewerBackgroundColor,
@@ -4163,6 +4163,35 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4163
4163
  };
4164
4164
  }
4165
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
4166
4195
  //#region src/core/ProteinStructureViewer/utils/focusRepresentation.ts
4167
4196
  /**
4168
4197
  * What Mol* draws around a focused selection, as ball-and-stick over the
@@ -4281,10 +4310,11 @@ function focusWithShell(plugin, loci, includeNeighbours, currentShell, pluginRef
4281
4310
  * chains around them instead, in `useChainHighlight`.
4282
4311
  *
4283
4312
  * The click subscription only reports the selection it was given, so a click
4284
- * moves the camera only once the consumer accepts it and echoes it back -
4285
- * which is what the prop being controlled is supposed to mean. Clicking still
4286
- * marks immediately, through Mol*'s own focus behavior, so the structure
4287
- * 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.
4288
4318
  */
4289
4319
  function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setClipRatio }) {
4290
4320
  var _selection$chains, _selection$residues;
@@ -4572,7 +4602,7 @@ function resolveScaleProps(overlay, hasPlddt) {
4572
4602
  }
4573
4603
  var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4574
4604
  var _selection$chains;
4575
- 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);
4576
4606
  const theme = useTheme();
4577
4607
  const mode = getMode({ theme });
4578
4608
  const semanticColors = getSemanticColors({ theme });
@@ -4600,9 +4630,14 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4600
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);
4601
4631
  }, [semanticColors]);
4602
4632
  const { handleResidueHover, hoveredResidue } = useResidueHoverState(onResidueHover);
4633
+ const { changeSelection, selection } = useSelection({
4634
+ onSelectionChange,
4635
+ selection: selectionProp,
4636
+ structure
4637
+ });
4603
4638
  const handleSelectionClear = useCallback(() => {
4604
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(null);
4605
- }, [onSelectionChange]);
4639
+ changeSelection(null);
4640
+ }, [changeSelection]);
4606
4641
  /**
4607
4642
  * Chains the selection covers whole, which is what makes a chain caption a
4608
4643
  * toggle rather than a one-way switch. Keyed on its contents because the
@@ -4622,9 +4657,8 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4622
4657
  */
4623
4658
  const handleChainSelect = useCallback((chainId) => {
4624
4659
  var _selection$chains2, _selection$residues;
4625
- 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);
4626
- onSelectionChange === null || onSelectionChange === void 0 || onSelectionChange(isOnlyThisChain ? null : { chains: [chainId] });
4627
- }, [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]);
4628
4662
  /**
4629
4663
  * Dims every other chain while a chain's name is pointed at, in the legend
4630
4664
  * or in the sequence panel's captions, and leaves the selected chain lit
@@ -4663,7 +4697,7 @@ var ProteinStructureViewer_default = memo(forwardRef((props, ref) => {
4663
4697
  onChainToggle: toggleChain,
4664
4698
  onResidueClick,
4665
4699
  onResidueHover: handleResidueHover,
4666
- onSelectionChange,
4700
+ onSelectionChange: changeSelection,
4667
4701
  onSelectionClear: handleSelectionClear,
4668
4702
  selectedChains,
4669
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.2",
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": "4fb1150d51f5cde0b88503ca0146eba3b53f1ab1"
150
+ "gitHead": "52f0027db4ead40a4a311a27aefd762ed8823d47"
151
151
  }