@czi-sds/data-viz 2.1.0 → 2.1.1

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.
@@ -1,8 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { HTMLAttributes } from "react";
3
3
  import { DatasetComponentOption, ECharts, EChartsOption, ScatterSeriesOption } from "echarts";
4
- import { DiscreteColorGeneratorOptions, TooltipTableContentProps } from "@czi-sds/components";
5
-
4
+ import { DiscreteColorGeneratorOptions, TooltipProps, TooltipTableContentProps } from "@czi-sds/components";
6
5
  //#region src/common/colorScales.d.ts
7
6
  /** RGB triplet with each channel in the range 0-255. */
8
7
  type Rgb = [number, number, number];
@@ -250,8 +249,18 @@ interface ResidueValueOverlay {
250
249
  colorScale?: ColorScale;
251
250
  /** Legend caption, e.g. "Feature activation". */
252
251
  label?: string;
253
- /** Help tooltip attached to the legend caption. */
252
+ /**
253
+ * Title of the help tooltip on the legend caption. The common case: a
254
+ * string is enough. Use `tooltipProps` when the tooltip needs a subtitle, a
255
+ * custom body, or any other SDS Tooltip prop. Overrides
256
+ * `tooltipProps.title` when both are set.
257
+ */
254
258
  tooltip?: string;
259
+ /**
260
+ * Props forwarded to the SDS Tooltip on the legend caption. The trigger is
261
+ * the caption's help icon, so `children` is omitted.
262
+ */
263
+ tooltipProps?: Partial<Omit<TooltipProps, "children">>;
255
264
  /**
256
265
  * Label for the per-residue readout that replaces a stat slot on hover.
257
266
  * @default "Value"
package/dist/index.cjs.js CHANGED
@@ -893,18 +893,22 @@ const ScaleColumn = (0, _emotion_styled.default)("div")`
893
893
  `;
894
894
  }}
895
895
  `;
896
+ /**
897
+ * A wrapping text line, not a flex row: the help icon has to sit at the end of
898
+ * the last line of the caption rather than beside the whole wrapped block.
899
+ */
896
900
  const ScaleCaption = (0, _emotion_styled.default)("span")`
897
901
  ${_czi_sds_components.fontBodyXxxs}
898
902
 
903
+ display: block;
899
904
  text-align: left;
900
- display: flex;
901
- align-items: center;
902
905
 
903
906
  ${(props) => {
904
907
  var _semanticColors$base3;
905
908
  const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
906
909
  return `
907
910
  color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textTertiaryOnDark};
911
+ max-width: 142px;
908
912
  `;
909
913
  }}
910
914
  `;
@@ -917,12 +921,18 @@ const TooltipAnchor = (0, _emotion_styled.default)("span")`
917
921
  const spaces = (0, _czi_sds_components.getSpaces)(props);
918
922
  const semanticColors = (0, _czi_sds_components.getSemanticColors)(props);
919
923
  return `
924
+ display: inline-block;
920
925
  padding-left: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px;
921
- margin-bottom: -${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px;
926
+ margin-bottom: 1px;
927
+ vertical-align: text-bottom;
922
928
 
923
929
  &:hover {
924
930
  color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base4 = semanticColors.base) === null || _semanticColors$base4 === void 0 ? void 0 : _semanticColors$base4.ornamentSecondary};
925
931
  }
932
+
933
+ svg {
934
+ display: block;
935
+ }
926
936
  `;
927
937
  }}
928
938
  `;
@@ -947,7 +957,7 @@ function StatColumn({ label, value }) {
947
957
  * residue takes priority so the user can inspect others without losing their
948
958
  * selection.
949
959
  */
950
- function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
960
+ function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
951
961
  var _stats$, _stats$2, _stats$3;
952
962
  const activeResidue = hoveredResidue !== null && hoveredResidue !== void 0 ? hoveredResidue : selectedResidue;
953
963
  const showResidue = activeResidue !== null;
@@ -979,15 +989,16 @@ function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax =
979
989
  max: scaleMax,
980
990
  min: scaleMin,
981
991
  scale
982
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ScaleCaption, { children: [scaleLabel, scaleTooltip !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Tooltip, {
992
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ScaleCaption, { children: [scaleLabel, (scaleTooltip !== void 0 || scaleTooltipProps !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Tooltip, _objectSpread2(_objectSpread2({
983
993
  arrow: true,
984
- placement: "bottom",
985
- title: scaleTooltip,
994
+ placement: "bottom"
995
+ }, scaleTooltipProps), {}, {
996
+ title: scaleTooltip !== null && scaleTooltip !== void 0 ? scaleTooltip : scaleTooltipProps === null || scaleTooltipProps === void 0 ? void 0 : scaleTooltipProps.title,
986
997
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipAnchor, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_phosphor_icons_react.QuestionIcon, {
987
998
  size: 10,
988
999
  weight: "bold"
989
1000
  }) })
990
- })] })] })]
1001
+ }))] })] })]
991
1002
  });
992
1003
  }
993
1004
  //#endregion
@@ -1806,6 +1817,48 @@ const Sequence = ({ chainLabel, hideSequenceNumbers, residueColors, sequenceNumb
1806
1817
  }, handlers), {}, { children: groups })), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResidueTooltip, { ref: tooltipRef })] });
1807
1818
  };
1808
1819
  //#endregion
1820
+ //#region src/core/ProteinStructureViewer/components/SequenceView/utils/copyText.ts
1821
+ /**
1822
+ * Writes `text` to the clipboard.
1823
+ *
1824
+ * The async Clipboard API is preferred, but nested srcdoc sandboxes — MCP
1825
+ * inspectors, Claude Desktop, and similar hosts — often omit `clipboard-write`
1826
+ * from their Permissions Policy, so `navigator.clipboard.writeText` rejects
1827
+ * even on a user click. `document.execCommand("copy")` only needs that click, so
1828
+ * it covers those hosts without the consumer having to patch the bundle.
1829
+ */
1830
+ function copyText(text) {
1831
+ var _navigator$clipboard;
1832
+ if (!((_navigator$clipboard = navigator.clipboard) === null || _navigator$clipboard === void 0 ? void 0 : _navigator$clipboard.writeText)) return copyWithSelection(text);
1833
+ return navigator.clipboard.writeText(text).catch(() => copyWithSelection(text));
1834
+ }
1835
+ /**
1836
+ * Copies by selecting a transient textarea. Must run under user activation,
1837
+ * which a click still has after the Clipboard API has been refused.
1838
+ */
1839
+ function copyWithSelection(text) {
1840
+ return new Promise((resolve, reject) => {
1841
+ const area = document.createElement("textarea");
1842
+ area.value = text;
1843
+ area.setAttribute("readonly", "");
1844
+ area.setAttribute("aria-hidden", "true");
1845
+ area.style.position = "fixed";
1846
+ area.style.top = "-1000px";
1847
+ document.body.appendChild(area);
1848
+ area.select();
1849
+ area.setSelectionRange(0, text.length);
1850
+ let copied = false;
1851
+ try {
1852
+ copied = document.execCommand("copy");
1853
+ } catch (_unused) {
1854
+ copied = false;
1855
+ }
1856
+ area.remove();
1857
+ if (copied) resolve();
1858
+ else reject(/* @__PURE__ */ new Error("The host blocked clipboard access."));
1859
+ });
1860
+ }
1861
+ //#endregion
1809
1862
  //#region src/core/ProteinStructureViewer/components/SequenceView/utils/sequenceText.ts
1810
1863
  /**
1811
1864
  * The structure's residues as text, one section per chain separated by
@@ -1834,12 +1887,11 @@ function useCopySequence(entries) {
1834
1887
  return {
1835
1888
  copied,
1836
1889
  copySequence: (0, react.useCallback)(() => {
1837
- const sequence = sequenceTextFromEntries(entries);
1838
- navigator.clipboard.writeText(sequence).then(() => {
1890
+ copyText(sequenceTextFromEntries(entries)).then(() => {
1839
1891
  setCopied(true);
1840
1892
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
1841
1893
  timeoutRef.current = setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
1842
- });
1894
+ }).catch(() => {});
1843
1895
  }, [entries])
1844
1896
  };
1845
1897
  }
@@ -3055,6 +3107,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3055
3107
  scaleMax: overlay.max,
3056
3108
  scaleMin: (_overlay$min = overlay.min) !== null && _overlay$min !== void 0 ? _overlay$min : 0,
3057
3109
  scaleTooltip: overlay.tooltip,
3110
+ scaleTooltipProps: overlay.tooltipProps,
3058
3111
  valueLabel: (_overlay$readoutLabel = overlay.readoutLabel) !== null && _overlay$readoutLabel !== void 0 ? _overlay$readoutLabel : DEFAULT_READOUT_LABEL
3059
3112
  };
3060
3113
  }
@@ -3063,6 +3116,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3063
3116
  scaleLabel: PLDDT_SCALE_LABEL,
3064
3117
  scaleMax: null,
3065
3118
  scaleTooltip: void 0,
3119
+ scaleTooltipProps: void 0,
3066
3120
  valueLabel: void 0
3067
3121
  };
3068
3122
  return {
@@ -3070,6 +3124,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3070
3124
  scaleLabel: void 0,
3071
3125
  scaleMax: null,
3072
3126
  scaleTooltip: void 0,
3127
+ scaleTooltipProps: void 0,
3073
3128
  valueLabel: void 0
3074
3129
  };
3075
3130
  }
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { DatasetComponentOption, ECharts, EChartsOption, ScatterSeriesOption } from "echarts";
3
3
  import { HTMLAttributes } from "react";
4
- import { DiscreteColorGeneratorOptions, TooltipTableContentProps } from "@czi-sds/components";
4
+ import { DiscreteColorGeneratorOptions, TooltipProps, TooltipTableContentProps } from "@czi-sds/components";
5
5
  import { CustomElementProperty } from "molstar/lib/mol-model-props/common/custom-element-property";
6
6
 
7
7
  //#region src/common/colorScales.d.ts
@@ -251,8 +251,18 @@ interface ResidueValueOverlay {
251
251
  colorScale?: ColorScale;
252
252
  /** Legend caption, e.g. "Feature activation". */
253
253
  label?: string;
254
- /** Help tooltip attached to the legend caption. */
254
+ /**
255
+ * Title of the help tooltip on the legend caption. The common case: a
256
+ * string is enough. Use `tooltipProps` when the tooltip needs a subtitle, a
257
+ * custom body, or any other SDS Tooltip prop. Overrides
258
+ * `tooltipProps.title` when both are set.
259
+ */
255
260
  tooltip?: string;
261
+ /**
262
+ * Props forwarded to the SDS Tooltip on the legend caption. The trigger is
263
+ * the caption's help icon, so `children` is omitted.
264
+ */
265
+ tooltipProps?: Partial<Omit<TooltipProps, "children">>;
256
266
  /**
257
267
  * Label for the per-residue readout that replaces a stat slot on hover.
258
268
  * @default "Value"
package/dist/index.esm.js CHANGED
@@ -868,18 +868,22 @@ const ScaleColumn = styled("div")`
868
868
  `;
869
869
  }}
870
870
  `;
871
+ /**
872
+ * A wrapping text line, not a flex row: the help icon has to sit at the end of
873
+ * the last line of the caption rather than beside the whole wrapped block.
874
+ */
871
875
  const ScaleCaption = styled("span")`
872
876
  ${fontBodyXxxs}
873
877
 
878
+ display: block;
874
879
  text-align: left;
875
- display: flex;
876
- align-items: center;
877
880
 
878
881
  ${(props) => {
879
882
  var _semanticColors$base3;
880
883
  const semanticColors = getSemanticColors(props);
881
884
  return `
882
885
  color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.textTertiaryOnDark};
886
+ max-width: 142px;
883
887
  `;
884
888
  }}
885
889
  `;
@@ -892,12 +896,18 @@ const TooltipAnchor = styled("span")`
892
896
  const spaces = getSpaces(props);
893
897
  const semanticColors = getSemanticColors(props);
894
898
  return `
899
+ display: inline-block;
895
900
  padding-left: ${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px;
896
- margin-bottom: -${spaces === null || spaces === void 0 ? void 0 : spaces.xxxs}px;
901
+ margin-bottom: 1px;
902
+ vertical-align: text-bottom;
897
903
 
898
904
  &:hover {
899
905
  color: ${semanticColors === null || semanticColors === void 0 || (_semanticColors$base4 = semanticColors.base) === null || _semanticColors$base4 === void 0 ? void 0 : _semanticColors$base4.ornamentSecondary};
900
906
  }
907
+
908
+ svg {
909
+ display: block;
910
+ }
901
911
  `;
902
912
  }}
903
913
  `;
@@ -922,7 +932,7 @@ function StatColumn({ label, value }) {
922
932
  * residue takes priority so the user can inspect others without losing their
923
933
  * selection.
924
934
  */
925
- function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
935
+ function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
926
936
  var _stats$, _stats$2, _stats$3;
927
937
  const activeResidue = hoveredResidue !== null && hoveredResidue !== void 0 ? hoveredResidue : selectedResidue;
928
938
  const showResidue = activeResidue !== null;
@@ -954,15 +964,16 @@ function StructureLegend({ hoveredResidue = null, scale, scaleLabel, scaleMax =
954
964
  max: scaleMax,
955
965
  min: scaleMin,
956
966
  scale
957
- }), /* @__PURE__ */ jsxs(ScaleCaption, { children: [scaleLabel, scaleTooltip !== void 0 && /* @__PURE__ */ jsx(Tooltip, {
967
+ }), /* @__PURE__ */ jsxs(ScaleCaption, { children: [scaleLabel, (scaleTooltip !== void 0 || scaleTooltipProps !== void 0) && /* @__PURE__ */ jsx(Tooltip, _objectSpread2(_objectSpread2({
958
968
  arrow: true,
959
- placement: "bottom",
960
- title: scaleTooltip,
969
+ placement: "bottom"
970
+ }, scaleTooltipProps), {}, {
971
+ title: scaleTooltip !== null && scaleTooltip !== void 0 ? scaleTooltip : scaleTooltipProps === null || scaleTooltipProps === void 0 ? void 0 : scaleTooltipProps.title,
961
972
  children: /* @__PURE__ */ jsx(TooltipAnchor, { children: /* @__PURE__ */ jsx(QuestionIcon, {
962
973
  size: 10,
963
974
  weight: "bold"
964
975
  }) })
965
- })] })] })]
976
+ }))] })] })]
966
977
  });
967
978
  }
968
979
  //#endregion
@@ -1781,6 +1792,48 @@ const Sequence = ({ chainLabel, hideSequenceNumbers, residueColors, sequenceNumb
1781
1792
  }, handlers), {}, { children: groups })), /* @__PURE__ */ jsx(ResidueTooltip, { ref: tooltipRef })] });
1782
1793
  };
1783
1794
  //#endregion
1795
+ //#region src/core/ProteinStructureViewer/components/SequenceView/utils/copyText.ts
1796
+ /**
1797
+ * Writes `text` to the clipboard.
1798
+ *
1799
+ * The async Clipboard API is preferred, but nested srcdoc sandboxes — MCP
1800
+ * inspectors, Claude Desktop, and similar hosts — often omit `clipboard-write`
1801
+ * from their Permissions Policy, so `navigator.clipboard.writeText` rejects
1802
+ * even on a user click. `document.execCommand("copy")` only needs that click, so
1803
+ * it covers those hosts without the consumer having to patch the bundle.
1804
+ */
1805
+ function copyText(text) {
1806
+ var _navigator$clipboard;
1807
+ if (!((_navigator$clipboard = navigator.clipboard) === null || _navigator$clipboard === void 0 ? void 0 : _navigator$clipboard.writeText)) return copyWithSelection(text);
1808
+ return navigator.clipboard.writeText(text).catch(() => copyWithSelection(text));
1809
+ }
1810
+ /**
1811
+ * Copies by selecting a transient textarea. Must run under user activation,
1812
+ * which a click still has after the Clipboard API has been refused.
1813
+ */
1814
+ function copyWithSelection(text) {
1815
+ return new Promise((resolve, reject) => {
1816
+ const area = document.createElement("textarea");
1817
+ area.value = text;
1818
+ area.setAttribute("readonly", "");
1819
+ area.setAttribute("aria-hidden", "true");
1820
+ area.style.position = "fixed";
1821
+ area.style.top = "-1000px";
1822
+ document.body.appendChild(area);
1823
+ area.select();
1824
+ area.setSelectionRange(0, text.length);
1825
+ let copied = false;
1826
+ try {
1827
+ copied = document.execCommand("copy");
1828
+ } catch (_unused) {
1829
+ copied = false;
1830
+ }
1831
+ area.remove();
1832
+ if (copied) resolve();
1833
+ else reject(/* @__PURE__ */ new Error("The host blocked clipboard access."));
1834
+ });
1835
+ }
1836
+ //#endregion
1784
1837
  //#region src/core/ProteinStructureViewer/components/SequenceView/utils/sequenceText.ts
1785
1838
  /**
1786
1839
  * The structure's residues as text, one section per chain separated by
@@ -1809,12 +1862,11 @@ function useCopySequence(entries) {
1809
1862
  return {
1810
1863
  copied,
1811
1864
  copySequence: useCallback(() => {
1812
- const sequence = sequenceTextFromEntries(entries);
1813
- navigator.clipboard.writeText(sequence).then(() => {
1865
+ copyText(sequenceTextFromEntries(entries)).then(() => {
1814
1866
  setCopied(true);
1815
1867
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
1816
1868
  timeoutRef.current = setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS);
1817
- });
1869
+ }).catch(() => {});
1818
1870
  }, [entries])
1819
1871
  };
1820
1872
  }
@@ -3030,6 +3082,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3030
3082
  scaleMax: overlay.max,
3031
3083
  scaleMin: (_overlay$min = overlay.min) !== null && _overlay$min !== void 0 ? _overlay$min : 0,
3032
3084
  scaleTooltip: overlay.tooltip,
3085
+ scaleTooltipProps: overlay.tooltipProps,
3033
3086
  valueLabel: (_overlay$readoutLabel = overlay.readoutLabel) !== null && _overlay$readoutLabel !== void 0 ? _overlay$readoutLabel : DEFAULT_READOUT_LABEL
3034
3087
  };
3035
3088
  }
@@ -3038,6 +3091,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3038
3091
  scaleLabel: PLDDT_SCALE_LABEL,
3039
3092
  scaleMax: null,
3040
3093
  scaleTooltip: void 0,
3094
+ scaleTooltipProps: void 0,
3041
3095
  valueLabel: void 0
3042
3096
  };
3043
3097
  return {
@@ -3045,6 +3099,7 @@ function resolveScaleProps(overlay, hasPlddt) {
3045
3099
  scaleLabel: void 0,
3046
3100
  scaleMax: null,
3047
3101
  scaleTooltip: void 0,
3102
+ scaleTooltipProps: void 0,
3048
3103
  valueLabel: void 0
3049
3104
  };
3050
3105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@czi-sds/data-viz",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "main": "dist/index.cjs.js",
5
5
  "types": "dist/index.cjs.d.ts",
6
6
  "module": "dist/index.esm.js",
@@ -84,5 +84,5 @@
84
84
  "echarts": "^6.0.0",
85
85
  "lodash": "^4.17.21"
86
86
  },
87
- "gitHead": "90a8e57f0c92c4bdea1f0cb4d1cd417461c50280"
87
+ "gitHead": "8fc0fe0e1562922e7e569f6e983968491d6db485"
88
88
  }