@cdc/markup-include 4.25.10 → 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.
package/index.html CHANGED
@@ -3,21 +3,20 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
6
- <style>
6
+ <style type="text/css">
7
7
  body {
8
- margin: 0 auto !important;
9
- display: flex;
10
- flex-direction: column;
11
- justify-content: center;
8
+ margin: 0;
12
9
  border-top: none !important;
13
10
  }
14
11
 
15
- .react-container + .react-container {
16
- margin-top: 3rem;
12
+ /* Add 1rem padding to mimic DFE when editor is not visible */
13
+ .cdc-open-viz-module:not(.isEditor) {
14
+ padding: 1rem;
17
15
  }
18
16
  </style>
19
17
  <link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
20
18
  </head>
19
+
21
20
  <body>
22
21
  <!-- Original -->
23
22
 
@@ -25,6 +24,6 @@
25
24
  <!-- <div class="react-container" data-config="/src/_stories/_mock/icon-no-text.json"></div> -->
26
25
  <!-- <div class="react-container" data-config="/src/_stories/_mock/image-with-text.json"></div> -->
27
26
  <div class="react-container" data-config="/src/_stories/_mock/button-and-text.json"></div>
28
- <script type="module" src="./src/index.jsx"></script>
27
+ <script type="module" src="./src/index.tsx"></script>
29
28
  </body>
30
29
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/markup-include",
3
- "version": "4.25.10",
3
+ "version": "4.26.1",
4
4
  "description": "React component for displaying HTML content from an outside link",
5
5
  "moduleName": "CdcMarkupInclude",
6
6
  "main": "dist/cdcmarkupinclude",
@@ -27,7 +27,7 @@
27
27
  "license": "Apache-2.0",
28
28
  "homepage": "https://github.com/CDCgov/cdc-open-viz#readme",
29
29
  "dependencies": {
30
- "@cdc/core": "^4.25.10",
30
+ "@cdc/core": "^4.26.1",
31
31
  "axios": "^1.9.0",
32
32
  "chroma": "0.0.1",
33
33
  "interweave": "^13.1.1",
@@ -41,9 +41,9 @@
41
41
  "devDependencies": {
42
42
  "@rollup/plugin-dsv": "^3.0.2",
43
43
  "@vitejs/plugin-react": "^4.3.4",
44
- "vite": "^4.4.11",
44
+ "sass": "^1.89.2",
45
45
  "vite-plugin-css-injected-by-js": "^2.4.0",
46
- "vite-plugin-svgr": "^2.4.0"
46
+ "vite-plugin-svgr": "^4.2.0"
47
47
  },
48
- "gitHead": "c2db758e74ab9b9ca1667a6f9cb41dd0dccf985d"
48
+ "gitHead": "7e3b27098c4eb7a24bc9c3654ad53f88d6419f16"
49
49
  }
@@ -65,20 +65,24 @@ 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,
72
72
  noDataMessageText,
73
73
  markupVariables: contentEditorMarkupVariables
74
74
  } = contentEditor || {}
75
- const data = configObj?.data
75
+ const data = configObj?.data || config?.data
76
76
 
77
77
  // Support markupVariables at root level or inside contentEditor
78
78
  const markupVariables = config?.markupVariables || contentEditorMarkupVariables || []
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 => {
@@ -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))
@@ -223,11 +287,13 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
223
287
  isEditor,
224
288
  showNoDataMessage,
225
289
  allowHideSection,
226
- filters: config?.filters || []
290
+ filters: config?.filters || [],
291
+ datasets,
292
+ configDataKey: config?.dataKey
227
293
  })
228
294
  : { processedContent: parseBodyMarkup(urlMarkup), shouldHideSection: false, shouldShowNoDataMessage: false }
229
295
 
230
- const markup = processedMarkup.processedContent
296
+ const markup = applyStyleTagsAsInlineStyles(processedMarkup.processedContent)
231
297
 
232
298
  const hideMarkupInclude = processedMarkup.shouldHideSection
233
299
  const _showNoDataMessage = processedMarkup.shouldShowNoDataMessage
@@ -240,8 +306,15 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
240
306
  {!hideMarkupInclude && (
241
307
  <Layout.Responsive isEditor={isEditor}>
242
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
+ />
243
317
  <div className={`markup-include-component ${contentClasses.join(' ')}`}>
244
- <Title title={title} isDashboard={isDashboard} classes={[`${theme}`, 'mb-0']} />
245
318
  <div className={`${innerContainerClasses.join(' ')}`}>
246
319
  {/* Filters */}
247
320
  {config.filters && config.filters.length > 0 && (
@@ -253,7 +326,11 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
253
326
  interactionLabel={interactionLabel || 'markup-include'}
254
327
  />
255
328
  )}
256
- <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
+ >
257
334
  {_showNoDataMessage && (
258
335
  <div className='no-data-message'>
259
336
  <p>{`${noDataMessageText}`}</p>
@@ -264,7 +341,13 @@ const CdcMarkupInclude: React.FC<CdcMarkupIncludeProps> = ({
264
341
  </div>
265
342
  </div>
266
343
  </div>
267
- <FootnotesStandAlone config={configObj?.footnotes} filters={config?.filters || []} />
344
+ <FootnotesStandAlone
345
+ config={configObj?.footnotes}
346
+ filters={config?.filters || []}
347
+ markupVariables={markupVariables}
348
+ enableMarkupVariables={config?.enableMarkupVariables}
349
+ data={data}
350
+ />
268
351
  </div>
269
352
  </Layout.Responsive>
270
353
  )}
@@ -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
  }
@@ -323,7 +329,7 @@ export const VisualSectionTests: Story = {
323
329
  }
324
330
  }
325
331
 
326
- const themeButtons = Array.from(canvasElement.querySelectorAll('.color-palette li')) as HTMLElement[]
332
+ const themeButtons = Array.from(canvasElement.querySelectorAll('.color-palette button')) as HTMLElement[]
327
333
  expect(themeButtons.length).toBeGreaterThan(1)
328
334
 
329
335
  await performAndAssert(
@@ -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
@@ -3,7 +3,8 @@ export default {
3
3
  inlineHTML: '<h2>Inline HTML</h2>',
4
4
  showHeader: true,
5
5
  srcUrl: '#example',
6
- title: 'Markup Include',
6
+ title: '',
7
+ titleStyle: 'small',
7
8
  useInlineHTML: false,
8
9
  showNoDataMessage: false,
9
10
  noDataMessageText: 'No Data Available'
@@ -1,14 +1,16 @@
1
1
  .markup-include {
2
- .checkbox-group {
3
- padding: 16px;
4
- border: 1px solid #c4c4c4;
5
- border-radius: 8px;
6
- margin-top: 8px;
7
- margin-bottom: 24px;
8
- }
9
-
10
2
  .cove-component__content-wrap {
11
- 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
+ }
12
14
 
13
15
  h1,
14
16
  h2,
@@ -22,9 +24,6 @@
22
24
  }
23
25
  }
24
26
 
25
- .cove-component__content {
26
- font-size: 14px;
27
- }
28
27
  .cove-component__content.component--hideBackgroundColor {
29
28
  background: transparent;
30
29
  }
@@ -1,7 +1,7 @@
1
1
  import { MarkupIncludeConfig } from '@cdc/core/types/MarkupInclude'
2
2
  import MarkupIncludeActions from './markupInclude.actions'
3
3
 
4
- export type MarkupIncludeState = {
4
+ type MarkupIncludeState = {
5
5
  config?: MarkupIncludeConfig
6
6
  loading: boolean
7
7
  urlMarkup: string
@@ -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
  })