@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.
- package/_stories/StoryRenderingTests.stories.tsx +164 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +32 -9
- package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
- package/components/CustomColorsEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +56 -38
- package/components/DataTable/DataTableStandAlone.tsx +8 -3
- package/components/DataTable/components/ChartHeader.tsx +44 -14
- package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
- package/components/DataTable/components/ExpandCollapse.tsx +10 -1
- package/components/DataTable/components/MapHeader.tsx +24 -13
- package/components/DataTable/data-table.css +12 -0
- package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -4
- package/components/DataTable/helpers/standardizeState.js +2 -2
- package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
- package/components/DownloadButton.tsx +40 -14
- package/components/EditorPanel/DataTableEditor.tsx +3 -3
- package/components/EditorPanel/EditorPanel.styles.css +423 -0
- package/components/EditorPanel/FootnotesEditor.tsx +44 -37
- package/components/EditorPanel/Inputs.tsx +12 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
- package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +450 -0
- package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
- package/components/ErrorBoundary.jsx +3 -1
- package/components/Filters/Filters.tsx +52 -24
- package/components/Filters/components/Dropdown.tsx +6 -1
- package/components/Filters/components/Tabs.tsx +1 -0
- package/components/Footnotes/Footnotes.tsx +35 -25
- package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
- package/components/HeaderThemeSelector/index.ts +2 -0
- package/components/Layout/styles/editor.scss +2 -1
- package/components/Legend/Legend.Gradient.tsx +3 -6
- package/components/LegendShape.tsx +121 -3
- package/components/Loader/Loader.tsx +1 -1
- package/components/MediaControls.tsx +72 -21
- package/components/PaletteConversionModal.tsx +90 -0
- package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
- package/components/PaletteSelector/PaletteSelector.css +94 -0
- package/components/PaletteSelector/PaletteSelector.tsx +112 -0
- package/components/PaletteSelector/index.ts +2 -0
- package/components/RichTooltip/RichTooltip.tsx +1 -0
- package/components/Table/Table.tsx +3 -1
- package/components/Table/components/Cell.tsx +23 -2
- package/components/Table/components/Row.tsx +5 -3
- package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
- package/components/_stories/DataTable.stories.tsx +1 -1
- package/components/_stories/Filters.stories.tsx +21 -2
- package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
- package/components/_stories/Footnotes.stories.tsx +769 -4
- package/components/_stories/Inputs.stories.tsx +3 -3
- package/components/_stories/MultiSelect.stories.tsx +3 -3
- package/components/_stories/NestedDropdown.stories.tsx +1 -1
- package/components/_stories/Table.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/_stories/Button.stories.tsx +1 -1
- package/components/elements/_stories/Card.stories.tsx +1 -1
- package/components/inputs/InputToggle.tsx +2 -0
- package/components/managers/DataDesigner.tsx +10 -9
- package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Tooltip.tsx +2 -1
- package/components/ui/_stories/Accordion.stories.tsx +1 -1
- package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
- package/components/ui/_stories/Colors.stories.tsx +330 -0
- package/components/ui/_stories/IconGallery.stories.tsx +316 -0
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/components/ui/accordion.styles.css +57 -0
- package/contexts/EditorContext.ts +18 -0
- package/contexts/editor.actions.ts +28 -0
- package/contexts/editor.reducer.ts +94 -0
- package/data/chartColorPalettes.ts +118 -0
- package/data/colorPalettes.ts +9 -0
- package/data/mapColorPalettes.ts +45 -0
- package/data/sharedPalettes.ts +50 -0
- package/dist/cove-main.css +63 -14
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +80 -0
- package/helpers/addValuesToFilters.ts +7 -3
- package/helpers/cloneConfig.ts +31 -0
- package/helpers/configDataHelpers.ts +128 -0
- package/helpers/configHelpers.ts +27 -0
- package/helpers/constants.ts +42 -2
- package/helpers/cove/number.ts +33 -12
- package/helpers/coveUpdateWorker.ts +15 -3
- package/helpers/fetchRemoteData.ts +3 -15
- package/helpers/filterColorPalettes.ts +152 -0
- package/helpers/generateColorsArray.ts +13 -0
- package/helpers/getColorPaletteVersion.ts +33 -0
- package/helpers/getPaletteAccessor.ts +18 -0
- package/helpers/markupProcessor.ts +220 -0
- package/helpers/mergeCustomOrderValues.ts +37 -0
- package/helpers/metrics/helpers.ts +42 -19
- package/helpers/metrics/types.ts +48 -9
- package/helpers/metrics/utils.ts +34 -0
- package/helpers/palettes/colorDistributions.ts +56 -0
- package/helpers/palettes/migratePaletteName.ts +150 -0
- package/helpers/palettes/standardizePaletteNames.ts +77 -0
- package/helpers/palettes/utils.ts +267 -0
- package/helpers/parseCsvWithQuotes.ts +65 -0
- package/helpers/queryStringUtils.ts +13 -0
- package/helpers/testing.ts +358 -0
- package/helpers/tests/addValuesToFilters.test.ts +1 -2
- package/helpers/tests/generateColorsArray.test.ts +24 -0
- package/helpers/tests/markupProcessor.test.ts +538 -0
- package/helpers/tests/testStandaloneBuild.ts +44 -0
- package/helpers/useMarkupVariables.ts +31 -0
- package/helpers/vegaConfig.ts +0 -1
- package/helpers/ver/4.24.10.ts +2 -1
- package/helpers/ver/4.24.11.ts +2 -1
- package/helpers/ver/4.24.3.ts +2 -1
- package/helpers/ver/4.24.4.ts +2 -1
- package/helpers/ver/4.24.5.ts +2 -1
- package/helpers/ver/4.24.7.ts +2 -1
- package/helpers/ver/4.24.9.ts +2 -1
- package/helpers/ver/4.25.1.ts +2 -1
- package/helpers/ver/4.25.10.ts +36 -0
- package/helpers/ver/4.25.11.ts +13 -0
- package/helpers/ver/4.25.3.ts +2 -1
- package/helpers/ver/4.25.4.ts +2 -1
- package/helpers/ver/4.25.6.ts +2 -1
- package/helpers/ver/4.25.7.ts +2 -1
- package/helpers/ver/4.25.8.ts +2 -1
- package/helpers/ver/4.25.9.ts +293 -0
- package/helpers/ver/tests/4.25.10.test.ts +204 -0
- package/helpers/ver/tests/4.25.8.test.ts +1 -1
- package/helpers/ver/tests/4.25.9.test.ts +51 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useColorPalette.ts +79 -0
- package/package.json +13 -4
- package/styles/_common-components.css +73 -0
- package/styles/_global.scss +32 -10
- package/styles/base.scss +8 -55
- package/styles/cove-main.scss +3 -1
- package/styles/filters.scss +10 -3
- package/styles/v2/base/index.scss +0 -1
- package/styles/v2/components/button.scss +4 -3
- package/styles/v2/components/editor.scss +16 -7
- package/styles/v2/layout/_data-table.scss +3 -2
- package/styles/v2/themes/_color-definitions.scss +18 -17
- package/styles/v2/utils/_breakpoints.scss +1 -1
- package/styles/v2/utils/index.scss +0 -1
- package/styles/waiting.scss +1 -1
- package/testing-setup.js +32 -0
- package/types/MarkupInclude.ts +8 -2
- package/types/MarkupVariable.ts +19 -0
- package/types/VizFilter.ts +2 -0
- package/vitest.config.ts +16 -0
- package/components/ui/_stories/Colors.stories.mdx +0 -220
- package/components/ui/_stories/IconGallery.stories.mdx +0 -14
- package/data/colorPalettes.js +0 -171
- package/helpers/formatConfigBeforeSave.ts +0 -135
- package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
- package/styles/_mixins.scss +0 -13
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/components/guidance-block.scss +0 -74
- package/styles/v2/utils/_functions.scss +0 -0
- /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
|
|
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
|
|
65
|
-
return [
|
|
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
|
-
?
|
|
68
|
+
? getSelectOptions(Object.keys(datasetsCache[footnotesConfig.dataKey]?.data?.[0] || {}))
|
|
70
69
|
: []
|
|
71
|
-
const dataSetOptions =
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
<
|
|
216
|
+
<div className='mt-2'>
|
|
233
217
|
<div className='edit-label column-heading float-right'>{filter.columnName} </div>
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
{
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
</
|
|
227
|
+
</div>
|
|
248
228
|
|
|
249
229
|
{subGrouping?.columnName && (
|
|
250
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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, ...
|
|
192
|
-
:
|
|
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
|
+
'&': '&',
|
|
68
|
+
'<': '<',
|
|
69
|
+
'>': '>',
|
|
70
|
+
'"': '"',
|
|
71
|
+
"'": '''
|
|
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
|