Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.

Commit cb1aa00

Browse files
committed
refactor: enhance field value handling and improve form validation logic
1 parent 6fd6c2d commit cb1aa00

4 files changed

Lines changed: 133 additions & 141 deletions

File tree

src/defaults/ConsentCheckbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const ConsentCheckbox: React.FC<FieldComponentProps> = ({
1414
>
1515
<input
1616
type="checkbox"
17-
checked={value || false}
17+
checked={Boolean(value)}
1818
onChange={(e) => onChange(e.target.checked)}
1919
disabled={isDisabled}
2020
/>

src/defaults/SignatureInput.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ export const SignatureInput: React.FC<FieldComponentProps> = ({
77
isDisabled,
88
label,
99
}) => {
10+
const stringValue = value == null ? "" : String(value);
1011
return (
1112
<div className="superdoc-esign-signature-input">
1213
{label && <label>{label}</label>}
1314
<input
1415
type="text"
15-
value={value || ""}
16+
value={stringValue}
1617
onChange={(e) => onChange(e.target.value)}
1718
disabled={isDisabled}
1819
placeholder="Type your full name"

src/index.tsx

Lines changed: 120 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
export * from "./types";
1919
export { SignatureInput, ConsentCheckbox };
2020

21+
type Editor = NonNullable<SuperDoc["activeEditor"]>;
22+
2123
const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
2224
(props, ref) => {
2325
const {
@@ -41,7 +43,9 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
4143
const [scrolled, setScrolled] = useState(
4244
!document.displayOptions?.scrollRequired,
4345
);
44-
const [fieldValues, setFieldValues] = useState<Map<string, any>>(new Map());
46+
const [fieldValues, setFieldValues] = useState<
47+
Map<string, Types.FieldValue>
48+
>(new Map());
4549
const [isValid, setIsValid] = useState(false);
4650
const [isSubmitting, setIsSubmitting] = useState(false);
4751
const [auditTrail, setAuditTrail] = useState<Types.AuditEvent[]>([]);
@@ -64,13 +68,76 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
6468
editor.commands.updateStructuredContentById(field.id, {
6569
text: textValue,
6670
});
67-
} else if (field.alias) {
71+
}
72+
if (field.alias) {
6873
editor.commands.updateStructuredContentByAlias(field.alias, {
6974
text: textValue,
7075
});
7176
}
7277
}, []);
7378

79+
// Discover fields in document and apply initial values
80+
const discoverAndApplyFields = useCallback(
81+
(editor: Editor) => {
82+
if (!editor) return;
83+
84+
const tags =
85+
editor.helpers.structuredContentCommands.getStructuredContentTags(
86+
editor.state,
87+
);
88+
89+
// Build initial values map
90+
const configValues = new Map<string, Types.FieldValue>();
91+
92+
fieldsRef.current.document?.forEach((f) => {
93+
if (f.id) configValues.set(f.id, f.value);
94+
if (f.alias) configValues.set(f.alias, f.value);
95+
});
96+
97+
fieldsRef.current.signer?.forEach((f) => {
98+
if (f.value !== undefined) {
99+
configValues.set(f.id, f.value);
100+
if (f.alias) configValues.set(f.alias, f.value);
101+
}
102+
});
103+
104+
// Discover fields
105+
const discovered: Types.FieldInfo[] = tags
106+
.map(({ node }: any) => ({
107+
id: node.attrs.id,
108+
alias: node.attrs.alias,
109+
label: node.attrs.alias,
110+
value:
111+
configValues.get(node.attrs.id) ??
112+
configValues.get(node.attrs.alias) ??
113+
node.textContent ??
114+
"",
115+
}))
116+
.filter((f: Types.FieldInfo) => f.id || f.alias);
117+
118+
if (discovered.length > 0) {
119+
onFieldsDiscovered?.(discovered);
120+
121+
// Apply initial values from both document and signer fields
122+
const allFields = [
123+
...(fieldsRef.current.document || []),
124+
...(fieldsRef.current.signer || []),
125+
];
126+
127+
allFields
128+
.filter((field) => field.value !== undefined)
129+
.forEach((field) =>
130+
updateFieldInDocument({
131+
id: field.id,
132+
alias: field.alias,
133+
value: field.value!,
134+
}),
135+
);
136+
}
137+
},
138+
[onFieldsDiscovered, updateFieldInDocument],
139+
);
140+
74141
// Add audit event
75142
const addAuditEvent = (event: Omit<Types.AuditEvent, "timestamp">) => {
76143
const auditEvent: Types.AuditEvent = {
@@ -92,80 +159,9 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
92159
document: document.source,
93160
documentMode: "viewing",
94161
onReady: () => {
95-
// Inline field discovery to avoid dependency issues
96162
if (instance.activeEditor) {
97-
const editor = instance.activeEditor;
98-
const tags =
99-
editor.helpers.structuredContentCommands.getStructuredContentTags(
100-
editor.state,
101-
);
102-
103-
// Create a map of initial values
104-
const configValues = new Map<string, any>();
105-
106-
// Add document field values
107-
fieldsRef.current.document?.forEach((f) => {
108-
if (f.id) configValues.set(f.id, f.value);
109-
if (f.alias) configValues.set(f.alias, f.value);
110-
});
111-
112-
// Add signer field initial values
113-
fieldsRef.current.signer?.forEach((f) => {
114-
if (f.value !== undefined) {
115-
configValues.set(f.id, f.value);
116-
if (f.alias) configValues.set(f.alias, f.value);
117-
}
118-
});
119-
120-
// Discover fields in document
121-
const discovered: Types.FieldInfo[] = tags
122-
.map(({ node }: any) => ({
123-
id: node.attrs.id,
124-
alias: node.attrs.alias,
125-
label: node.attrs.alias,
126-
value:
127-
configValues.get(node.attrs.id) ??
128-
configValues.get(node.attrs.alias) ??
129-
node.textContent ??
130-
"",
131-
}))
132-
.filter((f: Types.FieldInfo) => f.id || f.alias); // Keep fields with at least id or alias
133-
134-
if (discovered.length > 0) {
135-
onFieldsDiscovered?.(discovered);
136-
137-
// Apply initial values to document
138-
fieldsRef.current.document?.forEach((field) => {
139-
if (
140-
field.value !== undefined &&
141-
superdocRef.current?.activeEditor
142-
) {
143-
const textValue = String(field.value);
144-
if (field.id) {
145-
editor.commands.updateStructuredContentById(field.id, {
146-
text: textValue,
147-
});
148-
} else if (field.alias) {
149-
editor.commands.updateStructuredContentByAlias(
150-
field.alias,
151-
{ text: textValue },
152-
);
153-
}
154-
}
155-
});
156-
157-
// Apply signer field initial values if any
158-
fieldsRef.current.signer?.forEach((field) => {
159-
if (field.value !== undefined) {
160-
const textValue = String(field.value);
161-
editor.commands.updateStructuredContentById(field.id, {
162-
text: textValue,
163-
});
164-
}
165-
});
166-
}
163+
discoverAndApplyFields(instance.activeEditor);
167164
}
168-
169165
addAuditEvent({ type: "ready" });
170166
setIsReady(true);
171167
},
@@ -182,7 +178,7 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
182178
superdocRef.current = null;
183179
}
184180
};
185-
}, [document.source, document.mode, onFieldsDiscovered]); // Removed function dependency
181+
}, [document.source, document.mode, discoverAndApplyFields]);
186182

187183
// Track scroll manually
188184
useEffect(() => {
@@ -213,7 +209,7 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
213209

214210
// Handle field change from signer inputs
215211
const handleFieldChange = useCallback(
216-
(fieldId: string, value: any) => {
212+
(fieldId: string, value: Types.FieldValue) => {
217213
setFieldValues((prev) => {
218214
const previousValue = prev.get(fieldId);
219215
const newMap = new Map(prev);
@@ -250,47 +246,34 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
250246
[onFieldChange, updateFieldInDocument],
251247
);
252248

253-
// Validate form
254-
useEffect(() => {
255-
const checkValidity = () => {
256-
// Check scroll requirement
257-
if (document.displayOptions?.scrollRequired && !scrolled) {
258-
return false;
259-
}
260-
261-
// Check required fields
262-
const signerFields = fields.signer || [];
263-
for (const field of signerFields) {
264-
if (field.validation?.required) {
265-
const value = fieldValues.get(field.id);
266-
if (!value || (typeof value === "string" && !value.trim())) {
267-
return false;
268-
}
269-
}
270-
}
249+
// Check if form is valid
250+
const checkIsValid = useCallback((): boolean => {
251+
// Check scroll requirement
252+
if (document.displayOptions?.scrollRequired && !scrolled) {
253+
return false;
254+
}
271255

272-
return true;
273-
};
256+
// Check required fields
257+
return (fields.signer || []).every((field) => {
258+
if (!field.validation?.required) return true;
259+
const value = fieldValues.get(field.id);
260+
return value && (typeof value !== "string" || value.trim());
261+
});
262+
}, [scrolled, fields.signer, fieldValues, document.displayOptions]);
274263

275-
const valid = checkValidity();
264+
// Validate form and notify state changes
265+
useEffect(() => {
266+
const valid = checkIsValid();
276267
setIsValid(valid);
277268

278-
// Notify state change
279269
const state: Types.SigningState = {
280270
scrolled,
281271
fields: fieldValues,
282272
isValid: valid,
283273
isSubmitting,
284274
};
285275
onStateChange?.(state);
286-
}, [
287-
scrolled,
288-
fieldValues,
289-
fields.signer,
290-
document.displayOptions,
291-
isSubmitting,
292-
onStateChange,
293-
]);
276+
}, [scrolled, fieldValues, isSubmitting, checkIsValid, onStateChange]);
294277

295278
// Handle download
296279
const handleDownload = useCallback(async () => {
@@ -330,7 +313,7 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
330313
signerFields: (fields.signer || []).map((field) => ({
331314
id: field.id,
332315
alias: field.alias,
333-
value: fieldValues.get(field.id),
316+
value: fieldValues.get(field.id) ?? null,
334317
})),
335318
isFullyCompleted: isValid,
336319
};
@@ -358,9 +341,9 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
358341
return (
359342
<Component
360343
key={field.id}
361-
value={fieldValues.get(field.id)}
344+
value={fieldValues.get(field.id) ?? null}
362345
onChange={(value) => handleFieldChange(field.id, value)}
363-
isDisabled={isDisabled} //|| Boolean(document.displayOptions?.scrollRequired && !scrolled)
346+
isDisabled={isDisabled}
364347
label={field.label}
365348
/>
366349
);
@@ -380,10 +363,33 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
380363
}
381364
};
382365

383-
// Create button components
384-
const DownloadButton =
385-
download?.component || createDownloadButton(download);
386-
const SubmitButton = submit?.component || createSubmitButton(submit);
366+
// Render action buttons
367+
const renderActionButtons = () => {
368+
const DownloadButton =
369+
download?.component || createDownloadButton(download);
370+
const SubmitButton = submit?.component || createSubmitButton(submit);
371+
372+
return (
373+
<div
374+
className="superdoc-esign-actions"
375+
style={{ display: "flex", gap: "10px" }}
376+
>
377+
{document.mode !== "download" && (
378+
<SubmitButton
379+
onClick={handleSubmit}
380+
isValid={isValid}
381+
isDisabled={isDisabled}
382+
isSubmitting={isSubmitting}
383+
/>
384+
)}
385+
<DownloadButton
386+
onClick={handleDownload}
387+
fileName={download?.fileName}
388+
isDisabled={isDisabled}
389+
/>
390+
</div>
391+
);
392+
};
387393

388394
// Expose methods via ref
389395
useImperativeHandle(ref, () => ({
@@ -428,24 +434,7 @@ const SuperDocESign = forwardRef<any, Types.SuperDocESignProps>(
428434
)}
429435

430436
{/* Action buttons */}
431-
<div
432-
className="superdoc-esign-actions"
433-
style={{ display: "flex", gap: "10px" }}
434-
>
435-
{document.mode !== "download" && (
436-
<SubmitButton
437-
onClick={handleSubmit}
438-
isValid={isValid}
439-
isDisabled={isDisabled}
440-
isSubmitting={isSubmitting}
441-
/>
442-
)}
443-
<DownloadButton
444-
onClick={handleDownload}
445-
fileName={download?.fileName}
446-
isDisabled={isDisabled}
447-
/>
448-
</div>
437+
{renderActionButtons()}
449438
</div>
450439
</div>
451440
);

0 commit comments

Comments
 (0)