var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import { doesSurveyUrlMatch } from '../posthog-surveys';
import { SurveyQuestionBranchingType, SurveyQuestionType, SurveyType, } from '../posthog-surveys-types';
import * as Preact from 'preact';
import { useContext, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { addEventListener } from '../utils';
import { document as _document, window as _window } from '../utils/globals';
import { createLogger } from '../utils/logger';
import { isNull, isNumber } from '../utils/type-utils';
import { createWidgetShadow, createWidgetStyle } from './surveys-widget';
import { ConfirmationMessage } from './surveys/components/ConfirmationMessage';
import { Cancel } from './surveys/components/QuestionHeader';
import { LinkQuestion, MultipleChoiceQuestion, OpenTextQuestion, RatingQuestion, } from './surveys/components/QuestionTypes';
import { createShadow, defaultSurveyAppearance, dismissedSurveyEvent, getContrastingTextColor, getDisplayOrderQuestions, getSurveyResponseKey, getSurveySeen, hasWaitPeriodPassed, sendSurveyEvent, style, SURVEY_DEFAULT_Z_INDEX, SurveyContext, } from './surveys/surveys-utils';
import { prepareStylesheet } from './utils/stylesheet-loader';
var logger = createLogger('[Surveys]');
// We cast the types here which is dangerous but protected by the top level generateSurveys call
var window = _window;
var document = _document;
function getPosthogWidgetClass(surveyId) {
return ".PostHogWidget".concat(surveyId);
}
function getRatingBucketForResponseValue(responseValue, scale) {
if (scale === 3) {
if (responseValue < 1 || responseValue > 3) {
throw new Error('The response must be in range 1-3');
}
return responseValue === 1 ? 'negative' : responseValue === 2 ? 'neutral' : 'positive';
}
else if (scale === 5) {
if (responseValue < 1 || responseValue > 5) {
throw new Error('The response must be in range 1-5');
}
return responseValue <= 2 ? 'negative' : responseValue === 3 ? 'neutral' : 'positive';
}
else if (scale === 7) {
if (responseValue < 1 || responseValue > 7) {
throw new Error('The response must be in range 1-7');
}
return responseValue <= 3 ? 'negative' : responseValue === 4 ? 'neutral' : 'positive';
}
else if (scale === 10) {
if (responseValue < 0 || responseValue > 10) {
throw new Error('The response must be in range 0-10');
}
return responseValue <= 6 ? 'detractors' : responseValue <= 8 ? 'passives' : 'promoters';
}
throw new Error('The scale must be one of: 3, 5, 7, 10');
}
export function getNextSurveyStep(survey, currentQuestionIndex, response) {
var _a, _b, _c, _d, _e;
var question = survey.questions[currentQuestionIndex];
var nextQuestionIndex = currentQuestionIndex + 1;
if (!((_a = question.branching) === null || _a === void 0 ? void 0 : _a.type)) {
if (currentQuestionIndex === survey.questions.length - 1) {
return SurveyQuestionBranchingType.End;
}
return nextQuestionIndex;
}
if (question.branching.type === SurveyQuestionBranchingType.End) {
return SurveyQuestionBranchingType.End;
}
else if (question.branching.type === SurveyQuestionBranchingType.SpecificQuestion) {
if (Number.isInteger(question.branching.index)) {
return question.branching.index;
}
}
else if (question.branching.type === SurveyQuestionBranchingType.ResponseBased) {
// Single choice
if (question.type === SurveyQuestionType.SingleChoice) {
// :KLUDGE: for now, look up the choiceIndex based on the response
// TODO: once QuestionTypes.MultipleChoiceQuestion is refactored, pass the selected choiceIndex into this method
var selectedChoiceIndex = question.choices.indexOf("".concat(response));
if ((_c = (_b = question.branching) === null || _b === void 0 ? void 0 : _b.responseValues) === null || _c === void 0 ? void 0 : _c.hasOwnProperty(selectedChoiceIndex)) {
var nextStep = question.branching.responseValues[selectedChoiceIndex];
// Specific question
if (Number.isInteger(nextStep)) {
return nextStep;
}
if (nextStep === SurveyQuestionBranchingType.End) {
return SurveyQuestionBranchingType.End;
}
return nextQuestionIndex;
}
}
else if (question.type === SurveyQuestionType.Rating) {
if (typeof response !== 'number' || !Number.isInteger(response)) {
throw new Error('The response type must be an integer');
}
var ratingBucket = getRatingBucketForResponseValue(response, question.scale);
if ((_e = (_d = question.branching) === null || _d === void 0 ? void 0 : _d.responseValues) === null || _e === void 0 ? void 0 : _e.hasOwnProperty(ratingBucket)) {
var nextStep = question.branching.responseValues[ratingBucket];
// Specific question
if (Number.isInteger(nextStep)) {
return nextStep;
}
if (nextStep === SurveyQuestionBranchingType.End) {
return SurveyQuestionBranchingType.End;
}
return nextQuestionIndex;
}
}
return nextQuestionIndex;
}
logger.warn('Falling back to next question index due to unexpected branching type');
return nextQuestionIndex;
}
var SurveyManager = /** @class */ (function () {
function SurveyManager(posthog) {
var _this = this;
this.surveyTimeouts = new Map();
this.canShowNextEventBasedSurvey = function () {
var _a;
// with event based surveys, we need to show the next survey without reloading the page.
// A simple check for div elements with the class name pattern of PostHogSurvey_xyz doesn't work here
// because preact leaves behind the div element for any surveys responded/dismissed with a <style> node.
// To alleviate this, we check the last div in the dom and see if it has any elements other than a Style node.
// if the last PostHogSurvey_xyz div has only one style node, we can show the next survey in the queue
// without reloading the page.
var surveyPopups = document.querySelectorAll("div[class^=PostHogSurvey]");
if (surveyPopups.length > 0) {
return ((_a = surveyPopups[surveyPopups.length - 1].shadowRoot) === null || _a === void 0 ? void 0 : _a.childElementCount) === 1;
}
return true;
};
this.handlePopoverSurvey = function (survey) {
var _a, _b;
var surveyWaitPeriodInDays = (_a = survey.conditions) === null || _a === void 0 ? void 0 : _a.seenSurveyWaitPeriodInDays;
var lastSeenSurveyDate = localStorage.getItem("lastSeenSurveyDate");
if (!hasWaitPeriodPassed(lastSeenSurveyDate, surveyWaitPeriodInDays)) {
return;
}
var surveySeen = getSurveySeen(survey);
if (!surveySeen) {
_this.clearSurveyTimeout(survey.id);
_this.addSurveyToFocus(survey.id);
var delaySeconds = ((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.surveyPopupDelaySeconds) || 0;
var shadow_1 = createShadow(style(survey === null || survey === void 0 ? void 0 : survey.appearance), survey.id, undefined, _this.posthog);
if (delaySeconds <= 0) {
return Preact.render(<SurveyPopup key={'popover-survey'} posthog={_this.posthog} survey={survey} removeSurveyFromFocus={_this.removeSurveyFromFocus} isPopup={true}/>, shadow_1);
}
var timeoutId = setTimeout(function () {
if (!doesSurveyUrlMatch(survey)) {
return _this.removeSurveyFromFocus(survey.id);
}
// rendering with surveyPopupDelaySeconds = 0 because we're already handling the timeout here
Preact.render(<SurveyPopup key={'popover-survey'} posthog={_this.posthog} survey={__assign(__assign({}, survey), { appearance: __assign(__assign({}, survey.appearance), { surveyPopupDelaySeconds: 0 }) })} removeSurveyFromFocus={_this.removeSurveyFromFocus} isPopup={true}/>, shadow_1);
}, delaySeconds * 1000);
_this.surveyTimeouts.set(survey.id, timeoutId);
}
};
this.handleWidget = function (survey) {
var shadow = createWidgetShadow(survey, _this.posthog);
var stylesheetContent = style(survey.appearance);
var stylesheet = prepareStylesheet(document, stylesheetContent, _this.posthog);
if (stylesheet) {
shadow.appendChild(stylesheet);
}
Preact.render(<FeedbackWidget key={'feedback-survey'} posthog={_this.posthog} survey={survey} removeSurveyFromFocus={_this.removeSurveyFromFocus}/>, shadow);
};
this.handleWidgetSelector = function (survey) {
var _a, _b, _c;
var selectorOnPage = ((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.widgetSelector) && document.querySelector(survey.appearance.widgetSelector);
if (selectorOnPage) {
if (document.querySelectorAll(".PostHogWidget".concat(survey.id)).length === 0) {
_this.handleWidget(survey);
}
else if (document.querySelectorAll(".PostHogWidget".concat(survey.id)).length === 1) {
// we have to check if user selector already has a survey listener attached to it because we always have to check if it's on the page or not
if (!selectorOnPage.getAttribute('PHWidgetSurveyClickListener')) {
var surveyPopup_1 = (_c = (_b = document
.querySelector(getPosthogWidgetClass(survey.id))) === null || _b === void 0 ? void 0 : _b.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector(".survey-form");
addEventListener(selectorOnPage, 'click', function () {
if (surveyPopup_1) {
surveyPopup_1.style.display = surveyPopup_1.style.display === 'none' ? 'block' : 'none';
addEventListener(surveyPopup_1, 'PHSurveyClosed', function () {
_this.removeSurveyFromFocus(survey.id);
surveyPopup_1.style.display = 'none';
});
}
});
selectorOnPage.setAttribute('PHWidgetSurveyClickListener', 'true');
}
}
}
};
/**
* Checks the feature flags associated with this Survey to see if the survey can be rendered.
* @param survey
* @param instance
*/
this.canRenderSurvey = function (survey) {
var renderReason = {
visible: false,
};
if (survey.end_date) {
renderReason.disabledReason = "survey was completed on ".concat(survey.end_date);
return renderReason;
}
if (survey.type != SurveyType.Popover) {
renderReason.disabledReason = "Only Popover survey types can be rendered";
return renderReason;
}
var linkedFlagCheck = survey.linked_flag_key
? _this.posthog.featureFlags.isFeatureEnabled(survey.linked_flag_key)
: true;
if (!linkedFlagCheck) {
renderReason.disabledReason = "linked feature flag ".concat(survey.linked_flag_key, " is false");
return renderReason;
}
var targetingFlagCheck = survey.targeting_flag_key
? _this.posthog.featureFlags.isFeatureEnabled(survey.targeting_flag_key)
: true;
if (!targetingFlagCheck) {
renderReason.disabledReason = "targeting feature flag ".concat(survey.targeting_flag_key, " is false");
return renderReason;
}
var internalTargetingFlagCheck = survey.internal_targeting_flag_key
? _this.posthog.featureFlags.isFeatureEnabled(survey.internal_targeting_flag_key)
: true;
if (!internalTargetingFlagCheck) {
renderReason.disabledReason = "internal targeting feature flag ".concat(survey.internal_targeting_flag_key, " is false");
return renderReason;
}
renderReason.visible = true;
return renderReason;
};
this.renderSurvey = function (survey, selector) {
Preact.render(<SurveyPopup key={'popover-survey'} posthog={_this.posthog} survey={survey} removeSurveyFromFocus={_this.removeSurveyFromFocus} isPopup={false}/>, selector);
};
this.callSurveysAndEvaluateDisplayLogic = function (forceReload) {
var _a;
if (forceReload === void 0) { forceReload = false; }
(_a = _this.posthog) === null || _a === void 0 ? void 0 : _a.getActiveMatchingSurveys(function (surveys) {
var nonAPISurveys = surveys.filter(function (survey) { return survey.type !== 'api'; });
// Create a queue of surveys sorted by their appearance delay. We will evaluate the display logic
// for each survey in the queue in order, and only display one survey at a time.
var nonAPISurveyQueue = _this.sortSurveysByAppearanceDelay(nonAPISurveys);
nonAPISurveyQueue.forEach(function (survey) {
var _a, _b, _c;
// We only evaluate the display logic for one survey at a time
if (!isNull(_this.surveyInFocus)) {
return;
}
if (survey.type === SurveyType.Widget) {
if (((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.widgetType) === 'tab' &&
document.querySelectorAll(".PostHogWidget".concat(survey.id)).length === 0) {
_this.handleWidget(survey);
}
if (((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.widgetType) === 'selector' && ((_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.widgetSelector)) {
_this.handleWidgetSelector(survey);
}
}
if (survey.type === SurveyType.Popover && _this.canShowNextEventBasedSurvey()) {
_this.handlePopoverSurvey(survey);
}
});
}, forceReload);
};
this.addSurveyToFocus = function (id) {
if (!isNull(_this.surveyInFocus)) {
logger.error("Survey ".concat(__spreadArray([], __read(_this.surveyInFocus), false), " already in focus. Cannot add survey ").concat(id, "."));
}
_this.surveyInFocus = id;
};
this.removeSurveyFromFocus = function (id) {
if (_this.surveyInFocus !== id) {
logger.error("Survey ".concat(id, " is not in focus. Cannot remove survey ").concat(id, "."));
}
_this.clearSurveyTimeout(id);
_this.surveyInFocus = null;
};
this.posthog = posthog;
// This is used to track the survey that is currently in focus. We only show one survey at a time.
this.surveyInFocus = null;
}
SurveyManager.prototype.clearSurveyTimeout = function (surveyId) {
var timeout = this.surveyTimeouts.get(surveyId);
if (timeout) {
clearTimeout(timeout);
this.surveyTimeouts.delete(surveyId);
}
};
/**
* Sorts surveys by their appearance delay in ascending order. If a survey does not have an appearance delay,
* it is considered to have a delay of 0.
* @param surveys
* @returns The surveys sorted by their appearance delay
*/
SurveyManager.prototype.sortSurveysByAppearanceDelay = function (surveys) {
return surveys.sort(function (a, b) { var _a, _b; return (((_a = a.appearance) === null || _a === void 0 ? void 0 : _a.surveyPopupDelaySeconds) || 0) - (((_b = b.appearance) === null || _b === void 0 ? void 0 : _b.surveyPopupDelaySeconds) || 0); });
};
// Expose internal state and methods for testing
SurveyManager.prototype.getTestAPI = function () {
return {
addSurveyToFocus: this.addSurveyToFocus,
removeSurveyFromFocus: this.removeSurveyFromFocus,
surveyInFocus: this.surveyInFocus,
surveyTimeouts: this.surveyTimeouts,
canShowNextEventBasedSurvey: this.canShowNextEventBasedSurvey,
handleWidget: this.handleWidget,
handlePopoverSurvey: this.handlePopoverSurvey,
handleWidgetSelector: this.handleWidgetSelector,
sortSurveysByAppearanceDelay: this.sortSurveysByAppearanceDelay,
};
};
return SurveyManager;
}());
export { SurveyManager };
export var renderSurveysPreview = function (_a) {
var _b, _c;
var survey = _a.survey, parentElement = _a.parentElement, previewPageIndex = _a.previewPageIndex, forceDisableHtml = _a.forceDisableHtml, onPreviewSubmit = _a.onPreviewSubmit, posthog = _a.posthog;
var stylesheetContent = style(survey.appearance);
var stylesheet = prepareStylesheet(document, stylesheetContent, posthog);
// Remove previously attached <style>
Array.from(parentElement.children).forEach(function (child) {
if (child instanceof HTMLStyleElement) {
parentElement.removeChild(child);
}
});
if (stylesheet) {
parentElement.appendChild(stylesheet);
}
var textColor = getContrastingTextColor(((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.backgroundColor) || defaultSurveyAppearance.backgroundColor || 'white');
Preact.render(<SurveyPopup key="surveys-render-preview" survey={survey} forceDisableHtml={forceDisableHtml} style={{
position: 'relative',
right: 0,
borderBottom: "1px solid ".concat((_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.borderColor),
borderRadius: 10,
color: textColor,
}} onPreviewSubmit={onPreviewSubmit} previewPageIndex={previewPageIndex} removeSurveyFromFocus={function () { }} isPopup={true}/>, parentElement);
};
export var renderFeedbackWidgetPreview = function (_a) {
var _b;
var survey = _a.survey, root = _a.root, forceDisableHtml = _a.forceDisableHtml, posthog = _a.posthog;
var stylesheetContent = createWidgetStyle((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.widgetColor);
var stylesheet = prepareStylesheet(document, stylesheetContent, posthog);
if (stylesheet) {
root.appendChild(stylesheet);
}
Preact.render(<FeedbackWidget key={'feedback-render-preview'} forceDisableHtml={forceDisableHtml} survey={survey} readOnly={true} removeSurveyFromFocus={function () { }}/>, root);
};
// This is the main exported function
export function generateSurveys(posthog) {
// NOTE: Important to ensure we never try and run surveys without a window environment
if (!document || !window) {
return;
}
var surveyManager = new SurveyManager(posthog);
surveyManager.callSurveysAndEvaluateDisplayLogic(true);
// recalculate surveys every second to check if URL or selectors have changed
setInterval(function () {
surveyManager.callSurveysAndEvaluateDisplayLogic(false);
}, 1000);
return surveyManager;
}
/**
* This hook handles URL-based survey visibility after the initial mount.
* The initial URL check is handled by the `getActiveMatchingSurveys` method in the `PostHogSurveys` class,
* which ensures the URL matches before displaying a survey for the first time.
* That is the method that is called every second to see if there's a matching survey.
*
* This separation of concerns means:
* 1. Initial URL matching is done by `getActiveMatchingSurveys` before displaying the survey
* 2. Subsequent URL changes are handled here to hide the survey as the user navigates
*/
export function useHideSurveyOnURLChange(_a) {
var survey = _a.survey, removeSurveyFromFocus = _a.removeSurveyFromFocus, setSurveyVisible = _a.setSurveyVisible, _b = _a.isPreviewMode, isPreviewMode = _b === void 0 ? false : _b;
useEffect(function () {
var _a;
if (isPreviewMode || !((_a = survey.conditions) === null || _a === void 0 ? void 0 : _a.url)) {
return;
}
var checkUrlMatch = function () {
var urlCheck = doesSurveyUrlMatch(survey);
if (!urlCheck) {
setSurveyVisible(false);
return removeSurveyFromFocus(survey.id);
}
};
// Listen for browser back/forward browser history changes
addEventListener(window, 'popstate', checkUrlMatch);
// Listen for hash changes, for SPA frameworks that use hash-based routing
// The hashchange event is fired when the fragment identifier of the URL has changed (the part of the URL beginning with and following the # symbol).
addEventListener(window, 'hashchange', checkUrlMatch);
// Listen for SPA navigation
var originalPushState = window.history.pushState;
var originalReplaceState = window.history.replaceState;
window.history.pushState = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
originalPushState.apply(this, args);
checkUrlMatch();
};
window.history.replaceState = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
originalReplaceState.apply(this, args);
checkUrlMatch();
};
return function () {
window.removeEventListener('popstate', checkUrlMatch);
window.removeEventListener('hashchange', checkUrlMatch);
window.history.pushState = originalPushState;
window.history.replaceState = originalReplaceState;
};
}, [isPreviewMode, survey, removeSurveyFromFocus, setSurveyVisible]);
}
export function usePopupVisibility(survey, posthog, millisecondDelay, isPreviewMode, removeSurveyFromFocus) {
var _a = __read(useState(isPreviewMode || millisecondDelay === 0), 2), isPopupVisible = _a[0], setIsPopupVisible = _a[1];
var _b = __read(useState(false), 2), isSurveySent = _b[0], setIsSurveySent = _b[1];
useEffect(function () {
if (!posthog) {
logger.error('usePopupVisibility hook called without a PostHog instance.');
return;
}
if (isPreviewMode) {
return;
}
var handleSurveyClosed = function () {
removeSurveyFromFocus(survey.id);
setIsPopupVisible(false);
};
var handleSurveySent = function () {
var _a, _b;
if (!((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.displayThankYouMessage)) {
removeSurveyFromFocus(survey.id);
setIsPopupVisible(false);
}
else {
setIsSurveySent(true);
removeSurveyFromFocus(survey.id);
if ((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.autoDisappear) {
setTimeout(function () {
setIsPopupVisible(false);
}, 5000);
}
}
};
var showSurvey = function () {
var _a;
// check if the url is still matching, necessary for delayed surveys, as the URL may have changed
if (!doesSurveyUrlMatch(survey)) {
return;
}
setIsPopupVisible(true);
window.dispatchEvent(new Event('PHSurveyShown'));
posthog.capture('survey shown', {
$survey_name: survey.name,
$survey_id: survey.id,
$survey_iteration: survey.current_iteration,
$survey_iteration_start_date: survey.current_iteration_start_date,
sessionRecordingUrl: (_a = posthog.get_session_replay_url) === null || _a === void 0 ? void 0 : _a.call(posthog),
});
localStorage.setItem('lastSeenSurveyDate', new Date().toISOString());
setTimeout(function () {
var _a, _b;
var inputField = (_b = (_a = document
.querySelector(getPosthogWidgetClass(survey.id))) === null || _a === void 0 ? void 0 : _a.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('textarea, input[type="text"]');
if (inputField) {
inputField.focus();
}
}, 100);
};
addEventListener(window, 'PHSurveyClosed', handleSurveyClosed);
addEventListener(window, 'PHSurveySent', handleSurveySent);
if (millisecondDelay > 0) {
// This path is only used for direct usage of SurveyPopup,
// not for surveys managed by SurveyManager
var timeoutId_1 = setTimeout(showSurvey, millisecondDelay);
return function () {
clearTimeout(timeoutId_1);
window.removeEventListener('PHSurveyClosed', handleSurveyClosed);
window.removeEventListener('PHSurveySent', handleSurveySent);
};
}
else {
// This is the path used for surveys managed by SurveyManager
showSurvey();
return function () {
window.removeEventListener('PHSurveyClosed', handleSurveyClosed);
window.removeEventListener('PHSurveySent', handleSurveySent);
};
}
}, []);
useHideSurveyOnURLChange({
survey: survey,
removeSurveyFromFocus: removeSurveyFromFocus,
setSurveyVisible: setIsPopupVisible,
isPreviewMode: isPreviewMode,
});
return { isPopupVisible: isPopupVisible, isSurveySent: isSurveySent, setIsPopupVisible: setIsPopupVisible };
}
export function SurveyPopup(_a) {
var _b, _c, _d, _e;
var survey = _a.survey, forceDisableHtml = _a.forceDisableHtml, posthog = _a.posthog, style = _a.style, previewPageIndex = _a.previewPageIndex, removeSurveyFromFocus = _a.removeSurveyFromFocus, isPopup = _a.isPopup, _f = _a.onPreviewSubmit, onPreviewSubmit = _f === void 0 ? function () { } : _f, _g = _a.onPopupSurveyDismissed, onPopupSurveyDismissed = _g === void 0 ? function () { } : _g, _h = _a.onPopupSurveySent, onPopupSurveySent = _h === void 0 ? function () { } : _h;
var isPreviewMode = Number.isInteger(previewPageIndex);
// NB: The client-side code passes the millisecondDelay in seconds, but setTimeout expects milliseconds, so we multiply by 1000
var surveyPopupDelayMilliseconds = ((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.surveyPopupDelaySeconds)
? survey.appearance.surveyPopupDelaySeconds * 1000
: 0;
var _j = usePopupVisibility(survey, posthog, surveyPopupDelayMilliseconds, isPreviewMode, removeSurveyFromFocus), isPopupVisible = _j.isPopupVisible, isSurveySent = _j.isSurveySent, setIsPopupVisible = _j.setIsPopupVisible;
var shouldShowConfirmation = isSurveySent || previewPageIndex === survey.questions.length;
var confirmationBoxLeftStyle = (style === null || style === void 0 ? void 0 : style.left) && isNumber(style === null || style === void 0 ? void 0 : style.left) ? { left: style.left - 40 } : {};
if (isPreviewMode) {
style = style || {};
style.left = 'unset';
style.right = 'unset';
style.transform = 'unset';
}
return isPopupVisible ? (<SurveyContext.Provider value={{
isPreviewMode: isPreviewMode,
previewPageIndex: previewPageIndex,
onPopupSurveyDismissed: function () {
dismissedSurveyEvent(survey, posthog, isPreviewMode);
onPopupSurveyDismissed();
},
isPopup: isPopup || false,
onPreviewSubmit: onPreviewSubmit,
onPopupSurveySent: function () {
onPopupSurveySent();
},
}}>
{!shouldShowConfirmation ? (<Questions survey={survey} forceDisableHtml={!!forceDisableHtml} posthog={posthog} styleOverrides={style}/>) : (<ConfirmationMessage header={((_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.thankYouMessageHeader) || 'Thank you!'} description={((_d = survey.appearance) === null || _d === void 0 ? void 0 : _d.thankYouMessageDescription) || ''} forceDisableHtml={!!forceDisableHtml} contentType={(_e = survey.appearance) === null || _e === void 0 ? void 0 : _e.thankYouMessageDescriptionContentType} appearance={survey.appearance || defaultSurveyAppearance} styleOverrides={__assign(__assign({}, style), confirmationBoxLeftStyle)} onClose={function () { return setIsPopupVisible(false); }}/>)}
</SurveyContext.Provider>) : null;
}
export function Questions(_a) {
var _b, _c;
var survey = _a.survey, forceDisableHtml = _a.forceDisableHtml, posthog = _a.posthog, styleOverrides = _a.styleOverrides;
var textColor = getContrastingTextColor(((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.backgroundColor) || defaultSurveyAppearance.backgroundColor);
var _d = __read(useState({}), 2), questionsResponses = _d[0], setQuestionsResponses = _d[1];
var _e = useContext(SurveyContext), previewPageIndex = _e.previewPageIndex, onPopupSurveyDismissed = _e.onPopupSurveyDismissed, isPopup = _e.isPopup, onPreviewSubmit = _e.onPreviewSubmit, onPopupSurveySent = _e.onPopupSurveySent;
var _f = __read(useState(previewPageIndex || 0), 2), currentQuestionIndex = _f[0], setCurrentQuestionIndex = _f[1];
var surveyQuestions = useMemo(function () { return getDisplayOrderQuestions(survey); }, [survey]);
// Sync preview state
useEffect(function () {
setCurrentQuestionIndex(previewPageIndex !== null && previewPageIndex !== void 0 ? previewPageIndex : 0);
}, [previewPageIndex]);
var onNextButtonClick = function (_a) {
var _b, _c;
var res = _a.res, displayQuestionIndex = _a.displayQuestionIndex, questionId = _a.questionId;
if (!posthog) {
logger.error('onNextButtonClick called without a PostHog instance.');
return;
}
if (!questionId) {
logger.error('onNextButtonClick called without a questionId.');
return;
}
var responseKey = getSurveyResponseKey(questionId);
setQuestionsResponses(__assign(__assign({}, questionsResponses), (_b = {}, _b[responseKey] = res, _b)));
var nextStep = getNextSurveyStep(survey, displayQuestionIndex, res);
if (nextStep === SurveyQuestionBranchingType.End) {
sendSurveyEvent(__assign(__assign({}, questionsResponses), (_c = {}, _c[responseKey] = res, _c)), survey, posthog);
onPopupSurveySent();
}
else {
setCurrentQuestionIndex(nextStep);
}
};
return (<form className="survey-form" style={isPopup
? __assign({ color: textColor, borderColor: (_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.borderColor }, styleOverrides) : {}}>
{surveyQuestions.map(function (question, displayQuestionIndex) {
var _a;
var isVisible = currentQuestionIndex === displayQuestionIndex;
return (isVisible && (<div className="survey-box" style={isPopup
? {
backgroundColor: ((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.backgroundColor) ||
defaultSurveyAppearance.backgroundColor,
}
: {}}>
{isPopup && (<Cancel onClick={function () {
onPopupSurveyDismissed();
}}/>)}
{getQuestionComponent({
question: question,
forceDisableHtml: forceDisableHtml,
displayQuestionIndex: displayQuestionIndex,
appearance: survey.appearance || defaultSurveyAppearance,
onSubmit: function (res) {
return onNextButtonClick({
res: res,
displayQuestionIndex: displayQuestionIndex,
questionId: question.id,
});
},
onPreviewSubmit: onPreviewSubmit,
})}
</div>));
})}
</form>);
}
export function FeedbackWidget(_a) {
var _b, _c;
var survey = _a.survey, forceDisableHtml = _a.forceDisableHtml, posthog = _a.posthog, readOnly = _a.readOnly, removeSurveyFromFocus = _a.removeSurveyFromFocus;
var _d = __read(useState(true), 2), isFeedbackButtonVisible = _d[0], setIsFeedbackButtonVisible = _d[1];
var _e = __read(useState(false), 2), showSurvey = _e[0], setShowSurvey = _e[1];
var _f = __read(useState({}), 2), styleOverrides = _f[0], setStyle = _f[1];
var widgetRef = useRef(null);
useEffect(function () {
var _a, _b, _c, _d;
if (!posthog) {
logger.error('FeedbackWidget called without a PostHog instance.');
return;
}
if (readOnly) {
return;
}
if (((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.widgetType) === 'tab') {
if (widgetRef.current) {
var widgetPos = widgetRef.current.getBoundingClientRect();
var style_1 = {
top: '50%',
left: parseInt("".concat(widgetPos.right - 360)),
bottom: 'auto',
borderRadius: 10,
borderBottom: "1.5px solid ".concat(((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.borderColor) || '#c9c6c6'),
};
setStyle(style_1);
}
}
if (((_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.widgetType) === 'selector') {
var widget = (_d = document.querySelector(survey.appearance.widgetSelector || '')) !== null && _d !== void 0 ? _d : undefined;
addEventListener(widget, 'click', function (event) {
var _a, _b;
// Calculate position based on the selector button
var buttonRect = event.currentTarget.getBoundingClientRect();
var viewportHeight = window.innerHeight;
// Get survey width from maxWidth or default to 300px
var surveyWidth = parseInt(((_a = survey.appearance) === null || _a === void 0 ? void 0 : _a.maxWidth) || '300');
// Calculate horizontal center position of the button
var buttonCenterX = buttonRect.left + buttonRect.width / 2;
// Calculate horizontal center position
var left = buttonCenterX - surveyWidth / 2;
// Ensure the survey doesn't go off-screen horizontally
var rightEdge = left + surveyWidth;
if (rightEdge > window.innerWidth) {
left = window.innerWidth - surveyWidth - 20; // 20px padding from right edge
}
if (left < 20) {
left = 20; // 20px padding from left edge
}
// Determine if we should show above or below
var showAbove = false;
// Check if there's enough space below (need at least 300px)
// If not enough space below, show above
if (buttonRect.bottom + 300 > viewportHeight) {
showAbove = true;
}
// Simple spacing between button and survey
var spacing = 12;
// Calculate positions
var topPosition;
if (showAbove) {
// Problem: When showing above, we're trying to position based on an estimated height,
// but we don't know the actual height of the survey yet.
// Solution: Instead of using top positioning for above, use bottom positioning
// This will anchor the survey to the bottom edge at the button's top position
topPosition = null; // We'll use bottom positioning instead
}
else {
// When showing below, position the top of the survey below the button plus spacing
topPosition = buttonRect.bottom + window.scrollY + spacing;
}
// Set style overrides for positioning
setStyle({
position: 'fixed',
top: showAbove ? 'auto' : topPosition + 'px',
left: left + 'px',
right: 'auto',
bottom: showAbove ? window.innerHeight - buttonRect.top + spacing + 'px' : 'auto',
transform: 'none',
border: "1.5px solid ".concat(((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.borderColor) || '#c9c6c6'),
borderRadius: '10px',
width: "".concat(surveyWidth, "px"),
zIndex: SURVEY_DEFAULT_Z_INDEX,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
maxHeight: showAbove
? "calc(100vh - 40px - ".concat(spacing * 2, "px)")
: "calc(100vh - ".concat(topPosition, "px - 20px)"),
});
setShowSurvey(!showSurvey);
});
widget === null || widget === void 0 ? void 0 : widget.setAttribute('PHWidgetSurveyClickListener', 'true');
}
}, []);
useHideSurveyOnURLChange({
survey: survey,
removeSurveyFromFocus: removeSurveyFromFocus,
setSurveyVisible: setIsFeedbackButtonVisible,
});
if (!isFeedbackButtonVisible) {
return null;
}
var resetShowSurvey = function () {
setShowSurvey(false);
};
return (<Preact.Fragment>
{((_b = survey.appearance) === null || _b === void 0 ? void 0 : _b.widgetType) === 'tab' && (<div className="ph-survey-widget-tab" ref={widgetRef} onClick={function () { return !readOnly && setShowSurvey(!showSurvey); }} style={{ color: getContrastingTextColor(survey.appearance.widgetColor) }}>
<div className="ph-survey-widget-tab-icon"></div>
{((_c = survey.appearance) === null || _c === void 0 ? void 0 : _c.widgetLabel) || ''}
</div>)}
{showSurvey && (<SurveyPopup key={'feedback-widget-survey'} posthog={posthog} survey={survey} forceDisableHtml={forceDisableHtml} style={styleOverrides} removeSurveyFromFocus={removeSurveyFromFocus} isPopup={true} onPopupSurveyDismissed={resetShowSurvey} onPopupSurveySent={resetShowSurvey}/>)}
</Preact.Fragment>);
}
var getQuestionComponent = function (_a) {
var _b, _c;
var question = _a.question, forceDisableHtml = _a.forceDisableHtml, displayQuestionIndex = _a.displayQuestionIndex, appearance = _a.appearance, onSubmit = _a.onSubmit, onPreviewSubmit = _a.onPreviewSubmit;
var questionComponents = (_b = {},
_b[SurveyQuestionType.Open] = OpenTextQuestion,
_b[SurveyQuestionType.Link] = LinkQuestion,
_b[SurveyQuestionType.Rating] = RatingQuestion,
_b[SurveyQuestionType.SingleChoice] = MultipleChoiceQuestion,
_b[SurveyQuestionType.MultipleChoice] = MultipleChoiceQuestion,
_b);
var commonProps = {
question: question,
forceDisableHtml: forceDisableHtml,
appearance: appearance,
onPreviewSubmit: function (res) {
onPreviewSubmit(res);
},
onSubmit: function (res) {
onSubmit(res);
},
};
var additionalProps = (_c = {},
_c[SurveyQuestionType.Open] = {},
_c[SurveyQuestionType.Link] = {},
_c[SurveyQuestionType.Rating] = { displayQuestionIndex: displayQuestionIndex },
_c[SurveyQuestionType.SingleChoice] = { displayQuestionIndex: displayQuestionIndex },
_c[SurveyQuestionType.MultipleChoice] = { displayQuestionIndex: displayQuestionIndex },
_c);
var Component = questionComponents[question.type];
var componentProps = __assign(__assign({}, commonProps), additionalProps[question.type]);
return <Component {...componentProps}/>;
};
//# sourceMappingURL=surveys.jsx.map