@mrrocket/sdk 0.4.0 → 0.9.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.
- package/CHANGELOG.md +43 -0
- package/README.md +137 -8
- package/dist/api-client.d.ts +213 -0
- package/dist/api-client.js +67 -0
- package/dist/api-client.js.map +1 -0
- package/dist/api.d.ts +9 -109
- package/dist/api.js +7 -62
- package/dist/api.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +64 -0
- package/dist/browser/mrrocket-messenger.js +313 -0
- package/dist/browser/mrrocket-push.js +1 -0
- package/dist/browser/mrrocket-replay.js +3 -3
- package/dist/browser/mrrocket-sw.js +1 -0
- package/dist/browser/mrrocket.js +2 -1
- package/dist/browser.d.ts +3 -2
- package/dist/browser.js +6 -1
- package/dist/browser.js.map +1 -1
- package/dist/capture.d.ts +1 -13
- package/dist/capture.js +1 -22
- package/dist/capture.js.map +1 -1
- package/dist/client.d.ts +36 -1
- package/dist/client.js +161 -6
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +10 -0
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +64 -3
- package/dist/core.js +54 -2
- package/dist/core.js.map +1 -1
- package/dist/feedback.d.ts +19 -4
- package/dist/feedback.js +655 -190
- package/dist/feedback.js.map +1 -1
- package/dist/flows-ui.d.ts +23 -0
- package/dist/flows-ui.js +384 -0
- package/dist/flows-ui.js.map +1 -0
- package/dist/flows-web.d.ts +19 -0
- package/dist/flows-web.js +42 -0
- package/dist/flows-web.js.map +1 -0
- package/dist/flows.d.ts +222 -0
- package/dist/flows.js +409 -0
- package/dist/flows.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +47 -0
- package/dist/messenger-ui.js +583 -0
- package/dist/messenger-ui.js.map +1 -0
- package/dist/messenger.d.ts +37 -0
- package/dist/messenger.js +69 -0
- package/dist/messenger.js.map +1 -0
- package/dist/proxy.d.ts +34 -1
- package/dist/proxy.js +14 -1
- package/dist/proxy.js.map +1 -1
- package/dist/push-web.d.ts +43 -0
- package/dist/push-web.js +81 -0
- package/dist/push-web.js.map +1 -0
- package/dist/rage.d.ts +14 -0
- package/dist/rage.js +27 -0
- package/dist/rage.js.map +1 -0
- package/dist/react-native/index.d.cts +640 -3
- package/dist/react-native/index.d.ts +640 -3
- package/dist/react-native/index.js +2015 -34
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +6 -0
- package/dist/styles-css.js +327 -0
- package/dist/styles-css.js.map +1 -0
- package/dist/styles.d.ts +1 -2
- package/dist/styles.js +3 -134
- package/dist/styles.js.map +1 -1
- package/dist/ui.d.ts +3 -0
- package/dist/ui.js +12 -0
- package/dist/ui.js.map +1 -1
- package/package.json +20 -4
package/dist/flows-ui.js
ADDED
|
@@ -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":[]}
|