Skip to content
Merged
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
22 changes: 22 additions & 0 deletions components/MarketoContactForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ declare global {
}
}

function applyRequiredAsterisks(formElement: HTMLElement) {
if (typeof formElement.querySelectorAll !== "function") {
return;
}

formElement
.querySelectorAll<HTMLElement>(".mktoAsterix")
.forEach((asterisk) => {
const isRequired = Boolean(asterisk.closest(".mktoRequiredField"));
asterisk.style.setProperty(
"display",
isRequired ? "inline" : "none",
"important",
);
});
}

function MarketoSuccessPanel() {
return (
<div className="lf-success-panel" role="status" aria-live="polite">
Expand Down Expand Up @@ -71,6 +88,11 @@ export function MarketoContactForm() {
MARKETO_MUNCHKIN_ID,
MARKETO_FORM_ID,
(form) => {
const renderedForm = document.getElementById(MARKETO_FORM_ELEMENT_ID);
if (renderedForm) {
applyRequiredAsterisks(renderedForm);
Comment thread
marcklingen marked this conversation as resolved.
Comment thread
marcklingen marked this conversation as resolved.
}

setIsFormLoaded(true);

form.onSuccess(() => {
Expand Down
91 changes: 90 additions & 1 deletion scripts/test-use-case-analytics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,42 @@ test("sales completion is emitted only after Marketo success and is deduplicated
},
},
};
globalThis.document = { getElementById: () => ({ replaceChildren() {} }) };
const requiredAsterisk = {
display: null,
priority: null,
closest: (selector) =>
selector === ".mktoRequiredField" ? { required: true } : null,
style: {
setProperty(name, value, priority) {
if (name === "display") {
requiredAsterisk.display = value;
requiredAsterisk.priority = priority;
}
},
},
};
const optionalAsterisk = {
display: null,
priority: null,
closest: () => null,
style: {
setProperty(name, value, priority) {
if (name === "display") {
optionalAsterisk.display = value;
optionalAsterisk.priority = priority;
}
},
},
};
globalThis.document = {
getElementById: () => ({
replaceChildren() {},
querySelectorAll(selector) {
assert.equal(selector, ".mktoAsterix");
return [requiredAsterisk, optionalAsterisk];
},
}),
};
const form = load(compile("components/MarketoContactForm.tsx"), (name) => {
if (name === "react")
return {
Expand All @@ -496,6 +531,10 @@ test("sales completion is emitted only after Marketo success and is deduplicated
});
try {
form.MarketoContactForm();
assert.equal(requiredAsterisk.display, "inline");
assert.equal(requiredAsterisk.priority, "important");
assert.equal(optionalAsterisk.display, "none");
assert.equal(optionalAsterisk.priority, "important");
assert.equal(captured.length, 0, "Loading a form is not a conversion");
assert.equal(onSuccess({ Email: "test@example.invalid" }, ""), false);
assert.equal(onSuccess({}, ""), false);
Expand All @@ -511,3 +550,53 @@ test("sales completion is emitted only after Marketo success and is deduplicated
delete globalThis.document;
}
});

test("sales completion still registers when the form stub has no querySelectorAll", () => {
const captured = [];
let onSuccess;
globalThis.window = {
MktoForms2: {
loadForm(_base, _id, _form, callback) {
callback({
onSuccess: (fn) => {
onSuccess = fn;
},
});
},
},
};
globalThis.document = { getElementById: () => ({ replaceChildren() {} }) };
const form = load(compile("components/MarketoContactForm.tsx"), (name) => {
if (name === "react")
return {
useState: (value) => [value, () => {}],
useRef: (value) => ({ current: value }),
useCallback: (fn) => fn,
useEffect: (fn) => fn(),
};
if (name === "react/jsx-runtime")
return {
jsx: (type, props) => ({ type, props }),
jsxs: (type, props) => ({ type, props }),
};
if (name === "lucide-react") return { Check: () => null };
if (name === "posthog-js")
return {
capture: (event, props) => captured.push({ event, props }),
};
if (name === "@/lib/ad-conversions")
return { reportTalkToUsConversion: () => {} };
if (name === "@/lib/use-case-analytics")
return { readUseCaseAttribution: () => attribution };
throw Error(name);
});
try {
assert.doesNotThrow(() => form.MarketoContactForm());
assert.equal(typeof onSuccess, "function");
assert.equal(onSuccess({}, ""), false);
assert.equal(captured[0]?.event, "sales:inquiry_completed");
} finally {
delete globalThis.window;
delete globalThis.document;
}
});
23 changes: 21 additions & 2 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -978,15 +978,34 @@
line-height: 1.25rem !important;
}

.lf-marketo-form .mktoAsterix {
display: inline !important;
/* Marketo always emits .mktoAsterix in every label, then hides it unless
the field wrap has .mktoRequiredField (from the form descriptor's
IsRequired: true). Do not force-show every asterisk — that marks
optional fields such as Phone as required.

Selectors must beat the Marketo ThemeStyleOverride
`.lf-marketo-form .mktoAsterix { display: contents !important }` which
is injected after page CSS. The form id is always present; `.mktoForm`
is not reliably on the <form> when Forms2 finishes loading. */
.lf-marketo-form form[id^="mktoForm_"] .mktoAsterix {
display: none !important;
float: none !important;
line-height: inherit !important;
padding-left: 0 !important;
padding-right: 0.25rem !important;
color: hsl(var(--destructive)) !important;
}

.lf-marketo-form form[id^="mktoForm_"] .mktoRequiredField .mktoAsterix {
display: inline !important;
}

/* ThemeStyleOverride also injects " *" via ::after on required labels.
Suppress it so required fields do not show two marks. */
.lf-marketo-form form[id^="mktoForm_"] .mktoRequiredField .mktoLabel::after {
content: none !important;
}

.lf-marketo-form input[type="text"],
.lf-marketo-form input[type="email"],
.lf-marketo-form input[type="tel"],
Expand Down
Loading