@flamingo-stack/openframe-frontend-core 0.0.671 → 0.0.672-snapshot.20260928032919
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.
- package/dist/chat-protocol/card-marker.d.ts +12 -0
- package/dist/chat-protocol/card-marker.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +17 -0
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.d.ts +1 -0
- package/dist/chat-protocol/index.d.ts.map +1 -1
- package/dist/chat-protocol/index.js +15 -1
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/{chunk-4PVY4JJN.js → chunk-2TQ64TG3.js} +3 -3
- package/dist/{chunk-FAJUQKWQ.cjs → chunk-3EIB5PNM.cjs} +76 -75
- package/dist/chunk-3EIB5PNM.cjs.map +1 -0
- package/dist/{chunk-FUXUJSPP.cjs → chunk-3UDBK2YG.cjs} +1 -1
- package/dist/chunk-3UDBK2YG.cjs.map +1 -0
- package/dist/{chunk-G64BNLDO.js → chunk-3X6WFYSV.js} +2 -2
- package/dist/{chunk-DUZEFACP.js → chunk-4UAIOXYC.js} +3 -3
- package/dist/{chunk-SNNE34OP.js → chunk-5KD5SSP6.js} +5 -5
- package/dist/{chunk-RKUCK6MN.js → chunk-6NGIPAQ6.js} +2 -2
- package/dist/{chunk-OG2BNJ2Q.cjs → chunk-6PPEG2N4.cjs} +389 -375
- package/dist/chunk-6PPEG2N4.cjs.map +1 -0
- package/dist/{chunk-NIYIK32F.js → chunk-6ZX6TFSS.js} +18 -4
- package/dist/{chunk-NIYIK32F.js.map → chunk-6ZX6TFSS.js.map} +1 -1
- package/dist/{chunk-6C6UUOH4.js → chunk-A77HP3N6.js} +8 -7
- package/dist/{chunk-6C6UUOH4.js.map → chunk-A77HP3N6.js.map} +1 -1
- package/dist/{chunk-YNGAJGYB.cjs → chunk-AXMNWMAU.cjs} +340 -6
- package/dist/chunk-AXMNWMAU.cjs.map +1 -0
- package/dist/{chunk-PLG6LP7E.cjs → chunk-BAEHCDPI.cjs} +49 -49
- package/dist/{chunk-PLG6LP7E.cjs.map → chunk-BAEHCDPI.cjs.map} +1 -1
- package/dist/{chunk-R22IA7MP.js → chunk-BBDEIE56.js} +4 -4
- package/dist/{chunk-PQ7YC5Y6.js → chunk-D5HO3RNF.js} +3 -3
- package/dist/{chunk-II66XWU5.cjs → chunk-D6IOMPAH.cjs} +3 -3
- package/dist/{chunk-II66XWU5.cjs.map → chunk-D6IOMPAH.cjs.map} +1 -1
- package/dist/{chunk-DAGKBFZN.cjs → chunk-EQIBWG5U.cjs} +33 -33
- package/dist/{chunk-DAGKBFZN.cjs.map → chunk-EQIBWG5U.cjs.map} +1 -1
- package/dist/{chunk-3PVOF3IB.js → chunk-EVB3EO5S.js} +337 -3
- package/dist/{chunk-3PVOF3IB.js.map → chunk-EVB3EO5S.js.map} +1 -1
- package/dist/{chunk-SJZPKIW6.cjs → chunk-G64BDYU5.cjs} +12 -12
- package/dist/{chunk-SJZPKIW6.cjs.map → chunk-G64BDYU5.cjs.map} +1 -1
- package/dist/{chunk-BP542DGQ.cjs → chunk-GLPGYU6H.cjs} +55 -28
- package/dist/chunk-GLPGYU6H.cjs.map +1 -0
- package/dist/{chunk-TLLTL6LB.js → chunk-JD4U7QMG.js} +1 -1
- package/dist/chunk-JD4U7QMG.js.map +1 -0
- package/dist/{chunk-ZRIVCLPR.js → chunk-JD5TKRCU.js} +1 -1
- package/dist/chunk-JD5TKRCU.js.map +1 -0
- package/dist/{chunk-YJWR7D6O.cjs → chunk-JKIM7U7N.cjs} +3 -3
- package/dist/{chunk-YJWR7D6O.cjs.map → chunk-JKIM7U7N.cjs.map} +1 -1
- package/dist/{chunk-K3O4D7LW.js → chunk-KUASHD7Z.js} +4 -4
- package/dist/{chunk-GF5TSQ6C.cjs → chunk-KXL534EV.cjs} +10 -10
- package/dist/{chunk-GF5TSQ6C.cjs.map → chunk-KXL534EV.cjs.map} +1 -1
- package/dist/{chunk-FHTNCWIP.js → chunk-KYVFU7VQ.js} +5 -5
- package/dist/{chunk-OYMOG544.js → chunk-KZAT6VWE.js} +5 -5
- package/dist/{chunk-D55IENZJ.cjs → chunk-LCMT2GZK.cjs} +1 -1
- package/dist/chunk-LCMT2GZK.cjs.map +1 -0
- package/dist/{chunk-IJIIL6WU.js → chunk-MVU7HF5A.js} +6 -6
- package/dist/{chunk-XGXHHSDU.cjs → chunk-NYDFVUMH.cjs} +22 -22
- package/dist/{chunk-XGXHHSDU.cjs.map → chunk-NYDFVUMH.cjs.map} +1 -1
- package/dist/{chunk-STLBB6PF.js → chunk-O75Y5O3U.js} +35 -8
- package/dist/chunk-O75Y5O3U.js.map +1 -0
- package/dist/{chunk-DAWYS25M.cjs → chunk-PDNGWDJF.cjs} +17 -17
- package/dist/{chunk-DAWYS25M.cjs.map → chunk-PDNGWDJF.cjs.map} +1 -1
- package/dist/{chunk-T3ACDOVD.cjs → chunk-VRZZCL4R.cjs} +7 -7
- package/dist/{chunk-T3ACDOVD.cjs.map → chunk-VRZZCL4R.cjs.map} +1 -1
- package/dist/{chunk-XH4MRTZM.cjs → chunk-WQFT7GOC.cjs} +5 -5
- package/dist/{chunk-XH4MRTZM.cjs.map → chunk-WQFT7GOC.cjs.map} +1 -1
- package/dist/{chunk-YIDNSRPL.cjs → chunk-XGBFQ3YV.cjs} +31 -31
- package/dist/{chunk-YIDNSRPL.cjs.map → chunk-XGBFQ3YV.cjs.map} +1 -1
- package/dist/{chunk-UH3BL2OT.js → chunk-Y7TZHPS2.js} +2 -2
- package/dist/{chunk-EIAVE6SE.cjs → chunk-YKKW67A3.cjs} +10 -10
- package/dist/{chunk-EIAVE6SE.cjs.map → chunk-YKKW67A3.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +18 -17
- package/dist/components/case-studies/index.cjs.map +1 -1
- package/dist/components/case-studies/index.js +7 -6
- package/dist/components/case-studies/index.js.map +1 -1
- package/dist/components/case-studies/share-experience-section.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/contact-form.d.ts +6 -1
- package/dist/components/contact/contact-form.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +6 -6
- package/dist/components/contact/index.js +5 -5
- package/dist/components/docs/index.cjs +9 -9
- package/dist/components/docs/index.js +8 -8
- package/dist/components/embeds/index.cjs +6 -6
- package/dist/components/embeds/index.js +5 -5
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +5 -5
- package/dist/components/features/index.js +4 -4
- package/dist/components/features/waitlist-form.d.ts +3 -1
- package/dist/components/features/waitlist-form.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +77 -76
- package/dist/components/help-center-pages/index.cjs.map +1 -1
- package/dist/components/help-center-pages/index.js +17 -16
- package/dist/components/help-center-pages/index.js.map +1 -1
- package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
- package/dist/components/index.cjs +43 -43
- package/dist/components/index.js +15 -15
- package/dist/components/meeting-scheduler/booking-form.d.ts +4 -1
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +73 -39
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +44 -10
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +8 -8
- package/dist/components/navigation/index.js +7 -7
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/tickets/help-center-create-form.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +9 -9
- package/dist/components/tickets/index.js +8 -8
- package/dist/components/ui/index.cjs +5 -5
- package/dist/components/ui/index.js +4 -4
- package/dist/contexts/endpoints-runtime-context.d.ts +5 -0
- package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/hooks/index.cjs +5 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -2
- package/dist/hooks/use-form-rescue.d.ts +37 -0
- package/dist/hooks/use-form-rescue.d.ts.map +1 -0
- package/dist/index.cjs +51 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +50 -8
- package/dist/index.js.map +1 -1
- package/dist/utils/form-rescue.cjs +117 -0
- package/dist/utils/form-rescue.cjs.map +1 -0
- package/dist/utils/form-rescue.d.ts +98 -0
- package/dist/utils/form-rescue.d.ts.map +1 -0
- package/dist/utils/form-rescue.js +96 -0
- package/dist/utils/form-rescue.js.map +1 -0
- package/dist/utils/index.cjs +113 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +94 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +7 -1
- package/src/chat-protocol/__tests__/card-marker.test.ts +27 -1
- package/src/chat-protocol/card-marker.ts +24 -0
- package/src/chat-protocol/index.ts +3 -0
- package/src/components/case-studies/share-experience-section.tsx +1 -0
- package/src/components/contact/contact-form.tsx +38 -2
- package/src/components/features/waitlist-form.tsx +23 -2
- package/src/components/help-center-pages/trust-center-sections.tsx +1 -0
- package/src/components/meeting-scheduler/booking-form.tsx +14 -0
- package/src/components/meeting-scheduler/index.tsx +33 -3
- package/src/components/tickets/help-center-create-form.tsx +2 -0
- package/src/contexts/endpoints-runtime-context.tsx +5 -0
- package/src/hooks/__tests__/use-form-rescue.test.tsx +182 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/use-form-rescue.ts +315 -0
- package/src/utils/__tests__/form-rescue.test.ts +106 -0
- package/src/utils/form-rescue.ts +190 -0
- package/src/utils/index.ts +5 -0
- package/dist/chunk-BP542DGQ.cjs.map +0 -1
- package/dist/chunk-D55IENZJ.cjs.map +0 -1
- package/dist/chunk-FAJUQKWQ.cjs.map +0 -1
- package/dist/chunk-FUXUJSPP.cjs.map +0 -1
- package/dist/chunk-OG2BNJ2Q.cjs.map +0 -1
- package/dist/chunk-STLBB6PF.js.map +0 -1
- package/dist/chunk-TLLTL6LB.js.map +0 -1
- package/dist/chunk-YNGAJGYB.cjs.map +0 -1
- package/dist/chunk-ZRIVCLPR.js.map +0 -1
- /package/dist/{chunk-4PVY4JJN.js.map → chunk-2TQ64TG3.js.map} +0 -0
- /package/dist/{chunk-G64BNLDO.js.map → chunk-3X6WFYSV.js.map} +0 -0
- /package/dist/{chunk-DUZEFACP.js.map → chunk-4UAIOXYC.js.map} +0 -0
- /package/dist/{chunk-SNNE34OP.js.map → chunk-5KD5SSP6.js.map} +0 -0
- /package/dist/{chunk-RKUCK6MN.js.map → chunk-6NGIPAQ6.js.map} +0 -0
- /package/dist/{chunk-R22IA7MP.js.map → chunk-BBDEIE56.js.map} +0 -0
- /package/dist/{chunk-PQ7YC5Y6.js.map → chunk-D5HO3RNF.js.map} +0 -0
- /package/dist/{chunk-K3O4D7LW.js.map → chunk-KUASHD7Z.js.map} +0 -0
- /package/dist/{chunk-FHTNCWIP.js.map → chunk-KYVFU7VQ.js.map} +0 -0
- /package/dist/{chunk-OYMOG544.js.map → chunk-KZAT6VWE.js.map} +0 -0
- /package/dist/{chunk-IJIIL6WU.js.map → chunk-MVU7HF5A.js.map} +0 -0
- /package/dist/{chunk-UH3BL2OT.js.map → chunk-Y7TZHPS2.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/form-rescue.ts"],"names":[],"mappings":"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","file":"form-rescue.cjs","sourcesContent":["/**\n * Form rescue — the shared rules for saving a half-filled public form so the\n * team can follow up with a visitor who left before submitting.\n *\n * Pure and server-safe: the browser hook (`hooks/use-form-rescue`) and the\n * host's draft endpoint import the SAME allowlist, exclusions, caps and\n * completion math, so the server re-applies exactly the filter the client ran\n * and a rename here reaches both sides at once. Also exported through the\n * granular subpath `./utils/form-rescue` for server-only consumers.\n *\n * What a draft may carry:\n * - values of ALLOWLISTED contact fields only (`FORM_RESCUE_VALUE_FIELDS`),\n * trimmed and capped at `FORM_RESCUE_MAX_VALUE_CHARS`;\n * - for every other field, only whether it is filled (never its value);\n * - nothing whose name looks like a credential or payment field\n * (`isExcludedRescueField`), whatever the allowlist says.\n *\n * Field values go to the host only, never to analytics: the PostHog events\n * (`captureFormRescueEvent`) carry ids, field names and percentages.\n */\n\n/** Every public form that can be rescued. The host validates `form_id` against this list. */\nexport const FORM_RESCUE_FORM_IDS = [\n 'contact',\n 'case_study_pitch',\n 'data_room_request',\n 'trust_center_request',\n 'tmcg_join',\n 'meeting_booking',\n 'waitlist',\n] as const;\n\nexport type FormRescueFormId = (typeof FORM_RESCUE_FORM_IDS)[number];\n\nexport function isFormRescueFormId(value: unknown): value is FormRescueFormId {\n return typeof value === 'string' && (FORM_RESCUE_FORM_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Fields whose VALUES a draft keeps: who the visitor is and where they work.\n * Covers both naming schemes in use (the contact form's `name` / `companySize`,\n * the meeting booking's HubSpot `firstName` / `company` / `jobtitle`).\n * Free text (`message`) and phone numbers are reported as filled only.\n */\nexport const FORM_RESCUE_VALUE_FIELDS = [\n 'email',\n 'name',\n 'firstName',\n 'lastName',\n 'company',\n 'jobtitle',\n 'companySize',\n 'linkedin_url',\n] as const;\n\n/** Never send or store these, whatever the allowlist says. */\nconst EXCLUDED_FIELD_PATTERN =\n /pass(word|code)?|card|cvv|cvc|ssn|social.?security|iban|bank|routing|token|secret|otp|pin$/i;\n\nexport function isExcludedRescueField(name: string): boolean {\n return EXCLUDED_FIELD_PATTERN.test(name);\n}\n\n/** Longest value a draft keeps per field. */\nexport const FORM_RESCUE_MAX_VALUE_CHARS = 200;\n/** Save this long after the visitor stops typing. */\nexport const FORM_RESCUE_DEBOUNCE_MS = 1500;\n/** Most fields a draft reports (filled names), a bound on the payload. */\nexport const FORM_RESCUE_MAX_FIELDS = 40;\n\n/** Body keys a form's SUBMIT carries so the host can close the matching draft. */\nexport const FORM_RESCUE_ATTEMPT_FIELD = 'form_attempt_id';\nexport const FORM_RESCUE_RESUME_FIELD = 'form_resume_token';\n/** Every rescue key that rides in a submit body. Hosts forwarding a payload upstream strip by THIS array. */\nexport const FORM_RESCUE_SUBMIT_KEYS = [FORM_RESCUE_ATTEMPT_FIELD, FORM_RESCUE_RESUME_FIELD] as const;\n\n/** Query parameter a resume link carries. */\nexport const FORM_RESCUE_RESUME_PARAM = 'resume';\n\n/** Draft lifecycle. `submitted` is final; `rescued` can still become `submitted`. */\nexport const FORM_DRAFT_STATUSES = ['started', 'rescuable', 'rescued', 'submitted'] as const;\nexport type FormDraftStatus = (typeof FORM_DRAFT_STATUSES)[number];\n\n/** Same shape the host's `contact_submissions.email` check accepts. */\nconst EMAIL_PATTERN = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$/;\n\nexport function isRescueEmail(value: unknown): value is string {\n return typeof value === 'string' && value.length <= FORM_RESCUE_MAX_VALUE_CHARS && EMAIL_PATTERN.test(value.trim());\n}\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function isFormAttemptId(value: unknown): value is string {\n return typeof value === 'string' && UUID_PATTERN.test(value);\n}\n\nfunction isFilled(value: unknown): boolean {\n if (value == null) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (typeof value === 'boolean') return value;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.values(value as Record<string, unknown>).some(isFilled);\n return true;\n}\n\n/**\n * Keep only allowlisted, non-excluded string values, trimmed and capped.\n * The ONE filter, run by the browser before sending and by the host before storing.\n */\nexport function sanitizeRescueValues(values: Record<string, unknown> | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!values || typeof values !== 'object') return out;\n for (const key of FORM_RESCUE_VALUE_FIELDS) {\n if (isExcludedRescueField(key)) continue;\n const raw = values[key];\n if (typeof raw !== 'string') continue;\n const trimmed = raw.trim().slice(0, FORM_RESCUE_MAX_VALUE_CHARS);\n if (trimmed) out[key] = trimmed;\n }\n return out;\n}\n\n/** Names of the given fields that hold something, credentials never included. */\nexport function filledRescueFields(values: Record<string, unknown>, fieldNames: readonly string[]): string[] {\n return fieldNames\n .filter(name => !isExcludedRescueField(name) && isFilled(values[name]))\n .slice(0, FORM_RESCUE_MAX_FIELDS);\n}\n\nexport function rescueCompletionPct(filledCount: number, totalCount: number): number {\n if (totalCount <= 0) return 0;\n return Math.max(0, Math.min(100, Math.round((filledCount / totalCount) * 100)));\n}\n\n/** A field NAME the host stores (`last_field`, `fields_filled`); bounded, printable. */\nexport function sanitizeRescueFieldName(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return /^[A-Za-z0-9_.-]{1,64}$/.test(trimmed) && !isExcludedRescueField(trimmed) ? trimmed : null;\n}\n\n/** A form's progress at one moment: allowlisted values, filled field names, position. */\nexport interface FormDraftProgress {\n values: Record<string, string>;\n fields_filled: string[];\n last_field: string | null;\n completion_pct: number;\n}\n\n/** What the browser sends on every save (`POST <formDraftsUrl>`), beside the humanity signals. */\nexport interface FormDraftSaveRequest extends FormDraftProgress {\n /** The attempt's idempotency key: every save of one attempt updates one draft. */\n attempt_id: string;\n form_id: FormRescueFormId;\n source_path: string;\n utm?: Partial<Record<'source' | 'medium' | 'campaign' | 'content' | 'term', string>>;\n}\n\n/** What the host answers a resume link with: the allowlisted values, nothing internal. */\nexport interface FormDraftResumeResponse {\n form_id: FormRescueFormId;\n values: Record<string, string>;\n}\n\n/** The PostHog events this feature emits. Properties are ids, names and numbers, never values. */\nexport const FORM_RESCUE_EVENTS = {\n started: 'form_started',\n progress: 'form_progress',\n leadCaptured: 'form_lead_captured',\n abandoned: 'form_abandoned',\n submitted: 'form_submitted',\n rescued: 'form_rescued',\n resumed: 'form_resumed',\n} as const;\n\ntype AnalyticsValue = string | number | boolean | string[] | null;\n\n/**\n * Send one form event to PostHog when the page loaded it (GTM exposes\n * `window.posthog`); a no-op otherwise. Never throws.\n */\nexport function captureFormRescueEvent(event: string, properties: Record<string, AnalyticsValue>): void {\n try {\n if (typeof window === 'undefined') return;\n const ph = (window as unknown as { posthog?: { capture?: (e: string, p: object) => void } }).posthog;\n if (typeof ph?.capture === 'function') ph.capture(event, properties);\n } catch {\n // Analytics never breaks a form.\n }\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form rescue — the shared rules for saving a half-filled public form so the
|
|
3
|
+
* team can follow up with a visitor who left before submitting.
|
|
4
|
+
*
|
|
5
|
+
* Pure and server-safe: the browser hook (`hooks/use-form-rescue`) and the
|
|
6
|
+
* host's draft endpoint import the SAME allowlist, exclusions, caps and
|
|
7
|
+
* completion math, so the server re-applies exactly the filter the client ran
|
|
8
|
+
* and a rename here reaches both sides at once. Also exported through the
|
|
9
|
+
* granular subpath `./utils/form-rescue` for server-only consumers.
|
|
10
|
+
*
|
|
11
|
+
* What a draft may carry:
|
|
12
|
+
* - values of ALLOWLISTED contact fields only (`FORM_RESCUE_VALUE_FIELDS`),
|
|
13
|
+
* trimmed and capped at `FORM_RESCUE_MAX_VALUE_CHARS`;
|
|
14
|
+
* - for every other field, only whether it is filled (never its value);
|
|
15
|
+
* - nothing whose name looks like a credential or payment field
|
|
16
|
+
* (`isExcludedRescueField`), whatever the allowlist says.
|
|
17
|
+
*
|
|
18
|
+
* Field values go to the host only, never to analytics: the PostHog events
|
|
19
|
+
* (`captureFormRescueEvent`) carry ids, field names and percentages.
|
|
20
|
+
*/
|
|
21
|
+
/** Every public form that can be rescued. The host validates `form_id` against this list. */
|
|
22
|
+
export declare const FORM_RESCUE_FORM_IDS: readonly ["contact", "case_study_pitch", "data_room_request", "trust_center_request", "tmcg_join", "meeting_booking", "waitlist"];
|
|
23
|
+
export type FormRescueFormId = (typeof FORM_RESCUE_FORM_IDS)[number];
|
|
24
|
+
export declare function isFormRescueFormId(value: unknown): value is FormRescueFormId;
|
|
25
|
+
/**
|
|
26
|
+
* Fields whose VALUES a draft keeps: who the visitor is and where they work.
|
|
27
|
+
* Covers both naming schemes in use (the contact form's `name` / `companySize`,
|
|
28
|
+
* the meeting booking's HubSpot `firstName` / `company` / `jobtitle`).
|
|
29
|
+
* Free text (`message`) and phone numbers are reported as filled only.
|
|
30
|
+
*/
|
|
31
|
+
export declare const FORM_RESCUE_VALUE_FIELDS: readonly ["email", "name", "firstName", "lastName", "company", "jobtitle", "companySize", "linkedin_url"];
|
|
32
|
+
export declare function isExcludedRescueField(name: string): boolean;
|
|
33
|
+
/** Longest value a draft keeps per field. */
|
|
34
|
+
export declare const FORM_RESCUE_MAX_VALUE_CHARS = 200;
|
|
35
|
+
/** Save this long after the visitor stops typing. */
|
|
36
|
+
export declare const FORM_RESCUE_DEBOUNCE_MS = 1500;
|
|
37
|
+
/** Most fields a draft reports (filled names), a bound on the payload. */
|
|
38
|
+
export declare const FORM_RESCUE_MAX_FIELDS = 40;
|
|
39
|
+
/** Body keys a form's SUBMIT carries so the host can close the matching draft. */
|
|
40
|
+
export declare const FORM_RESCUE_ATTEMPT_FIELD = "form_attempt_id";
|
|
41
|
+
export declare const FORM_RESCUE_RESUME_FIELD = "form_resume_token";
|
|
42
|
+
/** Every rescue key that rides in a submit body. Hosts forwarding a payload upstream strip by THIS array. */
|
|
43
|
+
export declare const FORM_RESCUE_SUBMIT_KEYS: readonly ["form_attempt_id", "form_resume_token"];
|
|
44
|
+
/** Query parameter a resume link carries. */
|
|
45
|
+
export declare const FORM_RESCUE_RESUME_PARAM = "resume";
|
|
46
|
+
/** Draft lifecycle. `submitted` is final; `rescued` can still become `submitted`. */
|
|
47
|
+
export declare const FORM_DRAFT_STATUSES: readonly ["started", "rescuable", "rescued", "submitted"];
|
|
48
|
+
export type FormDraftStatus = (typeof FORM_DRAFT_STATUSES)[number];
|
|
49
|
+
export declare function isRescueEmail(value: unknown): value is string;
|
|
50
|
+
export declare function isFormAttemptId(value: unknown): value is string;
|
|
51
|
+
/**
|
|
52
|
+
* Keep only allowlisted, non-excluded string values, trimmed and capped.
|
|
53
|
+
* The ONE filter, run by the browser before sending and by the host before storing.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sanitizeRescueValues(values: Record<string, unknown> | null | undefined): Record<string, string>;
|
|
56
|
+
/** Names of the given fields that hold something, credentials never included. */
|
|
57
|
+
export declare function filledRescueFields(values: Record<string, unknown>, fieldNames: readonly string[]): string[];
|
|
58
|
+
export declare function rescueCompletionPct(filledCount: number, totalCount: number): number;
|
|
59
|
+
/** A field NAME the host stores (`last_field`, `fields_filled`); bounded, printable. */
|
|
60
|
+
export declare function sanitizeRescueFieldName(value: unknown): string | null;
|
|
61
|
+
/** A form's progress at one moment: allowlisted values, filled field names, position. */
|
|
62
|
+
export interface FormDraftProgress {
|
|
63
|
+
values: Record<string, string>;
|
|
64
|
+
fields_filled: string[];
|
|
65
|
+
last_field: string | null;
|
|
66
|
+
completion_pct: number;
|
|
67
|
+
}
|
|
68
|
+
/** What the browser sends on every save (`POST <formDraftsUrl>`), beside the humanity signals. */
|
|
69
|
+
export interface FormDraftSaveRequest extends FormDraftProgress {
|
|
70
|
+
/** The attempt's idempotency key: every save of one attempt updates one draft. */
|
|
71
|
+
attempt_id: string;
|
|
72
|
+
form_id: FormRescueFormId;
|
|
73
|
+
source_path: string;
|
|
74
|
+
utm?: Partial<Record<'source' | 'medium' | 'campaign' | 'content' | 'term', string>>;
|
|
75
|
+
}
|
|
76
|
+
/** What the host answers a resume link with: the allowlisted values, nothing internal. */
|
|
77
|
+
export interface FormDraftResumeResponse {
|
|
78
|
+
form_id: FormRescueFormId;
|
|
79
|
+
values: Record<string, string>;
|
|
80
|
+
}
|
|
81
|
+
/** The PostHog events this feature emits. Properties are ids, names and numbers, never values. */
|
|
82
|
+
export declare const FORM_RESCUE_EVENTS: {
|
|
83
|
+
readonly started: "form_started";
|
|
84
|
+
readonly progress: "form_progress";
|
|
85
|
+
readonly leadCaptured: "form_lead_captured";
|
|
86
|
+
readonly abandoned: "form_abandoned";
|
|
87
|
+
readonly submitted: "form_submitted";
|
|
88
|
+
readonly rescued: "form_rescued";
|
|
89
|
+
readonly resumed: "form_resumed";
|
|
90
|
+
};
|
|
91
|
+
type AnalyticsValue = string | number | boolean | string[] | null;
|
|
92
|
+
/**
|
|
93
|
+
* Send one form event to PostHog when the page loaded it (GTM exposes
|
|
94
|
+
* `window.posthog`); a no-op otherwise. Never throws.
|
|
95
|
+
*/
|
|
96
|
+
export declare function captureFormRescueEvent(event: string, properties: Record<string, AnalyticsValue>): void;
|
|
97
|
+
export {};
|
|
98
|
+
//# sourceMappingURL=form-rescue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-rescue.d.ts","sourceRoot":"","sources":["../../src/utils/form-rescue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/utils/form-rescue.ts
|
|
2
|
+
var FORM_RESCUE_FORM_IDS = [
|
|
3
|
+
"contact",
|
|
4
|
+
"case_study_pitch",
|
|
5
|
+
"data_room_request",
|
|
6
|
+
"trust_center_request",
|
|
7
|
+
"tmcg_join",
|
|
8
|
+
"meeting_booking",
|
|
9
|
+
"waitlist"
|
|
10
|
+
];
|
|
11
|
+
function isFormRescueFormId(value) {
|
|
12
|
+
return typeof value === "string" && FORM_RESCUE_FORM_IDS.includes(value);
|
|
13
|
+
}
|
|
14
|
+
var FORM_RESCUE_VALUE_FIELDS = [
|
|
15
|
+
"email",
|
|
16
|
+
"name",
|
|
17
|
+
"firstName",
|
|
18
|
+
"lastName",
|
|
19
|
+
"company",
|
|
20
|
+
"jobtitle",
|
|
21
|
+
"companySize",
|
|
22
|
+
"linkedin_url"
|
|
23
|
+
];
|
|
24
|
+
var EXCLUDED_FIELD_PATTERN = /pass(word|code)?|card|cvv|cvc|ssn|social.?security|iban|bank|routing|token|secret|otp|pin$/i;
|
|
25
|
+
function isExcludedRescueField(name) {
|
|
26
|
+
return EXCLUDED_FIELD_PATTERN.test(name);
|
|
27
|
+
}
|
|
28
|
+
var FORM_RESCUE_MAX_VALUE_CHARS = 200;
|
|
29
|
+
var FORM_RESCUE_DEBOUNCE_MS = 1500;
|
|
30
|
+
var FORM_RESCUE_MAX_FIELDS = 40;
|
|
31
|
+
var FORM_RESCUE_ATTEMPT_FIELD = "form_attempt_id";
|
|
32
|
+
var FORM_RESCUE_RESUME_FIELD = "form_resume_token";
|
|
33
|
+
var FORM_RESCUE_SUBMIT_KEYS = [FORM_RESCUE_ATTEMPT_FIELD, FORM_RESCUE_RESUME_FIELD];
|
|
34
|
+
var FORM_RESCUE_RESUME_PARAM = "resume";
|
|
35
|
+
var FORM_DRAFT_STATUSES = ["started", "rescuable", "rescued", "submitted"];
|
|
36
|
+
var EMAIL_PATTERN = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
|
37
|
+
function isRescueEmail(value) {
|
|
38
|
+
return typeof value === "string" && value.length <= FORM_RESCUE_MAX_VALUE_CHARS && EMAIL_PATTERN.test(value.trim());
|
|
39
|
+
}
|
|
40
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41
|
+
function isFormAttemptId(value) {
|
|
42
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
43
|
+
}
|
|
44
|
+
function isFilled(value) {
|
|
45
|
+
if (value == null) return false;
|
|
46
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
47
|
+
if (typeof value === "boolean") return value;
|
|
48
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
49
|
+
if (typeof value === "object") return Object.values(value).some(isFilled);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function sanitizeRescueValues(values) {
|
|
53
|
+
const out = {};
|
|
54
|
+
if (!values || typeof values !== "object") return out;
|
|
55
|
+
for (const key of FORM_RESCUE_VALUE_FIELDS) {
|
|
56
|
+
if (isExcludedRescueField(key)) continue;
|
|
57
|
+
const raw = values[key];
|
|
58
|
+
if (typeof raw !== "string") continue;
|
|
59
|
+
const trimmed = raw.trim().slice(0, FORM_RESCUE_MAX_VALUE_CHARS);
|
|
60
|
+
if (trimmed) out[key] = trimmed;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function filledRescueFields(values, fieldNames) {
|
|
65
|
+
return fieldNames.filter((name) => !isExcludedRescueField(name) && isFilled(values[name])).slice(0, FORM_RESCUE_MAX_FIELDS);
|
|
66
|
+
}
|
|
67
|
+
function rescueCompletionPct(filledCount, totalCount) {
|
|
68
|
+
if (totalCount <= 0) return 0;
|
|
69
|
+
return Math.max(0, Math.min(100, Math.round(filledCount / totalCount * 100)));
|
|
70
|
+
}
|
|
71
|
+
function sanitizeRescueFieldName(value) {
|
|
72
|
+
if (typeof value !== "string") return null;
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
return /^[A-Za-z0-9_.-]{1,64}$/.test(trimmed) && !isExcludedRescueField(trimmed) ? trimmed : null;
|
|
75
|
+
}
|
|
76
|
+
var FORM_RESCUE_EVENTS = {
|
|
77
|
+
started: "form_started",
|
|
78
|
+
progress: "form_progress",
|
|
79
|
+
leadCaptured: "form_lead_captured",
|
|
80
|
+
abandoned: "form_abandoned",
|
|
81
|
+
submitted: "form_submitted",
|
|
82
|
+
rescued: "form_rescued",
|
|
83
|
+
resumed: "form_resumed"
|
|
84
|
+
};
|
|
85
|
+
function captureFormRescueEvent(event, properties) {
|
|
86
|
+
try {
|
|
87
|
+
if (typeof window === "undefined") return;
|
|
88
|
+
const ph = window.posthog;
|
|
89
|
+
if (typeof ph?.capture === "function") ph.capture(event, properties);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { FORM_DRAFT_STATUSES, FORM_RESCUE_ATTEMPT_FIELD, FORM_RESCUE_DEBOUNCE_MS, FORM_RESCUE_EVENTS, FORM_RESCUE_FORM_IDS, FORM_RESCUE_MAX_FIELDS, FORM_RESCUE_MAX_VALUE_CHARS, FORM_RESCUE_RESUME_FIELD, FORM_RESCUE_RESUME_PARAM, FORM_RESCUE_SUBMIT_KEYS, FORM_RESCUE_VALUE_FIELDS, captureFormRescueEvent, filledRescueFields, isExcludedRescueField, isFormAttemptId, isFormRescueFormId, isRescueEmail, rescueCompletionPct, sanitizeRescueFieldName, sanitizeRescueValues };
|
|
95
|
+
//# sourceMappingURL=form-rescue.js.map
|
|
96
|
+
//# sourceMappingURL=form-rescue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/form-rescue.ts"],"names":[],"mappings":"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","file":"form-rescue.js","sourcesContent":["/**\n * Form rescue — the shared rules for saving a half-filled public form so the\n * team can follow up with a visitor who left before submitting.\n *\n * Pure and server-safe: the browser hook (`hooks/use-form-rescue`) and the\n * host's draft endpoint import the SAME allowlist, exclusions, caps and\n * completion math, so the server re-applies exactly the filter the client ran\n * and a rename here reaches both sides at once. Also exported through the\n * granular subpath `./utils/form-rescue` for server-only consumers.\n *\n * What a draft may carry:\n * - values of ALLOWLISTED contact fields only (`FORM_RESCUE_VALUE_FIELDS`),\n * trimmed and capped at `FORM_RESCUE_MAX_VALUE_CHARS`;\n * - for every other field, only whether it is filled (never its value);\n * - nothing whose name looks like a credential or payment field\n * (`isExcludedRescueField`), whatever the allowlist says.\n *\n * Field values go to the host only, never to analytics: the PostHog events\n * (`captureFormRescueEvent`) carry ids, field names and percentages.\n */\n\n/** Every public form that can be rescued. The host validates `form_id` against this list. */\nexport const FORM_RESCUE_FORM_IDS = [\n 'contact',\n 'case_study_pitch',\n 'data_room_request',\n 'trust_center_request',\n 'tmcg_join',\n 'meeting_booking',\n 'waitlist',\n] as const;\n\nexport type FormRescueFormId = (typeof FORM_RESCUE_FORM_IDS)[number];\n\nexport function isFormRescueFormId(value: unknown): value is FormRescueFormId {\n return typeof value === 'string' && (FORM_RESCUE_FORM_IDS as readonly string[]).includes(value);\n}\n\n/**\n * Fields whose VALUES a draft keeps: who the visitor is and where they work.\n * Covers both naming schemes in use (the contact form's `name` / `companySize`,\n * the meeting booking's HubSpot `firstName` / `company` / `jobtitle`).\n * Free text (`message`) and phone numbers are reported as filled only.\n */\nexport const FORM_RESCUE_VALUE_FIELDS = [\n 'email',\n 'name',\n 'firstName',\n 'lastName',\n 'company',\n 'jobtitle',\n 'companySize',\n 'linkedin_url',\n] as const;\n\n/** Never send or store these, whatever the allowlist says. */\nconst EXCLUDED_FIELD_PATTERN =\n /pass(word|code)?|card|cvv|cvc|ssn|social.?security|iban|bank|routing|token|secret|otp|pin$/i;\n\nexport function isExcludedRescueField(name: string): boolean {\n return EXCLUDED_FIELD_PATTERN.test(name);\n}\n\n/** Longest value a draft keeps per field. */\nexport const FORM_RESCUE_MAX_VALUE_CHARS = 200;\n/** Save this long after the visitor stops typing. */\nexport const FORM_RESCUE_DEBOUNCE_MS = 1500;\n/** Most fields a draft reports (filled names), a bound on the payload. */\nexport const FORM_RESCUE_MAX_FIELDS = 40;\n\n/** Body keys a form's SUBMIT carries so the host can close the matching draft. */\nexport const FORM_RESCUE_ATTEMPT_FIELD = 'form_attempt_id';\nexport const FORM_RESCUE_RESUME_FIELD = 'form_resume_token';\n/** Every rescue key that rides in a submit body. Hosts forwarding a payload upstream strip by THIS array. */\nexport const FORM_RESCUE_SUBMIT_KEYS = [FORM_RESCUE_ATTEMPT_FIELD, FORM_RESCUE_RESUME_FIELD] as const;\n\n/** Query parameter a resume link carries. */\nexport const FORM_RESCUE_RESUME_PARAM = 'resume';\n\n/** Draft lifecycle. `submitted` is final; `rescued` can still become `submitted`. */\nexport const FORM_DRAFT_STATUSES = ['started', 'rescuable', 'rescued', 'submitted'] as const;\nexport type FormDraftStatus = (typeof FORM_DRAFT_STATUSES)[number];\n\n/** Same shape the host's `contact_submissions.email` check accepts. */\nconst EMAIL_PATTERN = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$/;\n\nexport function isRescueEmail(value: unknown): value is string {\n return typeof value === 'string' && value.length <= FORM_RESCUE_MAX_VALUE_CHARS && EMAIL_PATTERN.test(value.trim());\n}\n\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nexport function isFormAttemptId(value: unknown): value is string {\n return typeof value === 'string' && UUID_PATTERN.test(value);\n}\n\nfunction isFilled(value: unknown): boolean {\n if (value == null) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (typeof value === 'boolean') return value;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.values(value as Record<string, unknown>).some(isFilled);\n return true;\n}\n\n/**\n * Keep only allowlisted, non-excluded string values, trimmed and capped.\n * The ONE filter, run by the browser before sending and by the host before storing.\n */\nexport function sanitizeRescueValues(values: Record<string, unknown> | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!values || typeof values !== 'object') return out;\n for (const key of FORM_RESCUE_VALUE_FIELDS) {\n if (isExcludedRescueField(key)) continue;\n const raw = values[key];\n if (typeof raw !== 'string') continue;\n const trimmed = raw.trim().slice(0, FORM_RESCUE_MAX_VALUE_CHARS);\n if (trimmed) out[key] = trimmed;\n }\n return out;\n}\n\n/** Names of the given fields that hold something, credentials never included. */\nexport function filledRescueFields(values: Record<string, unknown>, fieldNames: readonly string[]): string[] {\n return fieldNames\n .filter(name => !isExcludedRescueField(name) && isFilled(values[name]))\n .slice(0, FORM_RESCUE_MAX_FIELDS);\n}\n\nexport function rescueCompletionPct(filledCount: number, totalCount: number): number {\n if (totalCount <= 0) return 0;\n return Math.max(0, Math.min(100, Math.round((filledCount / totalCount) * 100)));\n}\n\n/** A field NAME the host stores (`last_field`, `fields_filled`); bounded, printable. */\nexport function sanitizeRescueFieldName(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return /^[A-Za-z0-9_.-]{1,64}$/.test(trimmed) && !isExcludedRescueField(trimmed) ? trimmed : null;\n}\n\n/** A form's progress at one moment: allowlisted values, filled field names, position. */\nexport interface FormDraftProgress {\n values: Record<string, string>;\n fields_filled: string[];\n last_field: string | null;\n completion_pct: number;\n}\n\n/** What the browser sends on every save (`POST <formDraftsUrl>`), beside the humanity signals. */\nexport interface FormDraftSaveRequest extends FormDraftProgress {\n /** The attempt's idempotency key: every save of one attempt updates one draft. */\n attempt_id: string;\n form_id: FormRescueFormId;\n source_path: string;\n utm?: Partial<Record<'source' | 'medium' | 'campaign' | 'content' | 'term', string>>;\n}\n\n/** What the host answers a resume link with: the allowlisted values, nothing internal. */\nexport interface FormDraftResumeResponse {\n form_id: FormRescueFormId;\n values: Record<string, string>;\n}\n\n/** The PostHog events this feature emits. Properties are ids, names and numbers, never values. */\nexport const FORM_RESCUE_EVENTS = {\n started: 'form_started',\n progress: 'form_progress',\n leadCaptured: 'form_lead_captured',\n abandoned: 'form_abandoned',\n submitted: 'form_submitted',\n rescued: 'form_rescued',\n resumed: 'form_resumed',\n} as const;\n\ntype AnalyticsValue = string | number | boolean | string[] | null;\n\n/**\n * Send one form event to PostHog when the page loaded it (GTM exposes\n * `window.posthog`); a no-op otherwise. Never throws.\n */\nexport function captureFormRescueEvent(event: string, properties: Record<string, AnalyticsValue>): void {\n try {\n if (typeof window === 'undefined') return;\n const ph = (window as unknown as { posthog?: { capture?: (e: string, p: object) => void } }).posthog;\n if (typeof ph?.capture === 'function') ph.capture(event, properties);\n } catch {\n // Analytics never breaks a form.\n }\n}\n"]}
|
package/dist/utils/index.cjs
CHANGED
|
@@ -5300,6 +5300,99 @@ function evaluateHumanitySignals(body, opts) {
|
|
|
5300
5300
|
}
|
|
5301
5301
|
var splitCsvEnv = (s) => s?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
5302
5302
|
|
|
5303
|
+
// src/utils/form-rescue.ts
|
|
5304
|
+
var FORM_RESCUE_FORM_IDS = [
|
|
5305
|
+
"contact",
|
|
5306
|
+
"case_study_pitch",
|
|
5307
|
+
"data_room_request",
|
|
5308
|
+
"trust_center_request",
|
|
5309
|
+
"tmcg_join",
|
|
5310
|
+
"meeting_booking",
|
|
5311
|
+
"waitlist"
|
|
5312
|
+
];
|
|
5313
|
+
function isFormRescueFormId(value) {
|
|
5314
|
+
return typeof value === "string" && FORM_RESCUE_FORM_IDS.includes(value);
|
|
5315
|
+
}
|
|
5316
|
+
var FORM_RESCUE_VALUE_FIELDS = [
|
|
5317
|
+
"email",
|
|
5318
|
+
"name",
|
|
5319
|
+
"firstName",
|
|
5320
|
+
"lastName",
|
|
5321
|
+
"company",
|
|
5322
|
+
"jobtitle",
|
|
5323
|
+
"companySize",
|
|
5324
|
+
"linkedin_url"
|
|
5325
|
+
];
|
|
5326
|
+
var EXCLUDED_FIELD_PATTERN = /pass(word|code)?|card|cvv|cvc|ssn|social.?security|iban|bank|routing|token|secret|otp|pin$/i;
|
|
5327
|
+
function isExcludedRescueField(name) {
|
|
5328
|
+
return EXCLUDED_FIELD_PATTERN.test(name);
|
|
5329
|
+
}
|
|
5330
|
+
var FORM_RESCUE_MAX_VALUE_CHARS = 200;
|
|
5331
|
+
var FORM_RESCUE_DEBOUNCE_MS = 1500;
|
|
5332
|
+
var FORM_RESCUE_MAX_FIELDS = 40;
|
|
5333
|
+
var FORM_RESCUE_ATTEMPT_FIELD = "form_attempt_id";
|
|
5334
|
+
var FORM_RESCUE_RESUME_FIELD = "form_resume_token";
|
|
5335
|
+
var FORM_RESCUE_SUBMIT_KEYS = [FORM_RESCUE_ATTEMPT_FIELD, FORM_RESCUE_RESUME_FIELD];
|
|
5336
|
+
var FORM_RESCUE_RESUME_PARAM = "resume";
|
|
5337
|
+
var FORM_DRAFT_STATUSES = ["started", "rescuable", "rescued", "submitted"];
|
|
5338
|
+
var EMAIL_PATTERN = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
|
5339
|
+
function isRescueEmail(value) {
|
|
5340
|
+
return typeof value === "string" && value.length <= FORM_RESCUE_MAX_VALUE_CHARS && EMAIL_PATTERN.test(value.trim());
|
|
5341
|
+
}
|
|
5342
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5343
|
+
function isFormAttemptId(value) {
|
|
5344
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
5345
|
+
}
|
|
5346
|
+
function isFilled(value) {
|
|
5347
|
+
if (value == null) return false;
|
|
5348
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
5349
|
+
if (typeof value === "boolean") return value;
|
|
5350
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
5351
|
+
if (typeof value === "object") return Object.values(value).some(isFilled);
|
|
5352
|
+
return true;
|
|
5353
|
+
}
|
|
5354
|
+
function sanitizeRescueValues(values) {
|
|
5355
|
+
const out = {};
|
|
5356
|
+
if (!values || typeof values !== "object") return out;
|
|
5357
|
+
for (const key of FORM_RESCUE_VALUE_FIELDS) {
|
|
5358
|
+
if (isExcludedRescueField(key)) continue;
|
|
5359
|
+
const raw = values[key];
|
|
5360
|
+
if (typeof raw !== "string") continue;
|
|
5361
|
+
const trimmed = raw.trim().slice(0, FORM_RESCUE_MAX_VALUE_CHARS);
|
|
5362
|
+
if (trimmed) out[key] = trimmed;
|
|
5363
|
+
}
|
|
5364
|
+
return out;
|
|
5365
|
+
}
|
|
5366
|
+
function filledRescueFields(values, fieldNames) {
|
|
5367
|
+
return fieldNames.filter((name) => !isExcludedRescueField(name) && isFilled(values[name])).slice(0, FORM_RESCUE_MAX_FIELDS);
|
|
5368
|
+
}
|
|
5369
|
+
function rescueCompletionPct(filledCount, totalCount) {
|
|
5370
|
+
if (totalCount <= 0) return 0;
|
|
5371
|
+
return Math.max(0, Math.min(100, Math.round(filledCount / totalCount * 100)));
|
|
5372
|
+
}
|
|
5373
|
+
function sanitizeRescueFieldName(value) {
|
|
5374
|
+
if (typeof value !== "string") return null;
|
|
5375
|
+
const trimmed = value.trim();
|
|
5376
|
+
return /^[A-Za-z0-9_.-]{1,64}$/.test(trimmed) && !isExcludedRescueField(trimmed) ? trimmed : null;
|
|
5377
|
+
}
|
|
5378
|
+
var FORM_RESCUE_EVENTS = {
|
|
5379
|
+
started: "form_started",
|
|
5380
|
+
progress: "form_progress",
|
|
5381
|
+
leadCaptured: "form_lead_captured",
|
|
5382
|
+
abandoned: "form_abandoned",
|
|
5383
|
+
submitted: "form_submitted",
|
|
5384
|
+
rescued: "form_rescued",
|
|
5385
|
+
resumed: "form_resumed"
|
|
5386
|
+
};
|
|
5387
|
+
function captureFormRescueEvent(event, properties) {
|
|
5388
|
+
try {
|
|
5389
|
+
if (typeof window === "undefined") return;
|
|
5390
|
+
const ph = window.posthog;
|
|
5391
|
+
if (typeof ph?.capture === "function") ph.capture(event, properties);
|
|
5392
|
+
} catch {
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5303
5396
|
// src/utils/doc-tree-nav.ts
|
|
5304
5397
|
var DEFAULT_FOLDER_INDEX_FILE = "README.md";
|
|
5305
5398
|
var DEFAULT_FOLDER_INDEX = DEFAULT_FOLDER_INDEX_FILE;
|
|
@@ -6032,6 +6125,17 @@ exports.DEV_SECTION_PARAM_KEYS = DEV_SECTION_PARAM_KEYS;
|
|
|
6032
6125
|
exports.DOC_TABLE_TYPES = DOC_TABLE_TYPES;
|
|
6033
6126
|
exports.ELAPSED_MS_FIELD = ELAPSED_MS_FIELD;
|
|
6034
6127
|
exports.FIRST_TOUCH_ATTRIBUTION_KEY = FIRST_TOUCH_ATTRIBUTION_KEY;
|
|
6128
|
+
exports.FORM_DRAFT_STATUSES = FORM_DRAFT_STATUSES;
|
|
6129
|
+
exports.FORM_RESCUE_ATTEMPT_FIELD = FORM_RESCUE_ATTEMPT_FIELD;
|
|
6130
|
+
exports.FORM_RESCUE_DEBOUNCE_MS = FORM_RESCUE_DEBOUNCE_MS;
|
|
6131
|
+
exports.FORM_RESCUE_EVENTS = FORM_RESCUE_EVENTS;
|
|
6132
|
+
exports.FORM_RESCUE_FORM_IDS = FORM_RESCUE_FORM_IDS;
|
|
6133
|
+
exports.FORM_RESCUE_MAX_FIELDS = FORM_RESCUE_MAX_FIELDS;
|
|
6134
|
+
exports.FORM_RESCUE_MAX_VALUE_CHARS = FORM_RESCUE_MAX_VALUE_CHARS;
|
|
6135
|
+
exports.FORM_RESCUE_RESUME_FIELD = FORM_RESCUE_RESUME_FIELD;
|
|
6136
|
+
exports.FORM_RESCUE_RESUME_PARAM = FORM_RESCUE_RESUME_PARAM;
|
|
6137
|
+
exports.FORM_RESCUE_SUBMIT_KEYS = FORM_RESCUE_SUBMIT_KEYS;
|
|
6138
|
+
exports.FORM_RESCUE_VALUE_FIELDS = FORM_RESCUE_VALUE_FIELDS;
|
|
6035
6139
|
exports.FullscreenSwitchController = FullscreenSwitchController;
|
|
6036
6140
|
exports.GENERIC_EMAIL_DOMAINS = GENERIC_EMAIL_DOMAINS;
|
|
6037
6141
|
exports.HEADING_EMOJI_RE = HEADING_EMOJI_RE;
|
|
@@ -6091,6 +6195,7 @@ exports.buildSuggestionUrl = buildSuggestionUrl;
|
|
|
6091
6195
|
exports.buildTicketOpenHref = buildTicketOpenHref;
|
|
6092
6196
|
exports.canonicalContentRefType = canonicalContentRefType;
|
|
6093
6197
|
exports.captureFirstTouchAttribution = captureFirstTouchAttribution;
|
|
6198
|
+
exports.captureFormRescueEvent = captureFormRescueEvent;
|
|
6094
6199
|
exports.clamp = clamp;
|
|
6095
6200
|
exports.clampPageToTotal = clampPageToTotal;
|
|
6096
6201
|
exports.classifySocialHost = classifySocialHost;
|
|
@@ -6138,6 +6243,7 @@ exports.extractSections = extractSections;
|
|
|
6138
6243
|
exports.faqItemAnchor = faqItemAnchor;
|
|
6139
6244
|
exports.faqSectionSlug = faqSectionSlug;
|
|
6140
6245
|
exports.fetchPriorityProp = fetchPriorityProp;
|
|
6246
|
+
exports.filledRescueFields = filledRescueFields;
|
|
6141
6247
|
exports.findAnchorElementByNormalizedId = findAnchorElementByNormalizedId;
|
|
6142
6248
|
exports.findDocNodeByPath = findDocNodeByPath;
|
|
6143
6249
|
exports.findHoneypotCopySource = findHoneypotCopySource;
|
|
@@ -6268,7 +6374,10 @@ exports.isBadgePaletteKey = isBadgePaletteKey;
|
|
|
6268
6374
|
exports.isBlankLine = isBlankLine;
|
|
6269
6375
|
exports.isCrossOriginUrl = isCrossOriginUrl;
|
|
6270
6376
|
exports.isDismissedCookieValue = isDismissedCookieValue;
|
|
6377
|
+
exports.isExcludedRescueField = isExcludedRescueField;
|
|
6271
6378
|
exports.isFigmaSlidesUrl = isFigmaSlidesUrl;
|
|
6379
|
+
exports.isFormAttemptId = isFormAttemptId;
|
|
6380
|
+
exports.isFormRescueFormId = isFormRescueFormId;
|
|
6272
6381
|
exports.isGenericDomain = isGenericDomain;
|
|
6273
6382
|
exports.isGenericWebsiteDomain = isGenericWebsiteDomain;
|
|
6274
6383
|
exports.isHumanitySignalKey = isHumanitySignalKey;
|
|
@@ -6278,6 +6387,7 @@ exports.isLocalUrl = isLocalUrl;
|
|
|
6278
6387
|
exports.isOSPlatform = isOSPlatform;
|
|
6279
6388
|
exports.isOrderedRange = isOrderedRange;
|
|
6280
6389
|
exports.isPublishableMediaType = isPublishableMediaType;
|
|
6390
|
+
exports.isRescueEmail = isRescueEmail;
|
|
6281
6391
|
exports.isToday = isToday;
|
|
6282
6392
|
exports.isValidEmailDomain = isValidEmailDomain;
|
|
6283
6393
|
exports.isValidSchedulingSlug = isValidSchedulingSlug;
|
|
@@ -6337,11 +6447,14 @@ exports.readDismissCookie = readDismissCookie;
|
|
|
6337
6447
|
exports.readLeadingDecisionFrame = readLeadingDecisionFrame;
|
|
6338
6448
|
exports.recordCountLabel = recordCountLabel;
|
|
6339
6449
|
exports.releaseTypeToBadgeColor = releaseTypeToBadgeColor;
|
|
6450
|
+
exports.rescueCompletionPct = rescueCompletionPct;
|
|
6340
6451
|
exports.resolveContentFetchPath = resolveContentFetchPath;
|
|
6341
6452
|
exports.resolveRedirectTarget = resolveRedirectTarget;
|
|
6342
6453
|
exports.resolveReleaseCover = resolveReleaseCover;
|
|
6343
6454
|
exports.rgbToHex = rgbToHex;
|
|
6344
6455
|
exports.sanitizeLandingUrl = sanitizeLandingUrl;
|
|
6456
|
+
exports.sanitizeRescueFieldName = sanitizeRescueFieldName;
|
|
6457
|
+
exports.sanitizeRescueValues = sanitizeRescueValues;
|
|
6345
6458
|
exports.sanitizeTitleForChat = sanitizeTitleForChat;
|
|
6346
6459
|
exports.scanHeadings = scanHeadings;
|
|
6347
6460
|
exports.schedulingAudienceKey = schedulingAudienceKey;
|