@qaiddev/quests-embed 1.4.0 → 1.5.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
@@ -348,8 +348,52 @@ const quests = new QaidQuests(config: QuestsConfig);
348
348
  | Method | Description |
349
349
  |--------|-------------|
350
350
  | `getAnswers()` | Read-only snapshot of the answers collected so far, keyed by question id |
351
+ | `getCurrentQuestionId()` | Id of the question on screen, or `null` if the embed isn't on one (loading, finished, or unmounted) |
352
+ | `goToStep(questionId)` | Jump to a visible question. Returns `false` if it's unknown or hidden by an unmet `visibleIf`. Latched if the embed is still initializing |
353
+ | `update(questionnaire)` | Swap in a new questionnaire without re-mounting. Returns whether it was applied — see below |
351
354
  | `destroy()` | Remove all DOM elements, event listeners, and injected styles. Safe to call multiple times |
352
355
 
356
+ #### `update(questionnaire)`
357
+
358
+ For hosts that render a questionnaire while it is being authored — a live
359
+ preview in an editor, say. Rebuilding the embed on every edit re-mounts the
360
+ shadow root, repaints the loading state, and re-runs both the theme fetch and
361
+ the create call; because that path awaits the network it always paints a blank
362
+ frame first, which is what makes a live preview flicker as the author types.
363
+
364
+ `update()` re-renders the header and the current step, synchronously. The
365
+ shadow root, the resolved theme, the response id and the answers so far all
366
+ survive, and focus is left wherever the host put it — it does not pull focus
367
+ into the form the way stepping through it does.
368
+
369
+ Answers are kept for questions that still exist and dropped for ones that
370
+ don't, so `getAnswers()` never reports an id the questionnaire has no question
371
+ for. The reader's place is kept the same way: if the question on screen is
372
+ still present and visible, the embed stays on it; otherwise the step index is
373
+ clamped into range.
374
+
375
+ It returns `false`, leaving the embed exactly as it was, when:
376
+
377
+ - the questionnaire has no questions,
378
+ - every question is gated off behind an unmet `visibleIf`, so there would be
379
+ nothing to render,
380
+ - or the reader has already completed the form — resuming a submitted response
381
+ isn't something the embed can decide for you, so rebuild if you want the new
382
+ form there.
383
+
384
+ Called before the embed has finished initializing, the update is latched and
385
+ applied as soon as it is ready (and reported as applied), the same way
386
+ `goToStep` is.
387
+
388
+ ```js
389
+ const quests = new QaidQuests({ endpoint, questionnaire, container: "#preview" });
390
+
391
+ // later, as the author edits — no teardown, no refetch, no flicker
392
+ if (!quests.update(nextQuestionnaire)) {
393
+ // refused: rebuild if you need the new questionnaire on screen
394
+ }
395
+ ```
396
+
353
397
  ### Server Protocol
354
398
 
355
399
  The embed talks to your endpoint in three steps:
package/dist/embed.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Answers, QuestsConfig } from "./types";
1
+ import type { Answers, Questionnaire, QuestsConfig } from "./types";
2
2
  export declare class QaidQuests {
3
3
  private config;
4
4
  private questionnaire;
@@ -8,6 +8,8 @@ export declare class QaidQuests {
8
8
  private stepIndex;
9
9
  private visibleQuestions;
10
10
  private pendingGoToStep;
11
+ private pendingUpdate;
12
+ private suppressStepFocus;
11
13
  private hasRenderedStep;
12
14
  private answers;
13
15
  private responseId;
@@ -101,6 +103,33 @@ export declare class QaidQuests {
101
103
  * user's place after a forced re-mount.
102
104
  */
103
105
  getCurrentQuestionId(): string | null;
106
+ /**
107
+ * Swap in a new questionnaire without tearing the embed down.
108
+ *
109
+ * Built for editor previews, where the alternative — `destroy()` plus a
110
+ * fresh construction on every edit — re-mounts the shadow root, repaints
111
+ * the loading state, and re-runs both the theme fetch and the create
112
+ * call. Because that path awaits the network it always paints a blank
113
+ * frame first, which is what makes a live preview strobe while the author
114
+ * types. This re-renders the header and the current step and nothing
115
+ * else, synchronously: the shadow root, the resolved theme, the response
116
+ * id and the answers so far all survive.
117
+ *
118
+ * Answers are kept for questions that still exist and dropped for ones
119
+ * that don't, so `getAnswers()` never reports an id the questionnaire has
120
+ * no question for. The reader's place is kept the same way: if the
121
+ * question on screen is still present and visible, the embed stays on it,
122
+ * otherwise the step index is clamped into range.
123
+ *
124
+ * Returns whether the update was applied. It is refused, leaving the
125
+ * embed exactly as it was, when the questionnaire has no questions, or
126
+ * once the reader has completed the form — resuming a submitted response
127
+ * is not something this can decide on the host's behalf, so a host that
128
+ * wants the new form there should rebuild. Called before the embed has
129
+ * finished initializing, the update is latched and applied as soon as it
130
+ * is ready (and reported as applied), the same way `goToStep` is.
131
+ */
132
+ update(questionnaire: Questionnaire): boolean;
104
133
  /**
105
134
  * Jump to the visible question with the given id. Returns true if
106
135
  * the question is currently visible (and the embed navigated to