@cdc/map 4.26.3 → 4.26.5

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.
Files changed (105) hide show
  1. package/CONFIG.md +268 -0
  2. package/README.md +74 -24
  3. package/dist/cdcmap-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcmap.js +29168 -27482
  6. package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
  7. package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
  8. package/examples/county-hsa-toggle.json +51993 -0
  9. package/examples/custom-map-layers.json +2 -2
  10. package/examples/default-county.json +6 -3
  11. package/examples/minimal-example.json +73 -0
  12. package/examples/private/annotation-bug.json +2 -2
  13. package/examples/private/css-issue.json +314 -0
  14. package/examples/private/region-breaking.json +1639 -0
  15. package/examples/private/test1.json +27247 -0
  16. package/package.json +4 -4
  17. package/src/CdcMapComponent.tsx +107 -14
  18. package/src/_stories/CdcMap.AltText.stories.tsx +122 -0
  19. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +600 -0
  20. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  21. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  22. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  23. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  24. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  25. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  26. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  27. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  28. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  29. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  30. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  31. package/src/_stories/CdcMap.FocusVisibility.stories.tsx +87 -0
  32. package/src/_stories/CdcMap.HiddenMount.stories.tsx +69 -0
  33. package/src/_stories/CdcMap.ResetBehavior.stories.tsx +32 -0
  34. package/src/_stories/CdcMap.Zoom.stories.tsx +111 -0
  35. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +60 -0
  36. package/src/_stories/_mock/alt_text_metadata.json +65 -0
  37. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  38. package/src/_stories/_mock/world-bubble-reset.json +138 -0
  39. package/src/_stories/_mock/world-data-zoom-filters.json +166 -0
  40. package/src/components/Annotation/AnnotationList.tsx +1 -1
  41. package/src/components/BubbleList.tsx +13 -0
  42. package/src/components/EditorPanel/components/EditorPanel.tsx +637 -382
  43. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  44. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +26 -13
  46. package/src/components/EditorPanel/components/editorPanel.styles.css +22 -2
  47. package/src/components/FilterControls.tsx +21 -0
  48. package/src/components/Legend/components/Legend.tsx +3 -3
  49. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  50. package/src/components/SmallMultiples/SmallMultiples.tsx +2 -2
  51. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  52. package/src/components/UsaMap/components/UsaMap.County.tsx +309 -108
  53. package/src/components/UsaMap/components/UsaMap.Region.tsx +5 -2
  54. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +33 -10
  55. package/src/components/UsaMap/components/UsaMap.State.tsx +10 -3
  56. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  57. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  58. package/src/components/WorldMap/WorldMap.tsx +37 -4
  59. package/src/components/WorldMap/data/world-topo.json +1 -1
  60. package/src/components/ZoomableGroup.tsx +23 -3
  61. package/src/components/filterControls.styles.css +6 -0
  62. package/src/data/initial-state.js +3 -0
  63. package/src/data/supported-counties.json +1 -1
  64. package/src/helpers/countyTerritories.ts +38 -0
  65. package/src/helpers/dataTableHelpers.ts +35 -6
  66. package/src/helpers/generateRuntimeFilters.ts +2 -1
  67. package/src/helpers/handleMapAriaLabels.ts +45 -30
  68. package/src/helpers/shouldAutoResetSingleStateZoom.ts +22 -0
  69. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  70. package/src/helpers/tests/handleMapAriaLabels.test.ts +71 -0
  71. package/src/helpers/tests/shouldAutoResetSingleStateZoom.test.ts +71 -0
  72. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  73. package/src/hooks/useGeoClickHandler.ts +13 -1
  74. package/src/hooks/useMapLayers.tsx +1 -1
  75. package/src/hooks/useStateZoom.tsx +39 -20
  76. package/src/hooks/useTooltip.test.tsx +2 -16
  77. package/src/hooks/useTooltip.ts +18 -7
  78. package/src/index.jsx +5 -2
  79. package/src/scss/main.scss +6 -21
  80. package/src/scss/map.scss +20 -0
  81. package/src/store/map.actions.ts +5 -2
  82. package/src/store/map.reducer.ts +12 -3
  83. package/src/test/CdcMap.test.jsx +24 -0
  84. package/src/types/MapConfig.ts +11 -0
  85. package/src/types/MapContext.ts +6 -1
  86. package/topojson-updater/README.txt +1 -1
  87. package/dist/cdcmap-vr9HZwRt.es.js +0 -6
  88. package/examples/__data__/city-state-data.json +0 -668
  89. package/examples/city-state.json +0 -434
  90. package/examples/default-world-data.json +0 -1450
  91. package/examples/new-cities.json +0 -656
  92. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3648
  93. package/topojson-updater/package-lock.json +0 -223
  94. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  95. /package/src/_stories/{CdcMap.Defaults.stories.tsx → CdcMap.Defaults.smoke.stories.tsx} +0 -0
  96. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  97. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  98. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  99. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  100. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  101. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  102. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  103. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  104. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  105. /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@cdc/map",
3
- "version": "4.26.3",
3
+ "version": "4.26.5",
4
4
  "description": "React component for visualizing tabular data on a map of the United States or the world.",
5
5
  "license": "Apache-2.0",
6
6
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
7
7
  "dependencies": {
8
- "@cdc/core": "^4.26.3",
8
+ "@cdc/core": "^4.26.5",
9
9
  "@googlemaps/markerclusterer": "^2.5.3",
10
10
  "@hello-pangea/dnd": "^16.2.0",
11
11
  "@react-google-maps/api": "^2.20.8",
@@ -24,7 +24,7 @@
24
24
  "d3-scale": "^4.0.2",
25
25
  "d3-selection": "^3.0.0",
26
26
  "d3-zoom": "^3.0.0",
27
- "dompurify": "^3.3.1",
27
+ "dompurify": "^3.4.0",
28
28
  "html-react-parser": "^5.2.3",
29
29
  "leaflet": "^1.9.4",
30
30
  "lodash": "^4.17.23",
@@ -42,7 +42,7 @@
42
42
  "vite-plugin-svgr": "^4.2.0",
43
43
  "whatwg-fetch": "^3.6.20"
44
44
  },
45
- "gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
45
+ "gitHead": "61c025165d96b45a6002c34582c5a622a9d865a9",
46
46
  "main": "dist/cdcmap",
47
47
  "moduleName": "CdcMap",
48
48
  "peerDependencies": {
@@ -14,6 +14,7 @@ import SkipTo from '@cdc/core/components/elements/SkipTo'
14
14
  import Title from '@cdc/core/components/ui/Title'
15
15
  import Waiting from '@cdc/core/components/Waiting'
16
16
  import FootnotesStandAlone from '@cdc/core/components/Footnotes/FootnotesStandAlone'
17
+ import { supportedStatesFipsCodes, supportedCounties } from './data/supported-geos'
17
18
 
18
19
  // types
19
20
  import { type MapConfig } from './types/MapConfig'
@@ -24,10 +25,17 @@ import './scss/main.scss'
24
25
  import './cdcMapComponent.styles.css'
25
26
 
26
27
  // Core Helpers
27
- import { getQueryStringFilterValue, isFilterHiddenByQuery } from '@cdc/core/helpers/queryStringUtils'
28
+ import {
29
+ getQueryStringFilterValue,
30
+ isFilterHiddenByQuery,
31
+ removeQueryParam,
32
+ updateQueryParam,
33
+ updateQueryParams
34
+ } from '@cdc/core/helpers/queryStringUtils'
28
35
  import { generateRuntimeFilters } from './helpers/generateRuntimeFilters'
29
36
  import { type MapReducerType, MapState } from './store/map.reducer'
30
37
  import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
38
+ import { hasVisibleVizFilters } from '@cdc/core/helpers/filterVisibility'
31
39
  import { processMarkupVariables } from '@cdc/core/helpers/markupProcessor'
32
40
 
33
41
  // Map Helpers
@@ -47,7 +55,6 @@ import { generateRuntimeLegend } from './helpers/generateRuntimeLegend'
47
55
  import generateRuntimeData from './helpers/generateRuntimeData'
48
56
  import { reloadURLData } from './helpers/urlDataHelpers'
49
57
  import { observeMapSvgLoaded } from './helpers/mapObserverHelpers'
50
- import { buildBodyWrapClassNames, buildSectionClassNames } from './helpers/componentHelpers'
51
58
  import { shouldShowDataTable, filterCountyTableRuntimeDataByStateCode } from './helpers/dataTableHelpers'
52
59
  import { prepareSmallMultiplesDataTable } from './helpers/smallMultiplesHelpers'
53
60
 
@@ -66,7 +73,6 @@ import useLegendMemo from './hooks/useLegendMemo'
66
73
  import { LegendMemoProvider } from './context/LegendMemoContext'
67
74
  import { VizFilter } from '@cdc/core/types/VizFilter'
68
75
  import { getInitialState, mapReducer } from './store/map.reducer'
69
- import { RuntimeData } from './types/RuntimeData'
70
76
  import defaults from './data/initial-state'
71
77
  import { LEGACY_MAP_DEFAULTS } from './data/legacy-defaults'
72
78
  import { backfillDefaults } from '@cdc/core/helpers/backfillDefaults'
@@ -79,6 +85,7 @@ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
79
85
  import { getVizTitle, getVizSubType } from '@cdc/core/helpers/metrics/utils'
80
86
  import { ENABLE_CHART_MAP_TP5_TREATMENT } from '@cdc/core/helpers/constants'
81
87
  import CalloutFlag from '@cdc/core/assets/callout-flag.svg?url'
88
+ import { useQueryParamsListener } from '@cdc/core/hooks/useQueryParamsListener'
82
89
 
83
90
  type CdcMapComponent = {
84
91
  config: MapConfig
@@ -88,6 +95,8 @@ type CdcMapComponent = {
88
95
  logo?: string
89
96
  navigationHandler: Function
90
97
  setSharedFilter: Function
98
+ clearSharedFilter?: (key: string) => void
99
+ hasActiveSharedFilter?: boolean
91
100
  setSharedFilterValue: Function
92
101
  setConfig?: Function
93
102
  loadConfig?: Function
@@ -102,6 +111,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
102
111
  isEditor = false,
103
112
  logo = '',
104
113
  setSharedFilter,
114
+ clearSharedFilter,
115
+ hasActiveSharedFilter = false,
105
116
  setSharedFilterValue,
106
117
  link,
107
118
  setConfig: setParentConfig,
@@ -124,6 +135,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
124
135
  modal,
125
136
  accessibleStatus,
126
137
  filteredCountryCode,
138
+ filteredCountyCode,
127
139
  filteredStateCode,
128
140
  position,
129
141
  scale,
@@ -165,6 +177,22 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
165
177
  }
166
178
  }
167
179
 
180
+ const setFilters = (filters: VizFilter[]) => {
181
+ const filterCopy = _.cloneDeep(filters)
182
+ if (config.general.showStateDropdown) {
183
+ const [stateFilter, countyFilter] = filterCopy.filter(
184
+ f => f.staticFilter && ['state', 'county'].includes(f.columnName)
185
+ )
186
+ const stateCode = (stateFilter?.active as string) || ''
187
+ const countyCode = (countyFilter?.active as string) || ''
188
+
189
+ setFilteredStateCountyCode(stateCode, countyCode)
190
+ if (countyFilter) filterCopy.pop() // remove county filter
191
+ filterCopy.pop() // remove state filter
192
+ }
193
+ _setRuntimeData(filterCopy)
194
+ }
195
+
168
196
  // Refs
169
197
  const innerContainerRef = useRef<HTMLDivElement | null>(null)
170
198
  const legendRef = useRef(null)
@@ -360,6 +388,32 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
360
388
  />
361
389
  )
362
390
 
391
+ const STATE_CODE = 'state-code'
392
+ const COUNTY_CODE = 'county-code'
393
+ const setFilteredStateCountyCode = (stateCode: string, countyCode?: string) => {
394
+ const stateCodePattern = /^\d\d$/
395
+ const normalizedStateCode = stateCodePattern.test(stateCode) ? stateCode : ''
396
+ let _countyCode = ''
397
+ if (countyCode) {
398
+ const countyCodePattern = /^\d{5}$/
399
+ _countyCode = countyCodePattern.test(countyCode) ? countyCode : ''
400
+ }
401
+ if (!normalizedStateCode) {
402
+ updateQueryParams({ [STATE_CODE]: '', [COUNTY_CODE]: '' })
403
+ } else {
404
+ updateQueryParams({ [STATE_CODE]: normalizedStateCode, [COUNTY_CODE]: _countyCode })
405
+ }
406
+ }
407
+
408
+ const setFilteredStateCodeFromQuery = ({
409
+ [STATE_CODE]: stateCode,
410
+ [COUNTY_CODE]: countyCode
411
+ }: Record<string, string>) => {
412
+ dispatch({ type: 'SET_FILTERED_STATE_COUNTY_CODE', payload: { stateCode, countyCode } })
413
+ }
414
+
415
+ useQueryParamsListener([STATE_CODE, COUNTY_CODE], setFilteredStateCodeFromQuery)
416
+
363
417
  const mapProps = {
364
418
  setParentConfig,
365
419
  container,
@@ -369,6 +423,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
369
423
  customNavigationHandler,
370
424
  dimensions,
371
425
  filteredCountryCode,
426
+ filteredCountyCode,
372
427
  filteredStateCode,
373
428
  isDashboard,
374
429
  isEditor,
@@ -381,8 +436,10 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
381
436
  runtimeLegend,
382
437
  scale,
383
438
  setConfig,
384
- setFilteredStateCode: (stateCode: string) => dispatch({ type: 'SET_FILTERED_STATE_CODE', payload: stateCode }),
439
+ setFilteredStateCountyCode,
385
440
  setSharedFilter,
441
+ clearSharedFilter,
442
+ hasActiveSharedFilter,
386
443
  setSharedFilterValue,
387
444
  config,
388
445
  statesToShow,
@@ -448,6 +505,42 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
448
505
  </a>
449
506
  )
450
507
 
508
+ const applyStateFilter = (config: MapConfig): MapConfig => {
509
+ if (config.general.showStateDropdown && config.general.geoType === 'us-county') {
510
+ const stateFilter: VizFilter = {
511
+ columnName: 'state',
512
+ label: 'Select Location',
513
+ filterStyle: 'dropdown',
514
+ labels: supportedStatesFipsCodes,
515
+ values: Object.keys(supportedStatesFipsCodes),
516
+ resetLabel: 'United States',
517
+ staticFilter: true,
518
+ active: filteredStateCode
519
+ }
520
+ let countyFilter: VizFilter | undefined
521
+ if (filteredStateCode) {
522
+ const counties = Object.keys(supportedCounties).filter(countyCode => countyCode.startsWith(filteredStateCode))
523
+ countyFilter = {
524
+ columnName: 'county',
525
+ label: 'Select County',
526
+ filterStyle: 'dropdown',
527
+ labels: supportedCounties,
528
+ values: counties,
529
+ resetLabel: 'All Counties',
530
+ staticFilter: true,
531
+ active: filteredCountyCode
532
+ }
533
+ }
534
+ return {
535
+ ...config,
536
+ filters: [...(config.filters || []), stateFilter, ...(countyFilter ? [countyFilter] : [])]
537
+ }
538
+ }
539
+ return config
540
+ }
541
+
542
+ const filterConfig = applyStateFilter(config)
543
+
451
544
  return (
452
545
  <LegendMemoProvider legendMemo={legendMemo} legendSpecialClassLastMemo={legendSpecialClassLastMemo}>
453
546
  <ConfigContext.Provider value={mapProps}>
@@ -472,7 +565,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
472
565
  ]
473
566
  .filter(Boolean)
474
567
  .join(' ')}
475
- innerProps={{ 'aria-label': 'Map: ' + title, ref: innerContainerRef }}
568
+ innerProps={{ ref: innerContainerRef }}
476
569
  bodyWrapClassName={isTp5Treatment ? 'cdc-callout d-flex flex-column' : ''}
477
570
  bodyClassName={[
478
571
  !config.visual?.border || isTp5Treatment ? 'no-borders' : '',
@@ -486,19 +579,18 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
486
579
  .filter(Boolean)
487
580
  .join(' ')}
488
581
  filters={
489
- config?.filters?.length > 0 ? (
582
+ hasVisibleVizFilters(filterConfig.filters) ? (
490
583
  <Filters
491
- config={config}
492
- setConfig={setConfig}
493
- filteredData={runtimeFilters}
494
- setFilters={_setRuntimeData}
584
+ config={filterConfig}
585
+ setFilters={setFilters}
495
586
  dimensions={dimensions}
496
- standaloneMap={!config}
497
587
  interactionLabel={interactionLabel}
498
588
  />
499
589
  ) : undefined
500
590
  }
501
- bodySubtext={processedSubtext.length > 0 ? <p className='subtext'>{parse(processedSubtext)}</p> : null}
591
+ bodySubtext={
592
+ processedSubtext.length > 0 ? <p className='subtext cove-prose'>{parse(processedSubtext)}</p> : null
593
+ }
502
594
  bodyFooter={
503
595
  <>
504
596
  {isDashboard && config.table?.forceDisplay && config.table.showDataTableLink
@@ -569,13 +661,13 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
569
661
  {config.annotations?.length > 0 && <Annotation.Dropdown />}
570
662
 
571
663
  {processedFootnotes && (
572
- <section className='footnotes pt-2 mt-4'>{parse(processedFootnotes)}</section>
664
+ <section className='footnotes cove-prose pt-2 mt-4'>{parse(processedFootnotes)}</section>
573
665
  )}
574
666
  </>
575
667
  }
576
668
  header={isTp5Treatment ? null : mapTitle}
577
669
  messageIsIntroText={!!processedIntroText}
578
- message={processedIntroText ? parse(processedIntroText) : null}
670
+ message={processedIntroText ? <div className='cove-prose'>{parse(processedIntroText)}</div> : null}
579
671
  >
580
672
  <>
581
673
  {isTp5Treatment && <img src={CalloutFlag} alt='' className='cdc-callout__flag' aria-hidden='true' />}
@@ -663,6 +755,7 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
663
755
  enableMarkupVariables={config.enableMarkupVariables}
664
756
  data={config.data}
665
757
  dataMetadata={config.dataMetadata}
758
+ footerClassName='cove-visualization__footnotes'
666
759
  />
667
760
  </VisualizationContainer>
668
761
  </MapDispatchContext.Provider>
@@ -0,0 +1,122 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import CdcMap from '../CdcMap'
4
+ import { assertVisualizationRendered, waitForPresence, waitForEditor, openAccordion } from '@cdc/core/helpers/testing'
5
+ import altTextConfig from './_mock/alt_text_metadata.json'
6
+
7
+ const meta: Meta<typeof CdcMap> = {
8
+ title: 'Components/Templates/Map/Accessible Alt Text',
9
+ component: CdcMap,
10
+ parameters: {
11
+ docs: {
12
+ description: {
13
+ component:
14
+ 'Demonstrates SVG accessibility for maps: auto-generated aria-label with optional configurable description concatenated into the label for reliable screen reader support.'
15
+ }
16
+ }
17
+ }
18
+ }
19
+
20
+ export default meta
21
+ type Story = StoryObj<typeof CdcMap>
22
+
23
+ export const MetadataDescription: Story = {
24
+ args: {
25
+ config: {
26
+ ...altTextConfig,
27
+ altText: { type: 'metadata', metadataKey: 'altDescription' }
28
+ },
29
+ isEditor: false
30
+ },
31
+ parameters: {
32
+ docs: {
33
+ description: {
34
+ story:
35
+ 'Description pulled from dataMetadata is concatenated into the aria-label after the auto-generated title for reliable screen reader support.'
36
+ }
37
+ }
38
+ },
39
+ play: async ({ canvasElement }) => {
40
+ await assertVisualizationRendered(canvasElement)
41
+ const svg = await waitForPresence('svg[role="img"]', canvasElement)
42
+
43
+ const expected = `United States map with the title: COVID-19 Case Rates by State. ${altTextConfig.dataMetadata.altDescription}`
44
+ expect(svg?.getAttribute('aria-label')).toBe(expected)
45
+ }
46
+ }
47
+
48
+ export const StaticDescription: Story = {
49
+ args: {
50
+ config: {
51
+ ...altTextConfig,
52
+ altText: {
53
+ type: 'static',
54
+ value: 'US map showing COVID-19 rates concentrated in the Southeast region.'
55
+ }
56
+ },
57
+ isEditor: false
58
+ },
59
+ parameters: {
60
+ docs: {
61
+ description: {
62
+ story: 'Static manually written description concatenated with the auto-generated title.'
63
+ }
64
+ }
65
+ },
66
+ play: async ({ canvasElement }) => {
67
+ await assertVisualizationRendered(canvasElement)
68
+ const svg = await waitForPresence('svg[role="img"]', canvasElement)
69
+
70
+ expect(svg?.getAttribute('aria-label')).toBe(
71
+ 'United States map with the title: COVID-19 Case Rates by State. US map showing COVID-19 rates concentrated in the Southeast region.'
72
+ )
73
+ }
74
+ }
75
+
76
+ export const AutoGenerated: Story = {
77
+ args: {
78
+ config: altTextConfig,
79
+ isEditor: false
80
+ },
81
+ parameters: {
82
+ docs: {
83
+ description: {
84
+ story:
85
+ 'No altText configured. Falls back to the auto-generated aria-label: "United States map with the title: COVID-19 Case Rates by State".'
86
+ }
87
+ }
88
+ },
89
+ play: async ({ canvasElement }) => {
90
+ await assertVisualizationRendered(canvasElement)
91
+ const svg = await waitForPresence('svg[role="img"]', canvasElement)
92
+
93
+ expect(svg?.getAttribute('aria-label')).toBe('United States map with the title: COVID-19 Case Rates by State')
94
+ }
95
+ }
96
+
97
+ export const EditorWithMetadata: Story = {
98
+ args: {
99
+ config: {
100
+ ...altTextConfig,
101
+ altText: { type: 'metadata', metadataKey: 'altDescription' }
102
+ },
103
+ isEditor: true
104
+ },
105
+ parameters: {
106
+ docs: {
107
+ description: {
108
+ story:
109
+ 'Editor mode showing the alt text description control in the General accordion with metadata-driven description.'
110
+ }
111
+ }
112
+ },
113
+ play: async ({ canvasElement }) => {
114
+ const canvas = within(canvasElement)
115
+ await waitForEditor(canvas)
116
+
117
+ await openAccordion(canvas, 'General')
118
+
119
+ const descPreview = await waitForPresence('[data-testid="alt-text-desc-preview"]', canvasElement)
120
+ expect(descPreview?.textContent).toContain('Choropleth map of the United States')
121
+ }
122
+ }