@cdc/core 4.25.11 → 4.26.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.
Files changed (147) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/_stories/Gallery.Charts.stories.tsx +300 -0
  4. package/_stories/Gallery.DataBite.stories.tsx +79 -0
  5. package/_stories/Gallery.Maps.stories.tsx +239 -0
  6. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  7. package/_stories/PageART.stories.tsx +193 -0
  8. package/_stories/PageBRFSS.stories.tsx +294 -0
  9. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  10. package/_stories/PageEasternEquineEncephalitis.stories.tsx +216 -0
  11. package/_stories/PageExcessiveAlcoholUse.stories.tsx +201 -0
  12. package/_stories/PageMaternalMortality.stories.tsx +193 -0
  13. package/_stories/PageOralHealth.stories.tsx +201 -0
  14. package/_stories/PageRespiratory.stories.tsx +332 -0
  15. package/_stories/PageSmokingTobacco.stories.tsx +200 -0
  16. package/_stories/PageStateDiabetesProfiles.stories.tsx +201 -0
  17. package/_stories/PageWastewater.stories.tsx +477 -0
  18. package/_stories/VegaImport.stories.tsx +401 -0
  19. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  20. package/_stories/vega-fixtures/bars.json +58 -0
  21. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  22. package/_stories/vega-fixtures/combo.json +68 -0
  23. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  25. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  26. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  27. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  28. package/_stories/vega-fixtures/lines.json +227 -0
  29. package/_stories/vega-fixtures/measles-bars.json +348 -0
  30. package/_stories/vega-fixtures/measles-map.json +11101 -0
  31. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  32. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  33. package/_stories/vega-fixtures/no-data.json +14 -0
  34. package/_stories/vega-fixtures/pie-chart.json +94 -0
  35. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  36. package/_stories/vega-fixtures/stacked-area.json +222 -0
  37. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  38. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  39. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  40. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  41. package/_stories/vega-fixtures/warning-combo.json +59 -0
  42. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  43. package/assets/icon-chart-area.svg +1 -0
  44. package/assets/icon-chart-radar.svg +23 -0
  45. package/assets/icon-magnifying-glass.svg +5 -0
  46. package/assets/icon-warming-stripes.svg +13 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
  49. package/components/AdvancedEditor/EmbedEditor.tsx +513 -0
  50. package/components/ComboBox/ComboBox.tsx +345 -0
  51. package/components/ComboBox/combobox.styles.css +185 -0
  52. package/components/ComboBox/index.ts +1 -0
  53. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  54. package/components/DataTable/DataTable.tsx +132 -58
  55. package/components/DataTable/data-table.css +216 -215
  56. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  57. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
  58. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  59. package/components/EditorPanel/DataTableEditor.tsx +51 -25
  60. package/components/EditorPanel/EditorPanel.styles.css +16 -0
  61. package/components/EditorPanel/EditorPanel.tsx +144 -0
  62. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  63. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  64. package/components/EditorPanel/Inputs.tsx +33 -7
  65. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
  66. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +240 -175
  67. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  68. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  69. package/components/Filters/Filters.tsx +31 -5
  70. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  71. package/components/Filters/helpers/handleSorting.ts +1 -1
  72. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +84 -2
  73. package/components/Layout/components/Visualization/index.tsx +27 -1
  74. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  75. package/components/Legend/Legend.Gradient.tsx +1 -1
  76. package/components/MediaControls.tsx +53 -28
  77. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  78. package/components/_stories/DataTable.stories.tsx +1 -0
  79. package/components/ui/Icon.tsx +3 -1
  80. package/components/ui/Title/index.tsx +30 -2
  81. package/components/ui/Title/title.styles.css +42 -0
  82. package/data/colorPalettes.ts +18 -5
  83. package/data/mapColorPalettes.ts +10 -0
  84. package/devTemplate/dev.js +235 -0
  85. package/devTemplate/index.html +30 -0
  86. package/devTemplate/preview.html +1503 -0
  87. package/devTemplate/sidebar.css +151 -0
  88. package/dist/cove-main.css +2803 -4448
  89. package/dist/cove-main.css.map +1 -1
  90. package/generateViteConfig.js +118 -2
  91. package/helpers/DataTransform.ts +1 -5
  92. package/helpers/addValuesToFilters.ts +6 -1
  93. package/helpers/cove/date.ts +33 -1
  94. package/helpers/cove/string.ts +29 -0
  95. package/helpers/coveUpdateWorker.ts +21 -12
  96. package/helpers/embed/embedCodeGenerator.ts +80 -0
  97. package/helpers/embed/embedHelper.js +158 -0
  98. package/helpers/embed/filterUtils.ts +121 -0
  99. package/helpers/embed/index.ts +21 -0
  100. package/helpers/embed/urlValidation.ts +119 -0
  101. package/helpers/filterVizData.ts +6 -1
  102. package/helpers/getFileExtension.ts +0 -6
  103. package/helpers/getUniqueValues.ts +19 -0
  104. package/helpers/hashObj.ts +25 -0
  105. package/helpers/isRightAlignedTableValue.js +5 -0
  106. package/helpers/metrics/helpers.ts +1 -0
  107. package/helpers/metrics/types.ts +3 -0
  108. package/helpers/palettes/colorDistributions.ts +1 -1
  109. package/helpers/palettes/utils.ts +12 -12
  110. package/helpers/parseCsvWithQuotes.ts +15 -14
  111. package/helpers/pivotData.ts +2 -2
  112. package/helpers/prepareScreenshot.ts +288 -0
  113. package/helpers/queryStringUtils.ts +29 -0
  114. package/helpers/testing.ts +44 -0
  115. package/helpers/tests/DataTransform.test.ts +125 -0
  116. package/helpers/tests/date.test.ts +64 -0
  117. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  118. package/helpers/tests/queryStringUtils.test.ts +381 -0
  119. package/helpers/tests/testStandaloneBuild.ts +23 -5
  120. package/helpers/useDataVizClasses.ts +0 -1
  121. package/helpers/vegaConfig.ts +1 -1
  122. package/helpers/vegaConfigImport.ts +160 -0
  123. package/helpers/ver/4.26.1.ts +80 -0
  124. package/helpers/ver/4.26.2.ts +84 -0
  125. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  126. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  127. package/helpers/viewports.ts +2 -0
  128. package/hooks/useDataColumns.ts +63 -0
  129. package/hooks/useFilterManagement.ts +94 -0
  130. package/hooks/useLegendSeparators.ts +26 -0
  131. package/hooks/useListManagement.ts +192 -0
  132. package/package.json +29 -33
  133. package/styles/_button-section.scss +0 -3
  134. package/styles/v2/components/editor.scss +9 -9
  135. package/styles/v2/utils/_grid.scss +8 -3
  136. package/types/Annotation.ts +10 -11
  137. package/types/Axis.ts +1 -0
  138. package/types/ForecastingSeriesKey.ts +1 -0
  139. package/types/General.ts +2 -0
  140. package/types/MarkupInclude.ts +1 -0
  141. package/types/Palette.ts +21 -0
  142. package/types/Series.ts +3 -0
  143. package/types/Table.ts +1 -0
  144. package/types/Visualization.ts +7 -0
  145. package/types/VizFilter.ts +1 -0
  146. package/LICENSE +0 -201
  147. package/_stories/StoryRenderingTests.stories.tsx +0 -164
@@ -13,12 +13,40 @@ type HeaderProps = {
13
13
  ariaLevel?: number
14
14
  config: Visualization
15
15
  theme?: string
16
+ titleStyle: 'legacy' | 'large' | 'small'
17
+ noContent?: boolean
16
18
  }
17
19
 
18
20
  const Title = (props: HeaderProps) => {
19
- const { isDashboard, title, superTitle, classes = [], showTitle = true, ariaLevel = 2 } = props
21
+ const {
22
+ isDashboard,
23
+ title,
24
+ superTitle,
25
+ classes = [],
26
+ showTitle = true,
27
+ ariaLevel = 2,
28
+ titleStyle,
29
+ noContent = false
30
+ } = props
20
31
 
21
- // standard classes every vis should have
32
+ if (titleStyle === 'large' || titleStyle === 'small') {
33
+ const TitleElement = titleStyle === 'large' ? 'h2' : 'h3'
34
+
35
+ return (
36
+ title &&
37
+ showTitle && (
38
+ <div
39
+ className={`cove-title cove-title--${titleStyle}${noContent ? ' cove-title--no-content' : ''}`}
40
+ style={props.style}
41
+ >
42
+ {superTitle && <sup>{parse(superTitle)}</sup>}
43
+ <TitleElement>{parse(title)}</TitleElement>
44
+ </div>
45
+ )
46
+ )
47
+ }
48
+
49
+ // LEGACY BLOCKY HEADER (Original design with colored backgrounds)
22
50
  const updatedClasses = ['cove-component__header', 'component__header', 'mb-3', ...classes]
23
51
 
24
52
  return (
@@ -96,3 +96,45 @@
96
96
  .type-sparkline.type-chart .cove-component__header {
97
97
  margin: 0px !important;
98
98
  }
99
+
100
+ /* ============================================================================
101
+ MODERN TITLE
102
+ ============================================================================ */
103
+
104
+ .cdc-open-viz-module .cove-title--small {
105
+ margin-bottom: 0.75rem;
106
+ }
107
+
108
+ .cdc-open-viz-module .cove-title--large {
109
+ margin-bottom: 1.33rem;
110
+ }
111
+
112
+ /* Used in dashboards to more closely associate a title-only component with the content below it */
113
+ .cdc-open-viz-module .cove-title--no-content {
114
+ margin-top: 1rem;
115
+ margin-bottom: 0;
116
+ }
117
+
118
+ .cdc-open-viz-module .cove-title h2 {
119
+ color: #0b4778;
120
+ font-size: 1.95rem;
121
+ font-family: var(--app-font-secondary);
122
+ border-bottom: 1px solid var(--cool-gray-10);
123
+ font-weight: 300;
124
+ }
125
+
126
+ .cdc-open-viz-module .cove-title h3 {
127
+ font-size: 1.5rem;
128
+ font-family: var(--app-font-secondary);
129
+ }
130
+
131
+ .cdc-open-viz-module .cove-title sup {
132
+ font-family: var(--app-font-secondary);
133
+ font-size: 0.75rem;
134
+ font-weight: 600;
135
+ text-transform: uppercase;
136
+ color: #666;
137
+ sup {
138
+ font-size: 75%;
139
+ }
140
+ }
@@ -1,9 +1,22 @@
1
- import { mapColorPalettes, mapColorPalettesV1, mapColorPalettesV2 } from './mapColorPalettes'
2
- import { chartColorPalettes, sequentialPalettes, colorPalettesChart, colorPalettesChartV1, colorPalettesChartV2, twoColorPalette } from './chartColorPalettes'
3
-
1
+ import { mapColorPalettes, mapColorPalettesV1, mapColorPalettesV2, sequentialZeroColors } from './mapColorPalettes'
2
+ import {
3
+ chartColorPalettes,
4
+ sequentialPalettes,
5
+ colorPalettesChart,
6
+ colorPalettesChartV1,
7
+ colorPalettesChartV2,
8
+ twoColorPalette
9
+ } from './chartColorPalettes'
4
10
 
5
11
  // Re-export map palettes (already processed in mapColorPalettes.ts)
6
- export { mapColorPalettes, mapColorPalettesV1, mapColorPalettesV2 }
12
+ export { mapColorPalettes, mapColorPalettesV1, mapColorPalettesV2, sequentialZeroColors }
7
13
 
8
14
  // Re-export chart palettes (already processed in chartColorPalettes.ts)
9
- export { chartColorPalettes, sequentialPalettes, colorPalettesChart, colorPalettesChartV1, colorPalettesChartV2, twoColorPalette }
15
+ export {
16
+ chartColorPalettes,
17
+ sequentialPalettes,
18
+ colorPalettesChart,
19
+ colorPalettesChartV1,
20
+ colorPalettesChartV2,
21
+ twoColorPalette
22
+ }
@@ -43,3 +43,13 @@ export const mapColorPalettes = {
43
43
 
44
44
  export const mapColorPalettesV1 = mapColorPalettes.v1
45
45
  export const mapColorPalettesV2 = mapColorPalettes.v2
46
+
47
+ // Lighter colors (first palette color 75% toward white) for zero-value categories in v2 sequential palettes
48
+ // Used when: categorical map, gradient legend, first category value is "0", palette not reversed
49
+ export const sequentialZeroColors: Record<string, string> = {
50
+ sequential_blue: '#F6F9FD',
51
+ sequential_teal: '#F9FEFE',
52
+ sequential_purple: '#F8F4F7',
53
+ sequential_orange: '#FFF8F5',
54
+ sequential_green: '#F5FEFD'
55
+ }
@@ -0,0 +1,235 @@
1
+ // Dev template JavaScript
2
+ // Handles config URL params, visualization reloading, and sidebar
3
+
4
+ // Apply config override from ?config= URL parameter (must happen before React loads)
5
+ const params = new URLSearchParams(window.location.search)
6
+ const configParam = params.get('config')
7
+ let editorEnabled = params.get('editor') === 'true'
8
+ const previewEnabled = params.get('preview') === 'true'
9
+
10
+ if (configParam) {
11
+ document.querySelector('.react-container').setAttribute('data-config', configParam)
12
+ }
13
+ if (editorEnabled) {
14
+ document.querySelector('.react-container').setAttribute('data-editor', 'true')
15
+ }
16
+
17
+ // Load the visualization component
18
+ await import('./src/index')
19
+
20
+ // Reload visualization without page refresh
21
+ window.reloadVisualization = async configUrl => {
22
+ const wrapper = document.getElementById('viz-wrapper')
23
+ const editorAttr = editorEnabled ? ' data-editor="true"' : ''
24
+ wrapper.innerHTML = `<div class="react-container" data-config="${configUrl}"${editorAttr}></div>`
25
+ await import(/* @vite-ignore */ `./src/index?t=${Date.now()}`)
26
+ }
27
+
28
+ // Initialize sidebar by default (hide with ?sidebar=false, or for editor package)
29
+ // __COVE_PACKAGE_NAME__ is injected by Vite's define option in generateViteConfig.js
30
+ const sidebarDisabled = params.get('sidebar') === 'false' || __COVE_PACKAGE_NAME__ === 'CdcEditor'
31
+ if (sidebarDisabled) {
32
+ // Remove sidebar margin (the inline script in the HTML template pre-allocates it for ?sidebar!=false,
33
+ // but CdcEditor also disables the sidebar without that param)
34
+ document.body.style.marginLeft = ''
35
+ }
36
+ if (!sidebarDisabled) {
37
+ document.body.classList.add('has-sidebar')
38
+
39
+ // Fetch examples list
40
+ const response = await fetch('/__examples')
41
+ const examples = await response.json()
42
+
43
+ // Get current config
44
+ const currentConfig = configParam || '/examples/default.json'
45
+
46
+ // Build sidebar HTML
47
+ const sidebarRoot = document.getElementById('dev-sidebar-root')
48
+
49
+ // Build a recursive tree structure for arbitrary nesting depth
50
+ const buildTree = files => {
51
+ const tree = { files: [], dirs: {} }
52
+ files.forEach(file => {
53
+ const parts = file.split('/')
54
+ if (parts.length === 1) {
55
+ tree.files.push(file)
56
+ } else {
57
+ const dir = parts[0]
58
+ if (!tree.dirs[dir]) tree.dirs[dir] = []
59
+ tree.dirs[dir].push(parts.slice(1).join('/'))
60
+ }
61
+ })
62
+ // Recursively build subtrees for each directory
63
+ Object.keys(tree.dirs).forEach(dir => {
64
+ tree.dirs[dir] = buildTree(tree.dirs[dir])
65
+ })
66
+ return tree
67
+ }
68
+
69
+ const tree = buildTree(examples)
70
+
71
+ const caseInsensitiveSort = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })
72
+
73
+ // Recursive function to render tree at any depth
74
+ const renderTree = (node, pathPrefix) => {
75
+ let html = ''
76
+
77
+ // Render files at this level
78
+ node.files.sort(caseInsensitiveSort).forEach(file => {
79
+ const configPath = pathPrefix + file
80
+ const isActive = configPath === currentConfig ? ' active' : ''
81
+ html += `<button class="dev-sidebar-item${isActive}" data-config="${configPath}">${file}</button>`
82
+ })
83
+
84
+ // Render subdirectories recursively
85
+ Object.keys(node.dirs)
86
+ .sort(caseInsensitiveSort)
87
+ .forEach(dir => {
88
+ const dirPath = pathPrefix + dir + '/'
89
+ const isOpen = currentConfig.startsWith(dirPath) ? ' open' : ''
90
+ html += `<div class="dev-sidebar-folder${isOpen}" data-folder-path="${dirPath}">${dir}</div>`
91
+ html += '<div class="dev-sidebar-folder-contents">'
92
+ html += renderTree(node.dirs[dir], dirPath)
93
+ html += '</div>'
94
+ })
95
+
96
+ return html
97
+ }
98
+
99
+ // Format package name for display (e.g., "CdcChart" -> "Chart", "CdcMap" -> "Map")
100
+ const formatPackageName = name => {
101
+ if (!name) return ''
102
+ return name
103
+ .replace(/^Cdc/, '')
104
+ .replace(/([A-Z])/g, ' $1')
105
+ .trim()
106
+ }
107
+ const packageDisplayName = formatPackageName(__COVE_PACKAGE_NAME__)
108
+
109
+ const editorToggleClass = editorEnabled ? ' active' : ''
110
+ const previewToggleClass = previewEnabled ? ' active' : ''
111
+ let html = '<nav class="dev-sidebar">'
112
+ html += `<div class="dev-sidebar-header"><span>${packageDisplayName} Examples</span><div class="dev-sidebar-toggles"><button class="dev-sidebar-toggle${previewToggleClass}" id="dev-preview-toggle" title="Toggle CDC Page Preview"><svg width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 1.5h7l3.5 3.5V14a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/><path d="M10 1.5V5h3.5"/></svg></button><button class="dev-sidebar-toggle dev-sidebar-editor-toggle${editorToggleClass}" id="dev-editor-toggle" title="Toggle Editor">⚙</button></div></div>`
113
+ html +=
114
+ '<div class="dev-sidebar-search"><input type="text" id="dev-sidebar-search-input" placeholder="Search examples..." /></div>'
115
+ html += '<div class="dev-sidebar-tree">'
116
+ html += renderTree(tree, '/examples/')
117
+ html += '</div></nav>'
118
+ sidebarRoot.innerHTML = html
119
+
120
+ // Search functionality
121
+ const searchInput = document.getElementById('dev-sidebar-search-input')
122
+ searchInput.addEventListener('input', e => {
123
+ const query = e.target.value.toLowerCase()
124
+ const items = sidebarRoot.querySelectorAll('.dev-sidebar-item')
125
+ const folders = sidebarRoot.querySelectorAll('.dev-sidebar-folder')
126
+
127
+ if (!query) {
128
+ // Reset: show all items, collapse folders (except those with active item)
129
+ items.forEach(item => (item.style.display = ''))
130
+ folders.forEach(folder => {
131
+ folder.style.display = ''
132
+ if (!folder.nextElementSibling?.querySelector('.active')) {
133
+ folder.classList.remove('open')
134
+ }
135
+ })
136
+ return
137
+ }
138
+
139
+ // Split query into tokens - all must match (in any order)
140
+ const tokens = query.split(/\s+/).filter(t => t)
141
+ const matchesAllTokens = text => tokens.every(token => text.includes(token))
142
+
143
+ // First pass: find folders that match the query
144
+ const matchingFolderPaths = new Set()
145
+ folders.forEach(folder => {
146
+ const folderName = folder.textContent.toLowerCase()
147
+ if (matchesAllTokens(folderName)) {
148
+ matchingFolderPaths.add(folder.dataset.folderPath)
149
+ }
150
+ })
151
+
152
+ // Filter items: show if item matches OR is inside a matching folder
153
+ items.forEach(item => {
154
+ const configPath = item.dataset.config
155
+ const itemMatches = matchesAllTokens(item.textContent.toLowerCase())
156
+ const inMatchingFolder = [...matchingFolderPaths].some(folderPath => configPath.startsWith(folderPath))
157
+ item.style.display = itemMatches || inMatchingFolder ? '' : 'none'
158
+ })
159
+
160
+ // Show/hide folders based on whether they match or have visible children
161
+ folders.forEach(folder => {
162
+ const folderPath = folder.dataset.folderPath
163
+ const folderMatches = matchingFolderPaths.has(folderPath)
164
+ const contents = folder.nextElementSibling
165
+ const hasVisibleChildren = contents?.querySelector('.dev-sidebar-item:not([style*="display: none"])')
166
+ folder.style.display = folderMatches || hasVisibleChildren ? '' : 'none'
167
+ if (folderMatches || hasVisibleChildren) {
168
+ folder.classList.add('open')
169
+ }
170
+ })
171
+ })
172
+
173
+ // Click handlers for files
174
+ sidebarRoot.querySelectorAll('.dev-sidebar-item').forEach(btn => {
175
+ btn.addEventListener('click', async () => {
176
+ const configPath = btn.dataset.config
177
+
178
+ // Update active state
179
+ sidebarRoot.querySelectorAll('.dev-sidebar-item').forEach(b => b.classList.remove('active'))
180
+ btn.classList.add('active')
181
+
182
+ // Update URL without reload - keep clean if selecting default
183
+ const url = new URL(window.location)
184
+ if (configPath === '/examples/default.json') {
185
+ url.searchParams.delete('config')
186
+ } else {
187
+ url.searchParams.set('config', configPath)
188
+ }
189
+ history.pushState({}, '', url.toString().replace(/%2F/g, '/'))
190
+
191
+ // Reload visualization
192
+ await window.reloadVisualization(configPath)
193
+ })
194
+ })
195
+
196
+ // Click handlers for folders
197
+ sidebarRoot.querySelectorAll('.dev-sidebar-folder').forEach(folder => {
198
+ folder.addEventListener('click', () => {
199
+ folder.classList.toggle('open')
200
+ })
201
+ })
202
+
203
+ // Editor toggle handler
204
+ const editorToggle = document.getElementById('dev-editor-toggle')
205
+ editorToggle.addEventListener('click', async () => {
206
+ editorEnabled = !editorEnabled
207
+ editorToggle.classList.toggle('active', editorEnabled)
208
+
209
+ // Update URL
210
+ const url = new URL(window.location)
211
+ if (editorEnabled) {
212
+ url.searchParams.set('editor', 'true')
213
+ } else {
214
+ url.searchParams.delete('editor')
215
+ }
216
+ history.pushState({}, '', url.toString().replace(/%2F/g, '/'))
217
+
218
+ // Reload visualization with new editor state
219
+ const currentConfig =
220
+ document.querySelector('.react-container')?.getAttribute('data-config') || '/examples/default.json'
221
+ await window.reloadVisualization(currentConfig)
222
+ })
223
+
224
+ // Preview toggle handler - full page reload since server needs to serve different HTML
225
+ const previewToggle = document.getElementById('dev-preview-toggle')
226
+ previewToggle.addEventListener('click', () => {
227
+ const url = new URL(window.location)
228
+ if (previewEnabled) {
229
+ url.searchParams.delete('preview')
230
+ } else {
231
+ url.searchParams.set('preview', 'true')
232
+ }
233
+ window.location.href = url.toString().replace(/%2F/g, '/')
234
+ })
235
+ }
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ border-top: none !important;
10
+ min-height: calc(100vh + 1px);
11
+ }
12
+ /* {{PACKAGE_CSS}} */
13
+ /* {{SIDEBAR_CSS}} */
14
+ </style>
15
+ <link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
16
+ </head>
17
+ <body>
18
+ <script>
19
+ if (new URLSearchParams(location.search).get('sidebar') !== 'false') document.body.style.marginLeft = '240px'
20
+ </script>
21
+ <div id="dev-sidebar-root"></div>
22
+ <div id="viz-wrapper">
23
+ <div class="react-container" data-config="/examples/default.json"></div>
24
+ </div>
25
+ <noscript>You need to enable JavaScript to run this app.</noscript>
26
+ <script type="module">
27
+ // {{DEV_JS}}
28
+ </script>
29
+ </body>
30
+ </html>