posthog-js
Version:
Posthog-js allows you to automatically capture usage and send events to PostHog.
214 lines (213 loc) • 10.4 kB
TypeScript
import { type JSX, type RefObject } from 'preact';
import { PostHog } from '../posthog-core';
import { DisplaySurveyPopoverOptions, Survey, SurveyCallback, SurveyWithTypeAndAppearance } from '../posthog-surveys-types';
import { Properties } from '../types';
import { getNextSurveyStep } from '../utils/survey-branching';
export { getNextSurveyStep };
/**
* `previewPageIndex` sentinel for the intro screen — the leading mirror of the confirmation
* page's `survey.questions.length` sentinel. Also serves as a capability marker for the main
* app: bundles without this export cannot render the intro screen preview.
*/
export declare const INTRO_SCREEN_PREVIEW_INDEX = -1;
export declare class SurveyManager {
private _posthog;
private _surveyInFocus;
private _surveyTimeouts;
private _widgetSelectorListeners;
private _renderedTargets;
private _prefillHandledSurveys;
private _automaticDisplayDispose?;
private _currentLanguage;
private _surveyIsRendered;
private _languageChangeListener;
private _unsubscribeFeatureFlags;
private _surveyPopupProps;
private _displayOptions;
constructor(posthog: PostHog);
private _onLanguageChange;
private get _featureFlags();
private _applyDisplayOverrides;
handlePageUnload: () => void;
private _clearSurveyTimeout;
/**
* cancel a "pending" survey, i.e. one that is waiting to be displayed in
* the _surveyTimeouts queue
*/
cancelSurvey(surveyId: string): void;
setAutomaticDisplayDispose(dispose: () => void): void;
dispose(): void;
/**
* `resumeDelayFromActivation` is internal to the display loop: a survey armed by an
* event/action trigger resumes its popup delay from when the trigger fired, so navigating
* mid-delay does not restart the countdown. An explicit `displaySurvey()` call carries its own
* `ignoreDelay` option instead, so it must always honor the full configured delay.
*/
handlePopoverSurvey: (surveyParam: Survey, options?: DisplaySurveyPopoverOptions, { resumeDelayFromActivation }?: {
resumeDelayFromActivation?: boolean;
}) => void;
private _handleWidget;
private _detachWidgetSelectorListener;
private _removeWidgetSelectorListener;
private _isWidgetSurveyOpen;
private _manageWidgetSelectorListener;
/**
* 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
*/
private _sortSurveysByAppearanceDelay;
renderPopover: (survey: Survey) => void;
renderSurvey: (survey: Survey, selector: Element, properties?: Properties) => void;
private _translateSurveyForRendering;
private _handleUrlPrefill;
/**
* On load, capture a response for prefilled questions only when:
* 1) the survey is complete, OR
* 2) partial responses are enabled AND the skipped questions were set to auto-submit.
*/
private _autoSubmitPrefilledResponses;
/**
* process prefilled responses for non-hosted surveys
*
* @returns boolean, true if the survey is completed
*/
private _handleInitialResponses;
private _processPrefillData;
private _isSurveyFeatureFlagEnabled;
private _isSurveyConditionMatched;
private _internalFlagCheckSatisfied;
checkSurveyEligibility(survey: Survey): {
eligible: boolean;
reason?: string;
};
/**
* Renderability = eligibility (running, type, flags, wait period, already-seen) plus the
* survey's event/action activation trigger. Used by the programmatic `canRenderSurvey` /
* `canRenderSurveyAsync` checks so they match the display loop.
*
* The trigger is intentionally kept out of `checkSurveyEligibility`: that method is also
* used by the explicit `displaySurvey` path, and the trigger state only lives in memory
* (a reload clears it, server-side events never set it). Gating eligibility on it would
* make explicit `displaySurvey('id')` calls silently show nothing.
*/
checkSurveyRenderability(survey: Survey): {
eligible: boolean;
reason?: string;
};
/**
* Surveys can be activated by events or actions. This method checks if the survey has events and actions,
* and if so, it checks if the survey has been activated.
* @param survey
*/
private _hasActionOrEventTriggeredSurvey;
private _checkFlags;
/**
* The full predicate for "should this survey display right now": eligibility (running,
* type, linked/targeting/internal flags, wait period, already-seen) plus the URL/device/
* selector conditions, the event/action trigger, and any feature-flag dependencies.
*
* Shared by the display loop and re-checked when a popover's delay timer fires, so a
* survey that became ineligible *during* the delay (e.g. an identify() reloaded flags and
* the internal targeting flag is now false) is not shown. Note this is purely an AND gate:
* adding it can only ever suppress a display, never cause an extra one.
*/
private _shouldDisplaySurvey;
getActiveMatchingSurveys: (callback: SurveyCallback, forceReload?: boolean) => void;
callSurveysAndEvaluateDisplayLogic: (forceReload?: boolean) => void;
private _addSurveyToFocus;
private _removeSurveyFromDom;
private _removeSurveyFromFocus;
getTestAPI(): {
addSurveyToFocus: (survey: Pick<Survey, "id">) => void;
removeSurveyFromFocus: (survey: SurveyWithTypeAndAppearance) => void;
surveyInFocus: string | null;
surveyTimeouts: Map<string, number>;
handleWidget: (survey: Survey) => void;
handlePopoverSurvey: (surveyParam: Survey, options?: DisplaySurveyPopoverOptions, { resumeDelayFromActivation }?: {
resumeDelayFromActivation?: boolean;
}) => void;
manageWidgetSelectorListener: (survey: Survey, selector: string) => void;
sortSurveysByAppearanceDelay: (surveys: Survey[]) => Survey[];
checkFlags: (survey: Survey) => boolean;
isSurveyFeatureFlagEnabled: (flagKey: string | null, flagVariant?: string | undefined) => boolean;
onLanguageChange: () => void;
currentLanguage: string | null;
surveyIsRendered: boolean;
};
}
export declare const renderSurveysPreview: ({ survey, parentElement, previewPageIndex, forceDisableHtml, onPreviewSubmit, onPreviewBack, positionStyles, }: {
survey: Survey;
parentElement: HTMLElement;
previewPageIndex: number;
forceDisableHtml?: boolean;
onPreviewSubmit?: (res: string | string[] | number | null) => void;
onPreviewBack?: () => void;
posthog?: PostHog;
positionStyles?: JSX.CSSProperties;
}) => void;
export declare const renderFeedbackWidgetPreview: ({ survey, root, forceDisableHtml, }: {
survey: Survey;
root: HTMLElement;
forceDisableHtml?: boolean;
}) => void;
export declare function generateSurveys(posthog: PostHog, isSurveysEnabled: boolean | undefined): SurveyManager | undefined;
type UseHideSurveyOnURLChangeProps = {
survey: Pick<Survey, 'id' | 'conditions' | 'type' | 'appearance'>;
removeSurveyFromFocus?: (survey: SurveyWithTypeAndAppearance) => void;
setSurveyVisible: (visible: boolean) => void;
isPreviewMode?: boolean;
posthog?: PostHog;
};
/**
* 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 declare function useHideSurveyOnURLChange({ survey, removeSurveyFromFocus, setSurveyVisible, isPreviewMode, posthog, }: UseHideSurveyOnURLChangeProps): void;
export declare function usePopupVisibility(survey: Survey, posthog: PostHog | undefined, millisecondDelay: number, isPreviewMode: boolean, removeSurveyFromFocus: (survey: SurveyWithTypeAndAppearance) => void, isPopup: boolean, surveyContainerRef?: RefObject<HTMLDivElement>, skipShownEvent?: boolean, surveyLanguage?: string | null): {
isPopupVisible: boolean;
isSurveySent: boolean;
setIsPopupVisible: import("preact/hooks").Dispatch<import("preact/hooks").StateUpdater<boolean>>;
hidePopupWithAnimation: () => void;
};
interface SurveyPopupProps {
survey: Survey;
forceDisableHtml?: boolean;
posthog?: PostHog;
style?: JSX.CSSProperties;
previewPageIndex?: number | undefined;
removeSurveyFromFocus?: (survey: SurveyWithTypeAndAppearance) => void;
isPopup?: boolean;
onPreviewSubmit?: (res: string | string[] | number | null) => void;
onPreviewBack?: () => void;
onPopupSurveyDismissed?: () => void;
onCloseConfirmationMessage?: () => void;
/** Additional properties to include in all survey events */
properties?: Properties;
/** if true, survey popup will only render if there is a thank-you message */
isSurveyCompleted?: boolean;
/** When true, `survey shown` events will not be emitted automatically */
skipShownEvent?: boolean;
/** The language that was applied to the survey. */
surveyLanguage?: string | null;
}
export declare function SurveyPopup({ survey, forceDisableHtml, posthog, style, previewPageIndex, removeSurveyFromFocus, isPopup, onPreviewSubmit, onPreviewBack, onPopupSurveyDismissed, onCloseConfirmationMessage, properties, isSurveyCompleted, skipShownEvent, surveyLanguage, }: SurveyPopupProps): JSX.Element | null;
export declare function Questions({ survey, forceDisableHtml, posthog, }: {
survey: Survey;
forceDisableHtml: boolean;
posthog?: PostHog;
}): JSX.Element | null;
export declare function FeedbackWidget({ survey, forceDisableHtml, posthog, readOnly, surveyLanguage, }: {
survey: Survey;
forceDisableHtml?: boolean;
posthog?: PostHog;
readOnly?: boolean;
surveyLanguage?: string | null;
}): JSX.Element | null;