@cdc/markup-include 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.
@@ -0,0 +1,69 @@
1
+ {
2
+ "contentEditor": {
3
+ "inlineHTML": "<div>The age adjusted rate for {{race}} was 644.2, compared to Non-Hispanic American Indian, which was {{ageAdjustedRate}}.</div>",
4
+ "markupVariables": [
5
+ {
6
+ "name": "race",
7
+ "tag": "{{race}}",
8
+ "columnName": "Race",
9
+ "conditions": [
10
+ {
11
+ "columnName": "Age-adjusted rate",
12
+ "isOrIsNotEqualTo": "is",
13
+ "value": "644.2"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "name": "ageAdjustedRate",
19
+ "tag": "{{ageAdjustedRate}}",
20
+ "columnName": "Age-adjusted rate",
21
+ "conditions": [
22
+ {
23
+ "columnName": "Race",
24
+ "isOrIsNotEqualTo": "is",
25
+ "value": "Non-Hispanic American Indian"
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "showHeader": false,
31
+ "srcUrl": "https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Markup-Include-Button-and-Text.html",
32
+ "title": "",
33
+ "useInlineHTML": false
34
+ },
35
+ "data": [
36
+ {
37
+ "Race": "Hispanic or Latino",
38
+ "Age-adjusted rate": "644.2"
39
+ },
40
+ {
41
+ "Race": "Non-Hispanic American Indian",
42
+ "Age-adjusted rate": "636.1"
43
+ },
44
+ {
45
+ "Race": "Non-Hispanic Black",
46
+ "Age-adjusted rate": "563.7"
47
+ },
48
+ {
49
+ "Race": "Non-Hispanic Asian or Pacific Islander",
50
+ "Age-adjusted rate": "202.5"
51
+ },
52
+ {
53
+ "Race": "Non-Hispanic White",
54
+ "Age-adjusted rate": "183.6"
55
+ }
56
+ ],
57
+ "legend": {},
58
+ "newViz": true,
59
+ "theme": "theme-amber",
60
+ "type": "markup-include",
61
+ "runtime": null,
62
+ "visual": {
63
+ "border": false,
64
+ "accent": true,
65
+ "background": true,
66
+ "hideBackgroundColor": false,
67
+ "borderColorTheme": false
68
+ }
69
+ }
package/index.html CHANGED
@@ -1,41 +1 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="utf-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7
- <style type="text/css">
8
- body {
9
- margin: 0;
10
- border-top: none !important;
11
- }
12
-
13
- .cdc-map-outer-container {
14
- min-height: 100vh;
15
- }
16
- </style>
17
- <link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
18
-
19
- <!-- This is temporary and for testing until Nunito/900 is added to TP -->
20
- <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@900&display=swap" rel="stylesheet" />
21
- <style type="text/css">
22
- @font-face {
23
- font-family: 'Nunito';
24
- font-weight: 900;
25
- font-display: swap;
26
- src: url('https://app.unpkg.com/@fontsource/nunito@5.0.18/files/files/nunito-latin-900-normal.woff2') format('woff2');
27
- }
28
- </style>
29
- </head>
30
-
31
- <body>
32
- <!-- Original -->
33
-
34
- <!-- DATA PRESENTATION GALLERY: https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/Markup-Include.html#examples -->
35
- <!-- <div class="react-container" data-config="/src/_stories/_mock/icon-no-text.json"></div> -->
36
- <!-- <div class="react-container" data-config="/src/_stories/_mock/image-with-text.json"></div> -->
37
- <div class="react-container" data-config="/src/_stories/_mock/button-and-text.json"></div>
38
- <script type="module" src="./src/index.tsx"></script>
39
- </body>
40
-
41
- </html>
1
+ <!-- index.html is generated by @cdc/core/generateViteConfig.js using the files in @cdc/core/devTemplate/ -->
package/package.json CHANGED
@@ -1,48 +1,46 @@
1
1
  {
2
2
  "name": "@cdc/markup-include",
3
- "version": "4.25.11",
3
+ "version": "4.26.2",
4
4
  "description": "React component for displaying HTML content from an outside link",
5
- "moduleName": "CdcMarkupInclude",
6
- "main": "dist/cdcmarkupinclude",
7
- "type": "module",
8
- "scripts": {
9
- "start": "vite --open",
10
- "build": "vite build",
11
- "preview": "vite preview",
12
- "graph": "nx graph",
13
- "prepublishOnly": "lerna run --scope @cdc/markup-include build",
14
- "test": "vitest run --reporter verbose",
15
- "test-watch": "vitest watch --reporter verbose",
16
- "test-watch:ui": "vitest --ui"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/CDCgov/cdc-open-viz",
21
- "directory": "packages/markup-include"
22
- },
23
- "author": "Rob Shelnutt <rob@blackairplane.com>",
24
- "bugs": {
25
- "url": "https://github.com/CDCgov/cdc-open-viz/issues"
26
- },
27
5
  "license": "Apache-2.0",
28
- "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
6
+ "author": "Rob Shelnutt <rob@blackairplane.com>",
7
+ "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
29
8
  "dependencies": {
30
- "@cdc/core": "^4.25.11",
31
- "axios": "^1.9.0",
32
- "chroma": "0.0.1",
9
+ "@cdc/core": "^4.26.2",
10
+ "axios": "^1.13.2",
33
11
  "interweave": "^13.1.1",
34
- "lodash": "^4.17.21",
12
+ "lodash": "^4.17.23",
35
13
  "react-accessible-accordion": "^5.0.1"
36
14
  },
15
+ "devDependencies": {
16
+ "@rollup/plugin-dsv": "^3.0.2",
17
+ "@vitejs/plugin-react": "^5.1.2",
18
+ "sass": "^1.89.2",
19
+ "vite-plugin-css-injected-by-js": "^2.4.0",
20
+ "vite-plugin-svgr": "^4.2.0"
21
+ },
22
+ "gitHead": "be3413e8e1149abf94225108f86a7910f56e0616",
23
+ "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
24
+ "main": "dist/cdcmarkupinclude",
25
+ "moduleName": "CdcMarkupInclude",
37
26
  "peerDependencies": {
38
27
  "react": "^18.2.0",
39
28
  "react-dom": "^18.2.0"
40
29
  },
41
- "devDependencies": {
42
- "@vitejs/plugin-react": "^4.3.4",
43
- "vite": "^4.4.11",
44
- "vite-plugin-css-injected-by-js": "^2.4.0",
45
- "vite-plugin-svgr": "^2.4.0"
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/CDCgov/cdc-open-viz",
33
+ "directory": "packages/markup-include"
34
+ },
35
+ "scripts": {
36
+ "build": "vite build",
37
+ "graph": "nx graph",
38
+ "prepublishOnly": "lerna run --scope @cdc/markup-include build",
39
+ "preview": "vite preview",
40
+ "start": "vite --open",
41
+ "test": "vitest run --reporter verbose",
42
+ "test-watch": "vitest watch --reporter verbose",
43
+ "test-watch:ui": "vitest --ui"
46
44
  },
47
- "gitHead": "5f09a137c22f454111ab5f4cd7fdf1d2d58e31bd"
45
+ "type": "module"
48
46
  }
@@ -65,7 +65,7 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
65
65
  const container = useRef()
66
66
 
67
67
  const { innerContainerClasses, contentClasses } = useDataVizClasses(config || {})
68
- const { contentEditor, theme } = config || {}
68
+ const { contentEditor, theme, visual } = config || {}
69
69
  const {
70
70
  showNoDataMessage,
71
71
  allowHideSection,
@@ -79,6 +79,10 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
79
79
 
80
80
  const { inlineHTML, srcUrl, title, useInlineHTML } = contentEditor || {}
81
81
 
82
+ const shouldApplyTopPadding =
83
+ visual?.border || visual?.background || (contentEditor?.title && contentEditor?.titleStyle === 'legacy')
84
+ const shouldApplySidePadding = visual?.border || visual?.accent || visual?.background
85
+
82
86
  // Default Functions
83
87
  const updateConfig = newConfig => {
84
88
  Object.keys(defaults).forEach(key => {
@@ -120,7 +124,7 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
120
124
  processedConfig.filters = addValuesToFilters(processedConfig.filters, responseData)
121
125
  }
122
126
 
123
- updateConfig({ ...configObj, ...processedConfig })
127
+ updateConfig({ ...defaults, ...processedConfig })
124
128
  dispatch({ type: 'SET_LOADING', payload: false })
125
129
  }
126
130
 
@@ -198,6 +202,66 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
198
202
  return hasBody ? parse[1] : parse
199
203
  }
200
204
 
205
+ /**
206
+ * Transforms HTML by extracting <style> tags and applying their CSS rules as inline styles.
207
+ * This ensures that the CSS is applied only to this COVE visualization.
208
+ */
209
+ const applyStyleTagsAsInlineStyles = (html: string): string => {
210
+ if (!html || typeof html !== 'string') return html
211
+
212
+ // Use DOMParser to parse HTML
213
+ const parser = new DOMParser()
214
+ const doc = parser.parseFromString(html, 'text/html')
215
+
216
+ // Extract all <style> elements
217
+ const styleElements = doc.querySelectorAll('style')
218
+ if (styleElements.length === 0) return html
219
+
220
+ // Parse CSS rules
221
+ const sheet = new CSSStyleSheet()
222
+ const cssRules: Array<{ selector: string; styles: string }> = []
223
+
224
+ styleElements.forEach(styleEl => {
225
+ try {
226
+ // replaceSync parses the CSS and throws if invalid
227
+ sheet.replaceSync(styleEl.textContent || '')
228
+
229
+ // Extract parsed rules from the stylesheet
230
+ for (let i = 0; i < sheet.cssRules.length; i++) {
231
+ const rule = sheet.cssRules[i]
232
+ if (rule instanceof CSSStyleRule) {
233
+ cssRules.push({
234
+ selector: rule.selectorText,
235
+ styles: rule.style.cssText
236
+ })
237
+ }
238
+ }
239
+ } catch (e) {
240
+ console.warn('Markup Include: Invalid CSS in style tag', e)
241
+ }
242
+
243
+ styleEl.remove()
244
+ })
245
+
246
+ // Apply each CSS rule to matching elements
247
+ for (const rule of cssRules) {
248
+ try {
249
+ const elements = doc.body.querySelectorAll(rule.selector)
250
+
251
+ elements.forEach(el => {
252
+ const existingStyle = el.getAttribute('style') || ''
253
+ const newStyle = existingStyle ? `${existingStyle}; ${rule.styles}` : rule.styles
254
+ el.setAttribute('style', newStyle)
255
+ })
256
+ } catch (e) {
257
+ // Skip invalid selectors (e.g., pseudo-selectors like :hover won't match)
258
+ console.warn(`Markup Include: Could not apply CSS rule for selector "${rule.selector}"`, e)
259
+ }
260
+ }
261
+
262
+ return doc.body.innerHTML
263
+ }
264
+
201
265
  //Load initial config
202
266
  useEffect(() => {
203
267
  loadConfig().catch(err => console.error(err))
@@ -229,7 +293,7 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
229
293
  })
230
294
  : { processedContent: parseBodyMarkup(urlMarkup), shouldHideSection: false, shouldShowNoDataMessage: false }
231
295
 
232
- const markup = processedMarkup.processedContent
296
+ const markup = applyStyleTagsAsInlineStyles(processedMarkup.processedContent)
233
297
 
234
298
  const hideMarkupInclude = processedMarkup.shouldHideSection
235
299
  const _showNoDataMessage = processedMarkup.shouldShowNoDataMessage
@@ -242,8 +306,15 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
242
306
  {!hideMarkupInclude && (
243
307
  <Layout.Responsive isEditor={isEditor}>
244
308
  <div className='markup-include-content-container cove-component__content no-borders'>
309
+ <Title
310
+ title={title}
311
+ isDashboard={isDashboard}
312
+ titleStyle={contentEditor.titleStyle}
313
+ config={config}
314
+ classes={[`${theme}`, 'mb-0']}
315
+ noContent={!markup}
316
+ />
245
317
  <div className={`markup-include-component ${contentClasses.join(' ')}`}>
246
- <Title title={title} isDashboard={isDashboard} classes={[`${theme}`, 'mb-0']} />
247
318
  <div className={`${innerContainerClasses.join(' ')}`}>
248
319
  {/* Filters */}
249
320
  {config.filters && config.filters.length > 0 && (
@@ -255,7 +326,11 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
255
326
  interactionLabel={interactionLabel || 'markup-include'}
256
327
  />
257
328
  )}
258
- <div className='cove-component__content-wrap'>
329
+ <div
330
+ className={`cove-component__content-wrap${shouldApplyTopPadding ? ' has-top-padding' : ''}${
331
+ shouldApplySidePadding ? ' has-side-padding' : ''
332
+ }`}
333
+ >
259
334
  {_showNoDataMessage && (
260
335
  <div className='no-data-message'>
261
336
  <p>{`${noDataMessageText}`}</p>
@@ -89,6 +89,8 @@ const testConfig = {
89
89
  legend: {},
90
90
  newViz: true,
91
91
  theme: 'theme-blue',
92
+ titleStyle: 'small',
93
+ showTitle: true,
92
94
  type: 'markup-include',
93
95
  visual: {
94
96
  border: false,
@@ -135,8 +137,10 @@ export const GeneralSectionTests: Story = {
135
137
  await performAndAssert(
136
138
  'Title Update',
137
139
  () => {
138
- const headerElement = canvasElement.querySelector('.cove-component__header h2')
139
- return headerElement?.textContent?.trim() || ''
140
+ const modernTitle = canvasElement.querySelector('.cove-title')
141
+ const legacyTitle = canvasElement.querySelector('.cove-component__header h2')
142
+ const titleElement = modernTitle || legacyTitle
143
+ return titleElement?.textContent?.trim() || ''
140
144
  },
141
145
  async () => {
142
146
  await userEvent.clear(titleInput)
@@ -145,7 +149,9 @@ export const GeneralSectionTests: Story = {
145
149
  (before, after) => after === 'Updated Markup Include Title E2E' && after !== before
146
150
  )
147
151
 
148
- const headerElement = canvasElement.querySelector('.cove-component__header h2')
152
+ const modernHeader = canvasElement.querySelector('.cove-title')
153
+ const legacyHeader = canvasElement.querySelector('.cove-component__header h2')
154
+ const headerElement = modernHeader || legacyHeader
149
155
  expect(headerElement).toBeTruthy()
150
156
  expect(headerElement!.textContent?.trim()).toBe('Updated Markup Include Title E2E')
151
157
  }
@@ -0,0 +1,6 @@
1
+ .cdc-open-viz-module.markup-include {
2
+ .need-data-source-prompt {
3
+ color: var(--gray);
4
+ font-size: 0.8rem;
5
+ }
6
+ }
@@ -0,0 +1,158 @@
1
+ import { useContext, useRef } from 'react'
2
+
3
+ // Context
4
+ import ConfigContext from '../../ConfigContext'
5
+
6
+ // Helpers
7
+ import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
8
+
9
+ // Components
10
+ import { EditorPanel as BaseEditorPanel } from '@cdc/core/components/EditorPanel/EditorPanel'
11
+ import { TextField, CheckBox, Select } from '@cdc/core/components/EditorPanel/Inputs'
12
+ import Accordion from '@cdc/core/components/ui/Accordion'
13
+ import Icon from '@cdc/core/components/ui/Icon'
14
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
15
+ import MarkupVariablesEditor from '@cdc/core/components/EditorPanel/components/MarkupVariablesEditor'
16
+ import FootnotesEditor from '@cdc/core/components/EditorPanel/FootnotesEditor'
17
+ import { VisualSection } from '@cdc/core/components/EditorPanel/sections/VisualSection'
18
+ import { Datasets } from '@cdc/core/types/DataSet'
19
+
20
+ // styles
21
+ import './EditorPanel.styles.css'
22
+
23
+ type MarkupIncludeEditorPanelProps = {
24
+ datasets?: Datasets
25
+ }
26
+
27
+ const EditorPanel: React.FC<MarkupIncludeEditorPanelProps> = ({ datasets }) => {
28
+ const { config, data, isDashboard, loading, setParentConfig, updateConfig } = useContext(ConfigContext)
29
+ const { contentEditor, theme, visual } = config
30
+ const { inlineHTML, srcUrl, title, useInlineHTML } = contentEditor || {}
31
+ const updateField = updateFieldFactory(config, updateConfig, true)
32
+
33
+ const textAreaInEditorContainer = useRef(null)
34
+
35
+ const handleMarkupVariablesChange = (variables: any[]) => {
36
+ updateConfig({
37
+ ...config,
38
+ markupVariables: variables
39
+ })
40
+ }
41
+
42
+ const handleToggleEnable = (enabled: boolean) => {
43
+ updateConfig({
44
+ ...config,
45
+ enableMarkupVariables: enabled
46
+ })
47
+ }
48
+
49
+ return (
50
+ <BaseEditorPanel
51
+ config={config}
52
+ updateConfig={updateConfig as (config: any) => void}
53
+ loading={loading}
54
+ setParentConfig={setParentConfig as ((config: any) => void) | undefined}
55
+ isDashboard={isDashboard}
56
+ title='Configure Markup Include'
57
+ >
58
+ {() => (
59
+ <Accordion>
60
+ <Accordion.Section title='General'>
61
+ <TextField
62
+ value={title || ''}
63
+ section='contentEditor'
64
+ fieldName='title'
65
+ label='Title'
66
+ placeholder='Markup Include Title'
67
+ updateField={updateField}
68
+ />
69
+ <Select
70
+ value={contentEditor.titleStyle || 'small'}
71
+ section='contentEditor'
72
+ fieldName='titleStyle'
73
+ label='Title Style'
74
+ updateField={updateField}
75
+ options={[
76
+ { value: 'small', label: 'Small (h3)' },
77
+ { value: 'large', label: 'Large (h2)' },
78
+ { value: 'legacy', label: 'Legacy' }
79
+ ]}
80
+ tooltip={
81
+ <Tooltip style={{ textTransform: 'none' }}>
82
+ <Tooltip.Target>
83
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
84
+ </Tooltip.Target>
85
+ <Tooltip.Content>
86
+ <p>Choose the visual style for the title.</p>
87
+ <p>
88
+ Consider heading order on your page when selecting the title style. For 508 reasons, ensure your
89
+ page follows a proper heading order.
90
+ </p>
91
+ </Tooltip.Content>
92
+ </Tooltip>
93
+ }
94
+ />
95
+ </Accordion.Section>
96
+ <Accordion.Section title='Content Editor'>
97
+ <span className='divider-heading'>Enter Markup</span>
98
+ <CheckBox
99
+ value={useInlineHTML}
100
+ section='contentEditor'
101
+ fieldName='useInlineHTML'
102
+ label='Use Inline HTML'
103
+ updateField={updateField}
104
+ />
105
+ <div className='column-edit'>
106
+ {useInlineHTML ? (
107
+ <>
108
+ {/* HTML Textbox */}
109
+ <div ref={textAreaInEditorContainer}>
110
+ <TextField
111
+ value={inlineHTML}
112
+ section='contentEditor'
113
+ fieldName='inlineHTML'
114
+ label='HTML'
115
+ placeholder='Add HTML here'
116
+ type='textarea'
117
+ updateField={updateField}
118
+ />
119
+ </div>
120
+ </>
121
+ ) : (
122
+ <TextField
123
+ value={srcUrl || ''}
124
+ section='contentEditor'
125
+ fieldName='srcUrl'
126
+ label='Source URL;'
127
+ placeholder='https://www.example.com/file.html'
128
+ updateField={updateField}
129
+ />
130
+ )}
131
+ </div>
132
+ </Accordion.Section>
133
+ <Accordion.Section title='Visual'>
134
+ <VisualSection config={config} updateField={updateField} updateConfig={updateConfig} />
135
+ </Accordion.Section>
136
+ {isDashboard && (
137
+ <Accordion.Section title='Footnotes'>
138
+ <FootnotesEditor config={config} updateField={updateField} datasets={datasets} />
139
+ </Accordion.Section>
140
+ )}
141
+ <Accordion.Section title='Markup Variables'>
142
+ <MarkupVariablesEditor
143
+ markupVariables={config.markupVariables || []}
144
+ data={data || []}
145
+ datasets={datasets}
146
+ config={config}
147
+ onChange={handleMarkupVariablesChange}
148
+ enableMarkupVariables={config.enableMarkupVariables || false}
149
+ onToggleEnable={handleToggleEnable}
150
+ />
151
+ </Accordion.Section>
152
+ </Accordion>
153
+ )}
154
+ </BaseEditorPanel>
155
+ )
156
+ }
157
+
158
+ export default EditorPanel
@@ -0,0 +1,3 @@
1
+ import EditorPanel from './EditorPanel'
2
+
3
+ export default EditorPanel
@@ -1,18 +1,21 @@
1
1
  export default {
2
2
  contentEditor: {
3
- inlineHTML: '<h2>Inline HTML</h2>',
3
+ inlineHTML: '<strong>Inline HTML</strong>',
4
4
  showHeader: true,
5
5
  srcUrl: '#example',
6
- title: 'Markup Include',
7
- useInlineHTML: false,
6
+ title: '',
7
+ titleStyle: 'small',
8
+ useInlineHTML: true,
8
9
  showNoDataMessage: false,
9
10
  noDataMessageText: 'No Data Available'
10
11
  },
11
12
  data: [],
12
13
  legend: {},
13
14
  newViz: true,
15
+ showEditorPanel: true,
14
16
  theme: 'theme-blue',
15
17
  type: 'markup-include',
18
+ visualizationType: 'markup-include',
16
19
  runtime: null,
17
20
  visual: {
18
21
  border: false,
@@ -1,6 +1,16 @@
1
1
  .markup-include {
2
2
  .cove-component__content-wrap {
3
- padding: 1rem 0;
3
+ padding: 0;
4
+
5
+ &.has-top-padding {
6
+ padding-top: 1rem;
7
+ padding-bottom: 1rem;
8
+ }
9
+
10
+ &.has-side-padding {
11
+ padding-left: 1rem;
12
+ padding-right: 1rem;
13
+ }
4
14
 
5
15
  h1,
6
16
  h2,
@@ -14,9 +24,6 @@
14
24
  }
15
25
  }
16
26
 
17
- .cove-component__content {
18
- font-size: 14px;
19
- }
20
27
  .cove-component__content.component--hideBackgroundColor {
21
28
  background: transparent;
22
29
  }
@@ -7,5 +7,5 @@ describe('Markup Include', () => {
7
7
  const pkgDir = path.join(__dirname, '..')
8
8
  const result = testStandaloneBuild(pkgDir)
9
9
  expect(result).toBe(true)
10
- })
10
+ }, 300000)
11
11
  })