@mrrocket/sdk 0.4.0 → 0.8.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +102 -7
  3. package/dist/api.d.ts +23 -3
  4. package/dist/api.js +7 -3
  5. package/dist/api.js.map +1 -1
  6. package/dist/browser/mrrocket-flows.js +64 -0
  7. package/dist/browser/mrrocket-push.js +1 -0
  8. package/dist/browser/mrrocket-replay.js +3 -3
  9. package/dist/browser/mrrocket-sw.js +1 -0
  10. package/dist/browser/mrrocket.js +2 -1
  11. package/dist/browser.d.ts +1 -1
  12. package/dist/browser.js +3 -1
  13. package/dist/browser.js.map +1 -1
  14. package/dist/client.d.ts +36 -1
  15. package/dist/client.js +161 -6
  16. package/dist/client.js.map +1 -1
  17. package/dist/context.d.ts +10 -0
  18. package/dist/context.js.map +1 -1
  19. package/dist/core.d.ts +64 -3
  20. package/dist/core.js +54 -2
  21. package/dist/core.js.map +1 -1
  22. package/dist/feedback.d.ts +19 -4
  23. package/dist/feedback.js +652 -190
  24. package/dist/feedback.js.map +1 -1
  25. package/dist/flows-ui.d.ts +23 -0
  26. package/dist/flows-ui.js +384 -0
  27. package/dist/flows-ui.js.map +1 -0
  28. package/dist/flows-web.d.ts +19 -0
  29. package/dist/flows-web.js +42 -0
  30. package/dist/flows-web.js.map +1 -0
  31. package/dist/flows.d.ts +222 -0
  32. package/dist/flows.js +409 -0
  33. package/dist/flows.js.map +1 -0
  34. package/dist/index.d.ts +5 -2
  35. package/dist/index.js +9 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/proxy.d.ts +34 -1
  38. package/dist/proxy.js +14 -1
  39. package/dist/proxy.js.map +1 -1
  40. package/dist/push-web.d.ts +43 -0
  41. package/dist/push-web.js +81 -0
  42. package/dist/push-web.js.map +1 -0
  43. package/dist/react-native/index.d.cts +305 -3
  44. package/dist/react-native/index.d.ts +305 -3
  45. package/dist/react-native/index.js +852 -31
  46. package/dist/react-native/index.js.map +1 -1
  47. package/dist/styles.d.ts +1 -1
  48. package/dist/styles.js +127 -24
  49. package/dist/styles.js.map +1 -1
  50. package/dist/ui.d.ts +3 -0
  51. package/dist/ui.js +12 -0
  52. package/dist/ui.js.map +1 -1
  53. package/package.json +10 -2
@@ -0,0 +1,384 @@
1
+ // src/flows-ui.ts
2
+ import { discounted, money, offerButton } from "./flows.js";
3
+ var STYLE_ID = "ckf-styles";
4
+ var FLOWS_CSS = `
5
+ .ckf-root{--ckf-accent:#3f76ff;--ckf-accent-ink:#fff;--ckf-bg:#fff;--ckf-fg:#1c1d20;--ckf-muted:#5d6068;--ckf-soft:#f4f5f7;--ckf-line:#e4e5e8;--ckf-danger:#d92d20;--ckf-radius:14px;--ckf-font:inherit;--ckf-shadow:0 16px 40px -12px rgba(16,18,24,.28),0 0 0 1px rgba(16,18,24,.07)}
6
+ .ckf-root[data-theme=dark]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}
7
+ @media (prefers-color-scheme:dark){.ckf-root[data-theme=auto]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}}
8
+ .ckf-root,.ckf-root *{box-sizing:border-box}
9
+ .ckf-root{font-family:var(--ckf-font);font-size:14px;line-height:1.45;color:var(--ckf-fg);-webkit-font-smoothing:antialiased}
10
+ .ckf-root :where(button,textarea){font:inherit;color:inherit;margin:0}
11
+ .ckf-root :focus-visible{outline:2px solid var(--ckf-accent);outline-offset:2px}
12
+ .ckf-scrim{position:fixed;inset:0;z-index:2147483002;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;animation:ckf-fade .15s ease-out}
13
+ .ckf-card{position:relative;width:100%;max-width:400px;max-height:calc(100dvh - 32px);overflow-y:auto;background:var(--ckf-bg);color:var(--ckf-fg);border-radius:var(--ckf-radius);box-shadow:var(--ckf-shadow);padding:18px 20px 20px;display:flex;flex-direction:column;gap:12px;animation:ckf-in .18s ease-out}
14
+ .ckf-float{position:fixed;z-index:2147483001;right:20px;bottom:20px;width:340px;max-width:calc(100vw - 24px)}
15
+ .ckf-top{display:flex;align-items:center;gap:8px;min-height:20px}
16
+ .ckf-dots{display:flex;gap:4px}
17
+ .ckf-dot{width:16px;height:4px;border-radius:4px;background:var(--ckf-line)}
18
+ .ckf-dot[data-on]{background:var(--ckf-accent)}
19
+ .ckf-x{margin-left:auto;width:28px;height:28px;border:0;border-radius:8px;background:none;color:var(--ckf-muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:18px;line-height:1}
20
+ .ckf-x:hover{background:var(--ckf-soft);color:var(--ckf-fg)}
21
+ .ckf-h{margin:0;font-size:17px;font-weight:650;letter-spacing:-.01em;text-wrap:balance}
22
+ .ckf-p{margin:0;color:var(--ckf-muted);text-wrap:pretty;white-space:pre-wrap}
23
+ .ckf-small{margin:0;font-size:12px;color:var(--ckf-muted)}
24
+ .ckf-opts{display:flex;flex-direction:column;gap:6px;margin:0;padding:0;list-style:none}
25
+ .ckf-opt{display:flex;align-items:center;gap:10px;width:100%;min-height:40px;padding:8px 12px;border:1px solid var(--ckf-line);border-radius:10px;background:var(--ckf-bg);text-align:left;cursor:pointer;font-weight:500}
26
+ .ckf-opt:hover{background:var(--ckf-soft)}
27
+ .ckf-opt[aria-checked=true]{border-color:var(--ckf-accent);background:color-mix(in srgb,var(--ckf-accent) 8%,var(--ckf-bg))}
28
+ .ckf-radio{width:16px;height:16px;flex:none;border-radius:50%;border:1.5px solid var(--ckf-line);display:inline-flex;align-items:center;justify-content:center}
29
+ .ckf-opt[aria-checked=true] .ckf-radio{border-color:var(--ckf-accent)}
30
+ .ckf-opt[aria-checked=true] .ckf-radio::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--ckf-accent)}
31
+ .ckf-box{border-radius:10px;background:var(--ckf-soft);padding:12px;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
32
+ .ckf-big{font-size:22px;font-weight:650;font-variant-numeric:tabular-nums}
33
+ .ckf-strike{color:var(--ckf-muted);text-decoration:line-through;font-variant-numeric:tabular-nums}
34
+ .ckf-text{width:100%;min-height:64px;resize:vertical;border:1px solid var(--ckf-line);border-radius:10px;background:var(--ckf-bg);padding:8px 10px}
35
+ .ckf-text::placeholder{color:var(--ckf-muted)}
36
+ .ckf-btns{display:flex;flex-direction:column;gap:6px}
37
+ .ckf-btn{height:38px;padding:0 14px;border-radius:10px;border:1px solid var(--ckf-line);background:var(--ckf-bg);font-weight:600;cursor:pointer;transition:filter .1s linear,background-color .1s linear}
38
+ .ckf-btn:hover{background:var(--ckf-soft)}
39
+ .ckf-btn[disabled]{opacity:.55;cursor:not-allowed}
40
+ .ckf-primary{background:var(--ckf-accent);border-color:var(--ckf-accent);color:var(--ckf-accent-ink)}
41
+ .ckf-primary:hover{background:var(--ckf-accent);filter:brightness(1.07)}
42
+ .ckf-danger{background:var(--ckf-danger);border-color:var(--ckf-danger);color:#fff}
43
+ .ckf-danger:hover{background:var(--ckf-danger);filter:brightness(1.07)}
44
+ .ckf-quiet{border-color:transparent;background:none;color:var(--ckf-muted);height:30px;font-weight:500}
45
+ .ckf-quiet:hover{background:none;color:var(--ckf-fg)}
46
+ .ckf-err{margin:0;color:var(--ckf-danger);font-size:13px;font-weight:500}
47
+ .ckf-row{display:flex;justify-content:flex-end;gap:8px;align-items:center}
48
+ .ckf-row .ckf-small{margin-right:auto}
49
+ .ckf-ring{position:fixed;z-index:2147483000;pointer-events:none;border-radius:10px;box-shadow:0 0 0 2px var(--ckf-accent),0 0 0 6px color-mix(in srgb,var(--ckf-accent) 25%,transparent);transition:all .15s ease-out}
50
+ .ckf-check{display:flex;align-items:flex-start;gap:10px;width:100%;padding:8px 6px;border:0;border-radius:8px;background:none;text-align:left}
51
+ .ckf-check:where(button){cursor:pointer}
52
+ .ckf-check:where(button):hover{background:var(--ckf-soft)}
53
+ .ckf-tick{width:18px;height:18px;flex:none;margin-top:1px;border-radius:50%;border:1.5px solid var(--ckf-line);display:inline-flex;align-items:center;justify-content:center;color:var(--ckf-accent-ink);font-size:11px;font-weight:700}
54
+ .ckf-check[data-done] .ckf-tick{background:var(--ckf-accent);border-color:var(--ckf-accent)}
55
+ .ckf-check[data-done] b{color:var(--ckf-muted);text-decoration:line-through}
56
+ .ckf-check span{display:flex;flex-direction:column;gap:2px;min-width:0}
57
+ .ckf-bar{height:4px;border-radius:4px;background:var(--ckf-soft);overflow:hidden}
58
+ .ckf-bar i{display:block;height:100%;background:var(--ckf-accent)}
59
+ .ckf-banner{position:fixed;z-index:2147483001;left:50%;top:12px;transform:translateX(-50%);width:min(640px,calc(100vw - 24px));background:var(--ckf-bg);border-radius:12px;box-shadow:var(--ckf-shadow);padding:10px 10px 10px 16px;display:flex;align-items:center;gap:12px;animation:ckf-in .18s ease-out}
60
+ .ckf-banner>span{flex:1;min-width:0;display:flex;flex-direction:column}
61
+ .ckf-banner .ckf-btn{height:32px}
62
+ @keyframes ckf-fade{from{opacity:0}}
63
+ @keyframes ckf-in{from{opacity:0;transform:translateY(6px)}}
64
+ .ckf-banner{animation-name:ckf-fade}
65
+ @media (max-width:520px){.ckf-scrim{align-items:flex-end;padding:0}.ckf-scrim .ckf-card{max-width:none;border-radius:var(--ckf-radius) var(--ckf-radius) 0 0;max-height:92dvh}.ckf-float{right:12px;bottom:12px}}
66
+ @media (prefers-reduced-motion:reduce){.ckf-root *{animation:none!important;transition:none!important}}
67
+ `;
68
+ var h = (tag, attrs = {}, ...children) => {
69
+ const el = document.createElement(tag);
70
+ for (const [k, v] of Object.entries(attrs)) {
71
+ if (v === void 0 || v === false) continue;
72
+ if (typeof v === "function") el.addEventListener(k.slice(2).toLowerCase(), v);
73
+ else if (k === "className") el.className = String(v);
74
+ else el.setAttribute(k, v === true ? "" : v);
75
+ }
76
+ for (const c of children) if (c || c === "") el.append(typeof c === "string" ? document.createTextNode(c) : c);
77
+ return el;
78
+ };
79
+ var button = (label, kind, onClick, opts = {}) => h(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ className: `ckf-btn${kind === "secondary" ? "" : ` ckf-${kind}`}`,
84
+ disabled: !!opts.disabled,
85
+ "data-ckf-id": opts.id,
86
+ onClick: () => onClick()
87
+ },
88
+ label
89
+ );
90
+ var closeButton = (label, onClick) => h("button", { type: "button", className: "ckf-x", "aria-label": label, "data-ckf-id": "x", onClick: () => onClick() }, "\xD7");
91
+ var dots = (step, steps) => steps > 1 ? h(
92
+ "span",
93
+ { className: "ckf-dots", role: "img", "aria-label": `Step ${step} of ${steps}` },
94
+ ...Array.from({ length: steps }, (_, i) => h("span", { className: "ckf-dot", "data-on": i < step }))
95
+ ) : h("span");
96
+ var screenKey = (v) => v.type === "cancel" ? `c:${v.screen}` : v.type === "step" ? `s:${v.step.id}` : v.type === "checklist" ? `l:${v.flow.id}` : `m:${v.message.id}`;
97
+ var mountFlows = (ctl, theme = {}) => {
98
+ if (typeof document === "undefined") return () => {
99
+ };
100
+ if (!document.getElementById(STYLE_ID)) {
101
+ const style = document.createElement("style");
102
+ style.id = STYLE_ID;
103
+ style.textContent = FLOWS_CSS;
104
+ document.head.appendChild(style);
105
+ }
106
+ const root = h("div", { className: `ckf-root${theme.className ? ` ${theme.className}` : ""}`, "data-theme": theme.theme ?? "auto" });
107
+ if (theme.accent) root.style.setProperty("--ckf-accent", theme.accent);
108
+ document.body.appendChild(root);
109
+ let lastKey = "";
110
+ let returnFocus = null;
111
+ let comment = "";
112
+ let placed = null;
113
+ const cancelCard = (v) => {
114
+ const titleId = "ckf-title";
115
+ const busy = v.busy;
116
+ const body = [];
117
+ if (v.screen === "survey") {
118
+ body.push(
119
+ h("h2", { className: "ckf-h", id: titleId }, v.flow.question || "Why are you leaving?"),
120
+ h(
121
+ "div",
122
+ { className: "ckf-opts", role: "radiogroup", "aria-labelledby": titleId },
123
+ ...v.flow.reasons.map(
124
+ (r) => h(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ role: "radio",
129
+ className: "ckf-opt",
130
+ "aria-checked": String(v.reason === r.id),
131
+ "data-ckf-id": `r:${r.id}`,
132
+ onClick: () => ctl.cancel.pick(r.id)
133
+ },
134
+ h("span", { className: "ckf-radio" }),
135
+ r.label
136
+ )
137
+ )
138
+ ),
139
+ h(
140
+ "div",
141
+ { className: "ckf-btns" },
142
+ button("Continue", "primary", ctl.cancel.next, { disabled: !v.reason, id: "go" }),
143
+ button("Never mind, keep my plan", "quiet", ctl.cancel.close)
144
+ )
145
+ );
146
+ } else if (v.screen === "offer" && v.offer) {
147
+ const o = v.offer;
148
+ const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;
149
+ let detail = false;
150
+ if (o.kind === "discount" && o.percent)
151
+ detail = v.price ? h(
152
+ "div",
153
+ { className: "ckf-box" },
154
+ h("span", { className: "ckf-big" }, money(discounted(v.price.amount, o.percent), v.price.currency)),
155
+ h("span", { className: "ckf-strike" }, money(v.price.amount, v.price.currency)),
156
+ h(
157
+ "span",
158
+ { className: "ckf-small" },
159
+ `/${v.price.interval ?? "month"}${o.months ? ` for ${o.months} ${o.months === 1 ? "month" : "months"}` : ""}`
160
+ )
161
+ ) : h(
162
+ "div",
163
+ { className: "ckf-box" },
164
+ h("span", { className: "ckf-big" }, `${o.percent}% off`),
165
+ o.months ? h("span", { className: "ckf-small" }, `for ${o.months} ${o.months === 1 ? "month" : "months"}`) : false
166
+ );
167
+ if (o.kind === "pause" && o.months)
168
+ detail = h(
169
+ "div",
170
+ { className: "ckf-box" },
171
+ h("span", {}, `Pause for up to ${o.months} ${o.months === 1 ? "month" : "months"}. Your data stays.`)
172
+ );
173
+ if (o.kind === "downgrade" && o.planName) detail = h("div", { className: "ckf-box" }, h("b", {}, o.planName));
174
+ const text = o.kind === "contact" ? h("textarea", {
175
+ className: "ckf-text",
176
+ placeholder: "What would make you stay?",
177
+ "aria-label": "Your message",
178
+ maxlength: "500",
179
+ "data-ckf-id": "comment",
180
+ onInput: (e) => comment = e.target.value
181
+ }) : null;
182
+ if (text) text.value = comment;
183
+ body.push(
184
+ label ? h("p", { className: "ckf-small" }, `You said: ${label}`) : false,
185
+ h("h2", { className: "ckf-h", id: titleId }, o.headline),
186
+ detail,
187
+ text ?? false,
188
+ h(
189
+ "div",
190
+ { className: "ckf-btns" },
191
+ button(busy ? "One moment\u2026" : offerButton(o), "primary", () => ctl.cancel.accept(comment), { disabled: busy, id: "go" }),
192
+ button("No thanks, continue cancelling", "quiet", ctl.cancel.decline, { disabled: busy })
193
+ )
194
+ );
195
+ } else {
196
+ const plain = v.steps === 1;
197
+ const text = plain ? null : h("textarea", {
198
+ className: "ckf-text",
199
+ placeholder: "Anything else we should know? (optional)",
200
+ "aria-label": "Anything else we should know? (optional)",
201
+ maxlength: "500",
202
+ "data-ckf-id": "comment",
203
+ onInput: (e) => comment = e.target.value
204
+ });
205
+ if (text) text.value = comment;
206
+ body.push(
207
+ h("h2", { className: "ckf-h", id: titleId }, v.flow.confirm.title),
208
+ v.flow.confirm.body ? h("p", { className: "ckf-p" }, v.flow.confirm.body) : false,
209
+ text ?? false,
210
+ h(
211
+ "div",
212
+ { className: "ckf-btns" },
213
+ button(busy ? "One moment\u2026" : v.flow.confirm.button, "danger", () => ctl.cancel.confirm(comment), { disabled: busy, id: "go" }),
214
+ button("Keep my plan", "secondary", ctl.cancel.close, { disabled: busy })
215
+ )
216
+ );
217
+ }
218
+ return h(
219
+ "div",
220
+ { className: "ckf-scrim", onClick: (e) => e.target === e.currentTarget && !busy && ctl.cancel.close() },
221
+ h(
222
+ "div",
223
+ { className: "ckf-card", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId },
224
+ h("div", { className: "ckf-top" }, dots(v.step, v.steps), closeButton("Close", ctl.cancel.close)),
225
+ ...body,
226
+ v.error ? h("p", { className: "ckf-err", role: "alert" }, v.error) : false
227
+ )
228
+ );
229
+ };
230
+ const stepCard = (v) => {
231
+ const s = v.step;
232
+ const card = h(
233
+ "div",
234
+ { className: "ckf-card", role: "dialog", "aria-modal": s.kind === "modal" ? "true" : void 0, "aria-labelledby": "ckf-title" },
235
+ h(
236
+ "div",
237
+ { className: "ckf-top" },
238
+ v.total > 1 ? h("p", { className: "ckf-small" }, `Step ${v.index + 1} of ${v.total}`) : h("span"),
239
+ closeButton("Close the tour", ctl.onboarding.dismiss)
240
+ ),
241
+ h("h2", { className: "ckf-h", id: "ckf-title" }, s.title),
242
+ s.body ? h("p", { className: "ckf-p" }, s.body) : false,
243
+ h("div", { className: "ckf-row" }, button(s.button || (v.last ? "Done" : "Next"), "primary", ctl.onboarding.next, { id: "go" }))
244
+ );
245
+ if (s.kind === "modal") return h("div", { className: "ckf-scrim" }, card);
246
+ const target = s.target ? document.querySelector(`[data-ck-target="${CSS.escape(s.target)}"]`) : null;
247
+ const float = h("div", { className: "ckf-float" }, card);
248
+ if (target) {
249
+ const ring = h("div", { className: "ckf-ring", "aria-hidden": "true" });
250
+ placed = { float, ring, target };
251
+ place(float, ring, target);
252
+ return h("div", {}, ring, float);
253
+ }
254
+ return float;
255
+ };
256
+ const place = (float, ring, target) => {
257
+ const r = target.getBoundingClientRect();
258
+ Object.assign(ring.style, { left: `${r.left - 4}px`, top: `${r.top - 4}px`, width: `${r.width + 8}px`, height: `${r.height + 8}px` });
259
+ const w = Math.min(340, window.innerWidth - 24);
260
+ const left = Math.max(12, Math.min(r.left, window.innerWidth - w - 12));
261
+ const below = r.bottom + 12;
262
+ Object.assign(float.style, { left: `${left}px`, right: "auto", width: `${w}px` });
263
+ if (below + 200 < window.innerHeight) Object.assign(float.style, { top: `${below}px`, bottom: "auto" });
264
+ else Object.assign(float.style, { top: "auto", bottom: `${Math.max(12, window.innerHeight - r.top + 12)}px` });
265
+ };
266
+ const checklist = (v) => {
267
+ const done = v.items.filter((i) => i.done).length;
268
+ return h(
269
+ "div",
270
+ { className: "ckf-float" },
271
+ h(
272
+ "div",
273
+ { className: "ckf-card", role: "region", "aria-labelledby": "ckf-title" },
274
+ h(
275
+ "div",
276
+ { className: "ckf-top" },
277
+ h("h2", { className: "ckf-h", id: "ckf-title" }, v.flow.name),
278
+ closeButton("Hide the checklist", ctl.onboarding.dismiss)
279
+ ),
280
+ h("p", { className: "ckf-small" }, `${done} of ${v.items.length} done`),
281
+ h(
282
+ "div",
283
+ { className: "ckf-bar", role: "progressbar", "aria-valuemin": "0", "aria-valuemax": String(v.items.length), "aria-valuenow": String(done) },
284
+ h("i", { style: `width:${done / v.items.length * 100}%` })
285
+ ),
286
+ h(
287
+ "div",
288
+ { className: "ckf-opts" },
289
+ ...v.items.map(({ step, done: d }) => {
290
+ const inner = [
291
+ h("span", { className: "ckf-tick", "aria-hidden": "true" }, d ? "\u2713" : ""),
292
+ h("span", {}, h("b", {}, step.title), step.body ? h("span", { className: "ckf-small" }, step.body) : false)
293
+ ];
294
+ return d || step.event ? h("div", { className: "ckf-check", "data-done": d, role: "listitem" }, ...inner) : h(
295
+ "button",
296
+ {
297
+ type: "button",
298
+ className: "ckf-check",
299
+ "aria-label": `Mark \u201C${step.title}\u201D done`,
300
+ "data-ckf-id": `i:${step.id}`,
301
+ onClick: () => ctl.onboarding.check(step.id)
302
+ },
303
+ ...inner
304
+ );
305
+ })
306
+ )
307
+ )
308
+ );
309
+ };
310
+ const message = (v) => {
311
+ const m = v.message;
312
+ const action = m.button ? button(m.button, "primary", ctl.message.click, { id: "go" }) : false;
313
+ if (m.style === "banner")
314
+ return h(
315
+ "div",
316
+ { className: "ckf-banner", role: "status" },
317
+ h("span", {}, h("b", {}, m.title), m.body ? h("span", { className: "ckf-small" }, m.body) : false),
318
+ action,
319
+ closeButton("Dismiss", ctl.message.dismiss)
320
+ );
321
+ return h(
322
+ "div",
323
+ { className: "ckf-scrim", onClick: (e) => e.target === e.currentTarget && ctl.message.dismiss() },
324
+ h(
325
+ "div",
326
+ { className: "ckf-card", role: "dialog", "aria-modal": "true", "aria-labelledby": "ckf-title" },
327
+ h("div", { className: "ckf-top" }, h("span"), closeButton("Dismiss", ctl.message.dismiss)),
328
+ h("h2", { className: "ckf-h", id: "ckf-title" }, m.title),
329
+ m.body ? h("p", { className: "ckf-p" }, m.body) : false,
330
+ action ? h("div", { className: "ckf-row" }, action) : false
331
+ )
332
+ );
333
+ };
334
+ const draw = () => {
335
+ const v = ctl.view();
336
+ const key = v ? screenKey(v) : "";
337
+ const focused = document.activeElement?.closest?.(".ckf-root") ? document.activeElement?.getAttribute("data-ckf-id") : null;
338
+ if (key && !lastKey && document.activeElement instanceof HTMLElement && !document.activeElement.closest(".ckf-root"))
339
+ returnFocus = document.activeElement;
340
+ if (key !== lastKey && !key.startsWith("c:")) comment = "";
341
+ if (!key) comment = "";
342
+ placed = null;
343
+ root.replaceChildren(
344
+ ...v ? [v.type === "cancel" ? cancelCard(v) : v.type === "step" ? stepCard(v) : v.type === "checklist" ? checklist(v) : message(v)] : []
345
+ );
346
+ if (key !== lastKey) {
347
+ const modal = root.querySelector("[aria-modal=true]");
348
+ const first = modal?.querySelector("[role=radio], [data-ckf-id=go], textarea") ?? null;
349
+ first?.focus();
350
+ if (!key && returnFocus) {
351
+ returnFocus.focus();
352
+ returnFocus = null;
353
+ }
354
+ } else if (focused) root.querySelector(`[data-ckf-id="${CSS.escape(focused)}"]`)?.focus();
355
+ lastKey = key;
356
+ };
357
+ const onKey = (e) => {
358
+ if (e.key !== "Escape") return;
359
+ const v = ctl.view();
360
+ if (v?.type === "cancel") ctl.cancel.close();
361
+ else if (v?.type === "message") ctl.message.dismiss();
362
+ else if (v?.type === "step" && v.step.kind === "modal") ctl.onboarding.dismiss();
363
+ };
364
+ const follow = () => {
365
+ if (placed) place(placed.float, placed.ring, placed.target);
366
+ };
367
+ document.addEventListener("keydown", onKey);
368
+ window.addEventListener("resize", follow);
369
+ window.addEventListener("scroll", follow, { passive: true, capture: true });
370
+ const off = ctl.subscribe(draw);
371
+ draw();
372
+ return () => {
373
+ off();
374
+ document.removeEventListener("keydown", onKey);
375
+ window.removeEventListener("resize", follow);
376
+ window.removeEventListener("scroll", follow, { capture: true });
377
+ root.remove();
378
+ };
379
+ };
380
+ export {
381
+ FLOWS_CSS,
382
+ mountFlows
383
+ };
384
+ //# sourceMappingURL=flows-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flows-ui.ts"],"sourcesContent":["/**\n * The web renderer of the cancel flow, onboarding and in-app messages (flows.ts): plain DOM, no React, so the Next.js\n * provider and the script tag share it. Both load it only when something is about to show (a dynamic import, and\n * `sdk-flows.js` for the script tag), so pages that show nothing never download it.\n *\n * Themeable three ways: `theme` (\"light\", \"dark\", \"auto\") and `accent` from the SDK's options, and CSS variables on\n * `.ckf-root` in the site's own stylesheet (`--ckf-accent`, `--ckf-bg`, `--ckf-fg`, `--ckf-muted`, `--ckf-line`,\n * `--ckf-radius`, `--ckf-font`). Every text is set with textContent; nothing from the server is parsed as HTML.\n */\nimport { type FlowView, type FlowsController, discounted, money, offerButton } from \"./flows\";\n\nexport interface FlowsTheme {\n theme?: \"light\" | \"dark\" | \"auto\";\n /** The main color of buttons and highlights, like \"#3f76ff\". */\n accent?: string;\n /** An extra class on the root, for the site's own CSS. */\n className?: string;\n}\n\nconst STYLE_ID = \"ckf-styles\";\n\nexport const FLOWS_CSS = `\n.ckf-root{--ckf-accent:#3f76ff;--ckf-accent-ink:#fff;--ckf-bg:#fff;--ckf-fg:#1c1d20;--ckf-muted:#5d6068;--ckf-soft:#f4f5f7;--ckf-line:#e4e5e8;--ckf-danger:#d92d20;--ckf-radius:14px;--ckf-font:inherit;--ckf-shadow:0 16px 40px -12px rgba(16,18,24,.28),0 0 0 1px rgba(16,18,24,.07)}\n.ckf-root[data-theme=dark]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}\n@media (prefers-color-scheme:dark){.ckf-root[data-theme=auto]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}}\n.ckf-root,.ckf-root *{box-sizing:border-box}\n.ckf-root{font-family:var(--ckf-font);font-size:14px;line-height:1.45;color:var(--ckf-fg);-webkit-font-smoothing:antialiased}\n.ckf-root :where(button,textarea){font:inherit;color:inherit;margin:0}\n.ckf-root :focus-visible{outline:2px solid var(--ckf-accent);outline-offset:2px}\n.ckf-scrim{position:fixed;inset:0;z-index:2147483002;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;padding:16px;animation:ckf-fade .15s ease-out}\n.ckf-card{position:relative;width:100%;max-width:400px;max-height:calc(100dvh - 32px);overflow-y:auto;background:var(--ckf-bg);color:var(--ckf-fg);border-radius:var(--ckf-radius);box-shadow:var(--ckf-shadow);padding:18px 20px 20px;display:flex;flex-direction:column;gap:12px;animation:ckf-in .18s ease-out}\n.ckf-float{position:fixed;z-index:2147483001;right:20px;bottom:20px;width:340px;max-width:calc(100vw - 24px)}\n.ckf-top{display:flex;align-items:center;gap:8px;min-height:20px}\n.ckf-dots{display:flex;gap:4px}\n.ckf-dot{width:16px;height:4px;border-radius:4px;background:var(--ckf-line)}\n.ckf-dot[data-on]{background:var(--ckf-accent)}\n.ckf-x{margin-left:auto;width:28px;height:28px;border:0;border-radius:8px;background:none;color:var(--ckf-muted);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:18px;line-height:1}\n.ckf-x:hover{background:var(--ckf-soft);color:var(--ckf-fg)}\n.ckf-h{margin:0;font-size:17px;font-weight:650;letter-spacing:-.01em;text-wrap:balance}\n.ckf-p{margin:0;color:var(--ckf-muted);text-wrap:pretty;white-space:pre-wrap}\n.ckf-small{margin:0;font-size:12px;color:var(--ckf-muted)}\n.ckf-opts{display:flex;flex-direction:column;gap:6px;margin:0;padding:0;list-style:none}\n.ckf-opt{display:flex;align-items:center;gap:10px;width:100%;min-height:40px;padding:8px 12px;border:1px solid var(--ckf-line);border-radius:10px;background:var(--ckf-bg);text-align:left;cursor:pointer;font-weight:500}\n.ckf-opt:hover{background:var(--ckf-soft)}\n.ckf-opt[aria-checked=true]{border-color:var(--ckf-accent);background:color-mix(in srgb,var(--ckf-accent) 8%,var(--ckf-bg))}\n.ckf-radio{width:16px;height:16px;flex:none;border-radius:50%;border:1.5px solid var(--ckf-line);display:inline-flex;align-items:center;justify-content:center}\n.ckf-opt[aria-checked=true] .ckf-radio{border-color:var(--ckf-accent)}\n.ckf-opt[aria-checked=true] .ckf-radio::after{content:\"\";width:8px;height:8px;border-radius:50%;background:var(--ckf-accent)}\n.ckf-box{border-radius:10px;background:var(--ckf-soft);padding:12px;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}\n.ckf-big{font-size:22px;font-weight:650;font-variant-numeric:tabular-nums}\n.ckf-strike{color:var(--ckf-muted);text-decoration:line-through;font-variant-numeric:tabular-nums}\n.ckf-text{width:100%;min-height:64px;resize:vertical;border:1px solid var(--ckf-line);border-radius:10px;background:var(--ckf-bg);padding:8px 10px}\n.ckf-text::placeholder{color:var(--ckf-muted)}\n.ckf-btns{display:flex;flex-direction:column;gap:6px}\n.ckf-btn{height:38px;padding:0 14px;border-radius:10px;border:1px solid var(--ckf-line);background:var(--ckf-bg);font-weight:600;cursor:pointer;transition:filter .1s linear,background-color .1s linear}\n.ckf-btn:hover{background:var(--ckf-soft)}\n.ckf-btn[disabled]{opacity:.55;cursor:not-allowed}\n.ckf-primary{background:var(--ckf-accent);border-color:var(--ckf-accent);color:var(--ckf-accent-ink)}\n.ckf-primary:hover{background:var(--ckf-accent);filter:brightness(1.07)}\n.ckf-danger{background:var(--ckf-danger);border-color:var(--ckf-danger);color:#fff}\n.ckf-danger:hover{background:var(--ckf-danger);filter:brightness(1.07)}\n.ckf-quiet{border-color:transparent;background:none;color:var(--ckf-muted);height:30px;font-weight:500}\n.ckf-quiet:hover{background:none;color:var(--ckf-fg)}\n.ckf-err{margin:0;color:var(--ckf-danger);font-size:13px;font-weight:500}\n.ckf-row{display:flex;justify-content:flex-end;gap:8px;align-items:center}\n.ckf-row .ckf-small{margin-right:auto}\n.ckf-ring{position:fixed;z-index:2147483000;pointer-events:none;border-radius:10px;box-shadow:0 0 0 2px var(--ckf-accent),0 0 0 6px color-mix(in srgb,var(--ckf-accent) 25%,transparent);transition:all .15s ease-out}\n.ckf-check{display:flex;align-items:flex-start;gap:10px;width:100%;padding:8px 6px;border:0;border-radius:8px;background:none;text-align:left}\n.ckf-check:where(button){cursor:pointer}\n.ckf-check:where(button):hover{background:var(--ckf-soft)}\n.ckf-tick{width:18px;height:18px;flex:none;margin-top:1px;border-radius:50%;border:1.5px solid var(--ckf-line);display:inline-flex;align-items:center;justify-content:center;color:var(--ckf-accent-ink);font-size:11px;font-weight:700}\n.ckf-check[data-done] .ckf-tick{background:var(--ckf-accent);border-color:var(--ckf-accent)}\n.ckf-check[data-done] b{color:var(--ckf-muted);text-decoration:line-through}\n.ckf-check span{display:flex;flex-direction:column;gap:2px;min-width:0}\n.ckf-bar{height:4px;border-radius:4px;background:var(--ckf-soft);overflow:hidden}\n.ckf-bar i{display:block;height:100%;background:var(--ckf-accent)}\n.ckf-banner{position:fixed;z-index:2147483001;left:50%;top:12px;transform:translateX(-50%);width:min(640px,calc(100vw - 24px));background:var(--ckf-bg);border-radius:12px;box-shadow:var(--ckf-shadow);padding:10px 10px 10px 16px;display:flex;align-items:center;gap:12px;animation:ckf-in .18s ease-out}\n.ckf-banner>span{flex:1;min-width:0;display:flex;flex-direction:column}\n.ckf-banner .ckf-btn{height:32px}\n@keyframes ckf-fade{from{opacity:0}}\n@keyframes ckf-in{from{opacity:0;transform:translateY(6px)}}\n.ckf-banner{animation-name:ckf-fade}\n@media (max-width:520px){.ckf-scrim{align-items:flex-end;padding:0}.ckf-scrim .ckf-card{max-width:none;border-radius:var(--ckf-radius) var(--ckf-radius) 0 0;max-height:92dvh}.ckf-float{right:12px;bottom:12px}}\n@media (prefers-reduced-motion:reduce){.ckf-root *{animation:none!important;transition:none!important}}\n`;\n\ntype Attrs = Record<string, string | boolean | undefined | ((e: Event) => void)>;\n\n/** A small element builder: attributes, `on*` handlers and children (strings become text, never HTML). */\nconst h = (tag: string, attrs: Attrs = {}, ...children: (Node | string | false | null | undefined)[]) => {\n const el = document.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) {\n if (v === undefined || v === false) continue;\n if (typeof v === \"function\") el.addEventListener(k.slice(2).toLowerCase(), v);\n else if (k === \"className\") el.className = String(v);\n else el.setAttribute(k, v === true ? \"\" : v);\n }\n for (const c of children) if (c || c === \"\") el.append(typeof c === \"string\" ? document.createTextNode(c) : c);\n return el;\n};\n\nconst button = (label: string, kind: \"primary\" | \"danger\" | \"secondary\" | \"quiet\", onClick: () => void, opts: { disabled?: boolean; id?: string } = {}) =>\n h(\n \"button\",\n {\n type: \"button\",\n className: `ckf-btn${kind === \"secondary\" ? \"\" : ` ckf-${kind}`}`,\n disabled: !!opts.disabled,\n \"data-ckf-id\": opts.id,\n onClick: () => onClick(),\n },\n label,\n );\n\nconst closeButton = (label: string, onClick: () => void) =>\n h(\"button\", { type: \"button\", className: \"ckf-x\", \"aria-label\": label, \"data-ckf-id\": \"x\", onClick: () => onClick() }, \"×\");\n\nconst dots = (step: number, steps: number) =>\n steps > 1\n ? h(\n \"span\",\n { className: \"ckf-dots\", role: \"img\", \"aria-label\": `Step ${step} of ${steps}` },\n ...Array.from({ length: steps }, (_, i) => h(\"span\", { className: \"ckf-dot\", \"data-on\": i < step })),\n )\n : h(\"span\");\n\n/** A key that changes when the screen (not just its state) changes: then focus moves to the new screen. */\nconst screenKey = (v: FlowView) =>\n v.type === \"cancel\" ? `c:${v.screen}` : v.type === \"step\" ? `s:${v.step.id}` : v.type === \"checklist\" ? `l:${v.flow.id}` : `m:${v.message.id}`;\n\n/**\n * Draws the controller's view into the page until the returned function is called. One root element under <body>;\n * nothing is drawn while there is nothing to show.\n */\nexport const mountFlows = (ctl: FlowsController, theme: FlowsTheme = {}): (() => void) => {\n if (typeof document === \"undefined\") return () => {};\n if (!document.getElementById(STYLE_ID)) {\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = FLOWS_CSS;\n document.head.appendChild(style);\n }\n const root = h(\"div\", { className: `ckf-root${theme.className ? ` ${theme.className}` : \"\"}`, \"data-theme\": theme.theme ?? \"auto\" });\n if (theme.accent) root.style.setProperty(\"--ckf-accent\", theme.accent);\n document.body.appendChild(root);\n\n let lastKey = \"\";\n let returnFocus: HTMLElement | null = null;\n let comment = \"\";\n let placed: { float: HTMLElement; ring: HTMLElement; target: HTMLElement } | null = null;\n\n // ---------- Cancel flow ----------\n\n const cancelCard = (v: Extract<FlowView, { type: \"cancel\" }>) => {\n const titleId = \"ckf-title\";\n const busy = v.busy;\n const body: (Node | false)[] = [];\n if (v.screen === \"survey\") {\n body.push(\n h(\"h2\", { className: \"ckf-h\", id: titleId }, v.flow.question || \"Why are you leaving?\"),\n h(\n \"div\",\n { className: \"ckf-opts\", role: \"radiogroup\", \"aria-labelledby\": titleId },\n ...v.flow.reasons.map((r) =>\n h(\n \"button\",\n {\n type: \"button\",\n role: \"radio\",\n className: \"ckf-opt\",\n \"aria-checked\": String(v.reason === r.id),\n \"data-ckf-id\": `r:${r.id}`,\n onClick: () => ctl.cancel.pick(r.id),\n },\n h(\"span\", { className: \"ckf-radio\" }),\n r.label,\n ),\n ),\n ),\n h(\n \"div\",\n { className: \"ckf-btns\" },\n button(\"Continue\", \"primary\", ctl.cancel.next, { disabled: !v.reason, id: \"go\" }),\n button(\"Never mind, keep my plan\", \"quiet\", ctl.cancel.close),\n ),\n );\n } else if (v.screen === \"offer\" && v.offer) {\n const o = v.offer;\n const label = v.flow.reasons.find((r) => r.id === v.reason)?.label;\n let detail: Node | false = false;\n if (o.kind === \"discount\" && o.percent)\n detail = v.price\n ? h(\n \"div\",\n { className: \"ckf-box\" },\n h(\"span\", { className: \"ckf-big\" }, money(discounted(v.price.amount, o.percent), v.price.currency)),\n h(\"span\", { className: \"ckf-strike\" }, money(v.price.amount, v.price.currency)),\n h(\n \"span\",\n { className: \"ckf-small\" },\n `/${v.price.interval ?? \"month\"}${o.months ? ` for ${o.months} ${o.months === 1 ? \"month\" : \"months\"}` : \"\"}`,\n ),\n )\n : h(\n \"div\",\n { className: \"ckf-box\" },\n h(\"span\", { className: \"ckf-big\" }, `${o.percent}% off`),\n o.months ? h(\"span\", { className: \"ckf-small\" }, `for ${o.months} ${o.months === 1 ? \"month\" : \"months\"}`) : false,\n );\n if (o.kind === \"pause\" && o.months)\n detail = h(\n \"div\",\n { className: \"ckf-box\" },\n h(\"span\", {}, `Pause for up to ${o.months} ${o.months === 1 ? \"month\" : \"months\"}. Your data stays.`),\n );\n if (o.kind === \"downgrade\" && o.planName) detail = h(\"div\", { className: \"ckf-box\" }, h(\"b\", {}, o.planName));\n const text =\n o.kind === \"contact\"\n ? (h(\"textarea\", {\n className: \"ckf-text\",\n placeholder: \"What would make you stay?\",\n \"aria-label\": \"Your message\",\n maxlength: \"500\",\n \"data-ckf-id\": \"comment\",\n onInput: (e) => (comment = (e.target as HTMLTextAreaElement).value),\n }) as HTMLTextAreaElement)\n : null;\n if (text) text.value = comment;\n body.push(\n label ? h(\"p\", { className: \"ckf-small\" }, `You said: ${label}`) : false,\n h(\"h2\", { className: \"ckf-h\", id: titleId }, o.headline),\n detail,\n text ?? false,\n h(\n \"div\",\n { className: \"ckf-btns\" },\n button(busy ? \"One moment…\" : offerButton(o), \"primary\", () => ctl.cancel.accept(comment), { disabled: busy, id: \"go\" }),\n button(\"No thanks, continue cancelling\", \"quiet\", ctl.cancel.decline, { disabled: busy }),\n ),\n );\n } else {\n const plain = v.steps === 1;\n const text = plain\n ? null\n : (h(\"textarea\", {\n className: \"ckf-text\",\n placeholder: \"Anything else we should know? (optional)\",\n \"aria-label\": \"Anything else we should know? (optional)\",\n maxlength: \"500\",\n \"data-ckf-id\": \"comment\",\n onInput: (e) => (comment = (e.target as HTMLTextAreaElement).value),\n }) as HTMLTextAreaElement);\n if (text) text.value = comment;\n body.push(\n h(\"h2\", { className: \"ckf-h\", id: titleId }, v.flow.confirm.title),\n v.flow.confirm.body ? h(\"p\", { className: \"ckf-p\" }, v.flow.confirm.body) : false,\n text ?? false,\n h(\n \"div\",\n { className: \"ckf-btns\" },\n button(busy ? \"One moment…\" : v.flow.confirm.button, \"danger\", () => ctl.cancel.confirm(comment), { disabled: busy, id: \"go\" }),\n button(\"Keep my plan\", \"secondary\", ctl.cancel.close, { disabled: busy }),\n ),\n );\n }\n return h(\n \"div\",\n { className: \"ckf-scrim\", onClick: (e) => e.target === e.currentTarget && !busy && ctl.cancel.close() },\n h(\n \"div\",\n { className: \"ckf-card\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": titleId },\n h(\"div\", { className: \"ckf-top\" }, dots(v.step, v.steps), closeButton(\"Close\", ctl.cancel.close)),\n ...body,\n v.error ? h(\"p\", { className: \"ckf-err\", role: \"alert\" }, v.error) : false,\n ),\n );\n };\n\n // ---------- Onboarding ----------\n\n const stepCard = (v: Extract<FlowView, { type: \"step\" }>) => {\n const s = v.step;\n const card = h(\n \"div\",\n { className: \"ckf-card\", role: \"dialog\", \"aria-modal\": s.kind === \"modal\" ? \"true\" : undefined, \"aria-labelledby\": \"ckf-title\" },\n h(\n \"div\",\n { className: \"ckf-top\" },\n v.total > 1 ? h(\"p\", { className: \"ckf-small\" }, `Step ${v.index + 1} of ${v.total}`) : h(\"span\"),\n closeButton(\"Close the tour\", ctl.onboarding.dismiss),\n ),\n h(\"h2\", { className: \"ckf-h\", id: \"ckf-title\" }, s.title),\n s.body ? h(\"p\", { className: \"ckf-p\" }, s.body) : false,\n h(\"div\", { className: \"ckf-row\" }, button(s.button || (v.last ? \"Done\" : \"Next\"), \"primary\", ctl.onboarding.next, { id: \"go\" })),\n );\n if (s.kind === \"modal\") return h(\"div\", { className: \"ckf-scrim\" }, card);\n const target = s.target ? document.querySelector<HTMLElement>(`[data-ck-target=\"${CSS.escape(s.target)}\"]`) : null;\n const float = h(\"div\", { className: \"ckf-float\" }, card);\n if (target) {\n const ring = h(\"div\", { className: \"ckf-ring\", \"aria-hidden\": \"true\" });\n placed = { float, ring, target };\n place(float, ring, target);\n return h(\"div\", {}, ring, float);\n }\n return float;\n };\n\n /** Puts a tour card next to the element it points at (below it, or above when there is no room). */\n const place = (float: HTMLElement, ring: HTMLElement, target: HTMLElement) => {\n const r = target.getBoundingClientRect();\n Object.assign(ring.style, { left: `${r.left - 4}px`, top: `${r.top - 4}px`, width: `${r.width + 8}px`, height: `${r.height + 8}px` });\n const w = Math.min(340, window.innerWidth - 24);\n const left = Math.max(12, Math.min(r.left, window.innerWidth - w - 12));\n const below = r.bottom + 12;\n Object.assign(float.style, { left: `${left}px`, right: \"auto\", width: `${w}px` });\n if (below + 200 < window.innerHeight) Object.assign(float.style, { top: `${below}px`, bottom: \"auto\" });\n else Object.assign(float.style, { top: \"auto\", bottom: `${Math.max(12, window.innerHeight - r.top + 12)}px` });\n };\n\n const checklist = (v: Extract<FlowView, { type: \"checklist\" }>) => {\n const done = v.items.filter((i) => i.done).length;\n return h(\n \"div\",\n { className: \"ckf-float\" },\n h(\n \"div\",\n { className: \"ckf-card\", role: \"region\", \"aria-labelledby\": \"ckf-title\" },\n h(\n \"div\",\n { className: \"ckf-top\" },\n h(\"h2\", { className: \"ckf-h\", id: \"ckf-title\" }, v.flow.name),\n closeButton(\"Hide the checklist\", ctl.onboarding.dismiss),\n ),\n h(\"p\", { className: \"ckf-small\" }, `${done} of ${v.items.length} done`),\n h(\n \"div\",\n { className: \"ckf-bar\", role: \"progressbar\", \"aria-valuemin\": \"0\", \"aria-valuemax\": String(v.items.length), \"aria-valuenow\": String(done) },\n h(\"i\", { style: `width:${(done / v.items.length) * 100}%` } as Attrs),\n ),\n h(\n \"div\",\n { className: \"ckf-opts\" },\n ...v.items.map(({ step, done: d }) => {\n const inner = [\n h(\"span\", { className: \"ckf-tick\", \"aria-hidden\": \"true\" }, d ? \"✓\" : \"\"),\n h(\"span\", {}, h(\"b\", {}, step.title), step.body ? h(\"span\", { className: \"ckf-small\" }, step.body) : false),\n ];\n // An item with an event ticks itself when the app records it; the others are ticked by hand.\n return d || step.event\n ? h(\"div\", { className: \"ckf-check\", \"data-done\": d, role: \"listitem\" }, ...inner)\n : h(\n \"button\",\n {\n type: \"button\",\n className: \"ckf-check\",\n \"aria-label\": `Mark “${step.title}” done`,\n \"data-ckf-id\": `i:${step.id}`,\n onClick: () => ctl.onboarding.check(step.id),\n },\n ...inner,\n );\n }),\n ),\n ),\n );\n };\n\n // ---------- Messages ----------\n\n const message = (v: Extract<FlowView, { type: \"message\" }>) => {\n const m = v.message;\n const action = m.button ? button(m.button, \"primary\", ctl.message.click, { id: \"go\" }) : false;\n if (m.style === \"banner\")\n return h(\n \"div\",\n { className: \"ckf-banner\", role: \"status\" },\n h(\"span\", {}, h(\"b\", {}, m.title), m.body ? h(\"span\", { className: \"ckf-small\" }, m.body) : false),\n action,\n closeButton(\"Dismiss\", ctl.message.dismiss),\n );\n return h(\n \"div\",\n { className: \"ckf-scrim\", onClick: (e) => e.target === e.currentTarget && ctl.message.dismiss() },\n h(\n \"div\",\n { className: \"ckf-card\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": \"ckf-title\" },\n h(\"div\", { className: \"ckf-top\" }, h(\"span\"), closeButton(\"Dismiss\", ctl.message.dismiss)),\n h(\"h2\", { className: \"ckf-h\", id: \"ckf-title\" }, m.title),\n m.body ? h(\"p\", { className: \"ckf-p\" }, m.body) : false,\n action ? h(\"div\", { className: \"ckf-row\" }, action) : false,\n ),\n );\n };\n\n // ---------- Drawing ----------\n\n const draw = () => {\n const v = ctl.view();\n const key = v ? screenKey(v) : \"\";\n const focused = (document.activeElement as HTMLElement | null)?.closest?.(\".ckf-root\") ? document.activeElement?.getAttribute(\"data-ckf-id\") : null;\n if (key && !lastKey && document.activeElement instanceof HTMLElement && !document.activeElement.closest(\".ckf-root\"))\n returnFocus = document.activeElement;\n if (key !== lastKey && !key.startsWith(\"c:\")) comment = \"\";\n if (!key) comment = \"\";\n placed = null;\n root.replaceChildren(\n ...(v ? [v.type === \"cancel\" ? cancelCard(v) : v.type === \"step\" ? stepCard(v) : v.type === \"checklist\" ? checklist(v) : message(v)] : []),\n );\n if (key !== lastKey) {\n // A new screen: focus its first control (a dialog's), so keyboard and screen reader users land in it.\n const modal = root.querySelector<HTMLElement>(\"[aria-modal=true]\");\n const first = modal?.querySelector<HTMLElement>(\"[role=radio], [data-ckf-id=go], textarea\") ?? null;\n first?.focus();\n if (!key && returnFocus) {\n returnFocus.focus();\n returnFocus = null;\n }\n } else if (focused) root.querySelector<HTMLElement>(`[data-ckf-id=\"${CSS.escape(focused)}\"]`)?.focus();\n lastKey = key;\n };\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n const v = ctl.view();\n if (v?.type === \"cancel\") ctl.cancel.close();\n else if (v?.type === \"message\") ctl.message.dismiss();\n else if (v?.type === \"step\" && v.step.kind === \"modal\") ctl.onboarding.dismiss();\n };\n // A tour card follows its element when the page scrolls or resizes.\n const follow = () => {\n if (placed) place(placed.float, placed.ring, placed.target);\n };\n document.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"resize\", follow);\n window.addEventListener(\"scroll\", follow, { passive: true, capture: true });\n const off = ctl.subscribe(draw);\n draw();\n return () => {\n off();\n document.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"resize\", follow);\n window.removeEventListener(\"scroll\", follow, { capture: true });\n root.remove();\n };\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What the flows controller (flows.ts) needs in a browser, shared by the Next.js provider and the script tag: the
3
+ * person's messages and onboarding progress from `GET /api/sdk/inapp`, progress kept in localStorage only while the
4
+ * consent is "cookies", and links opened in the same tab. Plain functions; only called in the browser.
5
+ */
6
+ import type { FlowsEnv } from "./flows.js";
7
+ export interface WebFlowsOptions {
8
+ host: string;
9
+ apiKey: string;
10
+ track: FlowsEnv["track"];
11
+ /** The identify() id while tracking is allowed; undefined otherwise (no messages, no stored progress then). */
12
+ personId: () => string | undefined;
13
+ /** True while the consent is "cookies": progress may be kept in localStorage. */
14
+ canStore: () => boolean;
15
+ auto?: boolean;
16
+ }
17
+ /** Removes what the flows kept in localStorage (consent withdrawn). */
18
+ export declare const forgetFlowsStorage: () => void;
19
+ export declare const webFlowsEnv: (o: WebFlowsOptions) => FlowsEnv;
@@ -0,0 +1,42 @@
1
+ // src/flows-web.ts
2
+ var local = {
3
+ get: (key) => {
4
+ try {
5
+ return window.localStorage.getItem(key);
6
+ } catch {
7
+ return null;
8
+ }
9
+ },
10
+ set: (key, value) => {
11
+ try {
12
+ window.localStorage.setItem(key, value);
13
+ } catch {
14
+ }
15
+ }
16
+ };
17
+ var forgetFlowsStorage = () => {
18
+ try {
19
+ for (let i = window.localStorage.length - 1; i >= 0; i--) {
20
+ const key = window.localStorage.key(i);
21
+ if (key?.startsWith("mrrocket:onboarding")) window.localStorage.removeItem(key);
22
+ }
23
+ } catch {
24
+ }
25
+ };
26
+ var webFlowsEnv = (o) => ({
27
+ track: o.track,
28
+ personId: o.personId,
29
+ auto: o.auto,
30
+ storage: () => o.canStore() ? local : null,
31
+ openUrl: (url) => window.location.assign(url),
32
+ fetchInApp: async (id) => {
33
+ if (!o.apiKey) return null;
34
+ const r = await fetch(`${o.host}/api/sdk/inapp?key=${encodeURIComponent(o.apiKey)}&distinctId=${encodeURIComponent(id)}`);
35
+ return r.ok ? await r.json() : null;
36
+ }
37
+ });
38
+ export {
39
+ forgetFlowsStorage,
40
+ webFlowsEnv
41
+ };
42
+ //# sourceMappingURL=flows-web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flows-web.ts"],"sourcesContent":["/**\n * What the flows controller (flows.ts) needs in a browser, shared by the Next.js provider and the script tag: the\n * person's messages and onboarding progress from `GET /api/sdk/inapp`, progress kept in localStorage only while the\n * consent is \"cookies\", and links opened in the same tab. Plain functions; only called in the browser.\n */\nimport type { FlowsEnv, InAppState } from \"./flows\";\n\nexport interface WebFlowsOptions {\n host: string;\n apiKey: string;\n track: FlowsEnv[\"track\"];\n /** The identify() id while tracking is allowed; undefined otherwise (no messages, no stored progress then). */\n personId: () => string | undefined;\n /** True while the consent is \"cookies\": progress may be kept in localStorage. */\n canStore: () => boolean;\n auto?: boolean;\n}\n\nconst local = {\n get: (key: string) => {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n },\n set: (key: string, value: string) => {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Private mode or full: progress stays in memory.\n }\n },\n};\n\n/** Removes what the flows kept in localStorage (consent withdrawn). */\nexport const forgetFlowsStorage = () => {\n try {\n for (let i = window.localStorage.length - 1; i >= 0; i--) {\n const key = window.localStorage.key(i);\n if (key?.startsWith(\"mrrocket:onboarding\")) window.localStorage.removeItem(key);\n }\n } catch {\n // No storage: nothing to delete.\n }\n};\n\nexport const webFlowsEnv = (o: WebFlowsOptions): FlowsEnv => ({\n track: o.track,\n personId: o.personId,\n auto: o.auto,\n storage: () => (o.canStore() ? local : null),\n openUrl: (url) => window.location.assign(url),\n fetchInApp: async (id) => {\n if (!o.apiKey) return null;\n const r = await fetch(`${o.host}/api/sdk/inapp?key=${encodeURIComponent(o.apiKey)}&distinctId=${encodeURIComponent(id)}`);\n return r.ok ? ((await r.json()) as InAppState) : null;\n },\n});\n"],"mappings":";AAkBA,IAAM,QAAQ;AAAA,EACV,KAAK,CAAC,QAAgB;AAClB,QAAI;AACA,aAAO,OAAO,aAAa,QAAQ,GAAG;AAAA,IAC1C,QAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA,EACA,KAAK,CAAC,KAAa,UAAkB;AACjC,QAAI;AACA,aAAO,aAAa,QAAQ,KAAK,KAAK;AAAA,IAC1C,QAAQ;AAAA,IAER;AAAA,EACJ;AACJ;AAGO,IAAM,qBAAqB,MAAM;AACpC,MAAI;AACA,aAAS,IAAI,OAAO,aAAa,SAAS,GAAG,KAAK,GAAG,KAAK;AACtD,YAAM,MAAM,OAAO,aAAa,IAAI,CAAC;AACrC,UAAI,KAAK,WAAW,qBAAqB,EAAG,QAAO,aAAa,WAAW,GAAG;AAAA,IAClF;AAAA,EACJ,QAAQ;AAAA,EAER;AACJ;AAEO,IAAM,cAAc,CAAC,OAAkC;AAAA,EAC1D,OAAO,EAAE;AAAA,EACT,UAAU,EAAE;AAAA,EACZ,MAAM,EAAE;AAAA,EACR,SAAS,MAAO,EAAE,SAAS,IAAI,QAAQ;AAAA,EACvC,SAAS,CAAC,QAAQ,OAAO,SAAS,OAAO,GAAG;AAAA,EAC5C,YAAY,OAAO,OAAO;AACtB,QAAI,CAAC,EAAE,OAAQ,QAAO;AACtB,UAAM,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,sBAAsB,mBAAmB,EAAE,MAAM,CAAC,eAAe,mBAAmB,EAAE,CAAC,EAAE;AACxH,WAAO,EAAE,KAAO,MAAM,EAAE,KAAK,IAAoB;AAAA,EACrD;AACJ;","names":[]}