Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-canvas-mock": "^2.5.8",
"jest-environment-jsdom": "^29.7.0",
"jest-transform-css": "^6.0.1",
"jsdom": "^22.1.0",
Expand Down
45 changes: 41 additions & 4 deletions src/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
import { CSSProperties, useEffect, useRef, useState } from 'react';
import { EventEmitter, Form as FormClass, Webform, Utils } from '@formio/js';
import { EventEmitter, Form as FormClass, Webform, Utils, Formio } from '@formio/js';
import { Component, Form as CoreFormType } from '@formio/core';
import structuredClone from '@ungap/structured-clone';

// Patch SignatureComponent to propagate the modified flag correctly when drawing
if (
Formio &&
Formio.Components &&
Formio.Components.components &&
Formio.Components.components.signature
) {
const SignatureComponent = Formio.Components.components.signature;
const originalSetValue = SignatureComponent.prototype.setValue;
SignatureComponent.prototype.setValue = function (value: any, flags: any = {}) {
const originalTriggerChange = this.triggerChange;
if (originalTriggerChange) {
this.triggerChange = (...args: any[]) => {
const finalArgs = args.length > 0 ? args : [flags];
return originalTriggerChange.apply(this, finalArgs);
};
}
try {
return originalSetValue.call(this, value, flags);
} finally {
if (originalTriggerChange) {
this.triggerChange = originalTriggerChange;
}
}
};
}






export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> &
Required<Pick<T, K>>;

Expand Down Expand Up @@ -254,6 +286,11 @@ export const Form = (props: FormProps) => {
const currentFormJson = useRef<FormType | null>(null);
const { formConstructor, formSource, formReadyCallback } =
getEffectiveProps(props);
const formReadyCallbackRef = useRef(formReadyCallback);
useEffect(() => {
formReadyCallbackRef.current = formReadyCallback;
}, [formReadyCallback]);

const {
src,
form,
Expand Down Expand Up @@ -330,8 +367,8 @@ export const Form = (props: FormProps) => {
}
}

if (formReadyCallback) {
formReadyCallback(instance);
if (formReadyCallbackRef.current) {
formReadyCallbackRef.current(instance);
}
setFormInstance((prevInstance) => {
if (prevInstance) {
Expand All @@ -345,7 +382,7 @@ export const Form = (props: FormProps) => {
};

createInstance();
}, [formConstructor, formReadyCallback, formSource, options, url]);
}, [formConstructor, formSource, options, url]);

useEffect(() => {
let onAnyHandler = null;
Expand Down
63 changes: 63 additions & 0 deletions src/components/__tests__/Signature.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'jest-canvas-mock';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';

import { Form } from '../Form';

class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}

globalThis.ResizeObserver = ResizeObserver;

const signatureForm = {
display: 'form' as const,
components: [
{
label: 'Signature',
key: 'signature',
type: 'signature',
input: true,
},
],
};

test('signature change is marked as modified', async () => {
let modified: boolean | undefined;

const formReady = new Promise<any>((resolve) => {
render(
<Form
src={signatureForm}
onChange={(_value, _flags, isModified) => {
modified = isModified;
}}
onFormReady={(form) => {
resolve(form);
}}
/>,
);
});

const form = await formReady;
const signature = form.components[0];

signature.signaturePad.toDataURL = jest.fn(
() => 'data:image/png;base64,test',
);

signature.signaturePad.dispatchEvent(
new CustomEvent('endStroke'),
);

expect(
document.querySelector('.signature-pad'),
).toBeInTheDocument();

await new Promise((resolve) => setTimeout(resolve, 1000));

expect(modified).toBe(true);
});