@qaiddev/quests-embed 1.2.0 → 1.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/README.md CHANGED
@@ -246,6 +246,27 @@ We offer a Free Plan that hosts both the endpoint and a dashboard for managing y
246
246
  | `saveDebounceMs` | `number` | `500` | Debounce in ms for autosave on text/currency/range |
247
247
  | `autoFocus` | `boolean` | `true` | Auto-focus the input on each step. Set `false` in preview/embedded contexts that shouldn't steal focus |
248
248
 
249
+ ### Host integration
250
+
251
+ For programmatic embedding — e.g. launching a quest from another widget — these hooks let the host pass correlation data in and react to the quest's lifecycle. They're only useful when you construct `QaidQuests` yourself (not via the auto-init `<script>` tag).
252
+
253
+ | Option | Type | Default | Description |
254
+ |--------|------|---------|-------------|
255
+ | `metadata` | `Record<string, unknown>` | — | Extra key/value pairs sent verbatim in the create-response `POST` body. The server decides which keys it persists; unknown keys are ignored. Used, for example, by `@qaiddev/thumbs-embed` to pass `{ feedbackId }` so the QAid backend joins the quest answers to the feedback record. |
256
+ | `onComplete` | `(answers) => void` | — | Called once when the quest is completed and submitted (just after the thank-you screen renders), with a copy of the collected answers. Fires in both modal and inline modes. |
257
+ | `onClose` | `() => void` | — | Called once when the embed is torn down — visitor close, host `destroy()`, or teardown after completion. Lets a host that launched the quest drop its reference. |
258
+
259
+ ```typescript
260
+ const quest = new QaidQuests({
261
+ endpoint: 'https://qaid.dev/api/quests/responses',
262
+ configUrl: 'https://qaid.dev/api/quests/<questId>/definition',
263
+ apiKey: 'YOUR_API_KEY',
264
+ metadata: { feedbackId: 'clx…' }, // correlate this response server-side
265
+ onComplete: (answers) => console.log('done', answers),
266
+ onClose: () => { /* drop your reference */ },
267
+ });
268
+ ```
269
+
249
270
  ### Appearance
250
271
 
251
272
  | Option | Type | Default | Description |
package/dist/embed.d.ts CHANGED
@@ -40,6 +40,10 @@ export declare class QaidQuests {
40
40
  private themeUrl;
41
41
  private themeDocument;
42
42
  private hostCss;
43
+ private metadata;
44
+ private onCompleteCb;
45
+ private onCloseCb;
46
+ private closed;
43
47
  constructor(config: QuestsConfig);
44
48
  private init;
45
49
  private loadTheme;
@@ -487,6 +487,13 @@ class he {
487
487
  themeUrl;
488
488
  themeDocument;
489
489
  hostCss;
490
+ // Host integration hooks (see QuestsConfig). Kept off the resolved
491
+ // value-config since they're behaviour, not render tokens.
492
+ metadata;
493
+ onCompleteCb;
494
+ onCloseCb;
495
+ // Guards onClose to fire exactly once even if destroy() runs twice.
496
+ closed = !1;
490
497
  constructor(e) {
491
498
  this.config = {
492
499
  endpoint: e.endpoint,
@@ -531,7 +538,7 @@ class he {
531
538
  backdropOpacity: e.backdropOpacity,
532
539
  fontFamily: e.fontFamily,
533
540
  fontSize: e.fontSize
534
- }, this.themeUrl = e.themeUrl, this.themeDocument = e.themeDocument, this.hostCss = e.css ?? "", this.inlineQuestionnaire = e.questionnaire, this.configUrl = e.configUrl, this.visitorId = le(), this.boundKeyDown = this.handleKeyDown.bind(this), this.init();
541
+ }, this.themeUrl = e.themeUrl, this.themeDocument = e.themeDocument, this.hostCss = e.css ?? "", this.metadata = e.metadata, this.onCompleteCb = e.onComplete, this.onCloseCb = e.onClose, this.inlineQuestionnaire = e.questionnaire, this.configUrl = e.configUrl, this.visitorId = le(), this.boundKeyDown = this.handleKeyDown.bind(this), this.init();
535
542
  }
536
543
  async init() {
537
544
  this.cssVars = P(this.hostInlineVars), this.mountShell(), this.renderLoading();
@@ -850,7 +857,8 @@ ${r}
850
857
  questId: this.questionnaire?.id,
851
858
  pageUrl: window.location.href,
852
859
  visitorId: this.visitorId,
853
- userAgent: navigator.userAgent
860
+ userAgent: navigator.userAgent,
861
+ metadata: this.metadata
854
862
  })
855
863
  });
856
864
  if (e.ok) {
@@ -874,7 +882,12 @@ ${r}
874
882
  } catch (e) {
875
883
  console.error("[quests-embed] failed to submit:", e);
876
884
  }
877
- this.state = "DONE", this.renderDone();
885
+ if (this.state = "DONE", this.renderDone(), this.onCompleteCb)
886
+ try {
887
+ this.onCompleteCb({ ...this.answers });
888
+ } catch (e) {
889
+ console.error("[quests-embed] onComplete handler threw:", e);
890
+ }
878
891
  }
879
892
  jsonHeaders() {
880
893
  const e = {
@@ -906,7 +919,12 @@ ${r}
906
919
  }
907
920
  /** Destroy the embed and clean up all resources */
908
921
  destroy() {
909
- document.removeEventListener("keydown", this.boundKeyDown), this.pendingSaveTimer && (clearTimeout(this.pendingSaveTimer), this.pendingSaveTimer = null), this.focusTrap && (this.focusTrap.release(), this.focusTrap = null), this.inertRestore && (this.inertRestore(), this.inertRestore = null), !this.isUserContainer && this.savedOpener && re(this.savedOpener), this.savedOpener = null, this.shadowHost && (this.shadowHost.remove(), this.shadowHost = null, this.shadowRoot = null), this.rootEl = null, this.cardEl = null, this.bodyEl = null, this.footerEl = null, this.titleEl = null, this.stepCounterEl = null, this.progressFillEl = null, this.savingEl = null, this.backdropEl = null, this.currentInput = null;
922
+ if (document.removeEventListener("keydown", this.boundKeyDown), this.pendingSaveTimer && (clearTimeout(this.pendingSaveTimer), this.pendingSaveTimer = null), this.focusTrap && (this.focusTrap.release(), this.focusTrap = null), this.inertRestore && (this.inertRestore(), this.inertRestore = null), !this.isUserContainer && this.savedOpener && re(this.savedOpener), this.savedOpener = null, this.shadowHost && (this.shadowHost.remove(), this.shadowHost = null, this.shadowRoot = null), this.rootEl = null, this.cardEl = null, this.bodyEl = null, this.footerEl = null, this.titleEl = null, this.stepCounterEl = null, this.progressFillEl = null, this.savingEl = null, this.backdropEl = null, this.currentInput = null, !this.closed && (this.closed = !0, this.onCloseCb))
923
+ try {
924
+ this.onCloseCb();
925
+ } catch (e) {
926
+ console.error("[quests-embed] onClose handler threw:", e);
927
+ }
910
928
  }
911
929
  /** Read-only snapshot of current answers */
912
930
  getAnswers() {