@posthog/browser-common 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/client.d.ts +6 -0
  2. package/dist/config.js +1 -1
  3. package/dist/config.mjs +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +58 -10
  6. package/dist/index.mjs +1 -0
  7. package/dist/survey-action-matcher.d.ts +27 -0
  8. package/dist/survey-action-matcher.js +169 -0
  9. package/dist/survey-action-matcher.mjs +131 -0
  10. package/dist/survey-constants.d.ts +86 -0
  11. package/dist/survey-constants.js +144 -0
  12. package/dist/survey-constants.mjs +76 -0
  13. package/dist/survey-event-host.d.ts +15 -0
  14. package/dist/survey-event-host.js +18 -0
  15. package/dist/survey-event-host.mjs +1 -0
  16. package/dist/survey-event-receiver-base.d.ts +180 -0
  17. package/dist/survey-event-receiver-base.js +354 -0
  18. package/dist/survey-event-receiver-base.mjs +316 -0
  19. package/dist/survey-event-receiver.d.ts +36 -0
  20. package/dist/survey-event-receiver.js +133 -0
  21. package/dist/survey-event-receiver.mjs +95 -0
  22. package/dist/survey-render-context.d.ts +13 -0
  23. package/dist/survey-render-context.js +45 -0
  24. package/dist/survey-render-context.mjs +7 -0
  25. package/dist/surveys/components/BottomSection.d.ts +12 -0
  26. package/dist/surveys/components/BottomSection.js +82 -0
  27. package/dist/surveys/components/BottomSection.mjs +44 -0
  28. package/dist/surveys/components/ConfirmationMessage.d.ts +10 -0
  29. package/dist/surveys/components/ConfirmationMessage.js +92 -0
  30. package/dist/surveys/components/ConfirmationMessage.mjs +54 -0
  31. package/dist/surveys/components/IntroScreen.d.ts +10 -0
  32. package/dist/surveys/components/IntroScreen.js +78 -0
  33. package/dist/surveys/components/IntroScreen.mjs +40 -0
  34. package/dist/surveys/components/PostHogLogo.d.ts +5 -0
  35. package/dist/surveys/components/PostHogLogo.js +54 -0
  36. package/dist/surveys/components/PostHogLogo.mjs +16 -0
  37. package/dist/surveys/components/QuestionHeader.d.ts +10 -0
  38. package/dist/surveys/components/QuestionHeader.js +82 -0
  39. package/dist/surveys/components/QuestionHeader.mjs +41 -0
  40. package/dist/surveys/components/QuestionTypes.d.ts +31 -0
  41. package/dist/surveys/components/QuestionTypes.js +519 -0
  42. package/dist/surveys/components/QuestionTypes.mjs +469 -0
  43. package/dist/surveys/icons.d.ts +12 -0
  44. package/dist/surveys/icons.js +212 -0
  45. package/dist/surveys/icons.mjs +147 -0
  46. package/dist/surveys/survey-branching.d.ts +12 -0
  47. package/dist/surveys/survey-branching.js +101 -0
  48. package/dist/surveys/survey-branching.mjs +60 -0
  49. package/dist/surveys/survey-styles.d.ts +1 -0
  50. package/dist/surveys/survey-styles.js +40 -0
  51. package/dist/surveys/survey-styles.mjs +2 -0
  52. package/dist/surveys/survey-translations.d.ts +22 -0
  53. package/dist/surveys/survey-translations.js +68 -0
  54. package/dist/surveys/survey-translations.mjs +27 -0
  55. package/dist/surveys/survey-url-prefill.d.ts +36 -0
  56. package/dist/surveys/survey-url-prefill.js +162 -0
  57. package/dist/surveys/survey-url-prefill.mjs +118 -0
  58. package/dist/surveys/surveys-extension-utils.d.ts +133 -0
  59. package/dist/surveys/surveys-extension-utils.js +739 -0
  60. package/dist/surveys/surveys-extension-utils.mjs +608 -0
  61. package/dist/surveys-config.d.ts +60 -0
  62. package/dist/surveys-config.js +68 -0
  63. package/dist/surveys-config.mjs +9 -0
  64. package/dist/surveys-renderer.d.ts +248 -0
  65. package/dist/surveys-renderer.js +1299 -0
  66. package/dist/surveys-renderer.mjs +1230 -0
  67. package/dist/surveys.d.ts +117 -0
  68. package/dist/surveys.js +566 -0
  69. package/dist/surveys.mjs +495 -0
  70. package/dist/types/surveys.d.ts +83 -86
  71. package/dist/types/surveys.js +1 -127
  72. package/dist/types/surveys.mjs +1 -76
  73. package/dist/utils/autocapture-utils.js +8 -9
  74. package/dist/utils/autocapture-utils.mjs +8 -9
  75. package/dist/utils/matcher-utils.d.ts +1 -1
  76. package/dist/utils/matcher-utils.js +13 -10
  77. package/dist/utils/matcher-utils.mjs +10 -7
  78. package/dist/utils/survey-utils.d.ts +16 -0
  79. package/dist/utils/survey-utils.js +121 -0
  80. package/dist/utils/survey-utils.mjs +38 -0
  81. package/dist/utils/url-targeting-utils.d.ts +3 -1
  82. package/dist/utils/url-targeting-utils.js +1 -2
  83. package/dist/utils/url-targeting-utils.mjs +1 -2
  84. package/dist/utils/uuidv7.js +4 -4
  85. package/dist/utils/uuidv7.mjs +4 -4
  86. package/package.json +96 -2
@@ -0,0 +1,1230 @@
1
+ import { jsx, jsxs } from "preact/jsx-runtime";
2
+ import { Fragment, createElement, render } from "preact";
3
+ import { SURVEYS } from "./surveys-config.mjs";
4
+ import { FeatureFlagsCommonExtension } from "./extension-tokens.mjs";
5
+ import { getSurveyReplayUrl } from "./survey-render-context.mjs";
6
+ import { uuidv7 } from "./utils/uuidv7.mjs";
7
+ import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
8
+ import { SurveyEventName, SurveyEventProperties, SurveyPosition, SurveyQuestionBranchingType, SurveyQuestionType, SurveySchedule, SurveyTabPosition, SurveyType, SurveyWidgetType } from "./survey-constants.mjs";
9
+ import { addEventListener } from "./utils/general-utils.mjs";
10
+ import { IN_APP_SURVEY_TYPES, SURVEY_CAPTURING_DISABLED, SURVEY_LOGGER, doesSurveyActivateByAction, doesSurveyActivateByEvent, isSurveyIterationBased, isSurveyRunning } from "./utils/survey-utils.mjs";
11
+ import { isArray, isNull, isNumber, isUndefined } from "@posthog/core";
12
+ import { recordSurveyAnswer } from "@posthog/core/surveys";
13
+ import { ConfirmationMessage } from "./surveys/components/ConfirmationMessage.mjs";
14
+ import { IntroScreen } from "./surveys/components/IntroScreen.mjs";
15
+ import { Cancel } from "./surveys/components/QuestionHeader.mjs";
16
+ import { LinkQuestion, MultipleChoiceQuestion, OpenTextQuestion, RatingQuestion } from "./surveys/components/QuestionTypes.mjs";
17
+ import { SurveyContext, addSurveyCSSVariablesToElement, canActivateRepeatedly, clearAllInMemoryInProgressSurveyState, clearInProgressSurveyState, defaultSurveyAppearance, dismissedSurveyEvent, doesSurveyDeviceTypesMatch, doesSurveyMatchSelector, doesSurveyUrlMatch, getDisplayOrderQuestions, getInProgressSurveyState, getPopoverPosition, getQuestionOrder, getSurveyContainerClass, getSurveyResponseKey, getSurveySeen, getSurveyStylesheet, hasWaitPeriodPassed, isSurveyInProgress, retrieveSurveyShadow, sendSurveyAbandonedEvent, sendSurveyEvent, setInProgressSurveyState } from "./surveys/surveys-extension-utils.mjs";
18
+ import { calculatePrefillStartIndex, convertPrefillToResponses, extractPrefillParamsFromUrl } from "./surveys/survey-url-prefill.mjs";
19
+ import { getNextSurveyStep } from "./surveys/survey-branching.mjs";
20
+ import { applySurveyTranslationForUser } from "./surveys/survey-translations.mjs";
21
+ const INTRO_SCREEN_PREVIEW_INDEX = -1;
22
+ const DISPATCH_FEEDBACK_WIDGET_EVENT = 'ph:show_survey_widget';
23
+ const WIDGET_LISTENER_ATTRIBUTE = 'PHWidgetSurveyClickListener';
24
+ const SURVEY_NEXT_TO_TRIGGER_PARAMS = {
25
+ ESTIMATED_MIN_HEIGHT: 250,
26
+ HORIZONTAL_PADDING: 20,
27
+ TRIGGER_SPACING: 12
28
+ };
29
+ function getNextToTriggerPosition(target, surveyWidth) {
30
+ try {
31
+ const buttonRect = target.getBoundingClientRect();
32
+ const viewportHeight = window.innerHeight;
33
+ const viewportWidth = window.innerWidth;
34
+ const estimatedMinSurveyHeight = SURVEY_NEXT_TO_TRIGGER_PARAMS.ESTIMATED_MIN_HEIGHT;
35
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2;
36
+ let left = buttonCenterX - surveyWidth / 2;
37
+ const horizontalPadding = SURVEY_NEXT_TO_TRIGGER_PARAMS.HORIZONTAL_PADDING;
38
+ if (left + surveyWidth > viewportWidth - horizontalPadding) left = viewportWidth - surveyWidth - horizontalPadding;
39
+ if (left < horizontalPadding) left = horizontalPadding;
40
+ const spacing = SURVEY_NEXT_TO_TRIGGER_PARAMS.TRIGGER_SPACING;
41
+ const spaceBelow = viewportHeight - buttonRect.bottom;
42
+ const spaceAbove = buttonRect.top;
43
+ const showAbove = spaceBelow < estimatedMinSurveyHeight && spaceAbove > spaceBelow;
44
+ return {
45
+ position: 'fixed',
46
+ top: showAbove ? 'auto' : `${buttonRect.bottom + spacing}px`,
47
+ left: `${left}px`,
48
+ right: 'auto',
49
+ bottom: showAbove ? `${viewportHeight - buttonRect.top + spacing}px` : 'auto',
50
+ zIndex: defaultSurveyAppearance.zIndex
51
+ };
52
+ } catch (error) {
53
+ SURVEY_LOGGER.warn('Failed to calculate trigger position:', error);
54
+ return null;
55
+ }
56
+ }
57
+ const SURVEY_TARGETING_FLAG_PREFIX = 'survey-targeting-';
58
+ class SurveyManager {
59
+ constructor(posthog){
60
+ this._surveyTimeouts = new Map();
61
+ this._widgetSelectorListeners = new Map();
62
+ this._renderedTabWidgets = new Map();
63
+ this._renderedTargets = new Map();
64
+ this._prefillHandledSurveys = new Set();
65
+ this._disposed = false;
66
+ this._currentLanguage = null;
67
+ this._surveyIsRendered = false;
68
+ this._languageChangeListener = null;
69
+ this._unsubscribeFeatureFlags = null;
70
+ this._surveyPopupProps = null;
71
+ this._loggedFeatureFlagsDisabledWarning = false;
72
+ this.handlePageUnload = ()=>{
73
+ const surveys = this._host.client?.kv.get(SURVEYS);
74
+ if (!surveys) return;
75
+ for (const survey of surveys)if (isSurveyInProgress(survey)) sendSurveyAbandonedEvent(survey, this._host);
76
+ };
77
+ this.handlePopoverSurvey = (surveyParam, options, { resumeDelayFromActivation = false } = {})=>{
78
+ if (!this._host.client?.canCapture) return;
79
+ const { survey: translatedSurvey, language: surveyLanguage } = this._translateSurveyForRendering(surveyParam);
80
+ this._currentLanguage = surveyLanguage;
81
+ this._surveyPopupProps = null;
82
+ this._displayOptions = options;
83
+ const survey = this._applyDisplayOverrides(translatedSurvey, options);
84
+ this._clearSurveyTimeout(survey.id);
85
+ const { properties, initialResponses } = options ?? {};
86
+ const hasPrefillData = initialResponses && Object.keys(initialResponses).length > 0;
87
+ const isSurveyCompleted = hasPrefillData ? this._handleInitialResponses(survey, initialResponses, properties, surveyLanguage) : false;
88
+ if (isSurveyCompleted && !survey.appearance?.displayThankYouMessage) return;
89
+ this._addSurveyToFocus(survey);
90
+ let positionStyle = {};
91
+ if (survey.appearance?.position === SurveyPosition.NextToTrigger && survey.appearance?.widgetSelector) {
92
+ const targetElement = document.querySelector(survey.appearance.widgetSelector);
93
+ if (targetElement) positionStyle = getNextToTriggerPosition(targetElement, parseInt(survey.appearance?.maxWidth || defaultSurveyAppearance.maxWidth)) || {};
94
+ }
95
+ const delaySeconds = survey.appearance?.surveyPopupDelaySeconds || 0;
96
+ const { shadow } = retrieveSurveyShadow(survey, this._host);
97
+ this._renderedTargets.set(shadow, shadow.host);
98
+ const surveyPopupProps = {
99
+ posthog: this._host,
100
+ survey: survey,
101
+ removeSurveyFromFocus: this._removeSurveyFromFocus,
102
+ properties: properties,
103
+ style: positionStyle,
104
+ isSurveyCompleted: isSurveyCompleted,
105
+ skipShownEvent: options?.skipShownEvent,
106
+ surveyLanguage
107
+ };
108
+ this._surveyPopupProps = surveyPopupProps;
109
+ if (delaySeconds <= 0) {
110
+ this._surveyIsRendered = true;
111
+ return render(/*#__PURE__*/ jsx(SurveyPopup, {
112
+ ...surveyPopupProps
113
+ }), shadow);
114
+ }
115
+ const renderAfterDelay = ()=>{
116
+ this._surveyIsRendered = true;
117
+ const { survey: freshlyTranslated, language: freshLanguage } = this._translateSurveyForRendering(surveyParam);
118
+ this._currentLanguage = freshLanguage;
119
+ const freshSurvey = this._applyDisplayOverrides(freshlyTranslated, options);
120
+ const freshPopupProps = {
121
+ ...surveyPopupProps,
122
+ survey: freshSurvey,
123
+ surveyLanguage: freshLanguage
124
+ };
125
+ this._surveyPopupProps = freshPopupProps;
126
+ render(/*#__PURE__*/ jsx(SurveyPopup, {
127
+ ...freshPopupProps,
128
+ survey: {
129
+ ...freshSurvey,
130
+ appearance: {
131
+ ...freshSurvey.appearance,
132
+ surveyPopupDelaySeconds: 0
133
+ }
134
+ }
135
+ }), shadow);
136
+ };
137
+ const renderIfStillEligible = ()=>{
138
+ if (!this._host.client?.canCapture || !this._shouldDisplaySurvey(survey)) {
139
+ SURVEY_LOGGER.info(`Survey ${survey.id} no longer eligible when its display delay elapsed; not displaying`);
140
+ return this._removeSurveyFromFocus(survey);
141
+ }
142
+ renderAfterDelay();
143
+ };
144
+ const activatedAt = resumeDelayFromActivation ? this._host.surveys?._surveyEventReceiver?.getActivationTimestamp?.(survey.id) : void 0;
145
+ const elapsedMs = isNumber(activatedAt) ? Math.max(0, Date.now() - activatedAt) : 0;
146
+ const remainingMs = Math.max(0, 1000 * delaySeconds - elapsedMs);
147
+ if (remainingMs <= 0) return renderIfStillEligible();
148
+ const timeoutId = window.setTimeout(()=>{
149
+ this._surveyTimeouts.delete(survey.id);
150
+ renderIfStillEligible();
151
+ }, remainingMs);
152
+ this._surveyTimeouts.set(survey.id, timeoutId);
153
+ };
154
+ this._handleWidget = (survey)=>{
155
+ const { survey: translatedSurvey, language: surveyLanguage } = this._translateSurveyForRendering(survey);
156
+ const { shadow, isNewlyCreated } = retrieveSurveyShadow(translatedSurvey, this._host);
157
+ this._renderedTargets.set(shadow, shadow.host);
158
+ if (survey.appearance?.widgetType === SurveyWidgetType.Tab) this._renderedTabWidgets.set(survey.id, survey);
159
+ if (!isNewlyCreated) return;
160
+ render(/*#__PURE__*/ jsx(FeedbackWidget, {
161
+ posthog: this._host,
162
+ survey: translatedSurvey,
163
+ surveyLanguage: surveyLanguage
164
+ }, survey.id), shadow);
165
+ };
166
+ this._detachWidgetSelectorListener = (surveyId)=>{
167
+ const existing = this._widgetSelectorListeners.get(surveyId);
168
+ if (existing) {
169
+ existing.element.removeEventListener('click', existing.listener);
170
+ existing.element.removeAttribute(WIDGET_LISTENER_ATTRIBUTE);
171
+ this._widgetSelectorListeners.delete(surveyId);
172
+ SURVEY_LOGGER.info(`Removed click listener for survey ${surveyId}`);
173
+ }
174
+ };
175
+ this._removeWidgetSelectorListener = (survey)=>{
176
+ if (this._isWidgetSurveyOpen(survey)) return;
177
+ this._removeSurveyFromDom(survey);
178
+ this._detachWidgetSelectorListener(survey.id);
179
+ };
180
+ this._removeTabWidget = (survey)=>{
181
+ if (this._isWidgetSurveyOpen(survey)) return;
182
+ this._removeSurveyFromDom(survey);
183
+ this._renderedTabWidgets.delete(survey.id);
184
+ };
185
+ this._isWidgetSurveyOpen = (survey)=>{
186
+ try {
187
+ const shadowContainer = document.querySelector(getSurveyContainerClass(survey, true));
188
+ return !!shadowContainer?.shadowRoot?.querySelector('.ph-survey');
189
+ } catch {
190
+ return false;
191
+ }
192
+ };
193
+ this._manageWidgetSelectorListener = (survey, selector)=>{
194
+ const currentElement = document.querySelector(selector);
195
+ const existingListenerData = this._widgetSelectorListeners.get(survey.id);
196
+ if (!currentElement) {
197
+ if (existingListenerData) this._removeWidgetSelectorListener(survey);
198
+ return;
199
+ }
200
+ this._handleWidget(survey);
201
+ if (existingListenerData) if (currentElement === existingListenerData.element) return;
202
+ else {
203
+ SURVEY_LOGGER.info(`Selector element changed for survey ${survey.id}. Re-attaching listener.`);
204
+ this._detachWidgetSelectorListener(survey.id);
205
+ }
206
+ if (!currentElement.hasAttribute(WIDGET_LISTENER_ATTRIBUTE)) {
207
+ const listener = (event)=>{
208
+ event.stopPropagation();
209
+ const positionStyles = survey.appearance?.position === SurveyPosition.NextToTrigger ? getNextToTriggerPosition(event.currentTarget, parseInt(survey.appearance?.maxWidth || defaultSurveyAppearance.maxWidth)) : {};
210
+ window.dispatchEvent(new CustomEvent(DISPATCH_FEEDBACK_WIDGET_EVENT, {
211
+ detail: {
212
+ surveyId: survey.id,
213
+ position: positionStyles
214
+ }
215
+ }));
216
+ };
217
+ addEventListener(currentElement, 'click', listener);
218
+ currentElement.setAttribute(WIDGET_LISTENER_ATTRIBUTE, 'true');
219
+ this._widgetSelectorListeners.set(survey.id, {
220
+ element: currentElement,
221
+ listener,
222
+ survey
223
+ });
224
+ SURVEY_LOGGER.info(`Attached click listener for feedback button survey ${survey.id}`);
225
+ }
226
+ };
227
+ this.renderPopover = (survey)=>{
228
+ if (!this._host.client?.canCapture) return;
229
+ const { survey: translatedSurvey, language: surveyLanguage } = this._translateSurveyForRendering(survey);
230
+ const { shadow } = retrieveSurveyShadow(translatedSurvey, this._host);
231
+ this._renderedTargets.set(shadow, shadow.host);
232
+ render(/*#__PURE__*/ jsx(SurveyPopup, {
233
+ posthog: this._host,
234
+ survey: translatedSurvey,
235
+ removeSurveyFromFocus: this._removeSurveyFromFocus,
236
+ surveyLanguage: surveyLanguage
237
+ }), shadow);
238
+ };
239
+ this.renderSurvey = (survey, selector, properties)=>{
240
+ if (!this._host.client?.canCapture) return;
241
+ const { survey: translatedSurvey, language: surveyLanguage } = this._translateSurveyForRendering(survey);
242
+ let isSurveyCompleted = false;
243
+ if (this._host.config.prefillFromUrl) isSurveyCompleted = this._handleUrlPrefill(translatedSurvey, surveyLanguage, properties);
244
+ render(/*#__PURE__*/ jsx(SurveyPopup, {
245
+ posthog: this._host,
246
+ survey: translatedSurvey,
247
+ removeSurveyFromFocus: this._removeSurveyFromFocus,
248
+ isPopup: false,
249
+ properties: properties,
250
+ isSurveyCompleted: isSurveyCompleted,
251
+ surveyLanguage: surveyLanguage
252
+ }), selector);
253
+ };
254
+ this.getActiveMatchingSurveys = (callback, forceReload = false)=>{
255
+ this._host.surveys?.getSurveys((surveys)=>{
256
+ const targetingMatchedSurveys = surveys.filter((survey)=>this._shouldDisplaySurvey(survey));
257
+ callback(targetingMatchedSurveys);
258
+ }, forceReload);
259
+ };
260
+ this.callSurveysAndEvaluateDisplayLogic = (forceReload = false)=>{
261
+ this.getActiveMatchingSurveys((surveys)=>{
262
+ const canCaptureResponse = this._host.client?.canCapture;
263
+ const inAppSurveysWithDisplayLogic = surveys.filter((survey)=>canCaptureResponse && (survey.type === SurveyType.Popover || survey.type === SurveyType.Widget));
264
+ const matchingIds = new Set(inAppSurveysWithDisplayLogic.map((survey)=>survey.id));
265
+ for (const pendingSurveyId of Array.from(this._surveyTimeouts.keys()))if (!matchingIds.has(pendingSurveyId)) this.cancelSurvey(pendingSurveyId);
266
+ const inAppSurveysQueue = this._sortSurveysByAppearanceDelay(inAppSurveysWithDisplayLogic);
267
+ const activeSelectorSurveys = new Set();
268
+ const activeTabWidgetSurveys = new Set();
269
+ inAppSurveysQueue.forEach((survey)=>{
270
+ if (survey.type === SurveyType.Widget) {
271
+ if (survey.appearance?.widgetType === SurveyWidgetType.Tab) {
272
+ activeTabWidgetSurveys.add(survey.id);
273
+ this._handleWidget(survey);
274
+ return;
275
+ }
276
+ if (survey.appearance?.widgetType === SurveyWidgetType.Selector && survey.appearance?.widgetSelector) {
277
+ activeSelectorSurveys.add(survey.id);
278
+ this._manageWidgetSelectorListener(survey, survey.appearance?.widgetSelector);
279
+ }
280
+ }
281
+ if (isNull(this._surveyInFocus) && survey.type === SurveyType.Popover) this.handlePopoverSurvey(survey, void 0, {
282
+ resumeDelayFromActivation: true
283
+ });
284
+ });
285
+ this._widgetSelectorListeners.forEach(({ survey })=>{
286
+ if (!activeSelectorSurveys.has(survey.id)) this._removeWidgetSelectorListener(survey);
287
+ });
288
+ this._renderedTabWidgets.forEach((tabSurvey, surveyId)=>{
289
+ if (!activeTabWidgetSurveys.has(surveyId)) this._removeTabWidget(tabSurvey);
290
+ });
291
+ }, forceReload);
292
+ };
293
+ this._addSurveyToFocus = (survey)=>{
294
+ if (!isNull(this._surveyInFocus)) SURVEY_LOGGER.error(`Survey ${this._surveyInFocus} already in focus. Cannot add survey ${survey.id}.`);
295
+ this._surveyInFocus = survey.id;
296
+ };
297
+ this._removeSurveyFromFocus = (survey)=>{
298
+ if (this._surveyInFocus !== survey.id) SURVEY_LOGGER.error(`Survey ${survey.id} is not in focus. Cannot remove survey ${survey.id}.`);
299
+ this._clearSurveyTimeout(survey.id);
300
+ this._surveyInFocus = null;
301
+ this._currentLanguage = null;
302
+ this._surveyIsRendered = false;
303
+ this._surveyPopupProps = null;
304
+ this._displayOptions = void 0;
305
+ this._removeSurveyFromDom(survey);
306
+ };
307
+ this._host = posthog;
308
+ this._surveyInFocus = null;
309
+ this._languageChangeListener = ()=>this._onLanguageChange();
310
+ addEventListener(window, 'languagechange', this._languageChangeListener);
311
+ this._unsubscribeFeatureFlags = posthog.client?.getExtension(FeatureFlagsCommonExtension)?.onFeatureFlags(()=>this._onLanguageChange()) ?? null;
312
+ }
313
+ _onLanguageChange() {
314
+ if (isNull(this._surveyInFocus)) return;
315
+ const surveys = this._host.client?.kv.get(SURVEYS);
316
+ const survey = surveys?.find((s)=>s.id === this._surveyInFocus);
317
+ if (!survey) return;
318
+ const { survey: translatedSurvey, language: newLanguage } = this._translateSurveyForRendering(survey);
319
+ if (newLanguage === this._currentLanguage) return;
320
+ this._currentLanguage = newLanguage;
321
+ if (!this._surveyIsRendered) return;
322
+ const overriddenSurvey = this._applyDisplayOverrides(translatedSurvey, this._displayOptions);
323
+ const { shadow } = retrieveSurveyShadow(overriddenSurvey, this._host);
324
+ render(/*#__PURE__*/ jsx(SurveyPopup, {
325
+ ...this._surveyPopupProps,
326
+ posthog: this._host,
327
+ survey: overriddenSurvey,
328
+ removeSurveyFromFocus: this._removeSurveyFromFocus,
329
+ surveyLanguage: newLanguage
330
+ }), shadow);
331
+ }
332
+ _applyDisplayOverrides(survey, options) {
333
+ return options?.position || options?.selector ? {
334
+ ...survey,
335
+ appearance: {
336
+ ...survey.appearance,
337
+ ...options.position && {
338
+ position: options.position
339
+ },
340
+ ...options.selector && {
341
+ widgetSelector: options.selector
342
+ }
343
+ }
344
+ } : survey;
345
+ }
346
+ _clearSurveyTimeout(surveyId) {
347
+ const timeout = this._surveyTimeouts.get(surveyId);
348
+ if (timeout) {
349
+ clearTimeout(timeout);
350
+ this._surveyTimeouts.delete(surveyId);
351
+ }
352
+ }
353
+ cancelSurvey(surveyId) {
354
+ if (!this._surveyTimeouts.has(surveyId)) return;
355
+ SURVEY_LOGGER.info(`Cancelled pending survey ${surveyId}`);
356
+ this._clearSurveyTimeout(surveyId);
357
+ if (this._surveyInFocus === surveyId) {
358
+ this._surveyInFocus = null;
359
+ this._currentLanguage = null;
360
+ this._surveyIsRendered = false;
361
+ this._surveyPopupProps = null;
362
+ this._displayOptions = void 0;
363
+ }
364
+ }
365
+ startAutomaticDisplay() {
366
+ if (this._disposed || this._automaticDisplayDispose || false === this._host.config.automaticDisplay) return;
367
+ this._automaticDisplayDispose = ()=>{};
368
+ const dispose = startAutomaticDisplay(this);
369
+ if (this._disposed) dispose();
370
+ else this._automaticDisplayDispose = dispose;
371
+ }
372
+ clearInMemoryInProgressSurveyState() {
373
+ clearAllInMemoryInProgressSurveyState();
374
+ }
375
+ dispose() {
376
+ this._disposed = true;
377
+ this._automaticDisplayDispose?.();
378
+ this._automaticDisplayDispose = void 0;
379
+ if (this._languageChangeListener) {
380
+ window.removeEventListener('languagechange', this._languageChangeListener);
381
+ this._languageChangeListener = null;
382
+ }
383
+ if (this._unsubscribeFeatureFlags) {
384
+ this._unsubscribeFeatureFlags();
385
+ this._unsubscribeFeatureFlags = null;
386
+ }
387
+ this._surveyTimeouts.forEach((_timeout, surveyId)=>this._clearSurveyTimeout(surveyId));
388
+ this._widgetSelectorListeners.forEach((_listener, surveyId)=>this._detachWidgetSelectorListener(surveyId));
389
+ this._renderedTargets.forEach((container, target)=>{
390
+ render(null, target);
391
+ container?.remove();
392
+ });
393
+ this._renderedTargets.clear();
394
+ this._renderedTabWidgets.clear();
395
+ this._surveyInFocus = null;
396
+ }
397
+ _sortSurveysByAppearanceDelay(surveys) {
398
+ return surveys.sort((a, b)=>{
399
+ const isSurveyInProgressA = isSurveyInProgress(a);
400
+ const isSurveyInProgressB = isSurveyInProgress(b);
401
+ if (isSurveyInProgressA && !isSurveyInProgressB) return -1;
402
+ if (!isSurveyInProgressA && isSurveyInProgressB) return 1;
403
+ const aIsAlways = a.schedule === SurveySchedule.Always;
404
+ const bIsAlways = b.schedule === SurveySchedule.Always;
405
+ if (aIsAlways && !bIsAlways) return 1;
406
+ if (!aIsAlways && bIsAlways) return -1;
407
+ return (a.appearance?.surveyPopupDelaySeconds || 0) - (b.appearance?.surveyPopupDelaySeconds || 0);
408
+ });
409
+ }
410
+ _translateSurveyForRendering(survey) {
411
+ return applySurveyTranslationForUser(survey, this._host);
412
+ }
413
+ _handleUrlPrefill(survey, surveyLanguage, properties) {
414
+ if (this._prefillHandledSurveys.has(survey.id)) return false;
415
+ const { params } = extractPrefillParamsFromUrl(window.location.search);
416
+ if (0 === Object.keys(params).length) return false;
417
+ SURVEY_LOGGER.info('[Survey Prefill] Detected URL prefill parameters');
418
+ const result = this._processPrefillData(survey, params, surveyLanguage);
419
+ if (!result) return false;
420
+ this._autoSubmitPrefilledResponses(survey, result, properties, surveyLanguage);
421
+ this._prefillHandledSurveys.add(survey.id);
422
+ return result.isSurveyCompleted;
423
+ }
424
+ _autoSubmitPrefilledResponses(survey, result, properties, surveyLanguage) {
425
+ const { responses, submissionId, isSurveyCompleted, skippedResponses } = result;
426
+ const shouldAutoSubmitPrefilled = Object.keys(skippedResponses).length > 0 && survey.enable_partial_responses;
427
+ if (shouldAutoSubmitPrefilled || isSurveyCompleted) sendSurveyEvent({
428
+ responses: isSurveyCompleted ? responses : skippedResponses,
429
+ survey,
430
+ surveySubmissionId: submissionId,
431
+ posthog: this._host,
432
+ isSurveyCompleted,
433
+ properties,
434
+ surveyLanguage
435
+ });
436
+ }
437
+ _handleInitialResponses(survey, initialResponses, properties, surveyLanguage) {
438
+ const prefillParams = {};
439
+ for (const [indexStr, value] of Object.entries(initialResponses)){
440
+ const index = parseInt(indexStr);
441
+ prefillParams[index] = isArray(value) ? value.map(String) : [
442
+ String(value)
443
+ ];
444
+ }
445
+ SURVEY_LOGGER.info('[Survey] Processing initial responses');
446
+ const result = this._processPrefillData(survey, prefillParams, surveyLanguage);
447
+ if (!result) return false;
448
+ this._autoSubmitPrefilledResponses(survey, result, properties, surveyLanguage);
449
+ return result.isSurveyCompleted;
450
+ }
451
+ _processPrefillData(survey, prefillParams, surveyLanguage) {
452
+ try {
453
+ const responses = convertPrefillToResponses(survey, prefillParams);
454
+ if (0 === Object.keys(responses).length) {
455
+ SURVEY_LOGGER.warn('[Survey Prefill] No valid responses after conversion');
456
+ return null;
457
+ }
458
+ const submissionId = uuidv7();
459
+ const prefilledIndices = Object.keys(prefillParams).map((k)=>parseInt(k, 10));
460
+ const { startQuestionIndex, skippedResponses, skippedIndices } = calculatePrefillStartIndex(survey, prefilledIndices, responses);
461
+ const isSurveyCompleted = startQuestionIndex >= survey.questions.length;
462
+ setInProgressSurveyState(survey, {
463
+ surveySubmissionId: submissionId,
464
+ responses: responses,
465
+ lastQuestionIndex: startQuestionIndex,
466
+ questionOrder: getQuestionOrder(survey.questions),
467
+ visitedIndices: skippedIndices,
468
+ surveyLanguage
469
+ });
470
+ SURVEY_LOGGER.info('[Survey Prefill] Stored prefilled responses in localStorage');
471
+ return {
472
+ responses,
473
+ submissionId,
474
+ startQuestionIndex,
475
+ isSurveyCompleted,
476
+ skippedResponses
477
+ };
478
+ } catch (error) {
479
+ SURVEY_LOGGER.error('[Survey Prefill] Error handling prefill:', error);
480
+ return null;
481
+ }
482
+ }
483
+ _isSurveyFeatureFlagEnabled(flagKey, flagVariant) {
484
+ if (!flagKey) return true;
485
+ const featureFlags = this._host.client?.getExtension(FeatureFlagsCommonExtension);
486
+ const isFeatureEnabled = !!featureFlags?.isFeatureEnabled(flagKey, {
487
+ send_event: !flagKey.startsWith(SURVEY_TARGETING_FLAG_PREFIX)
488
+ });
489
+ let flagVariantCheck = true;
490
+ if (flagVariant) {
491
+ const flagVariantValue = featureFlags?.getFeatureFlag(flagKey, {
492
+ send_event: false
493
+ });
494
+ flagVariantCheck = flagVariantValue === flagVariant || 'any' === flagVariant;
495
+ }
496
+ const enabled = isFeatureEnabled && flagVariantCheck;
497
+ if (!enabled) this._warnIfFeatureFlagsDisabled(flagKey);
498
+ return enabled;
499
+ }
500
+ _warnIfFeatureFlagsDisabled(flagKey) {
501
+ if (this._loggedFeatureFlagsDisabledWarning || false !== this._host.config.featureFlagEvaluation) return;
502
+ this._loggedFeatureFlagsDisabledWarning = true;
503
+ SURVEY_LOGGER.warn(`Survey feature flag "${flagKey}" evaluated to false because advanced_disable_feature_flags is set. PostHog creates an internal targeting flag for almost every survey, so no survey can display while flags are disabled. To keep surveys working, replace advanced_disable_feature_flags with advanced_only_evaluate_survey_feature_flags, which evaluates survey flags only.`);
504
+ }
505
+ _isSurveyConditionMatched(survey) {
506
+ if (!survey.conditions) return true;
507
+ return doesSurveyUrlMatch(survey, this._host) && doesSurveyDeviceTypesMatch(survey) && doesSurveyMatchSelector(survey);
508
+ }
509
+ _internalFlagCheckSatisfied(survey) {
510
+ if (canActivateRepeatedly(survey)) return {
511
+ satisfied: true
512
+ };
513
+ if (survey.internal_targeting_flag_key && isSurveyIterationBased(survey) && !this._host.client?.getExtension(FeatureFlagsCommonExtension)?.hasLoadedFlags) {
514
+ this._warnIfFeatureFlagsDisabled(survey.internal_targeting_flag_key);
515
+ return {
516
+ satisfied: false,
517
+ reason: 'Feature flags have not loaded yet; deferring internal targeting flag check'
518
+ };
519
+ }
520
+ return {
521
+ satisfied: this._isSurveyFeatureFlagEnabled(survey.internal_targeting_flag_key)
522
+ };
523
+ }
524
+ checkSurveyEligibility(survey) {
525
+ const eligibility = {
526
+ eligible: true,
527
+ reason: void 0
528
+ };
529
+ if (!isSurveyRunning(survey)) {
530
+ eligibility.eligible = false;
531
+ eligibility.reason = `Survey is not running. It was completed on ${survey.end_date}`;
532
+ return eligibility;
533
+ }
534
+ if (!IN_APP_SURVEY_TYPES.includes(survey.type)) {
535
+ eligibility.eligible = false;
536
+ eligibility.reason = `Surveys of type ${survey.type} are never eligible to be shown in the app`;
537
+ return eligibility;
538
+ }
539
+ const linkedFlagVariant = survey.conditions?.linkedFlagVariant;
540
+ if (!this._isSurveyFeatureFlagEnabled(survey.linked_flag_key, linkedFlagVariant)) {
541
+ eligibility.eligible = false;
542
+ if (linkedFlagVariant) eligibility.reason = `Survey linked feature flag is not enabled for variant ${linkedFlagVariant}`;
543
+ else eligibility.reason = "Survey linked feature flag is not enabled";
544
+ return eligibility;
545
+ }
546
+ if (!this._isSurveyFeatureFlagEnabled(survey.targeting_flag_key)) {
547
+ eligibility.eligible = false;
548
+ eligibility.reason = "Survey targeting feature flag is not enabled";
549
+ return eligibility;
550
+ }
551
+ const internalFlagCheck = this._internalFlagCheckSatisfied(survey);
552
+ if (!internalFlagCheck.satisfied) {
553
+ eligibility.eligible = false;
554
+ eligibility.reason = internalFlagCheck.reason ?? 'Survey internal targeting flag is not enabled and survey cannot activate repeatedly and survey is not in progress';
555
+ return eligibility;
556
+ }
557
+ if (!hasWaitPeriodPassed(survey.conditions?.seenSurveyWaitPeriodInDays)) {
558
+ eligibility.eligible = false;
559
+ eligibility.reason = "Survey wait period has not passed";
560
+ return eligibility;
561
+ }
562
+ if (getSurveySeen(survey)) {
563
+ eligibility.eligible = false;
564
+ eligibility.reason = "Survey has already been seen and it can't be activated again";
565
+ }
566
+ return eligibility;
567
+ }
568
+ checkSurveyCaptureEligibility() {
569
+ if (!this._host.client?.canCapture) return {
570
+ eligible: false,
571
+ reason: SURVEY_CAPTURING_DISABLED
572
+ };
573
+ return {
574
+ eligible: true
575
+ };
576
+ }
577
+ checkSurveyDisplayEligibility(survey) {
578
+ const captureEligibility = this.checkSurveyCaptureEligibility();
579
+ if (!captureEligibility.eligible) return captureEligibility;
580
+ return this.checkSurveyEligibility(survey);
581
+ }
582
+ checkSurveyRenderability(survey) {
583
+ const eligibility = this.checkSurveyDisplayEligibility(survey);
584
+ if (eligibility.eligible && !this._hasActionOrEventTriggeredSurvey(survey)) return {
585
+ eligible: false,
586
+ reason: "Survey event/action trigger has not been fired yet"
587
+ };
588
+ return eligibility;
589
+ }
590
+ _hasActionOrEventTriggeredSurvey(survey) {
591
+ if (!doesSurveyActivateByEvent(survey) && !doesSurveyActivateByAction(survey)) return true;
592
+ const surveysActivatedByEventsOrActions = this._host.surveys?._surveyEventReceiver?.getSurveys();
593
+ return !!surveysActivatedByEventsOrActions?.includes(survey.id);
594
+ }
595
+ _checkFlags(survey) {
596
+ if (!survey.feature_flag_keys?.length) return true;
597
+ return survey.feature_flag_keys.every(({ key, value })=>{
598
+ if (!key || !value) return true;
599
+ return this._isSurveyFeatureFlagEnabled(value);
600
+ });
601
+ }
602
+ _shouldDisplaySurvey(survey) {
603
+ return this.checkSurveyEligibility(survey).eligible && this._isSurveyConditionMatched(survey) && this._hasActionOrEventTriggeredSurvey(survey) && this._checkFlags(survey);
604
+ }
605
+ _removeSurveyFromDom(survey) {
606
+ try {
607
+ const shadowContainer = document.querySelector(getSurveyContainerClass(survey, true));
608
+ if (shadowContainer?.shadowRoot) {
609
+ render(null, shadowContainer.shadowRoot);
610
+ this._renderedTargets.delete(shadowContainer.shadowRoot);
611
+ }
612
+ shadowContainer?.remove();
613
+ } catch (error) {
614
+ SURVEY_LOGGER.warn(`Failed to remove survey ${survey.id} from DOM:`, error);
615
+ }
616
+ }
617
+ getTestAPI() {
618
+ return {
619
+ addSurveyToFocus: this._addSurveyToFocus,
620
+ removeSurveyFromFocus: this._removeSurveyFromFocus,
621
+ surveyInFocus: this._surveyInFocus,
622
+ surveyTimeouts: this._surveyTimeouts,
623
+ handleWidget: this._handleWidget,
624
+ handlePopoverSurvey: this.handlePopoverSurvey,
625
+ manageWidgetSelectorListener: this._manageWidgetSelectorListener,
626
+ sortSurveysByAppearanceDelay: this._sortSurveysByAppearanceDelay.bind(this),
627
+ checkFlags: this._checkFlags.bind(this),
628
+ isSurveyFeatureFlagEnabled: this._isSurveyFeatureFlagEnabled.bind(this),
629
+ onLanguageChange: this._onLanguageChange.bind(this),
630
+ currentLanguage: this._currentLanguage,
631
+ surveyIsRendered: this._surveyIsRendered
632
+ };
633
+ }
634
+ }
635
+ const DEFAULT_PREVIEW_POSITION_STYLES = {
636
+ position: 'relative',
637
+ left: 'unset',
638
+ right: 'unset',
639
+ top: 'unset',
640
+ bottom: 'unset',
641
+ transform: 'unset'
642
+ };
643
+ const renderSurveysPreview = ({ survey, parentElement, previewPageIndex, forceDisableHtml, onPreviewSubmit, onPreviewBack, posthog, positionStyles = DEFAULT_PREVIEW_POSITION_STYLES })=>{
644
+ const currentStyle = parentElement.querySelector('style[data-ph-survey-style]');
645
+ if (currentStyle) currentStyle.remove();
646
+ const stylesheet = getSurveyStylesheet();
647
+ if (stylesheet) {
648
+ parentElement.appendChild(stylesheet);
649
+ addSurveyCSSVariablesToElement(parentElement, survey.type, survey.appearance);
650
+ }
651
+ render(/*#__PURE__*/ jsx(SurveyPopup, {
652
+ survey: survey,
653
+ posthog: posthog,
654
+ forceDisableHtml: forceDisableHtml,
655
+ style: positionStyles,
656
+ onPreviewSubmit: onPreviewSubmit,
657
+ onPreviewBack: onPreviewBack,
658
+ previewPageIndex: previewPageIndex,
659
+ removeSurveyFromFocus: ()=>{}
660
+ }), parentElement);
661
+ };
662
+ const renderFeedbackWidgetPreview = ({ survey, root, forceDisableHtml, posthog })=>{
663
+ const stylesheet = getSurveyStylesheet();
664
+ if (stylesheet) {
665
+ root.appendChild(stylesheet);
666
+ addSurveyCSSVariablesToElement(root, survey.type, survey.appearance);
667
+ }
668
+ render(/*#__PURE__*/ jsx(FeedbackWidget, {
669
+ forceDisableHtml: forceDisableHtml,
670
+ survey: survey,
671
+ posthog: posthog,
672
+ readOnly: true
673
+ }), root);
674
+ };
675
+ function generateSurveys(posthog, isSurveysEnabled) {
676
+ if ("u" < typeof document || "u" < typeof window) return;
677
+ const surveyManager = new SurveyManager(posthog);
678
+ if (false === posthog.config.automaticDisplay) {
679
+ SURVEY_LOGGER.info('Surveys automatic display is disabled. Skipping call surveys and evaluate display logic.');
680
+ return surveyManager;
681
+ }
682
+ if (false === isSurveysEnabled) {
683
+ SURVEY_LOGGER.info('There are no surveys to load or Surveys is disabled in the project settings.');
684
+ return surveyManager;
685
+ }
686
+ surveyManager.startAutomaticDisplay();
687
+ return surveyManager;
688
+ }
689
+ function startAutomaticDisplay(surveyManager) {
690
+ surveyManager.callSurveysAndEvaluateDisplayLogic(true);
691
+ let intervalId;
692
+ const startInterval = ()=>{
693
+ if (!isUndefined(intervalId)) return;
694
+ intervalId = setInterval(()=>{
695
+ surveyManager.callSurveysAndEvaluateDisplayLogic(false);
696
+ }, 1000);
697
+ };
698
+ const stopInterval = ()=>{
699
+ if (!isUndefined(intervalId)) {
700
+ clearInterval(intervalId);
701
+ intervalId = void 0;
702
+ }
703
+ };
704
+ startInterval();
705
+ const onVisibilityChange = ()=>{
706
+ if (document.hidden) stopInterval();
707
+ else {
708
+ surveyManager.callSurveysAndEvaluateDisplayLogic(false);
709
+ startInterval();
710
+ }
711
+ };
712
+ addEventListener(document, 'visibilitychange', onVisibilityChange);
713
+ return ()=>{
714
+ stopInterval();
715
+ document.removeEventListener('visibilitychange', onVisibilityChange);
716
+ };
717
+ }
718
+ function useHideSurveyOnURLChange({ survey, removeSurveyFromFocus = ()=>{}, setSurveyVisible, isPreviewMode = false, posthog }) {
719
+ useEffect(()=>{
720
+ if (isPreviewMode || !survey.conditions?.url) return;
721
+ const checkUrlMatch = ()=>{
722
+ const isSurveyTypeWidget = survey.type === SurveyType.Widget;
723
+ const doesSurveyMatchUrlCondition = doesSurveyUrlMatch(survey, posthog);
724
+ const isSurveyWidgetTypeTab = survey.appearance?.widgetType === SurveyWidgetType.Tab && isSurveyTypeWidget;
725
+ if (doesSurveyMatchUrlCondition) {
726
+ if (isSurveyWidgetTypeTab) {
727
+ SURVEY_LOGGER.info(`Showing survey ${survey.id} because it is a feedback button tab and URL matches`);
728
+ setSurveyVisible(true);
729
+ }
730
+ return;
731
+ }
732
+ SURVEY_LOGGER.info(`Hiding survey ${survey.id} because URL does not match`);
733
+ setSurveyVisible(false);
734
+ return removeSurveyFromFocus(survey);
735
+ };
736
+ addEventListener(window, 'popstate', checkUrlMatch);
737
+ addEventListener(window, 'hashchange', checkUrlMatch);
738
+ const originalPushState = window.history.pushState;
739
+ const originalReplaceState = window.history.replaceState;
740
+ window.history.pushState = function(...args) {
741
+ originalPushState.apply(this, args);
742
+ checkUrlMatch();
743
+ };
744
+ window.history.replaceState = function(...args) {
745
+ originalReplaceState.apply(this, args);
746
+ checkUrlMatch();
747
+ };
748
+ return ()=>{
749
+ window.removeEventListener('popstate', checkUrlMatch);
750
+ window.removeEventListener('hashchange', checkUrlMatch);
751
+ window.history.pushState = originalPushState;
752
+ window.history.replaceState = originalReplaceState;
753
+ };
754
+ }, [
755
+ isPreviewMode,
756
+ survey,
757
+ removeSurveyFromFocus,
758
+ setSurveyVisible,
759
+ posthog
760
+ ]);
761
+ }
762
+ const CLOSE_ANIMATION_DURATION_MS = 200;
763
+ function usePopupVisibility(survey, posthog, millisecondDelay, isPreviewMode, removeSurveyFromFocus, isPopup, surveyContainerRef, skipShownEvent, surveyLanguage) {
764
+ const [isPopupVisible, setIsPopupVisible] = useState(isPreviewMode || 0 === millisecondDelay || survey.type === SurveyType.ExternalSurvey);
765
+ const [isSurveySent, setIsSurveySent] = useState(false);
766
+ const isClosingRef = useRef(false);
767
+ const hidePopupWithAnimation = ()=>{
768
+ const removeDOMAndHidePopup = ()=>{
769
+ if (isPopup) removeSurveyFromFocus(survey);
770
+ setIsPopupVisible(false);
771
+ };
772
+ if (isClosingRef.current) return;
773
+ const container = surveyContainerRef?.current;
774
+ if (!container) return void removeDOMAndHidePopup();
775
+ isClosingRef.current = true;
776
+ container.style.transition = `opacity ${CLOSE_ANIMATION_DURATION_MS}ms ease-out`;
777
+ container.style.opacity = '0';
778
+ setTimeout(()=>{
779
+ isClosingRef.current = false;
780
+ removeDOMAndHidePopup();
781
+ }, CLOSE_ANIMATION_DURATION_MS + 50);
782
+ };
783
+ const handleSurveyClosed = (event)=>{
784
+ if (event.detail.surveyId !== survey.id) return;
785
+ hidePopupWithAnimation();
786
+ };
787
+ useEffect(()=>{
788
+ if (!posthog) return void SURVEY_LOGGER.error('usePopupVisibility hook called without a PostHog instance.');
789
+ if (isPreviewMode) return;
790
+ let autoDisappearTimeout;
791
+ const handleSurveySent = (event)=>{
792
+ if (event.detail.surveyId !== survey.id) return;
793
+ if (!survey.appearance?.displayThankYouMessage) return hidePopupWithAnimation();
794
+ setIsSurveySent(true);
795
+ if (survey.appearance?.autoDisappear) {
796
+ if (autoDisappearTimeout) clearTimeout(autoDisappearTimeout);
797
+ autoDisappearTimeout = setTimeout(hidePopupWithAnimation, 5000);
798
+ }
799
+ };
800
+ const showSurvey = ()=>{
801
+ if (!doesSurveyUrlMatch(survey, posthog)) return;
802
+ setIsPopupVisible(true);
803
+ try {
804
+ localStorage.setItem('lastSeenSurveyDate', new Date().toISOString());
805
+ } catch {}
806
+ window.dispatchEvent(new Event('PHSurveyShown'));
807
+ if (!skipShownEvent) posthog.client?.capture(SurveyEventName.SHOWN, {
808
+ [SurveyEventProperties.SURVEY_NAME]: survey.name,
809
+ [SurveyEventProperties.SURVEY_ID]: survey.id,
810
+ [SurveyEventProperties.SURVEY_ITERATION]: survey.current_iteration,
811
+ [SurveyEventProperties.SURVEY_ITERATION_START_DATE]: survey.current_iteration_start_date,
812
+ ...surveyLanguage && {
813
+ [SurveyEventProperties.SURVEY_LANGUAGE]: surveyLanguage
814
+ },
815
+ sessionRecordingUrl: getSurveyReplayUrl(posthog)
816
+ });
817
+ };
818
+ addEventListener(window, 'PHSurveyClosed', handleSurveyClosed);
819
+ addEventListener(window, 'PHSurveySent', handleSurveySent);
820
+ const showTimeout = millisecondDelay > 0 ? setTimeout(showSurvey, millisecondDelay) : void 0;
821
+ if (isUndefined(showTimeout)) showSurvey();
822
+ return ()=>{
823
+ if (!isUndefined(showTimeout)) clearTimeout(showTimeout);
824
+ if (!isUndefined(autoDisappearTimeout)) clearTimeout(autoDisappearTimeout);
825
+ window.removeEventListener('PHSurveyClosed', handleSurveyClosed);
826
+ window.removeEventListener('PHSurveySent', handleSurveySent);
827
+ };
828
+ }, []);
829
+ useHideSurveyOnURLChange({
830
+ survey,
831
+ removeSurveyFromFocus,
832
+ setSurveyVisible: setIsPopupVisible,
833
+ isPreviewMode,
834
+ posthog
835
+ });
836
+ return {
837
+ isPopupVisible,
838
+ isSurveySent,
839
+ setIsPopupVisible,
840
+ hidePopupWithAnimation
841
+ };
842
+ }
843
+ function getTabPositionStyles(position = SurveyTabPosition.Right) {
844
+ switch(position){
845
+ case SurveyTabPosition.Top:
846
+ return {
847
+ top: '0',
848
+ left: '50%',
849
+ transform: 'translateX(-50%)'
850
+ };
851
+ case SurveyTabPosition.Left:
852
+ return {
853
+ top: '50%',
854
+ left: '0',
855
+ transform: 'rotate(90deg) translateY(-100%)',
856
+ transformOrigin: 'left top'
857
+ };
858
+ case SurveyTabPosition.Bottom:
859
+ return {
860
+ bottom: '0',
861
+ left: '50%',
862
+ transform: 'translateX(-50%)'
863
+ };
864
+ default:
865
+ case SurveyTabPosition.Right:
866
+ return {
867
+ top: '50%',
868
+ right: '0',
869
+ transform: 'rotate(-90deg) translateY(-100%)',
870
+ transformOrigin: 'right top'
871
+ };
872
+ }
873
+ }
874
+ function SurveyPopup({ survey, forceDisableHtml, posthog, style = {}, previewPageIndex, removeSurveyFromFocus = ()=>{}, isPopup = true, onPreviewSubmit = ()=>{}, onPreviewBack = ()=>{}, onPopupSurveyDismissed = ()=>{}, onCloseConfirmationMessage = ()=>{}, properties, isSurveyCompleted, skipShownEvent, surveyLanguage }) {
875
+ const surveyContainerRef = useRef(null);
876
+ const isPreviewMode = Number.isInteger(previewPageIndex);
877
+ const surveyPopupDelayMilliseconds = survey.appearance?.surveyPopupDelaySeconds ? 1000 * survey.appearance.surveyPopupDelaySeconds : 0;
878
+ const { isPopupVisible, isSurveySent, hidePopupWithAnimation } = usePopupVisibility(survey, posthog, surveyPopupDelayMilliseconds, isPreviewMode, removeSurveyFromFocus, isPopup, surveyContainerRef, skipShownEvent, surveyLanguage);
879
+ const shouldShowConfirmation = isSurveySent || previewPageIndex === survey.questions.length || true === isSurveyCompleted;
880
+ const [introScreenDismissed, setIntroScreenDismissed] = useState(false);
881
+ const hasInProgressState = useMemo(()=>!!getInProgressSurveyState(survey), [
882
+ survey
883
+ ]);
884
+ const hasIntroContent = !!(survey.appearance?.introScreenHeader || survey.appearance?.introScreenDescription);
885
+ const shouldShowIntroScreen = isPreviewMode ? previewPageIndex === INTRO_SCREEN_PREVIEW_INDEX : !!survey.appearance?.displayIntroScreen && hasIntroContent && !introScreenDismissed && !hasInProgressState;
886
+ const surveyContextValue = useMemo(()=>{
887
+ const getInProgressSurvey = getInProgressSurveyState(survey);
888
+ const surveySubmissionId = getInProgressSurvey?.surveySubmissionId || uuidv7() || '';
889
+ return {
890
+ isPreviewMode,
891
+ previewPageIndex: previewPageIndex,
892
+ onPopupSurveyDismissed: ()=>{
893
+ surveyLanguage ? dismissedSurveyEvent(survey, posthog, isPreviewMode, surveyLanguage) : dismissedSurveyEvent(survey, posthog, isPreviewMode);
894
+ onPopupSurveyDismissed();
895
+ },
896
+ isPopup: isPopup || false,
897
+ surveySubmissionId,
898
+ onPreviewSubmit,
899
+ onPreviewBack,
900
+ posthog,
901
+ properties,
902
+ surveyLanguage
903
+ };
904
+ }, [
905
+ isPreviewMode,
906
+ previewPageIndex,
907
+ isPopup,
908
+ posthog,
909
+ survey,
910
+ onPopupSurveyDismissed,
911
+ onPreviewSubmit,
912
+ onPreviewBack,
913
+ properties,
914
+ surveyLanguage
915
+ ]);
916
+ if (!isPopupVisible) return null;
917
+ return /*#__PURE__*/ jsx(SurveyContext.Provider, {
918
+ value: surveyContextValue,
919
+ children: /*#__PURE__*/ jsx("div", {
920
+ className: "ph-survey",
921
+ style: {
922
+ ...getPopoverPosition(survey.type, survey.appearance?.position, survey.appearance?.widgetType),
923
+ ...style
924
+ },
925
+ ref: surveyContainerRef,
926
+ children: shouldShowConfirmation ? /*#__PURE__*/ jsx(ConfirmationMessage, {
927
+ header: survey.appearance?.thankYouMessageHeader || 'Thank you!',
928
+ description: survey.appearance?.thankYouMessageDescription || '',
929
+ forceDisableHtml: !!forceDisableHtml,
930
+ contentType: survey.appearance?.thankYouMessageDescriptionContentType,
931
+ appearance: survey.appearance || defaultSurveyAppearance,
932
+ onClose: ()=>{
933
+ hidePopupWithAnimation();
934
+ onCloseConfirmationMessage();
935
+ }
936
+ }) : shouldShowIntroScreen ? /*#__PURE__*/ jsx(IntroScreen, {
937
+ header: survey.appearance?.introScreenHeader || '',
938
+ description: survey.appearance?.introScreenDescription || '',
939
+ forceDisableHtml: !!forceDisableHtml,
940
+ contentType: survey.appearance?.introScreenDescriptionContentType,
941
+ appearance: survey.appearance || defaultSurveyAppearance,
942
+ onStart: ()=>setIntroScreenDismissed(true)
943
+ }) : /*#__PURE__*/ jsx(Questions, {
944
+ survey: survey,
945
+ forceDisableHtml: !!forceDisableHtml,
946
+ posthog: posthog
947
+ })
948
+ })
949
+ });
950
+ }
951
+ function Questions({ survey, forceDisableHtml, posthog }) {
952
+ const initialInProgressState = useMemo(()=>{
953
+ const state = getInProgressSurveyState(survey);
954
+ if (!state) return null;
955
+ const hasIndex = isNumber(state.lastQuestionIndex);
956
+ const isIndexInRange = hasIndex && state.lastQuestionIndex >= 0 && state.lastQuestionIndex < survey.questions.length;
957
+ if (hasIndex && !isIndexInRange) {
958
+ clearInProgressSurveyState(survey);
959
+ return null;
960
+ }
961
+ return state;
962
+ }, [
963
+ survey
964
+ ]);
965
+ const [questionsResponses, setQuestionsResponses] = useState(()=>{
966
+ if (initialInProgressState?.responses) SURVEY_LOGGER.info('Survey is already in progress, filling in initial responses');
967
+ return initialInProgressState?.responses || {};
968
+ });
969
+ const [submissionBlocked, setSubmissionBlocked] = useState(false);
970
+ const { previewPageIndex, onPopupSurveyDismissed, isPopup, onPreviewSubmit, onPreviewBack, surveySubmissionId, isPreviewMode, properties, surveyLanguage } = useContext(SurveyContext);
971
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(()=>{
972
+ const savedIndex = initialInProgressState?.lastQuestionIndex;
973
+ const validSavedIndex = isNumber(savedIndex) && savedIndex >= 0 && savedIndex < survey.questions.length ? savedIndex : 0;
974
+ return isNumber(previewPageIndex) && previewPageIndex >= 0 ? previewPageIndex : validSavedIndex;
975
+ });
976
+ const [visitedIndices, setVisitedIndices] = useState(()=>(initialInProgressState?.visitedIndices ?? []).filter((index)=>index >= 0 && index < survey.questions.length));
977
+ const [questionSnapshots, setQuestionSnapshots] = useState(()=>{
978
+ const inProgressSurveyData = getInProgressSurveyState(survey);
979
+ return inProgressSurveyData?.questionSnapshots ?? {};
980
+ });
981
+ const questionOrderIds = useMemo(()=>getQuestionOrder(getDisplayOrderQuestions(survey, initialInProgressState)), [
982
+ survey.id,
983
+ initialInProgressState
984
+ ]);
985
+ const surveyQuestions = useMemo(()=>{
986
+ const hasUsableIds = !!questionOrderIds && new Set(questionOrderIds).size === questionOrderIds.length;
987
+ if (!hasUsableIds) return getDisplayOrderQuestions(survey, initialInProgressState);
988
+ const byId = new Map(survey.questions.map((question)=>[
989
+ question.id,
990
+ question
991
+ ]));
992
+ const mapped = questionOrderIds.map((id)=>byId.get(id)).filter((question)=>!!question);
993
+ return mapped.length === questionOrderIds.length ? mapped : survey.questions;
994
+ }, [
995
+ questionOrderIds,
996
+ survey.questions,
997
+ survey,
998
+ initialInProgressState
999
+ ]);
1000
+ useEffect(()=>{
1001
+ if (isPreviewMode && !isUndefined(previewPageIndex) && previewPageIndex >= 0) setCurrentQuestionIndex(previewPageIndex);
1002
+ }, [
1003
+ previewPageIndex,
1004
+ isPreviewMode
1005
+ ]);
1006
+ const onNextButtonClick = ({ res, displayQuestionIndex, questionId })=>{
1007
+ if (!posthog) return void SURVEY_LOGGER.error('onNextButtonClick called without a PostHog instance.');
1008
+ if (!posthog.client?.canCapture) return void setSubmissionBlocked(true);
1009
+ setSubmissionBlocked(false);
1010
+ if (!questionId) return void SURVEY_LOGGER.error('onNextButtonClick called without a questionId.');
1011
+ const { responses: newResponses, questionSnapshots: newSnapshots } = recordSurveyAnswer({
1012
+ responses: questionsResponses,
1013
+ questionSnapshots
1014
+ }, questionId, res, surveyQuestions[displayQuestionIndex]);
1015
+ setQuestionsResponses(newResponses);
1016
+ setQuestionSnapshots(newSnapshots);
1017
+ const nextStep = getNextSurveyStep(survey, displayQuestionIndex, res);
1018
+ const isSurveyCompleted = nextStep === SurveyQuestionBranchingType.End;
1019
+ const newVisitedIndices = [
1020
+ ...visitedIndices,
1021
+ displayQuestionIndex
1022
+ ];
1023
+ if (!isSurveyCompleted) {
1024
+ setVisitedIndices(newVisitedIndices);
1025
+ setCurrentQuestionIndex(nextStep);
1026
+ setInProgressSurveyState(survey, {
1027
+ surveySubmissionId: surveySubmissionId,
1028
+ responses: newResponses,
1029
+ lastQuestionIndex: nextStep,
1030
+ questionOrder: getQuestionOrder(surveyQuestions),
1031
+ visitedIndices: newVisitedIndices,
1032
+ surveyLanguage,
1033
+ questionSnapshots: newSnapshots
1034
+ });
1035
+ }
1036
+ if (survey.enable_partial_responses || isSurveyCompleted) {
1037
+ const visitedResponseKeys = new Set(newVisitedIndices.map((i)=>surveyQuestions[i]?.id).filter((id)=>!!id).map((id)=>getSurveyResponseKey(id)));
1038
+ const responsesOnPath = Object.fromEntries(Object.entries(newResponses).filter(([key])=>visitedResponseKeys.has(key)));
1039
+ sendSurveyEvent({
1040
+ responses: responsesOnPath,
1041
+ survey,
1042
+ surveySubmissionId,
1043
+ isSurveyCompleted,
1044
+ posthog,
1045
+ properties,
1046
+ surveyLanguage,
1047
+ questionSnapshots: newSnapshots
1048
+ });
1049
+ }
1050
+ };
1051
+ const onBackButtonClick = ()=>{
1052
+ if (isPreviewMode) return void onPreviewBack();
1053
+ if (0 === visitedIndices.length) return;
1054
+ const previousIndex = visitedIndices[visitedIndices.length - 1];
1055
+ const newVisitedIndices = visitedIndices.slice(0, -1);
1056
+ setVisitedIndices(newVisitedIndices);
1057
+ setCurrentQuestionIndex(previousIndex);
1058
+ setInProgressSurveyState(survey, {
1059
+ surveySubmissionId,
1060
+ responses: questionsResponses,
1061
+ lastQuestionIndex: previousIndex,
1062
+ questionOrder: getQuestionOrder(surveyQuestions),
1063
+ visitedIndices: newVisitedIndices,
1064
+ surveyLanguage,
1065
+ questionSnapshots
1066
+ });
1067
+ };
1068
+ const canGoBack = !!survey.appearance?.allowGoBack && (isPreviewMode ? (previewPageIndex ?? 0) > 0 : visitedIndices.length > 0);
1069
+ const currentQuestion = surveyQuestions.at(currentQuestionIndex);
1070
+ if (!currentQuestion) return null;
1071
+ const renderCancelButton = isPopup && survey.appearance?.hideCancelButton !== true;
1072
+ return /*#__PURE__*/ jsxs("form", {
1073
+ className: "survey-form",
1074
+ name: "surveyForm",
1075
+ children: [
1076
+ renderCancelButton && /*#__PURE__*/ jsx(Cancel, {
1077
+ onClick: ()=>{
1078
+ onPopupSurveyDismissed();
1079
+ }
1080
+ }),
1081
+ /*#__PURE__*/ jsxs("div", {
1082
+ className: "survey-box",
1083
+ "data-question-index": currentQuestionIndex,
1084
+ children: [
1085
+ submissionBlocked && /*#__PURE__*/ jsx("p", {
1086
+ role: "alert",
1087
+ children: "Your response could not be sent. Please try again later."
1088
+ }),
1089
+ getQuestionComponent({
1090
+ question: currentQuestion,
1091
+ forceDisableHtml,
1092
+ displayQuestionIndex: currentQuestionIndex,
1093
+ appearance: survey.appearance || defaultSurveyAppearance,
1094
+ onSubmit: (res)=>onNextButtonClick({
1095
+ res,
1096
+ displayQuestionIndex: currentQuestionIndex,
1097
+ questionId: currentQuestion.id
1098
+ }),
1099
+ onPreviewSubmit,
1100
+ initialValue: currentQuestion.id ? questionsResponses[getSurveyResponseKey(currentQuestion.id)] : void 0,
1101
+ canGoBack,
1102
+ onBack: onBackButtonClick
1103
+ })
1104
+ ]
1105
+ })
1106
+ ]
1107
+ });
1108
+ }
1109
+ function FeedbackWidget({ survey, forceDisableHtml, posthog, readOnly, surveyLanguage }) {
1110
+ const [isFeedbackButtonVisible, setIsFeedbackButtonVisible] = useState(true);
1111
+ const [showSurvey, setShowSurvey] = useState(false);
1112
+ const [styleOverrides, setStyleOverrides] = useState({});
1113
+ const resetTimeout = useRef(void 0);
1114
+ const toggleSurvey = ()=>{
1115
+ if (!showSurvey && posthog && !readOnly && !posthog.client?.canCapture) return;
1116
+ setShowSurvey(!showSurvey);
1117
+ };
1118
+ useEffect(()=>{
1119
+ if (!posthog) return void SURVEY_LOGGER.error('FeedbackWidget called without a PostHog instance.');
1120
+ if (readOnly) return;
1121
+ if (survey.appearance?.widgetType === 'tab') setStyleOverrides({
1122
+ top: '50%',
1123
+ bottom: 'auto'
1124
+ });
1125
+ const handleShowSurvey = (event)=>{
1126
+ const customEvent = event;
1127
+ if (customEvent.detail?.surveyId === survey.id) {
1128
+ SURVEY_LOGGER.info(`Received show event for feedback button survey ${survey.id}`);
1129
+ setStyleOverrides(customEvent.detail.position || {});
1130
+ toggleSurvey();
1131
+ }
1132
+ };
1133
+ addEventListener(window, DISPATCH_FEEDBACK_WIDGET_EVENT, handleShowSurvey);
1134
+ return ()=>{
1135
+ if (!isUndefined(resetTimeout.current)) {
1136
+ clearTimeout(resetTimeout.current);
1137
+ resetTimeout.current = void 0;
1138
+ }
1139
+ window.removeEventListener(DISPATCH_FEEDBACK_WIDGET_EVENT, handleShowSurvey);
1140
+ };
1141
+ }, [
1142
+ posthog,
1143
+ readOnly,
1144
+ survey.id,
1145
+ survey.appearance?.widgetType,
1146
+ survey.appearance?.widgetSelector,
1147
+ survey.appearance?.borderColor
1148
+ ]);
1149
+ useHideSurveyOnURLChange({
1150
+ survey,
1151
+ setSurveyVisible: setIsFeedbackButtonVisible,
1152
+ posthog
1153
+ });
1154
+ if (!isFeedbackButtonVisible) return null;
1155
+ const resetShowSurvey = ()=>{
1156
+ if (survey.schedule !== SurveySchedule.Always) setIsFeedbackButtonVisible(false);
1157
+ if (!isUndefined(resetTimeout.current)) clearTimeout(resetTimeout.current);
1158
+ resetTimeout.current = setTimeout(()=>{
1159
+ resetTimeout.current = void 0;
1160
+ setShowSurvey(false);
1161
+ }, 200);
1162
+ };
1163
+ return /*#__PURE__*/ jsxs(Fragment, {
1164
+ children: [
1165
+ survey.appearance?.widgetType === 'tab' && /*#__PURE__*/ jsx("button", {
1166
+ className: `ph-survey-widget-tab ${survey.appearance?.tabPosition === SurveyTabPosition.Top ? 'widget-tab-top' : ''}`,
1167
+ onClick: toggleSurvey,
1168
+ disabled: readOnly,
1169
+ style: getTabPositionStyles(survey.appearance?.tabPosition),
1170
+ children: survey.appearance?.widgetLabel || ''
1171
+ }),
1172
+ showSurvey && /*#__PURE__*/ jsx(SurveyPopup, {
1173
+ posthog: posthog,
1174
+ survey: survey,
1175
+ forceDisableHtml: forceDisableHtml,
1176
+ style: styleOverrides,
1177
+ onPopupSurveyDismissed: resetShowSurvey,
1178
+ onCloseConfirmationMessage: resetShowSurvey,
1179
+ surveyLanguage: surveyLanguage
1180
+ })
1181
+ ]
1182
+ });
1183
+ }
1184
+ const getQuestionComponent = ({ question, forceDisableHtml, displayQuestionIndex, appearance, onSubmit, onPreviewSubmit, initialValue, canGoBack, onBack })=>{
1185
+ const baseProps = {
1186
+ forceDisableHtml,
1187
+ appearance,
1188
+ onPreviewSubmit: (res)=>{
1189
+ onPreviewSubmit(res);
1190
+ },
1191
+ onSubmit: (res)=>{
1192
+ onSubmit(res);
1193
+ },
1194
+ initialValue,
1195
+ displayQuestionIndex,
1196
+ canGoBack,
1197
+ onBack
1198
+ };
1199
+ switch(question.type){
1200
+ case SurveyQuestionType.Open:
1201
+ return /*#__PURE__*/ createElement(OpenTextQuestion, {
1202
+ ...baseProps,
1203
+ question: question,
1204
+ key: question.id
1205
+ });
1206
+ case SurveyQuestionType.Link:
1207
+ return /*#__PURE__*/ createElement(LinkQuestion, {
1208
+ ...baseProps,
1209
+ question: question,
1210
+ key: question.id
1211
+ });
1212
+ case SurveyQuestionType.Rating:
1213
+ return /*#__PURE__*/ createElement(RatingQuestion, {
1214
+ ...baseProps,
1215
+ question: question,
1216
+ key: question.id
1217
+ });
1218
+ case SurveyQuestionType.SingleChoice:
1219
+ case SurveyQuestionType.MultipleChoice:
1220
+ return /*#__PURE__*/ createElement(MultipleChoiceQuestion, {
1221
+ ...baseProps,
1222
+ question: question,
1223
+ key: question.id
1224
+ });
1225
+ default:
1226
+ SURVEY_LOGGER.error(`Unsupported question type: ${question.type}`);
1227
+ return null;
1228
+ }
1229
+ };
1230
+ export { FeedbackWidget, INTRO_SCREEN_PREVIEW_INDEX, Questions, SurveyManager, SurveyPopup, generateSurveys, getNextSurveyStep, renderFeedbackWidgetPreview, renderSurveysPreview, useHideSurveyOnURLChange, usePopupVisibility };