@cdc/core 4.25.8 → 4.25.11

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 (163) hide show
  1. package/_stories/StoryRenderingTests.stories.tsx +164 -0
  2. package/components/AdvancedEditor/AdvancedEditor.tsx +32 -9
  3. package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
  4. package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
  5. package/components/CustomColorsEditor/index.ts +1 -0
  6. package/components/DataTable/DataTable.tsx +56 -38
  7. package/components/DataTable/DataTableStandAlone.tsx +8 -3
  8. package/components/DataTable/components/ChartHeader.tsx +44 -14
  9. package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
  10. package/components/DataTable/components/ExpandCollapse.tsx +10 -1
  11. package/components/DataTable/components/MapHeader.tsx +24 -13
  12. package/components/DataTable/data-table.css +12 -0
  13. package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
  14. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -4
  15. package/components/DataTable/helpers/standardizeState.js +2 -2
  16. package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
  17. package/components/DownloadButton.tsx +40 -14
  18. package/components/EditorPanel/DataTableEditor.tsx +3 -3
  19. package/components/EditorPanel/EditorPanel.styles.css +423 -0
  20. package/components/EditorPanel/FootnotesEditor.tsx +44 -37
  21. package/components/EditorPanel/Inputs.tsx +12 -2
  22. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
  23. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
  24. package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
  25. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +450 -0
  26. package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
  27. package/components/ErrorBoundary.jsx +3 -1
  28. package/components/Filters/Filters.tsx +52 -24
  29. package/components/Filters/components/Dropdown.tsx +6 -1
  30. package/components/Filters/components/Tabs.tsx +1 -0
  31. package/components/Footnotes/Footnotes.tsx +35 -25
  32. package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
  33. package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
  34. package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
  35. package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
  36. package/components/HeaderThemeSelector/index.ts +2 -0
  37. package/components/Layout/styles/editor.scss +2 -1
  38. package/components/Legend/Legend.Gradient.tsx +3 -6
  39. package/components/LegendShape.tsx +121 -3
  40. package/components/Loader/Loader.tsx +1 -1
  41. package/components/MediaControls.tsx +72 -21
  42. package/components/PaletteConversionModal.tsx +90 -0
  43. package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
  44. package/components/PaletteSelector/PaletteSelector.css +94 -0
  45. package/components/PaletteSelector/PaletteSelector.tsx +112 -0
  46. package/components/PaletteSelector/index.ts +2 -0
  47. package/components/RichTooltip/RichTooltip.tsx +1 -0
  48. package/components/Table/Table.tsx +3 -1
  49. package/components/Table/components/Cell.tsx +23 -2
  50. package/components/Table/components/Row.tsx +5 -3
  51. package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
  52. package/components/_stories/DataTable.stories.tsx +1 -1
  53. package/components/_stories/Filters.stories.tsx +21 -2
  54. package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
  55. package/components/_stories/Footnotes.stories.tsx +769 -4
  56. package/components/_stories/Inputs.stories.tsx +3 -3
  57. package/components/_stories/MultiSelect.stories.tsx +3 -3
  58. package/components/_stories/NestedDropdown.stories.tsx +1 -1
  59. package/components/_stories/Table.stories.tsx +1 -1
  60. package/components/_stories/styles.scss +0 -1
  61. package/components/elements/_stories/Button.stories.tsx +1 -1
  62. package/components/elements/_stories/Card.stories.tsx +1 -1
  63. package/components/inputs/InputToggle.tsx +2 -0
  64. package/components/managers/DataDesigner.tsx +10 -9
  65. package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
  66. package/components/ui/Accordion.jsx +1 -1
  67. package/components/ui/Tooltip.tsx +2 -1
  68. package/components/ui/_stories/Accordion.stories.tsx +1 -1
  69. package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
  70. package/components/ui/_stories/Colors.stories.tsx +330 -0
  71. package/components/ui/_stories/IconGallery.stories.tsx +316 -0
  72. package/components/ui/_stories/Title.stories.tsx +1 -1
  73. package/components/ui/accordion.styles.css +57 -0
  74. package/contexts/EditorContext.ts +18 -0
  75. package/contexts/editor.actions.ts +28 -0
  76. package/contexts/editor.reducer.ts +94 -0
  77. package/data/chartColorPalettes.ts +118 -0
  78. package/data/colorPalettes.ts +9 -0
  79. package/data/mapColorPalettes.ts +45 -0
  80. package/data/sharedPalettes.ts +50 -0
  81. package/dist/cove-main.css +63 -14
  82. package/dist/cove-main.css.map +1 -1
  83. package/generateViteConfig.js +80 -0
  84. package/helpers/addValuesToFilters.ts +7 -3
  85. package/helpers/cloneConfig.ts +31 -0
  86. package/helpers/configDataHelpers.ts +128 -0
  87. package/helpers/configHelpers.ts +27 -0
  88. package/helpers/constants.ts +42 -2
  89. package/helpers/cove/number.ts +33 -12
  90. package/helpers/coveUpdateWorker.ts +15 -3
  91. package/helpers/fetchRemoteData.ts +3 -15
  92. package/helpers/filterColorPalettes.ts +152 -0
  93. package/helpers/generateColorsArray.ts +13 -0
  94. package/helpers/getColorPaletteVersion.ts +33 -0
  95. package/helpers/getPaletteAccessor.ts +18 -0
  96. package/helpers/markupProcessor.ts +220 -0
  97. package/helpers/mergeCustomOrderValues.ts +37 -0
  98. package/helpers/metrics/helpers.ts +42 -19
  99. package/helpers/metrics/types.ts +48 -9
  100. package/helpers/metrics/utils.ts +34 -0
  101. package/helpers/palettes/colorDistributions.ts +56 -0
  102. package/helpers/palettes/migratePaletteName.ts +150 -0
  103. package/helpers/palettes/standardizePaletteNames.ts +77 -0
  104. package/helpers/palettes/utils.ts +267 -0
  105. package/helpers/parseCsvWithQuotes.ts +65 -0
  106. package/helpers/queryStringUtils.ts +13 -0
  107. package/helpers/testing.ts +358 -0
  108. package/helpers/tests/addValuesToFilters.test.ts +1 -2
  109. package/helpers/tests/generateColorsArray.test.ts +24 -0
  110. package/helpers/tests/markupProcessor.test.ts +538 -0
  111. package/helpers/tests/testStandaloneBuild.ts +44 -0
  112. package/helpers/useMarkupVariables.ts +31 -0
  113. package/helpers/vegaConfig.ts +0 -1
  114. package/helpers/ver/4.24.10.ts +2 -1
  115. package/helpers/ver/4.24.11.ts +2 -1
  116. package/helpers/ver/4.24.3.ts +2 -1
  117. package/helpers/ver/4.24.4.ts +2 -1
  118. package/helpers/ver/4.24.5.ts +2 -1
  119. package/helpers/ver/4.24.7.ts +2 -1
  120. package/helpers/ver/4.24.9.ts +2 -1
  121. package/helpers/ver/4.25.1.ts +2 -1
  122. package/helpers/ver/4.25.10.ts +36 -0
  123. package/helpers/ver/4.25.11.ts +13 -0
  124. package/helpers/ver/4.25.3.ts +2 -1
  125. package/helpers/ver/4.25.4.ts +2 -1
  126. package/helpers/ver/4.25.6.ts +2 -1
  127. package/helpers/ver/4.25.7.ts +2 -1
  128. package/helpers/ver/4.25.8.ts +2 -1
  129. package/helpers/ver/4.25.9.ts +293 -0
  130. package/helpers/ver/tests/4.25.10.test.ts +204 -0
  131. package/helpers/ver/tests/4.25.8.test.ts +1 -1
  132. package/helpers/ver/tests/4.25.9.test.ts +51 -0
  133. package/helpers/viewports.ts +2 -0
  134. package/hooks/useColorPalette.ts +79 -0
  135. package/package.json +13 -4
  136. package/styles/_common-components.css +73 -0
  137. package/styles/_global.scss +32 -10
  138. package/styles/base.scss +8 -55
  139. package/styles/cove-main.scss +3 -1
  140. package/styles/filters.scss +10 -3
  141. package/styles/v2/base/index.scss +0 -1
  142. package/styles/v2/components/button.scss +4 -3
  143. package/styles/v2/components/editor.scss +16 -7
  144. package/styles/v2/layout/_data-table.scss +3 -2
  145. package/styles/v2/themes/_color-definitions.scss +18 -17
  146. package/styles/v2/utils/_breakpoints.scss +1 -1
  147. package/styles/v2/utils/index.scss +0 -1
  148. package/styles/waiting.scss +1 -1
  149. package/testing-setup.js +32 -0
  150. package/types/MarkupInclude.ts +8 -2
  151. package/types/MarkupVariable.ts +19 -0
  152. package/types/VizFilter.ts +2 -0
  153. package/vitest.config.ts +16 -0
  154. package/components/ui/_stories/Colors.stories.mdx +0 -220
  155. package/components/ui/_stories/IconGallery.stories.mdx +0 -14
  156. package/data/colorPalettes.js +0 -171
  157. package/helpers/formatConfigBeforeSave.ts +0 -135
  158. package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
  159. package/styles/_mixins.scss +0 -13
  160. package/styles/v2/base/_typography.scss +0 -0
  161. package/styles/v2/components/guidance-block.scss +0 -74
  162. package/styles/v2/utils/_functions.scss +0 -0
  163. /package/{styles/_typography.scss → testBuild.js} +0 -0
@@ -3,8 +3,7 @@ import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
3
3
  import _ from 'lodash'
4
4
  import Footnotes, { Footnote } from '../../types/Footnotes'
5
5
  import { footnotesSymbols } from '../../helpers/footnoteSymbols'
6
- import InputSelect from '../inputs/InputSelect'
7
- import { TextField } from './Inputs'
6
+ import { TextField, Select } from './Inputs'
8
7
  import { Datasets } from '@cdc/core/types/DataSet'
9
8
  import DataTransform from '../../helpers/DataTransform'
10
9
  import fetchRemoteData from '../../helpers/fetchRemoteData'
@@ -61,14 +60,14 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
61
60
  updateField('footnotes', null, 'staticFootnotes', footnoteCopy)
62
61
  }
63
62
 
64
- const getOptions = (opts: string[]) => {
65
- return [['', '--Select--']].concat(opts.map(key => [key, key]))
63
+ const getSelectOptions = (opts: string[]) => {
64
+ return [{ value: '', label: '--Select--' }].concat(opts.map(key => ({ value: key, label: key })))
66
65
  }
67
66
 
68
67
  const dataColumns = footnotesConfig.dataKey
69
- ? getOptions(Object.keys(datasetsCache[footnotesConfig.dataKey]?.data?.[0] || {}))
68
+ ? getSelectOptions(Object.keys(datasetsCache[footnotesConfig.dataKey]?.data?.[0] || {}))
70
69
  : []
71
- const dataSetOptions = getOptions(Object.keys(datasetsCache))
70
+ const dataSetOptions = getSelectOptions(Object.keys(datasetsCache))
72
71
 
73
72
  const changeFootnoteDataKey = async value => {
74
73
  if (value) {
@@ -87,7 +86,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
87
86
  {loadingAPIData && <Loader fullScreen />}
88
87
  <em>Dynamic Footnotes</em>
89
88
  <div className='row border p-2'>
90
- <InputSelect
89
+ <Select
91
90
  label='Select a Footnote Dataset'
92
91
  value={footnotesConfig.dataKey}
93
92
  options={dataSetOptions}
@@ -100,7 +99,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
100
99
 
101
100
  {footnotesConfig.dataKey && (
102
101
  <div className='p-3'>
103
- <InputSelect
102
+ <Select
104
103
  label='Footnote Symbol Column'
105
104
  value={footnotesConfig.dynamicFootnotes?.symbolColumn}
106
105
  options={dataColumns}
@@ -109,7 +108,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
109
108
  fieldName='symbolColumn'
110
109
  updateField={updateField}
111
110
  />
112
- <InputSelect
111
+ <Select
113
112
  label='Footnote Text Column'
114
113
  value={footnotesConfig.dynamicFootnotes?.textColumn}
115
114
  options={dataColumns}
@@ -118,7 +117,7 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
118
117
  fieldName='textColumn'
119
118
  updateField={updateField}
120
119
  />
121
- <InputSelect
120
+ <Select
122
121
  label='Footnote Order Column'
123
122
  value={footnotesConfig.dynamicFootnotes?.orderColumn}
124
123
  options={dataColumns}
@@ -135,34 +134,42 @@ const FootnotesEditor: React.FC<FootnotesEditorProps> = ({ config, updateField,
135
134
 
136
135
  <em>Static Footnotes</em>
137
136
 
138
- {footnotesConfig.staticFootnotes?.map((note, index) => (
139
- <div key={index} className='row border p-2'>
140
- <div className='col-8'>
141
- <InputSelect
142
- label='Symbol'
143
- value={note.symbol}
144
- options={[['', '--Select--'], ...footnotesSymbols]}
145
- fieldName='symbol'
146
- updateField={(section, subsection, fieldName, value) =>
147
- updateStaticFootnote(index, { ...note, symbol: value })
148
- }
149
- />{' '}
150
- <TextField
151
- label='Text'
152
- value={note.text}
153
- fieldName='text'
154
- updateField={(section, subsection, fieldName, value) =>
155
- updateStaticFootnote(index, { ...note, text: value })
156
- }
157
- />
158
- </div>
159
- <div className='col-2 ms-4'>
160
- <button className='btn btn-danger p-1' onClick={() => deleteStaticFootnote(index)}>
161
- Delete
162
- </button>
137
+ {footnotesConfig.staticFootnotes?.map((note, index) => {
138
+ // Convert tuple format to {value, label} format for Select component
139
+ const symbolOptions = [
140
+ { value: '', label: '--Select--' },
141
+ ...footnotesSymbols.map(([value, label]) => ({ value, label }))
142
+ ]
143
+
144
+ return (
145
+ <div key={index} className='row border p-2'>
146
+ <div className='col-8'>
147
+ <Select
148
+ label='Symbol'
149
+ value={note.symbol}
150
+ options={symbolOptions}
151
+ fieldName='symbol'
152
+ updateField={(section, subsection, fieldName, value) =>
153
+ updateStaticFootnote(index, { ...note, symbol: value })
154
+ }
155
+ />{' '}
156
+ <TextField
157
+ label='Text'
158
+ value={note.text}
159
+ fieldName='text'
160
+ updateField={(section, subsection, fieldName, value) =>
161
+ updateStaticFootnote(index, { ...note, text: value })
162
+ }
163
+ />
164
+ </div>
165
+ <div className='col-2 ms-4'>
166
+ <button className='btn btn-danger p-1' onClick={() => deleteStaticFootnote(index)}>
167
+ Delete
168
+ </button>
169
+ </div>
163
170
  </div>
164
- </div>
165
- ))}
171
+ )
172
+ })}
166
173
  <button className='btn btn-primary' onClick={addStaticFootnote}>
167
174
  Add Static Footnote
168
175
  </button>
@@ -149,6 +149,7 @@ export type SelectProps = {
149
149
  options?: string[] | { label: string; value: string }[]
150
150
  required?: boolean
151
151
  initial?: string
152
+ disabled?: boolean
152
153
 
153
154
  // all other props
154
155
  [x: string]: any
@@ -167,6 +168,8 @@ const Select = memo((props: SelectProps) => {
167
168
  tooltip,
168
169
  updateField,
169
170
  initial: initialValue,
171
+ disabled = false,
172
+ onChange: onChangeProp,
170
173
  ...attributes
171
174
  } = props
172
175
  const optionsJsx = options?.map((option, index) => {
@@ -197,7 +200,7 @@ const Select = memo((props: SelectProps) => {
197
200
  }
198
201
 
199
202
  return (
200
- <label>
203
+ <label style={disabled ? { opacity: 0.6, pointerEvents: 'none' } : {}}>
201
204
  <span className='edit-label'>
202
205
  {label}
203
206
  {tooltip}
@@ -206,9 +209,16 @@ const Select = memo((props: SelectProps) => {
206
209
  className={`cove-form-select ${required && !value ? 'warning' : ''} ${DROPDOWN_STYLES}`}
207
210
  name={fieldName}
208
211
  value={value}
212
+ disabled={disabled}
209
213
  onChange={event => {
210
- updateField(section, subsection, fieldName, event.target.value)
214
+ if (updateField) {
215
+ updateField(section, subsection, fieldName, event.target.value)
216
+ }
217
+ if (onChangeProp) {
218
+ onChangeProp(event)
219
+ }
211
220
  }}
221
+ style={disabled ? { cursor: 'not-allowed', backgroundColor: '#e9ecef' } : {}}
212
222
  {...attributes}
213
223
  >
214
224
  {optionsJsx}
@@ -5,6 +5,7 @@ import { filterOrderOptions } from '../../../helpers/filterOrderOptions'
5
5
  import FilterOrder from './components/FilterOrder'
6
6
  import { Visualization } from '../../../types/Visualization'
7
7
  import { useMemo } from 'react'
8
+ import { Select } from '../Inputs'
8
9
 
9
10
  type NestedDropdownEditorProps = {
10
11
  config: Visualization
@@ -155,43 +156,26 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
155
156
  />
156
157
  </label>
157
158
 
158
- <label>
159
- <div className='edit-label column-heading mt-2'>
160
- Filter Grouping
161
- <span></span>
162
- </div>
163
- <select value={filter.columnName} onChange={e => handleGroupColumnNameChange(e.target.value)}>
164
- <option value=''>- Select Option -</option>
165
- {columnNameOptions.map((option, index) => (
166
- <option value={option} key={index}>
167
- {option}
168
- </option>
169
- ))}
170
- </select>
171
- </label>
172
- <label>
173
- <div className='edit-label column-heading mt-2'>
174
- Filter SubGrouping
175
- <span></span>
176
- </div>
177
- <select
178
- value={subGrouping?.columnName ?? ''}
179
- onChange={e => {
180
- handleSubGroupColumnNameChange(e.target.value)
181
- }}
182
- >
183
- <option value=''>- Select Option -</option>
184
- {columnNameOptions.map((option, index) => {
185
- if (option !== filter.columnName) {
186
- return (
187
- <option value={option} key={index}>
188
- {option}
189
- </option>
190
- )
191
- }
192
- })}
193
- </select>
194
- </label>
159
+ <Select
160
+ label='Filter Grouping'
161
+ value={filter.columnName}
162
+ options={[{ value: '', label: '- Select Option -' }, ...columnNameOptions.map(opt => ({ value: opt, label: opt }))]}
163
+ onChange={e => handleGroupColumnNameChange(e.target.value)}
164
+ />
165
+
166
+ <Select
167
+ label='Filter SubGrouping'
168
+ value={subGrouping?.columnName ?? ''}
169
+ options={[
170
+ { value: '', label: '- Select Option -' },
171
+ ...columnNameOptions
172
+ .filter(option => option !== filter.columnName)
173
+ .map(opt => ({ value: opt, label: opt }))
174
+ ]}
175
+ onChange={e => {
176
+ handleSubGroupColumnNameChange(e.target.value)
177
+ }}
178
+ />
195
179
 
196
180
  <label>
197
181
  <input
@@ -229,39 +213,28 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
229
213
  )}
230
214
  </label>
231
215
 
232
- <label className='mt-2'>
216
+ <div className='mt-2'>
233
217
  <div className='edit-label column-heading float-right'>{filter.columnName} </div>
234
- <span className={'edit-filterOrder column-heading '}>Group Order</span>
235
- <select value={filter.order} onChange={e => handleGroupingOrderBy(e.target.value as OrderBy)}>
236
- {filterOrderOptions.map((option, index) => {
237
- return (
238
- <option value={option.value} key={`filter-${option.label}-${index}`}>
239
- {option.label}
240
- </option>
241
- )
242
- })}
243
- </select>
218
+ <Select
219
+ label='Group Order'
220
+ value={filter.order}
221
+ options={filterOrderOptions}
222
+ onChange={e => handleGroupingOrderBy(e.target.value as OrderBy)}
223
+ />
244
224
  {filter.order === 'cust' && (
245
225
  <FilterOrder orderedValues={filter.orderedValues} handleFilterOrder={handleGroupingCustomOrder} />
246
226
  )}
247
- </label>
227
+ </div>
248
228
 
249
229
  {subGrouping?.columnName && (
250
- <label className='mt-2'>
251
- <span className={'edit-filterOrder column-heading'}>SubGrouping Order</span>
230
+ <div className='mt-2'>
252
231
  <div className='edit-label column-heading float-right'>{subGrouping.columnName} </div>
253
- <select
232
+ <Select
233
+ label='SubGrouping Order'
254
234
  value={subGrouping.order ? subGrouping.order : 'asc'}
235
+ options={filterOrderOptions}
255
236
  onChange={e => handleSubGroupingOrderBy(e.target.value as OrderBy)}
256
- >
257
- {filterOrderOptions.map((option, index) => {
258
- return (
259
- <option value={option.value} key={`filter-${index}`}>
260
- {option.label}
261
- </option>
262
- )
263
- })}
264
- </select>
237
+ />
265
238
  {subGrouping?.order === 'cust' &&
266
239
  filter.values.map((groupName, i) => {
267
240
  const orderedSubGroupValues = subGrouping.valuesLookup[groupName].orderedValues
@@ -278,7 +251,7 @@ const NestedDropdownEditor: React.FC<NestedDropdownEditorProps> = ({
278
251
  </div>
279
252
  )
280
253
  })}
281
- </label>
254
+ </div>
282
255
  )}
283
256
  </div>
284
257
  )
@@ -28,6 +28,16 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
28
28
  return _.uniq(_.flatten(rawData?.map(row => Object.keys(row))))
29
29
  }, [rawData])
30
30
 
31
+ // Helper function to get filter values from various sources
32
+ const getFilterValues = (filter: VizFilter) => {
33
+ if (filter.values && filter.values.length > 0) return filter.values
34
+ if (filter.orderedValues && filter.orderedValues.length > 0) return filter.orderedValues
35
+ if (filter.columnName && rawData && rawData.length > 0) {
36
+ return _.uniq(rawData.map(row => row[filter.columnName]))
37
+ }
38
+ return []
39
+ }
40
+
31
41
  const removeFilter = index => {
32
42
  let filters = _.cloneDeep(config.filters)
33
43
 
@@ -188,8 +198,8 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
188
198
  value={filter.defaultValue}
189
199
  options={
190
200
  filter.resetLabel
191
- ? [filter.resetLabel, ...config.filters?.[filterIndex].values]
192
- : config.filters?.[filterIndex].values
201
+ ? [filter.resetLabel, ...getFilterValues(filter)]
202
+ : getFilterValues(filter)
193
203
  }
194
204
  updateField={(_section, _subSection, _key, value) => {
195
205
  updateFilterDefaultValue(filterIndex, value)
@@ -0,0 +1,227 @@
1
+ import React, { memo, useEffect, useState, useRef, useMemo } from 'react'
2
+ import { useDebounce } from 'use-debounce'
3
+ import { MarkupVariable } from '../../../types/MarkupVariable'
4
+
5
+ type MarkupHighlightedTextFieldProps = {
6
+ className?: string
7
+ value: string | number
8
+ type?: 'text' | 'textarea'
9
+ label: string
10
+ placeholder?: string
11
+ fieldName?: string
12
+ section?: any
13
+ subsection?: any
14
+ updateField?: (section: any, subsection: any, fieldName: string, value: string) => void
15
+ markupVariables?: MarkupVariable[]
16
+ isEditor?: boolean
17
+ }
18
+
19
+ const MarkupHighlightedTextField: React.FC<MarkupHighlightedTextFieldProps> = memo((props) => {
20
+ const {
21
+ value: stateValue,
22
+ type = 'text',
23
+ label,
24
+ placeholder = '',
25
+ fieldName = '',
26
+ section = null,
27
+ subsection = null,
28
+ updateField,
29
+ markupVariables = [],
30
+ isEditor = false,
31
+ className = '',
32
+ ...attributes
33
+ } = props
34
+
35
+ const [value, setValue] = useState(String(stateValue))
36
+ const [debouncedValue] = useDebounce(value, 300) // Reduced debounce for faster response
37
+ const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null)
38
+ const highlightRef = useRef<HTMLDivElement>(null)
39
+
40
+ // Update field when debounced value changes
41
+ useEffect(() => {
42
+ if (stateValue !== debouncedValue && updateField) {
43
+ updateField(section, subsection, fieldName, debouncedValue)
44
+ }
45
+ }, [debouncedValue, section, subsection, fieldName, updateField, stateValue])
46
+
47
+ // Update local state when props change
48
+ useEffect(() => {
49
+ setValue(String(stateValue))
50
+ }, [stateValue])
51
+
52
+ // Get valid markup variable tags for highlighting (memoized)
53
+ const validTags = useMemo(() =>
54
+ markupVariables.map(variable => variable.tag).filter(Boolean),
55
+ [markupVariables]
56
+ )
57
+
58
+ // Memoized highlighting function to reduce recalculations
59
+ const highlightedContent = useMemo(() => {
60
+ if (!isEditor || validTags.length === 0) {
61
+ return value
62
+ }
63
+
64
+ // Escape HTML
65
+ const escapedText = value.replace(/[&<>"']/g, (match) => {
66
+ const escapeMap: { [key: string]: string } = {
67
+ '&': '&amp;',
68
+ '<': '&lt;',
69
+ '>': '&gt;',
70
+ '"': '&quot;',
71
+ "'": '&#39;'
72
+ }
73
+ return escapeMap[match]
74
+ })
75
+
76
+ // Highlight valid markup variables
77
+ return escapedText.replace(/\{\{([^}]+)\}\}/g, (match) => {
78
+ const isValid = validTags.includes(match)
79
+ const cssClass = isValid ? 'markup-variable-valid' : 'markup-variable-invalid'
80
+ return `<span class="${cssClass}">${match}</span>`
81
+ })
82
+ }, [value, validTags, isEditor])
83
+
84
+ // Optimized scroll sync
85
+ const handleScroll = useMemo(() => {
86
+ let rafId: number | null = null
87
+ return () => {
88
+ if (rafId) cancelAnimationFrame(rafId)
89
+ rafId = requestAnimationFrame(() => {
90
+ if (inputRef.current && highlightRef.current) {
91
+ highlightRef.current.scrollTop = inputRef.current.scrollTop
92
+ highlightRef.current.scrollLeft = inputRef.current.scrollLeft
93
+ }
94
+ })
95
+ }
96
+ }, [])
97
+
98
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
99
+ setValue(e.target.value)
100
+ }
101
+
102
+ const inputId = `${fieldName}-${section}-${subsection}`
103
+ const shouldShowHighlighting = isEditor && validTags.length > 0
104
+
105
+ // Optimized styles
106
+ const containerStyle = useMemo(() => ({
107
+ position: 'relative' as const,
108
+ display: 'block',
109
+ width: '100%',
110
+ }), [])
111
+
112
+ const inputStyle = useMemo(() => ({
113
+ width: '100%',
114
+ padding: '8px 12px',
115
+ border: '1px solid #ddd',
116
+ borderRadius: '4px',
117
+ fontFamily: 'inherit',
118
+ fontSize: '14px',
119
+ lineHeight: '1.4',
120
+ background: shouldShowHighlighting ? 'transparent' : '#fff',
121
+ color: shouldShowHighlighting ? 'transparent' : 'inherit',
122
+ caretColor: shouldShowHighlighting ? '#333' : 'inherit',
123
+ position: 'relative' as const,
124
+ zIndex: 2,
125
+ resize: type === 'textarea' ? 'vertical' as const : 'none' as const,
126
+ }), [shouldShowHighlighting, type])
127
+
128
+ const highlightStyle = useMemo(() => ({
129
+ position: 'absolute' as const,
130
+ top: 0,
131
+ left: 0,
132
+ right: 0,
133
+ bottom: 0,
134
+ padding: '8px 12px',
135
+ margin: 0,
136
+ border: '1px solid transparent',
137
+ background: '#fff',
138
+ borderRadius: '4px',
139
+ color: '#333',
140
+ whiteSpace: type === 'textarea' ? 'pre-wrap' as const : 'pre' as const,
141
+ overflow: 'hidden',
142
+ pointerEvents: 'none' as const,
143
+ fontFamily: 'inherit',
144
+ fontSize: '14px',
145
+ lineHeight: '1.4',
146
+ zIndex: 1,
147
+ wordWrap: 'break-word' as const,
148
+ }), [type])
149
+
150
+ const focusStyle = useMemo(() => ({
151
+ outline: 'none',
152
+ borderColor: '#0066cc',
153
+ boxShadow: '0 0 0 2px rgba(0, 102, 204, 0.2)',
154
+ }), [])
155
+
156
+ return (
157
+ <div className={`form-group ${className}`}>
158
+ <label htmlFor={inputId}>
159
+ <span className='edit-label'>{label}</span>
160
+ <div style={containerStyle}>
161
+ {/* Highlighting overlay */}
162
+ {shouldShowHighlighting && (
163
+ <div
164
+ ref={highlightRef}
165
+ style={highlightStyle}
166
+ dangerouslySetInnerHTML={{ __html: highlightedContent }}
167
+ className="markup-highlight-overlay"
168
+ />
169
+ )}
170
+
171
+ {/* Input field */}
172
+ {type === 'textarea' ? (
173
+ <textarea
174
+ ref={inputRef as React.RefObject<HTMLTextAreaElement>}
175
+ id={inputId}
176
+ value={value}
177
+ onChange={handleChange}
178
+ onScroll={handleScroll}
179
+ placeholder={placeholder}
180
+ style={inputStyle}
181
+ onFocus={(e) => Object.assign(e.target.style, focusStyle)}
182
+ onBlur={(e) => Object.assign(e.target.style, inputStyle)}
183
+ {...attributes}
184
+ />
185
+ ) : (
186
+ <input
187
+ ref={inputRef as React.RefObject<HTMLInputElement>}
188
+ type="text"
189
+ id={inputId}
190
+ value={value}
191
+ onChange={handleChange}
192
+ onScroll={handleScroll}
193
+ placeholder={placeholder}
194
+ style={inputStyle}
195
+ onFocus={(e) => Object.assign(e.target.style, focusStyle)}
196
+ onBlur={(e) => Object.assign(e.target.style, inputStyle)}
197
+ {...attributes}
198
+ />
199
+ )}
200
+ </div>
201
+ </label>
202
+
203
+ <style jsx>{`
204
+ .markup-highlight-overlay .markup-variable-valid {
205
+ background-color: #e6f3ff;
206
+ color: #0066cc;
207
+ border-radius: 3px;
208
+ padding: 0px 2px;
209
+ font-weight: 500;
210
+ }
211
+
212
+ .markup-highlight-overlay .markup-variable-invalid {
213
+ background-color: #ffe6e6;
214
+ color: #cc0000;
215
+ border-radius: 3px;
216
+ padding: 0px 2px;
217
+ font-weight: 500;
218
+ text-decoration: underline wavy #cc0000;
219
+ }
220
+ `}</style>
221
+ </div>
222
+ )
223
+ })
224
+
225
+ MarkupHighlightedTextField.displayName = 'MarkupHighlightedTextField'
226
+
227
+ export default MarkupHighlightedTextField