@cdc/map 4.25.7 → 4.25.10

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 (99) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/CLAUDE.local.md +0 -0
  3. package/dist/cdcmap.js +54785 -53159
  4. package/examples/private/c.json +290 -0
  5. package/examples/private/canvas-city-hover.json +787 -0
  6. package/examples/private/d.json +345 -0
  7. package/examples/private/filter-map.json +909 -0
  8. package/examples/private/g.json +1 -0
  9. package/examples/private/h.json +105911 -0
  10. package/examples/private/measles-data.json +378 -0
  11. package/examples/private/measles.json +211 -0
  12. package/examples/private/north-dakota.json +1132 -0
  13. package/examples/private/rsv-data.json +532 -0
  14. package/examples/private/state-with-pattern.json +883 -0
  15. package/examples/private/test.json +222 -640
  16. package/index.html +1 -1
  17. package/package.json +26 -5
  18. package/src/CdcMap.tsx +28 -8
  19. package/src/CdcMapComponent.tsx +230 -306
  20. package/src/_stories/CdcMap.Filters.stories.tsx +2 -2
  21. package/src/_stories/CdcMap.Legend.Gradient.stories.tsx +3 -3
  22. package/src/_stories/CdcMap.Legend.stories.tsx +7 -4
  23. package/src/_stories/CdcMap.Patterns.stories.tsx +2 -2
  24. package/src/_stories/CdcMap.Table.stories.tsx +2 -2
  25. package/src/_stories/CdcMap.stories.tsx +18 -11
  26. package/src/_stories/GoogleMap.stories.tsx +2 -2
  27. package/src/_stories/UsaMap.NoData.stories.tsx +2 -2
  28. package/src/_stories/_mock/equal-number.json +1109 -0
  29. package/src/_stories/_mock/multi-state.json +21389 -0
  30. package/src/_stories/_mock/us-bubble-cities.json +306 -0
  31. package/src/components/BubbleList.tsx +16 -12
  32. package/src/components/CityList.tsx +88 -110
  33. package/src/components/DataTable.tsx +44 -12
  34. package/src/components/EditorPanel/components/EditorPanel.tsx +201 -203
  35. package/src/components/EditorPanel/components/HexShapeSettings.tsx +3 -2
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +7 -5
  37. package/src/components/Geo.tsx +2 -0
  38. package/src/components/Legend/components/Legend.tsx +117 -93
  39. package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +10 -7
  40. package/src/components/MapContainer.tsx +52 -0
  41. package/src/components/MapControls.tsx +44 -0
  42. package/src/components/Modal.tsx +2 -8
  43. package/src/components/NavigationMenu.tsx +13 -1
  44. package/src/components/UsaMap/components/SingleState/SingleState.CountyOutput.tsx +24 -7
  45. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +21 -15
  46. package/src/components/UsaMap/components/TerritoriesSection.tsx +2 -2
  47. package/src/components/UsaMap/components/UsaMap.County.tsx +112 -33
  48. package/src/components/UsaMap/components/UsaMap.Region.tsx +23 -5
  49. package/src/components/UsaMap/components/UsaMap.SingleState.tsx +38 -26
  50. package/src/components/UsaMap/components/UsaMap.State.tsx +28 -10
  51. package/src/components/UsaMap/helpers/map.ts +16 -8
  52. package/src/components/WorldMap/WorldMap.tsx +116 -11
  53. package/src/components/ZoomControls.tsx +6 -9
  54. package/src/context/LegendMemoContext.tsx +30 -0
  55. package/src/context.ts +1 -39
  56. package/src/data/initial-state.js +143 -128
  57. package/src/data/supported-geos.js +202 -4
  58. package/src/helpers/addUIDs.ts +8 -8
  59. package/src/helpers/applyColorToLegend.ts +122 -45
  60. package/src/helpers/applyLegendToRow.ts +15 -13
  61. package/src/helpers/componentHelpers.ts +8 -0
  62. package/src/helpers/constants.ts +12 -0
  63. package/src/helpers/dataTableHelpers.ts +6 -0
  64. package/src/helpers/displayGeoName.ts +12 -7
  65. package/src/helpers/formatLegendLocation.ts +1 -3
  66. package/src/helpers/generateRuntimeLegend.ts +192 -340
  67. package/src/helpers/generateRuntimeLegendHash.ts +4 -2
  68. package/src/helpers/getColumnNames.ts +1 -1
  69. package/src/helpers/getPatternForRow.ts +36 -0
  70. package/src/helpers/getStatesPicked.ts +14 -0
  71. package/src/helpers/handleMapAriaLabels.ts +2 -2
  72. package/src/helpers/index.ts +11 -3
  73. package/src/helpers/isLegendItemDisabled.ts +16 -0
  74. package/src/helpers/mapObserverHelpers.ts +40 -0
  75. package/src/helpers/resetLegendToggles.ts +3 -2
  76. package/src/helpers/toggleLegendActive.ts +6 -11
  77. package/src/helpers/urlDataHelpers.ts +70 -0
  78. package/src/hooks/useGeoClickHandler.ts +35 -1
  79. package/src/hooks/useLegendMemo.ts +17 -0
  80. package/src/hooks/useMapLayers.tsx +5 -4
  81. package/src/hooks/useStateZoom.tsx +137 -88
  82. package/src/hooks/useTooltip.ts +1 -2
  83. package/src/index.jsx +6 -3
  84. package/src/scss/main.scss +23 -12
  85. package/src/store/map.actions.ts +2 -2
  86. package/src/store/map.reducer.ts +21 -10
  87. package/src/test/CdcMap.test.jsx +11 -0
  88. package/src/types/MapConfig.ts +25 -17
  89. package/src/types/MapContext.ts +2 -8
  90. package/src/types/runtimeLegend.ts +12 -10
  91. package/vite.config.js +2 -7
  92. package/vitest.config.ts +16 -0
  93. package/src/_stories/_mock/floating-point.json +0 -427
  94. package/src/coreStyles_map.scss +0 -3
  95. package/src/helpers/colorDistributions.ts +0 -12
  96. package/src/helpers/generateColorsArray.ts +0 -14
  97. package/src/helpers/getStatePicked.ts +0 -8
  98. package/src/helpers/tests/generateColorsArray.test.ts +0 -18
  99. package/src/helpers/tests/generateRuntimeLegendHash.test.ts +0 -11
@@ -2,12 +2,13 @@ import { hashObj } from './hashObj'
2
2
  import { MapConfig } from '../types/MapConfig'
3
3
 
4
4
  export const generateRuntimeLegendHash = (config: MapConfig, runtimeFilters) => {
5
+ const { name: paletteName } = config.general.palette
5
6
  return hashObj({
6
7
  unified: config.legend.unified ?? false,
7
8
  equalNumberOptIn: config.general.equalNumberOptIn ?? false,
8
9
  specialClassesLast: config.legend.showSpecialClassesLast ?? false,
9
- color: config.color,
10
- customColors: config.customColors,
10
+ color: paletteName,
11
+ customColors: config.general?.palette?.customColors,
11
12
  numberOfItems: config.legend.numberOfItems,
12
13
  type: config.legend.type,
13
14
  separateZero: config.legend.separateZero ?? false,
@@ -16,6 +17,7 @@ export const generateRuntimeLegendHash = (config: MapConfig, runtimeFilters) =>
16
17
  specialClasses: config.legend.specialClasses,
17
18
  geoType: config.general.geoType,
18
19
  data: config.data,
20
+ palette: config.general.palette,
19
21
  filters: {
20
22
  ...config.filters
21
23
  },
@@ -8,7 +8,7 @@ type ColumnNames = {
8
8
  categoricalColumnName: string | null
9
9
  } | null
10
10
 
11
- export const getColumnNames = (columns?: Pick<MapConfig, 'columns'>): ColumnNames => {
11
+ export const getColumnNames = (columns?: MapConfig['columns']): ColumnNames => {
12
12
  if (!columns) return null
13
13
  const geoColumnName = columns.geo?.name || null
14
14
  const primaryColumnName = columns.primary?.name || null
@@ -0,0 +1,36 @@
1
+ import { MapConfig } from '../types/MapConfig'
2
+
3
+ export interface PatternInfo {
4
+ pattern?: string
5
+ dataKey: string
6
+ size?: string
7
+ patternIndex: number
8
+ color?: string
9
+ }
10
+
11
+ export const getPatternForRow = (
12
+ rowObj: Record<string, any>,
13
+ config: MapConfig
14
+ ): PatternInfo | null => {
15
+ if (!config.map?.patterns || !rowObj) {
16
+ return null
17
+ }
18
+
19
+ // Find a pattern that matches this row's data
20
+ for (let i = 0; i < config.map.patterns.length; i++) {
21
+ const patternData = config.map.patterns[i]
22
+ const hasMatchingValues = patternData.dataValue === rowObj[patternData.dataKey]
23
+
24
+ if (hasMatchingValues) {
25
+ return {
26
+ pattern: patternData.pattern,
27
+ dataKey: patternData.dataKey,
28
+ size: patternData.size,
29
+ patternIndex: i,
30
+ color: patternData.color
31
+ }
32
+ }
33
+ }
34
+
35
+ return null
36
+ }
@@ -0,0 +1,14 @@
1
+ import { getFilterControllingStatesPicked } from '../components/UsaMap/helpers/map'
2
+ import { supportedStatesFipsCodes } from '../data/supported-geos'
3
+
4
+ export const getStatesPicked = (config, runtimeData) => {
5
+ const stateNames = getFilterControllingStatesPicked(config, runtimeData)
6
+ return stateNames.map(stateName => {
7
+ const fipsCode = Object.keys(supportedStatesFipsCodes).find(key => supportedStatesFipsCodes[key] === stateName)
8
+ if (!fipsCode) console.error(`State name "${stateName}" not found.`)
9
+ return {
10
+ fipsCode,
11
+ stateName
12
+ }
13
+ })
14
+ }
@@ -3,7 +3,7 @@ export const handleMapAriaLabels = (state: MapConfig = '', testing = false) => {
3
3
  try {
4
4
  if (!state.general.geoType) throw Error('handleMapAriaLabels: no geoType found in state')
5
5
  const {
6
- general: { title, geoType, statePicked }
6
+ general: { title, geoType, statesPicked }
7
7
  } = state
8
8
  let ariaLabel = ''
9
9
  switch (geoType) {
@@ -17,7 +17,7 @@ export const handleMapAriaLabels = (state: MapConfig = '', testing = false) => {
17
17
  ariaLabel += `United States county map`
18
18
  break
19
19
  case 'single-state':
20
- ariaLabel += `${statePicked.stateName} county map`
20
+ ariaLabel += `${statesPicked.map(sp => sp.stateName).join(', ')} county map`
21
21
  break
22
22
  case 'us-region':
23
23
  ariaLabel += `United States HHS Region map`
@@ -1,9 +1,8 @@
1
1
  export { addUIDs } from './addUIDs'
2
2
  export { applyColorToLegend } from './applyColorToLegend'
3
- export { colorDistributions } from './colorDistributions'
4
3
  export { displayGeoName } from './displayGeoName'
5
4
  export { formatLegendLocation } from './formatLegendLocation'
6
- export { generateColorsArray } from './generateColorsArray'
5
+ export { generateColorsArray } from '@cdc/core/helpers/generateColorsArray'
7
6
  export { generateRuntimeLegendHash } from './generateRuntimeLegendHash'
8
7
  export { getGeoStrokeColor, getGeoFillColor } from './colors'
9
8
  export { getUniqueValues } from './getUniqueValues'
@@ -11,6 +10,7 @@ export { handleMapAriaLabels } from './handleMapAriaLabels'
11
10
  export { handleMapTabbing } from './handleMapTabbing'
12
11
  export { hashObj } from './hashObj'
13
12
  export { indexOfIgnoreType } from './indexOfIgnoreType'
13
+ export { isLegendItemDisabled } from './isLegendItemDisabled'
14
14
  export { navigationHandler } from './navigationHandler'
15
15
  export { resetLegendToggles } from './resetLegendToggles'
16
16
  export { setBinNumbers } from './setBinNumbers'
@@ -19,4 +19,12 @@ export { titleCase as toTitleCase } from './toTitleCase'
19
19
  export { titleCase } from './titleCase'
20
20
  export { validateFipsCodeLength } from './validateFipsCodeLength'
21
21
  export { getMapContainerClasses } from './getMapContainerClasses'
22
- export { SVG_HEIGHT, SVG_WIDTH, SVG_PADDING, SVG_VIEWBOX, HEADER_COLORS, MAX_ZOOM_LEVEL } from './constants'
22
+ export {
23
+ SVG_HEIGHT,
24
+ SVG_WIDTH,
25
+ SVG_PADDING,
26
+ SVG_VIEWBOX,
27
+ HEADER_COLORS,
28
+ MAX_ZOOM_LEVEL,
29
+ DEFAULT_MAP_BACKGROUND
30
+ } from './constants'
@@ -0,0 +1,16 @@
1
+ import { hashObj } from './hashObj'
2
+
3
+ export const isLegendItemDisabled = (
4
+ dataForCheck: any,
5
+ runtimeLegend: any,
6
+ legendMemo: React.MutableRefObject<Map<number, number>>,
7
+ legendSpecialClassLastMemo: React.MutableRefObject<Map<number, number>>,
8
+ config: any
9
+ ): boolean => {
10
+ if (!dataForCheck || !runtimeLegend?.items) return false
11
+ const hash = hashObj(dataForCheck)
12
+ if (!legendMemo.current.has(hash)) return false
13
+ const idx = legendMemo.current.get(hash)
14
+ const disabledIdx = config.legend.showSpecialClassesLast ? legendSpecialClassLastMemo.current.get(hash) ?? idx : idx
15
+ return runtimeLegend.items[disabledIdx]?.disabled || false
16
+ }
@@ -0,0 +1,40 @@
1
+ import { publish } from '@cdc/core/helpers/events'
2
+ import { MapConfig } from '../types/MapConfig'
3
+ import MapActions from '../store/map.actions'
4
+ import { Dispatch } from 'react'
5
+
6
+ /**
7
+ * Publishes 'cove_loaded' only after the map SVG is rendered in the DOM.
8
+ * Checks immediately, then uses a MutationObserver as a fallback for async rendering.
9
+ * Update the mapSvg ref if the map container changes.
10
+ */
11
+ export const observeMapSvgLoaded = (
12
+ mapSvgRef: React.RefObject<HTMLElement>,
13
+ config: MapConfig,
14
+ coveLoadedHasRan: boolean,
15
+ dispatch: Dispatch<MapActions>
16
+ ): (() => void) => {
17
+ // Immediate check in case SVG is already present
18
+ const svgEl = mapSvgRef.current?.querySelector('svg')
19
+ if (svgEl && svgEl.childNodes.length > 0) {
20
+ publish('cove_loaded', { config })
21
+ dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
22
+ return () => {}
23
+ }
24
+
25
+ // Fallback to observer for async SVG rendering
26
+ const observer = new MutationObserver(() => {
27
+ const svgEl = mapSvgRef.current?.querySelector('svg')
28
+ if (svgEl && svgEl.childNodes.length > 0) {
29
+ publish('cove_loaded', { config })
30
+ dispatch({ type: 'SET_COVE_LOADED_HAS_RAN', payload: true })
31
+ observer.disconnect()
32
+ }
33
+ })
34
+
35
+ if (mapSvgRef.current) {
36
+ observer.observe(mapSvgRef.current, { childList: true, subtree: true })
37
+ }
38
+
39
+ return () => observer.disconnect()
40
+ }
@@ -1,5 +1,6 @@
1
1
  import _ from 'lodash'
2
- export const resetLegendToggles = (runtimeLegend, setRuntimeLegend) => {
2
+
3
+ export const resetLegendToggles = (runtimeLegend, dispatch) => {
3
4
  const legendCopy = _.cloneDeep(runtimeLegend)
4
5
 
5
6
  legendCopy.items.forEach(legendItem => {
@@ -9,5 +10,5 @@ export const resetLegendToggles = (runtimeLegend, setRuntimeLegend) => {
9
10
 
10
11
  legendCopy.runtimeDataHash = runtimeLegend.runtimeDataHash
11
12
 
12
- setRuntimeLegend(legendCopy)
13
+ dispatch({ type: 'SET_RUNTIME_LEGEND', payload: legendCopy })
13
14
  }
@@ -1,12 +1,6 @@
1
1
  import _ from 'lodash'
2
2
 
3
- export const toggleLegendActive = (
4
- i: number,
5
- legendLabel: string,
6
- runtimeLegend,
7
- setRuntimeLegend,
8
- setAccessibleStatus: (message: string) => void
9
- ) => {
3
+ export const toggleLegendActive = (i: number, legendLabel: string, runtimeLegend, dispatch) => {
10
4
  const runtimeLegendCopy = _.cloneDeep(runtimeLegend)
11
5
 
12
6
  // Create and toggle the new value
@@ -17,9 +11,10 @@ export const toggleLegendActive = (
17
11
 
18
12
  runtimeLegendCopy['disabledAmt'] = newValue ? disabledAmt + 1 : disabledAmt - 1
19
13
 
20
- setRuntimeLegend(runtimeLegendCopy)
14
+ dispatch({ type: 'SET_RUNTIME_LEGEND', payload: runtimeLegendCopy })
21
15
 
22
- setAccessibleStatus(
23
- `Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`
24
- )
16
+ dispatch({
17
+ type: 'SET_ACCESSIBLE_STATUS',
18
+ payload: `Disabled legend item ${legendLabel ?? ''}. Please reference the data table to see updated values.`
19
+ })
25
20
  }
@@ -0,0 +1,70 @@
1
+ import Papa from 'papaparse'
2
+ import _ from 'lodash'
3
+ import { DataTransform } from '@cdc/core/helpers/DataTransform'
4
+ import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
5
+ import { MapConfig } from '../types/MapConfig'
6
+ import { CSV_PARSE_CONFIG } from './constants'
7
+ import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
8
+
9
+ export const buildQueryString = (params: Record<string, string>): string =>
10
+ Object.keys(params)
11
+ .map((param, i) => {
12
+ let qs = i === 0 ? '?' : '&'
13
+ qs += param + '='
14
+ qs += params[param]
15
+ return qs
16
+ })
17
+ .join('')
18
+
19
+ export const reloadURLData = async (config: MapConfig, setConfig: (config: MapConfig) => void): Promise<void> => {
20
+ if (!config.dataUrl) return
21
+
22
+ const dataUrl = new URL(config.runtimeDataUrl || config.dataUrl, window.location.origin)
23
+ let qsParams = Object.fromEntries(new URLSearchParams(dataUrl.search))
24
+
25
+ let isUpdateNeeded = false
26
+ config.filters.forEach(filter => {
27
+ if (filter.type === 'url' && qsParams[filter.queryParameter] !== decodeURIComponent(filter.active)) {
28
+ qsParams[filter.queryParameter] = filter.active
29
+ isUpdateNeeded = true
30
+ }
31
+ })
32
+
33
+ if (!isUpdateNeeded) return
34
+
35
+ let dataUrlFinal = `${dataUrl.origin}${dataUrl.pathname}${buildQueryString(qsParams)}`
36
+ let data
37
+
38
+ try {
39
+ const regex = /(?:\.([^.]+))?$/
40
+ const ext = regex.exec(dataUrl.pathname)[1]
41
+
42
+ if ('csv' === ext || isSolrCsv(dataUrlFinal)) {
43
+ data = await fetch(dataUrlFinal)
44
+ .then(response => response.text())
45
+ .then(responseText => {
46
+ const parsedCsv = Papa.parse(responseText, CSV_PARSE_CONFIG)
47
+ return parsedCsv.data
48
+ })
49
+ } else if ('json' === ext || isSolrJson(dataUrlFinal)) {
50
+ data = await fetch(dataUrlFinal).then(response => response.json())
51
+ } else {
52
+ data = []
53
+ }
54
+ } catch (e) {
55
+ console.error(`Cannot parse URL: ${dataUrlFinal}`, e)
56
+ data = []
57
+ }
58
+
59
+ if (config.dataDescription) {
60
+ const transform = new DataTransform()
61
+ data = transform.autoStandardize(data)
62
+ data = transform.developerStandardize(data, config.dataDescription)
63
+ }
64
+
65
+ const newConfig = cloneConfig(config)
66
+ newConfig.data = data
67
+ newConfig.runtimeDataUrl = dataUrlFinal
68
+
69
+ setConfig(newConfig)
70
+ }
@@ -1,9 +1,17 @@
1
1
  import ConfigContext, { MapDispatchContext } from '../context'
2
2
  import { navigationHandler } from '../helpers'
3
3
  import { useContext } from 'react'
4
+ import { publishAnalyticsEvent } from '@cdc/core/helpers/metrics/helpers'
5
+ import { getVizTitle } from '@cdc/core/helpers/metrics/utils'
4
6
 
5
7
  const useGeoClickHandler = () => {
6
- const { config: state, setConfig, setSharedFilter, customNavigationHandler } = useContext(ConfigContext)
8
+ const {
9
+ config: state,
10
+ setConfig,
11
+ setSharedFilter,
12
+ customNavigationHandler,
13
+ interactionLabel
14
+ } = useContext(ConfigContext)
7
15
  const dispatch = useContext(MapDispatchContext)
8
16
 
9
17
  const geoClickHandler = (geoDisplayName: string, geoData: object): void => {
@@ -30,11 +38,37 @@ const useGeoClickHandler = () => {
30
38
  }
31
39
  dispatch({ type: 'SET_MODAL', payload: modalData })
32
40
 
41
+ // Track modal click analytics event
42
+ if (interactionLabel) {
43
+ const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
44
+ publishAnalyticsEvent({
45
+ vizType: 'map',
46
+ eventType: `modal_trigger` as any,
47
+ eventAction: 'click',
48
+ eventLabel: interactionLabel,
49
+ vizTitle: getVizTitle(state),
50
+ specifics: `clicked on: ${String(locationName).toLowerCase()}`
51
+ })
52
+ }
53
+
33
54
  return
34
55
  }
35
56
 
36
57
  // Otherwise if this item has a link specified for it, do regular navigation.
37
58
  if (state.columns.navigate && geoData[state.columns.navigate.name]) {
59
+ // Track navigation click analytics event
60
+ if (interactionLabel) {
61
+ const locationName = geoDisplayName.replace(/[^a-zA-Z0-9]/g, '_')
62
+ publishAnalyticsEvent({
63
+ vizType: 'map',
64
+ eventType: `map_trigger` as any,
65
+ eventAction: 'click',
66
+ eventLabel: interactionLabel,
67
+ vizTitle: getVizTitle(state),
68
+ specifics: `clicked on: ${String(locationName).toLowerCase()}`
69
+ })
70
+ }
71
+
38
72
  navigationHandler(state.general.navigationTarget, geoData[state.columns.navigate.name], customNavigationHandler)
39
73
  }
40
74
  }
@@ -0,0 +1,17 @@
1
+ import { useRef } from 'react'
2
+
3
+ /**
4
+ * Custom hook to manage legend memoization refs
5
+ * Extracted from context to reduce context props and improve performance
6
+ */
7
+ export const useLegendMemo = () => {
8
+ const legendMemo = useRef(new Map())
9
+ const legendSpecialClassLastMemo = useRef(new Map())
10
+
11
+ return {
12
+ legendMemo,
13
+ legendSpecialClassLastMemo
14
+ }
15
+ }
16
+
17
+ export default useLegendMemo
@@ -3,6 +3,7 @@ import { feature } from 'topojson-client'
3
3
  import { Group } from '@visx/group'
4
4
  import { MapConfig } from '../types/MapConfig'
5
5
  import _ from 'lodash'
6
+ import { cloneConfig } from '@cdc/core/helpers/cloneConfig'
6
7
 
7
8
  /**
8
9
  * This is the starting structure for adding custom geoJSON shape layers to a projection.
@@ -46,10 +47,10 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
46
47
 
47
48
  const handleRemoveLayer = (e: MouseEvent<HTMLButtonElement>, index: number) => {
48
49
  e.preventDefault()
49
- const newConfig = _.cloneDeep(config)
50
+ const newConfig = cloneConfig(config)
50
51
  const layers = newConfig.map.layers.filter((_layer, i) => i !== index)
51
52
  newConfig.map.layers = layers
52
- setConfig(newConfig )
53
+ setConfig(newConfig)
53
54
  }
54
55
 
55
56
  const handleAddLayer = (e: Event) => {
@@ -58,9 +59,9 @@ export default function useMapLayers(config: MapConfig, setConfig, pathGenerator
58
59
  name: 'New Custom Layer',
59
60
  url: ''
60
61
  }
61
- const newConfig = _.cloneDeep(config)
62
+ const newConfig = cloneConfig(config)
62
63
  newConfig.map.layers.unshift(placeHolderLayer)
63
- setConfig( newConfig )
64
+ setConfig(newConfig)
64
65
  }
65
66
 
66
67
  const handleMapLayer = (e: ChangeEvent<HTMLInputElement>, index: number, layerKey: string) => {