Compare commits

..

2 Commits

Author SHA1 Message Date
Cursor Agent
b22c839d3c chore: update pnpm-lock.yaml after dependency installation 2026-01-21 02:55:47 +00:00
Cursor Agent
d44cc1e27e fix: add safe requestSubmit wrapper for Mobile Safari 15.5 compatibility
- Add safeRequestSubmit helper function with null checks and browser compatibility fallback
- Replace all form.requestSubmit() calls with safeRequestSubmit(form)
- Dispatch submit event as fallback for browsers without requestSubmit support
- Fixes TypeError: t.requestSubmit is not a function on /s/:surveyId route
2026-01-21 02:53:10 +00:00
6 changed files with 43 additions and 110 deletions

View File

@@ -19,7 +19,6 @@ import { TwoFactorBackup } from "@/modules/ee/two-factor-auth/components/two-fac
import { Button } from "@/modules/ui/components/button";
import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/components/form";
import { PasswordInput } from "@/modules/ui/components/password-input";
import { safeFormRequestSubmit } from "@/modules/ui/lib/utils";
const ZLoginForm = z.object({
email: z.string().email(),
@@ -237,7 +236,7 @@ export const LoginForm = ({
// Add a slight delay before focusing the input field to ensure it's visible
setTimeout(() => emailRef.current?.focus(), 100);
} else if (formRef.current) {
safeFormRequestSubmit(formRef.current);
formRef.current.requestSubmit();
}
}}
className="relative w-full justify-center"

View File

@@ -4,27 +4,3 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Safely requests form submission with validation.
* Provides a fallback for browsers that don't support requestSubmit() (iOS Safari < 16.0).
* @param form The form element to submit
*/
export function safeFormRequestSubmit(form: HTMLFormElement): void {
// Check if requestSubmit is supported (iOS Safari 16.0+, all modern browsers)
if (typeof form.requestSubmit === "function") {
form.requestSubmit();
} else {
// Fallback for older browsers (iOS Safari < 16.0)
// reportValidity() triggers native validation UI
if (!form.reportValidity()) {
return;
}
// Dispatch submit event manually to trigger form submission handlers
const submitEvent = new Event("submit", {
bubbles: true,
cancelable: true,
});
form.dispatchEvent(submitEvent);
}
}

View File

@@ -14,7 +14,42 @@ import { SubmitButton } from "@/components/buttons/submit-button";
import { ElementConditional } from "@/components/general/element-conditional";
import { ScrollableContainer } from "@/components/wrappers/scrollable-container";
import { getLocalizedValue } from "@/lib/i18n";
import { cn, safeFormRequestSubmit } from "@/lib/utils";
import { cn } from "@/lib/utils";
/**
* Safely calls requestSubmit on a form element with fallback for browsers
* that don't support it (e.g., Mobile Safari 15.5)
*/
const safeRequestSubmit = (form: HTMLFormElement | null | undefined): void => {
if (!form) {
return;
}
// Check if requestSubmit is available
if (typeof form.requestSubmit === "function") {
try {
form.requestSubmit();
} catch (error) {
// Fallback if requestSubmit throws an error
console.warn("[Formbricks] form.requestSubmit() failed, using fallback:", error);
dispatchSubmitEvent(form);
}
} else {
// Fallback for browsers that don't support requestSubmit
dispatchSubmitEvent(form);
}
};
/**
* Fallback method to trigger form validation by dispatching a submit event
*/
const dispatchSubmitEvent = (form: HTMLFormElement): void => {
const submitEvent = new Event("submit", {
bubbles: true,
cancelable: true,
});
form.dispatchEvent(submitEvent);
};
interface BlockConditionalProps {
block: TSurveyBlock;
@@ -141,7 +176,7 @@ export function BlockConditional({
response.length < rankingElement.choices.length);
if (hasIncompleteRanking) {
safeFormRequestSubmit(form);
safeRequestSubmit(form);
return false;
}
return true;
@@ -174,7 +209,7 @@ export function BlockConditional({
element.type === TSurveyElementTypeEnum.ContactInfo
) {
if (!form.checkValidity()) {
safeFormRequestSubmit(form);
safeRequestSubmit(form);
return false;
}
return true;
@@ -191,14 +226,14 @@ export function BlockConditional({
response &&
hasUnansweredRows(response, element)
) {
safeFormRequestSubmit(form);
safeRequestSubmit(form);
return false;
}
// For other element types, check if required fields are empty
// CTA elements should not block navigation even if marked required (as they are informational)
if (element.type !== TSurveyElementTypeEnum.CTA && element.required && isEmptyResponse(response)) {
safeFormRequestSubmit(form);
safeRequestSubmit(form);
return false;
}
@@ -229,9 +264,7 @@ export function BlockConditional({
// Call each form's submit method to trigger TTC calculation
block.elements.forEach((element) => {
const form = elementFormRefs.current.get(element.id);
if (form) {
safeFormRequestSubmit(form);
}
safeRequestSubmit(form);
});
// Collect TTC from the ref (populated synchronously by form submissions)

View File

@@ -10,7 +10,6 @@ import {
getMimeType,
getShuffledChoicesIds,
getShuffledRowIndices,
safeFormRequestSubmit,
} from "./utils";
// Mock crypto.getRandomValues for deterministic shuffle tests
@@ -328,54 +327,3 @@ describe("findBlockByElementId", () => {
expect(block).toBeUndefined();
});
});
describe("safeFormRequestSubmit", () => {
let mockForm: HTMLFormElement;
beforeEach(() => {
// Create a mock form element
mockForm = document.createElement("form");
});
test("should call requestSubmit when it's supported", () => {
// Mock requestSubmit as a function
const requestSubmitSpy = vi.fn();
mockForm.requestSubmit = requestSubmitSpy;
safeFormRequestSubmit(mockForm);
expect(requestSubmitSpy).toHaveBeenCalled();
});
test("should use fallback when requestSubmit is not supported", () => {
// Remove requestSubmit to simulate iOS Safari 15.5
mockForm.requestSubmit = undefined as unknown as typeof mockForm.requestSubmit;
const reportValiditySpy = vi.spyOn(mockForm, "reportValidity").mockReturnValue(true);
const dispatchEventSpy = vi.spyOn(mockForm, "dispatchEvent");
safeFormRequestSubmit(mockForm);
expect(reportValiditySpy).toHaveBeenCalled();
expect(dispatchEventSpy).toHaveBeenCalled();
// Verify the submit event was dispatched with correct properties
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
expect(dispatchedEvent.type).toBe("submit");
expect(dispatchedEvent.bubbles).toBe(true);
expect(dispatchedEvent.cancelable).toBe(true);
});
test("should not dispatch event when reportValidity returns false", () => {
// Remove requestSubmit to simulate iOS Safari 15.5
mockForm.requestSubmit = undefined as unknown as typeof mockForm.requestSubmit;
const reportValiditySpy = vi.spyOn(mockForm, "reportValidity").mockReturnValue(false);
const dispatchEventSpy = vi.spyOn(mockForm, "dispatchEvent");
safeFormRequestSubmit(mockForm);
expect(reportValiditySpy).toHaveBeenCalled();
expect(dispatchEventSpy).not.toHaveBeenCalled();
});
});

View File

@@ -275,27 +275,3 @@ export const getFirstElementIdInBlock = (
const block = survey.blocks.find((b) => b.id === blockId);
return block?.elements[0]?.id;
};
/**
* Safely requests form submission with validation.
* Provides a fallback for browsers that don't support requestSubmit() (iOS Safari < 16.0).
* @param form The form element to submit
*/
export const safeFormRequestSubmit = (form: HTMLFormElement): void => {
// Check if requestSubmit is supported (iOS Safari 16.0+, all modern browsers)
if (typeof form.requestSubmit === "function") {
form.requestSubmit();
} else {
// Fallback for older browsers (iOS Safari < 16.0)
// reportValidity() triggers native validation UI
if (!form.reportValidity()) {
return;
}
// Dispatch submit event manually to trigger form submission handlers
const submitEvent = new Event("submit", {
bubbles: true,
cancelable: true,
});
form.dispatchEvent(submitEvent);
}
};

1
pnpm-lock.yaml generated
View File

@@ -10249,6 +10249,7 @@ packages:
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
tarn@3.0.2:
resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==}