fix: multi-select custom answer toggling

This commit is contained in:
Erik Kotsiuba
2026-05-03 22:13:30 +02:00
parent f86f3a578e
commit 780aac24cf
7 changed files with 112 additions and 11 deletions
+22 -4
View File
@@ -17,6 +17,7 @@ export function emptyAnswer(): AskStateAnswer {
export function cloneAnswer(answer: AskStateAnswer): AskStateAnswer {
return {
selected: answer.selected.map(cloneSelection),
customSelected: answer.customSelected,
customText: answer.customText,
note: answer.note,
optionNotes: answer.optionNotes ? { ...answer.optionNotes } : undefined,
@@ -87,13 +88,24 @@ export function saveCustomText(
next.selected = [];
}
if (!trimmed) {
next.customSelected = undefined;
next.customText = undefined;
return next;
}
next.customSelected = true;
next.customText = rawValue;
return next;
}
export function setCustomSelected(
answer: AskStateAnswer,
selected: boolean
): AskStateAnswer {
const next = cloneAnswer(answer);
next.customSelected = selected || undefined;
return next;
}
export function saveQuestionNote(
answer: AskStateAnswer,
rawValue: string
@@ -137,7 +149,10 @@ export function isAnswerAnswered(answer?: AskStateAnswer): boolean {
if (!answer) {
return false;
}
return answer.selected.length > 0 || !!answer.customText?.trim();
return (
answer.selected.length > 0 ||
!!(answer.customSelected && answer.customText?.trim())
);
}
export function hasAnswerNotes(answer?: AskStateAnswer): boolean {
@@ -178,13 +193,16 @@ export function isOptionSelected(
}
export function serializeAnswer(answer: AskStateAnswer): AskResultAnswer {
const selectedCustomText = answer.customSelected
? answer.customText
: undefined;
const values = [
...answer.selected.map((selection) => selection.value),
...(answer.customText ? [answer.customText] : []),
...(selectedCustomText ? [selectedCustomText] : []),
];
const labels = [
...answer.selected.map((selection) => selection.label),
...(answer.customText ? [answer.customText] : []),
...(selectedCustomText ? [selectedCustomText] : []),
];
const indices = answer.selected.map((selection) => selection.index);
const selectedNotes = answer.optionNotes
@@ -202,7 +220,7 @@ export function serializeAnswer(answer: AskStateAnswer): AskResultAnswer {
values,
labels,
indices,
customText: answer.customText,
customText: selectedCustomText,
note: answer.note,
optionNotes:
selectedNotes && Object.keys(selectedNotes).length > 0
+23 -2
View File
@@ -6,6 +6,7 @@ import {
saveCustomText,
saveOptionNote,
saveQuestionNote,
setCustomSelected,
setSingleSelection,
toggleSelection,
} from "./answers.ts";
@@ -187,7 +188,7 @@ function activateCurrentOption(
return state;
}
if (option.isCustomOption) {
return setView(state, inputView(question.id));
return activateCustomOption(state, question.id, question.type, trigger);
}
if (question.type === "multi") {
if (trigger === "confirm") {
@@ -215,6 +216,23 @@ function activateCurrentOption(
return trigger === "toggle" ? nextState : advanceToNextTab(nextState);
}
function activateCustomOption(
state: AskState,
questionId: string,
questionType: AskState["questions"][number]["type"],
trigger: "toggle" | "confirm" | "digit"
): AskState {
if (questionType === "multi" && trigger !== "confirm") {
const answer = getAnswer(state, questionId);
if (answer?.customText?.trim()) {
return updateAnswer(state, questionId, (currentAnswer) =>
setCustomSelected(currentAnswer, !answer.customSelected)
);
}
}
return setView(state, inputView(questionId));
}
function saveInputValue(
state: AskState,
rawValue: string,
@@ -239,7 +257,10 @@ function saveInputValue(
question.type === "multi" ? "multi" : "single"
)
);
if (!(submit && isAnswerAnswered(nextState.answers[question.id]))) {
if (
question.type === "multi" ||
!(submit && isAnswerAnswered(nextState.answers[question.id]))
) {
return nextState;
}
return advanceToNextTab(nextState);
+1
View File
@@ -46,6 +46,7 @@ export interface AskSelectedOption {
}
export interface AskStateAnswer {
customSelected?: boolean;
customText?: string;
note?: string;
optionNotes?: Record<string, string>;
+3 -3
View File
@@ -88,7 +88,7 @@ function buildOptionRowModel(
const answer = getAnswer(state, question.id);
const selected = index === state.activeOptionIndex;
const answered = option.isCustomOption
? !!answer?.customText
? !!(answer?.customSelected && answer.customText?.trim())
: isOptionSelected(answer, option.value);
const pointer = getOptionPointer(option, selected);
return {
@@ -180,10 +180,10 @@ function getPreviewLayout(
function getOptionPrefix(
questionType: QuestionRenderContext["question"]["type"],
option: AskDisplayOption,
_option: AskDisplayOption,
answered: boolean
): string {
if (questionType !== "multi" || option.isCustomOption) {
if (questionType !== "multi") {
return "";
}
return `[${answered ? "x" : " "}] `;