import { DbxInjectionComponentConfig, DbxActionSuccessHandlerFunction } from '@dereekb/dbx-core'; import { IndexNumber, Maybe, RangeInput, MaybeMap } from '@dereekb/util'; import * as rxjs from 'rxjs'; import { Observable, Subscription } from 'rxjs'; import * as _dereekb_dbx_form_quiz from '@dereekb/dbx-form/quiz'; import { ComponentStore } from '@ngrx/component-store'; import { ObservableOrValue, Work } from '@dereekb/rxjs'; import * as _angular_core from '@angular/core'; import { Provider, Signal } from '@angular/core'; /** * Unique identifier for a quiz instance. */ type QuizId = string; /** * Unique identifier for a question within a quiz. */ type QuizQuestionId = string; /** * Zero-based index of the currently selected question. */ type QuestionIndex = IndexNumber; /** * Full quiz definition including metadata, questions, and injection configs for pre/post views. * * @example * ```ts * const quiz: Quiz = { * id: 'onboarding', * titleDetails: { title: 'Onboarding Quiz' }, * questions: [{ id: 'q1', questionComponentConfig: { ... }, answerComponentConfig: { ... } }], * resultsComponentConfig: { componentClass: MyResultsComponent } * }; * ``` */ interface Quiz { readonly id: QuizId; /** * Title details for the quiz. */ readonly titleDetails: QuizTitleDetails; /** * Questions to display. The questions are displayed in order. */ readonly questions: QuizQuestion[]; /** * Component config for the pre-quiz view. */ readonly preQuizComponentConfig?: DbxInjectionComponentConfig; /** * Component config for the results view. */ readonly resultsComponentConfig: DbxInjectionComponentConfig; } /** * Display metadata for a quiz header area. */ interface QuizTitleDetails { /** * Name/title of the quiz */ readonly title: string; /** * Subtitle of the quiz */ readonly subtitle?: string; /** * Description of the quiz */ readonly description?: string; } /** * Pairs a question id with the user's typed answer data. */ interface QuizAnswer { /** * Id of the question this answer belongs to. */ readonly id: QuizQuestionId; /** * The answer payload, typed per-question component. */ readonly data: T; } /** * Lightweight pair linking a question id to its positional index in the quiz. */ interface QuizQuestionIdIndexPair { readonly id: QuizQuestionId; readonly index: QuestionIndex; } /** * Single question definition within a quiz, pairing a display component with an answer component. */ interface QuizQuestion { /** * Question id. Should be unique in the quiz. */ readonly id: QuizQuestionId; /** * Question component config */ readonly questionComponentConfig: DbxInjectionComponentConfig; /** * Answer component config */ readonly answerComponentConfig: DbxInjectionComponentConfig; } /** * A QuizQuestion enriched with its positional index, used by the store to track the current question. */ interface QuizQuestionWithIndex extends QuizQuestion, QuizQuestionIdIndexPair { } /** * Internal state shape managed by QuizStore. */ interface QuizStoreState { /** * Map of questions, keyed by id. * * Unset if the quiz is not yet set. */ readonly questionMap?: Maybe>; /** * Started quiz */ readonly startedQuiz: boolean; /** * Questions that have been answered, sorted by index. */ readonly completedQuestions: QuizQuestionIdIndexPair[]; /** * Questions that are remaining to be answered, sorted by index. */ readonly unansweredQuestions: QuizQuestionIdIndexPair[]; /** * Map of current answers. */ readonly answers: ReadonlyMap; /** * The current index that corresponds with the selected question. * * If null, defaults to the first question. * * If greater than the total number of questions, then the quiz is considered complete. */ readonly questionIndex?: Maybe; /** * If true, the quiz is locked from navigation. * * Typically used while submitting the quiz. */ readonly lockQuizNavigation?: boolean; /** * If true, the quiz has been marked as submitted. */ readonly submittedQuiz?: boolean; /** * The current/active quiz. */ readonly quiz?: Maybe; /** * If true, allows going back to visit previous questions. */ readonly allowVisitingPreviousQuestion: boolean; /** * If true, automatically advances to the next question when an answer is set. */ readonly autoAdvanceToNextQuestion: boolean; /** * Whether or not skipping questions is allowed. * * Defaults to false. */ readonly allowSkipQuestion: boolean; } /** * Lookup input for retrieving an answer by question id, index, or the current question. * Provide exactly one of `id`, `index`, or `currentIndex`. */ interface QuizStoreAnswerLookupInput extends Partial { readonly currentIndex?: boolean; } /** * NgRx ComponentStore that manages quiz lifecycle: question navigation, answer tracking, * submission state, and navigation locking. * * Provided at the component level by `QuizComponent`. * * @example * ```ts * // Access from a child component via DI: * readonly quizStore = inject(QuizStore); * this.quizStore.startQuiz(); * this.quizStore.updateAnswerForCurrentQuestion(5); * ``` */ declare class QuizStore extends ComponentStore { constructor(); readonly quiz$: Observable>; readonly titleDetails$: Observable<_dereekb_dbx_form_quiz.QuizTitleDetails | undefined>; readonly questions$: Observable; readonly startedQuiz$: Observable; readonly lockQuizNavigation$: Observable; readonly submittedQuiz$: Observable; readonly answers$: Observable>>; readonly questionIndex$: Observable; readonly completedQuestions$: Observable; readonly unansweredQuestions$: Observable; readonly hasAnswerForEachQuestion$: Observable; readonly isAtEndOfQuestions$: Observable; readonly canGoToPreviousQuestion$: Observable; readonly canGoToNextQuestion$: Observable; readonly currentQuestion$: Observable>; /** * Returns a reactive observable of the answer for a given question, looked up by id, index, or the current question. * * @param lookupInput - Lookup criteria specifying which question's answer to retrieve. * @param lookupInput - Lookup criteria specifying which question's answer to retrieve. * @returns An observable that emits the current answer for the specified question, or undefined if not answered. * * @example * ```ts * // By current question: * store.answerForQuestion({ currentIndex: true }).subscribe(answer => console.log(answer)); * // By question id: * store.answerForQuestion({ id: 'q1' }).subscribe(answer => console.log(answer)); * ``` */ answerForQuestion(lookupInput: ObservableOrValue): Observable>; readonly startQuiz: () => void; readonly setQuiz: (() => void) | ((observableOrValue: Maybe | Observable>) => rxjs.Subscription); /** * Resets the quiz entirely, back to the pre-quiz state. */ readonly resetQuiz: () => void; /** * Restarts the quiz to the first question. */ readonly restartQuizToFirstQuestion: () => void; readonly setAnswers: (() => void) | ((observableOrValue: Maybe[]> | Observable[]>>) => rxjs.Subscription); readonly updateAnswers: (() => void) | ((observableOrValue: Maybe[]> | Observable[]>>) => rxjs.Subscription); readonly updateAnswerForCurrentQuestion: (observableOrValue: unknown) => rxjs.Subscription; readonly setQuestionIndex: (observableOrValue: number | Observable) => rxjs.Subscription; readonly setAutoAdvanceToNextQuestion: (observableOrValue: boolean | Observable) => rxjs.Subscription; readonly setAllowSkipQuestion: (observableOrValue: boolean | Observable) => rxjs.Subscription; readonly setAllowVisitingPreviousQuestion: (observableOrValue: boolean | Observable) => rxjs.Subscription; readonly goToNextQuestion: () => void; readonly goToPreviousQuestion: () => void; readonly setLockQuizNavigation: (observableOrValue: boolean | Observable) => rxjs.Subscription; readonly setSubmittedQuiz: (observableOrValue: boolean | Observable) => rxjs.Subscription; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵprov: _angular_core.ɵɵInjectableDeclaration; } /** * Abstract accessor injected into answer/question child components to read the current question * and write answers back to the QuizStore without coupling to it directly. * * Use `provideCurrentQuestionQuizQuestionAccessor()` to bind this to the store's current question. */ declare abstract class QuizQuestionAccessor { /** * The active quiz definition. */ abstract readonly quiz$: Observable>; /** * The question this accessor is bound to. */ abstract readonly question$: Observable>; /** * The current answer for this question, or undefined if unanswered. */ abstract readonly answer$: Observable>>; /** * Submits an answer value for this question. */ abstract setAnswer(answer: T): void; /** * Binds an observable source whose emissions are forwarded as answer updates. */ abstract setAnswerSource(answer: Observable): Subscription; } /** * Provides QuizQuestionAccessor bound to the current question in QuizStore. * * @returns An Angular provider that binds QuizQuestionAccessor to the current quiz question. * * @usage * ```typescript * @Component ({ * providers: [QuizStore, provideCurrentQuestionQuizQuestionAccessor()] * }) * ``` */ declare function provideCurrentQuestionQuizQuestionAccessor(): Provider; /** * Lifecycle state of the quiz container view. */ type QuizComponentState = 'init' | 'pre-quiz' | 'quiz' | 'post-quiz'; /** * Discriminated union of view configs, one per quiz lifecycle state. */ type QuizComponentViewConfig = QuizComponentViewInitConfig | QuizComponentViewPreQuizConfig | QuizComponentViewQuizConfig | QuizComponentViewPostQuizConfig; interface QuizComponentViewInitConfig { readonly state: 'init'; } interface QuizComponentViewPreQuizConfig { readonly state: 'pre-quiz'; readonly preQuizComponent?: Maybe; } interface QuizComponentViewQuizConfig { readonly state: 'quiz'; readonly questionComponent?: Maybe; readonly answerComponent?: Maybe; } interface QuizComponentViewPostQuizConfig { readonly state: 'post-quiz'; readonly resultsComponent?: Maybe; } /** * Top-level quiz container that orchestrates pre-quiz, active quiz, and post-quiz views. * * Provides its own `QuizStore` and `QuizQuestionAccessor`, so child components injected via * `DbxInjectionComponent` can access quiz state directly through DI. * * Supports keyboard navigation: Enter (start / next), ArrowLeft (previous), ArrowRight (next). * * @example * ```html * * ``` */ declare class QuizComponent { readonly quizStore: QuizStore; readonly quiz: _angular_core.InputSignal>; readonly keysFilter: string[]; readonly quizEffect: _angular_core.EffectRef; readonly quiz$: Observable>; readonly quizTitleSignal: Signal; readonly currentQuestionSignal: Signal>; readonly questionTitleSignal: Signal; readonly startedQuiz$: Observable; readonly currentQuestion$: Observable>; readonly canGoToPreviousQuestionSignal: Signal; readonly canGoToNextQuestionSignal: Signal; readonly viewConfig$: Observable; readonly viewConfigSignal: Signal; readonly viewStateSignal: Signal; readonly preQuizComponentConfigSignal: Signal>; readonly questionComponentConfigSignal: Signal>; readonly answerComponentConfigSignal: Signal>; readonly resultsComponentConfigSignal: Signal>; handleKeyDown(event: KeyboardEvent): void; clickPreviousQuestion(): void; clickNextQuestion(): void; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Named preset for common number ranges. */ type QuizAnswerNumberComponentPreset = 'oneToFive'; /** * Configuration for the number answer component. Provide a preset, explicit range, or arbitrary numbers. */ interface QuizAnswerNumberComponentConfig { /** * Uses a preset to compute the range/numbers. */ readonly preset?: QuizAnswerNumberComponentPreset; /** * Range configuration */ readonly range?: RangeInput; /** * Arbitrary array of numbers */ readonly numbers?: number[]; } interface QuizAnswerNumberChoice { readonly number: number; readonly selected?: boolean; } /** * Answer component that displays configurable number buttons. * * @usage * Used as an answer component in a QuizQuestion's answerComponentConfig. * Defaults to 1-5 range if no config is provided. * * ```typescript * answerComponentConfig: { * componentClass: QuizAnswerNumberComponent, * init: (instance: QuizAnswerNumberComponent) => { * instance.config.set({ range: { start: 1, end: 11 } }); * } * } * ``` */ declare class QuizAnswerNumberComponent { readonly questionAccessor: QuizQuestionAccessor; readonly config: _angular_core.ModelSignal>; readonly currentAnswerSignal: _angular_core.Signal>>; readonly currentAnswerValueSignal: _angular_core.Signal; readonly choicesSignal: _angular_core.Signal; readonly relevantKeysSignal: _angular_core.Signal; clickedAnswer(answer: number): void; handleKeyDown(event: KeyboardEvent): void; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Upper-case letter label for a multiple choice option (e.g. "A", "B"). */ type MultipleChoiceLetter = string; /** * Display text for a single multiple choice option. */ type MultipleChoiceText = string; /** * Answer data stored when a multiple choice option is selected. */ interface MultipleChoiceAnswer { readonly isCorrectAnswer: boolean; readonly letter: MultipleChoiceLetter; readonly text: MultipleChoiceText; } /** * Configuration for the multiple choice answer component. */ interface QuizAnswerMultipleChoiceComponentConfig { /** * Ordered list of answer option texts. Letters are auto-assigned A, B, C, etc. */ readonly answerText: readonly MultipleChoiceText[]; /** * Zero-based index of the correct answer, used to set `isCorrectAnswer` on the stored answer. */ readonly correctAnswerIndex?: number; } interface QuizAnswerMultipleChoice extends MultipleChoiceAnswer { readonly selected?: boolean; } /** * Answer component that displays multiple choice letter-labeled buttons. * * @usage * Used as an answer component in a QuizQuestion's answerComponentConfig. * Supports keyboard shortcuts (pressing the letter key selects that answer). * * ```typescript * answerComponentConfig: { * componentClass: QuizAnswerMultipleChoiceComponent, * init: (instance: QuizAnswerMultipleChoiceComponent) => { * instance.config.set({ * answerText: ['Option A', 'Option B', 'Option C'], * correctAnswerIndex: 1 * }); * } * } * ``` */ declare class QuizAnswerMultipleChoiceComponent { readonly questionAccessor: QuizQuestionAccessor; readonly config: _angular_core.ModelSignal>; readonly currentAnswerSignal: _angular_core.Signal>>; readonly currentAnswerValueSignal: _angular_core.Signal; readonly choicesSignal: _angular_core.Signal; readonly relevantKeysSignal: _angular_core.Signal; clickedAnswer(answer: QuizAnswerMultipleChoice): void; handleKeyDown(event: KeyboardEvent): void; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Config for the pre-quiz intro component. * * Overrides the title details. */ type QuizPreQuizIntroConfig = Partial>; /** * Pre-quiz intro component that displays the quiz title, subtitle, description, and a start button. * * @usage * Used as a preQuizComponentConfig in a Quiz definition. * Inherits title details from the quiz unless overridden via config. * * ```typescript * preQuizComponentConfig: { * componentClass: QuizPreQuizIntroComponent, * init: (instance: QuizPreQuizIntroComponent) => { * instance.config.set({ subtitle: 'Custom subtitle' }); * } * } * ``` */ declare class QuizPreQuizIntroComponent { readonly quizStore: QuizStore; readonly config: _angular_core.ModelSignal>>>; readonly quizTitleDetailsSignal: _angular_core.Signal; readonly configSignal: _angular_core.Signal<{ title: string | undefined; subtitle: string | undefined; description: string | undefined; }>; readonly titleSignal: _angular_core.Signal; readonly subtitleSignal: _angular_core.Signal; readonly descriptionSignal: _angular_core.Signal; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Configuration for the text-based question display. Used with `quizAgreementPrompt()` and * `quizFrequencyPrompt()` helpers for Likert-scale questions. */ interface QuizQuestionTextComponentConfig { /** * Instructional prompt displayed above the main text (e.g. "Rate how much you agree:"). */ readonly prompt?: string; /** * The primary question or statement text. */ readonly text: string; /** * Scale guidance displayed below the text (e.g. "1 = Strongly Disagree, 5 = Strongly Agree"). */ readonly guidance?: string; } /** * Question component that displays text with optional prompt and guidance. * * @usage * Used as a questionComponentConfig in a QuizQuestion definition. * * ```typescript * questionComponentConfig: { * componentClass: QuizQuestionTextComponent, * init: (instance: QuizQuestionTextComponent) => { * instance.config.set({ text: 'How do you handle ambiguity?', prompt: 'Rate yourself:', guidance: '1=Never, 5=Always' }); * } * } * ``` */ declare class QuizQuestionTextComponent { readonly config: _angular_core.ModelSignal>; readonly promptSignal: _angular_core.Signal; readonly textSignal: _angular_core.Signal; readonly guidanceSignal: _angular_core.Signal; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Submission lifecycle state within the post-quiz view. */ type DbxQuizPostQuizState = 'presubmit' | 'postsubmit'; /** * Post-quiz component that handles quiz submission and displays pre/post submit content. * * @usage * Use as a wrapper in your results component template: * * ```html * *
Pre-submit content...
*
Post-submit content (scores, etc.)...
*
* ``` */ declare class DbxQuizPostQuizComponent { readonly quizStore: QuizStore; readonly quizSubmittedSignal: _angular_core.Signal; readonly stateSignal: _angular_core.Signal<"presubmit" | "postsubmit">; readonly handleSubmitQuiz: _angular_core.InputSignal | undefined>; readonly handleSubmitQuizButton: Work; readonly handleSubmitQuizSuccess: DbxActionSuccessHandlerFunction; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Button component that restarts the quiz to the first question. * * @usage * ```html * * ``` */ declare class DbxQuizResetButtonComponent { readonly quizStore: QuizStore; readonly buttonText: _angular_core.InputSignal; readonly handleResetQuizButton: Work; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Input data for rendering the quiz score display. */ interface DbxQuizScoreInput { /** * Whether to show the retake/reset button. */ readonly showRetakeButton?: boolean; /** * Feedback text to display. */ readonly feedbackText: string; /** * Optional subtitle text. */ readonly subtitle?: Maybe; /** * The score achieved. */ readonly score: number; /** * The maximum possible score. */ readonly maxScore: number; } /** * Generic quiz score display component. * * @usage * ```html * * ``` */ declare class DbxQuizScoreComponent { readonly input: _angular_core.InputSignal>; readonly scoreSignal: _angular_core.Signal; readonly maxScoreSignal: _angular_core.Signal; readonly feedbackTextSignal: _angular_core.Signal; readonly subtitleSignal: _angular_core.Signal>; readonly showRetakeButtonSignal: _angular_core.Signal; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } /** * Creates a Likert scale question config with agreement prompt (Strongly Disagree to Strongly Agree). * * @param text - The statement to rate agreement on. * @param text - The statement to rate agreement on. * @returns A quiz question config with an agreement-based prompt and guidance text. * * @example * ```ts * instance.config.set(quizAgreementPrompt('I feel confident leading under pressure.')); * // { prompt: 'Please rate how much you agree...', text: '...', guidance: '1 = Strongly Disagree, 5 = Strongly Agree' } * ``` */ declare function quizAgreementPrompt(text: string): QuizQuestionTextComponentConfig; /** * Creates a Likert scale question config with frequency prompt (Never to Always). * * @param text - The statement to rate frequency on. * @param text - The statement to rate frequency on. * @returns A quiz question config with a frequency-based prompt and guidance text. * * @example * ```ts * instance.config.set(quizFrequencyPrompt('I break vague direction into first steps.')); * // { prompt: 'Please rate how much you agree...', text: '...', guidance: '1 = Never, 5 = Always' } * ``` */ declare function quizFrequencyPrompt(text: string): QuizQuestionTextComponentConfig; export { DbxQuizPostQuizComponent, DbxQuizResetButtonComponent, DbxQuizScoreComponent, QuizAnswerMultipleChoiceComponent, QuizAnswerNumberComponent, QuizComponent, QuizPreQuizIntroComponent, QuizQuestionAccessor, QuizQuestionTextComponent, QuizStore, provideCurrentQuestionQuizQuestionAccessor, quizAgreementPrompt, quizFrequencyPrompt }; export type { DbxQuizPostQuizState, DbxQuizScoreInput, MultipleChoiceAnswer, MultipleChoiceLetter, MultipleChoiceText, QuestionIndex, Quiz, QuizAnswer, QuizAnswerMultipleChoiceComponentConfig, QuizAnswerNumberComponentConfig, QuizAnswerNumberComponentPreset, QuizComponentState, QuizComponentViewConfig, QuizComponentViewInitConfig, QuizComponentViewPostQuizConfig, QuizComponentViewPreQuizConfig, QuizComponentViewQuizConfig, QuizId, QuizPreQuizIntroConfig, QuizQuestion, QuizQuestionId, QuizQuestionIdIndexPair, QuizQuestionTextComponentConfig, QuizQuestionWithIndex, QuizStoreAnswerLookupInput, QuizStoreState, QuizTitleDetails };