@cdc/core 4.26.1 → 4.26.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.
Files changed (249) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/LICENSE +201 -0
  4. package/_stories/Gallery.Charts.stories.tsx +35 -42
  5. package/_stories/Gallery.DataBite.stories.tsx +15 -8
  6. package/_stories/Gallery.Maps.stories.tsx +37 -28
  7. package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
  8. package/_stories/PageART.stories.tsx +5 -4
  9. package/_stories/PageBRFSS.stories.tsx +21 -16
  10. package/_stories/PageCancerRegistries.stories.tsx +15 -15
  11. package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
  12. package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
  13. package/_stories/PageMaternalMortality.stories.tsx +5 -4
  14. package/_stories/PageOralHealth.stories.tsx +15 -10
  15. package/_stories/PageRespiratory.stories.tsx +4 -4
  16. package/_stories/PageSmokingTobacco.stories.tsx +15 -10
  17. package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
  18. package/_stories/PageWastewater.stories.tsx +44 -30
  19. package/_stories/VegaImport.stories.tsx +401 -0
  20. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  21. package/_stories/vega-fixtures/bars.json +58 -0
  22. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  23. package/_stories/vega-fixtures/combo.json +68 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  25. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  26. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  27. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  28. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  29. package/_stories/vega-fixtures/lines.json +227 -0
  30. package/_stories/vega-fixtures/measles-bars.json +348 -0
  31. package/_stories/vega-fixtures/measles-map.json +11101 -0
  32. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  33. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  34. package/_stories/vega-fixtures/no-data.json +14 -0
  35. package/_stories/vega-fixtures/pie-chart.json +94 -0
  36. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  37. package/_stories/vega-fixtures/stacked-area.json +222 -0
  38. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  39. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  40. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  41. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  42. package/_stories/vega-fixtures/warning-combo.json +59 -0
  43. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  44. package/assets/callout-flag.svg +7 -0
  45. package/assets/icon-chart-area.svg +1 -0
  46. package/assets/icon-chart-radar.svg +23 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
  49. package/components/Alert/components/Alert.styles.css +2 -2
  50. package/components/ComboBox/combobox.styles.css +48 -48
  51. package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
  52. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  53. package/components/DataTable/DataTable.tsx +46 -18
  54. package/components/DataTable/DataTableStandAlone.tsx +1 -0
  55. package/components/DataTable/components/ChartHeader.tsx +21 -12
  56. package/components/DataTable/components/MapHeader.tsx +34 -28
  57. package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
  58. package/components/DataTable/data-table.css +50 -52
  59. package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
  60. package/components/DataTable/helpers/getChartCellValue.ts +10 -7
  61. package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
  62. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  63. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
  64. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
  65. package/components/DownloadButton.tsx +14 -6
  66. package/components/EditorPanel/ColumnsEditor.tsx +38 -31
  67. package/components/EditorPanel/CustomSortOrder.tsx +94 -0
  68. package/components/EditorPanel/DataTableEditor.tsx +139 -23
  69. package/components/EditorPanel/EditorPanel.styles.css +71 -71
  70. package/components/EditorPanel/EditorPanel.tsx +3 -8
  71. package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
  72. package/components/EditorPanel/FootnotesEditor.tsx +2 -2
  73. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
  74. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
  75. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  76. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
  77. package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
  78. package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
  79. package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
  80. package/components/EditorPanel/sections/VisualSection.tsx +11 -0
  81. package/components/EditorWrapper/editor-wrapper.style.css +1 -1
  82. package/components/Filters/Filters.tsx +3 -5
  83. package/components/Filters/components/Tabs.tsx +19 -7
  84. package/{styles → components/Filters}/filters.scss +3 -3
  85. package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
  86. package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
  87. package/components/Layout/components/Responsive.tsx +14 -6
  88. package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
  89. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
  90. package/components/Layout/components/Visualization/index.tsx +50 -38
  91. package/components/Layout/components/Visualization/visualizations.scss +232 -15
  92. package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
  93. package/components/Layout/components/VisualizationContainer.tsx +37 -0
  94. package/components/Layout/components/VisualizationContent.test.tsx +182 -0
  95. package/components/Layout/components/VisualizationContent.tsx +75 -0
  96. package/components/Layout/index.tsx +5 -5
  97. package/components/Layout/styles/editor-utils.scss +3 -3
  98. package/components/Layout/styles/editor.scss +4 -4
  99. package/components/Legend/Legend.Gradient.tsx +7 -1
  100. package/components/Loader/loader.styles.css +2 -2
  101. package/components/Loading.jsx +1 -1
  102. package/components/MediaControls.tsx +10 -3
  103. package/components/MultiSelect/multiselect.styles.css +19 -19
  104. package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
  105. package/components/PaletteSelector/PaletteSelector.css +15 -15
  106. package/components/RichTooltip/richTooltip.css +6 -6
  107. package/components/Table/table.styles.css +2 -2
  108. package/components/Waiting.tsx +1 -1
  109. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  110. package/components/_stories/DataTable.stories.tsx +1 -0
  111. package/components/_stories/Filters.stories.tsx +1 -1
  112. package/components/_stories/styles.scss +0 -1
  113. package/components/elements/Button.jsx +1 -1
  114. package/components/elements/Card.jsx +1 -1
  115. package/{styles/v2/components → components/elements}/button.scss +9 -8
  116. package/components/inputs/InputCheckbox.jsx +1 -1
  117. package/components/inputs/InputSelect.tsx +1 -1
  118. package/components/inputs/InputText.jsx +1 -1
  119. package/components/inputs/InputToggle.tsx +1 -1
  120. package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
  121. package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
  122. package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
  123. package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
  124. package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
  125. package/{styles → components}/loading.scss +1 -1
  126. package/components/managers/DataDesigner.tsx +1 -1
  127. package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
  128. package/components/ui/Accordion.jsx +1 -1
  129. package/components/ui/Icon.tsx +1 -1
  130. package/components/ui/LoadSpin.jsx +1 -1
  131. package/components/ui/Modal.jsx +1 -1
  132. package/components/ui/Overlay.jsx +1 -1
  133. package/components/ui/Title/index.test.tsx +34 -0
  134. package/components/ui/Title/index.tsx +24 -7
  135. package/components/ui/Title/title.styles.css +119 -25
  136. package/components/ui/Tooltip.tsx +1 -1
  137. package/components/ui/_stories/Title.stories.tsx +1 -1
  138. package/{styles/v2/components → components/ui}/accordion.scss +3 -3
  139. package/components/ui/accordion.styles.css +11 -11
  140. package/{styles/v2/components → components/ui}/modal.scss +2 -2
  141. package/{styles/v2/components → components/ui}/overlay.scss +6 -6
  142. package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
  143. package/{styles → components}/waiting.scss +9 -3
  144. package/data/colorPalettes.ts +18 -5
  145. package/data/mapColorPalettes.ts +10 -0
  146. package/devTemplate/dev.js +285 -0
  147. package/devTemplate/index.html +30 -0
  148. package/devTemplate/preview.html +1503 -0
  149. package/devTemplate/sidebar.css +151 -0
  150. package/dist/cove-main.css +2530 -3901
  151. package/dist/cove-main.css.map +1 -1
  152. package/generateViteConfig.js +111 -2
  153. package/helpers/DataTransform.ts +1 -5
  154. package/helpers/backfillDefaults.ts +35 -0
  155. package/helpers/constants.ts +12 -0
  156. package/helpers/cove/date.ts +64 -3
  157. package/helpers/cove/number.ts +29 -15
  158. package/helpers/cove/string.ts +29 -0
  159. package/helpers/coveUpdateWorker.ts +14 -8
  160. package/helpers/displayDataAsText.ts +1 -1
  161. package/helpers/embed/embedCodeGenerator.ts +80 -0
  162. package/helpers/embed/embedHelper.js +169 -0
  163. package/helpers/embed/filterUtils.ts +121 -0
  164. package/helpers/embed/index.ts +17 -0
  165. package/helpers/embed/urlValidation.ts +119 -0
  166. package/helpers/extractDataAndMetadata.ts +20 -0
  167. package/helpers/fetchRemoteData.ts +14 -8
  168. package/helpers/filterVizData.ts +6 -1
  169. package/helpers/getFileExtension.ts +0 -6
  170. package/helpers/labelHash.ts +9 -0
  171. package/helpers/markupProcessor.ts +56 -38
  172. package/helpers/metrics/types.ts +3 -0
  173. package/helpers/palettes/colorDistributions.ts +1 -1
  174. package/helpers/palettes/utils.ts +12 -12
  175. package/helpers/parseCsvWithQuotes.ts +15 -14
  176. package/helpers/prepareScreenshot.ts +33 -10
  177. package/helpers/testing.ts +44 -0
  178. package/helpers/tests/DataTransform.test.ts +125 -0
  179. package/helpers/tests/abbreviateNumber.test.ts +59 -0
  180. package/helpers/tests/backfillDefaults.test.ts +253 -0
  181. package/helpers/tests/date.test.ts +110 -0
  182. package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
  183. package/helpers/tests/markupProcessor.test.ts +315 -124
  184. package/helpers/tests/number.test.ts +42 -0
  185. package/helpers/tests/prepareScreenshot.test.ts +28 -28
  186. package/helpers/tests/testStandaloneBuild.ts +36 -26
  187. package/helpers/tests/useDataVizClasses.test.ts +66 -0
  188. package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
  189. package/helpers/useDataVizClasses.ts +13 -7
  190. package/helpers/vegaConfig.ts +1 -1
  191. package/helpers/vegaConfigImport.ts +160 -0
  192. package/helpers/ver/4.24.4.ts +24 -0
  193. package/helpers/ver/4.26.1.ts +1 -1
  194. package/helpers/ver/4.26.2.ts +84 -0
  195. package/helpers/ver/4.26.3.ts +44 -0
  196. package/helpers/ver/4.26.4.ts +31 -0
  197. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  198. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  199. package/helpers/ver/tests/4.26.3.test.ts +168 -0
  200. package/helpers/ver/tests/4.26.4.test.ts +88 -0
  201. package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
  202. package/helpers/viewports.ts +2 -0
  203. package/package.json +27 -32
  204. package/styles/_global.scss +7 -7
  205. package/styles/_reset.scss +2 -2
  206. package/styles/{v2/base → base}/_file-selector.scss +4 -4
  207. package/styles/{v2/base → base}/_general.scss +2 -4
  208. package/styles/{v2/base → base}/index.scss +1 -1
  209. package/styles/base.scss +107 -165
  210. package/styles/cove-main.scss +3 -6
  211. package/styles/layout/_component.scss +110 -0
  212. package/styles/{v2/layout → layout}/_data-table.scss +7 -7
  213. package/styles/layout/_wrapper-padding.scss +27 -0
  214. package/styles/{v2/main.scss → main.scss} +3 -1
  215. package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
  216. package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
  217. package/styles/{v2/utils → utils}/_grid.scss +8 -3
  218. package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
  219. package/styles/{v2/utils → utils}/index.scss +2 -1
  220. package/types/Annotation.ts +10 -11
  221. package/types/Axis.ts +2 -0
  222. package/types/ComponentStyles.ts +1 -0
  223. package/types/ConfigureData.ts +1 -0
  224. package/types/General.ts +2 -0
  225. package/types/MarkupInclude.ts +1 -0
  226. package/types/MarkupVariable.ts +2 -1
  227. package/types/Palette.ts +22 -0
  228. package/types/Table.ts +9 -0
  229. package/types/Visualization.ts +7 -0
  230. package/_stories/StoryRenderingTests.stories.tsx +0 -164
  231. package/helpers/embedCodeGenerator.ts +0 -109
  232. package/styles/_common-components.css +0 -73
  233. package/styles/_variables.scss +0 -63
  234. package/styles/v2/layout/_component.scss +0 -21
  235. package/styles/v2/utils/_variables.scss +0 -9
  236. package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
  237. /package/{styles/v2/components → components/ui}/icon.scss +0 -0
  238. /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
  239. /package/styles/{v2/base → base}/_heading.scss +0 -0
  240. /package/styles/{v2/base → base}/_reset.scss +0 -0
  241. /package/styles/{v2/layout → layout}/_alert.scss +0 -0
  242. /package/styles/{v2/layout → layout}/_progression.scss +0 -0
  243. /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
  244. /package/styles/{v2/layout → layout}/index.scss +0 -0
  245. /package/styles/{v2/themes → themes}/index.scss +0 -0
  246. /package/styles/{v2/utils → utils}/_align.scss +0 -0
  247. /package/styles/{v2/utils → utils}/_animations.scss +0 -0
  248. /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
  249. /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
@@ -0,0 +1,285 @@
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
+ const sectionGapParam = params.get('sectionGap')
10
+ const fontSizeParam = params.get('fontSize')
11
+ const metaFontSizeParam = params.get('metaFontSize')
12
+
13
+ if (sectionGapParam) {
14
+ const numericGap = Number(sectionGapParam)
15
+ if (!Number.isNaN(numericGap) && numericGap >= 0) {
16
+ document.documentElement.style.setProperty('--cove-visualization-section-gap', `${numericGap}px`)
17
+ }
18
+ }
19
+
20
+ if (fontSizeParam) {
21
+ const numericFontSize = Number(fontSizeParam)
22
+ if (!Number.isNaN(numericFontSize) && numericFontSize > 0) {
23
+ document.documentElement.style.setProperty('--app-font-size', `${numericFontSize}px`)
24
+ }
25
+ }
26
+
27
+ if (metaFontSizeParam) {
28
+ const numericMetaFontSize = Number(metaFontSizeParam)
29
+ if (!Number.isNaN(numericMetaFontSize) && numericMetaFontSize > 0) {
30
+ document.documentElement.style.setProperty('--visualization-meta-font-size', `${numericMetaFontSize}px`)
31
+ }
32
+ }
33
+
34
+ window.addEventListener('message', event => {
35
+ const { data } = event
36
+ if (!data) return
37
+
38
+ if (data.type === 'cove:setSectionGap') {
39
+ const numericGap = Number(data.value)
40
+ if (Number.isNaN(numericGap) || numericGap < 0) return
41
+
42
+ document.documentElement.style.setProperty('--cove-visualization-section-gap', `${numericGap}px`)
43
+ }
44
+
45
+ if (data.type === 'cove:setFontSize') {
46
+ const numericFontSize = Number(data.value)
47
+ if (Number.isNaN(numericFontSize) || numericFontSize <= 0) return
48
+
49
+ document.documentElement.style.setProperty('--app-font-size', `${numericFontSize}px`)
50
+ }
51
+
52
+ if (data.type === 'cove:setMetaFontSize') {
53
+ const numericMetaFontSize = Number(data.value)
54
+ if (Number.isNaN(numericMetaFontSize) || numericMetaFontSize <= 0) return
55
+
56
+ document.documentElement.style.setProperty('--visualization-meta-font-size', `${numericMetaFontSize}px`)
57
+ }
58
+ })
59
+
60
+ if (configParam) {
61
+ document.querySelector('.react-container').setAttribute('data-config', configParam)
62
+ }
63
+ if (editorEnabled) {
64
+ document.querySelector('.react-container').setAttribute('data-editor', 'true')
65
+ }
66
+
67
+ // Load the visualization component
68
+ await import('./src/index')
69
+
70
+ // Reload visualization without page refresh
71
+ window.reloadVisualization = async configUrl => {
72
+ const wrapper = document.getElementById('viz-wrapper')
73
+ const editorAttr = editorEnabled ? ' data-editor="true"' : ''
74
+ wrapper.innerHTML = `<div class="react-container" data-config="${configUrl}"${editorAttr}></div>`
75
+ await import(/* @vite-ignore */ `./src/index?t=${Date.now()}`)
76
+ }
77
+
78
+ // Initialize sidebar by default (hide with ?sidebar=false, or for editor package)
79
+ // __COVE_PACKAGE_NAME__ is injected by Vite's define option in generateViteConfig.js
80
+ const sidebarDisabled = params.get('sidebar') === 'false' || __COVE_PACKAGE_NAME__ === 'CdcEditor'
81
+ if (sidebarDisabled) {
82
+ // Remove sidebar margin (the inline script in the HTML template pre-allocates it for ?sidebar!=false,
83
+ // but CdcEditor also disables the sidebar without that param)
84
+ document.body.style.marginLeft = ''
85
+ }
86
+ if (!sidebarDisabled) {
87
+ document.body.classList.add('has-sidebar')
88
+
89
+ // Fetch examples list
90
+ const response = await fetch('/__examples')
91
+ const examples = await response.json()
92
+
93
+ // Get current config
94
+ const currentConfig = configParam || '/examples/default.json'
95
+
96
+ // Build sidebar HTML
97
+ const sidebarRoot = document.getElementById('dev-sidebar-root')
98
+
99
+ // Build a recursive tree structure for arbitrary nesting depth
100
+ const buildTree = files => {
101
+ const tree = { files: [], dirs: {} }
102
+ files.forEach(file => {
103
+ const parts = file.split('/')
104
+ if (parts.length === 1) {
105
+ tree.files.push(file)
106
+ } else {
107
+ const dir = parts[0]
108
+ if (!tree.dirs[dir]) tree.dirs[dir] = []
109
+ tree.dirs[dir].push(parts.slice(1).join('/'))
110
+ }
111
+ })
112
+ // Recursively build subtrees for each directory
113
+ Object.keys(tree.dirs).forEach(dir => {
114
+ tree.dirs[dir] = buildTree(tree.dirs[dir])
115
+ })
116
+ return tree
117
+ }
118
+
119
+ const tree = buildTree(examples)
120
+
121
+ const caseInsensitiveSort = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })
122
+
123
+ // Recursive function to render tree at any depth
124
+ const renderTree = (node, pathPrefix) => {
125
+ let html = ''
126
+
127
+ // Render files at this level
128
+ node.files.sort(caseInsensitiveSort).forEach(file => {
129
+ const configPath = pathPrefix + file
130
+ const isActive = configPath === currentConfig ? ' active' : ''
131
+ html += `<button class="dev-sidebar-item${isActive}" data-config="${configPath}">${file}</button>`
132
+ })
133
+
134
+ // Render subdirectories recursively
135
+ Object.keys(node.dirs)
136
+ .sort(caseInsensitiveSort)
137
+ .forEach(dir => {
138
+ const dirPath = pathPrefix + dir + '/'
139
+ const isOpen = currentConfig.startsWith(dirPath) ? ' open' : ''
140
+ html += `<div class="dev-sidebar-folder${isOpen}" data-folder-path="${dirPath}">${dir}</div>`
141
+ html += '<div class="dev-sidebar-folder-contents">'
142
+ html += renderTree(node.dirs[dir], dirPath)
143
+ html += '</div>'
144
+ })
145
+
146
+ return html
147
+ }
148
+
149
+ // Format package name for display (e.g., "CdcChart" -> "Chart", "CdcMap" -> "Map")
150
+ const formatPackageName = name => {
151
+ if (!name) return ''
152
+ return name
153
+ .replace(/^Cdc/, '')
154
+ .replace(/([A-Z])/g, ' $1')
155
+ .trim()
156
+ }
157
+ const packageDisplayName = formatPackageName(__COVE_PACKAGE_NAME__)
158
+
159
+ const editorToggleClass = editorEnabled ? ' active' : ''
160
+ const previewToggleClass = previewEnabled ? ' active' : ''
161
+ let html = '<nav class="dev-sidebar">'
162
+ 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>`
163
+ html +=
164
+ '<div class="dev-sidebar-search"><input type="text" id="dev-sidebar-search-input" placeholder="Search examples..." /></div>'
165
+ html += '<div class="dev-sidebar-tree">'
166
+ html += renderTree(tree, '/examples/')
167
+ html += '</div></nav>'
168
+ sidebarRoot.innerHTML = html
169
+
170
+ // Search functionality
171
+ const searchInput = document.getElementById('dev-sidebar-search-input')
172
+ searchInput.addEventListener('input', e => {
173
+ const query = e.target.value.toLowerCase()
174
+ const items = sidebarRoot.querySelectorAll('.dev-sidebar-item')
175
+ const folders = sidebarRoot.querySelectorAll('.dev-sidebar-folder')
176
+
177
+ if (!query) {
178
+ // Reset: show all items, collapse folders (except those with active item)
179
+ items.forEach(item => (item.style.display = ''))
180
+ folders.forEach(folder => {
181
+ folder.style.display = ''
182
+ if (!folder.nextElementSibling?.querySelector('.active')) {
183
+ folder.classList.remove('open')
184
+ }
185
+ })
186
+ return
187
+ }
188
+
189
+ // Split query into tokens - all must match (in any order)
190
+ const tokens = query.split(/\s+/).filter(t => t)
191
+ const matchesAllTokens = text => tokens.every(token => text.includes(token))
192
+
193
+ // First pass: find folders that match the query
194
+ const matchingFolderPaths = new Set()
195
+ folders.forEach(folder => {
196
+ const folderName = folder.textContent.toLowerCase()
197
+ if (matchesAllTokens(folderName)) {
198
+ matchingFolderPaths.add(folder.dataset.folderPath)
199
+ }
200
+ })
201
+
202
+ // Filter items: show if item matches OR is inside a matching folder
203
+ items.forEach(item => {
204
+ const configPath = item.dataset.config
205
+ const itemMatches = matchesAllTokens(item.textContent.toLowerCase())
206
+ const inMatchingFolder = [...matchingFolderPaths].some(folderPath => configPath.startsWith(folderPath))
207
+ item.style.display = itemMatches || inMatchingFolder ? '' : 'none'
208
+ })
209
+
210
+ // Show/hide folders based on whether they match or have visible children
211
+ folders.forEach(folder => {
212
+ const folderPath = folder.dataset.folderPath
213
+ const folderMatches = matchingFolderPaths.has(folderPath)
214
+ const contents = folder.nextElementSibling
215
+ const hasVisibleChildren = contents?.querySelector('.dev-sidebar-item:not([style*="display: none"])')
216
+ folder.style.display = folderMatches || hasVisibleChildren ? '' : 'none'
217
+ if (folderMatches || hasVisibleChildren) {
218
+ folder.classList.add('open')
219
+ }
220
+ })
221
+ })
222
+
223
+ // Click handlers for files
224
+ sidebarRoot.querySelectorAll('.dev-sidebar-item').forEach(btn => {
225
+ btn.addEventListener('click', async () => {
226
+ const configPath = btn.dataset.config
227
+
228
+ // Update active state
229
+ sidebarRoot.querySelectorAll('.dev-sidebar-item').forEach(b => b.classList.remove('active'))
230
+ btn.classList.add('active')
231
+
232
+ // Update URL without reload - keep clean if selecting default
233
+ const url = new URL(window.location)
234
+ if (configPath === '/examples/default.json') {
235
+ url.searchParams.delete('config')
236
+ } else {
237
+ url.searchParams.set('config', configPath)
238
+ }
239
+ history.pushState({}, '', url.toString().replace(/%2F/g, '/'))
240
+
241
+ // Reload visualization
242
+ await window.reloadVisualization(configPath)
243
+ })
244
+ })
245
+
246
+ // Click handlers for folders
247
+ sidebarRoot.querySelectorAll('.dev-sidebar-folder').forEach(folder => {
248
+ folder.addEventListener('click', () => {
249
+ folder.classList.toggle('open')
250
+ })
251
+ })
252
+
253
+ // Editor toggle handler
254
+ const editorToggle = document.getElementById('dev-editor-toggle')
255
+ editorToggle.addEventListener('click', async () => {
256
+ editorEnabled = !editorEnabled
257
+ editorToggle.classList.toggle('active', editorEnabled)
258
+
259
+ // Update URL
260
+ const url = new URL(window.location)
261
+ if (editorEnabled) {
262
+ url.searchParams.set('editor', 'true')
263
+ } else {
264
+ url.searchParams.delete('editor')
265
+ }
266
+ history.pushState({}, '', url.toString().replace(/%2F/g, '/'))
267
+
268
+ // Reload visualization with new editor state
269
+ const currentConfig =
270
+ document.querySelector('.react-container')?.getAttribute('data-config') || '/examples/default.json'
271
+ await window.reloadVisualization(currentConfig)
272
+ })
273
+
274
+ // Preview toggle handler - full page reload since server needs to serve different HTML
275
+ const previewToggle = document.getElementById('dev-preview-toggle')
276
+ previewToggle.addEventListener('click', () => {
277
+ const url = new URL(window.location)
278
+ if (previewEnabled) {
279
+ url.searchParams.delete('preview')
280
+ } else {
281
+ url.searchParams.set('preview', 'true')
282
+ }
283
+ window.location.href = url.toString().replace(/%2F/g, '/')
284
+ })
285
+ }
@@ -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>