dexto 1.6.0 → 1.6.1
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.
- package/dist/agents/coding-agent/coding-agent.yml +3 -1
- package/dist/cli/assets/sounds/SOURCES.md +35 -0
- package/dist/cli/assets/sounds/boot.wav +0 -0
- package/dist/cli/assets/sounds/chime.wav +0 -0
- package/dist/cli/assets/sounds/coin.wav +0 -0
- package/dist/cli/assets/sounds/confirm.wav +0 -0
- package/dist/cli/assets/sounds/levelup.wav +0 -0
- package/dist/cli/assets/sounds/ping.wav +0 -0
- package/dist/cli/assets/sounds/powerup.wav +0 -0
- package/dist/cli/assets/sounds/startup.wav +0 -0
- package/dist/cli/assets/sounds/success.wav +0 -0
- package/dist/cli/assets/sounds/treasure.wav +0 -0
- package/dist/cli/assets/sounds/win.wav +0 -0
- package/dist/cli/commands/interactive-commands/exit-handler.d.ts +12 -0
- package/dist/cli/commands/interactive-commands/exit-handler.d.ts.map +1 -0
- package/dist/cli/commands/interactive-commands/exit-handler.js +20 -0
- package/dist/cli/commands/interactive-commands/exit-stats.d.ts +24 -0
- package/dist/cli/commands/interactive-commands/exit-stats.d.ts.map +1 -0
- package/dist/cli/commands/interactive-commands/exit-stats.js +17 -0
- package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/general-commands.js +53 -3
- package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.js +12 -67
- package/dist/cli/commands/interactive-commands/session/session-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/session/session-commands.js +0 -2
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts +1 -13
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/system/system-commands.js +45 -54
- package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/InkCLIRefactored.js +132 -21
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.js +74 -20
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts +5 -3
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ElicitationForm.js +414 -180
- package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ResourceAutocomplete.js +20 -11
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +47 -67
- package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/StatusBar.js +10 -4
- package/dist/cli/ink-cli/components/base/BaseSelector.d.ts +2 -1
- package/dist/cli/ink-cli/components/base/BaseSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/base/BaseSelector.js +37 -27
- package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/Header.js +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.js +3 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.js +5 -15
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +4 -2
- package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.js +9 -2
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts +13 -0
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.js +60 -0
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +213 -100
- package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptList.js +12 -16
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts +21 -0
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.js +566 -0
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +94 -39
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +8 -13
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts +3 -3
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +6 -5
- package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts +3 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.js +18 -7
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +7 -17
- package/dist/cli/ink-cli/components/renderers/index.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/index.js +1 -1
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts +7 -0
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.js +8 -0
- package/dist/cli/ink-cli/components/shared/HintBar.d.ts +6 -0
- package/dist/cli/ink-cli/components/shared/HintBar.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/shared/HintBar.js +6 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.d.ts +2 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.d.ts.map +1 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.js +1 -0
- package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
- package/dist/cli/ink-cli/constants/tips.js +1 -0
- package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/InputContainer.js +19 -15
- package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.js +21 -5
- package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts +11 -0
- package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts.map +1 -0
- package/dist/cli/ink-cli/hooks/useAnimationTick.js +54 -0
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
- package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
- package/dist/cli/ink-cli/services/processStream.js +17 -8
- package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
- package/dist/cli/ink-cli/state/initialState.js +1 -0
- package/dist/cli/ink-cli/state/types.d.ts +13 -1
- package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.d.ts +11 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.js +80 -0
- package/dist/cli/ink-cli/utils/index.d.ts +1 -1
- package/dist/cli/ink-cli/utils/index.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/index.js +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts +2 -17
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.js +22 -128
- package/dist/cli/ink-cli/utils/overlayPresentation.d.ts +19 -0
- package/dist/cli/ink-cli/utils/overlayPresentation.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/overlayPresentation.js +33 -0
- package/dist/cli/ink-cli/utils/overlaySizing.d.ts +19 -0
- package/dist/cli/ink-cli/utils/overlaySizing.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/overlaySizing.js +11 -0
- package/dist/cli/ink-cli/utils/soundNotification.d.ts +19 -13
- package/dist/cli/ink-cli/utils/soundNotification.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/soundNotification.js +120 -97
- package/dist/utils/session-logger-factory.d.ts.map +1 -1
- package/dist/utils/session-logger-factory.js +17 -2
- package/dist/webui/assets/{index-DwtueA8l.js → index-CKhumsZA.js} +135 -135
- package/dist/webui/index.html +1 -1
- package/package.json +11 -11
|
@@ -1,203 +1,335 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* ElicitationForm Component
|
|
4
|
-
* Renders a form for ask_user/elicitation requests in the CLI
|
|
5
|
-
*
|
|
4
|
+
* Renders a form for ask_user/elicitation requests in the CLI.
|
|
5
|
+
*
|
|
6
|
+
* Uses a wizard flow (one question at a time) to avoid huge modals and improve
|
|
7
|
+
* usability on small terminals.
|
|
6
8
|
*/
|
|
7
9
|
import { useState, forwardRef, useImperativeHandle, useCallback, useMemo } from 'react';
|
|
8
10
|
import { Box, Text } from 'ink';
|
|
11
|
+
import wrapAnsi from 'wrap-ansi';
|
|
12
|
+
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
|
13
|
+
import { parseElicitationSchema } from '../utils/elicitationSchema.js';
|
|
14
|
+
function hasOwn(obj, key) {
|
|
15
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
16
|
+
}
|
|
17
|
+
function getDisplayValue(value) {
|
|
18
|
+
if (value === undefined || value === null || value === '')
|
|
19
|
+
return '—';
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return value.length > 0 ? value.join(', ') : '—';
|
|
22
|
+
if (value === true)
|
|
23
|
+
return 'Yes';
|
|
24
|
+
if (value === false)
|
|
25
|
+
return 'No';
|
|
26
|
+
return String(value ?? '');
|
|
27
|
+
}
|
|
28
|
+
function clamp(value, min, max) {
|
|
29
|
+
return Math.max(min, Math.min(max, value));
|
|
30
|
+
}
|
|
9
31
|
/**
|
|
10
32
|
* Form component for elicitation/ask_user requests
|
|
11
33
|
*/
|
|
12
34
|
export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref) => {
|
|
13
|
-
|
|
35
|
+
const { rows: terminalRows, columns: terminalColumns } = useTerminalSize();
|
|
36
|
+
const headerLineCount = 1;
|
|
37
|
+
const stepHeaderLineCount = 2; // hard cap: 2 lines
|
|
38
|
+
const spacerAfterStepLineCount = 1;
|
|
39
|
+
const questionLineCount = 2;
|
|
40
|
+
const helpLineCount = 2;
|
|
41
|
+
const errorLineCount = 1;
|
|
42
|
+
const questionHeaderHeight = headerLineCount +
|
|
43
|
+
stepHeaderLineCount +
|
|
44
|
+
spacerAfterStepLineCount +
|
|
45
|
+
questionLineCount +
|
|
46
|
+
helpLineCount +
|
|
47
|
+
errorLineCount;
|
|
48
|
+
const reviewHeaderHeight = headerLineCount + spacerAfterStepLineCount;
|
|
49
|
+
const maxHeaderHeight = Math.max(questionHeaderHeight, reviewHeaderHeight);
|
|
50
|
+
const footerHeight = 1; // key hints
|
|
51
|
+
const minContentHeight = 4;
|
|
52
|
+
const viewportHeight = useMemo(() => {
|
|
53
|
+
// Ink clears + redraws when dynamic output height >= terminal rows, which looks like flicker.
|
|
54
|
+
// Keep the elicitation UI small and scroll internally to stay under that threshold.
|
|
55
|
+
// Leave slack so Ink doesn't hit the "clear + redraw everything" path.
|
|
56
|
+
// (Ink clears when dynamic output height >= terminal rows.)
|
|
57
|
+
const reservedRows = 8;
|
|
58
|
+
const minViewportHeight = maxHeaderHeight + footerHeight + minContentHeight;
|
|
59
|
+
const maxHeight = Math.max(minViewportHeight, terminalRows - reservedRows);
|
|
60
|
+
const desired = Math.max(minViewportHeight, Math.floor(terminalRows * 0.6));
|
|
61
|
+
return Math.min(maxHeight, desired);
|
|
62
|
+
}, [footerHeight, maxHeaderHeight, minContentHeight, terminalRows]);
|
|
63
|
+
const availableWidth = Math.max(20, terminalColumns - 2);
|
|
14
64
|
const fields = useMemo(() => {
|
|
15
|
-
|
|
16
|
-
|
|
65
|
+
return parseElicitationSchema(metadata.schema);
|
|
66
|
+
}, [metadata.schema]);
|
|
67
|
+
const wrapClampedLines = useCallback((text, maxLines) => {
|
|
68
|
+
if (maxLines <= 0)
|
|
17
69
|
return [];
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let type = 'string';
|
|
23
|
-
let enumValues;
|
|
24
|
-
if (prop.type === 'boolean') {
|
|
25
|
-
type = 'boolean';
|
|
26
|
-
}
|
|
27
|
-
else if (prop.type === 'number' || prop.type === 'integer') {
|
|
28
|
-
type = 'number';
|
|
29
|
-
}
|
|
30
|
-
else if (prop.enum && Array.isArray(prop.enum)) {
|
|
31
|
-
type = 'enum';
|
|
32
|
-
enumValues = prop.enum;
|
|
33
|
-
}
|
|
34
|
-
else if (prop.type === 'array' &&
|
|
35
|
-
typeof prop.items === 'object' &&
|
|
36
|
-
prop.items &&
|
|
37
|
-
'enum' in prop.items) {
|
|
38
|
-
type = 'array-enum';
|
|
39
|
-
enumValues = prop.items.enum;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
name,
|
|
43
|
-
label: prop.title || name,
|
|
44
|
-
type,
|
|
45
|
-
description: prop.description,
|
|
46
|
-
required: required.includes(name),
|
|
47
|
-
enumValues,
|
|
48
|
-
};
|
|
70
|
+
const wrapped = wrapAnsi(text, availableWidth, {
|
|
71
|
+
hard: true,
|
|
72
|
+
wordWrap: true,
|
|
73
|
+
trim: false,
|
|
49
74
|
});
|
|
50
|
-
|
|
75
|
+
const rawLines = wrapped.length > 0 ? wrapped.split('\n') : [''];
|
|
76
|
+
const didTruncate = rawLines.length > maxLines;
|
|
77
|
+
const lines = rawLines.slice(0, maxLines);
|
|
78
|
+
if (didTruncate && lines.length > 0) {
|
|
79
|
+
const lastIndex = lines.length - 1;
|
|
80
|
+
const lastLine = (lines[lastIndex] ?? '').replace(/\s+$/, '');
|
|
81
|
+
const safe = lastLine.length > 0
|
|
82
|
+
? `${lastLine.slice(0, Math.max(0, lastLine.length - 1))}…`
|
|
83
|
+
: '…';
|
|
84
|
+
lines[lastIndex] = safe;
|
|
85
|
+
}
|
|
86
|
+
while (lines.length < maxLines) {
|
|
87
|
+
lines.push('');
|
|
88
|
+
}
|
|
89
|
+
return lines;
|
|
90
|
+
}, [availableWidth]);
|
|
51
91
|
// Form state
|
|
52
92
|
const [activeFieldIndex, setActiveFieldIndex] = useState(0);
|
|
53
93
|
const [formData, setFormData] = useState({});
|
|
54
|
-
const [
|
|
55
|
-
const [enumIndex, setEnumIndex] = useState(0); // For enum
|
|
56
|
-
const [arraySelections, setArraySelections] = useState(new Set()); //
|
|
94
|
+
const [draftInputs, setDraftInputs] = useState({});
|
|
95
|
+
const [enumIndex, setEnumIndex] = useState(0); // For enum/array-enum focus
|
|
96
|
+
const [arraySelections, setArraySelections] = useState(new Set()); // array-enum
|
|
57
97
|
const [errors, setErrors] = useState({});
|
|
58
|
-
const [isReviewing, setIsReviewing] = useState(false);
|
|
98
|
+
const [isReviewing, setIsReviewing] = useState(false);
|
|
99
|
+
const [reviewScrollTop, setReviewScrollTop] = useState(0);
|
|
59
100
|
const activeField = fields[activeFieldIndex];
|
|
60
|
-
|
|
101
|
+
const contentHeight = useMemo(() => {
|
|
102
|
+
const activeHeaderHeight = isReviewing ? reviewHeaderHeight : questionHeaderHeight;
|
|
103
|
+
return Math.max(1, viewportHeight - activeHeaderHeight - footerHeight);
|
|
104
|
+
}, [footerHeight, isReviewing, questionHeaderHeight, reviewHeaderHeight, viewportHeight]);
|
|
61
105
|
const updateField = useCallback((name, value) => {
|
|
62
106
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
63
107
|
setErrors((prev) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
108
|
+
if (!hasOwn(prev, name))
|
|
109
|
+
return prev;
|
|
110
|
+
const next = { ...prev };
|
|
111
|
+
delete next[name];
|
|
112
|
+
return next;
|
|
67
113
|
});
|
|
68
114
|
}, []);
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
const goToFieldIndex = useCallback((index, data = formData) => {
|
|
116
|
+
if (index < 0 || index >= fields.length)
|
|
117
|
+
return;
|
|
118
|
+
setActiveFieldIndex(index);
|
|
119
|
+
const field = fields[index];
|
|
120
|
+
if (!field)
|
|
121
|
+
return;
|
|
122
|
+
if (field.type === 'boolean') {
|
|
123
|
+
const currentValue = data[field.name];
|
|
124
|
+
setEnumIndex(currentValue === false ? 1 : 0);
|
|
125
|
+
setArraySelections(new Set());
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (field.type === 'enum') {
|
|
129
|
+
const values = field.enumValues ?? [];
|
|
130
|
+
const currentValue = data[field.name];
|
|
131
|
+
const currentIndex = values.findIndex((v) => v === currentValue);
|
|
132
|
+
setEnumIndex(currentIndex >= 0 ? currentIndex : 0);
|
|
133
|
+
setArraySelections(new Set());
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (field.type === 'array-enum') {
|
|
137
|
+
const values = field.enumValues ?? [];
|
|
138
|
+
const currentValue = data[field.name];
|
|
139
|
+
const currentValues = Array.isArray(currentValue) ? currentValue : [];
|
|
140
|
+
const selections = new Set();
|
|
141
|
+
for (const selectedValue of currentValues) {
|
|
142
|
+
const selectedIndex = values.findIndex((v) => v === selectedValue);
|
|
143
|
+
if (selectedIndex >= 0)
|
|
144
|
+
selections.add(selectedIndex);
|
|
145
|
+
}
|
|
146
|
+
setArraySelections(selections);
|
|
147
|
+
const firstSelected = selections.values().next().value;
|
|
148
|
+
const maxIndex = Math.max(0, values.length - 1);
|
|
149
|
+
setEnumIndex(Math.min(firstSelected ?? 0, maxIndex));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setEnumIndex(0);
|
|
153
|
+
setArraySelections(new Set());
|
|
154
|
+
}, [fields, formData]);
|
|
155
|
+
const nextField = useCallback(() => {
|
|
156
|
+
if (activeFieldIndex < fields.length - 1) {
|
|
157
|
+
goToFieldIndex(activeFieldIndex + 1);
|
|
158
|
+
}
|
|
159
|
+
}, [activeFieldIndex, fields.length, goToFieldIndex]);
|
|
160
|
+
const prevField = useCallback(() => {
|
|
161
|
+
if (activeFieldIndex > 0) {
|
|
162
|
+
goToFieldIndex(activeFieldIndex - 1);
|
|
163
|
+
}
|
|
164
|
+
}, [activeFieldIndex, goToFieldIndex]);
|
|
71
165
|
const handleSubmit = useCallback((currentFieldValue) => {
|
|
72
166
|
const newErrors = {};
|
|
73
|
-
// Merge current field value since React state update is async
|
|
74
167
|
const finalFormData = currentFieldValue
|
|
75
168
|
? { ...formData, [currentFieldValue.name]: currentFieldValue.value }
|
|
76
|
-
: formData;
|
|
169
|
+
: { ...formData };
|
|
170
|
+
// Incorporate draft inputs (wizard UX is forgiving if you navigate without pressing Enter).
|
|
171
|
+
for (const field of fields) {
|
|
172
|
+
if (!hasOwn(draftInputs, field.name))
|
|
173
|
+
continue;
|
|
174
|
+
const rawDraft = draftInputs[field.name] ?? '';
|
|
175
|
+
if (field.type === 'number') {
|
|
176
|
+
const trimmed = rawDraft.trim();
|
|
177
|
+
if (trimmed === '') {
|
|
178
|
+
finalFormData[field.name] = '';
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const parsed = Number(trimmed);
|
|
182
|
+
if (Number.isNaN(parsed)) {
|
|
183
|
+
newErrors[field.name] = 'Invalid number';
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
finalFormData[field.name] = parsed;
|
|
187
|
+
}
|
|
188
|
+
if (field.type === 'string') {
|
|
189
|
+
finalFormData[field.name] = rawDraft;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Ensure boolean fields are always present in submitted data.
|
|
77
193
|
for (const field of fields) {
|
|
78
|
-
if (field.required)
|
|
79
|
-
|
|
80
|
-
|
|
194
|
+
if (!field.required)
|
|
195
|
+
continue;
|
|
196
|
+
const value = finalFormData[field.name];
|
|
197
|
+
if (field.type === 'array-enum') {
|
|
198
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
81
199
|
newErrors[field.name] = 'Required';
|
|
82
200
|
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (value === undefined || value === null || value === '') {
|
|
204
|
+
newErrors[field.name] = 'Required';
|
|
83
205
|
}
|
|
84
206
|
}
|
|
85
207
|
if (Object.keys(newErrors).length > 0) {
|
|
86
208
|
setErrors(newErrors);
|
|
87
|
-
// Focus first error field
|
|
88
209
|
const firstErrorField = fields.findIndex((f) => newErrors[f.name]);
|
|
89
210
|
if (firstErrorField >= 0) {
|
|
90
|
-
|
|
211
|
+
goToFieldIndex(firstErrorField, finalFormData);
|
|
91
212
|
}
|
|
213
|
+
setIsReviewing(false);
|
|
92
214
|
return;
|
|
93
215
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setFormData(finalFormData);
|
|
97
|
-
}
|
|
216
|
+
setFormData(finalFormData);
|
|
217
|
+
setReviewScrollTop(0);
|
|
98
218
|
setIsReviewing(true);
|
|
99
|
-
}, [fields, formData]);
|
|
100
|
-
// Final submission after review
|
|
219
|
+
}, [draftInputs, fields, formData, goToFieldIndex]);
|
|
101
220
|
const confirmSubmit = useCallback(() => {
|
|
102
221
|
onSubmit(formData);
|
|
103
222
|
}, [formData, onSubmit]);
|
|
104
|
-
// Navigate to next/previous field
|
|
105
|
-
const nextField = useCallback(() => {
|
|
106
|
-
if (activeFieldIndex < fields.length - 1) {
|
|
107
|
-
// Save current input for string/number fields
|
|
108
|
-
if (activeField?.type === 'string' || activeField?.type === 'number') {
|
|
109
|
-
if (currentInput.trim()) {
|
|
110
|
-
const value = activeField.type === 'number' ? Number(currentInput) : currentInput;
|
|
111
|
-
updateField(activeField.name, value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
setActiveFieldIndex((prev) => prev + 1);
|
|
115
|
-
setCurrentInput('');
|
|
116
|
-
setEnumIndex(0);
|
|
117
|
-
setArraySelections(new Set());
|
|
118
|
-
}
|
|
119
|
-
}, [activeFieldIndex, fields.length, activeField, currentInput, updateField]);
|
|
120
|
-
const prevField = useCallback(() => {
|
|
121
|
-
if (activeFieldIndex > 0) {
|
|
122
|
-
setActiveFieldIndex((prev) => prev - 1);
|
|
123
|
-
setCurrentInput('');
|
|
124
|
-
setEnumIndex(0);
|
|
125
|
-
setArraySelections(new Set());
|
|
126
|
-
}
|
|
127
|
-
}, [activeFieldIndex]);
|
|
128
|
-
// Handle keyboard input
|
|
129
223
|
useImperativeHandle(ref, () => ({
|
|
130
224
|
handleInput: (input, key) => {
|
|
131
|
-
// Review mode handling
|
|
132
225
|
if (isReviewing) {
|
|
226
|
+
const maxScrollTop = Math.max(0, fields.length - contentHeight);
|
|
133
227
|
if (key.return) {
|
|
134
228
|
confirmSubmit();
|
|
135
229
|
return true;
|
|
136
230
|
}
|
|
137
|
-
// Backspace to go back to editing
|
|
138
231
|
if (key.backspace || key.delete) {
|
|
139
232
|
setIsReviewing(false);
|
|
233
|
+
goToFieldIndex(Math.max(0, fields.length - 1));
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
if (key.leftArrow || (key.tab && key.shift)) {
|
|
237
|
+
setIsReviewing(false);
|
|
238
|
+
goToFieldIndex(Math.max(0, fields.length - 1));
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (key.upArrow) {
|
|
242
|
+
setReviewScrollTop((prev) => Math.max(0, prev - 1));
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (key.downArrow) {
|
|
246
|
+
setReviewScrollTop((prev) => Math.min(maxScrollTop, prev + 1));
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (key.pageUp) {
|
|
250
|
+
setReviewScrollTop((prev) => Math.max(0, prev - 5));
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (key.pageDown) {
|
|
254
|
+
setReviewScrollTop((prev) => Math.min(maxScrollTop, prev + 5));
|
|
140
255
|
return true;
|
|
141
256
|
}
|
|
142
|
-
// Esc to cancel entirely
|
|
143
257
|
if (key.escape) {
|
|
144
258
|
onCancel();
|
|
145
259
|
return true;
|
|
146
260
|
}
|
|
147
261
|
return false;
|
|
148
262
|
}
|
|
149
|
-
// Escape to cancel
|
|
150
263
|
if (key.escape) {
|
|
151
264
|
onCancel();
|
|
152
265
|
return true;
|
|
153
266
|
}
|
|
154
267
|
if (!activeField)
|
|
155
268
|
return false;
|
|
156
|
-
//
|
|
157
|
-
if ((key.tab && key.shift)
|
|
158
|
-
(key.upArrow &&
|
|
159
|
-
activeField.type !== 'enum' &&
|
|
160
|
-
activeField.type !== 'array-enum')) {
|
|
269
|
+
// Wizard navigation: Left/Right (or Shift+Tab/Tab) changes the question.
|
|
270
|
+
if (key.leftArrow || (key.tab && key.shift)) {
|
|
161
271
|
prevField();
|
|
162
272
|
return true;
|
|
163
273
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
274
|
+
if (key.rightArrow || (key.tab && !key.shift)) {
|
|
275
|
+
if (activeFieldIndex === fields.length - 1) {
|
|
276
|
+
handleSubmit();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
nextField();
|
|
280
|
+
}
|
|
170
281
|
return true;
|
|
171
282
|
}
|
|
172
|
-
|
|
283
|
+
const setDraftValue = (updater) => {
|
|
284
|
+
const hadError = hasOwn(errors, activeField.name);
|
|
285
|
+
setDraftInputs((prev) => {
|
|
286
|
+
const current = hasOwn(prev, activeField.name)
|
|
287
|
+
? (prev[activeField.name] ?? '')
|
|
288
|
+
: (() => {
|
|
289
|
+
const existing = formData[activeField.name];
|
|
290
|
+
return existing === undefined || existing === null
|
|
291
|
+
? ''
|
|
292
|
+
: String(existing);
|
|
293
|
+
})();
|
|
294
|
+
return { ...prev, [activeField.name]: updater(current) };
|
|
295
|
+
});
|
|
296
|
+
if (hadError) {
|
|
297
|
+
setErrors((prevErrors) => {
|
|
298
|
+
const nextErrors = { ...prevErrors };
|
|
299
|
+
delete nextErrors[activeField.name];
|
|
300
|
+
return nextErrors;
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
173
304
|
switch (activeField.type) {
|
|
174
305
|
case 'boolean': {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (activeFieldIndex === fields.length - 1) {
|
|
182
|
-
handleSubmit({ name: activeField.name, value: newValue });
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
nextField();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
306
|
+
if (key.upArrow) {
|
|
307
|
+
setEnumIndex((prev) => clamp(prev - 1, 0, 1));
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
if (key.downArrow) {
|
|
311
|
+
setEnumIndex((prev) => clamp(prev + 1, 0, 1));
|
|
188
312
|
return true;
|
|
189
313
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
314
|
+
if (input === ' ') {
|
|
315
|
+
setEnumIndex((prev) => (prev === 0 ? 1 : 0));
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (key.return) {
|
|
319
|
+
const nextValue = enumIndex === 0;
|
|
320
|
+
updateField(activeField.name, nextValue);
|
|
321
|
+
if (activeFieldIndex === fields.length - 1) {
|
|
322
|
+
handleSubmit({ name: activeField.name, value: nextValue });
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
nextField();
|
|
326
|
+
}
|
|
194
327
|
return true;
|
|
195
328
|
}
|
|
196
329
|
break;
|
|
197
330
|
}
|
|
198
331
|
case 'enum': {
|
|
199
332
|
const values = activeField.enumValues || [];
|
|
200
|
-
// Up/Down to navigate enum
|
|
201
333
|
if (key.upArrow) {
|
|
202
334
|
setEnumIndex((prev) => (prev > 0 ? prev - 1 : values.length - 1));
|
|
203
335
|
return true;
|
|
@@ -206,7 +338,6 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
206
338
|
setEnumIndex((prev) => (prev < values.length - 1 ? prev + 1 : 0));
|
|
207
339
|
return true;
|
|
208
340
|
}
|
|
209
|
-
// Enter to select and move to next (or submit if last)
|
|
210
341
|
if (key.return) {
|
|
211
342
|
const selectedValue = values[enumIndex];
|
|
212
343
|
updateField(activeField.name, selectedValue);
|
|
@@ -222,7 +353,6 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
222
353
|
}
|
|
223
354
|
case 'array-enum': {
|
|
224
355
|
const values = activeField.enumValues || [];
|
|
225
|
-
// Up/Down to navigate
|
|
226
356
|
if (key.upArrow) {
|
|
227
357
|
setEnumIndex((prev) => (prev > 0 ? prev - 1 : values.length - 1));
|
|
228
358
|
return true;
|
|
@@ -231,26 +361,18 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
231
361
|
setEnumIndex((prev) => (prev < values.length - 1 ? prev + 1 : 0));
|
|
232
362
|
return true;
|
|
233
363
|
}
|
|
234
|
-
// Space to toggle selection
|
|
235
364
|
if (input === ' ') {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Update form data
|
|
245
|
-
const selected = Array.from(next).map((i) => values[i]);
|
|
246
|
-
updateField(activeField.name, selected);
|
|
247
|
-
return next;
|
|
248
|
-
});
|
|
365
|
+
const nextSelections = new Set(arraySelections);
|
|
366
|
+
if (nextSelections.has(enumIndex))
|
|
367
|
+
nextSelections.delete(enumIndex);
|
|
368
|
+
else
|
|
369
|
+
nextSelections.add(enumIndex);
|
|
370
|
+
setArraySelections(nextSelections);
|
|
371
|
+
const selected = Array.from(nextSelections).map((i) => values[i]);
|
|
372
|
+
updateField(activeField.name, selected);
|
|
249
373
|
return true;
|
|
250
374
|
}
|
|
251
|
-
// Enter to confirm and move to next (or submit if last)
|
|
252
375
|
if (key.return) {
|
|
253
|
-
// Get current selections for submit
|
|
254
376
|
const selected = Array.from(arraySelections).map((i) => values[i]);
|
|
255
377
|
if (activeFieldIndex === fields.length - 1) {
|
|
256
378
|
handleSubmit({ name: activeField.name, value: selected });
|
|
@@ -264,18 +386,40 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
264
386
|
}
|
|
265
387
|
case 'string':
|
|
266
388
|
case 'number': {
|
|
267
|
-
|
|
389
|
+
const hasDraft = hasOwn(draftInputs, activeField.name);
|
|
390
|
+
const rawInput = hasDraft
|
|
391
|
+
? (draftInputs[activeField.name] ?? '')
|
|
392
|
+
: (() => {
|
|
393
|
+
const existing = formData[activeField.name];
|
|
394
|
+
return existing === undefined || existing === null
|
|
395
|
+
? ''
|
|
396
|
+
: String(existing);
|
|
397
|
+
})();
|
|
268
398
|
if (key.return) {
|
|
269
|
-
|
|
270
|
-
?
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
399
|
+
let value = hasDraft
|
|
400
|
+
? rawInput
|
|
401
|
+
: formData[activeField.name];
|
|
402
|
+
if (activeField.type === 'number' && hasDraft) {
|
|
403
|
+
const trimmed = rawInput.trim();
|
|
404
|
+
if (trimmed === '') {
|
|
405
|
+
value = '';
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
const parsed = Number(trimmed);
|
|
409
|
+
if (Number.isNaN(parsed)) {
|
|
410
|
+
setErrors((prev) => ({
|
|
411
|
+
...prev,
|
|
412
|
+
[activeField.name]: 'Invalid number',
|
|
413
|
+
}));
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
value = parsed;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (hasDraft) {
|
|
275
420
|
updateField(activeField.name, value);
|
|
276
421
|
}
|
|
277
422
|
if (activeFieldIndex === fields.length - 1) {
|
|
278
|
-
// Last field - submit with current value
|
|
279
423
|
handleSubmit(value !== undefined
|
|
280
424
|
? { name: activeField.name, value }
|
|
281
425
|
: undefined);
|
|
@@ -285,21 +429,20 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
285
429
|
}
|
|
286
430
|
return true;
|
|
287
431
|
}
|
|
288
|
-
// Backspace
|
|
289
432
|
if (key.backspace || key.delete) {
|
|
290
|
-
|
|
433
|
+
setDraftValue((prev) => prev.slice(0, -1));
|
|
291
434
|
return true;
|
|
292
435
|
}
|
|
293
|
-
// Regular character input
|
|
294
436
|
if (input && !key.ctrl && !key.meta) {
|
|
295
|
-
// For number type, only allow digits and decimal
|
|
296
437
|
if (activeField.type === 'number') {
|
|
297
|
-
|
|
298
|
-
|
|
438
|
+
// Accept either single-key entry or paste: filter to allowed chars.
|
|
439
|
+
const filtered = input.replace(/[^\d.-]/g, '');
|
|
440
|
+
if (filtered.length > 0) {
|
|
441
|
+
setDraftValue((prev) => prev + filtered);
|
|
299
442
|
}
|
|
300
443
|
}
|
|
301
444
|
else {
|
|
302
|
-
|
|
445
|
+
setDraftValue((prev) => prev + input);
|
|
303
446
|
}
|
|
304
447
|
return true;
|
|
305
448
|
}
|
|
@@ -312,47 +455,138 @@ export const ElicitationForm = forwardRef(({ metadata, onSubmit, onCancel }, ref
|
|
|
312
455
|
activeField,
|
|
313
456
|
activeFieldIndex,
|
|
314
457
|
arraySelections,
|
|
458
|
+
contentHeight,
|
|
315
459
|
confirmSubmit,
|
|
316
|
-
|
|
460
|
+
draftInputs,
|
|
317
461
|
enumIndex,
|
|
318
|
-
|
|
462
|
+
errors,
|
|
463
|
+
fields,
|
|
319
464
|
formData,
|
|
320
465
|
handleSubmit,
|
|
321
466
|
isReviewing,
|
|
467
|
+
goToFieldIndex,
|
|
322
468
|
nextField,
|
|
323
469
|
onCancel,
|
|
324
470
|
prevField,
|
|
471
|
+
reviewScrollTop,
|
|
325
472
|
updateField,
|
|
326
473
|
]);
|
|
327
474
|
if (fields.length === 0) {
|
|
328
475
|
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: _jsx(Text, { color: "red", children: "Invalid form schema" }) }));
|
|
329
476
|
}
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
477
|
+
const isAnswered = (field) => {
|
|
478
|
+
if (field.type === 'boolean')
|
|
479
|
+
return hasOwn(formData, field.name);
|
|
480
|
+
if (field.type === 'enum')
|
|
481
|
+
return hasOwn(formData, field.name);
|
|
482
|
+
if (field.type === 'array-enum') {
|
|
483
|
+
const value = formData[field.name];
|
|
484
|
+
return Array.isArray(value) && value.length > 0;
|
|
485
|
+
}
|
|
486
|
+
const draft = draftInputs[field.name];
|
|
487
|
+
if (typeof draft === 'string' && draft.trim() !== '')
|
|
488
|
+
return true;
|
|
489
|
+
const value = formData[field.name];
|
|
490
|
+
return value !== undefined && value !== null && value !== '';
|
|
491
|
+
};
|
|
492
|
+
if (!isReviewing && !activeField) {
|
|
493
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: _jsx(Text, { color: "red", children: "Invalid form state" }) }));
|
|
344
494
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
495
|
+
const value = activeField ? formData[activeField.name] : undefined;
|
|
496
|
+
const currentInput = activeField && hasOwn(draftInputs, activeField.name)
|
|
497
|
+
? (draftInputs[activeField.name] ?? '')
|
|
498
|
+
: value === undefined || value === null
|
|
499
|
+
? ''
|
|
500
|
+
: String(value);
|
|
501
|
+
const errorText = activeField ? (errors[activeField.name] ?? '') : '';
|
|
502
|
+
const renderContent = () => {
|
|
503
|
+
if (isReviewing) {
|
|
504
|
+
return (_jsx(Box, { overflowY: "scroll", overflowX: "hidden", scrollTop: reviewScrollTop, scrollbarThumbColor: "gray", flexDirection: "column", height: contentHeight, paddingRight: 1, children: fields.map((field) => (_jsxs(Text, { wrap: "truncate-end", children: [field.question, ": ", getDisplayValue(formData[field.name])] }, field.name))) }));
|
|
505
|
+
}
|
|
506
|
+
if (!activeField)
|
|
507
|
+
return null;
|
|
508
|
+
if (activeField.type === 'string' || activeField.type === 'number') {
|
|
509
|
+
return (_jsx(Box, { height: contentHeight, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: "> " }), currentInput || _jsx(Text, { color: "gray", children: "Type your answer\u2026" }), _jsx(Text, { color: "cyan", children: "\u258B" })] }) }));
|
|
510
|
+
}
|
|
511
|
+
const options = [];
|
|
512
|
+
if (activeField.type === 'boolean') {
|
|
513
|
+
options.push({
|
|
514
|
+
key: 'yes',
|
|
515
|
+
label: 'Yes',
|
|
516
|
+
isFocused: enumIndex === 0,
|
|
517
|
+
isSelected: false,
|
|
518
|
+
});
|
|
519
|
+
options.push({
|
|
520
|
+
key: 'no',
|
|
521
|
+
label: 'No',
|
|
522
|
+
isFocused: enumIndex === 1,
|
|
523
|
+
isSelected: false,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if ((activeField.type === 'enum' || activeField.type === 'array-enum') &&
|
|
527
|
+
activeField.enumValues) {
|
|
528
|
+
activeField.enumValues.forEach((opt, i) => {
|
|
529
|
+
const isFocused = i === enumIndex;
|
|
530
|
+
const isSelected = activeField.type === 'enum' ? false : arraySelections.has(i);
|
|
531
|
+
options.push({
|
|
532
|
+
key: `${String(opt)}-${i}`,
|
|
533
|
+
label: String(opt),
|
|
534
|
+
isFocused,
|
|
535
|
+
isSelected,
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
const focusedIndex = Math.max(0, options.findIndex((o) => o.isFocused));
|
|
540
|
+
const maxScrollTop = Math.max(0, options.length - contentHeight);
|
|
541
|
+
const targetScrollTop = clamp(focusedIndex - Math.floor(contentHeight / 2), 0, maxScrollTop);
|
|
542
|
+
return (_jsx(Box, { overflowY: "scroll", overflowX: "hidden", scrollbarThumbColor: "gray", height: contentHeight, scrollTop: targetScrollTop, flexDirection: "column", paddingRight: 1, children: options.map((opt) => {
|
|
543
|
+
const prefix = opt.isFocused ? '▶ ' : ' ';
|
|
544
|
+
const mark = activeField.type === 'array-enum'
|
|
545
|
+
? opt.isSelected
|
|
546
|
+
? '[✓] '
|
|
547
|
+
: '[ ] '
|
|
548
|
+
: '';
|
|
549
|
+
return (_jsxs(Text, { color: opt.isFocused ? 'cyan' : 'gray', wrap: "truncate-end", children: [prefix, mark, opt.label] }, opt.key));
|
|
550
|
+
}) }));
|
|
551
|
+
};
|
|
552
|
+
const hintLine = (() => {
|
|
553
|
+
if (isReviewing)
|
|
554
|
+
return 'Enter submit • Backspace/← edit • ↑↓ scroll • Esc cancel';
|
|
555
|
+
switch (activeField?.type) {
|
|
556
|
+
case 'string':
|
|
557
|
+
case 'number':
|
|
558
|
+
return 'Type to answer • Enter next • ←/→ question • Esc cancel';
|
|
559
|
+
case 'array-enum':
|
|
560
|
+
return '↑/↓ option • Space toggle • Enter next • ←/→ question • Esc cancel';
|
|
561
|
+
default:
|
|
562
|
+
return '↑/↓ option • Enter select • ←/→ question • Esc cancel';
|
|
563
|
+
}
|
|
564
|
+
})();
|
|
565
|
+
const headerText = isReviewing
|
|
566
|
+
? '📝 Review your answers'
|
|
567
|
+
: `📝 Please answer ${fields.length === 1 ? 'this' : 'these'} ${fields.length} ${fields.length === 1 ? 'question' : 'questions'}.`;
|
|
568
|
+
const stepText = (() => {
|
|
569
|
+
if (isReviewing) {
|
|
570
|
+
return '';
|
|
571
|
+
}
|
|
572
|
+
if (!activeField)
|
|
573
|
+
return '';
|
|
574
|
+
return `Question ${activeFieldIndex + 1}/${fields.length}: ${activeField.stepLabel}`;
|
|
575
|
+
})();
|
|
576
|
+
const questionText = !isReviewing && activeField
|
|
577
|
+
? `${activeField.question}${activeField.required ? '*' : ''}`
|
|
578
|
+
: '';
|
|
579
|
+
const helpText = !isReviewing && activeField?.helpText ? activeField.helpText : '';
|
|
580
|
+
const errorLineText = !isReviewing ? errorText : '';
|
|
581
|
+
const headerLines = wrapClampedLines(headerText, headerLineCount);
|
|
582
|
+
const stepLines = wrapClampedLines(stepText, stepHeaderLineCount);
|
|
583
|
+
const questionLines = wrapClampedLines(questionText, questionLineCount);
|
|
584
|
+
const helpLines = wrapClampedLines(helpText, helpLineCount);
|
|
585
|
+
const errorLines = wrapClampedLines(errorLineText, errorLineCount);
|
|
586
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 0, height: viewportHeight, children: [headerLines.map((line, index) => (_jsx(Text, { color: "yellowBright", bold: true, wrap: "truncate-end", children: line || ' ' }, `header-${index}`))), !isReviewing &&
|
|
587
|
+
stepLines.map((line, index) => (_jsx(Text, { color: "gray", dimColor: true, wrap: "truncate-end", children: line || ' ' }, `step-${index}`))), Array.from({ length: spacerAfterStepLineCount }, (_, index) => (_jsx(Text, { wrap: "truncate-end", children: ' ' }, `spacer-step-${index}`))), !isReviewing &&
|
|
588
|
+
questionLines.map((line, index) => (_jsx(Text, { color: "white", bold: true, wrap: "truncate-end", children: line || ' ' }, `question-${index}`))), !isReviewing &&
|
|
589
|
+
helpLines.map((line, index) => (_jsx(Text, { color: "gray", dimColor: true, wrap: "truncate-end", children: line || ' ' }, `help-${index}`))), !isReviewing &&
|
|
590
|
+
errorLines.map((line, index) => (_jsx(Text, { color: "red", wrap: "truncate-end", children: line || ' ' }, `error-${index}`))), renderContent(), _jsx(Text, { color: "gray", dimColor: true, wrap: "truncate-end", children: hintLine })] }));
|
|
357
591
|
});
|
|
358
592
|
ElicitationForm.displayName = 'ElicitationForm';
|