Skip to content

Commit cab1f3b

Browse files
committed
v14.2.12
1 parent bf7eb65 commit cab1f3b

File tree

5 files changed

+83
-59
lines changed

5 files changed

+83
-59
lines changed

build/cjs/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/esm/index.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

published/14.2.12/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

published/latest/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GleapCopilotTours.js

Lines changed: 79 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ const styleId = "copilot-tour-styles";
77
const copilotJoinedContainerId = "copilot-joined-container";
88
const copilotInfoBubbleId = "copilot-info-bubble";
99

10-
const arrowRightIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>`;
10+
const arrowRightIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
11+
<path fill="currentColor" d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/>
12+
</svg>`;
1113

1214
function estimateReadTime(text) {
1315
const wordsPerSecond = 3.6; // Average reading speed
@@ -25,7 +27,7 @@ function htmlToPlainText(html) {
2527
function scrollToElement(element) {
2628
if (element) {
2729
element.scrollIntoView({
28-
behavior: "smooth", // Ensures smooth scrolling
30+
behavior: "smooth",
2931
block: "center",
3032
inline: "center",
3133
});
@@ -83,7 +85,18 @@ async function canPlayAudio() {
8385
}
8486
}
8587

86-
// === Helper: Get scrollable ancestors of an element ===
88+
// Helper: Check if an element is fully visible in the viewport.
89+
function isElementFullyVisible(el) {
90+
const rect = el.getBoundingClientRect();
91+
return (
92+
rect.top >= 0 &&
93+
rect.left >= 0 &&
94+
rect.bottom <= window.innerHeight &&
95+
rect.right <= window.innerWidth
96+
);
97+
}
98+
99+
// Helper: Get scrollable ancestors of an element.
87100
function getScrollableAncestors(el) {
88101
let ancestors = [];
89102
let current = el.parentElement;
@@ -110,13 +123,13 @@ export default class GleapCopilotTours {
110123
audioMuted = false;
111124
currentAudio = undefined;
112125

113-
// Cached pointer container reference.
126+
// Cached pointer container.
114127
_pointerContainer = null;
115-
// New properties for scroll handling.
128+
// For scroll handling.
116129
_scrollListeners = [];
117130
_currentAnchor = null;
118131
_currentStep = null;
119-
_updateScheduled = false;
132+
_scrollDebounceTimer = null;
120133

121134
// GleapReplayRecorder singleton.
122135
static instance;
@@ -132,7 +145,7 @@ export default class GleapCopilotTours {
132145
this._scrollListeners = [];
133146
this._currentAnchor = null;
134147
this._currentStep = null;
135-
this._updateScheduled = false;
148+
this._scrollDebounceTimer = null;
136149

137150
window.addEventListener("resize", () => {
138151
if (
@@ -193,46 +206,47 @@ export default class GleapCopilotTours {
193206
}
194207
}
195208

196-
// === Attach scroll listeners to update the pointer position on scroll ===
209+
// Attach scroll listeners with a debounce to update the pointer position after scrolling stops.
197210
attachScrollListeners(anchor, currentStep) {
198211
if (!anchor) return;
199212
const scrollableAncestors = getScrollableAncestors(anchor);
200-
// Also include window to catch any page-level scrolling.
213+
// Also include window.
201214
scrollableAncestors.push(window);
202215
scrollableAncestors.forEach((el) => {
203216
const handler = () => {
204-
if (!this._updateScheduled) {
205-
this._updateScheduled = true;
206-
requestAnimationFrame(() => {
207-
this.updatePointerPosition(anchor, currentStep);
208-
this._updateScheduled = false;
209-
});
210-
}
217+
clearTimeout(this._scrollDebounceTimer);
218+
this._scrollDebounceTimer = setTimeout(() => {
219+
this.updatePointerPosition(anchor, currentStep);
220+
}, 150);
211221
};
212222
el.addEventListener("scroll", handler, { passive: true });
213223
this._scrollListeners.push({ el, handler });
214224
});
215225
}
216226

217-
// === Remove previously attached scroll listeners ===
227+
// Remove scroll listeners and clear debounce timer.
218228
removeScrollListeners() {
219229
if (this._scrollListeners && this._scrollListeners.length > 0) {
220230
this._scrollListeners.forEach(({ el, handler }) => {
221231
el.removeEventListener("scroll", handler);
222232
});
223233
this._scrollListeners = [];
224234
}
235+
if (this._scrollDebounceTimer) {
236+
clearTimeout(this._scrollDebounceTimer);
237+
this._scrollDebounceTimer = null;
238+
}
225239
}
226240

227-
// === Updated pointer position using fixed positioning and scroll listeners ===
241+
// Updated pointer position:
242+
// 1. Scroll the element into view.
243+
// 2. After the element is fully visible (or after a maximum delay), update the pointer position to point towards the element.
228244
updatePointerPosition(anchor, currentStep) {
229245
try {
230-
// Use the cached pointer container if available.
231246
const container =
232247
this._pointerContainer || document.getElementById(pointerContainerId);
233248
if (!container) return;
234249

235-
// If no anchor, center pointer in the viewport.
236250
if (!anchor) {
237251
container.style.position = "fixed";
238252
container.style.left = "50%";
@@ -244,44 +258,54 @@ export default class GleapCopilotTours {
244258
this._currentStep = null;
245259
return;
246260
}
247-
// Calculate element’s position relative to the viewport.
248-
const anchorRect = anchor.getBoundingClientRect();
249-
let anchorCenterX = anchorRect.left + anchorRect.width / 2;
250-
let anchorCenterY = anchorRect.top + anchorRect.height / 2;
251-
if (currentStep?.mode === "INPUT") {
252-
anchorCenterX -= anchorRect.width / 2 - 10;
253-
anchorCenterY += anchorRect.height / 2 - 5;
254-
}
255-
container.style.position = "fixed";
256-
container.style.left = `${anchorCenterX}px`;
257-
container.style.top = `${anchorCenterY}px`;
258-
container.style.transform = "translate(-50%, -50%)";
259-
260-
let containerWidthSpace = 350;
261-
if (containerWidthSpace > window.innerWidth - 40) {
262-
containerWidthSpace = window.innerWidth - 40;
263-
}
264-
const windowWidth = window.innerWidth;
265-
const isTooFarRight =
266-
anchorCenterX + containerWidthSpace > windowWidth - 20;
267-
if (isTooFarRight) {
268-
container.classList.add("copilot-pointer-container-right");
269-
} else {
270-
container.classList.remove("copilot-pointer-container-right");
271-
}
272261

273-
// If desired, consider debouncing this auto-scroll to avoid jarring effects.
262+
// Step 1: Scroll the element into view.
274263
scrollToElement(anchor);
275264

276-
// Reattach scroll listeners if the target or step has changed.
277-
if (this._currentAnchor !== anchor || this._currentStep !== currentStep) {
278-
this.removeScrollListeners();
279-
this._currentAnchor = anchor;
280-
this._currentStep = currentStep;
281-
this.attachScrollListeners(anchor, currentStep);
282-
}
265+
// Step 2: Poll until the element is fully visible (or after maximum polls).
266+
const pollInterval = 100;
267+
const maxPolls = 20;
268+
let pollCount = 0;
269+
const updateFinalPosition = () => {
270+
if (isElementFullyVisible(anchor) || pollCount >= maxPolls) {
271+
// Compute final target coordinates.
272+
const anchorRect = anchor.getBoundingClientRect();
273+
const targetX = anchorRect.left + anchorRect.width / 2;
274+
const targetY = anchorRect.top + anchorRect.height / 2 + 10; // 10px downward offset.
275+
container.style.position = "fixed";
276+
container.style.left = `${targetX}px`;
277+
container.style.top = `${targetY}px`;
278+
container.style.transform = "translate(-50%, -50%)";
279+
280+
// Adjust container if too far right.
281+
let containerWidthSpace = 350;
282+
if (containerWidthSpace > window.innerWidth - 40) {
283+
containerWidthSpace = window.innerWidth - 40;
284+
}
285+
if (targetX + containerWidthSpace > window.innerWidth - 20) {
286+
container.classList.add("copilot-pointer-container-right");
287+
} else {
288+
container.classList.remove("copilot-pointer-container-right");
289+
}
290+
291+
// Reattach scroll listeners if the target or step has changed.
292+
if (
293+
this._currentAnchor !== anchor ||
294+
this._currentStep !== currentStep
295+
) {
296+
this.removeScrollListeners();
297+
this._currentAnchor = anchor;
298+
this._currentStep = currentStep;
299+
this.attachScrollListeners(anchor, currentStep);
300+
}
301+
} else {
302+
pollCount++;
303+
setTimeout(updateFinalPosition, pollInterval);
304+
}
305+
};
306+
updateFinalPosition();
283307
} catch (e) {
284-
// Optionally log errors here.
308+
// Optionally log errors.
285309
}
286310
}
287311

@@ -563,7 +587,7 @@ export default class GleapCopilotTours {
563587
const container = document.createElement("div");
564588
container.id = pointerContainerId;
565589
container.style.opacity = 0;
566-
// Cache the pointer container reference.
590+
// Cache the pointer container.
567591
this._pointerContainer = container;
568592

569593
const svgMouse = document.createElementNS(

0 commit comments

Comments
 (0)