@officexapp/catalog-types 0.1.0

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.
@@ -0,0 +1,1043 @@
1
+ export type InputComponentType = "short_text" | "long_text" | "rich_text" | "email" | "phone" | "url" | "address" | "number" | "currency" | "date" | "datetime" | "time" | "date_range" | "dropdown" | "multiselect" | "multiple_choice" | "checkboxes" | "picture_choice" | "switch" | "checkbox" | "choice_matrix" | "ranking" | "star_rating" | "slider" | "opinion_scale" | "file_upload" | "signature" | "password" | "location";
2
+ export type DisplayComponentType = "heading" | "paragraph" | "banner" | "image" | "video" | "pdf_viewer" | "file_download" | "social_links" | "html" | "divider" | "faq" | "testimonial" | "pricing_card" | "timeline" | "iframe" | "modal" | "custom";
3
+ export type LayoutComponentType = "section_collapse" | "table" | "subform";
4
+ export type PageFeatureType = "payment" | "captcha";
5
+ export type ComponentType = InputComponentType | DisplayComponentType | LayoutComponentType | PageFeatureType;
6
+ export interface EmbeddedButton {
7
+ /** Button label text */
8
+ label: string;
9
+ /** URL to navigate to on click */
10
+ url: string;
11
+ /** Link target — "_blank" opens in new tab (default), "_self" opens in same tab */
12
+ target?: "_blank" | "_self";
13
+ /** Button size — "sm" (default), "md", "lg" */
14
+ size?: "sm" | "md" | "lg";
15
+ /** Visual style — "primary" (default), "secondary", "outline", "ghost" */
16
+ style?: "primary" | "secondary" | "outline" | "ghost";
17
+ /** Emoji or icon text shown before the label */
18
+ icon?: string;
19
+ }
20
+ export interface Option {
21
+ value: string;
22
+ label: string;
23
+ image?: string;
24
+ description?: string;
25
+ /** Optional button rendered inline with the option */
26
+ button?: EmbeddedButton;
27
+ /** When true, the option is visible but not selectable (grayed out) */
28
+ disabled?: boolean;
29
+ }
30
+ export interface BaseComponentProps {
31
+ label?: string;
32
+ /** Smaller text rendered directly below the label */
33
+ subheading?: string;
34
+ /** Tooltip text shown on hover/click of an info icon next to the label */
35
+ tooltip?: string;
36
+ required?: boolean;
37
+ placeholder?: string;
38
+ description?: string;
39
+ hidden?: boolean;
40
+ /** Render the input as read-only with a copy-to-clipboard button */
41
+ readonly?: boolean;
42
+ /** Show a copy-to-clipboard icon next to the input (works on editable inputs too) */
43
+ copyable?: boolean;
44
+ /** Render the input as disabled (greyed out, not interactive) */
45
+ disabled?: boolean;
46
+ }
47
+ export interface TextProps extends BaseComponentProps {
48
+ min_length?: number;
49
+ max_length?: number;
50
+ default_value?: string;
51
+ /** Number of visible text rows for long_text (default: 4) */
52
+ rows?: number;
53
+ /** Whether the textarea is resizable: "vertical" (default), "horizontal", "both", "none" */
54
+ resize?: "vertical" | "horizontal" | "both" | "none";
55
+ }
56
+ export interface NumberProps extends BaseComponentProps {
57
+ min?: number;
58
+ max?: number;
59
+ step?: number;
60
+ default_value?: number;
61
+ prefix?: string;
62
+ suffix?: string;
63
+ }
64
+ export interface QuizConfig {
65
+ /** The correct answer value (must match an option value). For checkboxes/multiselect, use an array. */
66
+ correct_answer: string | string[];
67
+ /** Points awarded for a correct answer (default: 1) */
68
+ points?: number;
69
+ /** Explanation shown when answer is revealed */
70
+ explanation?: string;
71
+ /** Show correct/incorrect feedback inline immediately after the user selects an answer (fillout.com-style) */
72
+ reveal_on_select?: boolean;
73
+ /** Custom message shown when the user gets the answer wrong (default: "You got the wrong answer.") */
74
+ wrong_message?: string;
75
+ /** Custom message shown when the user gets the answer right (default: "Correct!") */
76
+ correct_message?: string;
77
+ /** Per-option custom messages for choice-based inputs. Key = option value, shown when that specific wrong option is selected. */
78
+ option_messages?: Record<string, string>;
79
+ }
80
+ export interface ChoiceProps extends BaseComponentProps {
81
+ options: Option[];
82
+ other_option?: boolean;
83
+ randomize?: boolean;
84
+ default_value?: string | string[];
85
+ /** Require ALL options to be selected (for checkboxes). When true, every option must be checked and all nested required inputs filled. */
86
+ require_all?: boolean;
87
+ /** Quiz configuration — marks this component as a quiz question */
88
+ quiz?: QuizConfig;
89
+ }
90
+ export interface HeadingProps {
91
+ level: 1 | 2 | 3 | 4 | 5 | 6;
92
+ text: string;
93
+ /** Small eyebrow/kicker text rendered above the heading */
94
+ micro_heading?: string;
95
+ subtitle?: string;
96
+ align?: "left" | "center" | "right";
97
+ }
98
+ export interface ParagraphProps {
99
+ text: string;
100
+ align?: "left" | "center" | "right";
101
+ }
102
+ export interface BannerProps {
103
+ text: string;
104
+ variant: "info" | "warning" | "success" | "error";
105
+ }
106
+ export interface ImageProps {
107
+ /** Image URL — use the compressed_url from the image upload API for auto-optimized WebP delivery */
108
+ src: string;
109
+ alt?: string;
110
+ width?: number | string;
111
+ height?: number | string;
112
+ link?: string;
113
+ border_radius?: number;
114
+ /** Use the original (uncompressed) image URL instead of the compressed version */
115
+ use_original?: boolean;
116
+ }
117
+ export interface VideoChapter {
118
+ time: number;
119
+ label: string;
120
+ thumbnail?: string;
121
+ }
122
+ export interface VideoProps {
123
+ src: string;
124
+ hls_url?: string;
125
+ dash_url?: string;
126
+ autoplay?: boolean;
127
+ muted?: boolean;
128
+ loop?: boolean;
129
+ poster?: string;
130
+ chapters?: VideoChapter[];
131
+ skippable?: boolean;
132
+ require_watch_percent?: number;
133
+ video_id?: string;
134
+ }
135
+ export interface SliderProps extends BaseComponentProps {
136
+ min: number;
137
+ max: number;
138
+ step?: number;
139
+ default_value?: number;
140
+ show_value?: boolean;
141
+ labels?: {
142
+ min?: string;
143
+ max?: string;
144
+ };
145
+ }
146
+ export interface OpinionScaleProps extends BaseComponentProps {
147
+ min: number;
148
+ max: number;
149
+ labels?: {
150
+ min?: string;
151
+ mid?: string;
152
+ max?: string;
153
+ };
154
+ default_value?: number;
155
+ }
156
+ export interface StarRatingProps extends BaseComponentProps {
157
+ max_stars?: number;
158
+ default_value?: number;
159
+ }
160
+ export interface PaymentProps {
161
+ checkout_type: "stripe_embedded" | "redirect";
162
+ button_text?: string;
163
+ price_display?: string;
164
+ stripe_price_id?: string;
165
+ redirect_url?: string;
166
+ order_bumps?: OrderBump[];
167
+ }
168
+ export interface OrderBump {
169
+ id: string;
170
+ title: string;
171
+ description?: string;
172
+ price_display: string;
173
+ stripe_price_id?: string;
174
+ }
175
+ export interface SocialLinksProps {
176
+ links: {
177
+ platform: string;
178
+ url: string;
179
+ }[];
180
+ }
181
+ export interface HtmlProps {
182
+ content: string;
183
+ }
184
+ export interface IframeProps {
185
+ /** URL to embed — supports {{field_id}} templates for dynamic values */
186
+ src: string;
187
+ /** Height in px or CSS value (default: 400) */
188
+ height?: number | string;
189
+ /** Width — CSS value (default: "100%") */
190
+ width?: string;
191
+ /** Border radius in px (default: 16) */
192
+ border_radius?: number;
193
+ /** Sandbox attribute for the iframe (default: "allow-scripts allow-same-origin allow-forms") */
194
+ sandbox?: string;
195
+ /** Allow attribute (e.g. "camera; microphone") */
196
+ allow?: string;
197
+ /** Show a border (default: false) */
198
+ border?: boolean;
199
+ /** Title for accessibility */
200
+ title?: string;
201
+ }
202
+ export interface ModalNestedInput {
203
+ id: string;
204
+ type: string;
205
+ label?: string;
206
+ placeholder?: string;
207
+ required?: boolean;
208
+ props?: Record<string, any>;
209
+ }
210
+ export interface ModalProps {
211
+ button_label?: string;
212
+ button_style?: "primary" | "outline" | "ghost" | "link";
213
+ title?: string;
214
+ /** Static body text (markdown-style). Rendered above any embedded components. */
215
+ body?: string;
216
+ max_width?: string;
217
+ /** Embedded components rendered inside the modal body (inputs + display) */
218
+ components?: ModalNestedInput[];
219
+ /** Label for the confirm button (default: "Close"). When components are present, this becomes the primary action. */
220
+ confirm_label?: string;
221
+ /** When confirm is clicked, set this field ID to the given value (e.g. auto-check a checkbox) */
222
+ confirm_sets_field?: {
223
+ field_id: string;
224
+ value: any;
225
+ };
226
+ /** Disable the confirm button until all required embedded inputs are filled */
227
+ require_inputs?: boolean;
228
+ }
229
+ export interface CustomComponentProps {
230
+ /** Name of the component registered on window.__catalogkit_components */
231
+ component: string;
232
+ /** Arbitrary props passed through to the custom component */
233
+ [key: string]: any;
234
+ }
235
+ export interface FaqItem {
236
+ question: string;
237
+ answer: string;
238
+ }
239
+ export interface FaqProps {
240
+ items: FaqItem[];
241
+ title?: string;
242
+ /** Allow multiple items open at once (default: false) */
243
+ allow_multiple?: boolean;
244
+ /** Index of item to open by default (0-based) */
245
+ default_open?: number;
246
+ }
247
+ export interface TestimonialProps {
248
+ text: string;
249
+ author: string;
250
+ role?: string;
251
+ avatar?: string;
252
+ rating?: number;
253
+ /** Display style */
254
+ variant?: "card" | "quote" | "minimal";
255
+ }
256
+ export interface PricingCardProps {
257
+ title: string;
258
+ price: string;
259
+ price_subtext?: string;
260
+ badge?: string;
261
+ features: string[];
262
+ /** Features that are NOT included (rendered with strikethrough) */
263
+ excluded_features?: string[];
264
+ highlighted?: boolean;
265
+ cta_text?: string;
266
+ }
267
+ export interface FileUploadProps extends BaseComponentProps {
268
+ accept?: string[];
269
+ max_size_mb?: number;
270
+ multiple?: boolean;
271
+ /** Upload files to S3 with presigned URLs instead of storing as base64 data URLs.
272
+ * Requires authenticated catalog owner (credits charged: 1 per 50MB). */
273
+ s3_upload?: boolean;
274
+ }
275
+ export interface FileDownloadProps {
276
+ /** File URL — use the CDN URL from the file upload API */
277
+ src: string;
278
+ /** Display filename (shown on the button) */
279
+ filename: string;
280
+ /** File size in bytes (shown as formatted size) */
281
+ size_bytes?: number;
282
+ /** Button text (default: "Download") */
283
+ button_text?: string;
284
+ /** Button style */
285
+ style?: "primary" | "secondary" | "outline" | "ghost";
286
+ /** Optional description text shown below the filename */
287
+ description?: string;
288
+ /** Optional icon/emoji shown before the filename */
289
+ icon?: string;
290
+ }
291
+ export interface AddressProps extends BaseComponentProps {
292
+ show_line2?: boolean;
293
+ default_country?: string;
294
+ }
295
+ export interface SectionCollapseProps {
296
+ title: string;
297
+ default_open?: boolean;
298
+ components: CatalogComponent[];
299
+ }
300
+ export interface TableProps {
301
+ headers: string[];
302
+ rows: string[][];
303
+ }
304
+ export interface SubformProps extends BaseComponentProps {
305
+ title: string;
306
+ min_items?: number;
307
+ max_items?: number;
308
+ add_button_text?: string;
309
+ components: CatalogComponent[];
310
+ }
311
+ export interface AiPrefillConfig {
312
+ /** Enable AI prefill for this field */
313
+ enabled: boolean;
314
+ /** Minimum confidence threshold (0-1) for the AI to prefill this field (default: 0.7) */
315
+ confidence?: number;
316
+ /** Extra instructions for the AI when filling this specific field */
317
+ instructions?: string;
318
+ }
319
+ export type PrefillMode = "editable" | "readonly" | "hidden";
320
+ export interface CatalogComponent {
321
+ id: string;
322
+ type: ComponentType;
323
+ props: Record<string, any>;
324
+ /** Hide this component from rendering (static flag — for dynamic conditions use `visibility`) */
325
+ hidden?: boolean;
326
+ visibility?: ConditionGroup;
327
+ prefill_mode?: PrefillMode;
328
+ /** Component width within a row: "full" (default), "half", "third", "two_thirds" */
329
+ width?: "full" | "half" | "third" | "two_thirds";
330
+ /** Semantic hint for AI agents explaining what this field means */
331
+ agent_hint?: string;
332
+ /** AI prefill configuration — opt-in per field */
333
+ ai_prefill?: AiPrefillConfig;
334
+ className?: string;
335
+ style?: Record<string, string | number>;
336
+ }
337
+ export type PageActionStyle = "primary" | "secondary" | "ghost" | "danger";
338
+ export interface PageAction {
339
+ id: string;
340
+ label: string;
341
+ style?: PageActionStyle;
342
+ icon?: string;
343
+ redirect_url?: string;
344
+ /** Text shown to the right of the button */
345
+ side_statement?: string;
346
+ /** Small reassurance text shown below the button */
347
+ reassurance?: string;
348
+ }
349
+ export interface PageOffer {
350
+ id: string;
351
+ title: string;
352
+ price_display?: string;
353
+ price_subtext?: string;
354
+ image?: string;
355
+ stripe_price_id?: string;
356
+ /** Amount in cents for items without a stripe_price_id (e.g. 2999 = $29.99) */
357
+ amount_cents?: number;
358
+ /** Currency code (default: "usd") */
359
+ currency?: string;
360
+ /** Override the global payment_type per offer */
361
+ payment_type?: CheckoutPaymentType;
362
+ /** Billing interval for subscription offers */
363
+ interval?: "day" | "week" | "month" | "year";
364
+ /** The action id or choice value that means "accept this offer" */
365
+ accept_value?: string;
366
+ /** The field id that holds the accept/decline choice (for multiple_choice offers) */
367
+ accept_field?: string;
368
+ }
369
+ export interface CartItem {
370
+ offer_id: string;
371
+ page_id: string;
372
+ title: string;
373
+ price_display?: string;
374
+ price_subtext?: string;
375
+ image?: string;
376
+ stripe_price_id?: string;
377
+ /** Amount in cents for items without a stripe_price_id */
378
+ amount_cents?: number;
379
+ /** Override the global payment_type per item */
380
+ payment_type?: CheckoutPaymentType;
381
+ /** Currency code (default: "usd") */
382
+ currency?: string;
383
+ /** Billing interval for subscription items */
384
+ interval?: "day" | "week" | "month" | "year";
385
+ /** Optional side button (link) shown next to the item */
386
+ button?: EmbeddedButton;
387
+ }
388
+ export type StickyBarStyle = "solid" | "glass" | "glass_dark" | "gradient";
389
+ export interface StickyBarAction {
390
+ /** Button label — supports {{field_id}} templates */
391
+ label: string;
392
+ /** Visual style */
393
+ style?: "primary" | "secondary" | "ghost";
394
+ /**
395
+ * What happens on click:
396
+ * - "next" — advance to next page (default for primary)
397
+ * - "action:<action_id>" — trigger a page action by id (e.g. "action:accept", "action:skip")
398
+ * - "field:<field_id>:<value>" — set a field value then advance (e.g. "field:comp_offer1_cta_choice:accept")
399
+ */
400
+ action?: string;
401
+ }
402
+ export interface StickyBarConfig {
403
+ enabled: boolean;
404
+ /** Visual style of the bar */
405
+ style?: StickyBarStyle;
406
+ /** Primary CTA — if not set, falls back to button_text or page submit_label */
407
+ primary?: StickyBarAction;
408
+ /** Secondary action (e.g. decline/skip) — rendered as ghost/secondary button */
409
+ secondary?: StickyBarAction;
410
+ /** Simple override for primary CTA text (shorthand when you don't need full StickyBarAction) */
411
+ button_text?: string;
412
+ /** Subtitle text — supports {{field_id}} templates for dynamic values */
413
+ subtitle?: string;
414
+ /** Custom HTML — supports {{field_id}} templates. Replaces the default subtitle area. */
415
+ html?: string;
416
+ /** Show cart item count badge on the button */
417
+ show_cart_count?: boolean;
418
+ /** Hide the bar when user has scrolled to the bottom (near the normal submit button) */
419
+ hide_at_bottom?: boolean;
420
+ /** Show the bar only after user scrolls past an element with this id (e.g. "pricing-section").
421
+ * The bar starts hidden and appears once the anchor scrolls out of view (above the viewport). */
422
+ show_after?: string;
423
+ /** Delay in milliseconds before the bar can appear (starts hidden, then fades in after this delay) */
424
+ delay_ms?: number;
425
+ /** Scroll-direction behavior: "show_on_up" shows bar on scroll-up, hides on scroll-down */
426
+ scroll_behavior?: "show_on_up";
427
+ }
428
+ export type PageLayout = "cover" | "form" | "default";
429
+ export interface RevealAnswersConfig {
430
+ /** Page IDs whose quiz answers should be revealed on this page */
431
+ from_pages: string[];
432
+ /** Show which answer was correct (default: true) */
433
+ show_correct?: boolean;
434
+ /** Show the explanation text (default: true) */
435
+ show_explanation?: boolean;
436
+ /** Show score summary (default: true) */
437
+ show_score?: boolean;
438
+ }
439
+ export interface CatalogPage {
440
+ title: string;
441
+ description?: string;
442
+ layout?: PageLayout;
443
+ components: CatalogComponent[];
444
+ submit_label?: string;
445
+ /** Text shown to the right of the submit button */
446
+ submit_side_statement?: string;
447
+ /** Small reassurance text shown below the submit button */
448
+ submit_reassurance?: string;
449
+ actions?: PageAction[];
450
+ offer?: PageOffer;
451
+ hide_back?: boolean;
452
+ hide_navigation?: boolean;
453
+ /** Auto-advance to next page when the last visible selection input is answered */
454
+ auto_advance?: boolean;
455
+ /** Auto-skip this page if all visible input fields already have values (from prefill, defaults, or prior entry) */
456
+ auto_skip?: boolean;
457
+ /** Disable the Continue/Submit button until all required fields on this page are filled */
458
+ require_all_fields?: boolean;
459
+ /** Message shown when the user clicks a disabled button (default: "Please fill in all required fields") */
460
+ button_disabled_message?: string;
461
+ /** Sticky bottom bar config — overrides the global setting for this page */
462
+ sticky_bar?: StickyBarConfig;
463
+ /** Reveal quiz answers from previous pages */
464
+ reveal_answers?: RevealAnswersConfig;
465
+ /** Context hint for AI agents explaining this page's purpose */
466
+ agent_context?: string;
467
+ /** Max width of page content area (e.g. "max-w-2xl", "max-w-4xl", "800px") */
468
+ max_width?: string;
469
+ className?: string;
470
+ style?: Record<string, string | number>;
471
+ }
472
+ export interface ProgressStep {
473
+ id: string;
474
+ label: string;
475
+ pages: string[];
476
+ }
477
+ export interface TopBarCountdown {
478
+ /** ISO date string or epoch ms — countdown to this time */
479
+ target_date: string;
480
+ /** Label shown before the timer, e.g. "Sale ends in" */
481
+ label?: string;
482
+ /** Text shown when countdown expires */
483
+ expired_text?: string;
484
+ }
485
+ export interface TopBarConfig {
486
+ /** false = hide the entire top bar. Default: true */
487
+ enabled?: boolean;
488
+ /** Show the back button. Default: true */
489
+ show_back_button?: boolean;
490
+ /** Show stepper/progress bar. Default: true */
491
+ show_progress?: boolean;
492
+ /** Optional title text displayed in the center of the top bar */
493
+ title?: string;
494
+ /** Font weight for the title: "light" (300), "normal" (400), "medium" (500, default), "semibold" (600), "bold" (700) */
495
+ title_weight?: "light" | "normal" | "medium" | "semibold" | "bold";
496
+ /** Countdown timer config */
497
+ countdown?: TopBarCountdown;
498
+ /** Raw HTML to render in the top bar */
499
+ custom_html?: string;
500
+ /** Extra CSS class names */
501
+ className?: string;
502
+ /** Inline style overrides */
503
+ style?: Record<string, string | number>;
504
+ }
505
+ export type ConditionOperator = "equals" | "not_equals" | "contains" | "not_contains" | "greater_than" | "greater_than_or_equal" | "less_than" | "less_than_or_equal" | "is_empty" | "is_not_empty" | "matches_regex" | "in";
506
+ export type ConditionSource = "field" | "url_param" | "hint" | "tracer_prop" | "score" | "video";
507
+ export interface ConditionRule {
508
+ source?: ConditionSource;
509
+ field?: string;
510
+ param?: string;
511
+ operator: ConditionOperator;
512
+ value?: any;
513
+ }
514
+ export interface ConditionGroup {
515
+ match: "all" | "any";
516
+ rules: (ConditionRule | ConditionGroup)[];
517
+ }
518
+ export interface RoutingEdge {
519
+ from: string;
520
+ to: string | null;
521
+ conditions?: ConditionGroup;
522
+ priority?: number;
523
+ is_default?: boolean;
524
+ }
525
+ export interface Routing {
526
+ entry: string;
527
+ edges: RoutingEdge[];
528
+ }
529
+ export interface HintDefinition {
530
+ type: "enum" | "string";
531
+ options?: string[];
532
+ default?: string;
533
+ description?: string;
534
+ }
535
+ export interface CatalogVariant {
536
+ id: string;
537
+ slug: string;
538
+ description?: string;
539
+ hints?: Record<string, string>;
540
+ weight?: number;
541
+ /** If set, route to this OTHER catalog slug (replaces split tests) */
542
+ target_slug?: string;
543
+ url_params?: Record<string, string>;
544
+ /** If false, variant is excluded from routing (hint, random, hybrid). Default: true */
545
+ enabled?: boolean;
546
+ }
547
+ export interface PersonalizationRule {
548
+ target: string;
549
+ prop: string;
550
+ conditions: ConditionGroup;
551
+ value: any;
552
+ }
553
+ export interface ThemeSettings {
554
+ primary_color: string;
555
+ font?: string;
556
+ custom_fonts?: CustomFont[];
557
+ mode?: "light" | "dark";
558
+ border_radius?: number;
559
+ /**
560
+ * Base font size for body text and inputs in rem.
561
+ * Default: 1 (16px). Use 1.125 for 18px, 0.875 for 14px, etc.
562
+ */
563
+ font_size?: number;
564
+ background_image?: string;
565
+ background_color?: string;
566
+ /**
567
+ * Overlay on cover page background image.
568
+ * - "dark" (default) — dark gradient for white text readability
569
+ * - "light" — light overlay for dark text
570
+ * - "none" — no overlay
571
+ * - number (0–1) — custom dark opacity (e.g. 0.5)
572
+ * - string starting with "rgba" or "#" — custom CSS color/gradient
573
+ */
574
+ background_overlay?: "dark" | "light" | "none" | number | string;
575
+ }
576
+ export interface CustomFont {
577
+ family: string;
578
+ url?: string;
579
+ weights?: number[];
580
+ }
581
+ export interface ExitIntentSettings {
582
+ enabled: boolean;
583
+ offer_page_id?: string;
584
+ }
585
+ export type PopupTriggerType = "exit_intent" | "scroll_depth" | "inactive" | "timed" | "page_count" | "custom" | "video_progress" | "video_chapter";
586
+ export interface PopupTrigger {
587
+ type: PopupTriggerType;
588
+ /** Delay in ms before arming the trigger (default: 0) */
589
+ delay?: number;
590
+ /** Cooldown duration string e.g. "24h", "7d", "1h" — don't re-show within this window */
591
+ cooldown?: string;
592
+ /** For scroll_depth: percentage 0-100 */
593
+ percent?: number;
594
+ /** For inactive/timed: seconds */
595
+ seconds?: number;
596
+ /** For page_count: number of pages visited */
597
+ count?: number;
598
+ /** For custom: event name triggered programmatically */
599
+ event?: string;
600
+ /** For video_progress: percentage 0-100 to trigger at */
601
+ watch_percent?: number;
602
+ /** For video_chapter: chapter index to trigger at */
603
+ chapter_index?: number;
604
+ /** Target video component ID for video triggers */
605
+ video_component_id?: string;
606
+ }
607
+ export type PopupPosition = "center" | "bottom-right" | "bottom-left" | "full-screen";
608
+ export type PopupAnimation = "slide-up" | "slide-down" | "fade" | "scale";
609
+ export interface PopupStyle {
610
+ overlay?: "dark" | "light" | "none";
611
+ position?: PopupPosition;
612
+ animation?: PopupAnimation;
613
+ max_width?: string;
614
+ }
615
+ export interface PopupSubmitAction {
616
+ action: "post" | "track" | "close" | "redirect" | "next_page";
617
+ /** For post: webhook URL. For redirect: target URL. Supports {{field_id}} templates. */
618
+ url?: string;
619
+ /** For track: event name */
620
+ event?: string;
621
+ }
622
+ export interface PopupForm {
623
+ fields: CatalogComponent[];
624
+ submit_label?: string;
625
+ on_submit?: PopupSubmitAction[];
626
+ }
627
+ export interface PopupAction {
628
+ label: string;
629
+ /** Action to perform: navigate to URL, track event, or close toast */
630
+ action: "redirect" | "track" | "close" | "next_page";
631
+ url?: string;
632
+ event?: string;
633
+ /** Button style — default: "primary" */
634
+ style?: "primary" | "secondary" | "ghost";
635
+ }
636
+ export interface PopupContent {
637
+ headline?: string;
638
+ subheadline?: string;
639
+ image?: string;
640
+ /** Action buttons — especially useful for toast-mode popups */
641
+ actions?: PopupAction[];
642
+ }
643
+ export interface PopupConfig {
644
+ trigger: PopupTrigger;
645
+ /** Which page IDs this popup appears on. If omitted, appears on all pages. */
646
+ pages?: string[];
647
+ /** Popup mode — "modal" (default, blocks interaction) or "toast" (non-blocking notification) */
648
+ mode?: "modal" | "toast";
649
+ /** Auto-dismiss after N seconds — useful for toast mode */
650
+ auto_dismiss?: number;
651
+ form?: PopupForm;
652
+ content?: PopupContent;
653
+ style?: PopupStyle;
654
+ /** Custom React component name from merchant's components/ directory */
655
+ custom_component?: string;
656
+ /** Hint-variant overrides for any content field using __variants pattern */
657
+ [key: `${string}__variants`]: Record<string, string> | undefined;
658
+ }
659
+ export interface ScriptTag {
660
+ src: string;
661
+ position: "head" | "body_start" | "body_end";
662
+ }
663
+ export type CheckoutPaymentType = "one_time" | "subscription" | "pay_what_you_want";
664
+ export interface CheckoutPrefillFields {
665
+ /** Component ID to prefill customer email from */
666
+ customer_email?: string;
667
+ /** Component ID to prefill customer name from */
668
+ customer_name?: string;
669
+ /** Component ID to prefill customer phone from */
670
+ customer_phone?: string;
671
+ }
672
+ export interface CheckoutTestimonial {
673
+ enabled: boolean;
674
+ text?: string;
675
+ author?: string;
676
+ avatar?: string;
677
+ }
678
+ export interface CheckoutSettings {
679
+ payment_type: CheckoutPaymentType;
680
+ title?: string;
681
+ stripe_publishable_key?: string;
682
+ allow_discount_codes?: boolean;
683
+ free_trial?: {
684
+ enabled: boolean;
685
+ days?: number;
686
+ };
687
+ prefill_fields?: CheckoutPrefillFields;
688
+ payment_methods?: string[];
689
+ payment_description?: string;
690
+ /** Template string — use {{field_id}} to inject form field values, e.g. "{{comp_email}}" */
691
+ client_reference_id?: string;
692
+ /**
693
+ * Force 3D Secure verification on all card payments.
694
+ * Ensures the cardholder authenticates via OTP/biometric, reducing payment failures at trial end.
695
+ */
696
+ require_3ds?: boolean;
697
+ /**
698
+ * What happens if the customer's payment method is missing or fails at trial end.
699
+ * - "cancel" — cancel the subscription (default, recommended)
700
+ * - "create_invoice" — create an invoice and retry (Stripe default behavior)
701
+ * - "pause" — pause the subscription until payment is resolved
702
+ * Only applies to subscriptions with free_trial enabled.
703
+ */
704
+ trial_end_behavior?: "cancel" | "create_invoice" | "pause";
705
+ /**
706
+ * Pass-through overrides for Stripe Checkout Session params.
707
+ * Use this for advanced flows (e.g. Guarded Trial with manual capture) that are
708
+ * managed by your own billing server via Stripe webhooks.
709
+ *
710
+ * payment_intent_data: Applied when mode is "payment" (one_time, or subscription
711
+ * forced to payment mode via mode_override).
712
+ * subscription_data: Merged into subscription_data when mode is "subscription".
713
+ * mode_override: Force the session mode (e.g. "payment" for a guarded trial that
714
+ * uses manual capture instead of a subscription trial).
715
+ * consent_collection: Request consent for terms/promotions.
716
+ */
717
+ stripe_overrides?: {
718
+ payment_intent_data?: {
719
+ capture_method?: "automatic" | "automatic_async" | "manual";
720
+ setup_future_usage?: "off_session" | "on_session";
721
+ statement_descriptor?: string;
722
+ statement_descriptor_suffix?: string;
723
+ transfer_data?: {
724
+ destination: string;
725
+ amount?: number;
726
+ };
727
+ };
728
+ subscription_data?: {
729
+ description?: string;
730
+ metadata?: Record<string, string>;
731
+ };
732
+ /** Force session mode — use "payment" for guarded trials with manual capture */
733
+ mode_override?: "payment" | "subscription" | "setup";
734
+ consent_collection?: {
735
+ terms_of_service?: "required" | "none";
736
+ promotions?: "auto" | "none";
737
+ };
738
+ };
739
+ button_text?: string;
740
+ testimonial?: CheckoutTestimonial;
741
+ show_disclaimer?: boolean;
742
+ disclaimer_text?: string;
743
+ /** Custom display components to render below the order summary (FAQs, testimonials, etc.) */
744
+ components?: CatalogComponent[];
745
+ send_receipt?: boolean;
746
+ success_redirect?: string;
747
+ success_page_id?: string;
748
+ }
749
+ export interface CheckoutLineItem {
750
+ offer_id: string;
751
+ title: string;
752
+ stripe_price_id?: string;
753
+ /** For pay_what_you_want or arbitrary pricing: amount in cents */
754
+ amount_cents?: number;
755
+ quantity?: number;
756
+ /** Override the global payment_type per line item (allows mixing one_time + subscription in cart) */
757
+ payment_type?: CheckoutPaymentType;
758
+ /** Currency code (default: "usd") */
759
+ currency?: string;
760
+ /** Billing interval for subscription items (default: "month") */
761
+ interval?: "day" | "week" | "month" | "year";
762
+ }
763
+ export interface CreateCheckoutSessionRequest {
764
+ user_id: string;
765
+ catalog_slug: string;
766
+ tracer_id: string;
767
+ line_items: CheckoutLineItem[];
768
+ coupon_code?: string;
769
+ form_state: FormState;
770
+ success_url: string;
771
+ cancel_url: string;
772
+ }
773
+ export interface CreateCheckoutSessionResponse {
774
+ session_id: string;
775
+ session_url: string;
776
+ }
777
+ export interface UrlParamSettings {
778
+ prefill_mappings?: Record<string, string>;
779
+ tracking_params?: string[];
780
+ }
781
+ export type CartIconPreset = "cart" | "bag" | "basket";
782
+ export type CartPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
783
+ export interface CartSettings {
784
+ /** Cart icon: a preset name ("cart" | "bag" | "basket") or a URL to a custom image */
785
+ icon?: CartIconPreset | string;
786
+ /** Cart drawer title (default: "Your Cart") */
787
+ title?: string;
788
+ /** Override text for the "Proceed to Checkout" button in the cart drawer */
789
+ checkout_button_text?: string;
790
+ /** External URL to redirect to on checkout instead of the built-in checkout page.
791
+ * Supports {{field_id}} template variables (e.g. "https://pay.example.com?email={{email}}"). */
792
+ checkout_url?: string;
793
+ /** Position of the floating cart button (default: "bottom-right") */
794
+ position?: CartPosition;
795
+ /** Hide the floating cart button entirely (cart can still be opened programmatically via kit.openCart()) */
796
+ hide_button?: boolean;
797
+ /** Custom HTML for the cart header area (replaces default header) */
798
+ header_html?: string;
799
+ /** Custom HTML inserted above the checkout button in the footer */
800
+ footer_html?: string;
801
+ /** Custom HTML shown when the cart is empty (replaces default empty state) */
802
+ empty_html?: string;
803
+ /** Custom CSS injected as a <style> tag scoped to the cart drawer */
804
+ css?: string;
805
+ /** Structured components rendered below cart items (FAQ, text blocks, etc.) */
806
+ components?: CatalogComponent[];
807
+ }
808
+ /** Page transition animation style */
809
+ export type PageTransition = "slide-up" | "fade" | "slide-left" | "scale" | "none";
810
+ /** Scroll-to-top behavior when navigating between pages */
811
+ export type PageScrollBehavior = "instant" | "smooth";
812
+ export interface CatalogSettings {
813
+ theme: ThemeSettings;
814
+ custom_css?: string;
815
+ progress_bar?: boolean;
816
+ progress_steps?: ProgressStep[];
817
+ exit_intent?: ExitIntentSettings;
818
+ /** Trigger-based popups — keyed by popup ID */
819
+ popups?: Record<string, PopupConfig>;
820
+ gtm_id?: string;
821
+ scripts?: ScriptTag[];
822
+ checkout?: CheckoutSettings;
823
+ url_params?: UrlParamSettings;
824
+ /** Global sticky bottom bar — can be overridden per-page */
825
+ sticky_bar?: StickyBarConfig;
826
+ /** Top bar customization — hide it, add countdown, custom HTML, etc. */
827
+ top_bar?: TopBarConfig;
828
+ /** Thin progress line across the top of the viewport */
829
+ progress_line?: {
830
+ enabled?: boolean;
831
+ position?: "top" | "below_topbar";
832
+ height?: number;
833
+ color?: string;
834
+ };
835
+ /** Tips/messages shown during AI routing/prefill loading screen */
836
+ loading_tips?: string[];
837
+ /** Completion screen shown after final submit */
838
+ completion?: CompletionConfig;
839
+ /** Cart button, drawer, and checkout button customization */
840
+ cart?: CartSettings;
841
+ /** Page transition animation when navigating between pages (default: "slide-up") */
842
+ page_transition?: PageTransition;
843
+ /** Scroll-to-top behavior on page change: "instant" (default) or "smooth" */
844
+ page_scroll?: PageScrollBehavior;
845
+ }
846
+ export interface CompletionAction {
847
+ type: "fill_again" | "share" | "redirect";
848
+ label: string;
849
+ /** For redirect: target URL. Supports {{field_id}} templates. */
850
+ url?: string;
851
+ style?: "primary" | "secondary" | "ghost";
852
+ }
853
+ export interface CompletionConfig {
854
+ /** Custom heading (default: none, shows checkmark only) */
855
+ heading?: string;
856
+ /** Custom message body */
857
+ message?: string;
858
+ /** Auto-redirect URL after completion. Supports {{field_id}} templates. */
859
+ redirect_url?: string;
860
+ /** Delay in ms before auto-redirect (default: 0 = immediate) */
861
+ redirect_delay?: number;
862
+ /** Action buttons shown on completion screen */
863
+ actions?: CompletionAction[];
864
+ }
865
+ export interface CatalogSchema {
866
+ schema_version: string;
867
+ catalog_id: string;
868
+ slug: string;
869
+ tags?: string[];
870
+ settings: CatalogSettings;
871
+ pages: Record<string, CatalogPage>;
872
+ routing: Routing;
873
+ hints?: Record<string, HintDefinition>;
874
+ variants?: CatalogVariant[];
875
+ /** Variant routing strategy: random (weighted), hint (LLM), or hybrid */
876
+ variant_routing?: "random" | "hint" | "hybrid";
877
+ personalization?: {
878
+ rules: PersonalizationRule[];
879
+ };
880
+ /** Agent API configuration — enables headless form submission by AI agents */
881
+ agent?: AgentConfig;
882
+ }
883
+ export interface AgentConfig {
884
+ /** Enable the agent API for this catalog */
885
+ enabled: boolean;
886
+ /** Agent interaction mode: "form" (structured steps) or "both" (form + future chat) */
887
+ mode?: "form" | "both";
888
+ /** Description of what this catalog does, for agent discovery/routing */
889
+ description?: string;
890
+ /** Maximum session TTL in seconds (default: 3600) */
891
+ session_ttl?: number;
892
+ }
893
+ export type AgentSessionStatus = "active" | "completed" | "expired";
894
+ export interface AgentSession {
895
+ session_id: string;
896
+ catalog_id: string;
897
+ user_id: string;
898
+ catalog_slug: string;
899
+ mode: "form";
900
+ form_state: FormState;
901
+ current_page_id: string;
902
+ completed_pages: string[];
903
+ status: AgentSessionStatus;
904
+ created_at: string;
905
+ updated_at: string;
906
+ ttl: number;
907
+ }
908
+ export interface FormState {
909
+ [componentId: string]: any;
910
+ }
911
+ /** Per-question quiz result */
912
+ export interface QuizAnswer {
913
+ component_id: string;
914
+ page_id: string;
915
+ /** The question label (from component props) */
916
+ label?: string;
917
+ /** Available options for choice-based questions (value + label pairs) */
918
+ options?: Array<{
919
+ value: string;
920
+ label: string;
921
+ }>;
922
+ given_answer: any;
923
+ correct_answer: string | string[];
924
+ is_correct: boolean;
925
+ points_earned: number;
926
+ points_possible: number;
927
+ explanation?: string;
928
+ /** Custom message for wrong answer (from quiz config) */
929
+ wrong_message?: string;
930
+ }
931
+ /** Aggregated quiz scores */
932
+ export interface QuizScores {
933
+ /** Individual question results */
934
+ answers: QuizAnswer[];
935
+ /** Total points earned */
936
+ total: number;
937
+ /** Total points possible */
938
+ max: number;
939
+ /** Percentage score (0–100) */
940
+ percent: number;
941
+ /** Number of correct answers */
942
+ correct_count: number;
943
+ /** Total number of quiz questions answered */
944
+ question_count: number;
945
+ }
946
+ export interface VideoComponentState {
947
+ watch_percent: number;
948
+ watch_time: number;
949
+ duration: number;
950
+ current_chapter: number;
951
+ completed: boolean;
952
+ }
953
+ export interface VideoState {
954
+ [componentId: string]: VideoComponentState;
955
+ }
956
+ export interface TracerContext {
957
+ tracer_id: string;
958
+ url_params: Record<string, string>;
959
+ hints: Record<string, string>;
960
+ referrer?: string;
961
+ device_type?: string;
962
+ /** Quiz scores — populated at runtime for condition evaluation */
963
+ quiz_scores?: QuizScores;
964
+ /** Video player state — populated at runtime for condition evaluation */
965
+ video_state?: VideoState;
966
+ }
967
+ /**
968
+ * Event types follow the pattern `event` or `event:scope_id`.
969
+ * - Unscoped: fires for all pages/fields (e.g. `"fieldchange"`)
970
+ * - Scoped: fires only for a specific page or field (e.g. `"beforenext:checkout"`, `"fieldchange:email"`)
971
+ *
972
+ * Lifecycle events (React-internal, no DOM equivalent):
973
+ * pageenter, pageexit, beforenext, submit, fieldchange
974
+ *
975
+ * DOM events (blur, focus, click, input, etc.) should use the DOM directly.
976
+ */
977
+ export type CatalogKitEventType = string;
978
+ export interface FieldChangeEvent {
979
+ fieldId: string;
980
+ value: any;
981
+ prevValue: any;
982
+ }
983
+ export interface PageEnterEvent {
984
+ pageId: string;
985
+ }
986
+ export interface PageExitEvent {
987
+ pageId: string;
988
+ }
989
+ export interface BeforeNextEvent {
990
+ pageId: string;
991
+ /** Call to block navigation */
992
+ preventDefault(): void;
993
+ /** Override the next page destination */
994
+ setNextPage(pageId: string): void;
995
+ }
996
+ export interface SubmitEvent {
997
+ pageId: string;
998
+ formState: Readonly<Record<string, any>>;
999
+ /** Call to block submission */
1000
+ preventDefault(): void;
1001
+ }
1002
+ export interface CatalogKitAPI {
1003
+ getField(id: string): any;
1004
+ getAllFields(): Readonly<Record<string, any>>;
1005
+ getVar(key: string): any;
1006
+ getAllVars(): Readonly<Record<string, any>>;
1007
+ getUrlParam(key: string): string | undefined;
1008
+ getAllUrlParams(): Readonly<Record<string, string>>;
1009
+ getPageId(): string;
1010
+ getGlobal(key: string): any;
1011
+ setField(id: string, value: any): void;
1012
+ setVar(key: string, value: any): void;
1013
+ setGlobal(key: string, value: any): void;
1014
+ setButtonLoading(loading: boolean): void;
1015
+ setButtonDisabled(disabled: boolean): void;
1016
+ setValidationError(id: string, message: string | null): void;
1017
+ goNext(): void;
1018
+ goBack(): void;
1019
+ setComponentProp(id: string, prop: string, value: any): void;
1020
+ /**
1021
+ * Subscribe to a lifecycle event. Supports scoped events:
1022
+ * kit.on('fieldchange', cb) — all fields
1023
+ * kit.on('fieldchange:email', cb) — only the 'email' field
1024
+ * kit.on('beforenext:checkout', cb) — only on 'checkout' page
1025
+ * kit.on('pageenter', cb) — all pages
1026
+ *
1027
+ * Async callbacks are awaited for beforenext and submit events.
1028
+ */
1029
+ on(event: CatalogKitEventType, callback: (payload: any) => void | Promise<void>): void;
1030
+ off(event: CatalogKitEventType, callback: (payload: any) => void | Promise<void>): void;
1031
+ fetch: typeof globalThis.fetch;
1032
+ }
1033
+ export interface CatalogKitRegistry {
1034
+ /** Get an instance by catalog_id, or the most recently mounted one if omitted */
1035
+ get(catalogId?: string): CatalogKitAPI | undefined;
1036
+ /** Per-catalog instances keyed by catalog_id */
1037
+ [catalogId: string]: CatalogKitAPI | ((...args: any[]) => any);
1038
+ }
1039
+ declare global {
1040
+ interface Window {
1041
+ CatalogKit?: CatalogKitRegistry;
1042
+ }
1043
+ }