Skip to content

Commit 7813d37

Browse files
fix: Scrolling to timeslot issue on Safari with inline-embeds (calcom#22547)
* Add playground * Fix scroll issue on safari
1 parent fa47456 commit 7813d37

15 files changed

Lines changed: 657 additions & 19 deletions

File tree

packages/embeds/embed-core/index.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,16 @@ <h4 style="width: 30%">On the right side you can book a team meeting =></h4>
399399
</div>
400400
</div>
401401
<div class="inline-embed-container" id="cal-booking-place-hideEventTypeDetails">
402-
<h3><a href="?only=hideEventTypeDetails">Hide EventType Details Test</a></h3>
402+
<h3><a href="?only=ns:hideEventTypeDetails">Hide EventType Details Test</a></h3>
403+
<div class="place"></div>
404+
</div>
405+
406+
<div class="inline-embed-container" style="width: 500px; overflow-y: scroll;" id="cal-booking-place-windowScrollToTimeslot">
407+
<h3><a href="?only=ns:windowScrollToTimeslot">Scroll to Timeslot Test - Selecting a date would scroll the window to the timeslot</a></h3>
408+
<div class="place"></div>
409+
</div>
410+
<div class="inline-embed-container" style="height: 500px; width: 500px; overflow-y: scroll;" id="cal-booking-place-containerScrollToTimeslot">
411+
<h3><a href="?only=ns:containerScrollToTimeslot">Container Scroll Test - Selecting a date would scroll the element to the timeslot</a></h3>
403412
<div class="place"></div>
404413
</div>
405414
<div class="inline-embed-container" id="cal-booking-place-conflicting-theme">

packages/embeds/embed-core/playground/lib/playground.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ if (only === "all" || only === "inline-routing-form") {
288288
]);
289289
}
290290

291-
if (only === "all" || only === "hideEventTypeDetails") {
291+
if (only === "all" || only === "ns:hideEventTypeDetails") {
292292
const identifier = "hideEventTypeDetails";
293293
Cal("init", identifier, {
294294
debug: true,
@@ -693,6 +693,40 @@ if (only === "all" || only === "ns:routingFormWithoutPrerender") {
693693
});
694694
}
695695

696+
if (only === "all" || only === "ns:containerScrollToTimeslot") {
697+
Cal("init", "containerScrollToTimeslot", {
698+
debug: true,
699+
origin,
700+
});
701+
Cal.ns.containerScrollToTimeslot("inline", {
702+
elementOrSelector: "#cal-booking-place-containerScrollToTimeslot .place",
703+
calLink: "free/30min",
704+
config: {
705+
iframeAttrs: {
706+
id: "cal-booking-place-containerScrollToTimeslot-iframe",
707+
},
708+
"flag.coep": "true",
709+
},
710+
});
711+
}
712+
713+
if (only === "all" || only === "ns:windowScrollToTimeslot") {
714+
Cal("init", "windowScrollToTimeslot", {
715+
debug: true,
716+
origin,
717+
});
718+
Cal.ns.windowScrollToTimeslot("inline", {
719+
elementOrSelector: "#cal-booking-place-windowScrollToTimeslot .place",
720+
calLink: "free/30min",
721+
config: {
722+
iframeAttrs: {
723+
id: "cal-booking-place-windowScrollToTimeslot-iframe",
724+
},
725+
"flag.coep": "true",
726+
},
727+
});
728+
}
729+
696730
// Keep it at the bottom as it works on the API defined above for various cases
697731
(function ensureScrolledToCorrectIframe() {
698732
// Reset the hash so that we can scroll to correct iframe

packages/embeds/embed-core/src/Inline/inline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EmbedElement } from "../EmbedElement";
2+
import { getErrorString } from "../lib/utils";
23
import loaderCss from "../loader.css?inline";
3-
import { getErrorString } from "../utils";
44
import inlineHtml, { getSkeletonData } from "./inlineHtml";
55

66
export class Inline extends EmbedElement {

packages/embeds/embed-core/src/ModalBox/ModalBox.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EmbedElement } from "../EmbedElement";
2+
import { getErrorString } from "../lib/utils";
23
import loaderCss from "../loader.css";
3-
import { getErrorString } from "../utils";
44
import modalBoxHtml, { getSkeletonData } from "./ModalBoxHtml";
55

66
export class ModalBox extends EmbedElement {

packages/embeds/embed-core/src/__tests__/utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "vitest";
22

3-
import { generateDataAttributes, isSameBookingLink } from "../utils";
3+
import { generateDataAttributes, isSameBookingLink } from "../lib/utils";
44

55
describe("generateDataAttributes", () => {
66
it("should handle PascalCase property names correctly", () => {

packages/embeds/embed-core/src/embed-iframe/lib/embedStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isParamValuePresentInUrlSearchParams } from "../../lib/utils";
12
import type {
23
EmbedThemeConfig,
34
UiConfig,
@@ -6,7 +7,6 @@ import type {
67
SetStyles,
78
setNonStylesConfig,
89
} from "../../types";
9-
import { isParamValuePresentInUrlSearchParams } from "../../utils";
1010
import { runAsap } from "./utils";
1111

1212
export const enum EMBED_IFRAME_STATE {

packages/embeds/embed-core/src/embed.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@ import {
1010
} from "./constants";
1111
import type { InterfaceWithParent, interfaceWithParent } from "./embed-iframe";
1212
import css from "./embed.css";
13-
import { SdkActionManager } from "./sdk-action-manager";
14-
import type { EventData, EventDataMap } from "./sdk-action-manager";
15-
import tailwindCss from "./tailwindCss";
16-
import type { UiConfig, EmbedPageType, PrefillAndIframeAttrsConfig, ModalPrerenderOptions } from "./types";
17-
import { getMaxHeightForModal } from "./ui-utils";
13+
import { getScrollableAncestor } from "./lib/domUtils";
14+
import { getScrollByDistanceHandler } from "./lib/eventHandlers/scrollByDistanceEventHandler";
1815
import {
1916
fromEntriesWithDuplicateKeys,
2017
isRouterPath,
2118
generateDataAttributes,
2219
getConfigProp,
2320
isSameBookingLink,
2421
buildConfigWithPrerenderRelatedFields,
25-
} from "./utils";
22+
} from "./lib/utils";
23+
import { SdkActionManager } from "./sdk-action-manager";
24+
import type { EventData, EventDataMap } from "./sdk-action-manager";
25+
import tailwindCss from "./tailwindCss";
26+
import type { UiConfig, EmbedPageType, PrefillAndIframeAttrsConfig, ModalPrerenderOptions } from "./types";
27+
import { getMaxHeightForModal } from "./ui-utils";
2628

2729
// Exporting for consumption by @calcom/embed-core user
2830
export type { EmbedEvent } from "./sdk-action-manager";
@@ -472,6 +474,8 @@ export class Cal {
472474
}
473475
});
474476

477+
this.actionManager.on("__scrollByDistance", getScrollByDistanceHandler(this));
478+
475479
this.actionManager.on("linkReady", () => {
476480
if (this.isPrerendering) {
477481
// Ensure that we don't mark embed as loaded if it's prerendering otherwise prerendered embed could show-up without any user action
@@ -500,6 +504,20 @@ export class Cal {
500504
});
501505
}
502506

507+
scrollByDistance(distanceInPixels: number): void {
508+
if (!this.iframe) {
509+
return;
510+
}
511+
// We compute scrollable ancestor on demand here and not when the iframe is created because because we need to see if the ancestor has scrollable content at that time
512+
const scrollContainer = getScrollableAncestor(this.iframe);
513+
if (!scrollContainer) {
514+
return;
515+
}
516+
const newScrollTop = scrollContainer.scrollTop + distanceInPixels;
517+
518+
scrollContainer.scrollTo({ top: newScrollTop, behavior: "smooth" });
519+
}
520+
503521
private filterParams(params: Record<string, unknown>): Record<string, unknown> {
504522
return Object.fromEntries(Object.entries(params).filter(([key, value]) => !excludeParam(key, value)));
505523
}

0 commit comments

Comments
 (0)