Embed Improvements: Add auto-scroll support back, reduce CLS, remove loader for Routing Form embed (#4819)
* Add back auto-scroll fix with proper check of visibilith * Do improvement in CLS and dont show "Loading" text * Improve experience with routing forms embed Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
co-authored by
Alex van Andel
parent
88cad2e15f
commit
8583d9b2e6
@@ -35,6 +35,12 @@ function toRunBeforeReactOnClient() {
|
||||
const namespace = url.searchParams.get("embed");
|
||||
return namespace;
|
||||
};
|
||||
|
||||
window.isPageOptimizedForEmbed = () => {
|
||||
// Those pages are considered optimized, which know at backend that they are rendering for embed.
|
||||
// Such pages can be shown straightaway without a loader for a better embed experience
|
||||
return location.pathname.includes("forms/");
|
||||
};
|
||||
}
|
||||
|
||||
class MyDocument extends Document<Props> {
|
||||
@@ -76,7 +82,6 @@ class MyDocument extends Document<Props> {
|
||||
/>
|
||||
</Head>
|
||||
|
||||
{/* Keep the embed hidden till parent initializes and gives it the appropriate styles */}
|
||||
<body className="dark:bg-darkgray-50 desktop-transparent bg-gray-100">
|
||||
<Main />
|
||||
<NextScript />
|
||||
@@ -86,7 +91,9 @@ class MyDocument extends Document<Props> {
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
if (isEmbed()) {
|
||||
document.body.style.display="none";
|
||||
if(!isPageOptimizedForEmbed()) {
|
||||
document.body.style.display="none";
|
||||
}
|
||||
document.body.style.background="transparent";
|
||||
}`,
|
||||
}}
|
||||
|
||||
@@ -168,8 +168,11 @@ expect.extend({
|
||||
initialValuesSet: window.initialValuesSet,
|
||||
};
|
||||
});
|
||||
const isOptimizedPage = u.pathname.includes("forms/");
|
||||
expect(initialValuesSet).toBe(true);
|
||||
expect(displayBefore).toBe("none");
|
||||
if (!isOptimizedPage) {
|
||||
expect(displayBefore).toBe("none");
|
||||
}
|
||||
expect(backgroundBefore).toBe("transparent");
|
||||
|
||||
const { display: displayAfter, background: backgroundAfter } = await iframe.evaluate(() => {
|
||||
|
||||
@@ -19,6 +19,7 @@ declare global {
|
||||
resetEmbedStatus: () => void;
|
||||
getEmbedNamespace: () => string | null;
|
||||
getEmbedTheme: () => "dark" | "light" | null;
|
||||
isPageOptimizedForEmbed: (calLink: string) => boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +222,8 @@ export const useIsEmbed = () => {
|
||||
// We can't simply return isEmbed() from this method.
|
||||
// isEmbed() returns different values on server and browser, which messes up the hydration.
|
||||
// TODO: We can avoid using document.URL and instead use Router.
|
||||
const [isEmbed, setIsEmbed] = useState<boolean | null>(null);
|
||||
const router = useRouter();
|
||||
const [isEmbed, setIsEmbed] = useState<boolean | null>(typeof router.query.embed === "string");
|
||||
useEffect(() => {
|
||||
const namespace = getNamespace();
|
||||
const _isValidNamespace = isValidNamespace(namespace);
|
||||
@@ -283,7 +285,11 @@ export const methods = {
|
||||
return;
|
||||
}
|
||||
// No UI change should happen in sight. Let the parent height adjust and in next cycle show it.
|
||||
requestAnimationFrame(unhideBody);
|
||||
// HACK: React components take time to commit their updates of isEmbed=true to DOM, so add this random 500ms delay which seems to work
|
||||
// TODO: Optimize page by page and then remove this unnecessary delay
|
||||
setTimeout(() => {
|
||||
unhideBody();
|
||||
}, 500);
|
||||
sdkActionManager?.fire("linkReady", {});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ type Namespace = string;
|
||||
type Config = {
|
||||
origin: string;
|
||||
debug?: boolean;
|
||||
uiDebug?: boolean;
|
||||
};
|
||||
|
||||
const globalCal = (window as CalWindow).Cal;
|
||||
@@ -184,6 +185,9 @@ export class Cal {
|
||||
if (config.debug) {
|
||||
urlInstance.searchParams.set("debug", "" + config.debug);
|
||||
}
|
||||
if (config.uiDebug) {
|
||||
iframe.style.border = "1px solid green";
|
||||
}
|
||||
|
||||
// Merge searchParams from config onto the URL which might have query params already
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@@ -199,10 +203,11 @@ export class Cal {
|
||||
if (typeof namespaceOrConfig !== "string") {
|
||||
config = (namespaceOrConfig || {}) as Config;
|
||||
}
|
||||
if (config?.origin) {
|
||||
this.__config.origin = config.origin;
|
||||
const { origin, ...restConfig } = config;
|
||||
if (origin) {
|
||||
this.__config.origin = origin;
|
||||
}
|
||||
this.__config.debug = config.debug;
|
||||
this.__config = { ...this.__config, ...restConfig };
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
@@ -242,8 +247,8 @@ export class Cal {
|
||||
},
|
||||
},
|
||||
});
|
||||
const isCalPageOptimized = calLink.includes("forms/");
|
||||
config = config || {};
|
||||
|
||||
// Keeping auto-scroll disabled for two reasons:
|
||||
// - If user scrolls the content to an appropriate position, it again resets it to default position which might not be for the liking of the user
|
||||
// - Sometimes, the position can be wrong(e.g. if there is a fixed position header on top coming above the iframe content).
|
||||
@@ -263,7 +268,9 @@ export class Cal {
|
||||
throw new Error("Element not found");
|
||||
}
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = `<cal-inline style="max-height:inherit;height:inherit;min-height:inherit;display:flex;position:relative;flex-wrap:wrap;width:100%"></cal-inline>`;
|
||||
template.innerHTML = `<cal-inline ${
|
||||
isCalPageOptimized ? 'loading="done"' : ""
|
||||
} style="max-height:inherit;height:inherit;min-height:inherit;display:flex;position:relative;flex-wrap:wrap;width:100%"></cal-inline>`;
|
||||
this.inlineEl = template.content.children[0];
|
||||
(this.inlineEl as unknown as any).__CalAutoScroll = config.__autoScroll;
|
||||
this.inlineEl.appendChild(iframe);
|
||||
@@ -483,8 +490,11 @@ export class Cal {
|
||||
});
|
||||
|
||||
this.actionManager.on("__routeChanged", () => {
|
||||
if (this.inlineEl && (this.inlineEl as unknown as any).__CalAutoScroll) {
|
||||
this.inlineEl.scrollIntoView();
|
||||
const { top, height } = this.inlineEl.getBoundingClientRect();
|
||||
// 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) {
|
||||
this.inlineEl.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,17 @@ Embed Cal Link as a React Component
|
||||
|
||||
To know how to use it, follow the steps at <https://developer.cal.com/embed/install-with-react>
|
||||
|
||||
## Development
|
||||
|
||||
Following command starts a hot reloading server
|
||||
`yarn dev`
|
||||
|
||||
If you are working with embed on website, don't forget to do `yarn build` after every change.
|
||||
|
||||
## Running Tests
|
||||
|
||||
Runs tests and updates the snapshots. Right now we don't care about snapshots
|
||||
`yarn embed-tests-quick --update-snapshots`
|
||||
TODO
|
||||
|
||||
- Playwright tests.
|
||||
|
||||
@@ -6,12 +6,16 @@ import useEmbed from "./useEmbed";
|
||||
type CalProps = {
|
||||
calOrigin?: string;
|
||||
calLink: string;
|
||||
initConfig?: {
|
||||
debug?: boolean;
|
||||
uiDebug?: boolean;
|
||||
};
|
||||
config?: any;
|
||||
embedJsUrl?: string;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const Cal = function Cal(props: CalProps) {
|
||||
const { calLink, calOrigin, config, embedJsUrl, ...restProps } = props;
|
||||
const { calLink, calOrigin, config, initConfig = {}, embedJsUrl, ...restProps } = props;
|
||||
if (!calLink) {
|
||||
throw new Error("calLink is required");
|
||||
}
|
||||
@@ -24,20 +28,19 @@ const Cal = function Cal(props: CalProps) {
|
||||
}
|
||||
initializedRef.current = true;
|
||||
const element = ref.current;
|
||||
let initConfig = {};
|
||||
if (calOrigin) {
|
||||
(initConfig as any).origin = calOrigin;
|
||||
}
|
||||
Cal("init", initConfig);
|
||||
Cal("init", {
|
||||
...initConfig,
|
||||
origin: calOrigin,
|
||||
});
|
||||
Cal("inline", {
|
||||
elementOrSelector: element,
|
||||
calLink,
|
||||
config,
|
||||
});
|
||||
}, [Cal, calLink, config, calOrigin]);
|
||||
}, [Cal, calLink, config, calOrigin, initConfig]);
|
||||
|
||||
if (!Cal) {
|
||||
return <div {...restProps}>Loading {calLink} </div>;
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div ref={ref} {...restProps} />;
|
||||
|
||||
@@ -45,9 +45,10 @@ function App() {
|
||||
notes: "Test Meeting",
|
||||
guests: ["janedoe@gmail.com"],
|
||||
theme: "dark",
|
||||
}}></Cal>
|
||||
}}
|
||||
/>
|
||||
<button data-cal-link="pro">Popup</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
ReactDom.render(<App></App>, document.getElementById("root"));
|
||||
ReactDom.render(<App />, document.getElementById("root"));
|
||||
|
||||
Reference in New Issue
Block a user