@cdc/core 4.25.11 → 4.26.1

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 (77) hide show
  1. package/_stories/Gallery.Charts.stories.tsx +307 -0
  2. package/_stories/Gallery.DataBite.stories.tsx +72 -0
  3. package/_stories/Gallery.Maps.stories.tsx +230 -0
  4. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  5. package/_stories/PageART.stories.tsx +192 -0
  6. package/_stories/PageBRFSS.stories.tsx +289 -0
  7. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  8. package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
  9. package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
  10. package/_stories/PageMaternalMortality.stories.tsx +192 -0
  11. package/_stories/PageOralHealth.stories.tsx +196 -0
  12. package/_stories/PageRespiratory.stories.tsx +332 -0
  13. package/_stories/PageSmokingTobacco.stories.tsx +195 -0
  14. package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
  15. package/_stories/PageWastewater.stories.tsx +463 -0
  16. package/assets/icon-magnifying-glass.svg +5 -0
  17. package/assets/icon-warming-stripes.svg +13 -0
  18. package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
  19. package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
  20. package/components/ComboBox/ComboBox.tsx +345 -0
  21. package/components/ComboBox/combobox.styles.css +185 -0
  22. package/components/ComboBox/index.ts +1 -0
  23. package/components/DataTable/DataTable.tsx +132 -58
  24. package/components/DataTable/data-table.css +216 -215
  25. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
  26. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  27. package/components/EditorPanel/DataTableEditor.tsx +51 -25
  28. package/components/EditorPanel/EditorPanel.styles.css +16 -0
  29. package/components/EditorPanel/EditorPanel.tsx +144 -0
  30. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  31. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  32. package/components/EditorPanel/Inputs.tsx +33 -7
  33. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +236 -175
  34. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  35. package/components/Filters/Filters.tsx +31 -5
  36. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  37. package/components/Filters/helpers/handleSorting.ts +1 -1
  38. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
  39. package/components/Layout/components/Visualization/index.tsx +16 -1
  40. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  41. package/components/Legend/Legend.Gradient.tsx +1 -1
  42. package/components/MediaControls.tsx +53 -27
  43. package/components/ui/Icon.tsx +3 -1
  44. package/components/ui/Title/index.tsx +30 -2
  45. package/components/ui/Title/title.styles.css +42 -0
  46. package/dist/cove-main.css +26 -3
  47. package/dist/cove-main.css.map +1 -1
  48. package/generateViteConfig.js +8 -1
  49. package/helpers/addValuesToFilters.ts +6 -1
  50. package/helpers/coveUpdateWorker.ts +19 -12
  51. package/helpers/embedCodeGenerator.ts +109 -0
  52. package/helpers/getUniqueValues.ts +19 -0
  53. package/helpers/hashObj.ts +25 -0
  54. package/helpers/isRightAlignedTableValue.js +5 -0
  55. package/helpers/metrics/helpers.ts +1 -0
  56. package/helpers/pivotData.ts +2 -2
  57. package/helpers/prepareScreenshot.ts +268 -0
  58. package/helpers/queryStringUtils.ts +29 -0
  59. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  60. package/helpers/tests/queryStringUtils.test.ts +381 -0
  61. package/helpers/tests/testStandaloneBuild.ts +23 -5
  62. package/helpers/useDataVizClasses.ts +0 -1
  63. package/helpers/ver/4.26.1.ts +80 -0
  64. package/hooks/useDataColumns.ts +63 -0
  65. package/hooks/useFilterManagement.ts +94 -0
  66. package/hooks/useLegendSeparators.ts +26 -0
  67. package/hooks/useListManagement.ts +192 -0
  68. package/package.json +4 -3
  69. package/styles/_button-section.scss +0 -3
  70. package/types/Axis.ts +1 -0
  71. package/types/ForecastingSeriesKey.ts +1 -0
  72. package/types/MarkupInclude.ts +1 -0
  73. package/types/Series.ts +3 -0
  74. package/types/Table.ts +1 -0
  75. package/types/Visualization.ts +1 -0
  76. package/types/VizFilter.ts +1 -0
  77. package/LICENSE +0 -201
@@ -0,0 +1,414 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+ import { prepareClonedElements } from '../prepareScreenshot'
3
+
4
+ describe('prepareClonedElements', () => {
5
+ // Helper to create DOM structure and append to document
6
+ function createDOM(htmlString: string): HTMLElement {
7
+ const container = document.createElement('div')
8
+ container.innerHTML = htmlString
9
+ document.body.appendChild(container)
10
+ return container
11
+ }
12
+
13
+ // Clean up after each test
14
+ afterEach(() => {
15
+ document.body.innerHTML = ''
16
+ })
17
+
18
+ describe('includeContextInDownload = false', () => {
19
+ it('should return viz-only clone when context not requested', () => {
20
+ const container = createDOM(`
21
+ <div class="dfe-section">
22
+ <h2>Title</h2>
23
+ <p>Text</p>
24
+ <div class="cdc-open-viz-module" data-download-id="viz1">Viz Content</div>
25
+ </div>
26
+ `)
27
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
28
+
29
+ const result = prepareClonedElements(viz, false, 'viz1')
30
+
31
+ // When context not requested, clonedTree should be same as clonedViz (just the viz)
32
+ expect(result.clonedTree).toBe(result.clonedViz)
33
+ expect(result.clonedTree.querySelector('h2')).toBeNull()
34
+ expect(result.clonedTree.querySelector('p')).toBeNull()
35
+ })
36
+
37
+ it('should return viz-only even with perfect context available when not requested', () => {
38
+ const container = createDOM(`
39
+ <div class="dfe-section">
40
+ <h2>Perfect Title</h2>
41
+ <p>Perfect description</p>
42
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
43
+ </div>
44
+ `)
45
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
46
+
47
+ const result = prepareClonedElements(viz, false, 'viz1')
48
+
49
+ // Even though context is available, should not include it
50
+ expect(result.clonedTree).toBe(result.clonedViz)
51
+ expect(result.clonedTree.querySelector('h2')).toBeNull()
52
+ expect(result.clonedTree.querySelector('p')).toBeNull()
53
+ })
54
+ })
55
+
56
+ describe('Basic heading + viz patterns', () => {
57
+ it('should include H2 and paragraph when before viz', () => {
58
+ const container = createDOM(`
59
+ <div class="dfe-section">
60
+ <h2>Emergency Department Visits</h2>
61
+ <p>Weekly percent of total visits.</p>
62
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
63
+ </div>
64
+ `)
65
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
66
+
67
+ const result = prepareClonedElements(viz, true, 'viz1')
68
+ const children = Array.from(result.clonedTree.children)
69
+
70
+ expect(children.length).toBe(3)
71
+ expect(children[0].tagName).toBe('H2')
72
+ expect(children[1].tagName).toBe('P')
73
+ expect(result.clonedTree.contains(result.clonedViz)).toBe(true)
74
+ })
75
+
76
+ it('should include H3 and paragraph when before viz', () => {
77
+ const container = createDOM(`
78
+ <div class="dfe-section">
79
+ <h3>Emergency Department Visits by Age</h3>
80
+ <p>Weekly percent of total visits.</p>
81
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
82
+ </div>
83
+ `)
84
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
85
+
86
+ const result = prepareClonedElements(viz, true, 'viz1')
87
+ const children = Array.from(result.clonedTree.children)
88
+
89
+ expect(children.length).toBe(3)
90
+ expect(children[0].tagName).toBe('H3')
91
+ expect(children[1].tagName).toBe('P')
92
+ expect(result.clonedTree.contains(result.clonedViz)).toBe(true)
93
+ })
94
+
95
+ it('should prefer H3 over H2 when both present', () => {
96
+ const container = createDOM(`
97
+ <div class="dfe-section">
98
+ <h2>By Age</h2>
99
+ <h3>Emergency Department Visits by Age</h3>
100
+ <p>Weekly percent of total visits.</p>
101
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
102
+ </div>
103
+ `)
104
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
105
+
106
+ const result = prepareClonedElements(viz, true, 'viz1')
107
+
108
+ // Should include H3 (nearest to viz) but NOT H2
109
+ expect(result.clonedTree.querySelector('h2')).toBeNull()
110
+ expect(result.clonedTree.querySelector('h3')).not.toBeNull()
111
+ expect(result.clonedTree.querySelector('h3')?.textContent).toBe('Emergency Department Visits by Age')
112
+ })
113
+
114
+ it('should include all paragraphs between heading and viz', () => {
115
+ const container = createDOM(`
116
+ <div class="dfe-section">
117
+ <h3>Emergency Department Visits</h3>
118
+ <p>First paragraph.</p>
119
+ <p>Second paragraph.</p>
120
+ <p>Third paragraph.</p>
121
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
122
+ </div>
123
+ `)
124
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
125
+
126
+ const result = prepareClonedElements(viz, true, 'viz1')
127
+ const paragraphs = result.clonedTree.querySelectorAll('p')
128
+
129
+ expect(paragraphs.length).toBe(3)
130
+ expect(paragraphs[0].textContent).toBe('First paragraph.')
131
+ expect(paragraphs[1].textContent).toBe('Second paragraph.')
132
+ expect(paragraphs[2].textContent).toBe('Third paragraph.')
133
+ })
134
+ })
135
+
136
+ describe('Multiple visualizations', () => {
137
+ it('should return viz-only when viz2 follows viz1 with no heading between', () => {
138
+ const container = createDOM(`
139
+ <div class="dfe-section">
140
+ <h2>Title</h2>
141
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
142
+ <div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
143
+ </div>
144
+ `)
145
+ const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
146
+
147
+ const result = prepareClonedElements(viz2, true, 'viz2')
148
+
149
+ // findNearestHeadingIndex stops at viz1 (returns -1), so we get viz-only
150
+ expect(result.clonedTree).toBe(result.clonedViz)
151
+ })
152
+
153
+ it('should not include heading from previous viz when taking screenshot of second viz', () => {
154
+ const container = createDOM(`
155
+ <div class="dfe-section">
156
+ <h2>First Chart</h2>
157
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
158
+ <h2>Second Chart</h2>
159
+ <div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
160
+ </div>
161
+ `)
162
+ const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
163
+
164
+ const result = prepareClonedElements(viz2, true, 'viz2')
165
+
166
+ // Should include "Second Chart" but NOT "First Chart"
167
+ const headings = result.clonedTree.querySelectorAll('h2')
168
+ expect(headings.length).toBe(1)
169
+ expect(headings[0].textContent).toBe('Second Chart')
170
+ })
171
+
172
+ it('should ignore heading inside another viz', () => {
173
+ const container = createDOM(`
174
+ <div class="dfe-section">
175
+ <div class="cdc-open-viz-module" data-download-id="viz1">
176
+ <h2>Title Inside Viz1</h2>
177
+ <div>Chart 1</div>
178
+ </div>
179
+ <div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
180
+ </div>
181
+ `)
182
+ const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
183
+
184
+ const result = prepareClonedElements(viz2, true, 'viz2')
185
+
186
+ // findNearestHeadingIndex stops at viz1 (returns -1), so we get viz-only
187
+ expect(result.clonedTree).toBe(result.clonedViz)
188
+ })
189
+
190
+ it('should return viz-only when viz3 follows viz1 and viz2', () => {
191
+ const container = createDOM(`
192
+ <div class="dfe-section">
193
+ <h2>Title</h2>
194
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
195
+ <div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
196
+ <div class="cdc-open-viz-module" data-download-id="viz3">Chart 3</div>
197
+ </div>
198
+ `)
199
+ const viz3 = container.querySelector('[data-download-id="viz3"]') as HTMLElement
200
+
201
+ const result = prepareClonedElements(viz3, true, 'viz3')
202
+
203
+ // Should stop at viz2, return viz-only
204
+ expect(result.clonedTree).toBe(result.clonedViz)
205
+ })
206
+
207
+ it('should prefer H3 over H2 between two vizs', () => {
208
+ const container = createDOM(`
209
+ <div class="dfe-section">
210
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart 1</div>
211
+ <h2>Section</h2>
212
+ <h3>Subsection</h3>
213
+ <p>Description</p>
214
+ <div class="cdc-open-viz-module" data-download-id="viz2">Chart 2</div>
215
+ </div>
216
+ `)
217
+ const viz2 = container.querySelector('[data-download-id="viz2"]') as HTMLElement
218
+
219
+ const result = prepareClonedElements(viz2, true, 'viz2')
220
+
221
+ // Should include H3 (nearest) but not H2
222
+ expect(result.clonedTree.querySelector('h2')).toBeNull()
223
+ expect(result.clonedTree.querySelector('h3')?.textContent).toBe('Subsection')
224
+ })
225
+ })
226
+
227
+ describe('Section boundaries', () => {
228
+ it('should stop at dfe-section boundary', () => {
229
+ const container = createDOM(`
230
+ <div class="cdc-dfe-body__center">
231
+ <div class="dfe-section">
232
+ <h2>Previous Section</h2>
233
+ <p>Previous content</p>
234
+ <div class="cdc-open-viz-module" data-download-id="viz0">Other viz</div>
235
+ </div>
236
+ <div class="dfe-section">
237
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
238
+ </div>
239
+ </div>
240
+ `)
241
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
242
+
243
+ const result = prepareClonedElements(viz, true, 'viz1')
244
+
245
+ // Should return viz-only (stopped at section boundary, no heading in same section)
246
+ expect(result.clonedTree).toBe(result.clonedViz)
247
+ expect(result.clonedTree.querySelector('h2')).toBeNull()
248
+ expect(result.clonedTree.textContent).not.toContain('Previous Section')
249
+ })
250
+
251
+ it('should work without dfe-section wrapper', () => {
252
+ const container = createDOM(`
253
+ <div class="some-container">
254
+ <h2>Title</h2>
255
+ <p>Description</p>
256
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
257
+ </div>
258
+ `)
259
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
260
+
261
+ const result = prepareClonedElements(viz, true, 'viz1')
262
+ const children = Array.from(result.clonedTree.children)
263
+
264
+ // Should include H2 and P even without dfe-section
265
+ expect(children.length).toBe(3)
266
+ expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Title')
267
+ expect(result.clonedTree.querySelector('p')?.textContent).toBe('Description')
268
+ })
269
+
270
+ it('should stop at <section> tag boundary', () => {
271
+ const container = createDOM(`
272
+ <div class="outer-container">
273
+ <h2>Title Above Section</h2>
274
+ <section>
275
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
276
+ </section>
277
+ </div>
278
+ `)
279
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
280
+
281
+ const result = prepareClonedElements(viz, true, 'viz1')
282
+
283
+ // findParentWithContext stops at <section> boundary (returns null), so viz-only
284
+ expect(result.clonedTree).toBe(result.clonedViz)
285
+ })
286
+
287
+ it('should stop at innermost section boundary when nested', () => {
288
+ const container = createDOM(`
289
+ <div class="dfe-section">
290
+ <h2>Outer Title</h2>
291
+ <section>
292
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
293
+ </section>
294
+ </div>
295
+ `)
296
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
297
+
298
+ const result = prepareClonedElements(viz, true, 'viz1')
299
+
300
+ // Should stop at <section>, not climb to dfe-section
301
+ expect(result.clonedTree).toBe(result.clonedViz)
302
+ expect(result.clonedTree.textContent).not.toContain('Outer Title')
303
+ })
304
+
305
+ it('should include context when section tag is the parent with heading', () => {
306
+ const container = createDOM(`
307
+ <section>
308
+ <h2>Section Title</h2>
309
+ <p>Description</p>
310
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
311
+ </section>
312
+ `)
313
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
314
+
315
+ const result = prepareClonedElements(viz, true, 'viz1')
316
+
317
+ // Section with heading inside should work
318
+ expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Section Title')
319
+ expect(result.clonedTree.querySelector('p')?.textContent).toBe('Description')
320
+ })
321
+ })
322
+
323
+ describe('Edge cases', () => {
324
+ it('should return viz-only when only paragraph (no heading) before viz', () => {
325
+ const container = createDOM(`
326
+ <div class="dfe-section">
327
+ <p>Some paragraph without heading</p>
328
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
329
+ </div>
330
+ `)
331
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
332
+
333
+ const result = prepareClonedElements(viz, true, 'viz1')
334
+
335
+ // Should return viz-only (no heading found)
336
+ expect(result.clonedTree).toBe(result.clonedViz)
337
+ expect(result.clonedTree.querySelector('p')).toBeNull()
338
+ })
339
+
340
+ it('should not include content after viz', () => {
341
+ const container = createDOM(`
342
+ <div class="dfe-section">
343
+ <h2>Title</h2>
344
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
345
+ <p>Content after viz should not be included</p>
346
+ </div>
347
+ `)
348
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
349
+
350
+ const result = prepareClonedElements(viz, true, 'viz1')
351
+
352
+ // Should include H2 and viz, but NOT the paragraph after
353
+ expect(result.clonedTree.querySelector('h2')).not.toBeNull()
354
+ expect(result.clonedTree.textContent).not.toContain('Content after viz should not be included')
355
+ })
356
+
357
+ it('should handle direct H2 element as child', () => {
358
+ const container = createDOM(`
359
+ <div class="dfe-section">
360
+ <h2>Direct H2 Child</h2>
361
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
362
+ </div>
363
+ `)
364
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
365
+
366
+ const result = prepareClonedElements(viz, true, 'viz1')
367
+
368
+ // Should find H2 even though it's a direct child (not nested in div)
369
+ expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Direct H2 Child')
370
+ })
371
+
372
+ it('should handle H2 nested in div', () => {
373
+ const container = createDOM(`
374
+ <div class="dfe-section">
375
+ <div class="heading-wrapper">
376
+ <h2>Nested H2</h2>
377
+ </div>
378
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
379
+ </div>
380
+ `)
381
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
382
+
383
+ const result = prepareClonedElements(viz, true, 'viz1')
384
+
385
+ // Should find H2 even when nested
386
+ expect(result.clonedTree.querySelector('h2')?.textContent).toBe('Nested H2')
387
+ expect(result.clonedTree.querySelector('.heading-wrapper')).not.toBeNull()
388
+ })
389
+
390
+ it('should handle nested visualization wrapper', () => {
391
+ const container = createDOM(`
392
+ <div class="dfe-section">
393
+ <h2>Title</h2>
394
+ <p>Description</p>
395
+ <div class="outer-wrapper">
396
+ <div class="inner-wrapper">
397
+ <div class="cdc-open-viz-module" data-download-id="viz1">Chart</div>
398
+ </div>
399
+ </div>
400
+ </div>
401
+ `)
402
+ const viz = container.querySelector('[data-download-id="viz1"]') as HTMLElement
403
+
404
+ const result = prepareClonedElements(viz, true, 'viz1')
405
+
406
+ // Should include H2, P, and nested wrappers with viz
407
+ expect(result.clonedTree.querySelector('h2')).not.toBeNull()
408
+ expect(result.clonedTree.querySelector('p')).not.toBeNull()
409
+ expect(result.clonedTree.querySelector('.outer-wrapper')).not.toBeNull()
410
+ expect(result.clonedTree.querySelector('.inner-wrapper')).not.toBeNull()
411
+ expect(result.clonedViz.textContent).toBe('Chart')
412
+ })
413
+ })
414
+ })