@sundaeswap/sprinkles 0.5.0 → 0.6.0

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.
Files changed (112) hide show
  1. package/dist/cjs/Sprinkle/__tests__/encryption.test.js +3 -1
  2. package/dist/cjs/Sprinkle/__tests__/encryption.test.js.map +1 -1
  3. package/dist/cjs/Sprinkle/__tests__/enhancements.test.js +3 -37
  4. package/dist/cjs/Sprinkle/__tests__/enhancements.test.js.map +1 -1
  5. package/dist/cjs/Sprinkle/__tests__/field-utils.test.js +170 -0
  6. package/dist/cjs/Sprinkle/__tests__/field-utils.test.js.map +1 -0
  7. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js +242 -87
  8. package/dist/cjs/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  9. package/dist/cjs/Sprinkle/__tests__/formatting.test.js +97 -0
  10. package/dist/cjs/Sprinkle/__tests__/formatting.test.js.map +1 -0
  11. package/dist/cjs/Sprinkle/__tests__/show-menu.test.js +9 -5
  12. package/dist/cjs/Sprinkle/__tests__/show-menu.test.js.map +1 -1
  13. package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js +9 -0
  14. package/dist/cjs/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
  15. package/dist/cjs/Sprinkle/index.js +135 -91
  16. package/dist/cjs/Sprinkle/index.js.map +1 -1
  17. package/dist/cjs/Sprinkle/menus/array-menu.js +195 -0
  18. package/dist/cjs/Sprinkle/menus/array-menu.js.map +1 -0
  19. package/dist/cjs/Sprinkle/menus/field-menu.js +161 -0
  20. package/dist/cjs/Sprinkle/menus/field-menu.js.map +1 -0
  21. package/dist/cjs/Sprinkle/menus/index.js +33 -0
  22. package/dist/cjs/Sprinkle/menus/index.js.map +1 -0
  23. package/dist/cjs/Sprinkle/menus/object-menu.js +324 -0
  24. package/dist/cjs/Sprinkle/menus/object-menu.js.map +1 -0
  25. package/dist/cjs/Sprinkle/prompts.js +68 -2
  26. package/dist/cjs/Sprinkle/prompts.js.map +1 -1
  27. package/dist/cjs/Sprinkle/type-guards.js +48 -1
  28. package/dist/cjs/Sprinkle/type-guards.js.map +1 -1
  29. package/dist/cjs/Sprinkle/types.js +24 -0
  30. package/dist/cjs/Sprinkle/types.js.map +1 -1
  31. package/dist/cjs/Sprinkle/utils/field-utils.js +154 -0
  32. package/dist/cjs/Sprinkle/utils/field-utils.js.map +1 -0
  33. package/dist/cjs/Sprinkle/utils/formatting.js +126 -0
  34. package/dist/cjs/Sprinkle/utils/formatting.js.map +1 -0
  35. package/dist/cjs/Sprinkle/utils/index.js +56 -0
  36. package/dist/cjs/Sprinkle/utils/index.js.map +1 -0
  37. package/dist/esm/Sprinkle/__tests__/encryption.test.js +3 -1
  38. package/dist/esm/Sprinkle/__tests__/encryption.test.js.map +1 -1
  39. package/dist/esm/Sprinkle/__tests__/enhancements.test.js +3 -37
  40. package/dist/esm/Sprinkle/__tests__/enhancements.test.js.map +1 -1
  41. package/dist/esm/Sprinkle/__tests__/field-utils.test.js +168 -0
  42. package/dist/esm/Sprinkle/__tests__/field-utils.test.js.map +1 -0
  43. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js +243 -88
  44. package/dist/esm/Sprinkle/__tests__/fill-in-struct.test.js.map +1 -1
  45. package/dist/esm/Sprinkle/__tests__/formatting.test.js +95 -0
  46. package/dist/esm/Sprinkle/__tests__/formatting.test.js.map +1 -0
  47. package/dist/esm/Sprinkle/__tests__/show-menu.test.js +9 -5
  48. package/dist/esm/Sprinkle/__tests__/show-menu.test.js.map +1 -1
  49. package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js +9 -0
  50. package/dist/esm/Sprinkle/__tests__/tx-dialog.test.js.map +1 -1
  51. package/dist/esm/Sprinkle/index.js +102 -93
  52. package/dist/esm/Sprinkle/index.js.map +1 -1
  53. package/dist/esm/Sprinkle/menus/array-menu.js +190 -0
  54. package/dist/esm/Sprinkle/menus/array-menu.js.map +1 -0
  55. package/dist/esm/Sprinkle/menus/field-menu.js +155 -0
  56. package/dist/esm/Sprinkle/menus/field-menu.js.map +1 -0
  57. package/dist/esm/Sprinkle/menus/index.js +8 -0
  58. package/dist/esm/Sprinkle/menus/index.js.map +1 -0
  59. package/dist/esm/Sprinkle/menus/object-menu.js +318 -0
  60. package/dist/esm/Sprinkle/menus/object-menu.js.map +1 -0
  61. package/dist/esm/Sprinkle/prompts.js +59 -1
  62. package/dist/esm/Sprinkle/prompts.js.map +1 -1
  63. package/dist/esm/Sprinkle/type-guards.js +42 -0
  64. package/dist/esm/Sprinkle/type-guards.js.map +1 -1
  65. package/dist/esm/Sprinkle/types.js +24 -0
  66. package/dist/esm/Sprinkle/types.js.map +1 -1
  67. package/dist/esm/Sprinkle/utils/field-utils.js +145 -0
  68. package/dist/esm/Sprinkle/utils/field-utils.js.map +1 -0
  69. package/dist/esm/Sprinkle/utils/formatting.js +118 -0
  70. package/dist/esm/Sprinkle/utils/formatting.js.map +1 -0
  71. package/dist/esm/Sprinkle/utils/index.js +7 -0
  72. package/dist/esm/Sprinkle/utils/index.js.map +1 -0
  73. package/dist/types/Sprinkle/index.d.ts +9 -3
  74. package/dist/types/Sprinkle/index.d.ts.map +1 -1
  75. package/dist/types/Sprinkle/menus/array-menu.d.ts +31 -0
  76. package/dist/types/Sprinkle/menus/array-menu.d.ts.map +1 -0
  77. package/dist/types/Sprinkle/menus/field-menu.d.ts +34 -0
  78. package/dist/types/Sprinkle/menus/field-menu.d.ts.map +1 -0
  79. package/dist/types/Sprinkle/menus/index.d.ts +10 -0
  80. package/dist/types/Sprinkle/menus/index.d.ts.map +1 -0
  81. package/dist/types/Sprinkle/menus/object-menu.d.ts +34 -0
  82. package/dist/types/Sprinkle/menus/object-menu.d.ts.map +1 -0
  83. package/dist/types/Sprinkle/prompts.d.ts +25 -0
  84. package/dist/types/Sprinkle/prompts.d.ts.map +1 -1
  85. package/dist/types/Sprinkle/type-guards.d.ts +24 -1
  86. package/dist/types/Sprinkle/type-guards.d.ts.map +1 -1
  87. package/dist/types/Sprinkle/types.d.ts +53 -0
  88. package/dist/types/Sprinkle/types.d.ts.map +1 -1
  89. package/dist/types/Sprinkle/utils/field-utils.d.ts +47 -0
  90. package/dist/types/Sprinkle/utils/field-utils.d.ts.map +1 -0
  91. package/dist/types/Sprinkle/utils/formatting.d.ts +30 -0
  92. package/dist/types/Sprinkle/utils/formatting.d.ts.map +1 -0
  93. package/dist/types/tsconfig.build.tsbuildinfo +1 -1
  94. package/package.json +1 -1
  95. package/src/Sprinkle/__tests__/encryption.test.ts +2 -0
  96. package/src/Sprinkle/__tests__/enhancements.test.ts +3 -42
  97. package/src/Sprinkle/__tests__/field-utils.test.ts +191 -0
  98. package/src/Sprinkle/__tests__/fill-in-struct.test.ts +252 -103
  99. package/src/Sprinkle/__tests__/formatting.test.ts +115 -0
  100. package/src/Sprinkle/__tests__/show-menu.test.ts +14 -8
  101. package/src/Sprinkle/__tests__/tx-dialog.test.ts +9 -0
  102. package/src/Sprinkle/index.ts +131 -119
  103. package/src/Sprinkle/menus/array-menu.ts +191 -0
  104. package/src/Sprinkle/menus/field-menu.ts +145 -0
  105. package/src/Sprinkle/menus/index.ts +12 -0
  106. package/src/Sprinkle/menus/object-menu.ts +336 -0
  107. package/src/Sprinkle/prompts.ts +71 -1
  108. package/src/Sprinkle/type-guards.ts +42 -0
  109. package/src/Sprinkle/types.ts +43 -0
  110. package/src/Sprinkle/utils/field-utils.ts +158 -0
  111. package/src/Sprinkle/utils/formatting.ts +127 -0
  112. package/src/Sprinkle/utils/index.ts +17 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Field menu module for menu-based struct editing.
3
+ * Handles the sub-menu shown when selecting a field with an existing value.
4
+ */
5
+
6
+ import { Separator } from "@inquirer/core";
7
+ import { selectWithClear } from "../prompts.js";
8
+ import { isNullable, hasDefault, getDefault } from "../type-guards.js";
9
+ import { formatValuePreview } from "../utils/formatting.js";
10
+ import { bigIntReplacer } from "../encryption.js";
11
+
12
+ /**
13
+ * Options for the field menu prompt.
14
+ */
15
+
16
+ /**
17
+ * Show a sub-menu for a field that has a value.
18
+ * Provides options to edit, view, clear, set null, or reset to default.
19
+ *
20
+ * @param options - Field menu options
21
+ * @returns The user's chosen action
22
+ */
23
+ export async function promptFieldMenu(options) {
24
+ const {
25
+ fieldName,
26
+ fieldType,
27
+ state
28
+ } = options;
29
+ const nullable = isNullable(fieldType);
30
+ const hasDefaultVal = hasDefault(fieldType);
31
+ const defaultVal = hasDefaultVal ? getDefault(fieldType) : undefined;
32
+
33
+ // Build the display of current value
34
+ const currentDisplay = state.status === "null" ? "null" : state.status === "set" ? formatValuePreview(state.value, 40) : "[not set]";
35
+ const choices = [];
36
+
37
+ // Always offer edit
38
+ choices.push({
39
+ name: "Edit value",
40
+ value: "edit"
41
+ });
42
+
43
+ // View full value (only if there's a value to view)
44
+ if (state.status === "set") {
45
+ choices.push({
46
+ name: "View full value",
47
+ value: "view"
48
+ });
49
+ }
50
+
51
+ // For nullable fields with value: offer "Set to null" and "Clear value"
52
+ if (nullable && state.status === "set") {
53
+ choices.push({
54
+ name: "Set to null",
55
+ value: "setNull"
56
+ });
57
+ choices.push({
58
+ name: "Clear value (remove from output)",
59
+ value: "clear"
60
+ });
61
+ }
62
+
63
+ // For nullable fields that are null: offer "Clear value" to remove null
64
+ if (nullable && state.status === "null") {
65
+ choices.push({
66
+ name: "Clear value (remove from output)",
67
+ value: "clear"
68
+ });
69
+ }
70
+
71
+ // For non-nullable fields with default: offer "Reset to default"
72
+ if (hasDefaultVal && !nullable && state.status === "set") {
73
+ const defaultPreview = formatValuePreview(defaultVal, 20);
74
+ choices.push({
75
+ name: `Reset to default (${defaultPreview})`,
76
+ value: "reset"
77
+ });
78
+ }
79
+ choices.push(new Separator());
80
+ choices.push({
81
+ name: "Back (keep current)",
82
+ value: "back"
83
+ });
84
+ const selection = await selectWithClear({
85
+ message: `${fieldName}: ${currentDisplay}`,
86
+ choices
87
+ });
88
+
89
+ // Escape returns to menu
90
+ if (selection === null) {
91
+ return {
92
+ action: "back"
93
+ };
94
+ }
95
+ switch (selection) {
96
+ case "edit":
97
+ // Caller will handle the actual editing
98
+ return {
99
+ action: "edit"
100
+ };
101
+ case "view":
102
+ return {
103
+ action: "view"
104
+ };
105
+ case "clear":
106
+ return {
107
+ action: "clear"
108
+ };
109
+ case "setNull":
110
+ return {
111
+ action: "setNull"
112
+ };
113
+ case "reset":
114
+ return {
115
+ action: "reset",
116
+ defaultValue: defaultVal
117
+ };
118
+ case "back":
119
+ default:
120
+ return {
121
+ action: "back"
122
+ };
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Display the full value of a field.
128
+ * Shows formatted JSON and waits for user to press Enter.
129
+ *
130
+ * @param fieldName - The field name
131
+ * @param value - The value to display
132
+ */
133
+ export async function displayFullValue(fieldName, value) {
134
+ const header = `Current value of ${fieldName}:`;
135
+ const jsonStr = JSON.stringify(value, bigIntReplacer, 2);
136
+ const jsonLines = jsonStr.split("\n").length;
137
+ console.log("");
138
+ console.log(header);
139
+ console.log(jsonStr);
140
+ console.log("");
141
+
142
+ // Wait for user to press Enter
143
+ await selectWithClear({
144
+ message: "Press Enter to continue...",
145
+ choices: [{
146
+ name: "Continue",
147
+ value: "continue"
148
+ }]
149
+ });
150
+
151
+ // Clear all the output lines: empty line + header + json lines + empty line
152
+ const totalLines = 1 + 1 + jsonLines + 1;
153
+ process.stdout.write("\x1b[1A\x1b[2K".repeat(totalLines) + "\x1b[G");
154
+ }
155
+ //# sourceMappingURL=field-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-menu.js","names":["Separator","selectWithClear","isNullable","hasDefault","getDefault","formatValuePreview","bigIntReplacer","promptFieldMenu","options","fieldName","fieldType","state","nullable","hasDefaultVal","defaultVal","undefined","currentDisplay","status","value","choices","push","name","defaultPreview","selection","message","action","defaultValue","displayFullValue","header","jsonStr","JSON","stringify","jsonLines","split","length","console","log","totalLines","process","stdout","write","repeat"],"sources":["../../../../src/Sprinkle/menus/field-menu.ts"],"sourcesContent":["/**\n * Field menu module for menu-based struct editing.\n * Handles the sub-menu shown when selecting a field with an existing value.\n */\n\nimport { Separator } from \"@inquirer/core\";\nimport type { TSchema } from \"@sinclair/typebox\";\nimport { selectWithClear } from \"../prompts.js\";\nimport type { FieldMenuResult, FieldState } from \"../types.js\";\nimport { isNullable, hasDefault, getDefault } from \"../type-guards.js\";\nimport { formatValuePreview } from \"../utils/formatting.js\";\nimport { bigIntReplacer } from \"../encryption.js\";\n\n/**\n * Options for the field menu prompt.\n */\nexport interface FieldMenuOptions<T> {\n /** The field name */\n fieldName: string;\n /** The field's schema */\n fieldType: TSchema;\n /** Current state of the field */\n state: FieldState<T>;\n}\n\n/**\n * Show a sub-menu for a field that has a value.\n * Provides options to edit, view, clear, set null, or reset to default.\n *\n * @param options - Field menu options\n * @returns The user's chosen action\n */\nexport async function promptFieldMenu<T>(\n options: FieldMenuOptions<T>,\n): Promise<FieldMenuResult<T>> {\n const { fieldName, fieldType, state } = options;\n\n const nullable = isNullable(fieldType);\n const hasDefaultVal = hasDefault(fieldType);\n const defaultVal = hasDefaultVal ? getDefault(fieldType) : undefined;\n\n // Build the display of current value\n const currentDisplay =\n state.status === \"null\"\n ? \"null\"\n : state.status === \"set\"\n ? formatValuePreview(state.value, 40)\n : \"[not set]\";\n\n const choices: Array<{ name: string; value: string } | Separator> = [];\n\n // Always offer edit\n choices.push({ name: \"Edit value\", value: \"edit\" });\n\n // View full value (only if there's a value to view)\n if (state.status === \"set\") {\n choices.push({ name: \"View full value\", value: \"view\" });\n }\n\n // For nullable fields with value: offer \"Set to null\" and \"Clear value\"\n if (nullable && state.status === \"set\") {\n choices.push({ name: \"Set to null\", value: \"setNull\" });\n choices.push({ name: \"Clear value (remove from output)\", value: \"clear\" });\n }\n\n // For nullable fields that are null: offer \"Clear value\" to remove null\n if (nullable && state.status === \"null\") {\n choices.push({ name: \"Clear value (remove from output)\", value: \"clear\" });\n }\n\n // For non-nullable fields with default: offer \"Reset to default\"\n if (hasDefaultVal && !nullable && state.status === \"set\") {\n const defaultPreview = formatValuePreview(defaultVal, 20);\n choices.push({\n name: `Reset to default (${defaultPreview})`,\n value: \"reset\",\n });\n }\n\n choices.push(new Separator());\n choices.push({ name: \"Back (keep current)\", value: \"back\" });\n\n const selection = await selectWithClear({\n message: `${fieldName}: ${currentDisplay}`,\n choices,\n });\n\n // Escape returns to menu\n if (selection === null) {\n return { action: \"back\" };\n }\n\n switch (selection) {\n case \"edit\":\n // Caller will handle the actual editing\n return { action: \"edit\" };\n\n case \"view\":\n return { action: \"view\" };\n\n case \"clear\":\n return { action: \"clear\" };\n\n case \"setNull\":\n return { action: \"setNull\" };\n\n case \"reset\":\n return { action: \"reset\", defaultValue: defaultVal as T };\n\n case \"back\":\n default:\n return { action: \"back\" };\n }\n}\n\n/**\n * Display the full value of a field.\n * Shows formatted JSON and waits for user to press Enter.\n *\n * @param fieldName - The field name\n * @param value - The value to display\n */\nexport async function displayFullValue(\n fieldName: string,\n value: unknown,\n): Promise<void> {\n const header = `Current value of ${fieldName}:`;\n const jsonStr = JSON.stringify(value, bigIntReplacer, 2);\n const jsonLines = jsonStr.split(\"\\n\").length;\n\n console.log(\"\");\n console.log(header);\n console.log(jsonStr);\n console.log(\"\");\n\n // Wait for user to press Enter\n await selectWithClear({\n message: \"Press Enter to continue...\",\n choices: [{ name: \"Continue\", value: \"continue\" }],\n });\n\n // Clear all the output lines: empty line + header + json lines + empty line\n const totalLines = 1 + 1 + jsonLines + 1;\n process.stdout.write(\"\\x1b[1A\\x1b[2K\".repeat(totalLines) + \"\\x1b[G\");\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Menu modules for menu-based struct editing.
3
+ */
4
+
5
+ export { promptFieldMenu, displayFullValue } from "./field-menu.js";
6
+ export { promptObject } from "./object-menu.js";
7
+ export { promptArray } from "./array-menu.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["promptFieldMenu","displayFullValue","promptObject","promptArray"],"sources":["../../../../src/Sprinkle/menus/index.ts"],"sourcesContent":["/**\n * Menu modules for menu-based struct editing.\n */\n\nexport { promptFieldMenu, displayFullValue } from \"./field-menu.js\";\nexport type { FieldMenuOptions } from \"./field-menu.js\";\n\nexport { promptObject } from \"./object-menu.js\";\nexport type { ObjectMenuOptions, FillFunction } from \"./object-menu.js\";\n\nexport { promptArray } from \"./array-menu.js\";\nexport type { ArrayMenuOptions } from \"./array-menu.js\";\n"],"mappings":"AAAA;AACA;AACA;;AAEA,SAASA,eAAe,EAAEC,gBAAgB,QAAQ,iBAAiB;AAGnE,SAASC,YAAY,QAAQ,kBAAkB;AAG/C,SAASC,WAAW,QAAQ,iBAAiB","ignoreList":[]}
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Object menu module for menu-based struct editing.
3
+ * Shows all fields of an object and allows non-linear editing.
4
+ */
5
+
6
+ import { Separator } from "@inquirer/core";
7
+ import { selectWithClear, confirmWithClear, clearLines } from "../prompts.js";
8
+ import colors from "yoctocolors-cjs";
9
+ import { UserCancelledError } from "../types.js";
10
+ import { isOptional, hasDefault, getDefault, isLiteral } from "../type-guards.js";
11
+ import { buildFieldLabel, countRequiredFields, allRequiredFieldsFilled, getInitialFieldState, isFieldRequired } from "../utils/field-utils.js";
12
+ import { formatBreadcrumb } from "../utils/formatting.js";
13
+ import { promptFieldMenu, displayFullValue } from "./field-menu.js";
14
+
15
+ /**
16
+ * Type for the fill function that handles individual field types.
17
+ */
18
+
19
+ /**
20
+ * Options for the object menu prompt.
21
+ */
22
+
23
+ /**
24
+ * Show a menu-based editor for an object.
25
+ * Allows non-linear editing of fields with Submit/Cancel.
26
+ *
27
+ * @param options - Object menu options
28
+ * @returns The edited object or cancel result
29
+ */
30
+ export async function promptObject(options) {
31
+ const {
32
+ type,
33
+ path,
34
+ defs,
35
+ defaults = {},
36
+ fillField
37
+ } = options;
38
+ const fields = type.properties;
39
+ const fieldNames = Object.keys(fields);
40
+
41
+ // Edge case: empty struct
42
+ if (fieldNames.length === 0) {
43
+ return {
44
+ action: "submit",
45
+ value: {}
46
+ };
47
+ }
48
+
49
+ // Edge case: single required field with no optionals - skip menu
50
+ const requiredFields = fieldNames.filter(name => isFieldRequired(fields[name]));
51
+ const optionalFields = fieldNames.filter(name => !isFieldRequired(fields[name]));
52
+ if (requiredFields.length === 1 && optionalFields.length === 0) {
53
+ const fieldName = requiredFields[0];
54
+ const fieldType = fields[fieldName];
55
+ try {
56
+ const value = await fillField(fieldType, [...path, fieldName], defs, defaults[fieldName]);
57
+ return {
58
+ action: "submit",
59
+ value: {
60
+ [fieldName]: value
61
+ }
62
+ };
63
+ } catch (e) {
64
+ if (e instanceof UserCancelledError) {
65
+ return {
66
+ action: "cancel"
67
+ };
68
+ }
69
+ throw e;
70
+ }
71
+ }
72
+
73
+ // Initialize state for all fields
74
+ const state = new Map();
75
+ for (const fieldName of fieldNames) {
76
+ const fieldType = fields[fieldName];
77
+ // Auto-fill Literal fields with their const value
78
+ if (isLiteral(fieldType)) {
79
+ state.set(fieldName, {
80
+ status: "set",
81
+ value: fieldType.const
82
+ });
83
+ } else {
84
+ state.set(fieldName, getInitialFieldState(fieldType, defaults[fieldName]));
85
+ }
86
+ }
87
+
88
+ // Main menu loop
89
+ while (true) {
90
+ const {
91
+ total,
92
+ filled
93
+ } = countRequiredFields(type, state);
94
+ const canSubmit = allRequiredFieldsFilled(type, state);
95
+
96
+ // Build menu choices
97
+ const choices = [];
98
+
99
+ // Add field items
100
+ for (const fieldName of fieldNames) {
101
+ const fieldType = fields[fieldName];
102
+ const fieldState = state.get(fieldName);
103
+ const label = buildFieldLabel(fieldName, fieldType, fieldState);
104
+ choices.push({
105
+ name: label,
106
+ value: `field:${fieldName}`
107
+ });
108
+ }
109
+
110
+ // Add separator and actions
111
+ choices.push(new Separator("\u2500\u2500\u2500 Actions \u2500\u2500\u2500"));
112
+
113
+ // Submit button
114
+ if (canSubmit) {
115
+ choices.push({
116
+ name: "Submit",
117
+ value: "submit"
118
+ });
119
+ } else {
120
+ choices.push({
121
+ name: `Submit (${filled} of ${total} required complete)`,
122
+ value: "submit",
123
+ disabled: true
124
+ });
125
+ }
126
+ choices.push({
127
+ name: "Cancel",
128
+ value: "cancel"
129
+ });
130
+
131
+ // Show breadcrumb header if we're in a nested path
132
+ const breadcrumb = formatBreadcrumb(path);
133
+ if (breadcrumb) {
134
+ console.log(colors.dim(`📍 ${breadcrumb}`));
135
+ }
136
+
137
+ // Show menu
138
+ const selection = await selectWithClear({
139
+ message: "Edit:",
140
+ choices
141
+ });
142
+
143
+ // Clear the breadcrumb line too if we printed one
144
+ if (breadcrumb) {
145
+ clearLines(1);
146
+ }
147
+
148
+ // Handle escape
149
+ if (selection === null) {
150
+ // Check if any values have been set
151
+ const hasValues = Array.from(state.values()).some(s => s.status === "set" || s.status === "null");
152
+ if (hasValues) {
153
+ const confirmCancel = await confirmWithClear({
154
+ message: "Discard changes?",
155
+ default: false
156
+ });
157
+ if (confirmCancel === null || confirmCancel === false) {
158
+ continue; // Return to menu
159
+ }
160
+ }
161
+ return {
162
+ action: "cancel"
163
+ };
164
+ }
165
+
166
+ // Handle submit
167
+ if (selection === "submit") {
168
+ if (!canSubmit) {
169
+ continue; // Shouldn't happen due to disabled, but be safe
170
+ }
171
+ const result = buildResultObject(fields, state, defs);
172
+ return {
173
+ action: "submit",
174
+ value: result
175
+ };
176
+ }
177
+
178
+ // Handle cancel
179
+ if (selection === "cancel") {
180
+ const hasValues = Array.from(state.values()).some(s => s.status === "set" || s.status === "null");
181
+ if (hasValues) {
182
+ const confirmCancel = await confirmWithClear({
183
+ message: "Discard changes?",
184
+ default: false
185
+ });
186
+ if (confirmCancel === null || confirmCancel === false) {
187
+ continue;
188
+ }
189
+ }
190
+ return {
191
+ action: "cancel"
192
+ };
193
+ }
194
+
195
+ // Handle field selection
196
+ if (typeof selection === "string" && selection.startsWith("field:")) {
197
+ const fieldName = selection.slice(6);
198
+ const fieldType = fields[fieldName];
199
+ const fieldState = state.get(fieldName);
200
+
201
+ // If field has a value, show field sub-menu
202
+ if (fieldState.status === "set" || fieldState.status === "null") {
203
+ const menuResult = await promptFieldMenu({
204
+ fieldName,
205
+ fieldType,
206
+ state: fieldState
207
+ });
208
+ switch (menuResult.action) {
209
+ case "edit":
210
+ {
211
+ // Edit the field
212
+ try {
213
+ const newValue = await fillField(fieldType, [...path, fieldName], defs, fieldState.status === "set" ? fieldState.value : undefined);
214
+ state.set(fieldName, {
215
+ status: "set",
216
+ value: newValue
217
+ });
218
+ } catch (e) {
219
+ if (e instanceof UserCancelledError) {
220
+ // Return to menu without changing
221
+ continue;
222
+ }
223
+ throw e;
224
+ }
225
+ break;
226
+ }
227
+ case "view":
228
+ {
229
+ if (fieldState.status === "set") {
230
+ await displayFullValue(fieldName, fieldState.value);
231
+ }
232
+ break;
233
+ }
234
+ case "clear":
235
+ {
236
+ state.set(fieldName, {
237
+ status: "unset"
238
+ });
239
+ break;
240
+ }
241
+ case "setNull":
242
+ {
243
+ state.set(fieldName, {
244
+ status: "null"
245
+ });
246
+ break;
247
+ }
248
+ case "reset":
249
+ {
250
+ // Reset to default value - show as unset, will use default on submit
251
+ state.set(fieldName, {
252
+ status: "unset"
253
+ });
254
+ break;
255
+ }
256
+ case "back":
257
+ // Do nothing, return to main menu
258
+ break;
259
+ }
260
+ } else {
261
+ // Field is unset - directly prompt for value
262
+ try {
263
+ const newValue = await fillField(fieldType, [...path, fieldName], defs, defaults[fieldName]);
264
+ // If user skipped an optional field (returned undefined), leave as unset
265
+ if (newValue === undefined) {
266
+ // Don't change state - field remains unset
267
+ } else {
268
+ state.set(fieldName, {
269
+ status: "set",
270
+ value: newValue
271
+ });
272
+ }
273
+ } catch (e) {
274
+ if (e instanceof UserCancelledError) {
275
+ // Return to menu without changing
276
+ continue;
277
+ }
278
+ throw e;
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Build the result object from the state map.
287
+ * Applies defaults for unset fields with defaults.
288
+ */
289
+ function buildResultObject(fields, state, _defs) {
290
+ const result = {};
291
+ for (const [fieldName, fieldType] of Object.entries(fields)) {
292
+ const fieldState = state.get(fieldName);
293
+ if (!fieldState || fieldState.status === "unset") {
294
+ // If optional, skip (undefined)
295
+ if (isOptional(fieldType)) {
296
+ continue;
297
+ }
298
+
299
+ // If field has a default, use it
300
+ if (hasDefault(fieldType)) {
301
+ result[fieldName] = getDefault(fieldType);
302
+ continue;
303
+ }
304
+
305
+ // No value, no default: omit from output
306
+ continue;
307
+ }
308
+ if (fieldState.status === "null") {
309
+ result[fieldName] = null;
310
+ continue;
311
+ }
312
+ if (fieldState.status === "set") {
313
+ result[fieldName] = fieldState.value;
314
+ }
315
+ }
316
+ return result;
317
+ }
318
+ //# sourceMappingURL=object-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object-menu.js","names":["Separator","selectWithClear","confirmWithClear","clearLines","colors","UserCancelledError","isOptional","hasDefault","getDefault","isLiteral","buildFieldLabel","countRequiredFields","allRequiredFieldsFilled","getInitialFieldState","isFieldRequired","formatBreadcrumb","promptFieldMenu","displayFullValue","promptObject","options","type","path","defs","defaults","fillField","fields","properties","fieldNames","Object","keys","length","action","value","requiredFields","filter","name","optionalFields","fieldName","fieldType","e","state","Map","set","status","const","total","filled","canSubmit","choices","fieldState","get","label","push","disabled","breadcrumb","console","log","dim","selection","message","hasValues","Array","from","values","some","s","confirmCancel","default","result","buildResultObject","startsWith","slice","menuResult","newValue","undefined","_defs","entries"],"sources":["../../../../src/Sprinkle/menus/object-menu.ts"],"sourcesContent":["/**\n * Object menu module for menu-based struct editing.\n * Shows all fields of an object and allows non-linear editing.\n */\n\nimport { Separator } from \"@inquirer/core\";\nimport type { TObject, TSchema } from \"@sinclair/typebox\";\nimport { selectWithClear, confirmWithClear, clearLines } from \"../prompts.js\";\nimport colors from \"yoctocolors-cjs\";\nimport type { FieldState, ObjectMenuResult } from \"../types.js\";\nimport { UserCancelledError } from \"../types.js\";\nimport { isOptional, hasDefault, getDefault, isNullable, isLiteral } from \"../type-guards.js\";\nimport {\n buildFieldLabel,\n countRequiredFields,\n allRequiredFieldsFilled,\n getInitialFieldState,\n isFieldRequired,\n} from \"../utils/field-utils.js\";\nimport { formatBreadcrumb } from \"../utils/formatting.js\";\nimport { promptFieldMenu, displayFullValue } from \"./field-menu.js\";\n\n/**\n * Type for the fill function that handles individual field types.\n */\nexport type FillFunction = <U extends TSchema>(\n type: U,\n path: string[],\n defs: Record<string, TSchema>,\n def?: unknown,\n) => Promise<unknown>;\n\n/**\n * Options for the object menu prompt.\n */\nexport interface ObjectMenuOptions {\n /** The object schema */\n type: TObject;\n /** Current path for display */\n path: string[];\n /** Type definitions for resolving refs */\n defs: Record<string, TSchema>;\n /** Default/existing values */\n defaults?: Record<string, unknown>;\n /** Function to fill individual fields */\n fillField: FillFunction;\n}\n\n/**\n * Show a menu-based editor for an object.\n * Allows non-linear editing of fields with Submit/Cancel.\n *\n * @param options - Object menu options\n * @returns The edited object or cancel result\n */\nexport async function promptObject<T extends Record<string, unknown>>(\n options: ObjectMenuOptions,\n): Promise<ObjectMenuResult<T>> {\n const { type, path, defs, defaults = {}, fillField } = options;\n\n const fields = type.properties as Record<string, TSchema>;\n const fieldNames = Object.keys(fields);\n\n // Edge case: empty struct\n if (fieldNames.length === 0) {\n return { action: \"submit\", value: {} as T };\n }\n\n // Edge case: single required field with no optionals - skip menu\n const requiredFields = fieldNames.filter((name) =>\n isFieldRequired(fields[name]!),\n );\n const optionalFields = fieldNames.filter(\n (name) => !isFieldRequired(fields[name]!),\n );\n\n if (requiredFields.length === 1 && optionalFields.length === 0) {\n const fieldName = requiredFields[0]!;\n const fieldType = fields[fieldName]!;\n try {\n const value = await fillField(\n fieldType,\n [...path, fieldName],\n defs,\n defaults[fieldName],\n );\n return {\n action: \"submit\",\n value: { [fieldName]: value } as T,\n };\n } catch (e) {\n if (e instanceof UserCancelledError) {\n return { action: \"cancel\" };\n }\n throw e;\n }\n }\n\n // Initialize state for all fields\n const state = new Map<string, FieldState>();\n for (const fieldName of fieldNames) {\n const fieldType = fields[fieldName]!;\n // Auto-fill Literal fields with their const value\n if (isLiteral(fieldType)) {\n state.set(fieldName, { status: \"set\", value: fieldType.const });\n } else {\n state.set(fieldName, getInitialFieldState(fieldType, defaults[fieldName]));\n }\n }\n\n // Main menu loop\n while (true) {\n const { total, filled } = countRequiredFields(type, state);\n const canSubmit = allRequiredFieldsFilled(type, state);\n\n // Build menu choices\n const choices: Array<{ name: string; value: string; disabled?: boolean | string } | Separator> = [];\n\n // Add field items\n for (const fieldName of fieldNames) {\n const fieldType = fields[fieldName]!;\n const fieldState = state.get(fieldName)!;\n const label = buildFieldLabel(fieldName, fieldType, fieldState);\n choices.push({ name: label, value: `field:${fieldName}` });\n }\n\n // Add separator and actions\n choices.push(new Separator(\"\\u2500\\u2500\\u2500 Actions \\u2500\\u2500\\u2500\"));\n\n // Submit button\n if (canSubmit) {\n choices.push({ name: \"Submit\", value: \"submit\" });\n } else {\n choices.push({\n name: `Submit (${filled} of ${total} required complete)`,\n value: \"submit\",\n disabled: true,\n });\n }\n\n choices.push({ name: \"Cancel\", value: \"cancel\" });\n\n // Show breadcrumb header if we're in a nested path\n const breadcrumb = formatBreadcrumb(path);\n if (breadcrumb) {\n console.log(colors.dim(`📍 ${breadcrumb}`));\n }\n\n // Show menu\n const selection = await selectWithClear({\n message: \"Edit:\",\n choices,\n });\n\n // Clear the breadcrumb line too if we printed one\n if (breadcrumb) {\n clearLines(1);\n }\n\n // Handle escape\n if (selection === null) {\n // Check if any values have been set\n const hasValues = Array.from(state.values()).some(\n (s) => s.status === \"set\" || s.status === \"null\",\n );\n\n if (hasValues) {\n const confirmCancel = await confirmWithClear({\n message: \"Discard changes?\",\n default: false,\n });\n if (confirmCancel === null || confirmCancel === false) {\n continue; // Return to menu\n }\n }\n return { action: \"cancel\" };\n }\n\n // Handle submit\n if (selection === \"submit\") {\n if (!canSubmit) {\n continue; // Shouldn't happen due to disabled, but be safe\n }\n const result = buildResultObject(fields, state, defs);\n return { action: \"submit\", value: result as T };\n }\n\n // Handle cancel\n if (selection === \"cancel\") {\n const hasValues = Array.from(state.values()).some(\n (s) => s.status === \"set\" || s.status === \"null\",\n );\n\n if (hasValues) {\n const confirmCancel = await confirmWithClear({\n message: \"Discard changes?\",\n default: false,\n });\n if (confirmCancel === null || confirmCancel === false) {\n continue;\n }\n }\n return { action: \"cancel\" };\n }\n\n // Handle field selection\n if (typeof selection === \"string\" && selection.startsWith(\"field:\")) {\n const fieldName = selection.slice(6);\n const fieldType = fields[fieldName]!;\n const fieldState = state.get(fieldName)!;\n\n // If field has a value, show field sub-menu\n if (fieldState.status === \"set\" || fieldState.status === \"null\") {\n const menuResult = await promptFieldMenu({\n fieldName,\n fieldType,\n state: fieldState,\n });\n\n switch (menuResult.action) {\n case \"edit\": {\n // Edit the field\n try {\n const newValue = await fillField(\n fieldType,\n [...path, fieldName],\n defs,\n fieldState.status === \"set\" ? fieldState.value : undefined,\n );\n state.set(fieldName, { status: \"set\", value: newValue });\n } catch (e) {\n if (e instanceof UserCancelledError) {\n // Return to menu without changing\n continue;\n }\n throw e;\n }\n break;\n }\n\n case \"view\": {\n if (fieldState.status === \"set\") {\n await displayFullValue(fieldName, fieldState.value);\n }\n break;\n }\n\n case \"clear\": {\n state.set(fieldName, { status: \"unset\" });\n break;\n }\n\n case \"setNull\": {\n state.set(fieldName, { status: \"null\" });\n break;\n }\n\n case \"reset\": {\n // Reset to default value - show as unset, will use default on submit\n state.set(fieldName, { status: \"unset\" });\n break;\n }\n\n case \"back\":\n // Do nothing, return to main menu\n break;\n }\n } else {\n // Field is unset - directly prompt for value\n try {\n const newValue = await fillField(\n fieldType,\n [...path, fieldName],\n defs,\n defaults[fieldName],\n );\n // If user skipped an optional field (returned undefined), leave as unset\n if (newValue === undefined) {\n // Don't change state - field remains unset\n } else {\n state.set(fieldName, { status: \"set\", value: newValue });\n }\n } catch (e) {\n if (e instanceof UserCancelledError) {\n // Return to menu without changing\n continue;\n }\n throw e;\n }\n }\n }\n }\n}\n\n/**\n * Build the result object from the state map.\n * Applies defaults for unset fields with defaults.\n */\nfunction buildResultObject(\n fields: Record<string, TSchema>,\n state: Map<string, FieldState>,\n _defs: Record<string, TSchema>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [fieldName, fieldType] of Object.entries(fields)) {\n const fieldState = state.get(fieldName);\n\n if (!fieldState || fieldState.status === \"unset\") {\n // If optional, skip (undefined)\n if (isOptional(fieldType)) {\n continue;\n }\n\n // If field has a default, use it\n if (hasDefault(fieldType)) {\n result[fieldName] = getDefault(fieldType);\n continue;\n }\n\n // No value, no default: omit from output\n continue;\n }\n\n if (fieldState.status === \"null\") {\n result[fieldName] = null;\n continue;\n }\n\n if (fieldState.status === \"set\") {\n result[fieldName] = fieldState.value;\n }\n }\n\n return result;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;;AAEA,SAASA,SAAS,QAAQ,gBAAgB;AAE1C,SAASC,eAAe,EAAEC,gBAAgB,EAAEC,UAAU,QAAQ,eAAe;AAC7E,OAAOC,MAAM,MAAM,iBAAiB;AAEpC,SAASC,kBAAkB,QAAQ,aAAa;AAChD,SAASC,UAAU,EAAEC,UAAU,EAAEC,UAAU,EAAcC,SAAS,QAAQ,mBAAmB;AAC7F,SACEC,eAAe,EACfC,mBAAmB,EACnBC,uBAAuB,EACvBC,oBAAoB,EACpBC,eAAe,QACV,yBAAyB;AAChC,SAASC,gBAAgB,QAAQ,wBAAwB;AACzD,SAASC,eAAe,EAAEC,gBAAgB,QAAQ,iBAAiB;;AAEnE;AACA;AACA;;AAQA;AACA;AACA;;AAcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,YAAYA,CAChCC,OAA0B,EACI;EAC9B,MAAM;IAAEC,IAAI;IAAEC,IAAI;IAAEC,IAAI;IAAEC,QAAQ,GAAG,CAAC,CAAC;IAAEC;EAAU,CAAC,GAAGL,OAAO;EAE9D,MAAMM,MAAM,GAAGL,IAAI,CAACM,UAAqC;EACzD,MAAMC,UAAU,GAAGC,MAAM,CAACC,IAAI,CAACJ,MAAM,CAAC;;EAEtC;EACA,IAAIE,UAAU,CAACG,MAAM,KAAK,CAAC,EAAE;IAC3B,OAAO;MAAEC,MAAM,EAAE,QAAQ;MAAEC,KAAK,EAAE,CAAC;IAAO,CAAC;EAC7C;;EAEA;EACA,MAAMC,cAAc,GAAGN,UAAU,CAACO,MAAM,CAAEC,IAAI,IAC5CrB,eAAe,CAACW,MAAM,CAACU,IAAI,CAAE,CAC/B,CAAC;EACD,MAAMC,cAAc,GAAGT,UAAU,CAACO,MAAM,CACrCC,IAAI,IAAK,CAACrB,eAAe,CAACW,MAAM,CAACU,IAAI,CAAE,CAC1C,CAAC;EAED,IAAIF,cAAc,CAACH,MAAM,KAAK,CAAC,IAAIM,cAAc,CAACN,MAAM,KAAK,CAAC,EAAE;IAC9D,MAAMO,SAAS,GAAGJ,cAAc,CAAC,CAAC,CAAE;IACpC,MAAMK,SAAS,GAAGb,MAAM,CAACY,SAAS,CAAE;IACpC,IAAI;MACF,MAAML,KAAK,GAAG,MAAMR,SAAS,CAC3Bc,SAAS,EACT,CAAC,GAAGjB,IAAI,EAAEgB,SAAS,CAAC,EACpBf,IAAI,EACJC,QAAQ,CAACc,SAAS,CACpB,CAAC;MACD,OAAO;QACLN,MAAM,EAAE,QAAQ;QAChBC,KAAK,EAAE;UAAE,CAACK,SAAS,GAAGL;QAAM;MAC9B,CAAC;IACH,CAAC,CAAC,OAAOO,CAAC,EAAE;MACV,IAAIA,CAAC,YAAYlC,kBAAkB,EAAE;QACnC,OAAO;UAAE0B,MAAM,EAAE;QAAS,CAAC;MAC7B;MACA,MAAMQ,CAAC;IACT;EACF;;EAEA;EACA,MAAMC,KAAK,GAAG,IAAIC,GAAG,CAAqB,CAAC;EAC3C,KAAK,MAAMJ,SAAS,IAAIV,UAAU,EAAE;IAClC,MAAMW,SAAS,GAAGb,MAAM,CAACY,SAAS,CAAE;IACpC;IACA,IAAI5B,SAAS,CAAC6B,SAAS,CAAC,EAAE;MACxBE,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;QAAEM,MAAM,EAAE,KAAK;QAAEX,KAAK,EAAEM,SAAS,CAACM;MAAM,CAAC,CAAC;IACjE,CAAC,MAAM;MACLJ,KAAK,CAACE,GAAG,CAACL,SAAS,EAAExB,oBAAoB,CAACyB,SAAS,EAAEf,QAAQ,CAACc,SAAS,CAAC,CAAC,CAAC;IAC5E;EACF;;EAEA;EACA,OAAO,IAAI,EAAE;IACX,MAAM;MAAEQ,KAAK;MAAEC;IAAO,CAAC,GAAGnC,mBAAmB,CAACS,IAAI,EAAEoB,KAAK,CAAC;IAC1D,MAAMO,SAAS,GAAGnC,uBAAuB,CAACQ,IAAI,EAAEoB,KAAK,CAAC;;IAEtD;IACA,MAAMQ,OAAwF,GAAG,EAAE;;IAEnG;IACA,KAAK,MAAMX,SAAS,IAAIV,UAAU,EAAE;MAClC,MAAMW,SAAS,GAAGb,MAAM,CAACY,SAAS,CAAE;MACpC,MAAMY,UAAU,GAAGT,KAAK,CAACU,GAAG,CAACb,SAAS,CAAE;MACxC,MAAMc,KAAK,GAAGzC,eAAe,CAAC2B,SAAS,EAAEC,SAAS,EAAEW,UAAU,CAAC;MAC/DD,OAAO,CAACI,IAAI,CAAC;QAAEjB,IAAI,EAAEgB,KAAK;QAAEnB,KAAK,EAAE,SAASK,SAAS;MAAG,CAAC,CAAC;IAC5D;;IAEA;IACAW,OAAO,CAACI,IAAI,CAAC,IAAIpD,SAAS,CAAC,+CAA+C,CAAC,CAAC;;IAE5E;IACA,IAAI+C,SAAS,EAAE;MACbC,OAAO,CAACI,IAAI,CAAC;QAAEjB,IAAI,EAAE,QAAQ;QAAEH,KAAK,EAAE;MAAS,CAAC,CAAC;IACnD,CAAC,MAAM;MACLgB,OAAO,CAACI,IAAI,CAAC;QACXjB,IAAI,EAAE,WAAWW,MAAM,OAAOD,KAAK,qBAAqB;QACxDb,KAAK,EAAE,QAAQ;QACfqB,QAAQ,EAAE;MACZ,CAAC,CAAC;IACJ;IAEAL,OAAO,CAACI,IAAI,CAAC;MAAEjB,IAAI,EAAE,QAAQ;MAAEH,KAAK,EAAE;IAAS,CAAC,CAAC;;IAEjD;IACA,MAAMsB,UAAU,GAAGvC,gBAAgB,CAACM,IAAI,CAAC;IACzC,IAAIiC,UAAU,EAAE;MACdC,OAAO,CAACC,GAAG,CAACpD,MAAM,CAACqD,GAAG,CAAC,MAAMH,UAAU,EAAE,CAAC,CAAC;IAC7C;;IAEA;IACA,MAAMI,SAAS,GAAG,MAAMzD,eAAe,CAAC;MACtC0D,OAAO,EAAE,OAAO;MAChBX;IACF,CAAC,CAAC;;IAEF;IACA,IAAIM,UAAU,EAAE;MACdnD,UAAU,CAAC,CAAC,CAAC;IACf;;IAEA;IACA,IAAIuD,SAAS,KAAK,IAAI,EAAE;MACtB;MACA,MAAME,SAAS,GAAGC,KAAK,CAACC,IAAI,CAACtB,KAAK,CAACuB,MAAM,CAAC,CAAC,CAAC,CAACC,IAAI,CAC9CC,CAAC,IAAKA,CAAC,CAACtB,MAAM,KAAK,KAAK,IAAIsB,CAAC,CAACtB,MAAM,KAAK,MAC5C,CAAC;MAED,IAAIiB,SAAS,EAAE;QACb,MAAMM,aAAa,GAAG,MAAMhE,gBAAgB,CAAC;UAC3CyD,OAAO,EAAE,kBAAkB;UAC3BQ,OAAO,EAAE;QACX,CAAC,CAAC;QACF,IAAID,aAAa,KAAK,IAAI,IAAIA,aAAa,KAAK,KAAK,EAAE;UACrD,SAAS,CAAC;QACZ;MACF;MACA,OAAO;QAAEnC,MAAM,EAAE;MAAS,CAAC;IAC7B;;IAEA;IACA,IAAI2B,SAAS,KAAK,QAAQ,EAAE;MAC1B,IAAI,CAACX,SAAS,EAAE;QACd,SAAS,CAAC;MACZ;MACA,MAAMqB,MAAM,GAAGC,iBAAiB,CAAC5C,MAAM,EAAEe,KAAK,EAAElB,IAAI,CAAC;MACrD,OAAO;QAAES,MAAM,EAAE,QAAQ;QAAEC,KAAK,EAAEoC;MAAY,CAAC;IACjD;;IAEA;IACA,IAAIV,SAAS,KAAK,QAAQ,EAAE;MAC1B,MAAME,SAAS,GAAGC,KAAK,CAACC,IAAI,CAACtB,KAAK,CAACuB,MAAM,CAAC,CAAC,CAAC,CAACC,IAAI,CAC9CC,CAAC,IAAKA,CAAC,CAACtB,MAAM,KAAK,KAAK,IAAIsB,CAAC,CAACtB,MAAM,KAAK,MAC5C,CAAC;MAED,IAAIiB,SAAS,EAAE;QACb,MAAMM,aAAa,GAAG,MAAMhE,gBAAgB,CAAC;UAC3CyD,OAAO,EAAE,kBAAkB;UAC3BQ,OAAO,EAAE;QACX,CAAC,CAAC;QACF,IAAID,aAAa,KAAK,IAAI,IAAIA,aAAa,KAAK,KAAK,EAAE;UACrD;QACF;MACF;MACA,OAAO;QAAEnC,MAAM,EAAE;MAAS,CAAC;IAC7B;;IAEA;IACA,IAAI,OAAO2B,SAAS,KAAK,QAAQ,IAAIA,SAAS,CAACY,UAAU,CAAC,QAAQ,CAAC,EAAE;MACnE,MAAMjC,SAAS,GAAGqB,SAAS,CAACa,KAAK,CAAC,CAAC,CAAC;MACpC,MAAMjC,SAAS,GAAGb,MAAM,CAACY,SAAS,CAAE;MACpC,MAAMY,UAAU,GAAGT,KAAK,CAACU,GAAG,CAACb,SAAS,CAAE;;MAExC;MACA,IAAIY,UAAU,CAACN,MAAM,KAAK,KAAK,IAAIM,UAAU,CAACN,MAAM,KAAK,MAAM,EAAE;QAC/D,MAAM6B,UAAU,GAAG,MAAMxD,eAAe,CAAC;UACvCqB,SAAS;UACTC,SAAS;UACTE,KAAK,EAAES;QACT,CAAC,CAAC;QAEF,QAAQuB,UAAU,CAACzC,MAAM;UACvB,KAAK,MAAM;YAAE;cACX;cACA,IAAI;gBACF,MAAM0C,QAAQ,GAAG,MAAMjD,SAAS,CAC9Bc,SAAS,EACT,CAAC,GAAGjB,IAAI,EAAEgB,SAAS,CAAC,EACpBf,IAAI,EACJ2B,UAAU,CAACN,MAAM,KAAK,KAAK,GAAGM,UAAU,CAACjB,KAAK,GAAG0C,SACnD,CAAC;gBACDlC,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;kBAAEM,MAAM,EAAE,KAAK;kBAAEX,KAAK,EAAEyC;gBAAS,CAAC,CAAC;cAC1D,CAAC,CAAC,OAAOlC,CAAC,EAAE;gBACV,IAAIA,CAAC,YAAYlC,kBAAkB,EAAE;kBACnC;kBACA;gBACF;gBACA,MAAMkC,CAAC;cACT;cACA;YACF;UAEA,KAAK,MAAM;YAAE;cACX,IAAIU,UAAU,CAACN,MAAM,KAAK,KAAK,EAAE;gBAC/B,MAAM1B,gBAAgB,CAACoB,SAAS,EAAEY,UAAU,CAACjB,KAAK,CAAC;cACrD;cACA;YACF;UAEA,KAAK,OAAO;YAAE;cACZQ,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;gBAAEM,MAAM,EAAE;cAAQ,CAAC,CAAC;cACzC;YACF;UAEA,KAAK,SAAS;YAAE;cACdH,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;gBAAEM,MAAM,EAAE;cAAO,CAAC,CAAC;cACxC;YACF;UAEA,KAAK,OAAO;YAAE;cACZ;cACAH,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;gBAAEM,MAAM,EAAE;cAAQ,CAAC,CAAC;cACzC;YACF;UAEA,KAAK,MAAM;YACT;YACA;QACJ;MACF,CAAC,MAAM;QACL;QACA,IAAI;UACF,MAAM8B,QAAQ,GAAG,MAAMjD,SAAS,CAC9Bc,SAAS,EACT,CAAC,GAAGjB,IAAI,EAAEgB,SAAS,CAAC,EACpBf,IAAI,EACJC,QAAQ,CAACc,SAAS,CACpB,CAAC;UACD;UACA,IAAIoC,QAAQ,KAAKC,SAAS,EAAE;YAC1B;UAAA,CACD,MAAM;YACLlC,KAAK,CAACE,GAAG,CAACL,SAAS,EAAE;cAAEM,MAAM,EAAE,KAAK;cAAEX,KAAK,EAAEyC;YAAS,CAAC,CAAC;UAC1D;QACF,CAAC,CAAC,OAAOlC,CAAC,EAAE;UACV,IAAIA,CAAC,YAAYlC,kBAAkB,EAAE;YACnC;YACA;UACF;UACA,MAAMkC,CAAC;QACT;MACF;IACF;EACF;AACF;;AAEA;AACA;AACA;AACA;AACA,SAAS8B,iBAAiBA,CACxB5C,MAA+B,EAC/Be,KAA8B,EAC9BmC,KAA8B,EACL;EACzB,MAAMP,MAA+B,GAAG,CAAC,CAAC;EAE1C,KAAK,MAAM,CAAC/B,SAAS,EAAEC,SAAS,CAAC,IAAIV,MAAM,CAACgD,OAAO,CAACnD,MAAM,CAAC,EAAE;IAC3D,MAAMwB,UAAU,GAAGT,KAAK,CAACU,GAAG,CAACb,SAAS,CAAC;IAEvC,IAAI,CAACY,UAAU,IAAIA,UAAU,CAACN,MAAM,KAAK,OAAO,EAAE;MAChD;MACA,IAAIrC,UAAU,CAACgC,SAAS,CAAC,EAAE;QACzB;MACF;;MAEA;MACA,IAAI/B,UAAU,CAAC+B,SAAS,CAAC,EAAE;QACzB8B,MAAM,CAAC/B,SAAS,CAAC,GAAG7B,UAAU,CAAC8B,SAAS,CAAC;QACzC;MACF;;MAEA;MACA;IACF;IAEA,IAAIW,UAAU,CAACN,MAAM,KAAK,MAAM,EAAE;MAChCyB,MAAM,CAAC/B,SAAS,CAAC,GAAG,IAAI;MACxB;IACF;IAEA,IAAIY,UAAU,CAACN,MAAM,KAAK,KAAK,EAAE;MAC/ByB,MAAM,CAAC/B,SAAS,CAAC,GAAGY,UAAU,CAACjB,KAAK;IACtC;EACF;EAEA,OAAOoC,MAAM;AACf","ignoreList":[]}
@@ -61,7 +61,7 @@ function normalizeChoices(choices) {
61
61
  export const selectCancellable = createPrompt((config, done) => {
62
62
  const {
63
63
  loop = true,
64
- pageSize = 7
64
+ pageSize = 15
65
65
  } = config;
66
66
  const theme = makeTheme(selectTheme, config.theme);
67
67
  const [status, setStatus] = useState("idle");
@@ -156,6 +156,64 @@ export const selectCancellable = createPrompt((config, done) => {
156
156
  const helpTip = colors.dim("(Use arrow keys, Enter to select, Esc to cancel)");
157
157
  return `${prefix} ${config.message} ${helpTip}\n${page}`;
158
158
  });
159
+
160
+ /**
161
+ * Clears N lines above the cursor.
162
+ * Used after prompts to clean up menu output.
163
+ */
164
+ export function clearLines(count) {
165
+ // Move up and clear each line
166
+ process.stdout.write("\x1b[1A\x1b[2K".repeat(count));
167
+ }
168
+
169
+ /**
170
+ * Select prompt that clears its output after completion.
171
+ * Returns the selected value or null if cancelled.
172
+ */
173
+ export async function selectWithClear(config) {
174
+ const result = await selectCancellable(config);
175
+
176
+ // Clear the "done" line that inquirer left behind
177
+ // Move up one line, clear it, move cursor to start
178
+ process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
179
+ return result;
180
+ }
181
+
182
+ /**
183
+ * Input prompt that clears its output after completion.
184
+ * Returns the input value or null if cancelled.
185
+ */
186
+ export async function inputWithClear(config) {
187
+ const result = await inputCancellable(config);
188
+
189
+ // Clear the "done" line
190
+ process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
191
+ return result;
192
+ }
193
+
194
+ /**
195
+ * Password prompt that clears its output after completion.
196
+ * Returns the password or null if cancelled.
197
+ */
198
+ export async function passwordWithClear(config) {
199
+ const result = await passwordCancellable(config);
200
+
201
+ // Clear the "done" line
202
+ process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
203
+ return result;
204
+ }
205
+
206
+ /**
207
+ * Confirm prompt that clears its output after completion.
208
+ * Returns true/false or null if cancelled.
209
+ */
210
+ export async function confirmWithClear(config) {
211
+ const result = await confirmCancellable(config);
212
+
213
+ // Clear the "done" line
214
+ process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
215
+ return result;
216
+ }
159
217
  /**
160
218
  * Input prompt with escape key support.
161
219
  * Returns null if user presses Escape.