@pushwoosh/websdk-common 0.0.0 → 6.16.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,660 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Text styles a slot can use ('icon' = emoji line, 'eyebrow' = small caps kicker). */
3
+ export type WebPopupTextVariant = 'icon' | 'eyebrow' | 'h1' | 'h2' | 'h3' | 'body' | 'caption';
4
+ export type WebPopupTextAlign = 'left' | 'center' | 'right';
5
+ export type WebPopupButtonVariant = 'primary' | 'secondary';
6
+ /**
7
+ * What a button does: open a URL, dismiss the popup, switch to another page
8
+ * (`page` = WebPopupPageParams.key), or ask for push permission.
9
+ */
10
+ export type WebPopupButtonAction =
11
+ /** `target` absent / anything but '_self' = a new tab (the historical behavior). */
12
+ {
13
+ type: 'href';
14
+ url: string;
15
+ target?: '_blank' | '_self';
16
+ } | {
17
+ type: 'close';
18
+ } | {
19
+ type: 'page';
20
+ page: string;
21
+ }
22
+ /** Ask the browser for notification permission and subscribe the visitor. */
23
+ | {
24
+ type: 'subscribe';
25
+ };
26
+ /** What the host's subscribe handler settled on — the browser's permission after the ask. */
27
+ export type WebPopupSubscribeResult = 'granted' | 'denied' | 'default';
28
+ /**
29
+ * Input kinds an email-subscription-form slot renders — the same set the
30
+ * subscription-form service accepts in a submission ('custom' entries land in
31
+ * the `customFields` map under the field's `tag`).
32
+ */
33
+ export type WebPopupFormFieldKind = 'email' | 'firstName' | 'lastName' | 'phone' | 'custom';
34
+ export type WebPopupFormField = {
35
+ /** Stable id within the slot ('field-N'), not a display name. */
36
+ key: string;
37
+ kind: WebPopupFormFieldKind;
38
+ /** Caption above the input; empty = no caption. */
39
+ label: string;
40
+ placeholder: string;
41
+ required: boolean;
42
+ /** The tag a 'custom' field submits under; other kinds ignore it. */
43
+ tag?: string;
44
+ };
45
+ /** The consent row of a form slot: a required checkbox with a policy link. */
46
+ export type WebPopupFormPrivacy = {
47
+ checkboxLabel: string;
48
+ /** Policy page URL, rendered through safeUrl like every popup link. */
49
+ url: string;
50
+ linkLabel: string;
51
+ };
52
+ /** What a `timer` slot does once its deadline has passed. */
53
+ export type WebPopupTimerExpireAction = 'keep' | 'hide-popup' | 'page';
54
+ /** A countdown to a fixed deadline; the field contract is the email builder's timer block. */
55
+ export type WebPopupTimerSlot = {
56
+ type: 'timer';
57
+ key: string;
58
+ /** Countdown target as local wall-clock time in `timezone`. */
59
+ endTime: string;
60
+ /** IANA zone name; empty = UTC. */
61
+ timezone: string;
62
+ /** Unit-label language (ISO 639-1); empty/unknown = 'en'. */
63
+ language: string;
64
+ showLabels: boolean;
65
+ /** Behavior past the deadline; 'hide-popup' is the host's call, see webPopupExpired. */
66
+ onExpire: WebPopupTimerExpireAction;
67
+ /** Target page key for onExpire 'page'; normalization drops a pageless 'page' to 'keep'. */
68
+ expirePage?: string;
69
+ };
70
+ /** How a `choice` slot renders its options. */
71
+ export type WebPopupChoiceControl = 'checkbox' | 'chips';
72
+ export type WebPopupChoiceOption = {
73
+ /** Stable value within the slot — and what is written to the tag when `tagValue` is absent. */
74
+ value: string;
75
+ label: string;
76
+ /** Tag value this option submits, when it differs from `value`. */
77
+ tagValue?: string;
78
+ };
79
+ /** A topic picker written to a LIST device tag; no callback or no `tagName` = inert. */
80
+ export type WebPopupChoiceSlot = {
81
+ type: 'choice';
82
+ key: string;
83
+ control: WebPopupChoiceControl;
84
+ /** The device tag the choice is written to; '' = the slot is inert. */
85
+ tagName: string;
86
+ options: WebPopupChoiceOption[];
87
+ /** Selection bounds against the option count; 0 = unbounded. */
88
+ min: number;
89
+ max: number;
90
+ submitLabel: string;
91
+ submitVariant: WebPopupButtonVariant;
92
+ /** Replaces the slot after a successful submit. */
93
+ successMessage: string;
94
+ };
95
+ /** A rating slot's scale: 11 buttons (0..10), 5 stars, or 2 thumbs. */
96
+ export type WebPopupRatingScale = 'nps' | 'stars' | 'thumbs';
97
+ /** 'instant' posts the score on tap; 'button' waits for the submit pill. */
98
+ export type WebPopupRatingSubmitMode = 'instant' | 'button';
99
+ /** A rating question; the score leaves as an EVENT, and no callback or no `eventName` = inert. */
100
+ export type WebPopupRatingSlot = {
101
+ type: 'rating';
102
+ key: string;
103
+ scale: WebPopupRatingScale;
104
+ /** Captions under the extremes of the scale; '' = none. */
105
+ labels: {
106
+ low: string;
107
+ high: string;
108
+ };
109
+ submitMode: WebPopupRatingSubmitMode;
110
+ submitLabel: string;
111
+ /** The event the score is posted as; '' = the slot is inert. */
112
+ eventName: string;
113
+ /** Replaces the slot after a successful submit. */
114
+ successMessage: string;
115
+ };
116
+ /** The icon whitelist: inline svg only — no icon package in the widget bundle, no svg from json. */
117
+ export type WebPopupGlyphName = 'bell' | 'gift' | 'cart' | 'chart' | 'lock' | 'calendar' | 'check' | 'star' | 'clock' | 'tag' | 'mail' | 'phone' | 'download' | 'play' | 'shield' | 'sparkle' | 'percent' | 'truck' | 'ticket' | 'user';
118
+ export type WebPopupGlyphSize = 24 | 32 | 48;
119
+ /** A single icon; named `glyph` because WebPopupTextVariant already spends `icon` on its emoji line. */
120
+ export type WebPopupGlyphSlot = {
121
+ type: 'glyph';
122
+ key: string;
123
+ /** Whitelisted name; an unknown one drops the slot (no geometry to draw). */
124
+ name: WebPopupGlyphName;
125
+ size: WebPopupGlyphSize;
126
+ /** Hex '#rrggbb'; '' = the palette accent. */
127
+ color: string;
128
+ };
129
+ /** One entry of a page's content stack, in display order. */
130
+ export type WebPopupSlot = {
131
+ type: 'text';
132
+ key: string;
133
+ variant: WebPopupTextVariant;
134
+ html: string;
135
+ /** Per-slot alignment override; absent = the page kind's layout.align. */
136
+ align?: WebPopupTextAlign;
137
+ } | {
138
+ type: 'button';
139
+ key: string;
140
+ variant: WebPopupButtonVariant;
141
+ label: string;
142
+ action: WebPopupButtonAction;
143
+ }
144
+ /**
145
+ * An email-subscription form: input fields + a submit button, bound to a
146
+ * Pushwoosh subscription form entity by `formCode`. The fields are the
147
+ * slot's own (seeded from the form's configuration by the editor) — the
148
+ * runtime renders straight from the json and never fetches the form. The
149
+ * host submits via WebPopupReaderProps.onSubmitForm; without the callback
150
+ * (editor canvas, previews) the form renders inert. NOTE: engine builds
151
+ * that predate this slot type cannot render a popup containing it.
152
+ */
153
+ | {
154
+ type: 'email-subscription-form';
155
+ key: string;
156
+ /** Bound subscription form code; '' until picked — the form is inert. */
157
+ formCode: string;
158
+ fields: WebPopupFormField[];
159
+ submitLabel: string;
160
+ submitVariant: WebPopupButtonVariant;
161
+ /** Absent = no consent checkbox (submits policyAccepted: true). */
162
+ privacy?: WebPopupFormPrivacy;
163
+ /** Replaces the form after a subscribed / already-subscribed result. */
164
+ successMessage: string;
165
+ /** Replaces the form after the double-opt-in confirm-email result. */
166
+ confirmEmailMessage: string;
167
+ } | WebPopupTimerSlot | WebPopupChoiceSlot | WebPopupRatingSlot | WebPopupGlyphSlot;
168
+ /** What a media param plays instead of drawing its image; the param's `src` stays the poster, so engines predating video degrade to it. */
169
+ export type WebPopupVideo = {
170
+ /** Video url, rendered through safeUrl like every popup link. */
171
+ src: string;
172
+ /** Absent = chromeless playback (no browser controls). */
173
+ controls?: boolean;
174
+ /** Absent = loop; false = play once. */
175
+ loop?: boolean;
176
+ };
177
+ /**
178
+ * An image parameter: the data plus its presentation. WHERE it renders is
179
+ * the layout tree's business (an `Image` node or a container's
180
+ * `backgroundImage` reference this by key); HOW it renders — fit, blur — is
181
+ * the image's own, per-popup editable like the src.
182
+ */
183
+ export type WebPopupImage = {
184
+ key: string;
185
+ /** Shown as the field label in the editor rail. */
186
+ label: string;
187
+ src: string;
188
+ /** Drawn instead of `src` in the dark scheme (a light logo would stay light
189
+ * on a dark background); absent = `src` in both. */
190
+ srcDark?: string;
191
+ /** uuid of the media-gallery file `srcDark` was picked from; editor-only, like `mediaId`. */
192
+ mediaIdDark?: string;
193
+ /** Set = the param renders as a muted autoplaying video with `src` as its poster. */
194
+ video?: WebPopupVideo;
195
+ /**
196
+ * uuid of the media-gallery file `src` was picked from, kept alongside the
197
+ * url for dedup and gallery back-reference. Absent for externally-typed urls
198
+ * (no gallery entry) and legacy documents. The runtime ignores it — it
199
+ * renders by `src`; only the editor reads it.
200
+ */
201
+ mediaId?: string;
202
+ /**
203
+ * How the image fills its box when drawn by an `Image` node; a container
204
+ * background always covers. Absent = natural flow (full width, auto height).
205
+ */
206
+ fit?: 'cover' | 'contain';
207
+ /** Blur radius px applied when drawn (backdrop-filter over a container
208
+ * background, css filter on an image node). Absent / 0 = no blur. */
209
+ blur?: number;
210
+ /**
211
+ * Scrim: a dark translucent layer (0 = none … 1 = black) drawn over the
212
+ * image when it's a container BACKGROUND, keeping the copy above readable
213
+ * on light photos. `Image` nodes ignore it. Absent / 0 = no scrim.
214
+ */
215
+ dim?: number;
216
+ };
217
+ export type WebPopupVAnchor = 'top' | 'middle' | 'bottom';
218
+ export type WebPopupHAnchor = 'left' | 'center' | 'right';
219
+ /** Viewport placement: a 3×3 anchor plus the gutter from the anchored edges. */
220
+ export type WebPopupPlacement = {
221
+ v: WebPopupVAnchor;
222
+ h: WebPopupHAnchor;
223
+ /** Distance from the anchored edges, px (0 = flush). */
224
+ offset: number;
225
+ };
226
+ /** The page dim behind the popup; absent on params = no dimming. */
227
+ export type WebPopupOverlay = {
228
+ color: string;
229
+ /** 0 (transparent) to 1 (opaque). */
230
+ opacity: number;
231
+ };
232
+ export type WebPopupAnimation = 'none' | 'fade' | 'slide';
233
+ /**
234
+ * The popup palette — every color that varies. What derives from it is fixed:
235
+ * buttons pair the accent with white (primary = accent fill + white label,
236
+ * secondary = white fill + accent outline).
237
+ */
238
+ export type WebPopupColors = {
239
+ /** Popup background color. */
240
+ background: string;
241
+ /** Brand accent: primary button fill, secondary button outline. */
242
+ accent: string;
243
+ /** Main copy: h1–h3 and body ('icon' inherits it). */
244
+ text: string;
245
+ /** Secondary copy: caption and eyebrow. */
246
+ textSecondary: string;
247
+ /**
248
+ * Copy in inverse-tone zones (over a background image). Secondary variants
249
+ * (eyebrow/body/caption) derive their translucency from it per
250
+ * webPopupTextColor. Default white — set it dark for light photos.
251
+ */
252
+ textInverse: string;
253
+ };
254
+ /** Which of the two authored palettes a popup is being drawn with. */
255
+ export type WebPopupColorScheme = 'light' | 'dark';
256
+ /** What a host asks the reader for; 'auto' follows the visitor's `prefers-color-scheme`. */
257
+ export type WebPopupColorSchemePref = WebPopupColorScheme | 'auto';
258
+ /** The dark-scheme palette; `enabled: false` keeps it around while the popup
259
+ * renders light, which is what the editor's checkbox needs. */
260
+ export type WebPopupDark = {
261
+ enabled: boolean;
262
+ colors: WebPopupColors;
263
+ /** Page dim for the dark scheme; absent = the chrome's own overlay. */
264
+ overlay?: WebPopupOverlay;
265
+ /** Box shadow for the dark scheme ('none' = drop it); absent = the chrome's own. */
266
+ boxShadow?: string;
267
+ };
268
+ /** What a renderer draws with: the scheme's five authored roles plus the ones
269
+ * derived from them, which used to be hardcoded light values. */
270
+ export type WebPopupResolvedColors = WebPopupColors & {
271
+ /** Which palette this resolved from — 'light' for a popup with dark mode off. */
272
+ scheme: WebPopupColorScheme;
273
+ /** Fill of the cards that sit ON the popup background: inputs, secondary buttons. */
274
+ surface: string;
275
+ /** Copy inside a `surface` card. */
276
+ surfaceText: string;
277
+ /** Idle border of an input card. */
278
+ border: string;
279
+ /** Label over an accent fill: a primary button, a selected option. */
280
+ onAccent: string;
281
+ /** Validation errors and the generic retry line. */
282
+ error: string;
283
+ /** Glyph on a service chip drawn over the popup — today the ✕ button. */
284
+ chromeIcon: string;
285
+ };
286
+ /**
287
+ * The popup-level look shared by all pages: size, colors, viewport placement,
288
+ * page dim, animation and the box chrome. The retired top-level
289
+ * `background`/`accent` fields are folded into `colors` by parseWebPopupJson
290
+ * and mirrored back by stringifyWebPopup (so older engine builds keep
291
+ * rendering newly-saved popups) — they never appear on this type.
292
+ */
293
+ export type WebPopupChrome = {
294
+ /** Width px. Absent = stretch to the available viewport width. A page may
295
+ * override it (WebPopupPageParams.width). */
296
+ width?: number;
297
+ /** Height px. Absent = grow with content; a number = fixed, content
298
+ * scrolls; 'stretch' = fill the viewport height (both vertical edges pinned
299
+ * at the placement offset, like the width-absent horizontal stretch —
300
+ * engine builds that predate the value treat it as absent and grow with
301
+ * content). A page may override it. */
302
+ height?: number | 'stretch';
303
+ colors: WebPopupColors;
304
+ /** Dark-scheme palette; absent or disabled = the popup always renders light. */
305
+ dark?: WebPopupDark;
306
+ placement: WebPopupPlacement;
307
+ overlay?: WebPopupOverlay;
308
+ animation: WebPopupAnimation;
309
+ /** Corner radius px. 0 = sharp corners. */
310
+ borderRadius: number;
311
+ /** CSS box-shadow string; 'none' = no shadow. */
312
+ boxShadow: string;
313
+ };
314
+ /** A named group of slots a layout `Content` node binds to by key. */
315
+ export type WebPopupContentZone = {
316
+ /** Stable zone id (referenced by the layout tree), not a display name. */
317
+ key: string;
318
+ /** Shown as the zone's name in the editor. */
319
+ label: string;
320
+ slots: WebPopupSlot[];
321
+ };
322
+ /** A css length unit a `length` control accepts. */
323
+ export type WebPopupLengthUnit = 'px' | '%' | 'em' | 'rem' | 'fr';
324
+ /** Min/max bounds for a length amount — a control's default or one unit's override. */
325
+ export type WebPopupLengthRange = {
326
+ min?: number;
327
+ max?: number;
328
+ };
329
+ /**
330
+ * Which editor control a value param is edited with. Editor metadata (the
331
+ * runtime never reads it), stored in json like an image's label so
332
+ * API/AI-authored pages get knobs too. Absent / unknown = a plain text input.
333
+ */
334
+ export type WebPopupValueControl =
335
+ /**
336
+ * A number input with a unit. `min`/`max` bound the amount; `ranges` narrows
337
+ * them per unit (e.g. 120–480px but 20–60%) — a unit falls back to the flat
338
+ * `min`/`max` when it has no entry.
339
+ */
340
+ {
341
+ kind: 'length';
342
+ min?: number;
343
+ max?: number;
344
+ units?: WebPopupLengthUnit[];
345
+ ranges?: Partial<Record<WebPopupLengthUnit, WebPopupLengthRange>>;
346
+ }
347
+ /** A 0–100 slider writing 'NN%'. */
348
+ | {
349
+ kind: 'percent';
350
+ min?: number;
351
+ max?: number;
352
+ } | {
353
+ kind: 'select';
354
+ options: {
355
+ value: string;
356
+ label: string;
357
+ }[];
358
+ } | {
359
+ kind: 'text';
360
+ };
361
+ /**
362
+ * A named style value the page's layout tree references as a `$key` token
363
+ * (whole string prop or a whitespace-separated token inside a `sizes`
364
+ * template) — the third page-param family after images and contents: WHERE
365
+ * the value applies is the tree's business, the value itself is the param's
366
+ * own, per-popup editable. resolveWebPopupPages substitutes values BEFORE
367
+ * the layout normalization, so a substituted value passes the same
368
+ * whitelists/clamps as a literal one. Binding keys (`content`, `image`,
369
+ * `backgroundImage`) are never substituted.
370
+ */
371
+ export type WebPopupValueParam = {
372
+ /** Stable id the tree references as `$key`; not a display name. */
373
+ key: string;
374
+ /** Shown as the field label in the editor rail. */
375
+ label: string;
376
+ /** The current value, substituted into the tree verbatim. */
377
+ value: string;
378
+ control?: WebPopupValueControl;
379
+ };
380
+ /** A px number or a whitelisted css length string ('12px', '55%', '1.5em'). */
381
+ export type WebPopupCssLength = string | number;
382
+ export type WebPopupBoxSides = {
383
+ top?: WebPopupCssLength;
384
+ right?: WebPopupCssLength;
385
+ bottom?: WebPopupCssLength;
386
+ left?: WebPopupCssLength;
387
+ };
388
+ export type WebPopupAlignItems = 'start' | 'center' | 'end' | 'stretch';
389
+ export type WebPopupJustifyContent = 'start' | 'center' | 'end' | 'space-between';
390
+ /**
391
+ * A `$key` reference to a page value (WebPopupValueParam) in an AUTHORED
392
+ * (template/catalog/stored) tree — resolveWebPopupPages substitutes it before
393
+ * normalization, so a normalized tree only carries literal values. Every
394
+ * enum container prop accepts it (substitution walks all string props).
395
+ */
396
+ export type WebPopupValueRef = `$${string}`;
397
+ export type WebPopupContainerType = 'Vertical' | 'Horizontal' | 'Columns' | 'Rows';
398
+ export type WebPopupContainerProps = {
399
+ gap?: WebPopupCssLength;
400
+ padding?: WebPopupCssLength | WebPopupBoxSides;
401
+ alignItems?: WebPopupAlignItems | WebPopupValueRef;
402
+ justifyContent?: WebPopupJustifyContent | WebPopupValueRef;
403
+ /**
404
+ * Vertical distribution of the container's rows — visible only when the
405
+ * container is taller than its content (a 'stretch'-height page's root
406
+ * container fills the box). Same value set as justifyContent.
407
+ */
408
+ alignContent?: WebPopupJustifyContent | WebPopupValueRef;
409
+ /** Key into page images: the container draws it as a cover background. */
410
+ backgroundImage?: string;
411
+ /**
412
+ * Columns/Rows track template: N equal tracks, or a whitelist-validated
413
+ * template string ('1fr 220px'). Absent = one equal track per child.
414
+ * Ignored on Vertical/Horizontal.
415
+ */
416
+ sizes?: number | string;
417
+ /**
418
+ * 'reverse' renders the children in reverse order; on Columns/Rows the
419
+ * `sizes` template reverses with them, so each child keeps its track.
420
+ * Applied (and consumed) by normalizeWebPopupLayout — a normalized tree
421
+ * has the children already reordered and no `order` left.
422
+ */
423
+ order?: 'normal' | 'reverse' | WebPopupValueRef;
424
+ };
425
+ /** Text palette of a Content zone; 'inverse' = white copy over a dark image. */
426
+ export type WebPopupContentTone = 'default' | 'inverse';
427
+ export type WebPopupContentNode = {
428
+ type: 'Content';
429
+ props: {
430
+ /** WebPopupContentZone key; an unknown key renders as an empty zone. */
431
+ content: string;
432
+ /** Default text alignment; a text slot's own `align` overrides it. */
433
+ align?: WebPopupTextAlign;
434
+ tone?: WebPopupContentTone;
435
+ };
436
+ };
437
+ export type WebPopupImageNode = {
438
+ type: 'Image';
439
+ props: {
440
+ /** WebPopupImage key; an unknown key renders nothing. How the image
441
+ * renders (fit, blur) lives on the image param itself. */
442
+ image: string;
443
+ };
444
+ };
445
+ export type WebPopupContainerNode = {
446
+ type: WebPopupContainerType;
447
+ props?: WebPopupContainerProps;
448
+ children: WebPopupLayoutNode[];
449
+ };
450
+ export type WebPopupLayoutNode = WebPopupContainerNode | WebPopupContentNode | WebPopupImageNode;
451
+ /**
452
+ * One page of the popup. `key` is unique within the popup; button `page`
453
+ * actions point at it. The layout tree is EMBEDDED (copied from the kind by
454
+ * the editor on save), so the display engine renders the page self-contained
455
+ * — no kind catalog at runtime, and new kinds render on older engine builds.
456
+ */
457
+ export type WebPopupPageParams = {
458
+ key: string;
459
+ /** WebPopupKind id — editor metadata only; the runtime never resolves it. */
460
+ kind: string;
461
+ layout: WebPopupLayoutNode;
462
+ images: WebPopupImage[];
463
+ contents: WebPopupContentZone[];
464
+ /** Named style values the layout tree references as `$key` tokens. */
465
+ values?: WebPopupValueParam[];
466
+ /**
467
+ * The page's own box size, overriding the chrome's: a number = px, `null` =
468
+ * explicitly auto (needed when the chrome default IS a number), absent =
469
+ * the chrome's width/height. Engine builds that predate the field ignore it
470
+ * and show every page at the chrome size.
471
+ */
472
+ width?: number | null;
473
+ /** See `width`; auto height = grow with content, a number = fixed
474
+ * (scrolls), 'stretch' = fill the viewport height. */
475
+ height?: number | 'stretch' | null;
476
+ };
477
+ export type WebPopupParams = WebPopupChrome & {
478
+ /** The pages, in order; at least one. Only one shows at a time. */
479
+ pages: WebPopupPageParams[];
480
+ /** Key of the page shown first; absent/unknown = the first page. */
481
+ startPage?: string;
482
+ };
483
+ export type WebPopupKind = {
484
+ id: string;
485
+ label: string;
486
+ layout: WebPopupLayoutNode;
487
+ /** The chrome a fresh popup of this kind opens with in the editor gallery. */
488
+ chrome: WebPopupChrome;
489
+ /** The default content a new page of this kind opens with. */
490
+ content: {
491
+ images: WebPopupImage[];
492
+ contents: WebPopupContentZone[];
493
+ };
494
+ /**
495
+ * The value params the layout references as `$key` tokens; `value` is the
496
+ * default a fresh page's `values` open with.
497
+ */
498
+ params?: WebPopupValueParam[];
499
+ };
500
+ /**
501
+ * A system layout template — a blessed, parameterized arrangement the editor
502
+ * kinds instantiate instead of hand-writing trees (a kind may still carry its
503
+ * own tree where no template fits). The tree references `$key` value params
504
+ * and binds the canonical zone/image keys ('main', 'image'); the kind catalog
505
+ * may rebind those when instantiating. Editor-only, like the kind catalog.
506
+ */
507
+ export type WebPopupTemplate = {
508
+ name: string;
509
+ layout: WebPopupLayoutNode;
510
+ /** The template's knobs; `value` holds the default. */
511
+ params: WebPopupValueParam[];
512
+ };
513
+ /** A page with its layout tree validated/normalized — the renderable unit. */
514
+ export type WebPopupResolvedPage = {
515
+ key: string;
516
+ layout: WebPopupLayoutNode;
517
+ images: WebPopupImage[];
518
+ contents: WebPopupContentZone[];
519
+ /** The page's FINAL box width px (page ?? chrome, validated); absent =
520
+ * stretch to the available viewport width. */
521
+ width?: number;
522
+ /** Final box height: px, or 'stretch' = fill the viewport height; absent =
523
+ * grow with content. */
524
+ height?: number | 'stretch';
525
+ };
526
+ /** The stored format — PopupFormContent.json parses into this. */
527
+ export type WebPopupJson = {
528
+ /** Format version; content without it predates this engine and is unrenderable. */
529
+ version: number;
530
+ params: WebPopupParams;
531
+ };
532
+ export type WebPopupReaderMode = 'live' | 'preview';
533
+ /**
534
+ * What an email-subscription-form slot submits — mirrors the
535
+ * subscription-form service's submit request (the host adds transport fields
536
+ * like userId itself). Custom fields land under their `tag` (falling back to
537
+ * the field key).
538
+ */
539
+ export type WebPopupFormSubmitPayload = {
540
+ email: string;
541
+ firstName?: string;
542
+ lastName?: string;
543
+ phone?: string;
544
+ customFields?: Record<string, string>;
545
+ /** True when the slot has no privacy checkbox. */
546
+ policyAccepted: boolean;
547
+ };
548
+ /** A countdown's remaining time at some instant, broken down for display. */
549
+ export type WebPopupTimerRemaining = {
550
+ /** Whole milliseconds left; 0 = the deadline has passed (or can't be computed). */
551
+ total: number;
552
+ days: number;
553
+ hours: number;
554
+ minutes: number;
555
+ seconds: number;
556
+ };
557
+ /** The service's submit outcomes; 'confirm-email' = double-opt-in pending. */
558
+ export type WebPopupFormSubmitResult = 'subscribed' | 'confirm-email' | 'already-subscribed';
559
+ /**
560
+ * Submits a form slot's payload to the bound subscription form. Rejects on
561
+ * network/server failure — the slot shows a generic retryable error.
562
+ */
563
+ export type WebPopupFormSubmitHandler = (formCode: string, payload: WebPopupFormSubmitPayload) => Promise<WebPopupFormSubmitResult>;
564
+ /** What a `choice` slot submits: the picked options' tag values, in option order. */
565
+ export type WebPopupChoiceSubmitPayload = {
566
+ slotKey: string;
567
+ tagName: string;
568
+ values: string[];
569
+ };
570
+ /** Writes a choice slot's selection to the device tag; a rejection is what leaves the slot retryable. */
571
+ export type WebPopupChoiceSubmitHandler = (payload: WebPopupChoiceSubmitPayload) => Promise<void>;
572
+ /** What a `rating` slot submits: the score plus the scale it was given on. */
573
+ export type WebPopupRatingSubmitPayload = {
574
+ slotKey: string;
575
+ eventName: string;
576
+ score: number;
577
+ scale: WebPopupRatingScale;
578
+ };
579
+ /** Posts a rating slot's score as an event. Rejects on failure — see above. */
580
+ export type WebPopupRatingSubmitHandler = (payload: WebPopupRatingSubmitPayload) => Promise<void>;
581
+ /**
582
+ * Editor-only overrides for the tree's data-binding LEAVES. The container
583
+ * interpretation (grid divs, background images, dim, blur) is deliberately
584
+ * NOT overridable — rendering it here is what guarantees an editor canvas
585
+ * matches the runtime pixel for pixel. The runtime never passes these.
586
+ */
587
+ export type WebPopupLeafRenderers = {
588
+ /** Replaces a Content leaf's slot stack. Unknown zone keys still render nothing. */
589
+ renderZone?: (zone: WebPopupContentZone, node: WebPopupContentNode) => ReactNode;
590
+ /**
591
+ * Replaces an Image leaf's <img>. Called whenever the image param exists —
592
+ * including an empty src the runtime would hide (editors want a
593
+ * placeholder there). Unknown image keys still render nothing.
594
+ */
595
+ renderImage?: (image: WebPopupImage, node: WebPopupImageNode) => ReactNode;
596
+ };
597
+ export type WebPopupReaderProps = {
598
+ params: WebPopupParams;
599
+ /**
600
+ * 'live' (default): fixed-position chrome — overlay, viewport placement,
601
+ * enter animation, close button. 'preview': the popup box rendered inline
602
+ * (relative), for editor/list previews.
603
+ */
604
+ mode?: WebPopupReaderMode;
605
+ /** Palette to draw with; default 'auto' — the visitor's OS setting, watched live. */
606
+ colorScheme?: WebPopupColorSchemePref;
607
+ /** Close requests: the ✕ button, the overlay click and 'close'-action buttons. */
608
+ onRequestClose?: () => void;
609
+ /** Form-slot submits; absent = form slots render inert (previews, editor). */
610
+ onSubmitForm?: WebPopupFormSubmitHandler;
611
+ /** Choice-slot submits; absent = choice slots render inert. */
612
+ onSubmitChoice?: WebPopupChoiceSubmitHandler;
613
+ /** Rating-slot submits; absent = rating slots render inert. */
614
+ onSubmitRating?: WebPopupRatingSubmitHandler;
615
+ /** `subscribe`-action buttons; absent = they render inert (previews, editor). */
616
+ onRequestSubscribe?: () => Promise<WebPopupSubscribeResult>;
617
+ /** Editor-only leaf overrides — see WebPopupLeafRenderers. */
618
+ leafRenderers?: WebPopupLeafRenderers;
619
+ };
620
+ /** Props of WebPopupSlotView — one slot, drawn the runtime's way; the defaults are the inert ones an editor wants. */
621
+ export type WebPopupSlotViewProps = {
622
+ slot: WebPopupSlot;
623
+ align: WebPopupTextAlign;
624
+ /** Inverse tone (over a dark image) — the zone node's, not the slot's. */
625
+ inverse: boolean;
626
+ colors: WebPopupResolvedColors;
627
+ /** Default 'live'; an editor canvas or a preview card passes 'preview'. */
628
+ mode?: WebPopupReaderMode;
629
+ onRequestClose?: () => void;
630
+ onPageChange?: (page: string) => void;
631
+ onSubmitForm?: WebPopupFormSubmitHandler;
632
+ onSubmitChoice?: WebPopupChoiceSubmitHandler;
633
+ onSubmitRating?: WebPopupRatingSubmitHandler;
634
+ /** `subscribe`-action buttons; absent = they render inert (previews, editor). */
635
+ onRequestSubscribe?: () => Promise<WebPopupSubscribeResult>;
636
+ };
637
+ /** Props of WebPopupBox — one resolved page's box, no viewport chrome. */
638
+ export type WebPopupBoxProps = {
639
+ page: WebPopupResolvedPage;
640
+ params: WebPopupParams;
641
+ /** What the box is drawn for; default 'live'. 'preview' reaches the leaves: no autoplay, no interval. */
642
+ mode?: WebPopupReaderMode;
643
+ /** Palette to draw with; default 'light'. The reader resolves 'auto' before it gets here. */
644
+ colorScheme?: WebPopupColorScheme;
645
+ /** Editor-only leaf overrides — see WebPopupLeafRenderers. */
646
+ leafRenderers?: WebPopupLeafRenderers;
647
+ /** Play the page-switch fade on this render (the reader sets it after navigation). */
648
+ animatePage?: boolean;
649
+ onRequestClose?: () => void;
650
+ /** `page`-action buttons; absent = they render inert (editor canvas). */
651
+ onPageChange?: (page: string) => void;
652
+ /** Form-slot submits; absent = form slots render inert (previews, editor). */
653
+ onSubmitForm?: WebPopupFormSubmitHandler;
654
+ /** Choice-slot submits; absent = choice slots render inert. */
655
+ onSubmitChoice?: WebPopupChoiceSubmitHandler;
656
+ /** Rating-slot submits; absent = rating slots render inert. */
657
+ onSubmitRating?: WebPopupRatingSubmitHandler;
658
+ /** `subscribe`-action buttons; absent = they render inert (previews, editor). */
659
+ onRequestSubscribe?: () => Promise<WebPopupSubscribeResult>;
660
+ };
@@ -0,0 +1,10 @@
1
+ // The web-popup model. The stored/wire shape is WebPopupJson —
2
+ // { version: 2, params } serialized into PopupFormContent.json. A popup is a
3
+ // set of PAGES (each page = a layout TREE plus named content zones and
4
+ // images) under a shared CHROME (size, colors, placement, overlay,
5
+ // animation, ...). Only one page shows at a time; `page`-action buttons
6
+ // switch between them. The editor (smart-blocks) produces the json;
7
+ // WebPopupReader renders it on the customer's site (WebSDK) and in previews
8
+ // (content). Pure types only — constants live in constants.tsx, helpers in
9
+ // helpers.ts, layout-tree validation in layout.ts.
10
+ export {};