@tellescope/react-components 1.168.0 → 1.170.0
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/lib/cjs/Forms/form_responses.js +1 -1
- package/lib/cjs/Forms/form_responses.js.map +1 -1
- package/lib/cjs/Forms/forms.d.ts +9 -2
- package/lib/cjs/Forms/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +50 -42
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +7 -0
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +96 -68
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/Forms/inputs.d.ts +3 -2
- package/lib/cjs/Forms/inputs.d.ts.map +1 -1
- package/lib/cjs/Forms/inputs.js +80 -25
- package/lib/cjs/Forms/inputs.js.map +1 -1
- package/lib/cjs/Forms/types.d.ts +7 -0
- package/lib/cjs/Forms/types.d.ts.map +1 -1
- package/lib/cjs/Forms/wysiwyg.d.ts +12 -0
- package/lib/cjs/Forms/wysiwyg.d.ts.map +1 -0
- package/lib/cjs/Forms/wysiwyg.js +225 -0
- package/lib/cjs/Forms/wysiwyg.js.map +1 -0
- package/lib/cjs/inputs.d.ts +1 -1
- package/lib/cjs/inputs.d.ts.map +1 -1
- package/lib/cjs/state.d.ts.map +1 -1
- package/lib/cjs/state.js +4 -0
- package/lib/cjs/state.js.map +1 -1
- package/lib/cjs/table.js +1 -1
- package/lib/cjs/table.js.map +1 -1
- package/lib/esm/CMS/components.d.ts +0 -1
- package/lib/esm/CMS/components.d.ts.map +1 -1
- package/lib/esm/Forms/form_responses.js +1 -1
- package/lib/esm/Forms/form_responses.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts +12 -5
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +51 -43
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +7 -0
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +96 -68
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/Forms/inputs.d.ts +4 -3
- package/lib/esm/Forms/inputs.d.ts.map +1 -1
- package/lib/esm/Forms/inputs.js +78 -24
- package/lib/esm/Forms/inputs.js.map +1 -1
- package/lib/esm/Forms/inputs.native.d.ts +0 -1
- package/lib/esm/Forms/inputs.native.d.ts.map +1 -1
- package/lib/esm/Forms/types.d.ts +7 -0
- package/lib/esm/Forms/types.d.ts.map +1 -1
- package/lib/esm/Forms/wysiwyg.d.ts +12 -0
- package/lib/esm/Forms/wysiwyg.d.ts.map +1 -0
- package/lib/esm/Forms/wysiwyg.js +218 -0
- package/lib/esm/Forms/wysiwyg.js.map +1 -0
- package/lib/esm/controls.d.ts +2 -2
- package/lib/esm/inputs.d.ts +2 -2
- package/lib/esm/inputs.d.ts.map +1 -1
- package/lib/esm/inputs.native.d.ts +0 -1
- package/lib/esm/inputs.native.d.ts.map +1 -1
- package/lib/esm/layout.d.ts +1 -1
- package/lib/esm/state.d.ts +264 -264
- package/lib/esm/state.d.ts.map +1 -1
- package/lib/esm/state.js +4 -0
- package/lib/esm/state.js.map +1 -1
- package/lib/esm/table.js +1 -1
- package/lib/esm/table.js.map +1 -1
- package/lib/esm/theme.native.d.ts +0 -1
- package/lib/esm/theme.native.d.ts.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -9
- package/src/Forms/form_responses.tsx +1 -1
- package/src/Forms/forms.tsx +20 -2
- package/src/Forms/hooks.tsx +43 -26
- package/src/Forms/inputs.tsx +41 -9
- package/src/Forms/types.ts +4 -0
- package/src/Forms/wysiwyg.tsx +234 -0
- package/src/inputs.tsx +1 -1
- package/src/state.tsx +1 -0
- package/src/table.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellescope/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.170.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -47,23 +47,27 @@
|
|
|
47
47
|
"@reduxjs/toolkit": "^1.6.2",
|
|
48
48
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
49
49
|
"@stripe/stripe-js": "^1.52.1",
|
|
50
|
-
"@tellescope/constants": "^1.
|
|
51
|
-
"@tellescope/sdk": "^1.
|
|
52
|
-
"@tellescope/types-client": "^1.
|
|
53
|
-
"@tellescope/types-models": "^1.
|
|
54
|
-
"@tellescope/types-utilities": "^1.
|
|
55
|
-
"@tellescope/utilities": "^1.
|
|
56
|
-
"@tellescope/validation": "^1.
|
|
50
|
+
"@tellescope/constants": "^1.170.0",
|
|
51
|
+
"@tellescope/sdk": "^1.170.0",
|
|
52
|
+
"@tellescope/types-client": "^1.170.0",
|
|
53
|
+
"@tellescope/types-models": "^1.170.0",
|
|
54
|
+
"@tellescope/types-utilities": "^1.170.0",
|
|
55
|
+
"@tellescope/utilities": "^1.170.0",
|
|
56
|
+
"@tellescope/validation": "^1.170.0",
|
|
57
57
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
|
58
58
|
"@typescript-eslint/parser": "^4.33.0",
|
|
59
59
|
"css-to-react-native": "^3.0.0",
|
|
60
|
+
"draft-js": "^0.11.7",
|
|
61
|
+
"draftjs-to-html": "^0.9.1",
|
|
60
62
|
"eslint": "^7.32.0",
|
|
61
63
|
"eslint-plugin-react": "^7.26.1",
|
|
62
64
|
"formik": "^2.2.9",
|
|
63
65
|
"heic2any": "^0.0.4",
|
|
66
|
+
"html-to-draftjs": "^1.5.0",
|
|
64
67
|
"nodemon": "^2.0.13",
|
|
65
68
|
"react-beautiful-dnd": "^13.1.1",
|
|
66
69
|
"react-datepicker": "^3.4.1",
|
|
70
|
+
"react-draft-wysiwyg": "^1.15.0",
|
|
67
71
|
"react-draggable": "^4.4.6",
|
|
68
72
|
"react-dropzone": "^11.4.2",
|
|
69
73
|
"react-ga4": "^1.4.1",
|
|
@@ -80,7 +84,7 @@
|
|
|
80
84
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
81
85
|
"react-native": "^0.65.0 || ^0.66.0 || ^0.67.0 || ^0.68.0 || ^0.71.0"
|
|
82
86
|
},
|
|
83
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "d9bbecc704e9ecd06d59ffbdaa155879a109c925",
|
|
84
88
|
"publishConfig": {
|
|
85
89
|
"access": "public"
|
|
86
90
|
}
|
|
@@ -31,7 +31,7 @@ export const ResponseAnswer = ({ formResponse, fieldId, isHTML, answer: a, print
|
|
|
31
31
|
onImageClick?: (args: { src: string }) => void,
|
|
32
32
|
isHTML?: boolean,
|
|
33
33
|
}) => (
|
|
34
|
-
(isHTML && typeof a.value === 'string')
|
|
34
|
+
((isHTML || a.type === 'Rich Text') && typeof a.value === 'string')
|
|
35
35
|
? <div dangerouslySetInnerHTML={{ __html: remove_script_tags(a.value) }} />
|
|
36
36
|
: a.value
|
|
37
37
|
? (
|
package/src/Forms/forms.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
2
|
-
import { Button, CircularProgress, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
|
|
2
|
+
import { Button, CircularProgress, FileBlob, FileUploadHandler, Flex, LinearProgress, LoadingButton, Modal, Paper, Styled, Typography, form_display_text_for_language, useFileUpload, useFormResponses, useSession } from "../index"
|
|
3
3
|
import { useListForFormFields, useOrganizationTheme, useTellescopeForm, WithOrganizationTheme, Response, FileResponse, NextFieldLogicOptions } from "./hooks"
|
|
4
4
|
import { ChangeHandler, FormInputs } from "./types"
|
|
5
|
-
import { AddressInput, AllergiesInput, AppointmentBookingInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, defaultButtonStyles } from "./inputs"
|
|
5
|
+
import { AddressInput, AllergiesInput, AppointmentBookingInput, ConditionsInput, DatabaseSelectInput, DateInput, DateStringInput, DropdownInput, EmailInput, EmotiiInput, FileInput, FilesInput, HeightInput, HiddenValueInput, InsuranceInput, LanguageSelect, MedicationsInput, MultipleChoiceInput, NumberInput, PhoneInput, Progress, RankingInput, RatingInput, RedirectInput, RelatedContactsInput, RichTextInput, SignatureInput, StringInput, StringLongInput, StripeInput, TableInput, TimeInput, defaultButtonStyles } from "./inputs"
|
|
6
6
|
import { PRIMARY_HEX } from "@tellescope/constants"
|
|
7
7
|
import { FormResponse, FormField, Form, Enduser } from "@tellescope/types-client"
|
|
8
8
|
import { FormResponseAnswerFileValue, OrganizationTheme } from "@tellescope/types-models"
|
|
@@ -135,6 +135,7 @@ export const QuestionForField = ({
|
|
|
135
135
|
rootResponseId,
|
|
136
136
|
isInQuestionGroup,
|
|
137
137
|
logicOptions,
|
|
138
|
+
uploadingFiles, setUploadingFiles, handleFileUpload,
|
|
138
139
|
} : {
|
|
139
140
|
spacing?: number,
|
|
140
141
|
form?: Form,
|
|
@@ -147,6 +148,9 @@ export const QuestionForField = ({
|
|
|
147
148
|
isSinglePage?: boolean,
|
|
148
149
|
isInQuestionGroup?: boolean,
|
|
149
150
|
logicOptions?: NextFieldLogicOptions,
|
|
151
|
+
handleFileUpload: (blob: FileBlob, fieldId: string) => Promise<any>,
|
|
152
|
+
uploadingFiles: { fieldId: string }[]
|
|
153
|
+
setUploadingFiles: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
|
|
150
154
|
} & Pick<TellescopeFormProps, "rootResponseId" | "goToNextField" | "groupId" | "groupInstance" | "submit" | "formResponseId" | 'enduserId' | 'isPreviousDisabled' | 'goToPreviousField' | 'enduser' | 'handleDatabaseSelect' | 'onAddFile' | 'onFieldChange' | 'fields' | 'customInputs' | 'responses' | 'selectedFiles' | 'validateField'>) => {
|
|
151
155
|
const String = customInputs?.['string'] ?? StringInput
|
|
152
156
|
const StringLong = customInputs?.['stringLong'] ?? StringLongInput
|
|
@@ -175,6 +179,7 @@ export const QuestionForField = ({
|
|
|
175
179
|
const Emotii = customInputs?.['Emotii'] ?? EmotiiInput
|
|
176
180
|
const Allergies = customInputs?.['Allergies'] ?? AllergiesInput
|
|
177
181
|
const Conditions = customInputs?.['Conditions'] ?? ConditionsInput
|
|
182
|
+
const RichText = customInputs?.['Rich Text'] ?? RichTextInput
|
|
178
183
|
|
|
179
184
|
const validationMessage = validateField(field)
|
|
180
185
|
|
|
@@ -233,6 +238,7 @@ export const QuestionForField = ({
|
|
|
233
238
|
? value.answer.value?.name
|
|
234
239
|
: ''
|
|
235
240
|
}
|
|
241
|
+
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
236
242
|
/>
|
|
237
243
|
)
|
|
238
244
|
: field.type === 'files' ? (
|
|
@@ -242,6 +248,7 @@ export const QuestionForField = ({
|
|
|
242
248
|
// ? value.answer.value?.name
|
|
243
249
|
// : ''
|
|
244
250
|
// }
|
|
251
|
+
handleFileUpload={handleFileUpload} uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
245
252
|
/>
|
|
246
253
|
)
|
|
247
254
|
: field.type === 'dateString' ? (
|
|
@@ -283,6 +290,9 @@ export const QuestionForField = ({
|
|
|
283
290
|
: field.type === 'stringLong' ? (
|
|
284
291
|
<StringLong field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'string' | 'stringLong'>} form={form} />
|
|
285
292
|
)
|
|
293
|
+
: field.type === 'Rich Text' ? (
|
|
294
|
+
<RichText field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'Rich Text'>} form={form} />
|
|
295
|
+
)
|
|
286
296
|
: field.type === 'email' ? (
|
|
287
297
|
<Email field={field} disabled={value.disabled} value={value.answer.value as string} onChange={onFieldChange as ChangeHandler<'email'>} form={form} />
|
|
288
298
|
)
|
|
@@ -358,6 +368,8 @@ export const QuestionForField = ({
|
|
|
358
368
|
spacing={field.options?.groupPadding}
|
|
359
369
|
logicOptions={logicOptions}
|
|
360
370
|
isInQuestionGroup
|
|
371
|
+
uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
372
|
+
handleFileUpload={handleFileUpload}
|
|
361
373
|
/>
|
|
362
374
|
</Flex>
|
|
363
375
|
)
|
|
@@ -443,6 +455,7 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
|
|
|
443
455
|
groupId,
|
|
444
456
|
groupInstance,
|
|
445
457
|
logicOptions,
|
|
458
|
+
uploadingFiles, setUploadingFiles, handleFileUpload,
|
|
446
459
|
}) => {
|
|
447
460
|
const beforeunloadHandler = React.useCallback((e: BeforeUnloadEvent) => {
|
|
448
461
|
try {
|
|
@@ -541,6 +554,8 @@ export const TellescopeSingleQuestionFlow: typeof TellescopeForm = ({
|
|
|
541
554
|
validateField={validateField}
|
|
542
555
|
groupId={groupId} groupInstance={groupInstance}
|
|
543
556
|
logicOptions={logicOptions}
|
|
557
|
+
uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
558
|
+
handleFileUpload={handleFileUpload}
|
|
544
559
|
/>
|
|
545
560
|
</Flex>
|
|
546
561
|
</Flex>
|
|
@@ -942,6 +957,7 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
|
|
|
942
957
|
enduser,
|
|
943
958
|
groupId,
|
|
944
959
|
groupInstance,
|
|
960
|
+
uploadingFiles, setUploadingFiles, handleFileUpload,
|
|
945
961
|
...props
|
|
946
962
|
}) => {
|
|
947
963
|
const list = useListForFormFields(fields, responses, { form: props.form, gender: enduser?.gender })
|
|
@@ -1019,6 +1035,8 @@ export const TellescopeSinglePageForm: React.JSXElementConstructor<TellescopeFor
|
|
|
1019
1035
|
responses={responses} selectedFiles={selectedFiles}
|
|
1020
1036
|
validateField={validateField}
|
|
1021
1037
|
groupId={groupId} groupInstance={groupInstance}
|
|
1038
|
+
uploadingFiles={uploadingFiles} setUploadingFiles={setUploadingFiles}
|
|
1039
|
+
handleFileUpload={handleFileUpload}
|
|
1022
1040
|
/>
|
|
1023
1041
|
</Flex>
|
|
1024
1042
|
</Flex>
|
package/src/Forms/hooks.tsx
CHANGED
|
@@ -531,6 +531,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
531
531
|
const [submittingStatus, setSubmittingStatus] = useState<SubmitStatus>(undefined)
|
|
532
532
|
const [submitErrorMessage, setSubmitErrorMessage] = useState('')
|
|
533
533
|
const [currentPageIndex, setCurrentPageIndex] = useState(0)
|
|
534
|
+
const [uploadingFiles, setUploadingFiles] = useState<{ fieldId: string }[]>([])
|
|
534
535
|
const prevFieldStackRef = useRef<typeof root[]>([])
|
|
535
536
|
|
|
536
537
|
const [repeats, setRepeats] = useState({} as Record<string, string | number>)
|
|
@@ -815,6 +816,10 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
815
816
|
return null
|
|
816
817
|
}
|
|
817
818
|
|
|
819
|
+
if (value.answer?.type === 'Rich Text' && value.answer?.value?.trim() === '<p></p>' && !field.isOptional) {
|
|
820
|
+
return "Answer is required"
|
|
821
|
+
}
|
|
822
|
+
|
|
818
823
|
if (value.answer.type === 'Insurance') {
|
|
819
824
|
if (value.answer.value?.relationshipDetails?.dateOfBirth && !isDateString(value.answer.value.relationshipDetails.dateOfBirth)) {
|
|
820
825
|
return "Enter date of birth in MM-DD-YYYY format"
|
|
@@ -1102,6 +1107,31 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
1102
1107
|
return responsesToSubmit
|
|
1103
1108
|
}, [responses])
|
|
1104
1109
|
|
|
1110
|
+
const handleFileUpload = useCallback(async (blob: FileBlob, fieldId: string) => {
|
|
1111
|
+
const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
|
|
1112
|
+
const result: FormResponseAnswerFileValue = { name: blob.name, secureName: '' }
|
|
1113
|
+
const { secureName } = await handleUpload(
|
|
1114
|
+
{
|
|
1115
|
+
name: blob.name,
|
|
1116
|
+
size: blob.size,
|
|
1117
|
+
type: blob.type,
|
|
1118
|
+
enduserId,
|
|
1119
|
+
},
|
|
1120
|
+
blob
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
if (responses[responseIndex].answer.type === 'files') {
|
|
1124
|
+
if (!responses[responseIndex].answer.value) {
|
|
1125
|
+
responses[responseIndex].answer.value = []
|
|
1126
|
+
}
|
|
1127
|
+
(responses[responseIndex].answer.value as any[]).push({
|
|
1128
|
+
...result, type: blob.type, secureName, name: result.name ?? ''
|
|
1129
|
+
})
|
|
1130
|
+
} else {
|
|
1131
|
+
responses[responseIndex].answer.value = { ...result, type: blob.type, secureName, name: result.name ?? '' }
|
|
1132
|
+
}
|
|
1133
|
+
}, [responses, handleUpload])
|
|
1134
|
+
|
|
1105
1135
|
const submit = useCallback(async (options?: { onPreRedirect?: () => void, onFileUploadsDone?: () => void, onSuccess?: (r: FormResponse) => void, includedFieldIds?: string[], otherEnduserIds?: string[], onBulkErrors?: (errors: { enduserId: string, message: string }[]) => void }) => {
|
|
1106
1136
|
setSubmitErrorMessage('')
|
|
1107
1137
|
const hasFile = selectedFiles.find(f => !!f.blobs?.length) !== undefined
|
|
@@ -1113,31 +1143,14 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
1113
1143
|
for (const blobInfo of selectedFiles) {
|
|
1114
1144
|
const { blobs, fieldId } = blobInfo
|
|
1115
1145
|
if (!blobs) continue
|
|
1116
|
-
|
|
1146
|
+
|
|
1147
|
+
const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
|
|
1148
|
+
|
|
1149
|
+
const response = responses[responseIndex]
|
|
1150
|
+
if (response.field?.options?.autoUploadFiles) { continue } // must have uploaded prior to submission
|
|
1151
|
+
|
|
1117
1152
|
for (const blob of blobs) {
|
|
1118
|
-
|
|
1119
|
-
const { secureName } = await handleUpload(
|
|
1120
|
-
{
|
|
1121
|
-
name: blob.name,
|
|
1122
|
-
size: blob.size,
|
|
1123
|
-
type: blob.type,
|
|
1124
|
-
enduserId,
|
|
1125
|
-
},
|
|
1126
|
-
blob
|
|
1127
|
-
)
|
|
1128
|
-
|
|
1129
|
-
const responseIndex = responses.findIndex(f => f.fieldId === fieldId)
|
|
1130
|
-
|
|
1131
|
-
if (responses[responseIndex].answer.type === 'files') {
|
|
1132
|
-
if (!responses[responseIndex].answer.value) {
|
|
1133
|
-
responses[responseIndex].answer.value = []
|
|
1134
|
-
}
|
|
1135
|
-
(responses[responseIndex].answer.value as any[]).push({
|
|
1136
|
-
...result, type: blob.type, secureName, name: result.name ?? ''
|
|
1137
|
-
})
|
|
1138
|
-
} else {
|
|
1139
|
-
responses[responseIndex].answer.value = { ...result, type: blob.type, secureName, name: result.name ?? '' }
|
|
1140
|
-
}
|
|
1153
|
+
await handleFileUpload(blob, fieldId)
|
|
1141
1154
|
}
|
|
1142
1155
|
}
|
|
1143
1156
|
|
|
@@ -1260,9 +1273,11 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
1260
1273
|
} finally {
|
|
1261
1274
|
setSubmittingStatus(undefined)
|
|
1262
1275
|
}
|
|
1263
|
-
}, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions])
|
|
1276
|
+
}, [accessCode, automationStepId, enduserId, responses, selectedFiles, session, handleUpload, existingResponses, ga4measurementId, rootResponseId, parentResponseId, calendarEventId, goBackURL, logicOptions, handleFileUpload])
|
|
1264
1277
|
|
|
1265
1278
|
const isNextDisabled = useCallback(() => {
|
|
1279
|
+
if (uploadingFiles.length) { return true }
|
|
1280
|
+
|
|
1266
1281
|
if (activeField.children.length === 0) {
|
|
1267
1282
|
return true
|
|
1268
1283
|
}
|
|
@@ -1272,7 +1287,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
1272
1287
|
}
|
|
1273
1288
|
|
|
1274
1289
|
return false
|
|
1275
|
-
}, [activeField, validateField])
|
|
1290
|
+
}, [activeField, validateField, uploadingFiles])
|
|
1276
1291
|
|
|
1277
1292
|
const autoAdvanceRef = useRef(false)
|
|
1278
1293
|
const goToNextField = useCallback(() => {
|
|
@@ -1445,5 +1460,7 @@ export const useTellescopeForm = ({ isPublicForm, form, urlLogicValue, customiza
|
|
|
1445
1460
|
customization,
|
|
1446
1461
|
handleDatabaseSelect,
|
|
1447
1462
|
logicOptions,
|
|
1463
|
+
uploadingFiles, setUploadingFiles,
|
|
1464
|
+
handleFileUpload,
|
|
1448
1465
|
}
|
|
1449
1466
|
}
|
package/src/Forms/inputs.tsx
CHANGED
|
@@ -13,7 +13,7 @@ import LinearProgress from '@mui/material/LinearProgress';
|
|
|
13
13
|
import DatePicker from "react-datepicker";
|
|
14
14
|
import { datepickerCSS } from "./css/react-datepicker" // avoids build issue with RN
|
|
15
15
|
import { CancelIcon, FileBlob, IconButton, LabeledIconButton, LoadingButton, Styled, form_display_text_for_language, isDateString, useProducts, useResolvedSession } from ".."
|
|
16
|
-
import {
|
|
16
|
+
import { CalendarEvent, DatabaseRecord, FormField } from "@tellescope/types-client"
|
|
17
17
|
import { css } from '@emotion/css'
|
|
18
18
|
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
|
|
19
19
|
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
|
|
@@ -24,6 +24,7 @@ import LanguageIcon from '@mui/icons-material/Language';
|
|
|
24
24
|
import { Elements, PaymentElement, useStripe, useElements, EmbeddedCheckout, EmbeddedCheckoutProvider } from '@stripe/react-stripe-js';
|
|
25
25
|
import { loadStripe } from '@stripe/stripe-js';
|
|
26
26
|
import { CheckCircleOutline, Delete, Edit } from "@mui/icons-material"
|
|
27
|
+
import { WYSIWYG } from "./wysiwyg"
|
|
27
28
|
|
|
28
29
|
export const LanguageSelect = ({ value, ...props }: { value: string, onChange: (s: string) => void}) => (
|
|
29
30
|
<Grid container alignItems="center" justifyContent={"center"} wrap="nowrap" spacing={1}>
|
|
@@ -579,7 +580,8 @@ export const InsuranceInput = ({ field, value, onChange, form, responses, enduse
|
|
|
579
580
|
}
|
|
580
581
|
renderInput={(params) => (
|
|
581
582
|
<TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }}
|
|
582
|
-
required={!field.isOptional} size="small"
|
|
583
|
+
required={!field.isOptional} size="small" label={"Insurer"}
|
|
584
|
+
placeholder={field.options?.dataSource === CANVAS_TITLE ? "Search insurer..." : "Insurer"}
|
|
583
585
|
/>
|
|
584
586
|
)}
|
|
585
587
|
/>
|
|
@@ -1185,7 +1187,7 @@ export async function convertHEIC (file: FileBlob | string){
|
|
|
1185
1187
|
};
|
|
1186
1188
|
|
|
1187
1189
|
const value_is_image = (f?: { type?: string })=> f?.type?.includes('image')
|
|
1188
|
-
export const FileInput = ({ value, onChange, field, existingFileName }: FormInputProps<'file'> & { existingFileName?: string }) => {
|
|
1190
|
+
export const FileInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles }: FormInputProps<'file'> & { existingFileName?: string }) => {
|
|
1189
1191
|
const [error, setError] = useState('')
|
|
1190
1192
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
1191
1193
|
onDrop: useCallback(
|
|
@@ -1202,7 +1204,16 @@ export const FileInput = ({ value, onChange, field, existingFileName }: FormInpu
|
|
|
1202
1204
|
|
|
1203
1205
|
setError('')
|
|
1204
1206
|
onChange(file, field.id)
|
|
1205
|
-
|
|
1207
|
+
|
|
1208
|
+
if (field.options?.autoUploadFiles && handleFileUpload) {
|
|
1209
|
+
setUploadingFiles?.(fs => [...fs, { fieldId: field.id }])
|
|
1210
|
+
|
|
1211
|
+
handleFileUpload(file, field.id)
|
|
1212
|
+
.finally(
|
|
1213
|
+
() => setUploadingFiles?.(fs => fs.filter(f => f.fieldId !== field.id))
|
|
1214
|
+
)
|
|
1215
|
+
}
|
|
1216
|
+
}, [onChange, field.options?.validFileTypes, handleFileUpload, setUploadingFiles]
|
|
1206
1217
|
),
|
|
1207
1218
|
})
|
|
1208
1219
|
|
|
@@ -1221,7 +1232,9 @@ export const FileInput = ({ value, onChange, field, existingFileName }: FormInpu
|
|
|
1221
1232
|
}
|
|
1222
1233
|
}, [value])
|
|
1223
1234
|
|
|
1224
|
-
|
|
1235
|
+
if (uploadingFiles?.find(f => f.fieldId === field.id)) {
|
|
1236
|
+
return <LinearProgress />
|
|
1237
|
+
}
|
|
1225
1238
|
return (
|
|
1226
1239
|
<Grid container direction="column">
|
|
1227
1240
|
<Grid container {...getRootProps()} sx={{
|
|
@@ -1287,11 +1300,12 @@ export const safe_create_url = (file: any) => {
|
|
|
1287
1300
|
}
|
|
1288
1301
|
}
|
|
1289
1302
|
|
|
1290
|
-
export const FilesInput = ({ value, onChange, field, existingFileName }: FormInputProps<'files'> & { existingFileName?: string }) => {
|
|
1303
|
+
export const FilesInput = ({ value, onChange, field, existingFileName, uploadingFiles, handleFileUpload, setUploadingFiles }: FormInputProps<'files'> & { existingFileName?: string }) => {
|
|
1291
1304
|
const [error, setError] = useState('')
|
|
1292
1305
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
1293
1306
|
onDrop: useCallback(
|
|
1294
|
-
acceptedFiles => {
|
|
1307
|
+
async acceptedFiles => {
|
|
1308
|
+
setUploadingFiles?.(fs => [...fs, { fieldId: field.id }])
|
|
1295
1309
|
for (const file of acceptedFiles) {
|
|
1296
1310
|
if (field.options?.validFileTypes?.length) {
|
|
1297
1311
|
const match = field.options.validFileTypes.find(t => file.type.includes(t.toLowerCase()))
|
|
@@ -1299,11 +1313,16 @@ export const FilesInput = ({ value, onChange, field, existingFileName }: FormInp
|
|
|
1299
1313
|
return setError(`File must have type: ${field.options.validFileTypes.join(', ')}`)
|
|
1300
1314
|
}
|
|
1301
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
if (field.options?.autoUploadFiles && handleFileUpload) {
|
|
1318
|
+
await handleFileUpload(file, field.id).catch(console.error)
|
|
1319
|
+
}
|
|
1302
1320
|
}
|
|
1321
|
+
setUploadingFiles?.(fs => fs.filter(f => f.fieldId !== field.id))
|
|
1303
1322
|
|
|
1304
1323
|
setError('')
|
|
1305
1324
|
onChange([...(value ?? []), ...acceptedFiles], field.id)
|
|
1306
|
-
}, [onChange, value, field.options?.validFileTypes]
|
|
1325
|
+
}, [onChange, value, field.options?.validFileTypes, handleFileUpload, setUploadingFiles]
|
|
1307
1326
|
),
|
|
1308
1327
|
})
|
|
1309
1328
|
|
|
@@ -1313,6 +1332,9 @@ export const FilesInput = ({ value, onChange, field, existingFileName }: FormInp
|
|
|
1313
1332
|
})
|
|
1314
1333
|
), [value])
|
|
1315
1334
|
|
|
1335
|
+
if (uploadingFiles?.find(f => f.fieldId === field.id)) {
|
|
1336
|
+
return <LinearProgress />
|
|
1337
|
+
}
|
|
1316
1338
|
return (
|
|
1317
1339
|
<Grid container direction="column">
|
|
1318
1340
|
<Grid container {...getRootProps()} sx={{
|
|
@@ -2828,6 +2850,9 @@ export const AppointmentBookingInput = ({ formResponseId, field, value, onChange
|
|
|
2828
2850
|
field.options.userTags
|
|
2829
2851
|
.flatMap(t => {
|
|
2830
2852
|
// set dynamic tags if found
|
|
2853
|
+
if (t === '{{logic}}') {
|
|
2854
|
+
return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
|
|
2855
|
+
}
|
|
2831
2856
|
if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
|
|
2832
2857
|
const fieldId = t.replace('{{field.', '').replace(".value}}", '')
|
|
2833
2858
|
|
|
@@ -2852,6 +2877,9 @@ export const AppointmentBookingInput = ({ formResponseId, field, value, onChange
|
|
|
2852
2877
|
field.options.userFilterTags
|
|
2853
2878
|
.flatMap(t => {
|
|
2854
2879
|
// set dynamic tags if found
|
|
2880
|
+
if (t === '{{logic}}') {
|
|
2881
|
+
return new URL(window.location.href).searchParams.get('logic') || '{{logic}}'
|
|
2882
|
+
}
|
|
2855
2883
|
if (t.startsWith("{{field.") && t.endsWith(".value}}")) {
|
|
2856
2884
|
const fieldId = t.replace('{{field.', '').replace(".value}}", '')
|
|
2857
2885
|
|
|
@@ -3277,4 +3305,8 @@ export const ConditionsInput = ({ goToNextField, goToPreviousField, field, value
|
|
|
3277
3305
|
}
|
|
3278
3306
|
/>
|
|
3279
3307
|
)
|
|
3280
|
-
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
export const RichTextInput = ({ field, value, onChange }: FormInputProps<'Rich Text'>) => (
|
|
3311
|
+
<WYSIWYG initialHTML={value} onChange={v => onChange(v, field.id)} style={{ width: '100%' }} editorStyle={{ width: '100%' }} />
|
|
3312
|
+
)
|
package/src/Forms/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { DatabaseRecord, Enduser, Form, FormField } from "@tellescope/types-clie
|
|
|
3
3
|
import { FileBlob, TreeNode } from "@tellescope/types-utilities";
|
|
4
4
|
import { JSXElementConstructor } from "react";
|
|
5
5
|
import { Response } from "./hooks";
|
|
6
|
+
import { FileUploadHandler } from "../inputs";
|
|
6
7
|
|
|
7
8
|
export type FormFieldNode = TreeNode<FormField>
|
|
8
9
|
|
|
@@ -32,6 +33,9 @@ export interface FormInputProps<K extends keyof AnswerForType> {
|
|
|
32
33
|
groupInsance?: string,
|
|
33
34
|
disabled?: boolean,
|
|
34
35
|
isSinglePage?: boolean,
|
|
36
|
+
handleFileUpload?: (blob: FileBlob, fieldId: string) => Promise<any>,
|
|
37
|
+
uploadingFiles?: { fieldId: string }[]
|
|
38
|
+
setUploadingFiles?: React.Dispatch<React.SetStateAction<{ fieldId: string }[]>>,
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export type FormInputs = {
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Editor } from 'react-draft-wysiwyg';
|
|
3
|
+
import draftToHtml from 'draftjs-to-html';
|
|
4
|
+
import htmlToDraft from 'html-to-draftjs';
|
|
5
|
+
import { EditorState, ContentState, convertToRaw } from 'draft-js';
|
|
6
|
+
import { Paper } from '@mui/material';
|
|
7
|
+
import { Styled } from '../mui';
|
|
8
|
+
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
|
|
9
|
+
|
|
10
|
+
const getToolbar = ({ hideEmoji } : { hideEmoji?: boolean }) => ({
|
|
11
|
+
// hide image and embedded in favor of custom sections
|
|
12
|
+
options: [
|
|
13
|
+
// 'inline',
|
|
14
|
+
// 'blockType',
|
|
15
|
+
'fontSize',
|
|
16
|
+
// 'fontFamily',
|
|
17
|
+
'list',
|
|
18
|
+
'textAlign',
|
|
19
|
+
// 'colorPicker',
|
|
20
|
+
'link',
|
|
21
|
+
...(hideEmoji ? [] : ['emoji']),
|
|
22
|
+
// 'remove',
|
|
23
|
+
// 'history',
|
|
24
|
+
// 'image',
|
|
25
|
+
// 'embedded',
|
|
26
|
+
],
|
|
27
|
+
inline: {
|
|
28
|
+
inDropdown: false,
|
|
29
|
+
className: undefined,
|
|
30
|
+
component: undefined,
|
|
31
|
+
dropdownClassName: undefined,
|
|
32
|
+
options: ['bold', 'italic', 'underline', 'strikethrough', 'monospace', 'superscript', 'subscript'],
|
|
33
|
+
// bold: { icon: bold, className: undefined },
|
|
34
|
+
// italic: { icon: italic, className: undefined },
|
|
35
|
+
// underline: { icon: underline, className: undefined },
|
|
36
|
+
// strikethrough: { icon: strikethrough, className: undefined },
|
|
37
|
+
// monospace: { icon: monospace, className: undefined },
|
|
38
|
+
// superscript: { icon: superscript, className: undefined },
|
|
39
|
+
// subscript: { icon: subscript, className: undefined },
|
|
40
|
+
},
|
|
41
|
+
blockType: {
|
|
42
|
+
inDropdown: true,
|
|
43
|
+
options: ['Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote', 'Code'],
|
|
44
|
+
className: undefined,
|
|
45
|
+
component: undefined,
|
|
46
|
+
dropdownClassName: undefined,
|
|
47
|
+
},
|
|
48
|
+
fontSize: {
|
|
49
|
+
// icon: fontSize,
|
|
50
|
+
options: [8, 9, 10, 11, 12, 14, 16, 18, 24, 30, 36, 48, 60, 72, 96],
|
|
51
|
+
className: undefined,
|
|
52
|
+
component: undefined,
|
|
53
|
+
dropdownClassName: undefined,
|
|
54
|
+
},
|
|
55
|
+
fontFamily: {
|
|
56
|
+
options: ['Arial', 'Georgia', 'Impact', 'Tahoma', 'Times New Roman', 'Verdana'],
|
|
57
|
+
className: undefined,
|
|
58
|
+
component: undefined,
|
|
59
|
+
dropdownClassName: undefined,
|
|
60
|
+
},
|
|
61
|
+
list: {
|
|
62
|
+
inDropdown: false,
|
|
63
|
+
className: undefined,
|
|
64
|
+
component: undefined,
|
|
65
|
+
dropdownClassName: undefined,
|
|
66
|
+
options: ['unordered', 'ordered', 'indent', 'outdent'],
|
|
67
|
+
// unordered: { icon: unordered, className: undefined },
|
|
68
|
+
// ordered: { icon: ordered, className: undefined },
|
|
69
|
+
// indent: { icon: indent, className: undefined },
|
|
70
|
+
// outdent: { icon: outdent, className: undefined },
|
|
71
|
+
},
|
|
72
|
+
textAlign: {
|
|
73
|
+
inDropdown: false,
|
|
74
|
+
className: undefined,
|
|
75
|
+
component: undefined,
|
|
76
|
+
dropdownClassName: undefined,
|
|
77
|
+
options: ['left', 'center', 'right', 'justify'],
|
|
78
|
+
// left: { icon: left, className: undefined },
|
|
79
|
+
// center: { icon: center, className: undefined },
|
|
80
|
+
// right: { icon: right, className: undefined },
|
|
81
|
+
// justify: { icon: justify, className: undefined },
|
|
82
|
+
},
|
|
83
|
+
colorPicker: {
|
|
84
|
+
// icon: color,
|
|
85
|
+
className: undefined,
|
|
86
|
+
component: undefined,
|
|
87
|
+
popupClassName: undefined,
|
|
88
|
+
colors: ['rgb(97,189,109)', 'rgb(26,188,156)', 'rgb(84,172,210)', 'rgb(44,130,201)',
|
|
89
|
+
'rgb(147,101,184)', 'rgb(71,85,119)', 'rgb(204,204,204)', 'rgb(65,168,95)', 'rgb(0,168,133)',
|
|
90
|
+
'rgb(61,142,185)', 'rgb(41,105,176)', 'rgb(85,57,130)', 'rgb(40,50,78)', 'rgb(0,0,0)',
|
|
91
|
+
'rgb(247,218,100)', 'rgb(251,160,38)', 'rgb(235,107,86)', 'rgb(226,80,65)', 'rgb(163,143,132)',
|
|
92
|
+
'rgb(239,239,239)', 'rgb(255,255,255)', 'rgb(250,197,28)', 'rgb(243,121,52)', 'rgb(209,72,65)',
|
|
93
|
+
'rgb(184,49,47)', 'rgb(124,112,107)', 'rgb(209,213,216)'],
|
|
94
|
+
},
|
|
95
|
+
link: {
|
|
96
|
+
inDropdown: false,
|
|
97
|
+
className: undefined,
|
|
98
|
+
component: undefined,
|
|
99
|
+
popupClassName: undefined,
|
|
100
|
+
dropdownClassName: undefined,
|
|
101
|
+
showOpenOptionOnHover: true,
|
|
102
|
+
defaultTargetOption: '_blank',
|
|
103
|
+
options: ['link', 'unlink'],
|
|
104
|
+
// link: { icon: link, className: undefined },
|
|
105
|
+
// unlink: { icon: unlink, className: undefined },
|
|
106
|
+
linkCallback: undefined
|
|
107
|
+
},
|
|
108
|
+
emoji: {
|
|
109
|
+
// icon: emoji,
|
|
110
|
+
className: undefined,
|
|
111
|
+
component: undefined,
|
|
112
|
+
popupClassName: undefined,
|
|
113
|
+
emojis: [
|
|
114
|
+
'😀', '😁', '😂', '😃', '😉', '😋', '😎', '😍', '😗', '🤗', '🤔', '😣', '😫', '😴', '😌', '🤓',
|
|
115
|
+
'😛', '😜', '😠', '😇', '😷', '😈', '👻', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '🙈',
|
|
116
|
+
'🙉', '🙊', '👼', '👮', '🕵', '💂', '👳', '🎅', '👸', '👰', '👲', '🙍', '🙇', '🚶', '🏃', '💃',
|
|
117
|
+
'⛷', '🏂', '🏌', '🏄', '🚣', '🏊', '⛹', '🏋', '🚴', '👫', '💪', '👈', '👉', '👉', '👆', '🖕',
|
|
118
|
+
'👇', '🖖', '🤘', '🖐', '👌', '👍', '👎', '✊', '👊', '👏', '🙌', '🙏', '🐵', '🐶', '🐇', '🐥',
|
|
119
|
+
'🐸', '🐌', '🐛', '🐜', '🐝', '🍉', '🍄', '🍔', '🍤', '🍨', '🍪', '🎂', '🍰', '🍾', '🍷', '🍸',
|
|
120
|
+
'🍺', '🌍', '🚑', '⏰', '🌙', '🌝', '🌞', '⭐', '🌟', '🌠', '🌨', '🌩', '⛄', '🔥', '🎄', '🎈',
|
|
121
|
+
'🎉', '🎊', '🎁', '🎗', '🏀', '🏈', '🎲', '🔇', '🔈', '📣', '🔔', '🎵', '🎷', '💰', '🖊', '📅',
|
|
122
|
+
'✅', '❎', '💯',
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
embedded: {
|
|
126
|
+
// icon: embedded,
|
|
127
|
+
className: undefined,
|
|
128
|
+
component: undefined,
|
|
129
|
+
popupClassName: undefined,
|
|
130
|
+
embedCallback: undefined,
|
|
131
|
+
defaultSize: {
|
|
132
|
+
height: 'auto',
|
|
133
|
+
width: 'auto',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
image: {
|
|
137
|
+
// icon: image,
|
|
138
|
+
className: undefined,
|
|
139
|
+
component: undefined,
|
|
140
|
+
popupClassName: undefined,
|
|
141
|
+
urlEnabled: true,
|
|
142
|
+
uploadEnabled: true,
|
|
143
|
+
alignmentEnabled: true,
|
|
144
|
+
// uploadCallback,
|
|
145
|
+
previewImage: true,
|
|
146
|
+
inputAccept: 'image/gif,image/jpeg,image/jpg,image/png,image/svg',
|
|
147
|
+
alt: { present: false, mandatory: false },
|
|
148
|
+
defaultSize: {
|
|
149
|
+
height: 'auto',
|
|
150
|
+
width: 'auto',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
remove: {
|
|
154
|
+
// icon: eraser,
|
|
155
|
+
className: undefined, component: undefined
|
|
156
|
+
},
|
|
157
|
+
history: {
|
|
158
|
+
inDropdown: false,
|
|
159
|
+
className: undefined,
|
|
160
|
+
component: undefined,
|
|
161
|
+
dropdownClassName: undefined,
|
|
162
|
+
options: ['undo', 'redo'],
|
|
163
|
+
// undo: { icon: undo, className: undefined },
|
|
164
|
+
// redo: { icon: redo, className: undefined },
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
export const WYSIWYG = ({ updateHtml, initialHTML: _initialHTML='', autoFocus, onChange, style, editorStyle, hideEmoji } : {
|
|
171
|
+
initialHTML?: string, onChange: (html: string) => void, autoFocus?: boolean,
|
|
172
|
+
editorStyle?: React.CSSProperties,
|
|
173
|
+
hideEmoji?: boolean,
|
|
174
|
+
updateHtml?: string,
|
|
175
|
+
} & Styled) => {
|
|
176
|
+
const trimmed = _initialHTML.trim()
|
|
177
|
+
const initialHTML = (
|
|
178
|
+
trimmed.startsWith('<p>') && trimmed.endsWith('</p>')
|
|
179
|
+
? trimmed
|
|
180
|
+
: `<p>${trimmed}</p>`
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const [editorState, setEditorState] = useState(EditorState.createWithContent(
|
|
184
|
+
ContentState.createFromBlockArray(htmlToDraft(initialHTML).contentBlocks)
|
|
185
|
+
))
|
|
186
|
+
const editorStateRef = useRef(editorState)
|
|
187
|
+
|
|
188
|
+
const editorRef: React.MutableRefObject<Editor | null> = useRef(null)
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!updateHtml) return
|
|
192
|
+
|
|
193
|
+
setEditorState(
|
|
194
|
+
EditorState.createWithContent(
|
|
195
|
+
ContentState.createFromBlockArray(htmlToDraft(updateHtml).contentBlocks)
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
}, [updateHtml])
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!autoFocus) return
|
|
202
|
+
|
|
203
|
+
if (editorRef.current) {
|
|
204
|
+
editorRef.current?.focusEditor?.()
|
|
205
|
+
setEditorState(es => EditorState.moveFocusToEnd(es))
|
|
206
|
+
}
|
|
207
|
+
}, [editorRef, autoFocus])
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (editorStateRef.current === editorState) return
|
|
211
|
+
editorStateRef.current = editorState
|
|
212
|
+
|
|
213
|
+
// unbounce a bit for perf
|
|
214
|
+
const t = setTimeout(() => (
|
|
215
|
+
onChange(draftToHtml(convertToRaw(editorState.getCurrentContent())))
|
|
216
|
+
), 99)
|
|
217
|
+
|
|
218
|
+
return () => { clearTimeout(t) }
|
|
219
|
+
}, [onChange, editorState])
|
|
220
|
+
|
|
221
|
+
const toolbar = useMemo(() => getToolbar({ hideEmoji }), [hideEmoji])
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Paper sx={{ padding: 1 }} style={style}>
|
|
225
|
+
<Editor spellCheck ref={editorRef} editorStyle={editorStyle}
|
|
226
|
+
editorState={editorState}
|
|
227
|
+
wrapperClassName="demo-wrapper"
|
|
228
|
+
editorClassName="demo-editor"
|
|
229
|
+
onEditorStateChange={setEditorState}
|
|
230
|
+
toolbar={toolbar}
|
|
231
|
+
/>
|
|
232
|
+
</Paper>
|
|
233
|
+
)
|
|
234
|
+
}
|