lite-questionnaire 1.0.4 → 1.0.6
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/core.ts +1 -0
- package/index.ts +11 -3
- package/input.ts +34 -5
- package/modules/confirm.ts +16 -7
- package/modules/select.ts +4 -9
- package/package.json +1 -1
- package/render.ts +6 -2
package/core.ts
CHANGED
|
@@ -183,6 +183,7 @@ export class Core {
|
|
|
183
183
|
* 优先检查持久化答案;没有答案时再检查当前 UI 草稿/默认值。
|
|
184
184
|
*/
|
|
185
185
|
hasAnyValue(q: FlatQuestion, state?: QuestionUIState): boolean {
|
|
186
|
+
if (q.id === "__lq_notes__") return true;
|
|
186
187
|
const answer = this.answers.get(q.id);
|
|
187
188
|
if (this.answerHasValue(answer)) return true;
|
|
188
189
|
|
package/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
14
14
|
import { Type } from "typebox";
|
|
15
15
|
import { Editor, type EditorTheme, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
16
16
|
import { Core } from "./core";
|
|
17
|
-
import type { Answer, CancelledResult, QuestionnaireParams, QuestionnaireResult } from "./types";
|
|
17
|
+
import type { Answer, CancelledResult, QuestionnaireParams, QuestionnaireResult, Question } from "./types";
|
|
18
18
|
import {
|
|
19
19
|
panelTop,
|
|
20
20
|
panelBottom,
|
|
@@ -177,8 +177,16 @@ export default function questionnaire(pi: ExtensionAPI) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const core = new Core();
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
const notesQuestion: Question = {
|
|
181
|
+
id: "__lq_notes__",
|
|
182
|
+
label: "备注",
|
|
183
|
+
prompt: "有什么需要补充说明的吗?",
|
|
184
|
+
type: "text",
|
|
185
|
+
placeholder: "输入备注内容(可选)...",
|
|
186
|
+
};
|
|
187
|
+
const allQuestions = [...input.questions, notesQuestion];
|
|
188
|
+
core.init(allQuestions);
|
|
189
|
+
const originalQuestions = allQuestions;
|
|
182
190
|
const snapshotKey = questionnaireKey(originalQuestions);
|
|
183
191
|
const snapshot = loadSnapshot(
|
|
184
192
|
ctx.sessionManager.getBranch() as Array<{ type: string; customType?: string; data?: unknown }>,
|
package/input.ts
CHANGED
|
@@ -126,7 +126,7 @@ export function createInputHandler(
|
|
|
126
126
|
|
|
127
127
|
if (!text) {
|
|
128
128
|
core.deleteAnswer(q.id);
|
|
129
|
-
if (options.allowEmpty) {
|
|
129
|
+
if (options.allowEmpty || q.id === "__lq_notes__") {
|
|
130
130
|
return true;
|
|
131
131
|
}
|
|
132
132
|
core.leaveCurrentQuestion();
|
|
@@ -256,20 +256,33 @@ export function createInputHandler(
|
|
|
256
256
|
if (q.type === "confirm") {
|
|
257
257
|
if (matchesKey(data, Key.left)) {
|
|
258
258
|
const state = core.getUIState();
|
|
259
|
-
state.
|
|
259
|
+
state.optionIndex = 0; // 光标移到「是」
|
|
260
260
|
core.saveUIState(state);
|
|
261
261
|
onUpdate();
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
264
|
if (matchesKey(data, Key.right)) {
|
|
265
265
|
const state = core.getUIState();
|
|
266
|
-
state.
|
|
266
|
+
state.optionIndex = 1; // 光标移到「否」
|
|
267
|
+
core.saveUIState(state);
|
|
268
|
+
onUpdate();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (matchesKey(data, Key.space)) {
|
|
272
|
+
const state = core.getUIState();
|
|
273
|
+
state.confirmValue = state.optionIndex === 0; // ● 移到光标位置
|
|
267
274
|
core.saveUIState(state);
|
|
268
275
|
onUpdate();
|
|
269
276
|
return;
|
|
270
277
|
}
|
|
271
278
|
if (matchesKey(data, Key.enter)) {
|
|
272
|
-
|
|
279
|
+
const state = core.getUIState();
|
|
280
|
+
state.confirmValue = state.optionIndex === 0; // 确认选中
|
|
281
|
+
core.saveUIState(state);
|
|
282
|
+
core.inputMode = false;
|
|
283
|
+
core.inputQuestionId = null;
|
|
284
|
+
saveFn();
|
|
285
|
+
onUpdate();
|
|
273
286
|
return;
|
|
274
287
|
}
|
|
275
288
|
return;
|
|
@@ -492,7 +505,23 @@ export function createInputHandler(
|
|
|
492
505
|
return;
|
|
493
506
|
}
|
|
494
507
|
|
|
495
|
-
case "confirm":
|
|
508
|
+
case "confirm": {
|
|
509
|
+
if (matchesKey(data, Key.tab)) {
|
|
510
|
+
core.inputMode = true;
|
|
511
|
+
core.inputQuestionId = q.id;
|
|
512
|
+
// 进入选择态时,光标初始位置与当前 ● 一致
|
|
513
|
+
const state = core.getUIState();
|
|
514
|
+
state.optionIndex = state.confirmValue ? 0 : 1;
|
|
515
|
+
core.saveUIState(state);
|
|
516
|
+
onUpdate();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (matchesKey(data, Key.enter)) {
|
|
520
|
+
advance();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
496
525
|
case "rating": {
|
|
497
526
|
if (matchesKey(data, Key.tab)) {
|
|
498
527
|
core.inputMode = true;
|
package/modules/confirm.ts
CHANGED
|
@@ -25,18 +25,27 @@ export function renderConfirmQuestion(
|
|
|
25
25
|
const noLabel = q.noLabel || "否";
|
|
26
26
|
const selectedYes = state.confirmValue === true;
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
const
|
|
28
|
+
// 是否处于选择态(Tab 进入的编辑模式)
|
|
29
|
+
const isEditing = core.inputMode && core.inputQuestionId === q.id;
|
|
30
|
+
// 选择态下光标位置:optionIndex=0 表示在「是」,1 表示在「否」
|
|
31
|
+
const cursorOnYes = isEditing && state.optionIndex === 0;
|
|
32
|
+
const cursorOnNo = isEditing && state.optionIndex === 1;
|
|
33
|
+
|
|
34
|
+
// 渲染两个按钮:● 表示已选中(基于 confirmValue),背景高亮表示光标(仅选择态可见)
|
|
35
|
+
const dot = theme.fg("success", "●");
|
|
36
|
+
const blank = " ";
|
|
37
|
+
|
|
38
|
+
const yesBtn = cursorOnYes
|
|
30
39
|
? theme.bg("selectedBg", theme.fg("text", ` [${yesLabel}] `))
|
|
31
40
|
: ` [${yesLabel}] `;
|
|
32
|
-
const noBtn =
|
|
41
|
+
const noBtn = cursorOnNo
|
|
33
42
|
? theme.bg("selectedBg", theme.fg("text", ` [${noLabel}] `))
|
|
34
43
|
: ` [${noLabel}] `;
|
|
35
44
|
|
|
36
|
-
const
|
|
37
|
-
?
|
|
38
|
-
: "
|
|
45
|
+
const row = selectedYes
|
|
46
|
+
? dot + " " + yesBtn + " " + blank + " " + noBtn
|
|
47
|
+
: blank + " " + yesBtn + " " + dot + " " + noBtn;
|
|
39
48
|
|
|
40
|
-
lines.push(truncateToWidth(" " +
|
|
49
|
+
lines.push(truncateToWidth(" " + row, width));
|
|
41
50
|
return lines;
|
|
42
51
|
}
|
package/modules/select.ts
CHANGED
|
@@ -33,15 +33,10 @@ export function renderSelectOptions(
|
|
|
33
33
|
const hasCustomText = opt.isCustom && state.customText !== null;
|
|
34
34
|
const isSelected = state.selectedIndices.includes(i);
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} else if (isSelected) {
|
|
41
|
-
prefix = theme.fg("success", "• ");
|
|
42
|
-
} else {
|
|
43
|
-
prefix = " ";
|
|
44
|
-
}
|
|
36
|
+
// 前缀:两列独立 — 列0: >, 列1: ●
|
|
37
|
+
const col0 = isCursor ? theme.fg("accent", ">") : " ";
|
|
38
|
+
const col1 = isSelected ? theme.fg("success", "●") : " ";
|
|
39
|
+
const prefix = col0 + col1 + " ";
|
|
45
40
|
|
|
46
41
|
// 颜色
|
|
47
42
|
const color = isCursor ? "accent" : isSelected ? "success" : "text";
|
package/package.json
CHANGED
package/render.ts
CHANGED
|
@@ -108,7 +108,7 @@ export function renderTabBar(
|
|
|
108
108
|
// 提交 Tab
|
|
109
109
|
const isSubmit = core.isSubmitTab();
|
|
110
110
|
const allDone = core.allAnswered();
|
|
111
|
-
let submitText = " ✓
|
|
111
|
+
let submitText = " ✓ 提交 ";
|
|
112
112
|
if (isSubmit) {
|
|
113
113
|
submitText = theme.bg("selectedBg", allDone ? theme.fg("success", submitText) : theme.fg("text", submitText));
|
|
114
114
|
} else if (allDone) {
|
|
@@ -143,6 +143,9 @@ export function renderHelpBar(
|
|
|
143
143
|
theme: { fg: (c: string, t: string) => string },
|
|
144
144
|
): string {
|
|
145
145
|
if (inputMode) {
|
|
146
|
+
if (q?.type === "confirm") {
|
|
147
|
+
return theme.fg("dim", " ← → 切换 · Space 选中 · Enter 确认 · Esc 退出");
|
|
148
|
+
}
|
|
146
149
|
return theme.fg("dim", " ← → 调整/移动 · Enter 保存 · Esc 退出编辑");
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -162,7 +165,7 @@ export function renderHelpBar(
|
|
|
162
165
|
case "text":
|
|
163
166
|
return theme.fg("dim", " Tab 编辑 · Enter 提交 · Esc 取消" + (isMultiQuestion ? " · ← → 切换问题" : ""));
|
|
164
167
|
case "confirm":
|
|
165
|
-
return theme.fg("dim", " Tab
|
|
168
|
+
return theme.fg("dim", " Tab 编辑 · Enter 进入下一栏 · Esc 取消" + (isMultiQuestion ? " · ← → 切换问题" : ""));
|
|
166
169
|
case "rating":
|
|
167
170
|
return theme.fg("dim", " Tab 调整 · Enter 确认 · Esc 取消" + (isMultiQuestion ? " · ← → 切换问题" : ""));
|
|
168
171
|
default:
|
|
@@ -187,6 +190,7 @@ export function renderSubmitPage(
|
|
|
187
190
|
|
|
188
191
|
for (const q of core.questions) {
|
|
189
192
|
const answer = core.answers.get(q.id);
|
|
193
|
+
if (q.id === "__lq_notes__" && !answer) continue;
|
|
190
194
|
if (!answer) {
|
|
191
195
|
lines.push(` ⚠ ${theme.fg("error", q.label)}: ${theme.fg("dim", "(未回答)")}`);
|
|
192
196
|
} else if ('text' in answer) {
|