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 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
- core.init(input.questions);
181
- const originalQuestions = input.questions;
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.confirmValue = true;
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.confirmValue = false;
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
- advance();
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;
@@ -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 yesBtn = selectedYes
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 = !selectedYes
41
+ const noBtn = cursorOnNo
33
42
  ? theme.bg("selectedBg", theme.fg("text", ` [${noLabel}] `))
34
43
  : ` [${noLabel}] `;
35
44
 
36
- const arrow = selectedYes
37
- ? theme.fg("accent", ">") + " " + yesBtn + " " + noBtn
38
- : " " + yesBtn + " " + theme.fg("accent", ">") + " " + noBtn;
45
+ const row = selectedYes
46
+ ? dot + " " + yesBtn + " " + blank + " " + noBtn
47
+ : blank + " " + yesBtn + " " + dot + " " + noBtn;
39
48
 
40
- lines.push(truncateToWidth(" " + arrow, width));
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
- let prefix: string;
38
- if (isCursor) {
39
- prefix = theme.fg("accent", "> ");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lite-questionnaire",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "轻量级交互式问卷工具 — 单选、多选、文本、确认、评分,带条件子问题和声明式校验",
5
5
  "keywords": [
6
6
  "pi-package",
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 = " ✓ Submit ";
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 选择 · Enter 确认 · Esc 取消" + (isMultiQuestion ? " · ← → 切换问题" : ""));
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) {