@qaiddev/quests-embed 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 QAid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # @qaiddev/quests-embed
2
+
3
+ A zero-dependency, lightweight questionnaire embed that renders step-by-step forms from a JSON definition. Drop it inline on a page or open it as a modal — answers are autosaved as the user types and submitted to your API endpoint or the [QAid.dev hosted dashboard](https://qaid.dev).
4
+
5
+ ## Install
6
+
7
+ ### npm
8
+
9
+ ```bash
10
+ npm install @qaiddev/quests-embed
11
+ ```
12
+
13
+ ```typescript
14
+ import { QaidQuests } from '@qaiddev/quests-embed';
15
+
16
+ new QaidQuests({
17
+ endpoint: 'https://qaid.dev/api/responses',
18
+ apiKey: 'YOUR_API_KEY',
19
+ configUrl: '/forms/intake.json',
20
+ container: '#form',
21
+ });
22
+ ```
23
+
24
+ ### CDN / Script Tag
25
+
26
+ ```html
27
+ <div id="form"></div>
28
+
29
+ <script
30
+ src="https://unpkg.com/@qaiddev/quests-embed/dist/qaid-quests.umd.cjs"
31
+ data-endpoint="https://qaid.dev/api/responses"
32
+ data-api-key="YOUR_API_KEY"
33
+ data-config-url="/forms/intake.json"
34
+ data-container="#form"
35
+ ></script>
36
+ ```
37
+
38
+ The embed auto-initializes when it detects a `data-endpoint` attribute on its script tag and either `data-config-url` or an inline JSON config.
39
+
40
+ ### JSON Config (Script Tag)
41
+
42
+ For complex configurations or inline questionnaires, use a separate JSON config element:
43
+
44
+ ```html
45
+ <script type="application/json" data-quests-config>
46
+ {
47
+ "endpoint": "https://qaid.dev/api/responses",
48
+ "apiKey": "YOUR_API_KEY",
49
+ "container": "#form",
50
+ "questionnaire": {
51
+ "title": "Tell us about your project",
52
+ "questions": [
53
+ { "id": "name", "type": "text", "label": "Your name", "required": true },
54
+ { "id": "budget", "type": "currency", "label": "Project budget", "currency": "USD" },
55
+ {
56
+ "id": "stack",
57
+ "type": "multiple-choice",
58
+ "label": "Which stack are you using?",
59
+ "options": [
60
+ { "value": "react", "label": "React" },
61
+ { "value": "vue", "label": "Vue" },
62
+ { "value": "svelte", "label": "Svelte" }
63
+ ]
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ </script>
69
+ <script src="https://unpkg.com/@qaiddev/quests-embed/dist/qaid-quests.umd.cjs"></script>
70
+ ```
71
+
72
+ ## How It Works
73
+
74
+ 1. The embed loads the questionnaire — either inline via `questionnaire`, or fetched from `configUrl`
75
+ 2. A response record is created on your server (`POST` to `endpoint`)
76
+ 3. Questions render one at a time with a progress bar and Back/Next navigation
77
+ 4. Answers autosave as the user moves between steps (`PATCH` per question). Text and currency fields debounce by `saveDebounceMs` (default 500ms)
78
+ 5. On the final step, all answers are submitted in a single batch (`POST` to `{endpoint}/{id}/submit`)
79
+ 6. A "thank you" screen appears when finished
80
+
81
+ If `container` is set, the form renders inline inside that element. If not, it opens as a centered modal with a backdrop.
82
+
83
+ ## Questionnaire Definition
84
+
85
+ A `Questionnaire` is the JSON document that describes the form. It can be loaded inline via the `questionnaire` config option, or fetched from a URL via `configUrl`.
86
+
87
+ ```typescript
88
+ interface Questionnaire {
89
+ id?: string; // Stable identifier sent with createResponse
90
+ title?: string; // Shown at the top of the form
91
+ description?: string; // Shown under the title
92
+ thankYouTitle?: string; // Default "Thank you!"
93
+ thankYouMessage?: string; // Optional subtitle on the done screen
94
+ submitLabel?: string; // Default "Submit"
95
+ nextLabel?: string; // Default "Next"
96
+ backLabel?: string; // Default "Back"
97
+ questions: Question[];
98
+ }
99
+ ```
100
+
101
+ ### Question Types
102
+
103
+ All questions share these base fields:
104
+
105
+ | Field | Type | Description |
106
+ |-------|------|-------------|
107
+ | `id` | `string` | **(required)** Stable answer key |
108
+ | `label` | `string` | **(required)** Question label |
109
+ | `description` | `string` | Optional helper text |
110
+ | `required` | `boolean` | Whether an answer is required to advance |
111
+
112
+ Plus a `type` discriminator that selects one of:
113
+
114
+ #### Text — `type: "text"`
115
+
116
+ | Field | Type | Description |
117
+ |-------|------|-------------|
118
+ | `placeholder` | `string` | Input placeholder |
119
+ | `multiline` | `boolean` | Use a textarea instead of a single-line input |
120
+ | `maxLength` | `number` | Max length |
121
+ | `minLength` | `number` | Min length (only enforced when `required`) |
122
+ | `inputType` | `"text" \| "email" \| "tel" \| "url"` | Native input type for short fields. Default `"text"` |
123
+
124
+ #### Currency — `type: "currency"`
125
+
126
+ | Field | Type | Description |
127
+ |-------|------|-------------|
128
+ | `currency` | `string` | ISO currency code, e.g. `"USD"`. Default `"USD"` |
129
+ | `min` | `number` | Minimum value |
130
+ | `max` | `number` | Maximum value |
131
+ | `locale` | `string` | Locale for number formatting. Default browser locale |
132
+ | `placeholder` | `string` | Input placeholder |
133
+
134
+ #### Range — `type: "range"`
135
+
136
+ | Field | Type | Description |
137
+ |-------|------|-------------|
138
+ | `min` | `number` | **(required)** Minimum value |
139
+ | `max` | `number` | **(required)** Maximum value |
140
+ | `step` | `number` | Step size. Default `1` |
141
+ | `defaultValue` | `number` | Initial value when no answer yet. Default `min` |
142
+ | `unit` | `string` | Suffix shown next to the value (e.g. `"/10"`, `"%"`) |
143
+
144
+ #### Date — `type: "date"`
145
+
146
+ | Field | Type | Description |
147
+ |-------|------|-------------|
148
+ | `min` | `string` | ISO date string for the earliest allowed date |
149
+ | `max` | `string` | ISO date string for the latest allowed date |
150
+
151
+ #### Multiple Choice — `type: "multiple-choice"`
152
+
153
+ | Field | Type | Description |
154
+ |-------|------|-------------|
155
+ | `options` | `MultipleChoiceOption[]` | **(required)** List of options |
156
+ | `multiple` | `boolean` | Allow multiple selections. Answer becomes `string[]`. Default `false` |
157
+
158
+ Each option has `value`, `label`, and optional `description`.
159
+
160
+ ### Example questionnaire JSON
161
+
162
+ ```json
163
+ {
164
+ "id": "intake-2026",
165
+ "title": "Project intake",
166
+ "description": "Tell us a bit about your project. Takes about 2 minutes.",
167
+ "submitLabel": "Submit",
168
+ "thankYouTitle": "Got it!",
169
+ "thankYouMessage": "We'll be in touch within one business day.",
170
+ "questions": [
171
+ {
172
+ "id": "name",
173
+ "type": "text",
174
+ "label": "Your name",
175
+ "required": true,
176
+ "placeholder": "Jane Smith"
177
+ },
178
+ {
179
+ "id": "email",
180
+ "type": "text",
181
+ "label": "Best email to reach you",
182
+ "inputType": "email",
183
+ "required": true
184
+ },
185
+ {
186
+ "id": "budget",
187
+ "type": "currency",
188
+ "label": "What's your budget?",
189
+ "currency": "USD",
190
+ "min": 0
191
+ },
192
+ {
193
+ "id": "timeline",
194
+ "type": "range",
195
+ "label": "How urgent is this?",
196
+ "min": 1,
197
+ "max": 10,
198
+ "defaultValue": 5,
199
+ "unit": "/10"
200
+ },
201
+ {
202
+ "id": "start",
203
+ "type": "date",
204
+ "label": "When would you like to start?"
205
+ },
206
+ {
207
+ "id": "needs",
208
+ "type": "multiple-choice",
209
+ "label": "What do you need help with?",
210
+ "multiple": true,
211
+ "options": [
212
+ { "value": "design", "label": "Design" },
213
+ { "value": "engineering", "label": "Engineering" },
214
+ { "value": "strategy", "label": "Strategy" }
215
+ ]
216
+ }
217
+ ]
218
+ }
219
+ ```
220
+
221
+ ## Configuration
222
+
223
+ All options are optional except `endpoint` and one of `questionnaire` / `configUrl`.
224
+
225
+ We offer a Free Plan that hosts both the endpoint and a dashboard for managing your form responses.
226
+
227
+ ### Core Options
228
+
229
+ | Option | Type | Default | Description |
230
+ |--------|------|---------|-------------|
231
+ | `endpoint` | `string` | **(required)** | API endpoint URL for storing answers |
232
+ | `questionnaire` | `Questionnaire` | — | Inline questionnaire definition (takes precedence over `configUrl`) |
233
+ | `configUrl` | `string` | — | URL to fetch the questionnaire JSON from |
234
+ | `apiKey` | `string` | `""` | API key for authenticating with the service |
235
+ | `container` | `string` | `""` | CSS selector for a host element. If absent, the form opens as a modal |
236
+ | `zIndex` | `number` | `50` | z-index for modal mode |
237
+
238
+ ### Behavior
239
+
240
+ | Option | Type | Default | Description |
241
+ |--------|------|---------|-------------|
242
+ | `autoAdvance` | `boolean` | `false` | Advance automatically on selection for single-choice multiple-choice and range questions |
243
+ | `saveDebounceMs` | `number` | `500` | Debounce in ms for autosave on text/currency/range |
244
+ | `autoFocus` | `boolean` | `true` | Auto-focus the input on each step. Set `false` in preview/embedded contexts that shouldn't steal focus |
245
+
246
+ ### Appearance
247
+
248
+ | Option | Type | Default | Description |
249
+ |--------|------|---------|-------------|
250
+ | `modalWidth` | `number` | `480` | Width of the modal in pixels |
251
+ | `backdropOpacity` | `number` | `0.4` | Opacity of the dark backdrop in modal mode (0-1) |
252
+ | `fontFamily` | `string` | `"system-ui, -apple-system, sans-serif"` | Font family for all text |
253
+ | `fontSize` | `number` | `16` | Base font size in pixels |
254
+ | `css` | `string` | `""` | Custom CSS injected into the shadow root for theming |
255
+
256
+ ### Colors
257
+
258
+ Pass a `colors` object to customize the color scheme. Variable names are shared with `@qaiddev/thumbs-embed`, so themes written for one embed render identically in the other:
259
+
260
+ ```typescript
261
+ new QaidQuests({
262
+ endpoint: '/api/responses',
263
+ configUrl: '/forms/intake.json',
264
+ colors: {
265
+ positive: 'rgb(0, 200, 83)', // Progress bar, focus rings, submit button
266
+ negative: 'rgb(255, 0, 0)', // Validation errors
267
+ marker: '#6366f1', // Selection / highlight
268
+ },
269
+ });
270
+ ```
271
+
272
+ Colors accept hex (`#ABC`, `#AABBCC`) or `rgb(r, g, b)` format. The text color used on top of `positive` and `marker` is computed automatically based on luminance.
273
+
274
+ ## Script Tag Data Attributes
275
+
276
+ When using the script tag method, all config options are available as `data-*` attributes:
277
+
278
+ | Attribute | Maps To |
279
+ |-----------|---------|
280
+ | `data-endpoint` | `endpoint` |
281
+ | `data-config-url` | `configUrl` |
282
+ | `data-api-key` | `apiKey` |
283
+ | `data-container` | `container` |
284
+ | `data-zindex` | `zIndex` |
285
+ | `data-positive-color` | `colors.positive` |
286
+ | `data-negative-color` | `colors.negative` |
287
+ | `data-marker-color` | `colors.marker` |
288
+ | `data-modal-width` | `modalWidth` |
289
+ | `data-backdrop-opacity` | `backdropOpacity` |
290
+ | `data-font-family` | `fontFamily` |
291
+ | `data-font-size` | `fontSize` |
292
+ | `data-auto-advance` | `autoAdvance` (`"true"` to enable) |
293
+ | `data-save-debounce-ms` | `saveDebounceMs` |
294
+ | `data-css-selector` | CSS selector for an element whose `textContent` is used as `css` |
295
+
296
+ ## Modal vs. Inline
297
+
298
+ By default the embed opens as a fixed-position modal centered on the viewport. To render the form inline inside your own element, pass `container`:
299
+
300
+ ```html
301
+ <div id="my-form-spot"></div>
302
+
303
+ <script>
304
+ new QaidQuests({
305
+ endpoint: '/api/responses',
306
+ configUrl: '/forms/intake.json',
307
+ container: '#my-form-spot',
308
+ });
309
+ </script>
310
+ ```
311
+
312
+ When `container` is set, `zIndex` is ignored and the backdrop / close button are not rendered. You control the layout.
313
+
314
+ ## API
315
+
316
+ ### Constructor
317
+
318
+ ```typescript
319
+ const quests = new QaidQuests(config: QuestsConfig);
320
+ ```
321
+
322
+ ### Methods
323
+
324
+ | Method | Description |
325
+ |--------|-------------|
326
+ | `getAnswers()` | Read-only snapshot of the answers collected so far, keyed by question id |
327
+ | `destroy()` | Remove all DOM elements, event listeners, and injected styles. Safe to call multiple times |
328
+
329
+ ### Server Protocol
330
+
331
+ The embed talks to your endpoint in three steps:
332
+
333
+ **1. Create response** — `POST {endpoint}`:
334
+
335
+ ```json
336
+ {
337
+ "apiKey": "YOUR_API_KEY",
338
+ "questId": "intake-2026",
339
+ "pageUrl": "https://example.com/contact",
340
+ "visitorId": "a1b2c3d4-...",
341
+ "userAgent": "Mozilla/5.0 ..."
342
+ }
343
+ ```
344
+
345
+ Your endpoint should return `{ "id": "..." }` (string or number). The form is usable immediately while creation is in flight — saves queue until the id arrives.
346
+
347
+ **2. Save answer** — `PATCH {endpoint}/{id}` (one request per question, debounced for text/currency/range):
348
+
349
+ ```json
350
+ {
351
+ "questionId": "budget",
352
+ "value": 5000
353
+ }
354
+ ```
355
+
356
+ `value` is `string | number | string[] | null` depending on the question type.
357
+
358
+ **3. Submit** — `POST {endpoint}/{id}/submit`:
359
+
360
+ ```json
361
+ {
362
+ "answers": {
363
+ "name": "Jane Smith",
364
+ "budget": 5000,
365
+ "needs": ["design", "engineering"]
366
+ }
367
+ }
368
+ ```
369
+
370
+ When an `apiKey` is configured, it's also sent as an `X-API-Key` header on every request.
371
+
372
+ ## Answer Types
373
+
374
+ | Question type | Answer value type |
375
+ |---------------|-------------------|
376
+ | `text` | `string` |
377
+ | `currency` | `number` |
378
+ | `range` | `number` |
379
+ | `date` | `string` (ISO date) |
380
+ | `multiple-choice` (single) | `string` |
381
+ | `multiple-choice` (multiple) | `string[]` |
382
+
383
+ Unanswered questions are stored as `null`.
384
+
385
+ ## Visitor IDs
386
+
387
+ The embed generates a stable `visitorId` (UUID) per browser, stored in `localStorage` under the `qaid_visitor_id` key. It's sent with every `createResponse` call so you can correlate multiple responses from the same visitor. If `localStorage` is unavailable, a fresh UUID is generated per session.
388
+
389
+ ## Keyboard Behavior
390
+
391
+ - **Enter** — advance to the next question, or submit on the final step
392
+ - **Escape** — close the form (modal mode only; ignored when `container` is set)
393
+ - **Tab / Shift+Tab** — move focus between inputs and buttons
394
+
395
+ Each step auto-focuses its input on render so keyboard and screen-reader users land on the active question. Set `autoFocus: false` on the initial mount to opt out.
396
+
397
+ ## Theming
398
+
399
+ The embed renders inside a Shadow DOM, so your page's CSS can't leak in. To theme it, pass a `css` string that's injected into the shadow root, or override the CSS custom properties:
400
+
401
+ ```css
402
+ :root {
403
+ --qaid-positive: rgb(0, 200, 83);
404
+ --qaid-negative: rgb(255, 0, 0);
405
+ --qaid-marker: #6366f1;
406
+ --qaid-modal-width: 480px;
407
+ --qaid-backdrop-opacity: 0.4;
408
+ --qaid-font-family: system-ui, -apple-system, sans-serif;
409
+ --qaid-font-size: 16px;
410
+ }
411
+ ```
412
+
413
+ The variable names are shared with `@qaiddev/thumbs-embed`, so a single theme can drive both embeds.
414
+
415
+ ## Hosted Dashboard
416
+
417
+ [Qaid](https://qaid.dev) provides a hosted dashboard for managing responses collected by this embed:
418
+
419
+ - **Response inbox** with answers, timestamps, and visitor history
420
+ - **Form builder** for editing questionnaire JSON without redeploying
421
+ - **Project management** with multiple API keys
422
+ - **Team collaboration** with owners, members, and invitations
423
+ - **Analytics** and data retention
424
+
425
+ ### Plans
426
+
427
+ | | Cadet (Free) | Commander ($9/mo) | Admiral (Enterprise) |
428
+ |---|---|---|---|
429
+ | Projects | 1 | Unlimited | Unlimited |
430
+ | Responses | 100/mo | Unlimited | Unlimited |
431
+ | Data retention | 7 days | 1 year | Unlimited |
432
+ | Notifications | - | Email/Text | Email/Text |
433
+ | Integrations | - | GitHub + more | Custom |
434
+
435
+ Sign up at [qaid.dev](https://qaid.dev). You can also self-host — the embed works with any endpoint that accepts the protocol described above.
436
+
437
+ ## Self-Hosting
438
+
439
+ The embed is endpoint-agnostic. Point it at your own server:
440
+
441
+ ```typescript
442
+ new QaidQuests({
443
+ endpoint: 'https://your-server.com/api/responses',
444
+ configUrl: '/forms/intake.json',
445
+ });
446
+ ```
447
+
448
+ Your server needs to handle:
449
+
450
+ 1. `POST /api/responses` — Accept the create-response payload, return `{ "id": string | number }`
451
+ 2. `PATCH /api/responses/:id` — Accept `{ "questionId": string, "value": ... }` to save a single answer
452
+ 3. `POST /api/responses/:id/submit` — Accept `{ "answers": Record<string, ...> }` for the final batch
453
+
454
+ ## Project Badges
455
+
456
+ <a href="https://www.npmjs.com/package/@qaiddev/quests-embed">
457
+ <img alt="npm version" src="https://img.shields.io/npm/v/@qaiddev/quests-embed" />
458
+ </a>
459
+
460
+ <a href="https://github.com/qaiddev/quests-embed/blob/prod/LICENSE">
461
+ <img alt="license" src="https://img.shields.io/npm/l/@qaiddev/quests-embed"/>
462
+ </a>
@@ -0,0 +1,71 @@
1
+ import type { Answers, QuestsConfig } from "./types";
2
+ export declare class QaidQuests {
3
+ private config;
4
+ private questionnaire;
5
+ private inlineQuestionnaire;
6
+ private configUrl;
7
+ private state;
8
+ private stepIndex;
9
+ private visibleQuestions;
10
+ private pendingGoToStep;
11
+ private hasRenderedStep;
12
+ private answers;
13
+ private responseId;
14
+ private visitorId;
15
+ private pendingSaveTimer;
16
+ private pendingSaveQuestionId;
17
+ private pendingSaveValue;
18
+ private inflightSaves;
19
+ private shadowHost;
20
+ private shadowRoot;
21
+ private isUserContainer;
22
+ private rootEl;
23
+ private cardEl;
24
+ private titleEl;
25
+ private stepCounterEl;
26
+ private progressEl;
27
+ private progressFillEl;
28
+ private bodyEl;
29
+ private footerEl;
30
+ private savingEl;
31
+ private backdropEl;
32
+ private currentInput;
33
+ private boundKeyDown;
34
+ private cssVars;
35
+ constructor(config: QuestsConfig);
36
+ private init;
37
+ private loadQuestionnaire;
38
+ private mountShell;
39
+ private renderLoading;
40
+ private renderError;
41
+ private renderHeader;
42
+ private renderStep;
43
+ private renderDone;
44
+ private advance;
45
+ private back;
46
+ private recomputeVisible;
47
+ private handleAnswerChange;
48
+ private flushPendingSave;
49
+ private saveAnswer;
50
+ private doSave;
51
+ private createResponse;
52
+ private submit;
53
+ private jsonHeaders;
54
+ private showSaving;
55
+ private handleKeyDown;
56
+ private close;
57
+ /** Destroy the embed and clean up all resources */
58
+ destroy(): void;
59
+ /** Read-only snapshot of current answers */
60
+ getAnswers(): Answers;
61
+ /**
62
+ * Jump to the visible question with the given id. Returns true if
63
+ * the question is currently visible (and the embed navigated to
64
+ * it), false if it's hidden by an unmet `visibleIf` predicate or
65
+ * unknown. If the embed is still initializing, the request is
66
+ * latched and applied as soon as the questionnaire is ready.
67
+ *
68
+ * Intended for editor previews and other host-driven step control.
69
+ */
70
+ goToStep(questionId: string): boolean;
71
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @qaiddev/quests-embed
3
+ *
4
+ * Step-by-step JSON-defined form embed.
5
+ *
6
+ * Usage as ES module:
7
+ * ```ts
8
+ * import { QaidQuests } from "@qaiddev/quests-embed";
9
+ *
10
+ * new QaidQuests({
11
+ * endpoint: "/api/responses",
12
+ * configUrl: "/forms/intake.json",
13
+ * container: "#form",
14
+ * });
15
+ * ```
16
+ *
17
+ * Usage via script tag (auto-init):
18
+ * ```html
19
+ * <script src="qaid-quests.umd.cjs"
20
+ * data-endpoint="/api/responses"
21
+ * data-config-url="/forms/intake.json"
22
+ * data-container="#form"></script>
23
+ * ```
24
+ *
25
+ * Or with a JSON config tag:
26
+ * ```html
27
+ * <script type="application/json" data-quests-config>
28
+ * { "endpoint": "/api/responses", "configUrl": "/forms/intake.json" }
29
+ * </script>
30
+ * <script src="qaid-quests.umd.cjs"></script>
31
+ * ```
32
+ */
33
+ import { QaidQuests } from "./embed";
34
+ export { QaidQuests };
35
+ export { getVisibleQuestions, evaluateRule } from "./visibility";
36
+ export type { QuestsConfig, ResolvedQuestsConfig, Questionnaire, Question, TextQuestion, CurrencyQuestion, RangeQuestion, DateQuestion, MultipleChoiceQuestion, MultipleChoiceOption, Answers, AnswerValue, VisibilityRule, CreateResponsePayload, CreateResponseResult, UpdateAnswerPayload, SubmitPayload, } from "./types";
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Input renderers for each question type.
3
+ *
4
+ * Each renderer returns a small object with an element to mount, a
5
+ * focus helper, a synchronous current-value reader, and a validator.
6
+ *
7
+ * Per-keystroke autosave is wired by the embed: the renderer just
8
+ * calls back into onChange whenever its underlying value changes.
9
+ * Renderers also call onSubmit when the user presses Enter (or
10
+ * onAutoAdvance after a discrete selection that should auto-advance).
11
+ */
12
+ import type { AnswerValue, Question } from "./types";
13
+ export interface QuestionInput {
14
+ element: HTMLElement;
15
+ focus(): void;
16
+ getValue(): AnswerValue;
17
+ isValid(): boolean;
18
+ }
19
+ export interface QuestionInputOptions {
20
+ question: Question;
21
+ initialValue: AnswerValue;
22
+ /** Called any time the value changes (every keystroke for text). */
23
+ onChange: (value: AnswerValue) => void;
24
+ /** Called when the user presses Enter to advance. */
25
+ onSubmit: () => void;
26
+ /**
27
+ * Called after a discrete selection that should auto-advance
28
+ * (e.g. picking a single-choice option when autoAdvance is on).
29
+ */
30
+ onAutoAdvance: () => void;
31
+ /** Whether the embed is configured to auto-advance after discrete selections */
32
+ autoAdvance: boolean;
33
+ }
34
+ export declare function createInput(options: QuestionInputOptions): QuestionInput;