@vitessce/vit-s 3.2.0 → 3.2.2

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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$6 from "react";
2
- import React__default, { Children, isValidElement, cloneElement as cloneElement$1, useReducer, useRef, useDebugValue, useEffect, useLayoutEffect, createContext as createContext$1, createElement, useContext, useMemo, useCallback, useState, useId } from "react";
2
+ import React__default, { Children, isValidElement, cloneElement as cloneElement$1, useReducer, useRef, useDebugValue, useEffect, useLayoutEffect, createContext as createContext$1, createElement, useContext, useMemo, useCallback, useState } from "react";
3
3
  import * as ReactDOM from "react-dom";
4
4
  import ReactDOM__default from "react-dom";
5
5
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
@@ -35383,10 +35383,10 @@ const AUTO_INDEPENDENT_COORDINATION_TYPES = [
35383
35383
  CoordinationType$1.EMBEDDING_OBS_OPACITY
35384
35384
  ];
35385
35385
  const note = "This file is autogenerated by .changeset/post-changelog.mjs.";
35386
- const version = "3.2.0";
35387
- const date = "2023-09-12";
35386
+ const version = "3.2.2";
35387
+ const date = "2023-09-20";
35388
35388
  const branch = "changeset-release/main";
35389
- const hash = "13836ae8";
35389
+ const hash = "715881ca";
35390
35390
  const META_VERSION = {
35391
35391
  note,
35392
35392
  version,
@@ -35632,7 +35632,13 @@ function buildConfigSchema(pluginFileTypes, pluginJointFileTypes, pluginCoordina
35632
35632
  // Wrap each value schema in z.record()
35633
35633
  fromEntries(pluginCoordinationTypes.map((ct) => [
35634
35634
  ct.name,
35635
- z.record(coordinationScopeName, ct.valueSchema.optional()).optional()
35635
+ z.record(
35636
+ // For now, assume the key type is string (though it would be
35637
+ // slightly nicer if we could use the coordinationScopeName schema here).
35638
+ // Once https://github.com/colinhacks/zod/issues/2746 gets resolved
35639
+ // then we can try that approach again, but it should not be a big deal.
35640
+ ct.valueSchema.optional()
35641
+ ).optional()
35636
35642
  ]))
35637
35643
  ).strict().describe("The coordination space stores the values for each scope of each coordination object.").optional(),
35638
35644
  layout: z.array(z.object({
@@ -43699,6 +43705,38 @@ const useTitleStyles = makeStyles((theme) => ({
43699
43705
  }
43700
43706
  }
43701
43707
  }));
43708
+ function arrayToString(arr) {
43709
+ const arrF = arr.filter((item) => item !== void 0);
43710
+ if (arrF.length === 0) {
43711
+ return "";
43712
+ }
43713
+ if (arrF.length === 1) {
43714
+ return arrF[0];
43715
+ }
43716
+ return `${arr.slice(0, -1).join(", ")} and ${arr.slice(-1)}`;
43717
+ }
43718
+ const ViewTypesToText = /* @__PURE__ */ new Map([
43719
+ [ViewType$1.DESCRIPTION, "description"],
43720
+ [ViewType$1.STATUS, "status view for debugging"],
43721
+ [ViewType$1.SCATTERPLOT, "scatterplot with embeddings"],
43722
+ [ViewType$1.SPATIAL, "spatial view"],
43723
+ [ViewType$1.SPATIAL_BETA, "spatial view"],
43724
+ [ViewType$1.HEATMAP, "cell by gene heatmap"],
43725
+ [ViewType$1.LAYER_CONTROLLER, "layer controller"],
43726
+ [ViewType$1.LAYER_CONTROLLER_BETA, "layer controller"],
43727
+ [ViewType$1.GENOMIC_PROFILES, "genome browser tracks with bar plots"],
43728
+ [ViewType$1.GATING, "scatterplot of gated gene expression data"],
43729
+ [ViewType$1.FEATURE_LIST, "interactive list of features"],
43730
+ [ViewType$1.OBS_SETS, "list of potential observation sets"],
43731
+ [ViewType$1.OBS_SET_SIZES, "sizes of selected observation sets"],
43732
+ [ViewType$1.OBS_SET_FEATURE_VALUE_DISTRIBUTION, "violin plot with values"],
43733
+ [ViewType$1.FEATURE_VALUE_HISTOGRAM, "distribution of values"]
43734
+ ]);
43735
+ function getAltText(config2) {
43736
+ const componentList = config2.layout.map((c) => c.component);
43737
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map((c) => ViewTypesToText.get(c) || "plugin view"))}.`;
43738
+ return altText;
43739
+ }
43702
43740
  const padding = 10;
43703
43741
  const margin = 5;
43704
43742
  function VitessceGrid(props) {
@@ -43719,6 +43757,7 @@ function VitessceGrid(props) {
43719
43757
  const [componentWidth] = useClosestVitessceContainerSize(containerRef);
43720
43758
  const classes = useVitessceContainerStyles();
43721
43759
  const titleClasses = useTitleStyles();
43760
+ const altText = useMemo(() => getAltText(config2), [configKey]);
43722
43761
  useEffect(() => {
43723
43762
  onResize();
43724
43763
  }, [rowHeight, onResize]);
@@ -43753,9 +43792,12 @@ function VitessceGrid(props) {
43753
43792
  {
43754
43793
  ref: containerRef,
43755
43794
  className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer),
43795
+ role: "group",
43796
+ "aria-label": altText,
43756
43797
  children: layout2 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
43757
43798
  VitessceGridLayout,
43758
43799
  {
43800
+ role: "group",
43759
43801
  layout: layout2,
43760
43802
  height: height2,
43761
43803
  rowHeight,
@@ -45826,6 +45868,132 @@ class LoaderResult {
45826
45868
  this.coordinationValues = coordinationValues;
45827
45869
  }
45828
45870
  }
45871
+ const $b5e257d569688ac6$var$defaultContext = {
45872
+ prefix: String(Math.round(Math.random() * 1e10)),
45873
+ current: 0
45874
+ };
45875
+ const $b5e257d569688ac6$var$SSRContext = /* @__PURE__ */ React__default.createContext($b5e257d569688ac6$var$defaultContext);
45876
+ const $b5e257d569688ac6$var$IsSSRContext = /* @__PURE__ */ React__default.createContext(false);
45877
+ let $b5e257d569688ac6$var$canUseDOM = Boolean(typeof window !== "undefined" && window.document && window.document.createElement);
45878
+ let $b5e257d569688ac6$var$componentIds = /* @__PURE__ */ new WeakMap();
45879
+ function $b5e257d569688ac6$var$useCounter(isDisabled = false) {
45880
+ let ctx = useContext($b5e257d569688ac6$var$SSRContext);
45881
+ let ref = useRef(null);
45882
+ if (ref.current === null && !isDisabled) {
45883
+ var _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_ReactCurrentOwner;
45884
+ let currentOwner = (_React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = React__default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) === null || _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === void 0 ? void 0 : (_React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_ReactCurrentOwner = _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner) === null || _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_ReactCurrentOwner === void 0 ? void 0 : _React___SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_ReactCurrentOwner.current;
45885
+ if (currentOwner) {
45886
+ let prevComponentValue = $b5e257d569688ac6$var$componentIds.get(currentOwner);
45887
+ if (prevComponentValue == null)
45888
+ $b5e257d569688ac6$var$componentIds.set(currentOwner, {
45889
+ id: ctx.current,
45890
+ state: currentOwner.memoizedState
45891
+ });
45892
+ else if (currentOwner.memoizedState !== prevComponentValue.state) {
45893
+ ctx.current = prevComponentValue.id;
45894
+ $b5e257d569688ac6$var$componentIds.delete(currentOwner);
45895
+ }
45896
+ }
45897
+ ref.current = ++ctx.current;
45898
+ }
45899
+ return ref.current;
45900
+ }
45901
+ function $b5e257d569688ac6$var$useLegacySSRSafeId(defaultId) {
45902
+ let ctx = useContext($b5e257d569688ac6$var$SSRContext);
45903
+ if (ctx === $b5e257d569688ac6$var$defaultContext && !$b5e257d569688ac6$var$canUseDOM)
45904
+ console.warn("When server rendering, you must wrap your application in an <SSRProvider> to ensure consistent ids are generated between the client and server.");
45905
+ let counter = $b5e257d569688ac6$var$useCounter(!!defaultId);
45906
+ let prefix2 = ctx === $b5e257d569688ac6$var$defaultContext && false ? "react-aria" : `react-aria${ctx.prefix}`;
45907
+ return defaultId || `${prefix2}-${counter}`;
45908
+ }
45909
+ function $b5e257d569688ac6$var$useModernSSRSafeId(defaultId) {
45910
+ let id = React__default.useId();
45911
+ let [didSSR] = useState($b5e257d569688ac6$export$535bd6ca7f90a273());
45912
+ let prefix2 = didSSR || false ? "react-aria" : `react-aria${$b5e257d569688ac6$var$defaultContext.prefix}`;
45913
+ return defaultId || `${prefix2}-${id}`;
45914
+ }
45915
+ const $b5e257d569688ac6$export$619500959fc48b26 = typeof React__default["useId"] === "function" ? $b5e257d569688ac6$var$useModernSSRSafeId : $b5e257d569688ac6$var$useLegacySSRSafeId;
45916
+ function $b5e257d569688ac6$var$getSnapshot() {
45917
+ return false;
45918
+ }
45919
+ function $b5e257d569688ac6$var$getServerSnapshot() {
45920
+ return true;
45921
+ }
45922
+ function $b5e257d569688ac6$var$subscribe(onStoreChange) {
45923
+ return () => {
45924
+ };
45925
+ }
45926
+ function $b5e257d569688ac6$export$535bd6ca7f90a273() {
45927
+ if (typeof React__default["useSyncExternalStore"] === "function")
45928
+ return React__default["useSyncExternalStore"]($b5e257d569688ac6$var$subscribe, $b5e257d569688ac6$var$getSnapshot, $b5e257d569688ac6$var$getServerSnapshot);
45929
+ return useContext($b5e257d569688ac6$var$IsSSRContext);
45930
+ }
45931
+ const $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c = typeof document !== "undefined" ? React__default.useLayoutEffect : () => {
45932
+ };
45933
+ let $bdb11010cef70236$var$idsUpdaterMap = /* @__PURE__ */ new Map();
45934
+ function $bdb11010cef70236$export$f680877a34711e37(defaultId) {
45935
+ let [value, setValue] = useState(defaultId);
45936
+ let nextId = useRef(null);
45937
+ let res = $b5e257d569688ac6$export$619500959fc48b26(value);
45938
+ let updateValue = useCallback((val) => {
45939
+ nextId.current = val;
45940
+ }, []);
45941
+ $bdb11010cef70236$var$idsUpdaterMap.set(res, updateValue);
45942
+ $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c(() => {
45943
+ let r2 = res;
45944
+ return () => {
45945
+ $bdb11010cef70236$var$idsUpdaterMap.delete(r2);
45946
+ };
45947
+ }, [
45948
+ res
45949
+ ]);
45950
+ useEffect(() => {
45951
+ let newId = nextId.current;
45952
+ if (newId) {
45953
+ nextId.current = null;
45954
+ setValue(newId);
45955
+ }
45956
+ });
45957
+ return res;
45958
+ }
45959
+ let $bbed8b41f857bcc0$var$transitionsByElement = /* @__PURE__ */ new Map();
45960
+ let $bbed8b41f857bcc0$var$transitionCallbacks = /* @__PURE__ */ new Set();
45961
+ function $bbed8b41f857bcc0$var$setupGlobalEvents() {
45962
+ if (typeof window === "undefined")
45963
+ return;
45964
+ let onTransitionStart = (e) => {
45965
+ let transitions2 = $bbed8b41f857bcc0$var$transitionsByElement.get(e.target);
45966
+ if (!transitions2) {
45967
+ transitions2 = /* @__PURE__ */ new Set();
45968
+ $bbed8b41f857bcc0$var$transitionsByElement.set(e.target, transitions2);
45969
+ e.target.addEventListener("transitioncancel", onTransitionEnd);
45970
+ }
45971
+ transitions2.add(e.propertyName);
45972
+ };
45973
+ let onTransitionEnd = (e) => {
45974
+ let properties2 = $bbed8b41f857bcc0$var$transitionsByElement.get(e.target);
45975
+ if (!properties2)
45976
+ return;
45977
+ properties2.delete(e.propertyName);
45978
+ if (properties2.size === 0) {
45979
+ e.target.removeEventListener("transitioncancel", onTransitionEnd);
45980
+ $bbed8b41f857bcc0$var$transitionsByElement.delete(e.target);
45981
+ }
45982
+ if ($bbed8b41f857bcc0$var$transitionsByElement.size === 0) {
45983
+ for (let cb of $bbed8b41f857bcc0$var$transitionCallbacks)
45984
+ cb();
45985
+ $bbed8b41f857bcc0$var$transitionCallbacks.clear();
45986
+ }
45987
+ };
45988
+ document.body.addEventListener("transitionrun", onTransitionStart);
45989
+ document.body.addEventListener("transitionend", onTransitionEnd);
45990
+ }
45991
+ if (typeof document !== "undefined") {
45992
+ if (document.readyState !== "loading")
45993
+ $bbed8b41f857bcc0$var$setupGlobalEvents();
45994
+ else
45995
+ document.addEventListener("DOMContentLoaded", $bbed8b41f857bcc0$var$setupGlobalEvents);
45996
+ }
45829
45997
  const useStyles = makeStyles((theme) => ({
45830
45998
  box: {
45831
45999
  boxSizing: "border-box"
@@ -45897,7 +46065,7 @@ function CellColorEncodingOption(props) {
45897
46065
  setCellColorEncoding
45898
46066
  } = props;
45899
46067
  const classes = useStyles();
45900
- const cellColorEncodingId = useId();
46068
+ const cellColorEncodingId = $bdb11010cef70236$export$f680877a34711e37();
45901
46069
  const observationsLabelNice = capitalize(observationsLabel);
45902
46070
  function handleColorEncodingChange(event) {
45903
46071
  setCellColorEncoding(event.target.value);
@@ -1 +1 @@
1
- {"version":3,"file":"VitessceGrid.d.ts","sourceRoot":"","sources":["../src/VitessceGrid.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;GAWG;AACH;IATyB,SAAS,EAAvB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACU,MAAM;IACE,SAAS,EAAjC,gBAAgB;IACQ,SAAS,EAAjC,gBAAgB;IACgB,iBAAiB,EAAjD,wBAAwB;gBAuFlC"}
1
+ {"version":3,"file":"VitessceGrid.d.ts","sourceRoot":"","sources":["../src/VitessceGrid.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;GAWG;AACH;IATyB,SAAS,EAAvB,MAAM;IACQ,MAAM,EAApB,MAAM;IACQ,KAAK,EAAnB,MAAM;IACQ,MAAM,EAApB,MAAM;IACU,MAAM;IACE,SAAS,EAAjC,gBAAgB;IACQ,SAAS,EAAjC,gBAAgB;IACgB,iBAAiB,EAAjD,wBAAwB;gBA4FlC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useEffect, useCallback, } from 'react';
2
+ import React, { useEffect, useCallback, useMemo, } from 'react';
3
3
  import clsx from 'clsx';
4
4
  import { VITESSCE_CONTAINER } from './classNames.js';
5
5
  import { VitessceGridLayout } from './vitessce-grid-layout/index.js';
@@ -8,6 +8,7 @@ import { useViewConfigStoreApi, useSetViewConfig, useSetLoaders, useEmitGridResi
8
8
  import { useClosestVitessceContainerSize, } from './hooks.js';
9
9
  import { useVitessceContainerStyles } from './shared-mui/container.js';
10
10
  import { useTitleStyles } from './title-styles.js';
11
+ import { getAltText } from './generate-alt-text.js';
11
12
  const padding = 10;
12
13
  const margin = 5;
13
14
  /**
@@ -29,6 +30,7 @@ export default function VitessceGrid(props) {
29
30
  const [componentWidth] = useClosestVitessceContainerSize(containerRef);
30
31
  const classes = useVitessceContainerStyles();
31
32
  const titleClasses = useTitleStyles();
33
+ const altText = useMemo(() => getAltText(config), [configKey]);
32
34
  // When the row height has changed, publish a GRID_RESIZE event.
33
35
  useEffect(() => {
34
36
  onResize();
@@ -59,5 +61,5 @@ export default function VitessceGrid(props) {
59
61
  setLoaders(newLoaders);
60
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
61
63
  }, [success, configKey]);
62
- return (_jsx("div", { ref: containerRef, className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer), children: layout ? (_jsx(VitessceGridLayout, { layout: layout, height: height, rowHeight: rowHeight, theme: theme, viewTypes: viewTypes, draggableHandle: titleClasses.title, margin: margin, padding: padding, onRemoveComponent: removeComponent, onLayoutChange: changeLayoutPostMount, isBounded: isBounded, onResize: onResize, onResizeStop: onResize })) : null }));
64
+ return (_jsx("div", { ref: containerRef, className: clsx(VITESSCE_CONTAINER, classes.vitessceContainer), role: "group", "aria-label": altText, children: layout ? (_jsx(VitessceGridLayout, { role: "group", layout: layout, height: height, rowHeight: rowHeight, theme: theme, viewTypes: viewTypes, draggableHandle: titleClasses.title, margin: margin, padding: padding, onRemoveComponent: removeComponent, onLayoutChange: changeLayoutPostMount, isBounded: isBounded, onResize: onResize, onResizeStop: onResize })) : null }));
63
65
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Method for converting an array with strings to a natural sentence,
3
+ * concatenating the items with commans, and the last item with 'and'.
4
+ * @param {Array} arr Array with text items.
5
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
6
+ */
7
+ export function arrayToString(arr: any[]): string;
8
+ /**
9
+ * Method for converting an array with strings to a natural sentence,
10
+ * concatenating the items with commans, and the last item with 'and'.
11
+ * @param {Object} config Vitessce config.
12
+ * @returns {string} Natural language sentence describing config (alt-text).
13
+ */
14
+ export function getAltText(config: Object): string;
15
+ /**
16
+ * Mapping of ViewType to natural language description of ViewType.
17
+ */
18
+ export const ViewTypesToText: Map<string, string>;
19
+ //# sourceMappingURL=generate-alt-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-alt-text.d.ts","sourceRoot":"","sources":["../src/generate-alt-text.js"],"names":[],"mappings":"AAEA;;;;;KAKK;AACL,2CAFe,MAAM,CAWpB;AAuBD;;;;;KAKK;AACL,mCAHa,MAAM,GACJ,MAAM,CAMpB;AA/BD;;KAEK;AACL,kDAgBG"}
@@ -0,0 +1,48 @@
1
+ import { ViewType } from '@vitessce/constants-internal';
2
+ /**
3
+ * Method for converting an array with strings to a natural sentence,
4
+ * concatenating the items with commans, and the last item with 'and'.
5
+ * @param {Array} arr Array with text items.
6
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
7
+ */
8
+ export function arrayToString(arr) {
9
+ const arrF = arr.filter(item => item !== undefined);
10
+ if (arrF.length === 0) {
11
+ return '';
12
+ }
13
+ if (arrF.length === 1) {
14
+ return arrF[0];
15
+ }
16
+ return `${arr.slice(0, -1).join(', ')} and ${arr.slice(-1)}`;
17
+ }
18
+ /**
19
+ * Mapping of ViewType to natural language description of ViewType.
20
+ */
21
+ export const ViewTypesToText = new Map([
22
+ [ViewType.DESCRIPTION, 'description'],
23
+ [ViewType.STATUS, 'status view for debugging'],
24
+ [ViewType.SCATTERPLOT, 'scatterplot with embeddings'],
25
+ [ViewType.SPATIAL, 'spatial view'],
26
+ [ViewType.SPATIAL_BETA, 'spatial view'],
27
+ [ViewType.HEATMAP, 'cell by gene heatmap'],
28
+ [ViewType.LAYER_CONTROLLER, 'layer controller'],
29
+ [ViewType.LAYER_CONTROLLER_BETA, 'layer controller'],
30
+ [ViewType.GENOMIC_PROFILES, 'genome browser tracks with bar plots'],
31
+ [ViewType.GATING, 'scatterplot of gated gene expression data'],
32
+ [ViewType.FEATURE_LIST, 'interactive list of features'],
33
+ [ViewType.OBS_SETS, 'list of potential observation sets'],
34
+ [ViewType.OBS_SET_SIZES, 'sizes of selected observation sets'],
35
+ [ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION, 'violin plot with values'],
36
+ [ViewType.FEATURE_VALUE_HISTOGRAM, 'distribution of values'],
37
+ ]);
38
+ /**
39
+ * Method for converting an array with strings to a natural sentence,
40
+ * concatenating the items with commans, and the last item with 'and'.
41
+ * @param {Object} config Vitessce config.
42
+ * @returns {string} Natural language sentence describing config (alt-text).
43
+ */
44
+ export function getAltText(config) {
45
+ const componentList = config.layout.map(c => c.component);
46
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map(c => ViewTypesToText.get(c) || 'plugin view'))}.`;
47
+ return altText;
48
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate-alt-text.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-alt-text.test.d.ts","sourceRoot":"","sources":["../src/generate-alt-text.test.js"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ViewType } from '@vitessce/constants-internal';
3
+ import { arrayToString, ViewTypesToText, getAltText } from './generate-alt-text.js';
4
+ describe('Tests for generating an alt text from the vitessce config', () => {
5
+ describe('Tests for arrayToString', () => {
6
+ it('should return empty string for empty array', () => {
7
+ const arr = [];
8
+ const str = arrayToString(arr);
9
+ expect(str).toEqual('');
10
+ });
11
+ it('should return the element if the araay only has one element', () => {
12
+ const arr = ['one'];
13
+ const str = arrayToString(arr);
14
+ expect(str).toEqual('one');
15
+ });
16
+ it('should return the elements concatenated with "and" when the array has two elements', () => {
17
+ const arr = ['one', 'two'];
18
+ const str = arrayToString(arr);
19
+ expect(str).toEqual('one and two');
20
+ });
21
+ it('should return the elements concatenated with ", " and the last two elements with "and" when the array has more than two elements', () => {
22
+ const arr = ['one', 'two', 'three', 'four'];
23
+ const str = arrayToString(arr);
24
+ expect(str).toEqual('one, two, three and four');
25
+ });
26
+ });
27
+ describe('Tests for (const) ViewTypesToText', () => {
28
+ it('should have a natural language mapping for every defined viewtype', () => {
29
+ expect(ViewTypesToText.length).toEqual(ViewType.length);
30
+ });
31
+ });
32
+ describe('Tests for getAltTest', () => {
33
+ const config = {
34
+ coordinationSpace: {},
35
+ datasets: {},
36
+ description: 'Spatial organization of the somatosensory cortex revealed by osmFISH',
37
+ initStrategy: 'auto',
38
+ layout: [
39
+ { component: 'description', props: { description: 'Codeluppi et al., Nature Methods 2018: Spatial organization of the somatosensory cortex revealed by osmFISH' }, x: 0, y: 0, w: 2 },
40
+ { component: 'layerController', x: 0, y: 1, w: 2, h: 4 },
41
+ { component: 'status', x: 0, y: 5, w: 2, h: 1 },
42
+ { component: 'spatial', props: { channelNamesVisible: true }, x: 2, y: 0, w: 4 },
43
+ { component: 'featureList', x: 9, y: 0, w: 3, h: 2 },
44
+ { component: 'obsSets', x: 9, y: 3, w: 3, h: 2 },
45
+ { component: 'heatmap', props: { transpose: true }, x: 2, y: 4, w: 5 },
46
+ { component: 'obsSetFeatureValueDistribution', x: 7, y: 4, w: 5, h: 2 },
47
+ { component: 'scatterplot', x: 6, y: 0, w: 3, h: 2 },
48
+ { component: 'scatterplot', x: 6, y: 2, w: 3, h: 2 },
49
+ ],
50
+ name: 'Codeluppi et al., Nature Methods 2018',
51
+ uid: 'A',
52
+ version: '1.0.16',
53
+ };
54
+ const altText = getAltText(config);
55
+ it('should return a string', () => {
56
+ expect(typeof altText).toEqual('string');
57
+ });
58
+ it('should contain the number of views', () => {
59
+ const hasNViews = altText.includes('10');
60
+ expect(hasNViews).toEqual(true);
61
+ });
62
+ it('should contain mappings', () => {
63
+ const hasHeatmap = altText.includes('heatmap') && altText.includes('spatial') && altText.includes('description') && altText.includes('scatterplot');
64
+ expect(hasHeatmap).toEqual(true);
65
+ });
66
+ });
67
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"CellColorEncodingOption.d.ts","sourceRoot":"","sources":["../../src/shared-plot-options/CellColorEncodingOption.js"],"names":[],"mappings":"AAMA,yEAyCC"}
1
+ {"version":3,"file":"CellColorEncodingOption.d.ts","sourceRoot":"","sources":["../../src/shared-plot-options/CellColorEncodingOption.js"],"names":[],"mappings":"AAOA,yEAyCC"}
@@ -1,5 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useId } from 'react';
2
+ import React from 'react';
3
+ import { useId } from 'react-aria';
3
4
  import { TableCell, TableRow } from '@material-ui/core';
4
5
  import { capitalize } from '@vitessce/utils';
5
6
  import OptionSelect from './OptionSelect.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitessce/vit-s",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "author": "Gehlenborg Lab",
5
5
  "homepage": "http://vitessce.io",
6
6
  "repository": {
@@ -28,10 +28,11 @@
28
28
  "react-grid-layout-with-lodash": "^1.3.5",
29
29
  "uuid": "^9.0.0",
30
30
  "zustand": "^3.5.10",
31
- "@vitessce/plugins": "3.2.0",
32
- "@vitessce/schemas": "3.2.0",
33
- "@vitessce/constants-internal": "3.2.0",
34
- "@vitessce/utils": "3.2.0"
31
+ "react-aria": "^3.28.0",
32
+ "@vitessce/constants-internal": "3.2.2",
33
+ "@vitessce/plugins": "3.2.2",
34
+ "@vitessce/schemas": "3.2.2",
35
+ "@vitessce/utils": "3.2.2"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@testing-library/jest-dom": "^5.16.4",
@@ -1,6 +1,7 @@
1
1
  import React, {
2
2
  useEffect,
3
3
  useCallback,
4
+ useMemo,
4
5
  } from 'react';
5
6
  import clsx from 'clsx';
6
7
  import { VITESSCE_CONTAINER } from './classNames.js';
@@ -20,6 +21,7 @@ import {
20
21
  } from './hooks.js';
21
22
  import { useVitessceContainerStyles } from './shared-mui/container.js';
22
23
  import { useTitleStyles } from './title-styles.js';
24
+ import { getAltText } from './generate-alt-text.js';
23
25
 
24
26
  const padding = 10;
25
27
  const margin = 5;
@@ -58,6 +60,8 @@ export default function VitessceGrid(props) {
58
60
  const classes = useVitessceContainerStyles();
59
61
  const titleClasses = useTitleStyles();
60
62
 
63
+ const altText = useMemo(() => getAltText(config), [configKey]);
64
+
61
65
  // When the row height has changed, publish a GRID_RESIZE event.
62
66
  useEffect(() => {
63
67
  onResize();
@@ -101,9 +105,12 @@ export default function VitessceGrid(props) {
101
105
  <div
102
106
  ref={containerRef}
103
107
  className={clsx(VITESSCE_CONTAINER, classes.vitessceContainer)}
108
+ role="group"
109
+ aria-label={altText}
104
110
  >
105
111
  {layout ? (
106
112
  <VitessceGridLayout
113
+ role="group"
107
114
  layout={layout}
108
115
  height={height}
109
116
  rowHeight={rowHeight}
@@ -0,0 +1,51 @@
1
+ import { ViewType } from '@vitessce/constants-internal';
2
+
3
+ /**
4
+ * Method for converting an array with strings to a natural sentence,
5
+ * concatenating the items with commans, and the last item with 'and'.
6
+ * @param {Array} arr Array with text items.
7
+ * @returns {string} Natural language sentence in the form of 'a, b and c', where a/b/c are items.
8
+ */
9
+ export function arrayToString(arr) {
10
+ const arrF = arr.filter(item => item !== undefined);
11
+ if (arrF.length === 0) {
12
+ return '';
13
+ }
14
+ if (arrF.length === 1) {
15
+ return arrF[0];
16
+ }
17
+ return `${arr.slice(0, -1).join(', ')} and ${arr.slice(-1)}`;
18
+ }
19
+
20
+ /**
21
+ * Mapping of ViewType to natural language description of ViewType.
22
+ */
23
+ export const ViewTypesToText = new Map([
24
+ [ViewType.DESCRIPTION, 'description'],
25
+ [ViewType.STATUS, 'status view for debugging'],
26
+ [ViewType.SCATTERPLOT, 'scatterplot with embeddings'],
27
+ [ViewType.SPATIAL, 'spatial view'],
28
+ [ViewType.SPATIAL_BETA, 'spatial view'],
29
+ [ViewType.HEATMAP, 'cell by gene heatmap'],
30
+ [ViewType.LAYER_CONTROLLER, 'layer controller'],
31
+ [ViewType.LAYER_CONTROLLER_BETA, 'layer controller'],
32
+ [ViewType.GENOMIC_PROFILES, 'genome browser tracks with bar plots'],
33
+ [ViewType.GATING, 'scatterplot of gated gene expression data'],
34
+ [ViewType.FEATURE_LIST, 'interactive list of features'],
35
+ [ViewType.OBS_SETS, 'list of potential observation sets'],
36
+ [ViewType.OBS_SET_SIZES, 'sizes of selected observation sets'],
37
+ [ViewType.OBS_SET_FEATURE_VALUE_DISTRIBUTION, 'violin plot with values'],
38
+ [ViewType.FEATURE_VALUE_HISTOGRAM, 'distribution of values'],
39
+ ]);
40
+
41
+ /**
42
+ * Method for converting an array with strings to a natural sentence,
43
+ * concatenating the items with commans, and the last item with 'and'.
44
+ * @param {Object} config Vitessce config.
45
+ * @returns {string} Natural language sentence describing config (alt-text).
46
+ */
47
+ export function getAltText(config) {
48
+ const componentList = config.layout.map(c => c.component);
49
+ const altText = `Vitessce grid with ${componentList.length} views, including ${arrayToString(componentList.map(c => ViewTypesToText.get(c) || 'plugin view'))}.`;
50
+ return altText;
51
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ViewType } from '@vitessce/constants-internal';
3
+ import { arrayToString, ViewTypesToText, getAltText } from './generate-alt-text.js';
4
+
5
+ describe('Tests for generating an alt text from the vitessce config', () => {
6
+ describe('Tests for arrayToString', () => {
7
+ it('should return empty string for empty array', () => {
8
+ const arr = [];
9
+ const str = arrayToString(arr);
10
+ expect(str).toEqual('');
11
+ });
12
+
13
+ it('should return the element if the araay only has one element', () => {
14
+ const arr = ['one'];
15
+ const str = arrayToString(arr);
16
+ expect(str).toEqual('one');
17
+ });
18
+
19
+ it('should return the elements concatenated with "and" when the array has two elements', () => {
20
+ const arr = ['one', 'two'];
21
+ const str = arrayToString(arr);
22
+ expect(str).toEqual('one and two');
23
+ });
24
+
25
+ it('should return the elements concatenated with ", " and the last two elements with "and" when the array has more than two elements', () => {
26
+ const arr = ['one', 'two', 'three', 'four'];
27
+ const str = arrayToString(arr);
28
+ expect(str).toEqual('one, two, three and four');
29
+ });
30
+ });
31
+
32
+ describe('Tests for (const) ViewTypesToText', () => {
33
+ it('should have a natural language mapping for every defined viewtype', () => {
34
+ expect(ViewTypesToText.length).toEqual(ViewType.length);
35
+ });
36
+ });
37
+
38
+ describe('Tests for getAltTest', () => {
39
+ const config = {
40
+ coordinationSpace: {},
41
+ datasets: {},
42
+ description: 'Spatial organization of the somatosensory cortex revealed by osmFISH',
43
+ initStrategy: 'auto',
44
+ layout: [
45
+ { component: 'description', props: { description: 'Codeluppi et al., Nature Methods 2018: Spatial organization of the somatosensory cortex revealed by osmFISH' }, x: 0, y: 0, w: 2 },
46
+ { component: 'layerController', x: 0, y: 1, w: 2, h: 4 },
47
+ { component: 'status', x: 0, y: 5, w: 2, h: 1 },
48
+ { component: 'spatial', props: { channelNamesVisible: true }, x: 2, y: 0, w: 4 },
49
+ { component: 'featureList', x: 9, y: 0, w: 3, h: 2 },
50
+ { component: 'obsSets', x: 9, y: 3, w: 3, h: 2 },
51
+ { component: 'heatmap', props: { transpose: true }, x: 2, y: 4, w: 5 },
52
+ { component: 'obsSetFeatureValueDistribution', x: 7, y: 4, w: 5, h: 2 },
53
+ { component: 'scatterplot', x: 6, y: 0, w: 3, h: 2 },
54
+ { component: 'scatterplot', x: 6, y: 2, w: 3, h: 2 },
55
+ ],
56
+ name: 'Codeluppi et al., Nature Methods 2018',
57
+ uid: 'A',
58
+ version: '1.0.16',
59
+ };
60
+ const altText = getAltText(config);
61
+ it('should return a string', () => {
62
+ expect(typeof altText).toEqual('string');
63
+ });
64
+ it('should contain the number of views', () => {
65
+ const hasNViews = altText.includes('10');
66
+ expect(hasNViews).toEqual(true);
67
+ });
68
+ it('should contain mappings', () => {
69
+ const hasHeatmap = altText.includes('heatmap') && altText.includes('spatial') && altText.includes('description') && altText.includes('scatterplot');
70
+ expect(hasHeatmap).toEqual(true);
71
+ });
72
+ });
73
+ });
@@ -1,4 +1,5 @@
1
- import React, { useId } from 'react';
1
+ import React from 'react';
2
+ import { useId } from 'react-aria';
2
3
  import { TableCell, TableRow } from '@material-ui/core';
3
4
  import { capitalize } from '@vitessce/utils';
4
5
  import OptionSelect from './OptionSelect.js';