From d1f272a008d923c408d48708a5307157fdd1ed16 Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Fri, 12 Apr 2024 07:00:27 +0200 Subject: [PATCH 1/3] fix: updated inputType properties definitions so they are registered on component itself and not in quickform --- .../core/src/hooks/useFocusableQuestion.ts | 6 ++-- packages/core/src/model/InputType.ts | 36 ------------------- packages/core/src/model/QuickFormModel.ts | 2 +- .../model/json-definitions/JsonDataModels.ts | 17 ++++----- .../defaults/DefaultInputTypeResolver.ts | 23 ++---------- .../defaults/DefaultInputValidator.ts | 18 ++-------- packages/core/src/style/QuickForm.css | 6 ---- .../components/buttons-input/ButtonsInput.tsx | 20 ++++++++--- .../src/components/radio-input/RadioInput.tsx | 10 ++++-- .../components/slider-input/SliderInput.tsx | 9 ++++- 10 files changed, 47 insertions(+), 100 deletions(-) delete mode 100644 packages/core/src/style/QuickForm.css diff --git a/packages/core/src/hooks/useFocusableQuestion.ts b/packages/core/src/hooks/useFocusableQuestion.ts index 59f4f36..e6410d7 100644 --- a/packages/core/src/hooks/useFocusableQuestion.ts +++ b/packages/core/src/hooks/useFocusableQuestion.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import { useQuickForm } from "../state"; @@ -10,8 +10,8 @@ export const useFocusableQuestion = (questionkey: string, if (ref.current && isFirstQuestionInCurrentSlide(questionkey)) { ref.current.focus(options); } - }, [ref,questionkey]); - + }, [ref, questionkey]); + return ref; } \ No newline at end of file diff --git a/packages/core/src/model/InputType.ts b/packages/core/src/model/InputType.ts index 1ed4d40..bc18891 100644 --- a/packages/core/src/model/InputType.ts +++ b/packages/core/src/model/InputType.ts @@ -9,42 +9,23 @@ export type InputProps = { const Email = "email"; const Multilinetext = "multilinetext"; -const Radio = "radio"; -const Slider = "slider"; const Text = "text"; -const Buttons = "buttons"; const Phone = "phone"; export interface InputTypeMap { - [Buttons]: ButtonsProperties; [Phone]: PhoneProperties; [Email]: EmailProperties; [Multilinetext]: MultilineProperties; - [Radio]: RadioProperties; - [Slider]: SliderProperties; [Text]: TextProperties; } export type InputPropertiesTypes = - ButtonsProperties | EmailProperties | MultilineProperties | - RadioProperties | - SliderProperties | TextProperties | PhoneProperties | {}; -export type ButtonsProperties = { - inputType: typeof Buttons; - options: { - key: string | undefined; - label: string; - } - defaultValue?: string; -} - - export type PhoneProperties = { inputType: typeof Phone; defaultValue?: number; @@ -60,23 +41,6 @@ export type MultilineProperties = { defaultValue?: string; } -export type RadioProperties = { - inputType: typeof Radio; - options: { - [key: string]: string; - } - defaultValue?: boolean; - direction?: "horizontal" | "vertical"; -} - -export type SliderProperties = { - inputType: typeof Slider; - min: number; - max: number; - step: number; - defaultValue?: number; -} - export type TextProperties = { inputType: typeof Text; defaultValue?: string; diff --git a/packages/core/src/model/QuickFormModel.ts b/packages/core/src/model/QuickFormModel.ts index e593d0e..04a1dd1 100644 --- a/packages/core/src/model/QuickFormModel.ts +++ b/packages/core/src/model/QuickFormModel.ts @@ -1,4 +1,4 @@ -import { EndingModel, IntroModel, QuestionModel, SlideModel, SubmitModel } from "./index"; +import { EndingModel, IntroModel, SlideModel, SubmitModel } from "./index"; export type QuickFormModel = { intro?: IntroModel; diff --git a/packages/core/src/model/json-definitions/JsonDataModels.ts b/packages/core/src/model/json-definitions/JsonDataModels.ts index 1762271..8b34522 100644 --- a/packages/core/src/model/json-definitions/JsonDataModels.ts +++ b/packages/core/src/model/json-definitions/JsonDataModels.ts @@ -1,4 +1,4 @@ -import { ButtonsProperties, EmailProperties, MultilineProperties, RadioProperties, SliderProperties, TextProperties } from "../InputType"; +import { EmailProperties, MultilineProperties, TextProperties } from "../InputType"; type QuickFormQuestionDefinition = { @@ -49,7 +49,7 @@ type QuickFormQuestionDefinition = { /** * All questions support conditional rendering, allowing one to specify a rule and a engine to execute it. - * TODO: the rule should be of type any, because its the engine (type) that knows its data type. + * The rule is of type any, because its the engine (type) that knows its data type. */ visible?: { engine: string; @@ -68,12 +68,7 @@ type QuickFormQuestionDefinition = { * * TODO - need to be able to extend this in a meaning full way. */ -export type QuestionJsonModel = - QuickFormQuestionDefinition | - QuickFormQuestionDefinition & ButtonsProperties | - // QuickFormQuestionDefinition & DropDownProperties | - QuickFormQuestionDefinition & EmailProperties | - QuickFormQuestionDefinition & MultilineProperties | - QuickFormQuestionDefinition & RadioProperties | - QuickFormQuestionDefinition & SliderProperties | - QuickFormQuestionDefinition & TextProperties; \ No newline at end of file +export type QuestionJsonModel = QuickFormQuestionDefinition + | QuickFormQuestionDefinition & EmailProperties + | QuickFormQuestionDefinition & MultilineProperties + | QuickFormQuestionDefinition & TextProperties; \ No newline at end of file diff --git a/packages/core/src/services/defaults/DefaultInputTypeResolver.ts b/packages/core/src/services/defaults/DefaultInputTypeResolver.ts index 3f51005..dbbfe3f 100644 --- a/packages/core/src/services/defaults/DefaultInputTypeResolver.ts +++ b/packages/core/src/services/defaults/DefaultInputTypeResolver.ts @@ -1,5 +1,5 @@ import { FC } from "react"; -import { RadioProperties, SliderProperties, ButtonsProperties, InputPropertiesTypes, InputProps } from "../../model"; +import { InputPropertiesTypes, InputProps } from "../../model"; import { QuestionJsonModel } from "../../model/json-definitions/JsonDataModels"; import { registerQuickFormService } from "../QuickFormServices"; @@ -19,23 +19,7 @@ const parseInputProperties = (questionJsonModel: QuestionJsonModel): InputProper .map(([key, schema]) => [key, questionJsonModel[key as keyof QuestionJsonModel] ?? getDefaultValue(schema)])) as InputPropertiesTypes; } - const inputTypePropertiesMap: { [key: string]: () => InputPropertiesTypes } = { - buttons: () => ({ - inputType, - options: (questionJsonModel as QuestionJsonModel & ButtonsProperties).options - }), - radio: () => ({ - inputType, - options: (questionJsonModel as QuestionJsonModel & RadioProperties).options, - direction: (questionJsonModel as QuestionJsonModel & RadioProperties).direction - }), - slider: () => ({ - inputType, - min: (questionJsonModel as QuestionJsonModel & SliderProperties).min, - max: (questionJsonModel as QuestionJsonModel & SliderProperties).max, - step: (questionJsonModel as QuestionJsonModel & SliderProperties).step - }), - }; + const inputTypePropertiesMap: { [key: string]: () => InputPropertiesTypes } = {}; return inputType in inputTypePropertiesMap ? inputTypePropertiesMap[inputType]() : {}; }; @@ -85,9 +69,6 @@ const ThrowIfUsed: InputComponentType = (props) => { throw new Error("Not regist const inputComponents: InputComponentDictionary = { text: ThrowIfUsed, none: ThrowIfUsed, - dropdown: ThrowIfUsed, - slider: ThrowIfUsed, - toggle: ThrowIfUsed, multilinetext: ThrowIfUsed }; diff --git a/packages/core/src/services/defaults/DefaultInputValidator.ts b/packages/core/src/services/defaults/DefaultInputValidator.ts index 9961040..5564fd2 100644 --- a/packages/core/src/services/defaults/DefaultInputValidator.ts +++ b/packages/core/src/services/defaults/DefaultInputValidator.ts @@ -1,5 +1,5 @@ import { ValidationResult } from "../../model/ValidationResult"; -import { InputPropertiesTypes, QuestionModel, SliderProperties } from "../../model"; +import { InputPropertiesTypes, QuestionModel } from "../../model"; import { registerQuickFormService } from "../QuickFormServices"; const validateText = (output: any): ValidationResult => { @@ -36,8 +36,8 @@ const validateEmail = (output: any): ValidationResult => { }; const validatePhone = async (output: any): Promise => { - // Wait for 4 seconds - await new Promise(resolve => setTimeout(resolve, 4000)); + // Wait for 2 seconds to demo + await new Promise(resolve => setTimeout(resolve, 2000)); const phoneRegex = /^[0-9]{8,}$/; const valid = typeof output === 'string' && phoneRegex.test(output); @@ -49,16 +49,6 @@ const validatePhone = async (output: any): Promise => { }; }; - -const validateSlider = (output: any, properties: SliderProperties): ValidationResult => { - const valid = typeof output === 'number' && output >= properties.min && output <= properties.max; - return { - isValid: valid, - message: valid ? "" : `Value must be a number between ${properties.min} and ${properties.max}.`, - validatedOutput: output, - }; -}; - type ValidatorMap = { [inputType: string]: (output: any, properties?: any) => Promise; }; @@ -66,7 +56,6 @@ type ValidatorMap = { const validatorMap: ValidatorMap = { email: (output: any) => Promise.resolve(validateEmail(output)), phone: (output: any) => Promise.resolve(validatePhone(output)), - slider: (output: any, properties: SliderProperties) => Promise.resolve(validateSlider(output, properties)), text: (output: any) => Promise.resolve(validateText(output)), multilinetext: (output: any) => Promise.resolve(validateMultilineText(output)) }; @@ -82,7 +71,6 @@ const validateQuestionOutput = async (quest isValidating: false, timestamp: new Date().getTime() }); - // return Promise.resolve({ isValid: false, message: `No validator available for inputType: ${questionModel.inputType}`, validatedOutput: questionModel.output }); } return await validator(questionModel.output, questionModel.inputProperties); diff --git a/packages/core/src/style/QuickForm.css b/packages/core/src/style/QuickForm.css deleted file mode 100644 index a3029fa..0000000 --- a/packages/core/src/style/QuickForm.css +++ /dev/null @@ -1,6 +0,0 @@ -:root { - /* Font family */ - --font-family: 'var(--chivo), Monaco, Consolas'; - --font-family: 'Nunito', sans-serif; - font-family: var(--font-family); -} \ No newline at end of file diff --git a/packages/playground/src/components/buttons-input/ButtonsInput.tsx b/packages/playground/src/components/buttons-input/ButtonsInput.tsx index beeb67b..3ddc88a 100644 --- a/packages/playground/src/components/buttons-input/ButtonsInput.tsx +++ b/packages/playground/src/components/buttons-input/ButtonsInput.tsx @@ -1,14 +1,26 @@ "use client"; import { useState } from "react"; import { InputComponentType, registerInputComponent, useQuickForm } from "@eavfw/quickform-core"; -import { RadioProperties } from "@eavfw/quickform-core/src/model"; import { buttonsInputSchema } from "./ButtonsInputSchema"; -export const ButtonsInput: InputComponentType = ({ questionModel, options }) => { +type buttonProps = { key: string, label: string }; + +type ButtonsProperties = { + inputType: "buttons"; + options: { + [key: string]: { + key: string; + label: string; + } + } + defaultValue?: string; +} + +export const ButtonsInput: InputComponentType = ({ questionModel, options }) => { const { answerQuestion } = useQuickForm(); - const [selectedValue, setSelectedValue] = useState(questionModel.output); + const [selectedValue, setSelectedValue] = useState(questionModel.output); - const handleChange = (value: string) => { + const handleChange = (value: buttonProps) => { setSelectedValue(value); answerQuestion(questionModel.logicalName, value); }; diff --git a/packages/playground/src/components/radio-input/RadioInput.tsx b/packages/playground/src/components/radio-input/RadioInput.tsx index 6461dd9..c3fc7b9 100644 --- a/packages/playground/src/components/radio-input/RadioInput.tsx +++ b/packages/playground/src/components/radio-input/RadioInput.tsx @@ -1,8 +1,14 @@ import React, { useState } from "react"; import { InputComponentType, registerInputComponent, useQuickForm } from "@eavfw/quickform-core"; -import { RadioProperties } from "@eavfw/quickform-core/src/model"; import { radioInputSchema } from "./RadioInputSchema"; +type RadioProperties = { + inputType: "radio"; + options: { + [key: string]: string; + } + direction?: "horizontal" | "vertical"; +} export const RadioInput: InputComponentType = ({ questionModel, options, direction, }) => { const { answerQuestion } = useQuickForm(); @@ -41,4 +47,4 @@ export const RadioInput: InputComponentType = ({ questionModel, }; RadioInput.inputSchema = radioInputSchema; -registerInputComponent("radio", RadioInput); +registerInputComponent("radio", RadioInput); \ No newline at end of file diff --git a/packages/playground/src/components/slider-input/SliderInput.tsx b/packages/playground/src/components/slider-input/SliderInput.tsx index 3a9e155..93ce4b5 100644 --- a/packages/playground/src/components/slider-input/SliderInput.tsx +++ b/packages/playground/src/components/slider-input/SliderInput.tsx @@ -1,8 +1,15 @@ import React, { ChangeEvent, useState } from "react"; import { InputComponentType, registerInputComponent, useQuickForm } from "@eavfw/quickform-core"; -import { SliderProperties } from "@eavfw/quickform-core/src/model"; import { sliderInputSchema } from "./SliderInputSchema"; +type SliderProperties = { + inputType: "slider"; + min: number; + max: number; + step: number; + defaultValue?: number; +} + export const SliderInput: InputComponentType = ({ questionModel, max = 100, min = 0, step = 1, }) => { const { answerQuestion } = useQuickForm(); From 3b0e7313efa75cd6dfa65531c4e8a290758d1215 Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Fri, 12 Apr 2024 07:19:06 +0200 Subject: [PATCH 2/3] fix: updated <'ErrorPopup /> so it now blurs background and pops up mid-screen --- .../src/components/error-popup/ErrorPopup.tsx | 91 ++++++++++--------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/packages/core/src/components/error-popup/ErrorPopup.tsx b/packages/core/src/components/error-popup/ErrorPopup.tsx index ffaaa56..b64b319 100644 --- a/packages/core/src/components/error-popup/ErrorPopup.tsx +++ b/packages/core/src/components/error-popup/ErrorPopup.tsx @@ -1,6 +1,4 @@ -"use client"; import React, { useState, useEffect } from 'react'; -import { useDelayedClickListener } from "../../hooks"; import { useQuickForm } from "../../state/QuickFormContext"; import { quickformtokens } from '../../style/quickFormTokensDefinition'; @@ -8,65 +6,68 @@ type ErrorPopupProps = { readonly message: string; }; -export const ErrorPopup: React.FC = ({ message }: ErrorPopupProps) => { +export const ErrorPopup: React.FC = ({ message }) => { const [isVisible, setIsVisible] = useState(false); - const { dispatch, state } = useQuickForm(); + const [opacity, setOpacity] = useState(0); + const { dispatch } = useQuickForm(); - /** - * DISCUSS - What cases is there for resetting error and can it be handled in reducer all alone?. - * When an error is shown, upon next answer it can be cleared. - * Possible a dissmis button - but i dont think it should automatically just remove when clicked. - */ - - - //const resetErrorMessage = () => { - // if (state.errorMsg !== "") { - // dispatch({ type: "SET_ERROR_MSG", msg: "" }) - // } - //} - - // useDelayedClickListener(resetErrorMessage); + const resetErrorPopup = () => { + dispatch({ type: 'SET_ERROR_MSG', msg: "" }); + setIsVisible(false); + setOpacity(0); + } useEffect(() => { + let timer: NodeJS.Timeout; if (message) { setIsVisible(true); - setTimeout(() => setIsVisible(false), 350); + setOpacity(0); + setTimeout(() => setOpacity(1), 10); + + timer = setTimeout(() => { + resetErrorPopup(); + }, 3000); + } else { + resetErrorPopup(); } + + return () => { + clearTimeout(timer); + setOpacity(0); + }; }, [message]); - if (message === "") { - return <>; - } + if (!isVisible) return null; - const errorStyle: React.CSSProperties = { - alignItems: 'flex-end', - animation: isVisible ? 'slide-up 0.35s linear 1 forwards' : '', - backgroundColor: quickformtokens.error, - borderRadius: '3px', - color: quickformtokens.onError, - display: 'flex', - fontSize: '1.5rem', - marginTop: '15px', - padding: '8px 12px', - width: 'max-content', - }; - - const mobileErrorStyle: React.CSSProperties = { - ...errorStyle, - fontSize: '1.75rem', - marginTop: '22px', + const backdropStyle: React.CSSProperties = { + position: 'fixed', + top: 0, + left: 0, width: '100%', + height: '100%', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 1000, }; - const imgStyle: React.CSSProperties = { - marginRight: '4px', + const toastStyle: React.CSSProperties = { + padding: '20px', + borderRadius: '10px', + backgroundColor: quickformtokens.error, + color: quickformtokens.onError, + fontSize: '16px', + boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', + opacity: opacity, + transition: 'opacity 0.5s ease-in', }; return ( -
- {/* If there's an image you want to include inside the error message */} - {/* Error */} - {message} +
+
e.stopPropagation()}> + {message} +
); }; From 4c8448d0eb6c05c49892e90ef91044de7f76e1ff Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Fri, 12 Apr 2024 08:30:16 +0200 Subject: [PATCH 3/3] fix: updated validatortypes --- .../core/src/components/question/Question.tsx | 2 +- packages/core/src/services/QuickFormServices.ts | 3 ++- .../services/defaults/DefaultInputValidator.ts | 13 ++++++++----- packages/core/src/state/QuickformProvider.tsx | 6 ++---- .../action-handlers/QuestionActionHandler.ts | 15 +++++++++++---- .../core/src/utils/isFirstQuestionInSlide.ts | 17 +++++++++++++++++ .../DropdownSelectOption.tsx | 4 ++-- 7 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 packages/core/src/utils/isFirstQuestionInSlide.ts diff --git a/packages/core/src/components/question/Question.tsx b/packages/core/src/components/question/Question.tsx index 158de99..67e1fbe 100644 --- a/packages/core/src/components/question/Question.tsx +++ b/packages/core/src/components/question/Question.tsx @@ -65,7 +65,7 @@ export const Question: React.FC = ({ model, style }) => { questionModel={model} {...model.inputProperties ?? {}} /> - {model.validationResult?.message !== "" && } + {typeof (model.validationResult?.message) !== "undefined" && model.validationResult?.message !== "" && }
); } \ No newline at end of file diff --git a/packages/core/src/services/QuickFormServices.ts b/packages/core/src/services/QuickFormServices.ts index 8d5a156..4dddaae 100644 --- a/packages/core/src/services/QuickFormServices.ts +++ b/packages/core/src/services/QuickFormServices.ts @@ -4,13 +4,14 @@ import { InputPropertiesTypes, QuestionModel, QuickFormModel } from "../model"; import { QuickFormDefinition } from "../model"; import { QuestionJsonModel } from "../model/json-definitions/JsonDataModels"; import { InputComponentType } from "./defaults/DefaultInputTypeResolver"; +import { QuickformState } from "../state"; export type HeadingNumberDisplayProvider = () => boolean; export type QuickFormModelTransformer = (data: QuickFormDefinition, payload: any) => QuickFormModel; export type QuestionTransformer = (key: string, question: QuestionJsonModel, value?: any, visible?: { type: string; rule: string; }) => QuestionModel; export type InputTypePropertiesTransformer = (questionJsonModel: QuestionJsonModel) => InputPropertiesTypes | undefined; export type RegisterInputTypeComponent = (key: string, component: InputComponentType) => void; -export type InputValidator = (questionModel: QuestionModel) => Promise; +export type InputValidator = (questionModel: QuestionModel, state: QuickformState) => Promise; export interface IQuickFormLogger { log(body: string, ...args: any[]): void; warn(body: string, ...args: any[]): void; diff --git a/packages/core/src/services/defaults/DefaultInputValidator.ts b/packages/core/src/services/defaults/DefaultInputValidator.ts index 5564fd2..e9c3bb3 100644 --- a/packages/core/src/services/defaults/DefaultInputValidator.ts +++ b/packages/core/src/services/defaults/DefaultInputValidator.ts @@ -1,6 +1,7 @@ import { ValidationResult } from "../../model/ValidationResult"; import { InputPropertiesTypes, QuestionModel } from "../../model"; import { registerQuickFormService } from "../QuickFormServices"; +import { QuickformState } from "../../state"; const validateText = (output: any): ValidationResult => { const text = typeof output === 'string' ? output.trim() : ''; @@ -50,7 +51,7 @@ const validatePhone = async (output: any): Promise => { }; type ValidatorMap = { - [inputType: string]: (output: any, properties?: any) => Promise; + [inputType: string]: ValidatorFunction, QuickformState>; }; const validatorMap: ValidatorMap = { @@ -60,7 +61,7 @@ const validatorMap: ValidatorMap = { multilinetext: (output: any) => Promise.resolve(validateMultilineText(output)) }; -const validateQuestionOutput = async (questionModel: QuestionModel): Promise => { +const validateQuestionOutput = async (questionModel: QuestionModel, state: QuickformState): Promise => { const validator = validatorMap[questionModel.inputType]; if (!validator) { // This is to support if no validation is created for inputtype.. defaults to validated.. @@ -73,11 +74,13 @@ const validateQuestionOutput = async (quest }); } - return await validator(questionModel.output, questionModel.inputProperties); + return await validator(questionModel.output, questionModel.inputProperties, questionModel,state); }; -export const registerInputTypeValidator = (key: string, validator: (output: any, properties?: any) => Promise) => { - validatorMap[key] = validator; +export type ValidatorFunction, TQuickFormState extends QuickformState> = (output: TAnswer, properties: TInputProps, questionModel: TQuestionModel, state: TQuickFormState) => Promise; + +export const registerInputTypeValidator = , TQuickFormState extends QuickformState>(key: string, validator: ValidatorFunction) => { + validatorMap[key] = validator as ValidatorFunction, QuickformState>; }; registerQuickFormService("inputValidator", validateQuestionOutput); \ No newline at end of file diff --git a/packages/core/src/state/QuickformProvider.tsx b/packages/core/src/state/QuickformProvider.tsx index 4d7d10f..41127d0 100644 --- a/packages/core/src/state/QuickformProvider.tsx +++ b/packages/core/src/state/QuickformProvider.tsx @@ -7,6 +7,7 @@ import { QuickFormTokens, defineQuickFormTokens } from "../style/quickFormTokens import { QuickFormDefinition } from "../model"; import { resolveQuickFormService } from "../services/QuickFormServices"; import { kbaQuickFormTokens } from "../style/kbaQuickFormTokens"; +import { isFirstQInCurrentSlide } from "../utils/isFirstQuestionInSlide"; type QuickFormProviderProps = { children: React.ReactNode; @@ -47,10 +48,7 @@ export const QuickFormProvider: React.FC = ( } const setIntroVisited = () => { dispatch({ type: 'SET_INTRO_VISITED' }) }; const setErrorMsg = (msg: string) => { dispatch({ type: "SET_ERROR_MSG", msg: msg }) }; - const isFirstQuestionInCurrentSlide = (questionLogicalName: string) => { - const currSlide = state.slides[state.currIdx]; - return currSlide.questions && currSlide.questions.length > 0 && currSlide.questions[0].logicalName === questionLogicalName && currSlide.questions[0].visited !== true; - } + const isFirstQuestionInCurrentSlide = (questionLogicalName: string) => { return isFirstQInCurrentSlide(questionLogicalName, state); } const getCurrentSlide = () => (state.slides[state.currIdx]); return ( diff --git a/packages/core/src/state/action-handlers/QuestionActionHandler.ts b/packages/core/src/state/action-handlers/QuestionActionHandler.ts index 278839f..37d1bbb 100644 --- a/packages/core/src/state/action-handlers/QuestionActionHandler.ts +++ b/packages/core/src/state/action-handlers/QuestionActionHandler.ts @@ -68,7 +68,7 @@ export class QuestionActionHandler { }; static startQuestionValidation = (state: QuickformState, logicalName: string, timestamp: number) => { - const currentValidationResult = findQuestionByLogicalName(logicalName, getAllQuestions(state.slides)).validationResult; + const currentValidationResult = findQuestionByLogicalName(logicalName, getAllQuestions(state.slides))?.validationResult; return this.updateQuestionProperties(state, logicalName, { validationResult: { ...currentValidationResult, timestamp: timestamp, isValidating: true, isValid: false } @@ -76,8 +76,8 @@ export class QuestionActionHandler { }; static updateQuestionValidation = (state: QuickformState, logicalName: string, validationResult: ValidationResult, timestamp: number) => { - const currentValidationResult = findQuestionByLogicalName(logicalName, getAllQuestions(state.slides)).validationResult; - if (currentValidationResult.timestamp !== timestamp) { + const currentValidationResult = findQuestionByLogicalName(logicalName, getAllQuestions(state.slides))?.validationResult; + if (currentValidationResult?.timestamp !== timestamp) { return state; } return this.updateQuestionProperties(state, logicalName, { @@ -87,6 +87,13 @@ export class QuestionActionHandler { static async validateInput(state: QuickformState, logicalName: string): Promise { const questionRef = findQuestionByKey(logicalName, getAllQuestions(state.slides)); - return await QuestionActionHandler.inputValidator(questionRef); + if (!questionRef) { + return { + isValid: false, + message: 'Question not valid', + validatedOutput: '' + } + } + return await QuestionActionHandler.inputValidator(questionRef, state); } } \ No newline at end of file diff --git a/packages/core/src/utils/isFirstQuestionInSlide.ts b/packages/core/src/utils/isFirstQuestionInSlide.ts new file mode 100644 index 0000000..a2f43c8 --- /dev/null +++ b/packages/core/src/utils/isFirstQuestionInSlide.ts @@ -0,0 +1,17 @@ +import { QuickformState } from "../state"; + +/** + * Determines if the provided question is the first in the current slide and if it should receive autofocus. + * The method checks if any question on the slide has been visited, and if so, it will not autofocus the first question. + * @param questionLogicalName The logical name of the question to check. + * @returns boolean indicating if the question is the first and should be autofocused. + */ +export const isFirstQInCurrentSlide = (questionLogicalName: string, state: QuickformState): boolean => { + const currSlide = state.slides[state.currIdx]; + if (currSlide.questions && currSlide.questions.length > 0) { + const isFirstQuestion = currSlide.questions[0].logicalName === questionLogicalName; + const anyQuestionVisited = currSlide.questions.some(q => q.visited); + return isFirstQuestion && !anyQuestionVisited; + } + return false; +} diff --git a/packages/inputs/select/src/dropdown-options-list/dropdown-select-option/DropdownSelectOption.tsx b/packages/inputs/select/src/dropdown-options-list/dropdown-select-option/DropdownSelectOption.tsx index e6bfc21..83a9024 100644 --- a/packages/inputs/select/src/dropdown-options-list/dropdown-select-option/DropdownSelectOption.tsx +++ b/packages/inputs/select/src/dropdown-options-list/dropdown-select-option/DropdownSelectOption.tsx @@ -2,9 +2,9 @@ import { MouseEventHandler, ReactNode } from "react"; import classNames from "classnames"; import styles from "./DropdownSelectOption.module.css"; import { makeStyles, mergeClasses } from "@griffel/react"; -import { quickformtokens } from "@eavfw/quickform-core/src/style/quickformtokens"; import { Checkmark } from "@eavfw/quickform-core/src/components/icons"; import { shorthands } from "@fluentui/react-components"; +import { quickformtokens } from "@eavfw/quickform-core/src/style/quickFormTokensDefinition"; type DropdownSelectOptionProps = { readonly isSelected?: boolean; @@ -35,7 +35,7 @@ const useDropDownSelectOptionStyles = makeStyles({ color: quickformtokens.onSurface, backgroundColor: 'transparent', - ...shorthands.border('1px', 'solid', quickformtokens.borderColor), + ...shorthands.border('1px', 'solid', quickformtokens.primary), ...shorthands.borderRadius('5px'), ':hover': { color: quickformtokens.onSurface,