@react-magma/charts 15.0.0-rc.2 → 15.0.0-rc.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-magma/charts",
3
- "version": "15.0.0-rc.2",
3
+ "version": "15.0.0-rc.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,10 +17,14 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({
17
17
  disconnect: jest.fn(),
18
18
  }));
19
19
 
20
- // Capture MutationObserver callbacks so we can trigger them manually
21
- let mutationObserverCallback;
20
+ // Capture MutationObserver callbacks so we can trigger them manually.
21
+ // Multiple observers may be created per component; we collect all of them and
22
+ // call each one so that no observer is silently skipped.
23
+ let mutationObserverCallbacks = [];
24
+ const mutationObserverCallback = mutations =>
25
+ mutationObserverCallbacks.forEach(cb => cb(mutations));
22
26
  global.MutationObserver = jest.fn().mockImplementation(callback => {
23
- mutationObserverCallback = callback;
27
+ mutationObserverCallbacks.push(callback);
24
28
  return {
25
29
  observe: jest.fn(),
26
30
  disconnect: jest.fn(),
@@ -603,6 +607,7 @@ describe('CarbonChart', () => {
603
607
  let otherButton;
604
608
 
605
609
  beforeEach(() => {
610
+ mutationObserverCallbacks = [];
606
611
  jest.useFakeTimers();
607
612
  jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
608
613
  cb(0);
@@ -24,11 +24,13 @@ import {
24
24
  import styled from '@emotion/styled';
25
25
  import { transparentize } from 'polished';
26
26
  import {
27
+ Announce,
27
28
  DropdownDivider,
28
29
  DropdownMenuItem,
29
30
  ThemeInterface,
30
31
  ThemeContext,
31
32
  useIsInverse,
33
+ VisuallyHidden,
32
34
  } from 'react-magma-dom';
33
35
  import {
34
36
  FullscreenExitIcon,
@@ -1075,7 +1077,9 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
1075
1077
 
1076
1078
  const [isTableOpen, setIsTableOpen] = React.useState(false);
1077
1079
  const [isFullscreen, setIsFullscreen] = React.useState(false);
1080
+ const [legendAnnouncement, setLegendAnnouncement] = React.useState('');
1078
1081
  const lastTableTriggerRef = React.useRef<HTMLButtonElement | null>(null);
1082
+ const legendAnnouncedRef = React.useRef(false);
1079
1083
 
1080
1084
  const mergedRef = React.useCallback(
1081
1085
  (node: HTMLDivElement | null) => {
@@ -1179,7 +1183,9 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
1179
1183
  if (parent?.tagName === 'FIELDSET') {
1180
1184
  const existingCaption = parent.querySelector('legend');
1181
1185
 
1182
- if (existingCaption) existingCaption.textContent = legendLabel;
1186
+ if (existingCaption && existingCaption.textContent !== legendLabel) {
1187
+ existingCaption.textContent = legendLabel;
1188
+ }
1183
1189
 
1184
1190
  return;
1185
1191
  }
@@ -1290,12 +1296,61 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
1290
1296
  return () => clearTimeout(timer);
1291
1297
  }, [type, dataSet]);
1292
1298
 
1299
+ React.useEffect(() => {
1300
+ const wrapper = internalRef.current;
1301
+
1302
+ if (!wrapper) return;
1303
+
1304
+ const observer = new MutationObserver(() => {
1305
+ const allItems = wrapper.querySelectorAll<HTMLElement>(
1306
+ '.legend-item:not(.additional)'
1307
+ );
1308
+
1309
+ if (allItems.length <= 1) return;
1310
+
1311
+ const activeLabels: string[] = [];
1312
+
1313
+ allItems.forEach(el => {
1314
+ const checkbox = el.querySelector<HTMLElement>('.checkbox');
1315
+ const label = el.querySelector('p');
1316
+
1317
+ if (checkbox?.getAttribute('aria-checked') === 'true' && label) {
1318
+ activeLabels.push(label.textContent?.trim() || '');
1319
+ }
1320
+ });
1321
+
1322
+ if (activeLabels.length === 1) {
1323
+ if (!legendAnnouncedRef.current) {
1324
+ legendAnnouncedRef.current = true;
1325
+ setLegendAnnouncement(`Only ${activeLabels[0]} is selected`);
1326
+ }
1327
+ } else if (activeLabels.length === allItems.length) {
1328
+ legendAnnouncedRef.current = false;
1329
+ setLegendAnnouncement('All items selected');
1330
+ } else {
1331
+ legendAnnouncedRef.current = false;
1332
+ setLegendAnnouncement('');
1333
+ }
1334
+ });
1335
+
1336
+ observer.observe(wrapper, {
1337
+ subtree: true,
1338
+ attributes: true,
1339
+ attributeFilter: ['aria-checked'],
1340
+ });
1341
+
1342
+ return () => observer.disconnect();
1343
+ }, [type, dataSet]);
1344
+
1293
1345
  const groupsLength = Object.keys(buildColors()).length;
1294
1346
 
1295
1347
  const showTable = chartToolbar?.showAsTable !== false;
1296
1348
 
1297
1349
  return (
1298
1350
  <FullscreenRoot ref={mergedRef} isInverse={isInverse} theme={theme}>
1351
+ <VisuallyHidden>
1352
+ <Announce>{legendAnnouncement}</Announce>
1353
+ </VisuallyHidden>
1299
1354
  <CarbonChartWrapper
1300
1355
  data-testid={testId}
1301
1356
  isInverse={isInverse}