From 78c411621e416fa296da4a212786e6adca3f2ff0 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Thu, 4 Dec 2025 14:18:11 +0530 Subject: [PATCH] feat(embed-core): add public close() API for modal-based embeds (#24746) * feat(embed-core): add public close() API for modal-based embeds Add a proper public API method close() to the CalApi class that allows users to programmatically close modal-based embeds (ModalBox and FloatingButton). This replaces the need for the undocumented hack: cal.instance.actionManager.fire('__closeIframe') The new API is cleaner and more intuitive: cal('close') Key features: - Works for modal-based embeds only (ModalBox and FloatingButton) - Throws a clear error if called on inline embeds - Well-documented with JSDoc comments explaining usage and limitations Example usage: cal('on', { action: 'bookingSuccessful', callback: () => { cal('close'); } }); Also fixed pre-existing lint issues: unused variable in catch block and eslint-disable comment for missing rule definition. Co-Authored-By: hariom@cal.com * refactor(embed-core): rename close() to closeModal() for clarity Rename the close() method to closeModal() to make it immediately clear from the API name itself that this method is for modal-based embeds only. This makes the API more self-documenting: - Before: cal('close') - After: cal('closeModal') Updated: - Method name from close() to closeModal() - JSDoc example to use cal('closeModal') - Error message to reference closeModal() The TypeScript types automatically recognize the new action name through the SingleInstructionMap type system. Co-Authored-By: hariom@cal.com * cleanup --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/embeds/embed-core/src/embed.ts | 31 ++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index c9255a1dfd..0c46dba8f5 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -84,7 +84,7 @@ initializeGlobalCalProps(); document.head.appendChild(document.createElement("style")).innerHTML = css; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type ValidationSchemaPropType = string | Function; type ValidationSchema = { @@ -468,7 +468,6 @@ export class Cal { // Try to readjust and scroll into view if more than 25% is hidden. // Otherwise we assume that user might have positioned the content appropriately already if (top < 0 && Math.abs(top / height) >= 0.25) { - // eslint-disable-next-line @calcom/eslint/no-scroll-into-view-embed -- Intentionally done this.inlineEl.scrollIntoView({ behavior: "smooth" }); } }); @@ -1292,6 +1291,32 @@ class CalApi { }) { this.cal.actionManager.off(action, callback); } + + /** + * Closes modal-based embeds programmatically. + * + * @throws {Error} If called on an inline embed (only works for modal-based embeds) + * + * @example + * ```javascript + * // Close the modal after a successful booking + * cal("on", { + * action: "bookingSuccessful", + * callback: () => { + * cal("closeModal"); + * } + * }); + * ``` + */ + closeModal() { + if (this.cal.inlineEl && !this.cal.modalBox) { + throw new Error( + "closeModal() is only supported for modal-based embeds (ModalBox and FloatingButton). It cannot be used with inline embeds." + ); + } + this.cal.actionManager.fire("__closeIframe", {}); + } + /** * * type is provided and prerenderIframe not set. We would assume prerenderIframe to be true @@ -1533,7 +1558,7 @@ document.addEventListener("click", (e) => { let config; try { config = JSON.parse(configString); - } catch (e) { + } catch { config = {}; }