@dereekb/dbx-form 13.3.1 → 13.4.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.
@@ -0,0 +1,773 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, input, effect, computed, Component, model } from '@angular/core';
3
+ import { ComponentStore } from '@ngrx/component-store';
4
+ import { asArray, range } from '@dereekb/util';
5
+ import { map, combineLatest, distinctUntilChanged, shareReplay, switchMap, of, first } from 'rxjs';
6
+ import { asObservable } from '@dereekb/rxjs';
7
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
8
+ import * as i1$1 from '@dereekb/dbx-core';
9
+ import { DbxInjectionComponent } from '@dereekb/dbx-core';
10
+ import { NgTemplateOutlet } from '@angular/common';
11
+ import * as i1 from '@dereekb/dbx-web';
12
+ import { DbxButtonModule, DbxWindowKeyDownListenerDirective, DbxActionModule } from '@dereekb/dbx-web';
13
+
14
+ /**
15
+ * NgRx ComponentStore that manages quiz lifecycle: question navigation, answer tracking,
16
+ * submission state, and navigation locking.
17
+ *
18
+ * Provided at the component level by `QuizComponent`.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // Access from a child component via DI:
23
+ * readonly quizStore = inject(QuizStore);
24
+ * this.quizStore.startQuiz();
25
+ * this.quizStore.updateAnswerForCurrentQuestion(5);
26
+ * ```
27
+ */
28
+ class QuizStore extends ComponentStore {
29
+ constructor() {
30
+ super({
31
+ quiz: undefined,
32
+ startedQuiz: false,
33
+ submittedQuiz: false,
34
+ answers: new Map(),
35
+ questionIndex: undefined,
36
+ unansweredQuestions: [],
37
+ completedQuestions: [],
38
+ questionMap: undefined,
39
+ autoAdvanceToNextQuestion: true,
40
+ allowVisitingPreviousQuestion: true,
41
+ allowSkipQuestion: false
42
+ });
43
+ }
44
+ quiz$ = this.select((state) => state.quiz);
45
+ titleDetails$ = this.quiz$.pipe(map((quiz) => quiz?.titleDetails));
46
+ questions$ = this.quiz$.pipe(map((quiz) => quiz?.questions ?? []));
47
+ startedQuiz$ = this.select((state) => state.startedQuiz);
48
+ lockQuizNavigation$ = this.select((state) => state.lockQuizNavigation);
49
+ submittedQuiz$ = this.select((state) => state.submittedQuiz);
50
+ answers$ = this.select((state) => state.answers);
51
+ questionIndex$ = this.select((state) => state.questionIndex ?? 0);
52
+ completedQuestions$ = this.select((state) => state.completedQuestions);
53
+ unansweredQuestions$ = this.select((state) => state.unansweredQuestions);
54
+ hasAnswerForEachQuestion$ = this.select((state) => state.unansweredQuestions.length === 0);
55
+ isAtEndOfQuestions$ = this.select((state) => (state.questionIndex ?? 0) >= (state.quiz?.questions.length ?? 0));
56
+ canGoToPreviousQuestion$ = this.select((state) => !state.lockQuizNavigation && state.allowVisitingPreviousQuestion && state.questionIndex != null && state.questionIndex > 0);
57
+ canGoToNextQuestion$ = this.select((state) => {
58
+ const newQuestionIndex = computeAdvanceIndexOnState(state, 1);
59
+ return !state.lockQuizNavigation && state.questionIndex != null && newQuestionIndex != null && newQuestionIndex > state.questionIndex;
60
+ });
61
+ currentQuestion$ = combineLatest([this.questions$, this.questionIndex$]).pipe(map(([questions, questionIndex]) => {
62
+ const question = questions[questionIndex];
63
+ let result;
64
+ if (question) {
65
+ result = {
66
+ ...question,
67
+ index: questionIndex
68
+ };
69
+ }
70
+ return result;
71
+ }), distinctUntilChanged(), shareReplay(1));
72
+ /**
73
+ * Returns a reactive observable of the answer for a given question, looked up by id, index, or the current question.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * // By current question:
78
+ * store.answerForQuestion({ currentIndex: true }).subscribe(answer => console.log(answer));
79
+ * // By question id:
80
+ * store.answerForQuestion({ id: 'q1' }).subscribe(answer => console.log(answer));
81
+ * ```
82
+ */
83
+ answerForQuestion(lookupInput) {
84
+ return asObservable(lookupInput).pipe(switchMap((lookup) => {
85
+ const { id, index, currentIndex } = lookup;
86
+ let result;
87
+ if (currentIndex) {
88
+ result = this.currentQuestion$.pipe(switchMap((question) => this.answerForQuestion({ index: question?.index })));
89
+ }
90
+ else if (id != null) {
91
+ result = this.answers$.pipe(map((answers) => answers.get(id)));
92
+ }
93
+ else if (index != null) {
94
+ result = this.questions$.pipe(switchMap((questions) => {
95
+ const question = questions[index];
96
+ let result;
97
+ if (question) {
98
+ result = this.answerForQuestion({ id: question.id });
99
+ }
100
+ else {
101
+ result = of(undefined);
102
+ }
103
+ return result;
104
+ }));
105
+ }
106
+ else {
107
+ result = of(undefined);
108
+ }
109
+ return result;
110
+ }), distinctUntilChanged(), shareReplay(1));
111
+ }
112
+ startQuiz = this.updater((state) => startQuizOnState(state));
113
+ setQuiz = this.updater((state, quiz) => setQuizOnState(state, quiz));
114
+ /**
115
+ * Resets the quiz entirely, back to the pre-quiz state.
116
+ */
117
+ resetQuiz = this.updater((state) => resetQuizOnState(state));
118
+ /**
119
+ * Restarts the quiz to the first question.
120
+ */
121
+ restartQuizToFirstQuestion = this.updater((state) => restartQuizToFirstQuestionOnState(state));
122
+ setAnswers = this.updater((state, answers) => setAnswersOnState(state, answers));
123
+ updateAnswers = this.updater((state, answers) => updateAnswersOnState(state, answers));
124
+ updateAnswerForCurrentQuestion = this.updater((state, answerData) => updateAnswerForCurrentQuestionOnState(state, answerData));
125
+ setQuestionIndex = this.updater((state, questionIndex) => ({ ...state, questionIndex }));
126
+ setAutoAdvanceToNextQuestion = this.updater((state, autoAdvanceToNextQuestion) => ({ ...state, autoAdvanceToNextQuestion }));
127
+ setAllowSkipQuestion = this.updater((state, allowSkipQuestion) => ({ ...state, allowSkipQuestion }));
128
+ setAllowVisitingPreviousQuestion = this.updater((state, allowVisitingPreviousQuestion) => ({ ...state, allowVisitingPreviousQuestion }));
129
+ goToNextQuestion = this.updater((state) => advanceQuestionOnState(state, 1));
130
+ goToPreviousQuestion = this.updater((state) => advanceQuestionOnState(state, -1));
131
+ setLockQuizNavigation = this.updater((state, lockQuizNavigation) => ({ ...state, lockQuizNavigation }));
132
+ setSubmittedQuiz = this.updater((state, submittedQuiz) => ({ ...state, submittedQuiz }));
133
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
134
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizStore });
135
+ }
136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizStore, decorators: [{
137
+ type: Injectable
138
+ }], ctorParameters: () => [] });
139
+ function computeAdvanceIndexOnState(state, advancement) {
140
+ const { questionIndex, allowSkipQuestion, unansweredQuestions, lockQuizNavigation } = state;
141
+ const maxQuestionIndex = state.quiz?.questions.length;
142
+ let newQuestionIndex;
143
+ if (maxQuestionIndex != null && questionIndex != null && !lockQuizNavigation) {
144
+ let maxAllowedIndex = maxQuestionIndex;
145
+ if (!allowSkipQuestion) {
146
+ maxAllowedIndex = unansweredQuestions[0]?.index ?? maxQuestionIndex;
147
+ }
148
+ newQuestionIndex = Math.max(0, Math.min(maxAllowedIndex, questionIndex + advancement));
149
+ }
150
+ return newQuestionIndex;
151
+ }
152
+ function advanceQuestionOnState(state, advancement) {
153
+ const newQuestionIndex = computeAdvanceIndexOnState(state, advancement);
154
+ let nextState = state;
155
+ if (newQuestionIndex != null) {
156
+ nextState = {
157
+ ...state,
158
+ questionIndex: newQuestionIndex
159
+ };
160
+ }
161
+ return nextState;
162
+ }
163
+ function startQuizOnState(state) {
164
+ const { startedQuiz } = state;
165
+ let nextState = state;
166
+ if (!startedQuiz) {
167
+ nextState = {
168
+ ...state,
169
+ startedQuiz: true,
170
+ submittedQuiz: false,
171
+ lockQuizNavigation: false,
172
+ questionIndex: 0
173
+ };
174
+ }
175
+ return nextState;
176
+ }
177
+ function resetQuizOnState(state) {
178
+ return {
179
+ ...restartQuizToFirstQuestionOnState(state),
180
+ startedQuiz: false
181
+ };
182
+ }
183
+ function restartQuizToFirstQuestionOnState(state) {
184
+ return setAnswersOnState(startQuizOnState({
185
+ ...state,
186
+ startedQuiz: false
187
+ }), []);
188
+ }
189
+ function setQuizOnState(state, quiz) {
190
+ let questionMap = undefined;
191
+ const currentAnswers = Array.from(state.answers.values());
192
+ if (quiz?.questions) {
193
+ questionMap = new Map(quiz.questions.map((question) => [question.id, question]));
194
+ }
195
+ return setAnswersOnState({ ...state, quiz, questionMap }, currentAnswers);
196
+ }
197
+ function setAnswersOnState(state, newAnswers) {
198
+ return updateAnswersOnState({
199
+ ...state,
200
+ answers: new Map()
201
+ }, newAnswers);
202
+ }
203
+ function updateAnswerForCurrentQuestionOnState(state, answerData) {
204
+ const { questionIndex } = state;
205
+ let nextState = state;
206
+ if (questionIndex != null) {
207
+ const currentQuestion = state.quiz?.questions[questionIndex];
208
+ if (currentQuestion) {
209
+ const answer = {
210
+ id: currentQuestion.id,
211
+ data: answerData
212
+ };
213
+ nextState = updateAnswersOnState(state, [answer]);
214
+ if (state.autoAdvanceToNextQuestion) {
215
+ nextState.questionIndex = questionIndex + 1;
216
+ }
217
+ }
218
+ }
219
+ return nextState;
220
+ }
221
+ function updateAnswersOnState(state, inputAnswers) {
222
+ const { quiz, answers: currentAnswers } = state;
223
+ const answers = new Map(currentAnswers);
224
+ asArray(inputAnswers).forEach((answer) => {
225
+ answers.set(answer.id, answer);
226
+ });
227
+ const completedQuestions = [];
228
+ const unansweredQuestions = [];
229
+ if (quiz?.questions) {
230
+ quiz.questions.forEach((question, index) => {
231
+ if (answers.has(question.id)) {
232
+ completedQuestions.push({ id: question.id, index });
233
+ }
234
+ else {
235
+ unansweredQuestions.push({ id: question.id, index });
236
+ }
237
+ });
238
+ }
239
+ return { ...state, unansweredQuestions, completedQuestions, answers };
240
+ }
241
+
242
+ /**
243
+ * Abstract accessor injected into answer/question child components to read the current question
244
+ * and write answers back to the QuizStore without coupling to it directly.
245
+ *
246
+ * Use `provideCurrentQuestionQuizQuestionAccessor()` to bind this to the store's current question.
247
+ */
248
+ class QuizQuestionAccessor {
249
+ }
250
+ /**
251
+ * Provides QuizQuestionAccessor bound to the current question in QuizStore.
252
+ *
253
+ * @usage
254
+ * ```typescript
255
+ * @Component({
256
+ * providers: [QuizStore, provideCurrentQuestionQuizQuestionAccessor()]
257
+ * })
258
+ * ```
259
+ */
260
+ function provideCurrentQuestionQuizQuestionAccessor() {
261
+ return {
262
+ provide: QuizQuestionAccessor,
263
+ useFactory: (quizStore) => {
264
+ return {
265
+ quiz$: quizStore.quiz$,
266
+ question$: quizStore.currentQuestion$,
267
+ answer$: quizStore.answerForQuestion({ currentIndex: true }),
268
+ setAnswer: (answer) => quizStore.updateAnswerForCurrentQuestion(answer),
269
+ setAnswerSource: (answer) => quizStore.updateAnswerForCurrentQuestion(answer)
270
+ };
271
+ },
272
+ deps: [QuizStore]
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Top-level quiz container that orchestrates pre-quiz, active quiz, and post-quiz views.
278
+ *
279
+ * Provides its own `QuizStore` and `QuizQuestionAccessor`, so child components injected via
280
+ * `DbxInjectionComponent` can access quiz state directly through DI.
281
+ *
282
+ * Supports keyboard navigation: Enter (start / next), ArrowLeft (previous), ArrowRight (next).
283
+ *
284
+ * @example
285
+ * ```html
286
+ * <dbx-quiz [quiz]="myQuiz"></dbx-quiz>
287
+ * ```
288
+ */
289
+ class QuizComponent {
290
+ quizStore = inject(QuizStore);
291
+ quiz = input.required(...(ngDevMode ? [{ debugName: "quiz" }] : []));
292
+ keysFilter = ['Enter', 'ArrowLeft', 'ArrowRight'];
293
+ quizEffect = effect(() => {
294
+ const quiz = this.quiz();
295
+ this.quizStore.setQuiz(quiz);
296
+ }, { ...(ngDevMode ? { debugName: "quizEffect" } : {}), allowSignalWrites: true });
297
+ quiz$ = toObservable(this.quiz);
298
+ quizTitleSignal = computed(() => this.quiz()?.titleDetails.title, ...(ngDevMode ? [{ debugName: "quizTitleSignal" }] : []));
299
+ currentQuestionSignal = toSignal(this.quizStore.currentQuestion$);
300
+ questionTitleSignal = computed(() => {
301
+ const currentQuestion = this.currentQuestionSignal();
302
+ return currentQuestion ? `Question ${currentQuestion.index + 1}` : '';
303
+ }, ...(ngDevMode ? [{ debugName: "questionTitleSignal" }] : []));
304
+ startedQuiz$ = this.quizStore.startedQuiz$;
305
+ currentQuestion$ = this.quizStore.currentQuestion$;
306
+ canGoToPreviousQuestionSignal = toSignal(this.quizStore.canGoToPreviousQuestion$, { initialValue: false });
307
+ canGoToNextQuestionSignal = toSignal(this.quizStore.canGoToNextQuestion$, { initialValue: false });
308
+ viewConfig$ = this.startedQuiz$.pipe(switchMap((started) => {
309
+ if (!started) {
310
+ return this.quiz$.pipe(map((quiz) => {
311
+ const viewConfig = {
312
+ state: 'pre-quiz',
313
+ preQuizComponent: quiz?.preQuizComponentConfig
314
+ };
315
+ return viewConfig;
316
+ }));
317
+ }
318
+ else {
319
+ return combineLatest([this.quiz$, this.currentQuestion$, this.quizStore.isAtEndOfQuestions$]).pipe(map(([quiz, currentQuestion, isAtEndOfQuestions]) => {
320
+ let viewConfig;
321
+ if (isAtEndOfQuestions) {
322
+ viewConfig = {
323
+ state: 'post-quiz',
324
+ resultsComponent: quiz?.resultsComponentConfig
325
+ };
326
+ }
327
+ else {
328
+ viewConfig = {
329
+ state: 'quiz',
330
+ questionComponent: currentQuestion?.questionComponentConfig,
331
+ answerComponent: currentQuestion?.answerComponentConfig
332
+ };
333
+ }
334
+ return viewConfig;
335
+ }));
336
+ }
337
+ }));
338
+ viewConfigSignal = toSignal(this.viewConfig$, { initialValue: { state: 'init' } });
339
+ viewStateSignal = computed(() => this.viewConfigSignal()?.state ?? 'init', ...(ngDevMode ? [{ debugName: "viewStateSignal" }] : []));
340
+ preQuizComponentConfigSignal = computed(() => this.viewConfigSignal()?.preQuizComponent, ...(ngDevMode ? [{ debugName: "preQuizComponentConfigSignal" }] : []));
341
+ questionComponentConfigSignal = computed(() => this.viewConfigSignal()?.questionComponent, ...(ngDevMode ? [{ debugName: "questionComponentConfigSignal" }] : []));
342
+ answerComponentConfigSignal = computed(() => this.viewConfigSignal()?.answerComponent, ...(ngDevMode ? [{ debugName: "answerComponentConfigSignal" }] : []));
343
+ resultsComponentConfigSignal = computed(() => this.viewConfigSignal()?.resultsComponent, ...(ngDevMode ? [{ debugName: "resultsComponentConfigSignal" }] : []));
344
+ handleKeyDown(event) {
345
+ const code = event.code;
346
+ switch (code) {
347
+ case 'Enter':
348
+ this.quizStore.startedQuiz$.pipe(first()).subscribe((started) => {
349
+ if (!started) {
350
+ this.quizStore.startQuiz();
351
+ }
352
+ else {
353
+ this.clickNextQuestion();
354
+ }
355
+ });
356
+ break;
357
+ case 'ArrowLeft':
358
+ this.clickPreviousQuestion();
359
+ break;
360
+ case 'ArrowRight':
361
+ this.clickNextQuestion();
362
+ break;
363
+ }
364
+ }
365
+ clickPreviousQuestion() {
366
+ this.quizStore.goToPreviousQuestion();
367
+ }
368
+ clickNextQuestion() {
369
+ this.quizStore.goToNextQuestion();
370
+ }
371
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
372
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: QuizComponent, isStandalone: true, selector: "dbx-quiz", inputs: { quiz: { classPropertyName: "quiz", publicName: "quiz", isSignal: true, isRequired: true, transformFunction: null } }, providers: [QuizStore, provideCurrentQuestionQuizQuestionAccessor()], ngImport: i0, template: "<div class=\"dbx-quiz\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"keysFilter\">\n @switch (viewStateSignal()) {\n @case ('pre-quiz') {\n <ng-container *ngTemplateOutlet=\"preQuizTemplate\"></ng-container>\n }\n @case ('quiz') {\n <ng-container *ngTemplateOutlet=\"quizTemplate\"></ng-container>\n }\n @case ('post-quiz') {\n <ng-container *ngTemplateOutlet=\"postQuizTemplate\"></ng-container>\n }\n }\n</div>\n\n<!-- Pre-Quiz -->\n<ng-template #preQuizTemplate>\n <div class=\"dbx-quiz-pre-quiz\">\n <dbx-injection [config]=\"preQuizComponentConfigSignal()\"></dbx-injection>\n </div>\n</ng-template>\n\n<!-- Quiz -->\n<ng-template #quizTemplate>\n <div class=\"dbx-quiz-quiz\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n <div class=\"dbx-quiz-question dbx-pb3\">\n <h4 class=\"dbx-quiz-question-title\">{{ questionTitleSignal() }}</h4>\n <dbx-injection [config]=\"questionComponentConfigSignal()\"></dbx-injection>\n </div>\n <div class=\"dbx-quiz-answer dbx-pt3\">\n <dbx-injection [config]=\"answerComponentConfigSignal()\"></dbx-injection>\n </div>\n </div>\n</ng-template>\n\n<!-- Post-Quiz -->\n<ng-template #postQuizTemplate>\n <div class=\"dbx-quiz-post-quiz\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n <dbx-injection [config]=\"resultsComponentConfigSignal()\"></dbx-injection>\n </div>\n</ng-template>\n\n<!-- Header Template -->\n<ng-template #headerTemplate>\n <div class=\"dbx-quiz-header\">\n <div class=\"dbx-flex-group\">\n <dbx-button [disabled]=\"!canGoToPreviousQuestionSignal()\" icon=\"arrow_back\" (buttonClick)=\"clickPreviousQuestion()\"></dbx-button>\n <span class=\"spacer\"></span>\n <span class=\"mat-h2\">{{ quizTitleSignal() }}</span>\n <span class=\"spacer\"></span>\n <dbx-button [disabled]=\"!canGoToNextQuestionSignal()\" icon=\"arrow_forward\" (buttonClick)=\"clickNextQuestion()\"></dbx-button>\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxWindowKeyDownListenerDirective, selector: "[dbxWindowKeyDownListener]", inputs: ["dbxWindowKeyDownEnabled", "dbxWindowKeyDownFilter"], outputs: ["dbxWindowKeyDownListener"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
373
+ }
374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizComponent, decorators: [{
375
+ type: Component,
376
+ args: [{ selector: 'dbx-quiz', imports: [DbxInjectionComponent, DbxButtonModule, DbxWindowKeyDownListenerDirective, NgTemplateOutlet], providers: [QuizStore, provideCurrentQuestionQuizQuestionAccessor()], standalone: true, template: "<div class=\"dbx-quiz\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"keysFilter\">\n @switch (viewStateSignal()) {\n @case ('pre-quiz') {\n <ng-container *ngTemplateOutlet=\"preQuizTemplate\"></ng-container>\n }\n @case ('quiz') {\n <ng-container *ngTemplateOutlet=\"quizTemplate\"></ng-container>\n }\n @case ('post-quiz') {\n <ng-container *ngTemplateOutlet=\"postQuizTemplate\"></ng-container>\n }\n }\n</div>\n\n<!-- Pre-Quiz -->\n<ng-template #preQuizTemplate>\n <div class=\"dbx-quiz-pre-quiz\">\n <dbx-injection [config]=\"preQuizComponentConfigSignal()\"></dbx-injection>\n </div>\n</ng-template>\n\n<!-- Quiz -->\n<ng-template #quizTemplate>\n <div class=\"dbx-quiz-quiz\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n <div class=\"dbx-quiz-question dbx-pb3\">\n <h4 class=\"dbx-quiz-question-title\">{{ questionTitleSignal() }}</h4>\n <dbx-injection [config]=\"questionComponentConfigSignal()\"></dbx-injection>\n </div>\n <div class=\"dbx-quiz-answer dbx-pt3\">\n <dbx-injection [config]=\"answerComponentConfigSignal()\"></dbx-injection>\n </div>\n </div>\n</ng-template>\n\n<!-- Post-Quiz -->\n<ng-template #postQuizTemplate>\n <div class=\"dbx-quiz-post-quiz\">\n <ng-container *ngTemplateOutlet=\"headerTemplate\"></ng-container>\n <dbx-injection [config]=\"resultsComponentConfigSignal()\"></dbx-injection>\n </div>\n</ng-template>\n\n<!-- Header Template -->\n<ng-template #headerTemplate>\n <div class=\"dbx-quiz-header\">\n <div class=\"dbx-flex-group\">\n <dbx-button [disabled]=\"!canGoToPreviousQuestionSignal()\" icon=\"arrow_back\" (buttonClick)=\"clickPreviousQuestion()\"></dbx-button>\n <span class=\"spacer\"></span>\n <span class=\"mat-h2\">{{ quizTitleSignal() }}</span>\n <span class=\"spacer\"></span>\n <dbx-button [disabled]=\"!canGoToNextQuestionSignal()\" icon=\"arrow_forward\" (buttonClick)=\"clickNextQuestion()\"></dbx-button>\n </div>\n </div>\n</ng-template>\n" }]
377
+ }], propDecorators: { quiz: [{ type: i0.Input, args: [{ isSignal: true, alias: "quiz", required: true }] }] } });
378
+
379
+ /**
380
+ * Answer component that displays configurable number buttons.
381
+ *
382
+ * @usage
383
+ * Used as an answer component in a QuizQuestion's answerComponentConfig.
384
+ * Defaults to 1-5 range if no config is provided.
385
+ *
386
+ * ```typescript
387
+ * answerComponentConfig: {
388
+ * componentClass: QuizAnswerNumberComponent,
389
+ * init: (instance: QuizAnswerNumberComponent) => {
390
+ * instance.config.set({ range: { start: 1, end: 11 } });
391
+ * }
392
+ * }
393
+ * ```
394
+ */
395
+ class QuizAnswerNumberComponent {
396
+ questionAccessor = inject(QuizQuestionAccessor);
397
+ config = model(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
398
+ currentAnswerSignal = toSignal(this.questionAccessor.answer$);
399
+ currentAnswerValueSignal = computed(() => this.currentAnswerSignal()?.data, ...(ngDevMode ? [{ debugName: "currentAnswerValueSignal" }] : []));
400
+ choicesSignal = computed(() => {
401
+ const { range: inputRange, numbers: inputNumbers, preset } = this.config() ?? { preset: 'oneToFive' };
402
+ const currentAnswer = this.currentAnswerValueSignal();
403
+ let useRange;
404
+ let useNumbers;
405
+ if (preset) {
406
+ switch (preset) {
407
+ case 'oneToFive':
408
+ default:
409
+ useRange = { start: 1, end: 6 };
410
+ break;
411
+ }
412
+ }
413
+ else if (inputRange) {
414
+ useRange = inputRange;
415
+ }
416
+ else if (inputNumbers) {
417
+ useNumbers = inputNumbers;
418
+ }
419
+ let numbers;
420
+ if (useRange) {
421
+ numbers = range(useRange);
422
+ }
423
+ else if (useNumbers) {
424
+ numbers = useNumbers ?? [];
425
+ }
426
+ else {
427
+ numbers = [];
428
+ }
429
+ const choices = numbers.map((number) => {
430
+ return {
431
+ number,
432
+ selected: currentAnswer === number
433
+ };
434
+ });
435
+ return choices;
436
+ }, ...(ngDevMode ? [{ debugName: "choicesSignal" }] : []));
437
+ relevantKeysSignal = computed(() => {
438
+ const choices = this.choicesSignal();
439
+ return choices.map((choice) => choice.number.toString());
440
+ }, ...(ngDevMode ? [{ debugName: "relevantKeysSignal" }] : []));
441
+ clickedAnswer(answer) {
442
+ this.questionAccessor.setAnswer(answer);
443
+ }
444
+ handleKeyDown(event) {
445
+ const number = Number(event.key);
446
+ if (!isNaN(number)) {
447
+ this.clickedAnswer(number);
448
+ }
449
+ }
450
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizAnswerNumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
451
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: QuizAnswerNumberComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { config: "configChange" }, ngImport: i0, template: "<div class=\"dbx-quiz-answer-number\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"relevantKeysSignal()\">\n <div class=\"dbx-quiz-answer-number-buttons dbx-button-wrap-group\">\n @for (choice of choicesSignal(); track choice.number) {\n <dbx-button [color]=\"choice.selected ? 'accent' : 'primary'\" [raised]=\"true\" (buttonClick)=\"clickedAnswer(choice.number)\">{{ choice.number }}</dbx-button>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxWindowKeyDownListenerDirective, selector: "[dbxWindowKeyDownListener]", inputs: ["dbxWindowKeyDownEnabled", "dbxWindowKeyDownFilter"], outputs: ["dbxWindowKeyDownListener"] }] });
452
+ }
453
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizAnswerNumberComponent, decorators: [{
454
+ type: Component,
455
+ args: [{ imports: [DbxButtonModule, DbxWindowKeyDownListenerDirective], standalone: true, template: "<div class=\"dbx-quiz-answer-number\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"relevantKeysSignal()\">\n <div class=\"dbx-quiz-answer-number-buttons dbx-button-wrap-group\">\n @for (choice of choicesSignal(); track choice.number) {\n <dbx-button [color]=\"choice.selected ? 'accent' : 'primary'\" [raised]=\"true\" (buttonClick)=\"clickedAnswer(choice.number)\">{{ choice.number }}</dbx-button>\n }\n </div>\n</div>\n" }]
456
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }, { type: i0.Output, args: ["configChange"] }] } });
457
+
458
+ const MULTIPLE_CHOICE_LETTERS = `abcdefghijklmnopqrstuvwxyz`;
459
+ /**
460
+ * Answer component that displays multiple choice letter-labeled buttons.
461
+ *
462
+ * @usage
463
+ * Used as an answer component in a QuizQuestion's answerComponentConfig.
464
+ * Supports keyboard shortcuts (pressing the letter key selects that answer).
465
+ *
466
+ * ```typescript
467
+ * answerComponentConfig: {
468
+ * componentClass: QuizAnswerMultipleChoiceComponent,
469
+ * init: (instance: QuizAnswerMultipleChoiceComponent) => {
470
+ * instance.config.set({
471
+ * answerText: ['Option A', 'Option B', 'Option C'],
472
+ * correctAnswerIndex: 1
473
+ * });
474
+ * }
475
+ * }
476
+ * ```
477
+ */
478
+ class QuizAnswerMultipleChoiceComponent {
479
+ questionAccessor = inject(QuizQuestionAccessor);
480
+ config = model(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
481
+ currentAnswerSignal = toSignal(this.questionAccessor.answer$);
482
+ currentAnswerValueSignal = computed(() => this.currentAnswerSignal()?.data, ...(ngDevMode ? [{ debugName: "currentAnswerValueSignal" }] : []));
483
+ choicesSignal = computed(() => {
484
+ const config = this.config();
485
+ const currentAnswer = this.currentAnswerValueSignal();
486
+ const answers = config?.answerText ?? [];
487
+ const correctAnswerIndex = config?.correctAnswerIndex;
488
+ const choices = answers.map((text, i) => {
489
+ const letter = MULTIPLE_CHOICE_LETTERS[i];
490
+ return {
491
+ letter: MULTIPLE_CHOICE_LETTERS[i].toUpperCase(),
492
+ text,
493
+ selected: currentAnswer?.letter === letter,
494
+ isCorrectAnswer: correctAnswerIndex === i
495
+ };
496
+ });
497
+ return choices;
498
+ }, ...(ngDevMode ? [{ debugName: "choicesSignal" }] : []));
499
+ relevantKeysSignal = computed(() => {
500
+ const answersCount = this.config()?.answerText.length ?? 0;
501
+ const relevantKeys = [];
502
+ const numbersRange = answersCount > 0 ? range(1, answersCount + 1) : [];
503
+ for (const number of numbersRange) {
504
+ const answerLetter = MULTIPLE_CHOICE_LETTERS[number - 1];
505
+ relevantKeys.push(answerLetter);
506
+ }
507
+ return relevantKeys;
508
+ }, ...(ngDevMode ? [{ debugName: "relevantKeysSignal" }] : []));
509
+ clickedAnswer(answer) {
510
+ this.questionAccessor.setAnswer(answer);
511
+ }
512
+ handleKeyDown(event) {
513
+ if (event.key.length === 1) {
514
+ const choices = this.choicesSignal();
515
+ const selectedLetter = event.key.toUpperCase();
516
+ const choice = choices.find((x) => x.letter === selectedLetter);
517
+ if (choice) {
518
+ this.clickedAnswer(choice);
519
+ }
520
+ }
521
+ }
522
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizAnswerMultipleChoiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
523
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: QuizAnswerMultipleChoiceComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { config: "configChange" }, ngImport: i0, template: "<div class=\"dbx-quiz-answer-multiplechoice\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"relevantKeysSignal()\">\n <div class=\"dbx-quiz-answer-multiplechoice-buttons dbx-button-column dbx-button-wide\">\n @for (choice of choicesSignal(); track choice.letter) {\n <dbx-button class=\"dbx-w100\" [color]=\"choice.selected ? 'accent' : 'primary'\" [raised]=\"true\" (buttonClick)=\"clickedAnswer(choice)\">\n <div class=\"dbx-quiz-answer-multiplechoice-button-content\">\n <span class=\"dbx-quiz-answer-multiplechoice-button-letter\">{{ choice.letter }})</span>\n <span class=\"dbx-quiz-answer-multiplechoice-button-text\">{{ choice.text }}</span>\n </div>\n </dbx-button>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxWindowKeyDownListenerDirective, selector: "[dbxWindowKeyDownListener]", inputs: ["dbxWindowKeyDownEnabled", "dbxWindowKeyDownFilter"], outputs: ["dbxWindowKeyDownListener"] }] });
524
+ }
525
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizAnswerMultipleChoiceComponent, decorators: [{
526
+ type: Component,
527
+ args: [{ imports: [DbxButtonModule, DbxWindowKeyDownListenerDirective], standalone: true, template: "<div class=\"dbx-quiz-answer-multiplechoice\" (dbxWindowKeyDownListener)=\"handleKeyDown($event)\" [dbxWindowKeyDownFilter]=\"relevantKeysSignal()\">\n <div class=\"dbx-quiz-answer-multiplechoice-buttons dbx-button-column dbx-button-wide\">\n @for (choice of choicesSignal(); track choice.letter) {\n <dbx-button class=\"dbx-w100\" [color]=\"choice.selected ? 'accent' : 'primary'\" [raised]=\"true\" (buttonClick)=\"clickedAnswer(choice)\">\n <div class=\"dbx-quiz-answer-multiplechoice-button-content\">\n <span class=\"dbx-quiz-answer-multiplechoice-button-letter\">{{ choice.letter }})</span>\n <span class=\"dbx-quiz-answer-multiplechoice-button-text\">{{ choice.text }}</span>\n </div>\n </dbx-button>\n }\n </div>\n</div>\n" }]
528
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }, { type: i0.Output, args: ["configChange"] }] } });
529
+
530
+ /**
531
+ * Pre-quiz intro component that displays the quiz title, subtitle, description, and a start button.
532
+ *
533
+ * @usage
534
+ * Used as a preQuizComponentConfig in a Quiz definition.
535
+ * Inherits title details from the quiz unless overridden via config.
536
+ *
537
+ * ```typescript
538
+ * preQuizComponentConfig: {
539
+ * componentClass: QuizPreQuizIntroComponent,
540
+ * init: (instance: QuizPreQuizIntroComponent) => {
541
+ * instance.config.set({ subtitle: 'Custom subtitle' });
542
+ * }
543
+ * }
544
+ * ```
545
+ */
546
+ class QuizPreQuizIntroComponent {
547
+ quizStore = inject(QuizStore);
548
+ config = model(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
549
+ quizTitleDetailsSignal = toSignal(this.quizStore.titleDetails$);
550
+ configSignal = computed(() => {
551
+ const config = this.config();
552
+ const titleDetails = this.quizTitleDetailsSignal();
553
+ return {
554
+ title: config?.title ?? titleDetails?.title,
555
+ subtitle: config?.subtitle ?? titleDetails?.subtitle,
556
+ description: config?.description ?? titleDetails?.description
557
+ };
558
+ }, ...(ngDevMode ? [{ debugName: "configSignal" }] : []));
559
+ titleSignal = computed(() => this.configSignal()?.title, ...(ngDevMode ? [{ debugName: "titleSignal" }] : []));
560
+ subtitleSignal = computed(() => this.configSignal()?.subtitle, ...(ngDevMode ? [{ debugName: "subtitleSignal" }] : []));
561
+ descriptionSignal = computed(() => this.configSignal()?.description, ...(ngDevMode ? [{ debugName: "descriptionSignal" }] : []));
562
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizPreQuizIntroComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
563
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: QuizPreQuizIntroComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { config: "configChange" }, ngImport: i0, template: "<div>\n <div>\n <h1>{{ titleSignal() }}</h1>\n <h2>{{ subtitleSignal() }}</h2>\n <p>{{ descriptionSignal() }}</p>\n </div>\n <div>\n <dbx-button [raised]=\"true\" (buttonClick)=\"quizStore.startQuiz()\" text=\"Start Quiz\"></dbx-button>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: DbxButtonModule }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }] });
564
+ }
565
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizPreQuizIntroComponent, decorators: [{
566
+ type: Component,
567
+ args: [{ imports: [DbxButtonModule], standalone: true, template: "<div>\n <div>\n <h1>{{ titleSignal() }}</h1>\n <h2>{{ subtitleSignal() }}</h2>\n <p>{{ descriptionSignal() }}</p>\n </div>\n <div>\n <dbx-button [raised]=\"true\" (buttonClick)=\"quizStore.startQuiz()\" text=\"Start Quiz\"></dbx-button>\n </div>\n</div>\n" }]
568
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }, { type: i0.Output, args: ["configChange"] }] } });
569
+
570
+ /**
571
+ * Question component that displays text with optional prompt and guidance.
572
+ *
573
+ * @usage
574
+ * Used as a questionComponentConfig in a QuizQuestion definition.
575
+ *
576
+ * ```typescript
577
+ * questionComponentConfig: {
578
+ * componentClass: QuizQuestionTextComponent,
579
+ * init: (instance: QuizQuestionTextComponent) => {
580
+ * instance.config.set({ text: 'How do you handle ambiguity?', prompt: 'Rate yourself:', guidance: '1=Never, 5=Always' });
581
+ * }
582
+ * }
583
+ * ```
584
+ */
585
+ class QuizQuestionTextComponent {
586
+ config = model(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
587
+ promptSignal = computed(() => this.config()?.prompt, ...(ngDevMode ? [{ debugName: "promptSignal" }] : []));
588
+ textSignal = computed(() => this.config()?.text, ...(ngDevMode ? [{ debugName: "textSignal" }] : []));
589
+ guidanceSignal = computed(() => this.config()?.guidance, ...(ngDevMode ? [{ debugName: "guidanceSignal" }] : []));
590
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizQuestionTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
591
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: QuizQuestionTextComponent, isStandalone: true, selector: "ng-component", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { config: "configChange" }, ngImport: i0, template: "<div class=\"dbx-quiz-question-text\">\n <div class=\"dbx-quiz-question-text-content\">\n @if (promptSignal() !== null) {\n <div class=\"dbx-hint dbx-pb3\">{{ promptSignal() }}</div>\n }\n <div class=\"dbx-pb3\">{{ textSignal() }}</div>\n @if (guidanceSignal() !== null) {\n <div class=\"dbx-small dbx-hint\">{{ guidanceSignal() }}</div>\n }\n </div>\n</div>\n" });
592
+ }
593
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: QuizQuestionTextComponent, decorators: [{
594
+ type: Component,
595
+ args: [{ standalone: true, template: "<div class=\"dbx-quiz-question-text\">\n <div class=\"dbx-quiz-question-text-content\">\n @if (promptSignal() !== null) {\n <div class=\"dbx-hint dbx-pb3\">{{ promptSignal() }}</div>\n }\n <div class=\"dbx-pb3\">{{ textSignal() }}</div>\n @if (guidanceSignal() !== null) {\n <div class=\"dbx-small dbx-hint\">{{ guidanceSignal() }}</div>\n }\n </div>\n</div>\n" }]
596
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }, { type: i0.Output, args: ["configChange"] }] } });
597
+
598
+ /**
599
+ * Post-quiz component that handles quiz submission and displays pre/post submit content.
600
+ *
601
+ * @usage
602
+ * Use as a wrapper in your results component template:
603
+ *
604
+ * ```html
605
+ * <dbx-quiz-post-quiz [handleSubmitQuiz]="handleSubmitQuiz">
606
+ * <div presubmit>Pre-submit content...</div>
607
+ * <div postsubmit>Post-submit content (scores, etc.)...</div>
608
+ * </dbx-quiz-post-quiz>
609
+ * ```
610
+ */
611
+ class DbxQuizPostQuizComponent {
612
+ quizStore = inject(QuizStore);
613
+ quizSubmittedSignal = toSignal(this.quizStore.submittedQuiz$);
614
+ stateSignal = computed(() => {
615
+ const submitted = this.quizSubmittedSignal();
616
+ if (submitted) {
617
+ return 'postsubmit';
618
+ }
619
+ else {
620
+ return 'presubmit';
621
+ }
622
+ }, ...(ngDevMode ? [{ debugName: "stateSignal" }] : []));
623
+ handleSubmitQuiz = input(...(ngDevMode ? [undefined, { debugName: "handleSubmitQuiz" }] : []));
624
+ handleSubmitQuizButton = (_, context) => {
625
+ this.quizStore.setLockQuizNavigation(true);
626
+ const handler = this.handleSubmitQuiz();
627
+ if (handler) {
628
+ return handler(_, context);
629
+ }
630
+ else {
631
+ context.reject();
632
+ }
633
+ };
634
+ handleSubmitQuizSuccess = () => {
635
+ this.quizStore.setSubmittedQuiz(true);
636
+ };
637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizPostQuizComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
638
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxQuizPostQuizComponent, isStandalone: true, selector: "dbx-quiz-post-quiz", inputs: { handleSubmitQuiz: { classPropertyName: "handleSubmitQuiz", publicName: "handleSubmitQuiz", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"dbx-post-quiz text-center\">\n <h3>Quiz Completed</h3>\n <div>\n <ng-container *ngTemplateOutlet=\"contentTemplate\"></ng-container>\n @if (stateSignal() === 'presubmit') {\n <ng-container *ngTemplateOutlet=\"preSubmitTemplate\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"postSubmitTemplate\"></ng-container>\n }\n </div>\n</div>\n\n<!-- Pre-Submit -->\n<ng-template #preSubmitTemplate>\n <ng-content select=\"[presubmit]\"></ng-content>\n @if (handleSubmitQuiz()) {\n <div class=\"dbx-post-quiz-submit\">\n <div dbxAction dbxActionLogger dbxActionValue dbxActionSnackbarError [dbxActionHandler]=\"handleSubmitQuizButton\" [dbxActionSuccessHandler]=\"handleSubmitQuizSuccess\">\n <dbx-button [disabled]=\"quizSubmittedSignal()\" [raised]=\"true\" dbxActionButton>Submit Quiz</dbx-button>\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Post-Submit -->\n<ng-template #postSubmitTemplate>\n <ng-content select=\"[postsubmit]\"></ng-content>\n</ng-template>\n\n<!-- Content -->\n<ng-template #contentTemplate>\n <ng-content></ng-content>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: DbxButtonModule }, { kind: "directive", type: i1$1.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i1$1.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i1$1.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i1$1.DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "directive", type: i1$1.DbxActionContextLoggerDirective, selector: "[dbxActionLogger],[dbxActionContextLogger]" }, { kind: "directive", type: i1$1.DbxActionSuccessHandlerDirective, selector: "[dbxActionSuccessHandler]", inputs: ["dbxActionSuccessHandler"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
639
+ }
640
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizPostQuizComponent, decorators: [{
641
+ type: Component,
642
+ args: [{ selector: 'dbx-quiz-post-quiz', imports: [DbxButtonModule, DbxActionModule, NgTemplateOutlet], standalone: true, template: "<div class=\"dbx-post-quiz text-center\">\n <h3>Quiz Completed</h3>\n <div>\n <ng-container *ngTemplateOutlet=\"contentTemplate\"></ng-container>\n @if (stateSignal() === 'presubmit') {\n <ng-container *ngTemplateOutlet=\"preSubmitTemplate\"></ng-container>\n } @else {\n <ng-container *ngTemplateOutlet=\"postSubmitTemplate\"></ng-container>\n }\n </div>\n</div>\n\n<!-- Pre-Submit -->\n<ng-template #preSubmitTemplate>\n <ng-content select=\"[presubmit]\"></ng-content>\n @if (handleSubmitQuiz()) {\n <div class=\"dbx-post-quiz-submit\">\n <div dbxAction dbxActionLogger dbxActionValue dbxActionSnackbarError [dbxActionHandler]=\"handleSubmitQuizButton\" [dbxActionSuccessHandler]=\"handleSubmitQuizSuccess\">\n <dbx-button [disabled]=\"quizSubmittedSignal()\" [raised]=\"true\" dbxActionButton>Submit Quiz</dbx-button>\n </div>\n </div>\n }\n</ng-template>\n\n<!-- Post-Submit -->\n<ng-template #postSubmitTemplate>\n <ng-content select=\"[postsubmit]\"></ng-content>\n</ng-template>\n\n<!-- Content -->\n<ng-template #contentTemplate>\n <ng-content></ng-content>\n</ng-template>\n" }]
643
+ }], propDecorators: { handleSubmitQuiz: [{ type: i0.Input, args: [{ isSignal: true, alias: "handleSubmitQuiz", required: false }] }] } });
644
+
645
+ /**
646
+ * Button component that restarts the quiz to the first question.
647
+ *
648
+ * @usage
649
+ * ```html
650
+ * <dbx-quiz-reset-button buttonText="Try Again"></dbx-quiz-reset-button>
651
+ * ```
652
+ */
653
+ class DbxQuizResetButtonComponent {
654
+ quizStore = inject(QuizStore);
655
+ buttonText = input(`Restart Quiz`, ...(ngDevMode ? [{ debugName: "buttonText" }] : []));
656
+ handleResetQuizButton = (_, context) => {
657
+ this.quizStore.restartQuizToFirstQuestion();
658
+ context.success();
659
+ };
660
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizResetButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
661
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: DbxQuizResetButtonComponent, isStandalone: true, selector: "dbx-quiz-reset-button", inputs: { buttonText: { classPropertyName: "buttonText", publicName: "buttonText", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
662
+ <div class="dbx-quiz-reset-button">
663
+ <div dbxAction dbxActionLogger dbxActionValue dbxActionSnackbarError [dbxActionHandler]="handleResetQuizButton">
664
+ <dbx-button [raised]="true" [text]="buttonText()" dbxActionButton></dbx-button>
665
+ </div>
666
+ </div>
667
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: DbxButtonModule }, { kind: "directive", type: i1$1.DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: i1.DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "ngmodule", type: DbxActionModule }, { kind: "directive", type: i1$1.DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: i1$1.DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: i1$1.DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "directive", type: i1$1.DbxActionContextLoggerDirective, selector: "[dbxActionLogger],[dbxActionContextLogger]" }] });
668
+ }
669
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizResetButtonComponent, decorators: [{
670
+ type: Component,
671
+ args: [{
672
+ selector: 'dbx-quiz-reset-button',
673
+ template: `
674
+ <div class="dbx-quiz-reset-button">
675
+ <div dbxAction dbxActionLogger dbxActionValue dbxActionSnackbarError [dbxActionHandler]="handleResetQuizButton">
676
+ <dbx-button [raised]="true" [text]="buttonText()" dbxActionButton></dbx-button>
677
+ </div>
678
+ </div>
679
+ `,
680
+ imports: [DbxButtonModule, DbxActionModule],
681
+ standalone: true
682
+ }]
683
+ }], propDecorators: { buttonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonText", required: false }] }] } });
684
+
685
+ /**
686
+ * Generic quiz score display component.
687
+ *
688
+ * @usage
689
+ * ```html
690
+ * <dbx-quiz-score [input]="scoreInput"></dbx-quiz-score>
691
+ * ```
692
+ */
693
+ class DbxQuizScoreComponent {
694
+ input = input(...(ngDevMode ? [undefined, { debugName: "input" }] : []));
695
+ scoreSignal = computed(() => this.input()?.score, ...(ngDevMode ? [{ debugName: "scoreSignal" }] : []));
696
+ maxScoreSignal = computed(() => this.input()?.maxScore, ...(ngDevMode ? [{ debugName: "maxScoreSignal" }] : []));
697
+ feedbackTextSignal = computed(() => this.input()?.feedbackText ?? '', ...(ngDevMode ? [{ debugName: "feedbackTextSignal" }] : []));
698
+ subtitleSignal = computed(() => this.input()?.subtitle, ...(ngDevMode ? [{ debugName: "subtitleSignal" }] : []));
699
+ showRetakeButtonSignal = computed(() => this.input()?.showRetakeButton, ...(ngDevMode ? [{ debugName: "showRetakeButtonSignal" }] : []));
700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizScoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
701
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxQuizScoreComponent, isStandalone: true, selector: "dbx-quiz-score", inputs: { input: { classPropertyName: "input", publicName: "input", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
702
+ <div class="dbx-quiz-score">
703
+ <h3 class="dbx-quiz-score-score">{{ scoreSignal() }}/{{ maxScoreSignal() }}</h3>
704
+ <p class="dbx-quiz-score-text">{{ feedbackTextSignal() }}</p>
705
+ @if (subtitleSignal()) {
706
+ <p class="dbx-quiz-score-subtitle">{{ subtitleSignal() }}</p>
707
+ }
708
+ @if (showRetakeButtonSignal()) {
709
+ <dbx-quiz-reset-button buttonText="Retake Quiz"></dbx-quiz-reset-button>
710
+ }
711
+ </div>
712
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxQuizResetButtonComponent, selector: "dbx-quiz-reset-button", inputs: ["buttonText"] }] });
713
+ }
714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxQuizScoreComponent, decorators: [{
715
+ type: Component,
716
+ args: [{
717
+ selector: 'dbx-quiz-score',
718
+ template: `
719
+ <div class="dbx-quiz-score">
720
+ <h3 class="dbx-quiz-score-score">{{ scoreSignal() }}/{{ maxScoreSignal() }}</h3>
721
+ <p class="dbx-quiz-score-text">{{ feedbackTextSignal() }}</p>
722
+ @if (subtitleSignal()) {
723
+ <p class="dbx-quiz-score-subtitle">{{ subtitleSignal() }}</p>
724
+ }
725
+ @if (showRetakeButtonSignal()) {
726
+ <dbx-quiz-reset-button buttonText="Retake Quiz"></dbx-quiz-reset-button>
727
+ }
728
+ </div>
729
+ `,
730
+ imports: [DbxQuizResetButtonComponent],
731
+ standalone: true
732
+ }]
733
+ }], propDecorators: { input: [{ type: i0.Input, args: [{ isSignal: true, alias: "input", required: false }] }] } });
734
+
735
+ /**
736
+ * Creates a Likert scale question config with agreement prompt (Strongly Disagree to Strongly Agree).
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * instance.config.set(quizAgreementPrompt('I feel confident leading under pressure.'));
741
+ * // { prompt: 'Please rate how much you agree...', text: '...', guidance: '1 = Strongly Disagree, 5 = Strongly Agree' }
742
+ * ```
743
+ */
744
+ function quizAgreementPrompt(text) {
745
+ return {
746
+ prompt: 'Please rate how much you agree with the following statement:',
747
+ text,
748
+ guidance: '1 = Strongly Disagree, 5 = Strongly Agree'
749
+ };
750
+ }
751
+ /**
752
+ * Creates a Likert scale question config with frequency prompt (Never to Always).
753
+ *
754
+ * @example
755
+ * ```ts
756
+ * instance.config.set(quizFrequencyPrompt('I break vague direction into first steps.'));
757
+ * // { prompt: 'Please rate how much you agree...', text: '...', guidance: '1 = Never, 5 = Always' }
758
+ * ```
759
+ */
760
+ function quizFrequencyPrompt(text) {
761
+ return {
762
+ prompt: 'Please rate how much you agree with the following statement:',
763
+ text,
764
+ guidance: '1 = Never, 5 = Always'
765
+ };
766
+ }
767
+
768
+ /**
769
+ * Generated bundle index. Do not edit.
770
+ */
771
+
772
+ export { DbxQuizPostQuizComponent, DbxQuizResetButtonComponent, DbxQuizScoreComponent, QuizAnswerMultipleChoiceComponent, QuizAnswerNumberComponent, QuizComponent, QuizPreQuizIntroComponent, QuizQuestionAccessor, QuizQuestionTextComponent, QuizStore, provideCurrentQuestionQuizQuestionAccessor, quizAgreementPrompt, quizFrequencyPrompt };
773
+ //# sourceMappingURL=dereekb-dbx-form-quiz.mjs.map