@react-magma/charts 15.0.0-rc.2 → 15.0.0-rc.4
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/charts.js +47 -2
- package/dist/charts.js.map +1 -1
- package/dist/charts.modern.module.js +48 -3
- package/dist/charts.modern.module.js.map +1 -1
- package/dist/charts.umd.js +209 -7
- package/dist/charts.umd.js.map +1 -1
- package/dist/components/CarbonChart/CarbonChart.d.ts +5 -0
- package/package.json +1 -1
- package/src/components/CarbonChart/CarbonChart.test.js +29 -3
- package/src/components/CarbonChart/CarbonChart.tsx +66 -2
- package/src/components/CarbonChart/CarbonChartBar.stories.tsx +9 -2
|
@@ -56,6 +56,11 @@ export interface ChartToolbarConfig {
|
|
|
56
56
|
* @default 2
|
|
57
57
|
*/
|
|
58
58
|
tableHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
59
|
+
/**
|
|
60
|
+
* Heading level for the chart title.
|
|
61
|
+
* @default 2
|
|
62
|
+
*/
|
|
63
|
+
titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
59
64
|
}
|
|
60
65
|
export interface CarbonChartProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
61
66
|
dataSet: Array<object>;
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -848,6 +853,27 @@ describe('CarbonChart', () => {
|
|
|
848
853
|
).not.toBeInTheDocument();
|
|
849
854
|
});
|
|
850
855
|
|
|
856
|
+
it('should render the chart title as h2 by default', () => {
|
|
857
|
+
render(<CarbonChart {...toolbarProps} />);
|
|
858
|
+
|
|
859
|
+
expect(
|
|
860
|
+
screen.getByRole('heading', { level: 2, name: chartOptions.title })
|
|
861
|
+
).toBeInTheDocument();
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('should render the chart title at the level set by titleLevel', () => {
|
|
865
|
+
render(
|
|
866
|
+
<CarbonChart {...toolbarProps} chartToolbar={{ titleLevel: 3 }} />
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
expect(
|
|
870
|
+
screen.getByRole('heading', { level: 3, name: chartOptions.title })
|
|
871
|
+
).toBeInTheDocument();
|
|
872
|
+
expect(
|
|
873
|
+
screen.queryByRole('heading', { level: 2, name: chartOptions.title })
|
|
874
|
+
).not.toBeInTheDocument();
|
|
875
|
+
});
|
|
876
|
+
|
|
851
877
|
it('should always render the more options dropdown with built-in download items', () => {
|
|
852
878
|
render(<CarbonChart {...toolbarProps} />);
|
|
853
879
|
|
|
@@ -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,
|
|
@@ -103,6 +105,11 @@ export interface ChartToolbarConfig {
|
|
|
103
105
|
* @default 2
|
|
104
106
|
*/
|
|
105
107
|
tableHeaderLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
108
|
+
/**
|
|
109
|
+
* Heading level for the chart title.
|
|
110
|
+
* @default 2
|
|
111
|
+
*/
|
|
112
|
+
titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
export interface CarbonChartProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -1022,7 +1029,11 @@ function CarbonChartToolbar({
|
|
|
1022
1029
|
isInverse={isInverse}
|
|
1023
1030
|
theme={theme}
|
|
1024
1031
|
>
|
|
1025
|
-
<ChartTitle
|
|
1032
|
+
<ChartTitle
|
|
1033
|
+
as={`h${config.titleLevel ?? 2}` as keyof JSX.IntrinsicElements}
|
|
1034
|
+
isInverse={isInverse}
|
|
1035
|
+
theme={theme}
|
|
1036
|
+
>
|
|
1026
1037
|
{resolvedTitle}
|
|
1027
1038
|
</ChartTitle>
|
|
1028
1039
|
<ToolbarActions theme={theme}>
|
|
@@ -1075,7 +1086,9 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1075
1086
|
|
|
1076
1087
|
const [isTableOpen, setIsTableOpen] = React.useState(false);
|
|
1077
1088
|
const [isFullscreen, setIsFullscreen] = React.useState(false);
|
|
1089
|
+
const [legendAnnouncement, setLegendAnnouncement] = React.useState('');
|
|
1078
1090
|
const lastTableTriggerRef = React.useRef<HTMLButtonElement | null>(null);
|
|
1091
|
+
const legendAnnouncedRef = React.useRef(false);
|
|
1079
1092
|
|
|
1080
1093
|
const mergedRef = React.useCallback(
|
|
1081
1094
|
(node: HTMLDivElement | null) => {
|
|
@@ -1179,7 +1192,9 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1179
1192
|
if (parent?.tagName === 'FIELDSET') {
|
|
1180
1193
|
const existingCaption = parent.querySelector('legend');
|
|
1181
1194
|
|
|
1182
|
-
if (existingCaption
|
|
1195
|
+
if (existingCaption && existingCaption.textContent !== legendLabel) {
|
|
1196
|
+
existingCaption.textContent = legendLabel;
|
|
1197
|
+
}
|
|
1183
1198
|
|
|
1184
1199
|
return;
|
|
1185
1200
|
}
|
|
@@ -1290,12 +1305,61 @@ export const CarbonChart = React.forwardRef<HTMLDivElement, CarbonChartProps>(
|
|
|
1290
1305
|
return () => clearTimeout(timer);
|
|
1291
1306
|
}, [type, dataSet]);
|
|
1292
1307
|
|
|
1308
|
+
React.useEffect(() => {
|
|
1309
|
+
const wrapper = internalRef.current;
|
|
1310
|
+
|
|
1311
|
+
if (!wrapper) return;
|
|
1312
|
+
|
|
1313
|
+
const observer = new MutationObserver(() => {
|
|
1314
|
+
const allItems = wrapper.querySelectorAll<HTMLElement>(
|
|
1315
|
+
'.legend-item:not(.additional)'
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
if (allItems.length <= 1) return;
|
|
1319
|
+
|
|
1320
|
+
const activeLabels: string[] = [];
|
|
1321
|
+
|
|
1322
|
+
allItems.forEach(el => {
|
|
1323
|
+
const checkbox = el.querySelector<HTMLElement>('.checkbox');
|
|
1324
|
+
const label = el.querySelector('p');
|
|
1325
|
+
|
|
1326
|
+
if (checkbox?.getAttribute('aria-checked') === 'true' && label) {
|
|
1327
|
+
activeLabels.push(label.textContent?.trim() || '');
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
if (activeLabels.length === 1) {
|
|
1332
|
+
if (!legendAnnouncedRef.current) {
|
|
1333
|
+
legendAnnouncedRef.current = true;
|
|
1334
|
+
setLegendAnnouncement(`Only ${activeLabels[0]} is selected`);
|
|
1335
|
+
}
|
|
1336
|
+
} else if (activeLabels.length === allItems.length) {
|
|
1337
|
+
legendAnnouncedRef.current = false;
|
|
1338
|
+
setLegendAnnouncement('All items selected');
|
|
1339
|
+
} else {
|
|
1340
|
+
legendAnnouncedRef.current = false;
|
|
1341
|
+
setLegendAnnouncement('');
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
observer.observe(wrapper, {
|
|
1346
|
+
subtree: true,
|
|
1347
|
+
attributes: true,
|
|
1348
|
+
attributeFilter: ['aria-checked'],
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
return () => observer.disconnect();
|
|
1352
|
+
}, [type, dataSet]);
|
|
1353
|
+
|
|
1293
1354
|
const groupsLength = Object.keys(buildColors()).length;
|
|
1294
1355
|
|
|
1295
1356
|
const showTable = chartToolbar?.showAsTable !== false;
|
|
1296
1357
|
|
|
1297
1358
|
return (
|
|
1298
1359
|
<FullscreenRoot ref={mergedRef} isInverse={isInverse} theme={theme}>
|
|
1360
|
+
<VisuallyHidden>
|
|
1361
|
+
<Announce>{legendAnnouncement}</Announce>
|
|
1362
|
+
</VisuallyHidden>
|
|
1299
1363
|
<CarbonChartWrapper
|
|
1300
1364
|
data-testid={testId}
|
|
1301
1365
|
isInverse={isInverse}
|
|
@@ -18,12 +18,19 @@ export default {
|
|
|
18
18
|
control: { type: 'select' },
|
|
19
19
|
options: Object.values(CarbonChartType),
|
|
20
20
|
},
|
|
21
|
+
titleLevel: {
|
|
22
|
+
control: { type: 'select' },
|
|
23
|
+
options: [1, 2, 3, 4, 5, 6],
|
|
24
|
+
description: 'Heading level for the chart title (h1–h6).',
|
|
25
|
+
},
|
|
21
26
|
},
|
|
22
27
|
} as Meta;
|
|
23
28
|
|
|
24
|
-
const Template: StoryFn<
|
|
29
|
+
const Template: StoryFn<
|
|
30
|
+
CarbonChartProps & { titleLevel?: 1 | 2 | 3 | 4 | 5 | 6 }
|
|
31
|
+
> = ({ titleLevel, ...args }) => (
|
|
25
32
|
<Card isInverse={args.isInverse} style={{ padding: '12px' }}>
|
|
26
|
-
<CarbonChart {...args} />
|
|
33
|
+
<CarbonChart {...args} chartToolbar={{ titleLevel }} />
|
|
27
34
|
</Card>
|
|
28
35
|
);
|
|
29
36
|
|