@constructor-io/constructorio-ui-quizzes 1.18.2 → 1.19.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.
Files changed (37) hide show
  1. package/dist/constructorio-ui-quizzes-bundled.js +15 -15
  2. package/lib/cjs/components/CioQuiz/actions.js +5 -3
  3. package/lib/cjs/components/CioQuiz/index.js +2 -1
  4. package/lib/cjs/components/CioQuiz/quizApiReducer.js +3 -0
  5. package/lib/cjs/components/CioQuiz/quizLocalReducer.js +23 -12
  6. package/lib/cjs/hooks/usePropsGetters/index.js +6 -3
  7. package/lib/cjs/hooks/usePropsGetters/useJumpToQuestionButtonProps.js +34 -0
  8. package/lib/cjs/hooks/usePropsGetters/useSelectInputProps.js +3 -2
  9. package/lib/cjs/hooks/useQuiz.js +7 -1
  10. package/lib/cjs/hooks/useQuizEvents/index.js +7 -0
  11. package/lib/cjs/hooks/useQuizEvents/useJumpToQuestion.js +25 -0
  12. package/lib/cjs/hooks/useQuizState/useQuizApiState.js +1 -1
  13. package/lib/cjs/hooks/useQuizState/useSessionStorageState.js +2 -1
  14. package/lib/cjs/version.js +1 -1
  15. package/lib/mjs/components/CioQuiz/actions.js +5 -3
  16. package/lib/mjs/components/CioQuiz/index.js +2 -1
  17. package/lib/mjs/components/CioQuiz/quizApiReducer.js +8 -0
  18. package/lib/mjs/components/CioQuiz/quizLocalReducer.js +35 -39
  19. package/lib/mjs/hooks/usePropsGetters/index.js +6 -3
  20. package/lib/mjs/hooks/usePropsGetters/useJumpToQuestionButtonProps.js +29 -0
  21. package/lib/mjs/hooks/usePropsGetters/useSelectInputProps.js +3 -2
  22. package/lib/mjs/hooks/useQuiz.js +7 -1
  23. package/lib/mjs/hooks/useQuizEvents/index.js +7 -0
  24. package/lib/mjs/hooks/useQuizEvents/useJumpToQuestion.js +21 -0
  25. package/lib/mjs/hooks/useQuizState/useQuizApiState.js +1 -1
  26. package/lib/mjs/hooks/useQuizState/useSessionStorageState.js +3 -2
  27. package/lib/mjs/version.js +1 -1
  28. package/lib/types/components/CioQuiz/actions.d.ts +13 -6
  29. package/lib/types/components/CioQuiz/context.d.ts +2 -1
  30. package/lib/types/hooks/usePropsGetters/index.d.ts +9 -2
  31. package/lib/types/hooks/usePropsGetters/useJumpToQuestionButtonProps.d.ts +4 -0
  32. package/lib/types/hooks/usePropsGetters/useSelectInputProps.d.ts +1 -1
  33. package/lib/types/hooks/useQuizEvents/useJumpToQuestion.d.ts +11 -0
  34. package/lib/types/hooks/useQuizEvents/useQuizResetClick.d.ts +1 -1
  35. package/lib/types/types.d.ts +10 -0
  36. package/lib/types/version.d.ts +1 -1
  37. package/package.json +1 -1
@@ -16,6 +16,7 @@ var QuestionTypes;
16
16
  QuestionTypes["Reset"] = "reset";
17
17
  QuestionTypes["Hydrate"] = "hydrate";
18
18
  QuestionTypes["Complete"] = "complete";
19
+ QuestionTypes["JumpToQuestion"] = "jump_to_question";
19
20
  })(QuestionTypes = exports.QuestionTypes || (exports.QuestionTypes = {}));
20
21
  // API actions
21
22
  var QuizAPIActionTypes;
@@ -25,7 +26,8 @@ var QuizAPIActionTypes;
25
26
  QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS"] = 2] = "SET_QUIZ_RESULTS";
26
27
  QuizAPIActionTypes[QuizAPIActionTypes["SET_CURRENT_QUESTION"] = 3] = "SET_CURRENT_QUESTION";
27
28
  QuizAPIActionTypes[QuizAPIActionTypes["RESET_QUIZ"] = 4] = "RESET_QUIZ";
28
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_SHARED_RESULTS"] = 5] = "SET_QUIZ_SHARED_RESULTS";
29
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG"] = 6] = "SET_QUIZ_RESULTS_CONFIG";
30
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG_ERROR"] = 7] = "SET_QUIZ_RESULTS_CONFIG_ERROR";
29
+ QuizAPIActionTypes[QuizAPIActionTypes["JUMP_TO_QUESTION"] = 5] = "JUMP_TO_QUESTION";
30
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_SHARED_RESULTS"] = 6] = "SET_QUIZ_SHARED_RESULTS";
31
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG"] = 7] = "SET_QUIZ_RESULTS_CONFIG";
32
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG_ERROR"] = 8] = "SET_QUIZ_RESULTS_CONFIG_ERROR";
31
33
  })(QuizAPIActionTypes = exports.QuizAPIActionTypes || (exports.QuizAPIActionTypes = {}));
@@ -16,7 +16,7 @@ const ProgressBar_1 = tslib_1.__importDefault(require("../ProgressBar/ProgressBa
16
16
  const actions_1 = require("./actions");
17
17
  function CioQuiz(props) {
18
18
  var _a;
19
- const { cioClient, state, events: { hydrateQuiz, resetSessionStorageState }, getAddToCartButtonProps, getAddToFavoritesButtonProps, getCoverQuestionProps, getHydrateQuizButtonProps, getNextQuestionButtonProps, getSkipQuestionButtonProps, getOpenTextInputProps, getPreviousQuestionButtonProps, getQuizImageProps, getQuizResultButtonProps, getQuizResultLinkProps, getResetQuizButtonProps, getSelectInputProps, primaryColorStyles, getShareResultsButtonProps, } = (0, useQuiz_1.default)(props);
19
+ const { cioClient, state, events: { hydrateQuiz, resetSessionStorageState }, getAddToCartButtonProps, getAddToFavoritesButtonProps, getCoverQuestionProps, getHydrateQuizButtonProps, getNextQuestionButtonProps, getSkipQuestionButtonProps, getJumpToQuestionButtonProps, getOpenTextInputProps, getPreviousQuestionButtonProps, getQuizImageProps, getQuizResultButtonProps, getQuizResultLinkProps, getResetQuizButtonProps, getSelectInputProps, primaryColorStyles, getShareResultsButtonProps, } = (0, useQuiz_1.default)(props);
20
20
  const [showSessionPrompt, setShowSessionPrompt] = (0, react_1.useState)(false);
21
21
  const [showShareModal, setShowShareModal] = (0, react_1.useState)(false);
22
22
  const { callbacks, sessionStateOptions, questionsPageOptions, resultCardOptions, resultsPageOptions, } = props;
@@ -40,6 +40,7 @@ function CioQuiz(props) {
40
40
  getHydrateQuizButtonProps,
41
41
  getNextQuestionButtonProps,
42
42
  getSkipQuestionButtonProps,
43
+ getJumpToQuestionButtonProps,
43
44
  getOpenTextInputProps,
44
45
  getPreviousQuestionButtonProps,
45
46
  getQuizImageProps,
@@ -35,6 +35,9 @@ function apiReducer(state, action) {
35
35
  case actions_1.QuizAPIActionTypes.SET_QUIZ_SHARED_RESULTS: {
36
36
  return Object.assign(Object.assign({}, state), { quizRequestState: constants_1.RequestStates.Success, quizResults: (_o = action.payload) === null || _o === void 0 ? void 0 : _o.quizResults, quizCurrentQuestion: undefined, selectedOptionsWithAttributes: (_p = action.payload) === null || _p === void 0 ? void 0 : _p.quizResults.attributes });
37
37
  }
38
+ case actions_1.QuizAPIActionTypes.JUMP_TO_QUESTION: {
39
+ return Object.assign(Object.assign({}, state), { quizResults: undefined, selectedOptionsWithAttributes: undefined, matchedOptions: undefined });
40
+ }
38
41
  case actions_1.QuizAPIActionTypes.SET_QUIZ_RESULTS_CONFIG: {
39
42
  return Object.assign(Object.assign({}, state), { metadata: (_q = action.payload) === null || _q === void 0 ? void 0 : _q.quizResultsConfig.metadata, resultsConfig: (_r = action.payload) === null || _r === void 0 ? void 0 : _r.quizResultsConfig.results_config });
40
43
  }
@@ -8,12 +8,6 @@ exports.initialState = {
8
8
  prevAnswerInputs: {},
9
9
  isQuizCompleted: false,
10
10
  };
11
- function answerInputReducer(state, action) {
12
- return Object.assign(Object.assign({}, state), { [String(action.payload.questionId)]: {
13
- type: action.type,
14
- value: action.payload.input,
15
- } });
16
- }
17
11
  function handleNextQuestion(state) {
18
12
  var _a;
19
13
  const { answers, answerInputs } = state;
@@ -40,20 +34,21 @@ function handleNextQuestion(state) {
40
34
  // We now commit current answers to prevAnswerInputs
41
35
  prevAnswerInputs: answerInputs, answers: newAnswers, isQuizCompleted: false });
42
36
  }
37
+ const handleAnswerInput = (state, action) => (Object.assign(Object.assign({}, state), { answerInputs: Object.assign(Object.assign({}, state.answerInputs), { [String(action.payload.questionId)]: {
38
+ type: action.type,
39
+ value: action.payload.input,
40
+ } }), isQuizCompleted: false }));
41
+ // eslint-disable-next-line complexity
43
42
  function quizLocalReducer(state, action) {
43
+ var _a;
44
44
  switch (action.type) {
45
45
  case actions_1.QuestionTypes.OpenText:
46
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
47
46
  case actions_1.QuestionTypes.Cover:
48
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
49
47
  case actions_1.QuestionTypes.SingleSelect:
50
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
51
48
  case actions_1.QuestionTypes.MultipleSelect:
52
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
53
49
  case actions_1.QuestionTypes.SingleFilterValue:
54
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
55
50
  case actions_1.QuestionTypes.MultipleFilterValues:
56
- return Object.assign(Object.assign({}, state), { answerInputs: answerInputReducer(state.answerInputs, action), isQuizCompleted: false });
51
+ return handleAnswerInput(state, action);
57
52
  case actions_1.QuestionTypes.Next: {
58
53
  return handleNextQuestion(state);
59
54
  }
@@ -84,6 +79,22 @@ function quizLocalReducer(state, action) {
84
79
  const prevAnswerInputs = Object.assign({}, state.prevAnswerInputs);
85
80
  return Object.assign(Object.assign({}, state), { answerInputs: prevAnswerInputs, answers: [...state.answers.slice(0, -1)], isQuizCompleted: false });
86
81
  }
82
+ case actions_1.QuestionTypes.JumpToQuestion: {
83
+ const questionId = (_a = action.payload) === null || _a === void 0 ? void 0 : _a.questionId;
84
+ if (questionId === undefined)
85
+ return state;
86
+ const prevAnswerInputs = Object.assign({}, state.prevAnswerInputs);
87
+ // Remove all keys greater than questionId from answerInputs
88
+ const filteredAnswerInputs = {};
89
+ Object.keys(prevAnswerInputs).forEach((key) => {
90
+ if (parseInt(key, 10) >= questionId)
91
+ return;
92
+ filteredAnswerInputs[key] = prevAnswerInputs[key];
93
+ });
94
+ // Calculate the number of questions to keep (questions <= questionId)
95
+ const questionsToKeep = Object.keys(filteredAnswerInputs).length;
96
+ return Object.assign(Object.assign({}, state), { answerInputs: filteredAnswerInputs, prevAnswerInputs: filteredAnswerInputs, answers: state.answers.slice(0, questionsToKeep), isQuizCompleted: false });
97
+ }
87
98
  case actions_1.QuestionTypes.Reset:
88
99
  return Object.assign({}, exports.initialState);
89
100
  case actions_1.QuestionTypes.Hydrate:
@@ -9,14 +9,16 @@ const useNextQuestionButtonProps_1 = tslib_1.__importDefault(require("./useNextQ
9
9
  const usePreviousQuestionButtonProps_1 = tslib_1.__importDefault(require("./usePreviousQuestionButtonProps"));
10
10
  const useAddToFavoritesButtonProps_1 = tslib_1.__importDefault(require("./useAddToFavoritesButtonProps"));
11
11
  const useSkipQuestionButtonProps_1 = tslib_1.__importDefault(require("./useSkipQuestionButtonProps"));
12
- const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems) => {
12
+ const useJumpToQuestionButtonProps_1 = tslib_1.__importDefault(require("./useJumpToQuestionButtonProps"));
13
+ const usePropsGetters = ({ questionsPageOptions, favoriteItems, quizEvents, quizApiState, quizLocalState, }) => {
13
14
  var _a, _b, _c;
14
- const { quizAnswerChanged, nextQuestion, skipQuestion, previousQuestion, resetQuiz, hydrateQuiz, addToCart, addToFavorites, resultClick, } = quizEvents;
15
+ const { quizAnswerChanged, nextQuestion, skipQuestion, previousQuestion, resetQuiz, hydrateQuiz, addToCart, addToFavorites, resultClick, jumpToQuestion, } = quizEvents;
15
16
  const getOpenTextInputProps = (0, useOpenTextInputProps_1.default)(quizAnswerChanged, nextQuestion, (_a = quizApiState.quizCurrentQuestion) === null || _a === void 0 ? void 0 : _a.next_question, quizLocalState.answerInputs);
16
17
  const getCoverQuestionProps = (0, useCoverQuestionProps_1.default)(quizAnswerChanged, (_b = quizApiState.quizCurrentQuestion) === null || _b === void 0 ? void 0 : _b.next_question);
17
- const getSelectInputProps = (0, useSelectInputProps_1.default)(quizAnswerChanged, nextQuestion, (_c = quizApiState.quizCurrentQuestion) === null || _c === void 0 ? void 0 : _c.next_question, quizLocalState.answerInputs);
18
+ const getSelectInputProps = (0, useSelectInputProps_1.default)(quizAnswerChanged, nextQuestion, (_c = quizApiState.quizCurrentQuestion) === null || _c === void 0 ? void 0 : _c.next_question, quizLocalState.answerInputs, questionsPageOptions === null || questionsPageOptions === void 0 ? void 0 : questionsPageOptions.nextQuestionOnSingleSelect);
18
19
  const getNextQuestionButtonProps = (0, useNextQuestionButtonProps_1.default)(nextQuestion, quizApiState, quizLocalState);
19
20
  const getSkipQuestionButtonProps = (0, useSkipQuestionButtonProps_1.default)(skipQuestion, quizApiState);
21
+ const getJumpToQuestionButtonProps = (0, useJumpToQuestionButtonProps_1.default)(jumpToQuestion, quizApiState, quizLocalState);
20
22
  const getPreviousQuestionButtonProps = (0, usePreviousQuestionButtonProps_1.default)(quizApiState, previousQuestion);
21
23
  const getResetQuizButtonProps = (0, react_1.useCallback)((stylesType = 'primary') => ({
22
24
  className: stylesType === 'primary' ? 'cio-question-cta-button' : 'cio-question-redo-button',
@@ -88,6 +90,7 @@ const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems
88
90
  getQuizResultButtonProps,
89
91
  getQuizResultLinkProps,
90
92
  getSkipQuestionButtonProps,
93
+ getJumpToQuestionButtonProps,
91
94
  };
92
95
  };
93
96
  exports.default = usePropsGetters;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
4
+ function useJumpToQuestionButtonProps(jumpToQuestion, quizApiState, quizLocalState) {
5
+ var _a, _b;
6
+ const getJumpToQuestionButtonProps = (0, react_1.useCallback)((id) => {
7
+ var _a, _b;
8
+ let buttonDisabled;
9
+ const currentQuestionId = (_b = (_a = quizApiState.quizCurrentQuestion) === null || _a === void 0 ? void 0 : _a.next_question) === null || _b === void 0 ? void 0 : _b.id;
10
+ const answerIds = Object.keys(quizLocalState.prevAnswerInputs);
11
+ const isInvalidQuestionId = answerIds && !answerIds.includes(String(id));
12
+ const isCurrentOrFutureQuestionId = currentQuestionId && id >= currentQuestionId;
13
+ if (isInvalidQuestionId || isCurrentOrFutureQuestionId) {
14
+ buttonDisabled = true;
15
+ }
16
+ return {
17
+ className: buttonDisabled ? 'cio-question-cta-button disabled' : 'cio-question-cta-button',
18
+ tabIndex: buttonDisabled ? -1 : 0,
19
+ 'aria-disabled': buttonDisabled ? 'true' : 'false',
20
+ 'aria-describedby': buttonDisabled ? 'jump-to-button-help' : '',
21
+ disabled: !!buttonDisabled,
22
+ type: 'button',
23
+ onClick: () => {
24
+ jumpToQuestion(id);
25
+ },
26
+ };
27
+ }, [
28
+ quizLocalState.prevAnswerInputs,
29
+ (_b = (_a = quizApiState.quizCurrentQuestion) === null || _a === void 0 ? void 0 : _a.next_question) === null || _b === void 0 ? void 0 : _b.id,
30
+ jumpToQuestion,
31
+ ]);
32
+ return getJumpToQuestionButtonProps;
33
+ }
34
+ exports.default = useJumpToQuestionButtonProps;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const react_1 = require("react");
4
4
  const actions_1 = require("../../components/CioQuiz/actions");
5
5
  // eslint-disable-next-line max-params
6
- function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionData, answerInputs) {
6
+ function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionData, answerInputs, nextQuestionOnSingleSelect = true) {
7
7
  var _a;
8
8
  const type = currentQuestionData === null || currentQuestionData === void 0 ? void 0 : currentQuestionData.type;
9
9
  const hasImages = (_a = currentQuestionData === null || currentQuestionData === void 0 ? void 0 : currentQuestionData.options) === null || _a === void 0 ? void 0 : _a.some((option) => option.images);
@@ -79,7 +79,8 @@ function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionDat
79
79
  (0, react_1.useEffect)(() => {
80
80
  if (((currentQuestionData === null || currentQuestionData === void 0 ? void 0 : currentQuestionData.type) === actions_1.QuestionTypes.SingleSelect ||
81
81
  (currentQuestionData === null || currentQuestionData === void 0 ? void 0 : currentQuestionData.type) === actions_1.QuestionTypes.SingleFilterValue) &&
82
- singleSelectClicked.current) {
82
+ singleSelectClicked.current &&
83
+ nextQuestionOnSingleSelect) {
83
84
  nextQuestion();
84
85
  }
85
86
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -20,7 +20,13 @@ const useQuiz = (quizOptions) => {
20
20
  const quizEvents = (0, useQuizEvents_1.default)(quizOptions, cioClient, quizState);
21
21
  // Props getters
22
22
  const { quizApiState, quizLocalState, quizSessionStorageState } = quizState;
23
- const propGetters = (0, usePropsGetters_1.default)(quizEvents, quizApiState, quizLocalState, resultsPageOptions === null || resultsPageOptions === void 0 ? void 0 : resultsPageOptions.favoriteItems);
23
+ const propGetters = (0, usePropsGetters_1.default)({
24
+ quizEvents,
25
+ quizApiState,
26
+ quizLocalState,
27
+ favoriteItems: resultsPageOptions === null || resultsPageOptions === void 0 ? void 0 : resultsPageOptions.favoriteItems,
28
+ questionsPageOptions: quizOptions.questionsPageOptions,
29
+ });
24
30
  const primaryColorStyles = (0, usePrimaryColorStyles_1.default)(primaryColor);
25
31
  return Object.assign(Object.assign({ cioClient, state: {
26
32
  answers: {
@@ -12,6 +12,7 @@ const useHydrateQuizLocalState_1 = tslib_1.__importDefault(require("./useHydrate
12
12
  const utils_1 = require("../../utils");
13
13
  const useQuizAddToFavorites_1 = tslib_1.__importDefault(require("./useQuizAddToFavorites"));
14
14
  const useQuizSkipClick_1 = tslib_1.__importDefault(require("./useQuizSkipClick"));
15
+ const useJumpToQuestion_1 = tslib_1.__importDefault(require("./useJumpToQuestion"));
15
16
  const useQuizEvents = (quizOptions, cioClient, quizState) => {
16
17
  const { quizApiState, dispatchLocalState, dispatchApiState, quizLocalState, quizSessionStorageState, } = quizState;
17
18
  const { callbacks } = quizOptions;
@@ -38,6 +39,11 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
38
39
  dispatchApiState,
39
40
  quizResults: quizApiState.quizResults,
40
41
  });
42
+ const jumpToQuestion = (0, useJumpToQuestion_1.default)({
43
+ dispatchLocalState,
44
+ dispatchApiState,
45
+ quizApiState,
46
+ });
41
47
  // Quiz rehydrate
42
48
  const hydrateQuizLocalState = (0, useHydrateQuizLocalState_1.default)(quizOptions.quizId, quizSessionStorageState, dispatchLocalState);
43
49
  return {
@@ -49,6 +55,7 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
49
55
  nextQuestion,
50
56
  skipQuestion,
51
57
  resetQuiz,
58
+ jumpToQuestion,
52
59
  hydrateQuiz: hydrateQuizLocalState,
53
60
  resetSessionStorageState,
54
61
  };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
4
+ const actions_1 = require("../../components/CioQuiz/actions");
5
+ const useJumpToQuestion = (props) => {
6
+ var _a;
7
+ const { dispatchLocalState, dispatchApiState, quizApiState } = props;
8
+ const quizJumpToQuestionClickHandler = (0, react_1.useCallback)((questionId) => {
9
+ var _a;
10
+ const currentQuestionId = (_a = quizApiState.quizCurrentQuestion) === null || _a === void 0 ? void 0 : _a.id;
11
+ if (questionId >= currentQuestionId) {
12
+ return;
13
+ }
14
+ dispatchLocalState({
15
+ type: actions_1.QuestionTypes.JumpToQuestion,
16
+ payload: { questionId },
17
+ });
18
+ dispatchApiState({
19
+ type: actions_1.QuizAPIActionTypes.JUMP_TO_QUESTION,
20
+ payload: { questionId },
21
+ });
22
+ }, [(_a = quizApiState.quizCurrentQuestion) === null || _a === void 0 ? void 0 : _a.id, dispatchLocalState, dispatchApiState]);
23
+ return quizJumpToQuestionClickHandler;
24
+ };
25
+ exports.default = useJumpToQuestion;
@@ -81,7 +81,7 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults,
81
81
  dispatchQuizResultsConfig();
82
82
  return;
83
83
  }
84
- const { onQuizResultsConfigLoaded } = callbacks;
84
+ const { onQuizResultsConfigLoaded } = callbacks || {};
85
85
  if (onQuizResultsConfigLoaded && (0, utils_1.isFunction)(onQuizResultsConfigLoaded)) {
86
86
  onQuizResultsConfigLoaded(quizApiState.resultsConfig, quizApiState.metadata);
87
87
  }
@@ -6,6 +6,7 @@ const constants_1 = require("../../constants");
6
6
  const useSessionStorageState = (quizId, quizLocalState, sessionStateOptions, enableHydration) => {
7
7
  var _a;
8
8
  const quizSessionStorageStateKey = (sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.sessionStateKey) || constants_1.quizSessionStateKey;
9
+ const [quizData, setQuizData] = (0, react_1.useState)((0, utils_1.getStateFromSessionStorage)(quizSessionStorageStateKey));
9
10
  // Save state to session storage
10
11
  (0, react_1.useEffect)(() => {
11
12
  var _a, _b;
@@ -14,9 +15,9 @@ const useSessionStorageState = (quizId, quizLocalState, sessionStateOptions, ena
14
15
  const data = (0, utils_1.getStateFromSessionStorage)(quizSessionStorageStateKey);
15
16
  const dataToSave = Object.assign(Object.assign({}, data), { [quizId]: quizLocalState });
16
17
  (_b = window === null || window === void 0 ? void 0 : window.sessionStorage) === null || _b === void 0 ? void 0 : _b.setItem(quizSessionStorageStateKey, JSON.stringify(dataToSave));
18
+ setQuizData(data);
17
19
  }
18
20
  }, [quizLocalState, quizSessionStorageStateKey, enableHydration, quizId]);
19
- const quizData = (0, utils_1.getStateFromSessionStorage)(quizSessionStorageStateKey);
20
21
  const skipToResults = !!enableHydration &&
21
22
  !!((_a = quizData === null || quizData === void 0 ? void 0 : quizData[quizId]) === null || _a === void 0 ? void 0 : _a.isQuizCompleted) &&
22
23
  !(sessionStateOptions === null || sessionStateOptions === void 0 ? void 0 : sessionStateOptions.showSessionModalOnResults);
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = '1.18.2';
3
+ exports.default = '1.19.0';
@@ -13,6 +13,7 @@ export var QuestionTypes;
13
13
  QuestionTypes["Reset"] = "reset";
14
14
  QuestionTypes["Hydrate"] = "hydrate";
15
15
  QuestionTypes["Complete"] = "complete";
16
+ QuestionTypes["JumpToQuestion"] = "jump_to_question";
16
17
  })(QuestionTypes || (QuestionTypes = {}));
17
18
  // API actions
18
19
  export var QuizAPIActionTypes;
@@ -22,7 +23,8 @@ export var QuizAPIActionTypes;
22
23
  QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS"] = 2] = "SET_QUIZ_RESULTS";
23
24
  QuizAPIActionTypes[QuizAPIActionTypes["SET_CURRENT_QUESTION"] = 3] = "SET_CURRENT_QUESTION";
24
25
  QuizAPIActionTypes[QuizAPIActionTypes["RESET_QUIZ"] = 4] = "RESET_QUIZ";
25
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_SHARED_RESULTS"] = 5] = "SET_QUIZ_SHARED_RESULTS";
26
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG"] = 6] = "SET_QUIZ_RESULTS_CONFIG";
27
- QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG_ERROR"] = 7] = "SET_QUIZ_RESULTS_CONFIG_ERROR";
26
+ QuizAPIActionTypes[QuizAPIActionTypes["JUMP_TO_QUESTION"] = 5] = "JUMP_TO_QUESTION";
27
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_SHARED_RESULTS"] = 6] = "SET_QUIZ_SHARED_RESULTS";
28
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG"] = 7] = "SET_QUIZ_RESULTS_CONFIG";
29
+ QuizAPIActionTypes[QuizAPIActionTypes["SET_QUIZ_RESULTS_CONFIG_ERROR"] = 8] = "SET_QUIZ_RESULTS_CONFIG_ERROR";
28
30
  })(QuizAPIActionTypes || (QuizAPIActionTypes = {}));
@@ -12,7 +12,7 @@ import { convertPrimaryColorsToString, renderImages } from '../../utils';
12
12
  import ProgressBar from '../ProgressBar/ProgressBar';
13
13
  import { QuestionTypes } from './actions';
14
14
  export default function CioQuiz(props) {
15
- const { cioClient, state, events: { hydrateQuiz, resetSessionStorageState }, getAddToCartButtonProps, getAddToFavoritesButtonProps, getCoverQuestionProps, getHydrateQuizButtonProps, getNextQuestionButtonProps, getSkipQuestionButtonProps, getOpenTextInputProps, getPreviousQuestionButtonProps, getQuizImageProps, getQuizResultButtonProps, getQuizResultLinkProps, getResetQuizButtonProps, getSelectInputProps, primaryColorStyles, getShareResultsButtonProps, } = useQuiz(props);
15
+ const { cioClient, state, events: { hydrateQuiz, resetSessionStorageState }, getAddToCartButtonProps, getAddToFavoritesButtonProps, getCoverQuestionProps, getHydrateQuizButtonProps, getNextQuestionButtonProps, getSkipQuestionButtonProps, getJumpToQuestionButtonProps, getOpenTextInputProps, getPreviousQuestionButtonProps, getQuizImageProps, getQuizResultButtonProps, getQuizResultLinkProps, getResetQuizButtonProps, getSelectInputProps, primaryColorStyles, getShareResultsButtonProps, } = useQuiz(props);
16
16
  const [showSessionPrompt, setShowSessionPrompt] = useState(false);
17
17
  const [showShareModal, setShowShareModal] = useState(false);
18
18
  const { callbacks, sessionStateOptions, questionsPageOptions, resultCardOptions, resultsPageOptions, } = props;
@@ -36,6 +36,7 @@ export default function CioQuiz(props) {
36
36
  getHydrateQuizButtonProps,
37
37
  getNextQuestionButtonProps,
38
38
  getSkipQuestionButtonProps,
39
+ getJumpToQuestionButtonProps,
39
40
  getOpenTextInputProps,
40
41
  getPreviousQuestionButtonProps,
41
42
  getQuizImageProps,
@@ -71,6 +71,14 @@ export default function apiReducer(state, action) {
71
71
  selectedOptionsWithAttributes: action.payload?.quizResults.attributes,
72
72
  };
73
73
  }
74
+ case QuizAPIActionTypes.JUMP_TO_QUESTION: {
75
+ return {
76
+ ...state,
77
+ quizResults: undefined,
78
+ selectedOptionsWithAttributes: undefined,
79
+ matchedOptions: undefined,
80
+ };
81
+ }
74
82
  case QuizAPIActionTypes.SET_QUIZ_RESULTS_CONFIG: {
75
83
  return {
76
84
  ...state,
@@ -5,15 +5,6 @@ export const initialState = {
5
5
  prevAnswerInputs: {},
6
6
  isQuizCompleted: false,
7
7
  };
8
- function answerInputReducer(state, action) {
9
- return {
10
- ...state,
11
- [String(action.payload.questionId)]: {
12
- type: action.type,
13
- value: action.payload.input,
14
- },
15
- };
16
- }
17
8
  function handleNextQuestion(state) {
18
9
  const { answers, answerInputs } = state;
19
10
  const newAnswers = [...answers];
@@ -43,44 +34,27 @@ function handleNextQuestion(state) {
43
34
  isQuizCompleted: false,
44
35
  };
45
36
  }
37
+ const handleAnswerInput = (state, action) => ({
38
+ ...state,
39
+ answerInputs: {
40
+ ...state.answerInputs,
41
+ [String(action.payload.questionId)]: {
42
+ type: action.type,
43
+ value: action.payload.input,
44
+ },
45
+ },
46
+ isQuizCompleted: false,
47
+ });
48
+ // eslint-disable-next-line complexity
46
49
  export default function quizLocalReducer(state, action) {
47
50
  switch (action.type) {
48
51
  case QuestionTypes.OpenText:
49
- return {
50
- ...state,
51
- answerInputs: answerInputReducer(state.answerInputs, action),
52
- isQuizCompleted: false,
53
- };
54
52
  case QuestionTypes.Cover:
55
- return {
56
- ...state,
57
- answerInputs: answerInputReducer(state.answerInputs, action),
58
- isQuizCompleted: false,
59
- };
60
53
  case QuestionTypes.SingleSelect:
61
- return {
62
- ...state,
63
- answerInputs: answerInputReducer(state.answerInputs, action),
64
- isQuizCompleted: false,
65
- };
66
54
  case QuestionTypes.MultipleSelect:
67
- return {
68
- ...state,
69
- answerInputs: answerInputReducer(state.answerInputs, action),
70
- isQuizCompleted: false,
71
- };
72
55
  case QuestionTypes.SingleFilterValue:
73
- return {
74
- ...state,
75
- answerInputs: answerInputReducer(state.answerInputs, action),
76
- isQuizCompleted: false,
77
- };
78
56
  case QuestionTypes.MultipleFilterValues:
79
- return {
80
- ...state,
81
- answerInputs: answerInputReducer(state.answerInputs, action),
82
- isQuizCompleted: false,
83
- };
57
+ return handleAnswerInput(state, action);
84
58
  case QuestionTypes.Next: {
85
59
  return handleNextQuestion(state);
86
60
  }
@@ -120,6 +94,28 @@ export default function quizLocalReducer(state, action) {
120
94
  isQuizCompleted: false,
121
95
  };
122
96
  }
97
+ case QuestionTypes.JumpToQuestion: {
98
+ const questionId = action.payload?.questionId;
99
+ if (questionId === undefined)
100
+ return state;
101
+ const prevAnswerInputs = { ...state.prevAnswerInputs };
102
+ // Remove all keys greater than questionId from answerInputs
103
+ const filteredAnswerInputs = {};
104
+ Object.keys(prevAnswerInputs).forEach((key) => {
105
+ if (parseInt(key, 10) >= questionId)
106
+ return;
107
+ filteredAnswerInputs[key] = prevAnswerInputs[key];
108
+ });
109
+ // Calculate the number of questions to keep (questions <= questionId)
110
+ const questionsToKeep = Object.keys(filteredAnswerInputs).length;
111
+ return {
112
+ ...state,
113
+ answerInputs: filteredAnswerInputs,
114
+ prevAnswerInputs: filteredAnswerInputs,
115
+ answers: state.answers.slice(0, questionsToKeep),
116
+ isQuizCompleted: false,
117
+ };
118
+ }
123
119
  case QuestionTypes.Reset:
124
120
  return {
125
121
  ...initialState,
@@ -6,13 +6,15 @@ import useNextQuestionButtonProps from './useNextQuestionButtonProps';
6
6
  import usePreviousQuestionButtonProps from './usePreviousQuestionButtonProps';
7
7
  import useAddToFavoritesButtonProps from './useAddToFavoritesButtonProps';
8
8
  import useSkipQuestionButtonProps from './useSkipQuestionButtonProps';
9
- const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems) => {
10
- const { quizAnswerChanged, nextQuestion, skipQuestion, previousQuestion, resetQuiz, hydrateQuiz, addToCart, addToFavorites, resultClick, } = quizEvents;
9
+ import useJumpToQuestionButtonProps from './useJumpToQuestionButtonProps';
10
+ const usePropsGetters = ({ questionsPageOptions, favoriteItems, quizEvents, quizApiState, quizLocalState, }) => {
11
+ const { quizAnswerChanged, nextQuestion, skipQuestion, previousQuestion, resetQuiz, hydrateQuiz, addToCart, addToFavorites, resultClick, jumpToQuestion, } = quizEvents;
11
12
  const getOpenTextInputProps = useOpenTextInputProps(quizAnswerChanged, nextQuestion, quizApiState.quizCurrentQuestion?.next_question, quizLocalState.answerInputs);
12
13
  const getCoverQuestionProps = useCoverQuestionProps(quizAnswerChanged, quizApiState.quizCurrentQuestion?.next_question);
13
- const getSelectInputProps = useSelectInputProps(quizAnswerChanged, nextQuestion, quizApiState.quizCurrentQuestion?.next_question, quizLocalState.answerInputs);
14
+ const getSelectInputProps = useSelectInputProps(quizAnswerChanged, nextQuestion, quizApiState.quizCurrentQuestion?.next_question, quizLocalState.answerInputs, questionsPageOptions?.nextQuestionOnSingleSelect);
14
15
  const getNextQuestionButtonProps = useNextQuestionButtonProps(nextQuestion, quizApiState, quizLocalState);
15
16
  const getSkipQuestionButtonProps = useSkipQuestionButtonProps(skipQuestion, quizApiState);
17
+ const getJumpToQuestionButtonProps = useJumpToQuestionButtonProps(jumpToQuestion, quizApiState, quizLocalState);
16
18
  const getPreviousQuestionButtonProps = usePreviousQuestionButtonProps(quizApiState, previousQuestion);
17
19
  const getResetQuizButtonProps = useCallback((stylesType = 'primary') => ({
18
20
  className: stylesType === 'primary' ? 'cio-question-cta-button' : 'cio-question-redo-button',
@@ -75,6 +77,7 @@ const usePropsGetters = (quizEvents, quizApiState, quizLocalState, favoriteItems
75
77
  getQuizResultButtonProps,
76
78
  getQuizResultLinkProps,
77
79
  getSkipQuestionButtonProps,
80
+ getJumpToQuestionButtonProps,
78
81
  };
79
82
  };
80
83
  export default usePropsGetters;
@@ -0,0 +1,29 @@
1
+ import { useCallback } from 'react';
2
+ export default function useJumpToQuestionButtonProps(jumpToQuestion, quizApiState, quizLocalState) {
3
+ const getJumpToQuestionButtonProps = useCallback((id) => {
4
+ let buttonDisabled;
5
+ const currentQuestionId = quizApiState.quizCurrentQuestion?.next_question?.id;
6
+ const answerIds = Object.keys(quizLocalState.prevAnswerInputs);
7
+ const isInvalidQuestionId = answerIds && !answerIds.includes(String(id));
8
+ const isCurrentOrFutureQuestionId = currentQuestionId && id >= currentQuestionId;
9
+ if (isInvalidQuestionId || isCurrentOrFutureQuestionId) {
10
+ buttonDisabled = true;
11
+ }
12
+ return {
13
+ className: buttonDisabled ? 'cio-question-cta-button disabled' : 'cio-question-cta-button',
14
+ tabIndex: buttonDisabled ? -1 : 0,
15
+ 'aria-disabled': buttonDisabled ? 'true' : 'false',
16
+ 'aria-describedby': buttonDisabled ? 'jump-to-button-help' : '',
17
+ disabled: !!buttonDisabled,
18
+ type: 'button',
19
+ onClick: () => {
20
+ jumpToQuestion(id);
21
+ },
22
+ };
23
+ }, [
24
+ quizLocalState.prevAnswerInputs,
25
+ quizApiState.quizCurrentQuestion?.next_question?.id,
26
+ jumpToQuestion,
27
+ ]);
28
+ return getJumpToQuestionButtonProps;
29
+ }
@@ -1,7 +1,7 @@
1
1
  import { useState, useCallback, useEffect, useRef } from 'react';
2
2
  import { QuestionTypes } from '../../components/CioQuiz/actions';
3
3
  // eslint-disable-next-line max-params
4
- export default function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionData, answerInputs) {
4
+ export default function useSelectInputProps(quizAnswerChanged, nextQuestion, currentQuestionData, answerInputs, nextQuestionOnSingleSelect = true) {
5
5
  const type = currentQuestionData?.type;
6
6
  const hasImages = currentQuestionData?.options?.some((option) => option.images);
7
7
  const [selected, setSelected] = useState({});
@@ -78,7 +78,8 @@ export default function useSelectInputProps(quizAnswerChanged, nextQuestion, cur
78
78
  useEffect(() => {
79
79
  if ((currentQuestionData?.type === QuestionTypes.SingleSelect ||
80
80
  currentQuestionData?.type === QuestionTypes.SingleFilterValue) &&
81
- singleSelectClicked.current) {
81
+ singleSelectClicked.current &&
82
+ nextQuestionOnSingleSelect) {
82
83
  nextQuestion();
83
84
  }
84
85
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -16,7 +16,13 @@ const useQuiz = (quizOptions) => {
16
16
  const quizEvents = useQuizEvents(quizOptions, cioClient, quizState);
17
17
  // Props getters
18
18
  const { quizApiState, quizLocalState, quizSessionStorageState } = quizState;
19
- const propGetters = usePropsGetters(quizEvents, quizApiState, quizLocalState, resultsPageOptions?.favoriteItems);
19
+ const propGetters = usePropsGetters({
20
+ quizEvents,
21
+ quizApiState,
22
+ quizLocalState,
23
+ favoriteItems: resultsPageOptions?.favoriteItems,
24
+ questionsPageOptions: quizOptions.questionsPageOptions,
25
+ });
20
26
  const primaryColorStyles = usePrimaryColorStyles(primaryColor);
21
27
  return {
22
28
  cioClient,
@@ -9,6 +9,7 @@ import useHydrateQuizLocalState from './useHydrateQuizLocalState';
9
9
  import { resetQuizSessionStorageState } from '../../utils';
10
10
  import useQuizAddToFavorites from './useQuizAddToFavorites';
11
11
  import useQuizSkipClick from './useQuizSkipClick';
12
+ import useJumpToQuestion from './useJumpToQuestion';
12
13
  const useQuizEvents = (quizOptions, cioClient, quizState) => {
13
14
  const { quizApiState, dispatchLocalState, dispatchApiState, quizLocalState, quizSessionStorageState, } = quizState;
14
15
  const { callbacks } = quizOptions;
@@ -35,6 +36,11 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
35
36
  dispatchApiState,
36
37
  quizResults: quizApiState.quizResults,
37
38
  });
39
+ const jumpToQuestion = useJumpToQuestion({
40
+ dispatchLocalState,
41
+ dispatchApiState,
42
+ quizApiState,
43
+ });
38
44
  // Quiz rehydrate
39
45
  const hydrateQuizLocalState = useHydrateQuizLocalState(quizOptions.quizId, quizSessionStorageState, dispatchLocalState);
40
46
  return {
@@ -46,6 +52,7 @@ const useQuizEvents = (quizOptions, cioClient, quizState) => {
46
52
  nextQuestion,
47
53
  skipQuestion,
48
54
  resetQuiz,
55
+ jumpToQuestion,
49
56
  hydrateQuiz: hydrateQuizLocalState,
50
57
  resetSessionStorageState,
51
58
  };
@@ -0,0 +1,21 @@
1
+ import { useCallback } from 'react';
2
+ import { QuestionTypes, QuizAPIActionTypes, } from '../../components/CioQuiz/actions';
3
+ const useJumpToQuestion = (props) => {
4
+ const { dispatchLocalState, dispatchApiState, quizApiState } = props;
5
+ const quizJumpToQuestionClickHandler = useCallback((questionId) => {
6
+ const currentQuestionId = quizApiState.quizCurrentQuestion?.id;
7
+ if (questionId >= currentQuestionId) {
8
+ return;
9
+ }
10
+ dispatchLocalState({
11
+ type: QuestionTypes.JumpToQuestion,
12
+ payload: { questionId },
13
+ });
14
+ dispatchApiState({
15
+ type: QuizAPIActionTypes.JUMP_TO_QUESTION,
16
+ payload: { questionId },
17
+ });
18
+ }, [quizApiState.quizCurrentQuestion?.id, dispatchLocalState, dispatchApiState]);
19
+ return quizJumpToQuestionClickHandler;
20
+ };
21
+ export default useJumpToQuestion;
@@ -89,7 +89,7 @@ const useQuizApiState = (quizOptions, cioClient, quizLocalState, skipToResults,
89
89
  dispatchQuizResultsConfig();
90
90
  return;
91
91
  }
92
- const { onQuizResultsConfigLoaded } = callbacks;
92
+ const { onQuizResultsConfigLoaded } = callbacks || {};
93
93
  if (onQuizResultsConfigLoaded && isFunction(onQuizResultsConfigLoaded)) {
94
94
  onQuizResultsConfigLoaded(quizApiState.resultsConfig, quizApiState.metadata);
95
95
  }