@agoodway/goodleads-quiz-core
v0.1.0
Published
Core quiz funnel engine for GoodLeads lead capture. Provides a multi-step questionnaire state machine with conditional branching, validation, and DOM rendering — all framework-agnostic.
Readme
@agoodway/goodleads-quiz-core
Core quiz funnel engine for GoodLeads lead capture. Provides a multi-step questionnaire state machine with conditional branching, validation, and DOM rendering — all framework-agnostic.
Installation
npm install @agoodway/goodleads-quiz-core
# or
bun add @agoodway/goodleads-quiz-coreUsage
Creating a Quiz Engine
The quiz engine attaches to a container element and drives a multi-step questionnaire:
import { createQuizEngine } from "@agoodway/goodleads-quiz-core";
import type { Questionnaire } from "@agoodway/goodleads-quiz-core";
const questionnaire: Questionnaire = {
questions: [
{
id: "service_type",
text: "What service do you need?",
type: "multiple_choice",
required: true,
options: [
{ value: "roofing", label: "Roofing" },
{ value: "plumbing", label: "Plumbing" },
{ value: "electrical", label: "Electrical" },
],
},
{
id: "zip_code",
text: "What's your ZIP code?",
type: "zip",
required: true,
},
{
id: "email",
text: "What's your email address?",
type: "email",
required: true,
},
],
};
const container = document.getElementById("quiz")!;
createQuizEngine(container, questionnaire, {
onStepComplete: (stepIndex, questionId, answer, allAnswers) => {
console.log(`Step ${stepIndex} answered: ${questionId} = ${answer}`);
},
onFunnelComplete: (allAnswers) => {
console.log("Quiz complete!", allAnswers);
// Submit to your API
},
});Conditional Questions
Questions can be shown/hidden based on previous answers:
{
id: "roof_material",
text: "What material is your roof?",
type: "multiple_choice",
options: [
{ value: "shingle", label: "Shingle" },
{ value: "metal", label: "Metal" },
{ value: "tile", label: "Tile" },
],
conditions: [
{ questionId: "service_type", operator: "equals", value: "roofing" }
]
}Question Types
| Type | Description | Input |
|------|-------------|-------|
| text | Free-form text | Single-line input |
| number | Numeric value | Numeric input |
| email | Email address | Email input with validation |
| tel | Phone number | Tel input |
| zip | 5-digit ZIP code | Numeric input with validation |
| textarea | Multi-line text | Textarea |
| select | Dropdown select | Select element |
| multiple_choice | Option cards | Clickable cards (auto-advances) |
| boolean | Yes/No | Two option cards |
| address | Full US address | Street, city, state, ZIP composite |
| consent | Consent checkbox | Checkbox with legal text |
Condition Operators
| Operator | Description |
|----------|-------------|
| equals | Exact match (supports arrays for OR logic) |
| not_equals | Not equal (supports arrays for AND-NOT logic) |
| greater_than | Numeric comparison |
| less_than | Numeric comparison |
| contains | Case-insensitive substring match |
Hidden Questions with Default Values
Questions can be hidden and pre-populated (useful for tracking/metadata):
{
id: "source",
text: "Source",
type: "text",
hidden: true,
default_value: "organic"
}Resuming a Session
Pass initial state to resume a partially completed quiz:
createQuizEngine(container, questionnaire, callbacks, {
answers: { service_type: "roofing", zip_code: "90210" },
qualifyQuestionId: null,
});Engine Controls
The onEngineReady callback provides controls for programmatic interaction:
createQuizEngine(container, questionnaire, {
onEngineReady: (controls) => {
// Prevent user from navigating back past this point
controls.lockHistory();
},
});Utility Functions
import { escapeHtml, getVisibleQuestions } from "@agoodway/goodleads-quiz-core";
// XSS-safe HTML escaping
escapeHtml("<script>alert('xss')</script>");
// → "<script>alert('xss')</script>"
// Get questions visible given current answers
const visible = getVisibleQuestions(questionnaire.questions, currentAnswers);Container HTML Structure
The engine expects this DOM structure in your container:
<div data-quiz-funnel>
<div data-quiz-progress>
<div data-quiz-progress-bar role="progressbar"></div>
<span data-quiz-progress-text></span>
</div>
<div data-quiz-step></div>
<div data-quiz-nav>
<button data-quiz-back>← Back</button>
<button data-quiz-next>Next →</button>
</div>
</div>CSS
The engine adds class names like quiz-option-card, quiz-text-input, quiz-label, etc. You provide the styles. See the @agoodway/goodleads-astro package for a complete stylesheet.
API Reference
Types
Questionnaire— Top-level quiz definition with questions array and optional messagesQuestion— Individual question with type, options, conditions, and actionsQuestionCondition— Conditional visibility ruleQuizEngineControls— Programmatic engine control (lockHistory)QuizInitialState— Saved state for session resumptionQuestionnaireMessages— Configurable UI messages for completion, disqualification, etc.
Functions
createQuizEngine(container, questionnaire, callbacks?, initialState?)— Initialize the quiz enginegetVisibleQuestions(questions, answers)— Compute visible questions given current answerscollectHiddenDefaultAnswers(questions)— Extract hidden question defaultsescapeHtml(str)— XSS-safe HTML escapingrenderQuestion(question, currentValue?)— Render a question to HTML string
License
MIT
