@sijanbhattarai/veda-utils 1.0.35

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,300 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useRef, useState } from 'react';
4
+
5
+ import {
6
+ TextInput,
7
+ TextInputMask,
8
+ Textarea,
9
+ Label,
10
+ DatePicker,
11
+ Checkbox,
12
+ Select,
13
+ } from '@trussworks/react-uswds';
14
+ import {
15
+ handleMapDateValidation,
16
+ handleMapArrayValidation,
17
+ handleChartDateValidation,
18
+ } from './inputValidation';
19
+
20
+ interface FieldProps {
21
+ fieldName: string;
22
+ value: string;
23
+ hint?: string;
24
+ onChange: (value: string) => void;
25
+ isRequired?: boolean;
26
+ isDate?: boolean;
27
+ numeric?: boolean;
28
+ onBlur?: (value: string) => void;
29
+ onFocus?: (value: string) => void;
30
+ type?: string;
31
+ componentProps: any;
32
+ propName: string;
33
+ customClass?: string;
34
+ placeHolder?: string;
35
+ draftInputs?: any;
36
+ inputErrors?: any;
37
+ setDraftInputs?: (value: any) => void;
38
+ setInputErrors?: (value: any) => void;
39
+ options?: string[];
40
+ }
41
+ const checkRequired = (isRequired, value) => {
42
+ return isRequired && !value ? { validationStatus: 'error' } : '';
43
+ };
44
+
45
+ const colorSchemes = [
46
+ 'Blues',
47
+ 'Greens',
48
+ 'Greys',
49
+ 'Oranges',
50
+ 'Purples',
51
+ 'Reds',
52
+ 'Turbo',
53
+ 'Viridis',
54
+ 'Inferno',
55
+ 'Magma',
56
+ 'Plasma',
57
+ 'Cividis',
58
+ 'Warm',
59
+ 'Cool',
60
+ 'CubehelixDefault',
61
+ ];
62
+
63
+ const setInput = (props) => {
64
+ const {
65
+ value,
66
+ isRequired,
67
+ type,
68
+ fieldName,
69
+ hint,
70
+ onChange,
71
+ componentProps,
72
+ propName,
73
+ placeHolder,
74
+ validateAgainst,
75
+ draftInputs,
76
+ setDraftInputs,
77
+ inputErrors,
78
+ setInputErrors,
79
+ options,
80
+ } = props;
81
+
82
+ if (options && Array.isArray(options)) {
83
+ return (
84
+ <>
85
+ <Label htmlFor={propName} className='margin-top-2'>
86
+ {fieldName}
87
+ </Label>
88
+ <span className='usa-hint'>{hint}</span>
89
+ <Select
90
+ id={propName}
91
+ name={propName}
92
+ value={value}
93
+ onChange={(e) =>
94
+ onChange({ ...componentProps, [propName]: e.target.value })
95
+ }
96
+ {...checkRequired(isRequired, value)}
97
+ >
98
+ <option value=''>- Select option -</option>
99
+ {options.map((option) => {
100
+ // Check if option is a string or an object with value/label
101
+ const value = typeof option === 'object' ? option.value : option;
102
+ const label = typeof option === 'object' ? option.label : option;
103
+
104
+ return (
105
+ <option key={value} value={value}>
106
+ {label}
107
+ </option>
108
+ );
109
+ })}
110
+ </Select>
111
+ </>
112
+ );
113
+ }
114
+
115
+ const cleanedType = type !== undefined && type.toLowerCase();
116
+
117
+ const [draft, setDraft] = useState(value);
118
+
119
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
120
+
121
+ useEffect(() => {
122
+
123
+ if (propName === 'dateFormat' && draft != draftInputs.draftDateFormat) {
124
+ setDraftInputs({ ...draftInputs, draftDateFormat: draft });
125
+ }
126
+ if (
127
+ propName === 'highlightStart' &&
128
+ draft != draftInputs.draftHighlightStart
129
+ ) {
130
+ setDraftInputs({ ...draftInputs, draftHighlightStart: draft });
131
+ }
132
+ if (propName === 'highlightEnd' && draft != draftInputs.draftHighlightEnd) {
133
+ setDraftInputs({ ...draftInputs, draftHighlightEnd: draft });
134
+ }
135
+ clearTimeout(timeoutRef.current);
136
+ timeoutRef.current = setTimeout(() => {
137
+
138
+ if (validateAgainst) {
139
+ if (
140
+ propName === 'dateFormat' ||
141
+ propName === 'highlightStart' ||
142
+ propName === 'highlightEnd'
143
+ ) {
144
+ handleChartDateValidation(
145
+ propName,
146
+ draftInputs,
147
+ setInputErrors,
148
+ inputErrors,
149
+ draft,
150
+ onChange,
151
+ componentProps,
152
+ );
153
+ } else if (validateAgainst === 'defaultDateFormat') {
154
+ handleMapDateValidation(
155
+ propName,
156
+ draftInputs,
157
+ inputErrors,
158
+ setInputErrors,
159
+ draft,
160
+ onChange,
161
+ componentProps,
162
+ );
163
+ } else if (validateAgainst === 'centerFormat') {
164
+ handleMapArrayValidation(
165
+ propName,
166
+ draftInputs,
167
+ inputErrors,
168
+ setInputErrors,
169
+ draft,
170
+ onChange,
171
+ componentProps,
172
+ );
173
+ } else {
174
+ onChange({ ...componentProps, [propName]: draft });
175
+ }
176
+ }
177
+ }, 400);
178
+
179
+ return () => clearTimeout(timeoutRef.current);
180
+ }, [draft, draftInputs]);
181
+
182
+ //Format date and submitted dates need to work or else the chart will throw an error.
183
+
184
+ switch (cleanedType) {
185
+ case 'date':
186
+ return (
187
+ //CHORE: Need to clean up or delete
188
+ <>
189
+ <Label htmlFor='input-type-text' className='margin-top-2'>
190
+ {fieldName}
191
+ </Label>
192
+
193
+ <span className='usa-hint'>{hint}</span>
194
+
195
+ <DatePicker
196
+ defaultValue={value}
197
+ onChange={(e) => console.log('DatePicker', e)}
198
+ {...checkRequired(isRequired, value)}
199
+ />
200
+ </>
201
+ );
202
+ break;
203
+ case 'checkbox':
204
+ return (
205
+ <Checkbox
206
+ id={fieldName}
207
+ name='checkbox'
208
+ label={fieldName}
209
+ onChange={(e) =>
210
+ onChange({ ...componentProps, [propName]: e.target.value })
211
+ }
212
+ />
213
+ );
214
+ break;
215
+ case 'select':
216
+ return (
217
+ <>
218
+ <Label htmlFor='input-type-text' className='margin-top-2'>
219
+ {fieldName}
220
+ </Label>
221
+ <span className='usa-hint'>{hint}</span>
222
+ <Select
223
+ id={fieldName}
224
+ name={fieldName}
225
+ onChange={(e) =>
226
+ onChange({ ...componentProps, [propName]: e.target.value })
227
+ }
228
+ >
229
+ {colorSchemes.map((scheme) => {
230
+ return (
231
+ <option key={scheme} value={scheme}>
232
+ {scheme}
233
+ </option>
234
+ );
235
+ })}
236
+ </Select>
237
+ </>
238
+ );
239
+ break;
240
+ case 'area':
241
+ return (
242
+ <>
243
+ <Label htmlFor='input-type-text' className='margin-top-2'>
244
+ {fieldName}
245
+ </Label>
246
+ <span className='usa-hint'>{hint}</span>
247
+ <Textarea
248
+ id='input-type-text'
249
+ name='input-type-text'
250
+ value={value}
251
+ onChange={(e) => {
252
+ onChange({ ...componentProps, [propName]: e.target.value });
253
+ }}
254
+ className=''
255
+ {...checkRequired(isRequired, value)}
256
+ />
257
+ </>
258
+ );
259
+ break;
260
+ default:
261
+ return (
262
+ <>
263
+ <Label htmlFor='input-type-text' className='margin-top-2'>
264
+ {fieldName}
265
+ </Label>
266
+
267
+ <span className='usa-hint'>{hint}</span>
268
+ <TextInput
269
+ id='input-type-text'
270
+ name='input-type-text'
271
+ type='text'
272
+ value={validateAgainst ? draft : value}
273
+ onChange={(e) => {
274
+ if (validateAgainst) {
275
+
276
+ setDraft(e.target.value);
277
+ } else {
278
+
279
+ onChange({ ...componentProps, [propName]: e.target.value });
280
+ }
281
+ }}
282
+ placeholder={placeHolder}
283
+ {...checkRequired(isRequired, value)}
284
+ validationStatus={
285
+ validateAgainst && (inputErrors[propName] ? 'error' : undefined)
286
+ }
287
+ />
288
+ </>
289
+ );
290
+ }
291
+ };
292
+ export const InputField: React.FC<FieldProps> = (props) => {
293
+ const { propName, customClass } = props;
294
+
295
+ return (
296
+ <div key={propName} className={customClass}>
297
+ {setInput(props)}
298
+ </div>
299
+ );
300
+ };
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext } from 'react';
4
+ import { LexicalEditor, LexicalNode } from 'lexical';
5
+
6
+ interface MapContextValue {
7
+ parentEditor: LexicalEditor;
8
+ lexicalNode: LexicalNode;
9
+ }
10
+
11
+ const MapContext = createContext<MapContextValue | null>(null);
12
+
13
+ export const MapContextProvider = ({
14
+ children,
15
+ value,
16
+ }: {
17
+ children: React.ReactNode;
18
+ value: MapContextValue;
19
+ }) => {
20
+ return <MapContext.Provider value={value}>{children}</MapContext.Provider>;
21
+ };
22
+
23
+ export const useMapContext = () => {
24
+ const context = useContext(MapContext);
25
+ if (!context) {
26
+ throw new Error('useMapContext must be used within a MapContextProvider');
27
+ }
28
+ return context;
29
+ };
@@ -0,0 +1,18 @@
1
+ import type { DatasetData, StoryData } from './lib';
2
+
3
+ interface MetadataWithSlug<T> {
4
+ metadata: T;
5
+ slug: string;
6
+ }
7
+
8
+ interface WithContent<T> extends MetadataWithSlug<T> {
9
+ content: string;
10
+ }
11
+
12
+ export type DatasetMetadata = MetadataWithSlug<DatasetData>;
13
+ export type DatasetWithContent = WithContent<DatasetData>;
14
+
15
+ export type StoryMetadata = MetadataWithSlug<StoryData>;
16
+ export type StoryWithContent = WithContent<StoryData>;
17
+
18
+ export type ContentMetadata = MetadataWithSlug<DatasetData | StoryData>;
package/src/data.tsx ADDED
@@ -0,0 +1,36 @@
1
+ import type { DatasetData, StoryData, VedaData } from '@lib';
2
+ import type { DatasetMetadata, DatasetWithContent } from './content';
3
+
4
+ export function processTaxonomies(data): DatasetData | StoryData {
5
+ const updatedTax = data.taxonomy.map((t) => {
6
+ const updatedVals = t.values.map((v) => {
7
+ return {
8
+ id: v.replace(/ /g, '_').toLowerCase(),
9
+ name: v,
10
+ };
11
+ });
12
+ return { ...t, values: updatedVals };
13
+ });
14
+ return { ...data, taxonomy: updatedTax };
15
+ }
16
+
17
+ export const transformToDatasetsList = (
18
+ content: DatasetMetadata[],
19
+ ): DatasetData[] => {
20
+ return content?.map((post) => ({
21
+ ...post.metadata,
22
+ }));
23
+ };
24
+
25
+ export const transformToVedaData = (
26
+ datasets: DatasetMetadata[] | undefined,
27
+ ): VedaData<DatasetData> => {
28
+ const transformed = {};
29
+ datasets?.map((dataset) => {
30
+ const id = dataset.metadata.id;
31
+ transformed[id] = {
32
+ data: dataset.metadata,
33
+ };
34
+ });
35
+ return transformed;
36
+ };
@@ -0,0 +1,45 @@
1
+ export const extractImports = (ast) => {
2
+ const imports: any = [];
3
+ const visit = (node, parent, index) => {
4
+ // If it's a text node with 'import' in its value
5
+ if (
6
+ node.type === 'text' &&
7
+ node.value &&
8
+ node.value.includes('import') &&
9
+ node.value.includes('from')
10
+ ) {
11
+ if (
12
+ parent &&
13
+ Array.isArray(parent.children) &&
14
+ typeof index === 'number'
15
+ ) {
16
+ // Remove the node from its original position
17
+ parent.children.splice(index, 1);
18
+ // Wrap it in a paragraph or keep as-is depending on your use case
19
+ imports.push({
20
+ type: 'paragraph',
21
+ children: [node],
22
+ });
23
+ return; // Skip deeper recursion into removed node
24
+ }
25
+ }
26
+
27
+ // Recurse through children if they exist
28
+ if (node.children && Array.isArray(node.children)) {
29
+ // Copy to avoid mutation issues when removing nodes
30
+ const childrenCopy = [...node.children];
31
+ for (let i = 0; i < childrenCopy.length; i++) {
32
+ visit(childrenCopy[i], node, i);
33
+ }
34
+ }
35
+ };
36
+
37
+ visit(ast, null, null);
38
+
39
+ // Prepend extracted imports to top-level children
40
+ if (ast.type === 'root' && Array.isArray(ast.children)) {
41
+ ast.children = [...imports, ...ast.children];
42
+ }
43
+
44
+ return ast;
45
+ };
@@ -0,0 +1,90 @@
1
+ import { handleTwoColumn } from './parseTwoColumn';
2
+ import { wrapComponent } from './wrapComponent';
3
+
4
+ export const groupByBreakIntoBlocks = (ast) => {
5
+ const result: any = [];
6
+ const proseWrapper = (children) => {
7
+ return {
8
+ type: 'mdxJsxFlowElement',
9
+ name: 'Prose',
10
+ children: [...children],
11
+ };
12
+ };
13
+
14
+ const groupChildren = (children) => {
15
+ const groups: any = [];
16
+ let currentGroup: any = [];
17
+
18
+ for (const child of children) {
19
+ if (
20
+ child.type === 'mdxJsxTextElement' ||
21
+ child.type === 'mdxJsxFlowElement'
22
+ ) {
23
+ if (child.name === 'Break') {
24
+ if (currentGroup.length > 0) {
25
+ groups.push([proseWrapper(currentGroup)]);
26
+ currentGroup = [];
27
+ }
28
+ } else if (
29
+ child.name === 'Block' ||
30
+ child.name === 'Chart' ||
31
+ child.name === 'Map' ||
32
+ child.name === 'MapBlock' ||
33
+ child.name === 'TwoColumn'
34
+ ) {
35
+ groups.push([proseWrapper(currentGroup)]);
36
+
37
+ if (child.name === 'Chart' || child.name === 'Map') {
38
+ groups.push([wrapComponent(child)]);
39
+ } else if (child.name === 'TwoColumn') {
40
+ const parsedColumn = handleTwoColumn(child);
41
+ groups.push(parsedColumn);
42
+ }
43
+ currentGroup = [];
44
+ }
45
+ } else {
46
+ currentGroup.push(child);
47
+ }
48
+ }
49
+
50
+ if (currentGroup.length > 0) {
51
+ groups.push([...currentGroup]);
52
+ }
53
+ return groups;
54
+ };
55
+
56
+ if (ast.type === 'root' && Array.isArray(ast.children)) {
57
+ const groups = groupChildren(ast.children);
58
+
59
+ for (const group of groups) {
60
+ // Check for prose wrapper inside group If no prose wrapper
61
+ // then wrap group inside prose object before adding to block element
62
+
63
+ if (
64
+ group.some((item) => {
65
+ return item.name === 'Prose';
66
+ })
67
+ ) {
68
+ result.push({
69
+ type: 'mdxJsxFlowElement',
70
+ name: 'Block',
71
+ children: [...group],
72
+ });
73
+ } else {
74
+ result.push({
75
+ type: 'mdxJsxFlowElement',
76
+ name: 'Block',
77
+ children: [
78
+ {
79
+ type: 'mdxJsxFlowElement',
80
+ name: 'Prose',
81
+ children: [...group],
82
+ },
83
+ ],
84
+ });
85
+ }
86
+ }
87
+ }
88
+
89
+ return result;
90
+ };
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ // Export all utilities
2
+ export * from './reserializeMDast';
3
+ export * from './parseTwoColumn';
4
+ export * from './ChartContext';
5
+ export * from './MapContext';
6
+ export * from './CreateInterface';
7
+ export * from './extractImports';
8
+ export * from './groupElements';
9
+ export * from './inputValidation';
10
+ export * from './wrapComponent';
11
+ export * from './data';
12
+ export * from './content';
13
+ export * from './lib';
@@ -0,0 +1,148 @@
1
+ export const inputValidation = () => {};
2
+
3
+ export const dateStringToregex = (format) => {
4
+ const tokens = {
5
+ '%d': '(0[1-9]|[12][0-9]|3[01])',
6
+ '%m': '(0[1-9]|1[0-2])',
7
+ '%Y': '\\d{4}',
8
+ '%y': '\\d{2}',
9
+ '%H': '([01][0-9]|2[0-3])',
10
+ '%M': '([0-5][0-9])',
11
+ '%S': '([0-5][0-9])',
12
+ };
13
+
14
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+
16
+ let pattern = '';
17
+
18
+ for (let i = 0; i < format.length; i++) {
19
+ if (format[i] === '%' && i < format.length - 1) {
20
+ const directive = format[i] + format[i + 1];
21
+ pattern += tokens[directive] || escape(directive); // unknown: treat literally
22
+ i++; // skip the directive's second char
23
+ } else {
24
+ pattern += escape(format[i]);
25
+ }
26
+ }
27
+
28
+ return new RegExp(`^${pattern}$`);
29
+ };
30
+
31
+ export const dateFormatValidation = (format, input) => {
32
+ const regexToTest = dateStringToregex(format);
33
+
34
+ //return false no errors if regex passes, return true there are erros if fails
35
+ return regexToTest.test(input) ? false : true;
36
+ };
37
+
38
+ export const handleChartDateValidation = (
39
+ propName,
40
+ draftInputs,
41
+ setInputErrors,
42
+ inputErrors,
43
+ draft,
44
+ onChange,
45
+ chartProps,
46
+ ) => {
47
+ if (propName === 'highlightStart' || propName === 'highlightEnd') {
48
+ if (dateFormatValidation(draftInputs.draftDateFormat, draft) === false) {
49
+ setInputErrors({
50
+ highlightStart: dateFormatValidation(
51
+ draftInputs.draftDateFormat,
52
+ draftInputs.draftHighlightStart,
53
+ ),
54
+ highlightEnd: dateFormatValidation(
55
+ draftInputs.draftDateFormat,
56
+ draftInputs.draftHighlightEnd,
57
+ ),
58
+ });
59
+ if (
60
+ inputErrors.highlightStart == false &&
61
+ inputErrors.highlightEnd == false
62
+ ) {
63
+ onChange({
64
+ ...chartProps,
65
+ dateFormat: draftInputs.draftDateFormat,
66
+ highlightStart: draftInputs.draftHighlightStart,
67
+ highlightEnd: draftInputs.draftHighlightEnd,
68
+ });
69
+ }
70
+ } else {
71
+ setInputErrors({
72
+ highlightStart: dateFormatValidation(
73
+ draftInputs.draftDateFormat,
74
+ draftInputs.draftHighlightStart,
75
+ ),
76
+ highlightEnd: dateFormatValidation(
77
+ draftInputs.draftDateFormat,
78
+ draftInputs.draftHighlightEnd,
79
+ ),
80
+ });
81
+ }
82
+ } else if (propName === 'dateFormat') {
83
+ setInputErrors({
84
+ highlightStart: dateFormatValidation(draft, chartProps.highlightStart),
85
+ highlightEnd: dateFormatValidation(draft, chartProps.highlightEnd),
86
+ });
87
+ } else if (
88
+ inputErrors.highlightStart == false &&
89
+ inputErrors.highlightEnd == false
90
+ ) {
91
+ onChange({
92
+ ...chartProps,
93
+ dateFormat: draftInputs.draftDateFormat,
94
+ highlightStart: draftInputs.draftHighlightStart,
95
+ highlightEnd: draftInputs.draftHighlightEnd,
96
+ });
97
+ }
98
+ };
99
+ export const handleMapDateValidation = (
100
+ propName,
101
+ draftInputs,
102
+ inputErrors,
103
+ setInputErrors,
104
+ draft,
105
+ onChange,
106
+ componentProps,
107
+ ) => {
108
+ if (
109
+ dateFormatValidation(draftInputs.defaultDateFormat, draft) === false ||
110
+ draft === ''
111
+ ) {
112
+ setInputErrors({ ...inputErrors, [propName]: false });
113
+ onChange({ ...componentProps, [propName]: draft });
114
+ } else {
115
+ setInputErrors({ ...inputErrors, [propName]: true });
116
+ }
117
+ };
118
+
119
+ export const handleMapArrayValidation = (
120
+ propName,
121
+ draftInputs,
122
+ inputErrors,
123
+ setInputErrors,
124
+ draft,
125
+ onChange,
126
+ componentProps,
127
+ ) => {
128
+ const numberPattern =
129
+ /^\[[+-]?(0|[1-9][0-9]*)(\.[0-9]+)?(?:,\s*[+-]?(0|[1-9][0-9]*)(\.[0-9]+)?)*\]$/;
130
+ //This regex checks that the input is wrapped in [...]
131
+ //Checks for no trailing decimal points ex: -91.
132
+ //Checks that there is no leading 0 unless followed by a . ex 0.12 or 0 are acceptable
133
+ const cleanedDraft = draft.replace(/\s/g, '');
134
+ if (numberPattern.test(cleanedDraft)) {
135
+ const parsedValues = JSON.parse(cleanedDraft);
136
+ const checkLong = (long) => (long <= 180 && long >= -180 ? true : false);
137
+ const checkLat = (lat) => (lat <= 90 && lat >= -90 ? true : false);
138
+ //validating upper and lower limits of long and lat
139
+ if (checkLong(parsedValues[0]) && checkLat(parsedValues[1])) {
140
+ setInputErrors({ ...inputErrors, [propName]: false });
141
+ onChange({ ...componentProps, [propName]: draft });
142
+ } else {
143
+ setInputErrors({ ...inputErrors, [propName]: true });
144
+ }
145
+ } else {
146
+ setInputErrors({ ...inputErrors, [propName]: true });
147
+ }
148
+ };