@sightspool/sdk 0.2.1 → 0.3.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/dist/index.cjs CHANGED
@@ -199,6 +199,125 @@ input { font: inherit; font-size: 13px; width: 100%; box-sizing: border-box; pad
199
199
  }
200
200
  });
201
201
 
202
+ // src/probe.ts
203
+ var probe_exports = {};
204
+ __export(probe_exports, {
205
+ showProbe: () => showProbe
206
+ });
207
+ function findSlot(slot) {
208
+ try {
209
+ return document.querySelector(`[data-sightspool-slot="${slot}"]`);
210
+ } catch (e) {
211
+ return null;
212
+ }
213
+ }
214
+ function showProbe(config) {
215
+ return new Promise((resolve) => {
216
+ if (typeof document === "undefined") {
217
+ resolve({ dismissed: true, mounted: false });
218
+ return;
219
+ }
220
+ const anchor = config.slot ? findSlot(config.slot) : null;
221
+ if (config.slot && !anchor) {
222
+ resolve({ mounted: false });
223
+ return;
224
+ }
225
+ let settled = false;
226
+ const inline = !!anchor;
227
+ const host = document.createElement(inline ? "span" : "div");
228
+ const shadow = host.attachShadow({ mode: "open" });
229
+ function teardown() {
230
+ try {
231
+ host.remove();
232
+ } catch (e) {
233
+ }
234
+ }
235
+ function finish(result, disclose) {
236
+ if (settled) return;
237
+ settled = true;
238
+ resolve(result);
239
+ if (disclose) {
240
+ disclosure();
241
+ setTimeout(teardown, DISCLOSURE_MS);
242
+ } else {
243
+ teardown();
244
+ }
245
+ }
246
+ const style = document.createElement("style");
247
+ style.textContent = inline ? INLINE_STYLE : FLOATING_STYLE;
248
+ shadow.appendChild(style);
249
+ const wrap = document.createElement(inline ? "span" : "div");
250
+ if (!inline) wrap.className = "wrap";
251
+ shadow.appendChild(wrap);
252
+ function container() {
253
+ wrap.innerHTML = "";
254
+ if (inline) return wrap;
255
+ const c = document.createElement("div");
256
+ c.className = "card";
257
+ wrap.appendChild(c);
258
+ return c;
259
+ }
260
+ function probe() {
261
+ const c = container();
262
+ if (!inline) {
263
+ const x = document.createElement("button");
264
+ x.className = "x";
265
+ x.textContent = "\xD7";
266
+ x.setAttribute("aria-label", "Dismiss");
267
+ x.onclick = () => finish({ dismissed: true, mounted: true }, false);
268
+ c.appendChild(x);
269
+ }
270
+ const b = document.createElement("button");
271
+ b.className = "probe";
272
+ b.textContent = config.label;
273
+ b.onclick = () => finish({ voted: true, mounted: true }, true);
274
+ c.appendChild(b);
275
+ }
276
+ function disclosure() {
277
+ const c = container();
278
+ const p = document.createElement("p");
279
+ p.className = "done";
280
+ p.textContent = config.disclosure;
281
+ c.appendChild(p);
282
+ }
283
+ try {
284
+ (anchor != null ? anchor : document.body).appendChild(host);
285
+ probe();
286
+ } catch (e) {
287
+ finish({ dismissed: true, mounted: false }, false);
288
+ }
289
+ });
290
+ }
291
+ var FLOATING_STYLE, INLINE_STYLE, DISCLOSURE_MS;
292
+ var init_probe = __esm({
293
+ "src/probe.ts"() {
294
+ FLOATING_STYLE = `
295
+ :host { all: initial; }
296
+ .wrap { position: fixed; bottom: 20px; right: 20px; z-index: 2147483000;
297
+ width: 340px; max-width: calc(100vw - 32px); font-family: system-ui, -apple-system, sans-serif; }
298
+ .card { position: relative; background: #fff; color: #18181b; border: 1px solid #e4e4e7;
299
+ border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.12); padding: 16px; }
300
+ .probe { font: inherit; font-size: 13px; cursor: pointer; display: block; width: 100%;
301
+ text-align: left; border: 1px dashed #a1a1aa; border-radius: 8px; padding: 10px 12px;
302
+ background: #fafafa; color: #3f3f46; }
303
+ .probe:hover { border-color: #18181b; color: #18181b; }
304
+ .x { position: absolute; top: 8px; right: 10px; border: none; background: none; font-size: 16px;
305
+ color: #a1a1aa; padding: 2px 6px; cursor: pointer; }
306
+ .done { font-size: 13px; color: #18181b; margin: 0; line-height: 1.4; }
307
+ `;
308
+ INLINE_STYLE = `
309
+ :host { all: initial; display: inline-block; max-width: 100%; }
310
+ .probe { font: inherit; font-family: system-ui, -apple-system, sans-serif; font-size: 13px;
311
+ cursor: pointer; display: inline-block; text-align: left; border: 1px dashed #a1a1aa;
312
+ border-radius: 8px; padding: 8px 12px; background: transparent; color: #52525b; }
313
+ .probe:hover { border-color: #18181b; color: #18181b; }
314
+ .done { font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: #52525b;
315
+ margin: 0; line-height: 1.4; border: 1px dashed #d4d4d8; border-radius: 8px; padding: 8px 12px; }
316
+ `;
317
+ DISCLOSURE_MS = 3200;
318
+ }
319
+ });
320
+
202
321
  // src/survey.ts
203
322
  var survey_exports = {};
204
323
  __export(survey_exports, {
@@ -725,7 +844,7 @@ function canPrompt(state, now, caps = DEFAULT_CAPS) {
725
844
  }
726
845
 
727
846
  // src/intervention.ts
728
- var RENDERABLE = /* @__PURE__ */ new Set(["survey"]);
847
+ var RENDERABLE = /* @__PURE__ */ new Set(["survey", "demand_probe"]);
729
848
  function str(v, max) {
730
849
  if (typeof v !== "string") return void 0;
731
850
  const t = v.trim();
@@ -748,6 +867,14 @@ function normalizeServed(raw) {
748
867
  const type = str(r.type, 40);
749
868
  if (!id || !type || !RENDERABLE.has(type)) return null;
750
869
  const cfg = r.config && typeof r.config === "object" ? r.config : {};
870
+ if (type === "demand_probe") {
871
+ const label = str(cfg.label, 200);
872
+ const disclosure = str(cfg.disclosure, 300);
873
+ if (!label || !disclosure) return null;
874
+ const rawSlot = str(cfg.slot, 64);
875
+ const slot = rawSlot && /^[a-z0-9][a-z0-9_-]*$/i.test(rawSlot) ? rawSlot : void 0;
876
+ return { id, type, config: { label, disclosure, ...slot ? { slot } : {} } };
877
+ }
751
878
  const question = str(cfg.question, 500);
752
879
  if (!question) return null;
753
880
  const options = Array.isArray(cfg.options) ? cfg.options.map((o) => str(o, 200)).filter((o) => !!o).slice(0, 6) : void 0;
@@ -756,7 +883,7 @@ function normalizeServed(raw) {
756
883
  ...options && options.length ? { options } : {},
757
884
  allow_text: cfg.allow_text === true
758
885
  };
759
- return { id, type, config };
886
+ return { id, type: "survey", config };
760
887
  }
761
888
  function pickRespondentKey(userId, persisted, sessionRef) {
762
889
  const uid = str(userId, 200);
@@ -929,20 +1056,39 @@ async function maybeServeIntervention(c) {
929
1056
  debug: c.config.debug
930
1057
  });
931
1058
  if (!served || !c.running) return;
1059
+ const prevShown = c.interventionsShown;
1060
+ const prevPromptAt = c.fatigue.lastPromptAt;
932
1061
  c.surveyOnScreen = true;
933
1062
  c.interventionsShown += 1;
934
1063
  c.fatigue.lastPromptAt = Date.now();
935
1064
  try {
936
- const { showSurvey: showSurvey2 } = await Promise.resolve().then(() => (init_survey(), survey_exports));
937
- const result = await showSurvey2(served.config);
938
- if (!result.dismissed && (result.choice || result.text)) {
939
- submitResponse({
940
- endpoint: c.config.endpoint,
941
- key: c.config.key,
942
- interventionId: served.id,
943
- response: { choice: result.choice, text: result.text },
944
- respondentKey
945
- });
1065
+ if (served.type === "demand_probe") {
1066
+ const { showProbe: showProbe2 } = await Promise.resolve().then(() => (init_probe(), probe_exports));
1067
+ const result = await showProbe2(served.config);
1068
+ if (!result.mounted) {
1069
+ c.interventionsShown = prevShown;
1070
+ c.fatigue.lastPromptAt = prevPromptAt;
1071
+ } else if (result.voted) {
1072
+ submitResponse({
1073
+ endpoint: c.config.endpoint,
1074
+ key: c.config.key,
1075
+ interventionId: served.id,
1076
+ response: { choice: "vote" },
1077
+ respondentKey
1078
+ });
1079
+ }
1080
+ } else {
1081
+ const { showSurvey: showSurvey2 } = await Promise.resolve().then(() => (init_survey(), survey_exports));
1082
+ const result = await showSurvey2(served.config);
1083
+ if (!result.dismissed && (result.choice || result.text)) {
1084
+ submitResponse({
1085
+ endpoint: c.config.endpoint,
1086
+ key: c.config.key,
1087
+ interventionId: served.id,
1088
+ response: { choice: result.choice, text: result.text },
1089
+ respondentKey
1090
+ });
1091
+ }
946
1092
  }
947
1093
  } catch (e) {
948
1094
  } finally {