@compilr-dev/cli 0.4.0 → 0.5.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.
- package/README.md +30 -12
- package/dist/agent.d.ts +74 -1
- package/dist/agent.js +259 -76
- package/dist/anchors/index.d.ts +9 -0
- package/dist/anchors/index.js +9 -0
- package/dist/anchors/project-anchors.d.ts +79 -0
- package/dist/anchors/project-anchors.js +202 -0
- package/dist/commands/handler-types.d.ts +68 -0
- package/dist/commands/handler-types.js +8 -0
- package/dist/commands/handlers/agent-commands.d.ts +13 -0
- package/dist/commands/handlers/agent-commands.js +305 -0
- package/dist/commands/handlers/design-commands.d.ts +15 -0
- package/dist/commands/handlers/design-commands.js +334 -0
- package/dist/commands/handlers/index.d.ts +20 -0
- package/dist/commands/handlers/index.js +43 -0
- package/dist/commands/handlers/overlay-commands.d.ts +21 -0
- package/dist/commands/handlers/overlay-commands.js +287 -0
- package/dist/commands/handlers/project-commands.d.ts +11 -0
- package/dist/commands/handlers/project-commands.js +167 -0
- package/dist/commands/handlers/simple-commands.d.ts +19 -0
- package/dist/commands/handlers/simple-commands.js +144 -0
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/registry.d.ts +50 -0
- package/dist/commands/registry.js +75 -0
- package/dist/commands-v2/handlers/context.d.ts +13 -0
- package/dist/commands-v2/handlers/context.js +348 -0
- package/dist/commands-v2/handlers/core.d.ts +13 -0
- package/dist/commands-v2/handlers/core.js +165 -0
- package/dist/commands-v2/handlers/debug.d.ts +11 -0
- package/dist/commands-v2/handlers/debug.js +159 -0
- package/dist/commands-v2/handlers/index.d.ts +12 -0
- package/dist/commands-v2/handlers/index.js +24 -0
- package/dist/commands-v2/handlers/project.d.ts +22 -0
- package/dist/commands-v2/handlers/project.js +814 -0
- package/dist/commands-v2/handlers/settings.d.ts +15 -0
- package/dist/commands-v2/handlers/settings.js +235 -0
- package/dist/commands-v2/index.d.ts +13 -0
- package/dist/commands-v2/index.js +15 -0
- package/dist/commands-v2/registry.d.ts +37 -0
- package/dist/commands-v2/registry.js +80 -0
- package/dist/commands-v2/types.d.ts +75 -0
- package/dist/commands-v2/types.js +7 -0
- package/dist/commands.js +110 -7
- package/dist/index.js +288 -29
- package/dist/input-handlers/index.d.ts +7 -0
- package/dist/input-handlers/index.js +7 -0
- package/dist/input-handlers/memory-handler.d.ts +26 -0
- package/dist/input-handlers/memory-handler.js +68 -0
- package/dist/repl-helpers.d.ts +63 -0
- package/dist/repl-helpers.js +318 -0
- package/dist/repl-v2.d.ts +155 -0
- package/dist/repl-v2.js +774 -0
- package/dist/repl.d.ts +32 -4
- package/dist/repl.js +250 -977
- package/dist/settings/index.d.ts +23 -0
- package/dist/settings/index.js +48 -0
- package/dist/settings/paths.d.ts +110 -0
- package/dist/settings/paths.js +264 -0
- package/dist/templates/compilr-md.js +7 -4
- package/dist/templates/index.js +3 -4
- package/dist/themes/colors.js +3 -1
- package/dist/themes/registry.d.ts +5 -36
- package/dist/themes/registry.js +11 -95
- package/dist/themes/types.d.ts +3 -38
- package/dist/themes/types.js +2 -2
- package/dist/tools/anchor-tools.d.ts +31 -0
- package/dist/tools/anchor-tools.js +255 -0
- package/dist/tools/backlog-wrappers.d.ts +54 -0
- package/dist/tools/backlog-wrappers.js +338 -0
- package/dist/tools/backlog.js +1 -1
- package/dist/tools/db-tools.d.ts +65 -0
- package/dist/tools/db-tools.js +19 -0
- package/dist/tools/document-db.d.ts +43 -0
- package/dist/tools/document-db.js +220 -0
- package/dist/tools/project-db.d.ts +102 -0
- package/dist/tools/project-db.js +370 -0
- package/dist/tools/workitem-db.d.ts +103 -0
- package/dist/tools/workitem-db.js +549 -0
- package/dist/tools.js +13 -3
- package/dist/ui/agents-overlay-v2.d.ts +43 -0
- package/dist/ui/agents-overlay-v2.js +809 -0
- package/dist/ui/agents-overlay.d.ts +5 -5
- package/dist/ui/agents-overlay.js +782 -420
- package/dist/ui/anchors-overlay.d.ts +12 -0
- package/dist/ui/anchors-overlay.js +775 -0
- package/dist/ui/arch-type-overlay.d.ts +1 -6
- package/dist/ui/arch-type-overlay.js +175 -203
- package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
- package/dist/ui/ask-user-overlay-v2.js +555 -0
- package/dist/ui/ask-user-overlay.d.ts +2 -2
- package/dist/ui/ask-user-overlay.js +443 -535
- package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
- package/dist/ui/ask-user-simple-overlay.js +182 -209
- package/dist/ui/backlog-overlay.d.ts +16 -1
- package/dist/ui/backlog-overlay.js +525 -659
- package/dist/ui/base/index.d.ts +26 -0
- package/dist/ui/base/index.js +33 -0
- package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
- package/dist/ui/base/inline-overlay-utils.js +320 -0
- package/dist/ui/base/inline-overlay.d.ts +159 -0
- package/dist/ui/base/inline-overlay.js +257 -0
- package/dist/ui/base/key-utils.d.ts +15 -0
- package/dist/ui/base/key-utils.js +30 -0
- package/dist/ui/base/overlay-base-v2.d.ts +193 -0
- package/dist/ui/base/overlay-base-v2.js +246 -0
- package/dist/ui/base/overlay-base.d.ts +156 -0
- package/dist/ui/base/overlay-base.js +238 -0
- package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
- package/dist/ui/base/overlay-lifecycle.js +159 -0
- package/dist/ui/base/overlay-types.d.ts +185 -0
- package/dist/ui/base/overlay-types.js +7 -0
- package/dist/ui/base/render-utils.d.ts +8 -0
- package/dist/ui/base/render-utils.js +11 -0
- package/dist/ui/base/screen-stack.d.ts +148 -0
- package/dist/ui/base/screen-stack.js +184 -0
- package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
- package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
- package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
- package/dist/ui/base/tabbed-list-overlay.js +369 -0
- package/dist/ui/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/commands-overlay-v2.js +441 -0
- package/dist/ui/commands-overlay.d.ts +7 -2
- package/dist/ui/commands-overlay.js +384 -355
- package/dist/ui/config-overlay.d.ts +5 -4
- package/dist/ui/config-overlay.js +243 -513
- package/dist/ui/conversation.d.ts +75 -4
- package/dist/ui/conversation.js +374 -161
- package/dist/ui/docs-overlay.d.ts +17 -0
- package/dist/ui/docs-overlay.js +303 -0
- package/dist/ui/ephemeral.d.ts +1 -1
- package/dist/ui/ephemeral.js +1 -1
- package/dist/ui/features/index.d.ts +34 -0
- package/dist/ui/features/index.js +34 -0
- package/dist/ui/features/input-feature.d.ts +85 -0
- package/dist/ui/features/input-feature.js +238 -0
- package/dist/ui/features/list-feature.d.ts +155 -0
- package/dist/ui/features/list-feature.js +244 -0
- package/dist/ui/features/pagination-feature.d.ts +154 -0
- package/dist/ui/features/pagination-feature.js +238 -0
- package/dist/ui/features/search-feature.d.ts +148 -0
- package/dist/ui/features/search-feature.js +185 -0
- package/dist/ui/features/tab-feature.d.ts +194 -0
- package/dist/ui/features/tab-feature.js +307 -0
- package/dist/ui/footer-v2.d.ts +222 -0
- package/dist/ui/footer-v2.js +1349 -0
- package/dist/ui/footer.d.ts +107 -0
- package/dist/ui/footer.js +359 -67
- package/dist/ui/guardrail-overlay.d.ts +29 -0
- package/dist/ui/guardrail-overlay.js +145 -0
- package/dist/ui/help-overlay-v2.d.ts +34 -0
- package/dist/ui/help-overlay-v2.js +309 -0
- package/dist/ui/help-overlay.d.ts +16 -0
- package/dist/ui/help-overlay.js +316 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -3
- package/dist/ui/init-overlay-v2.d.ts +34 -0
- package/dist/ui/init-overlay-v2.js +600 -0
- package/dist/ui/init-overlay.d.ts +12 -2
- package/dist/ui/init-overlay.js +349 -270
- package/dist/ui/input-prompt-v2.d.ts +1 -0
- package/dist/ui/input-prompt-v2.js +14 -6
- package/dist/ui/input-prompt.d.ts +116 -33
- package/dist/ui/input-prompt.js +536 -337
- package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay-v2.js +114 -0
- package/dist/ui/iteration-limit-overlay.d.ts +2 -2
- package/dist/ui/iteration-limit-overlay.js +92 -128
- package/dist/ui/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/keys-overlay-v2.js +248 -0
- package/dist/ui/keys-overlay.d.ts +1 -0
- package/dist/ui/keys-overlay.js +203 -141
- package/dist/ui/line-utils.d.ts +88 -0
- package/dist/ui/line-utils.js +150 -0
- package/dist/ui/live-region.d.ts +161 -0
- package/dist/ui/live-region.js +387 -0
- package/dist/ui/mascot/expressions.d.ts +32 -0
- package/dist/ui/mascot/expressions.js +213 -0
- package/dist/ui/mascot/index.d.ts +8 -0
- package/dist/ui/mascot/index.js +8 -0
- package/dist/ui/mascot/renderer.d.ts +19 -0
- package/dist/ui/mascot/renderer.js +97 -0
- package/dist/ui/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/mascot-overlay-v2.js +138 -0
- package/dist/ui/mascot-overlay.d.ts +21 -0
- package/dist/ui/mascot-overlay.js +146 -0
- package/dist/ui/model-overlay-v2.d.ts +49 -0
- package/dist/ui/model-overlay-v2.js +118 -0
- package/dist/ui/model-overlay.d.ts +27 -0
- package/dist/ui/model-overlay.js +221 -0
- package/dist/ui/model-warning-overlay.js +3 -5
- package/dist/ui/new-overlay.d.ts +34 -0
- package/dist/ui/new-overlay.js +604 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
- package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
- package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
- package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
- package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
- package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
- package/dist/ui/overlay/index.d.ts +33 -0
- package/dist/ui/overlay/index.js +35 -0
- package/dist/ui/overlay/key-utils.d.ts +6 -0
- package/dist/ui/overlay/key-utils.js +6 -0
- package/dist/ui/overlay/overlay-types.d.ts +128 -0
- package/dist/ui/overlay/overlay-types.js +22 -0
- package/dist/ui/overlay/types.d.ts +135 -0
- package/dist/ui/overlay/types.js +22 -0
- package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
- package/dist/ui/overlays/help-overlay-v2.js +198 -0
- package/dist/ui/overlays/index.d.ts +11 -0
- package/dist/ui/overlays/index.js +11 -0
- package/dist/ui/overlays.d.ts +0 -4
- package/dist/ui/overlays.js +0 -444
- package/dist/ui/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/permission-overlay-v2.js +380 -0
- package/dist/ui/permission-overlay.d.ts +1 -1
- package/dist/ui/permission-overlay.js +186 -298
- package/dist/ui/projects-overlay.d.ts +19 -0
- package/dist/ui/projects-overlay.js +484 -0
- package/dist/ui/providers/types.d.ts +178 -0
- package/dist/ui/providers/types.js +9 -0
- package/dist/ui/render-modes.d.ts +36 -0
- package/dist/ui/render-modes.js +44 -0
- package/dist/ui/startup-menu.d.ts +36 -0
- package/dist/ui/startup-menu.js +236 -0
- package/dist/ui/subagent-renderer.d.ts +117 -0
- package/dist/ui/subagent-renderer.js +334 -0
- package/dist/ui/terminal-codes.d.ts +94 -0
- package/dist/ui/terminal-codes.js +124 -0
- package/dist/ui/terminal-renderer.d.ts +221 -0
- package/dist/ui/terminal-renderer.js +751 -0
- package/dist/ui/terminal-ui.d.ts +463 -0
- package/dist/ui/terminal-ui.js +2296 -0
- package/dist/ui/terminal.d.ts +20 -0
- package/dist/ui/terminal.js +72 -0
- package/dist/ui/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/theme-overlay-v2.js +135 -0
- package/dist/ui/theme-overlay.d.ts +24 -0
- package/dist/ui/theme-overlay.js +127 -0
- package/dist/ui/todo-zone.js +53 -25
- package/dist/ui/tool-formatters.d.ts +16 -0
- package/dist/ui/tool-formatters.js +516 -0
- package/dist/ui/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/tools-overlay-v2.js +218 -0
- package/dist/ui/tools-overlay.d.ts +10 -2
- package/dist/ui/tools-overlay.js +172 -220
- package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/tutorial-overlay.d.ts +1 -0
- package/dist/ui/tutorial-overlay.js +400 -302
- package/dist/ui/workflow-overlay.d.ts +22 -0
- package/dist/ui/workflow-overlay.js +636 -0
- package/dist/utils/debug-log.d.ts +28 -0
- package/dist/utils/debug-log.js +57 -0
- package/dist/utils/model-tiers.js +1 -1
- package/dist/utils/path-safety.d.ts +56 -0
- package/dist/utils/path-safety.js +239 -0
- package/dist/workflow/guided-mode-injector.d.ts +42 -0
- package/dist/workflow/guided-mode-injector.js +191 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/step-criteria.d.ts +62 -0
- package/dist/workflow/step-criteria.js +150 -0
- package/dist/workflow/step-tracker.d.ts +92 -0
- package/dist/workflow/step-tracker.js +141 -0
- package/package.json +12 -5
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Feature
|
|
3
|
+
*
|
|
4
|
+
* Composable text input handling for overlays.
|
|
5
|
+
* Provides cursor movement, text editing, and rendering.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* class MyScreen extends BaseScreen {
|
|
10
|
+
* private input = new InputFeature({ placeholder: 'Enter text...' });
|
|
11
|
+
*
|
|
12
|
+
* render(): string[] {
|
|
13
|
+
* return [
|
|
14
|
+
* ' ' + this.input.render(styles),
|
|
15
|
+
* ];
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* handleKey(data: Buffer): ScreenResult {
|
|
19
|
+
* const result = this.input.handleKey(data);
|
|
20
|
+
* if (result.handled) {
|
|
21
|
+
* return stay();
|
|
22
|
+
* }
|
|
23
|
+
* // Handle other keys (Enter, Escape, etc.)
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { isLeftArrow, isRightArrow, isBackspace, isDelete, isHome, isEnd, isCtrlA, isCtrlE, isWordLeft, isWordRight, extractPrintable, } from '../base/key-utils.js';
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// InputFeature Class
|
|
31
|
+
// =============================================================================
|
|
32
|
+
export class InputFeature {
|
|
33
|
+
/** Current text value */
|
|
34
|
+
_value;
|
|
35
|
+
/** Cursor position (0 = before first char, value.length = after last char) */
|
|
36
|
+
_cursor;
|
|
37
|
+
/** Placeholder text */
|
|
38
|
+
placeholder;
|
|
39
|
+
/** Maximum length */
|
|
40
|
+
maxLength;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this._value = options.initialValue ?? '';
|
|
43
|
+
this._cursor = this._value.length;
|
|
44
|
+
this.placeholder = options.placeholder ?? '';
|
|
45
|
+
this.maxLength = options.maxLength ?? 0;
|
|
46
|
+
}
|
|
47
|
+
// ===========================================================================
|
|
48
|
+
// Getters/Setters
|
|
49
|
+
// ===========================================================================
|
|
50
|
+
/** Get current value */
|
|
51
|
+
get value() {
|
|
52
|
+
return this._value;
|
|
53
|
+
}
|
|
54
|
+
/** Set value (resets cursor to end) */
|
|
55
|
+
set value(v) {
|
|
56
|
+
this._value = v;
|
|
57
|
+
this._cursor = v.length;
|
|
58
|
+
}
|
|
59
|
+
/** Get cursor position */
|
|
60
|
+
get cursor() {
|
|
61
|
+
return this._cursor;
|
|
62
|
+
}
|
|
63
|
+
/** Check if empty */
|
|
64
|
+
get isEmpty() {
|
|
65
|
+
return this._value.length === 0;
|
|
66
|
+
}
|
|
67
|
+
/** Get trimmed value */
|
|
68
|
+
get trimmedValue() {
|
|
69
|
+
return this._value.trim();
|
|
70
|
+
}
|
|
71
|
+
// ===========================================================================
|
|
72
|
+
// Key Handling
|
|
73
|
+
// ===========================================================================
|
|
74
|
+
/**
|
|
75
|
+
* Handle a key press.
|
|
76
|
+
* Returns whether the key was handled and whether to re-render.
|
|
77
|
+
*/
|
|
78
|
+
handleKey(data) {
|
|
79
|
+
// Left arrow - move cursor left
|
|
80
|
+
if (isLeftArrow(data)) {
|
|
81
|
+
if (this._cursor > 0) {
|
|
82
|
+
this._cursor--;
|
|
83
|
+
return { handled: true, render: true };
|
|
84
|
+
}
|
|
85
|
+
return { handled: true, render: false };
|
|
86
|
+
}
|
|
87
|
+
// Right arrow - move cursor right
|
|
88
|
+
if (isRightArrow(data)) {
|
|
89
|
+
if (this._cursor < this._value.length) {
|
|
90
|
+
this._cursor++;
|
|
91
|
+
return { handled: true, render: true };
|
|
92
|
+
}
|
|
93
|
+
return { handled: true, render: false };
|
|
94
|
+
}
|
|
95
|
+
// Home or Ctrl+A - move to start
|
|
96
|
+
if (isHome(data) || isCtrlA(data)) {
|
|
97
|
+
if (this._cursor > 0) {
|
|
98
|
+
this._cursor = 0;
|
|
99
|
+
return { handled: true, render: true };
|
|
100
|
+
}
|
|
101
|
+
return { handled: true, render: false };
|
|
102
|
+
}
|
|
103
|
+
// End or Ctrl+E - move to end
|
|
104
|
+
if (isEnd(data) || isCtrlE(data)) {
|
|
105
|
+
if (this._cursor < this._value.length) {
|
|
106
|
+
this._cursor = this._value.length;
|
|
107
|
+
return { handled: true, render: true };
|
|
108
|
+
}
|
|
109
|
+
return { handled: true, render: false };
|
|
110
|
+
}
|
|
111
|
+
// Word left (Ctrl+Left, Alt+b)
|
|
112
|
+
if (isWordLeft(data)) {
|
|
113
|
+
const newPos = this.findWordBoundaryLeft();
|
|
114
|
+
if (newPos !== this._cursor) {
|
|
115
|
+
this._cursor = newPos;
|
|
116
|
+
return { handled: true, render: true };
|
|
117
|
+
}
|
|
118
|
+
return { handled: true, render: false };
|
|
119
|
+
}
|
|
120
|
+
// Word right (Ctrl+Right, Alt+f)
|
|
121
|
+
if (isWordRight(data)) {
|
|
122
|
+
const newPos = this.findWordBoundaryRight();
|
|
123
|
+
if (newPos !== this._cursor) {
|
|
124
|
+
this._cursor = newPos;
|
|
125
|
+
return { handled: true, render: true };
|
|
126
|
+
}
|
|
127
|
+
return { handled: true, render: false };
|
|
128
|
+
}
|
|
129
|
+
// Backspace - delete before cursor
|
|
130
|
+
if (isBackspace(data)) {
|
|
131
|
+
if (this._cursor > 0) {
|
|
132
|
+
this._value =
|
|
133
|
+
this._value.slice(0, this._cursor - 1) + this._value.slice(this._cursor);
|
|
134
|
+
this._cursor--;
|
|
135
|
+
return { handled: true, render: true };
|
|
136
|
+
}
|
|
137
|
+
return { handled: true, render: false };
|
|
138
|
+
}
|
|
139
|
+
// Delete - delete at cursor
|
|
140
|
+
if (isDelete(data)) {
|
|
141
|
+
if (this._cursor < this._value.length) {
|
|
142
|
+
this._value =
|
|
143
|
+
this._value.slice(0, this._cursor) + this._value.slice(this._cursor + 1);
|
|
144
|
+
return { handled: true, render: true };
|
|
145
|
+
}
|
|
146
|
+
return { handled: true, render: false };
|
|
147
|
+
}
|
|
148
|
+
// Printable characters - insert at cursor
|
|
149
|
+
const printable = extractPrintable(data);
|
|
150
|
+
if (printable.length > 0) {
|
|
151
|
+
// Check max length
|
|
152
|
+
if (this.maxLength > 0 && this._value.length + printable.length > this.maxLength) {
|
|
153
|
+
const allowed = this.maxLength - this._value.length;
|
|
154
|
+
if (allowed <= 0) {
|
|
155
|
+
return { handled: true, render: false };
|
|
156
|
+
}
|
|
157
|
+
const truncated = printable.slice(0, allowed);
|
|
158
|
+
this._value =
|
|
159
|
+
this._value.slice(0, this._cursor) + truncated + this._value.slice(this._cursor);
|
|
160
|
+
this._cursor += truncated.length;
|
|
161
|
+
return { handled: true, render: true };
|
|
162
|
+
}
|
|
163
|
+
this._value =
|
|
164
|
+
this._value.slice(0, this._cursor) + printable + this._value.slice(this._cursor);
|
|
165
|
+
this._cursor += printable.length;
|
|
166
|
+
return { handled: true, render: true };
|
|
167
|
+
}
|
|
168
|
+
// Not handled
|
|
169
|
+
return { handled: false, render: false };
|
|
170
|
+
}
|
|
171
|
+
// ===========================================================================
|
|
172
|
+
// Rendering
|
|
173
|
+
// ===========================================================================
|
|
174
|
+
/**
|
|
175
|
+
* Render the input field with cursor.
|
|
176
|
+
* Returns a string like: "> text with cursor_"
|
|
177
|
+
*
|
|
178
|
+
* @param styles - Theme styles for coloring
|
|
179
|
+
* @param prefix - Prefix before the input (default: "> ")
|
|
180
|
+
*/
|
|
181
|
+
render(styles, prefix = '> ') {
|
|
182
|
+
if (this._value.length === 0 && this.placeholder) {
|
|
183
|
+
// Show placeholder with cursor at start
|
|
184
|
+
return styles.primary(prefix) + styles.primary('▋') + styles.muted(this.placeholder);
|
|
185
|
+
}
|
|
186
|
+
// Split text at cursor position
|
|
187
|
+
const before = this._value.slice(0, this._cursor);
|
|
188
|
+
const after = this._value.slice(this._cursor);
|
|
189
|
+
return styles.primary(prefix) + before + styles.primary('▋') + after;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Render input field without cursor (for non-focused state).
|
|
193
|
+
*/
|
|
194
|
+
renderWithoutCursor(styles, prefix = '> ') {
|
|
195
|
+
if (this._value.length === 0 && this.placeholder) {
|
|
196
|
+
return styles.muted(prefix + this.placeholder);
|
|
197
|
+
}
|
|
198
|
+
return prefix + this._value;
|
|
199
|
+
}
|
|
200
|
+
// ===========================================================================
|
|
201
|
+
// Helpers
|
|
202
|
+
// ===========================================================================
|
|
203
|
+
/** Clear the input */
|
|
204
|
+
clear() {
|
|
205
|
+
this._value = '';
|
|
206
|
+
this._cursor = 0;
|
|
207
|
+
}
|
|
208
|
+
/** Find word boundary to the left of cursor */
|
|
209
|
+
findWordBoundaryLeft() {
|
|
210
|
+
if (this._cursor === 0)
|
|
211
|
+
return 0;
|
|
212
|
+
let pos = this._cursor - 1;
|
|
213
|
+
// Skip whitespace
|
|
214
|
+
while (pos > 0 && /\s/.test(this._value[pos])) {
|
|
215
|
+
pos--;
|
|
216
|
+
}
|
|
217
|
+
// Skip word characters
|
|
218
|
+
while (pos > 0 && !/\s/.test(this._value[pos - 1])) {
|
|
219
|
+
pos--;
|
|
220
|
+
}
|
|
221
|
+
return pos;
|
|
222
|
+
}
|
|
223
|
+
/** Find word boundary to the right of cursor */
|
|
224
|
+
findWordBoundaryRight() {
|
|
225
|
+
if (this._cursor >= this._value.length)
|
|
226
|
+
return this._value.length;
|
|
227
|
+
let pos = this._cursor;
|
|
228
|
+
// Skip current word
|
|
229
|
+
while (pos < this._value.length && !/\s/.test(this._value[pos])) {
|
|
230
|
+
pos++;
|
|
231
|
+
}
|
|
232
|
+
// Skip whitespace
|
|
233
|
+
while (pos < this._value.length && /\s/.test(this._value[pos])) {
|
|
234
|
+
pos++;
|
|
235
|
+
}
|
|
236
|
+
return pos;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Feature
|
|
3
|
+
*
|
|
4
|
+
* Composable feature for list selection and navigation.
|
|
5
|
+
* Handles up/down navigation, selection, and rendering.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* interface MyState extends OverlayState, ListState {
|
|
10
|
+
* items: string[];
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* class MyOverlay extends BaseOverlay<MyState, string | null> {
|
|
14
|
+
* private listFeature = new ListFeature<string>(
|
|
15
|
+
* () => this.state.items,
|
|
16
|
+
* (item) => this.onSelect(item)
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* handleKey(data: Buffer): void {
|
|
20
|
+
* const result = this.listFeature.handleKey(data, this.state);
|
|
21
|
+
* if (result?.handled) {
|
|
22
|
+
* if (result.action === 'render') this.update();
|
|
23
|
+
* return;
|
|
24
|
+
* }
|
|
25
|
+
* // ... other key handling
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* render(): string[] {
|
|
29
|
+
* return [
|
|
30
|
+
* ...this.renderHeader('Select Item'),
|
|
31
|
+
* ...this.listFeature.renderItems(this.state, (opts) => {
|
|
32
|
+
* const prefix = opts.isSelected ? ' > ' : ' ';
|
|
33
|
+
* return opts.isSelected
|
|
34
|
+
* ? this.styles.primary(`${prefix}${opts.item}`)
|
|
35
|
+
* : this.styles.muted(`${prefix}${opts.item}`);
|
|
36
|
+
* }),
|
|
37
|
+
* ...this.renderFooter('↑↓/jk Navigate · Enter Select')
|
|
38
|
+
* ];
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
import type { KeyResult, ListState, ListItemRenderer } from '../base/overlay-types.js';
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for ListFeature.
|
|
46
|
+
*/
|
|
47
|
+
export interface ListFeatureConfig<T> {
|
|
48
|
+
/**
|
|
49
|
+
* Function to get the current list of items.
|
|
50
|
+
* Called each time keys are handled or items are rendered.
|
|
51
|
+
*/
|
|
52
|
+
getItems: () => T[];
|
|
53
|
+
/**
|
|
54
|
+
* Optional callback when an item is selected (Enter pressed).
|
|
55
|
+
* If not provided, Enter key is not handled by this feature.
|
|
56
|
+
*/
|
|
57
|
+
onSelect?: (item: T, index: number) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Whether to wrap navigation at boundaries.
|
|
60
|
+
* If true, navigating up from first item goes to last, and vice versa.
|
|
61
|
+
* Default: false
|
|
62
|
+
*/
|
|
63
|
+
wrapNavigation?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Feature for list selection and navigation.
|
|
67
|
+
*
|
|
68
|
+
* Handles:
|
|
69
|
+
* - Up/Down arrows and j/k for navigation
|
|
70
|
+
* - Enter for selection (if onSelect provided)
|
|
71
|
+
*
|
|
72
|
+
* @template T - Type of items in the list
|
|
73
|
+
*/
|
|
74
|
+
export declare class ListFeature<T> {
|
|
75
|
+
private readonly config;
|
|
76
|
+
/**
|
|
77
|
+
* Create a new ListFeature.
|
|
78
|
+
*
|
|
79
|
+
* @param getItems - Function to get current items
|
|
80
|
+
* @param onSelect - Optional callback for selection
|
|
81
|
+
*/
|
|
82
|
+
constructor(getItems: () => T[], onSelect?: (item: T, index: number) => void);
|
|
83
|
+
/**
|
|
84
|
+
* Create from config object (alternative constructor).
|
|
85
|
+
*/
|
|
86
|
+
static fromConfig<T>(config: ListFeatureConfig<T>): ListFeature<T>;
|
|
87
|
+
/**
|
|
88
|
+
* Handle a key press.
|
|
89
|
+
*
|
|
90
|
+
* @param data - Raw key buffer
|
|
91
|
+
* @param state - State containing selectedIndex
|
|
92
|
+
* @returns KeyResult if handled, null otherwise
|
|
93
|
+
*/
|
|
94
|
+
handleKey(data: Buffer, state: ListState): KeyResult | null;
|
|
95
|
+
/**
|
|
96
|
+
* Render list items using a custom renderer.
|
|
97
|
+
*
|
|
98
|
+
* @param state - State containing selectedIndex
|
|
99
|
+
* @param renderer - Function to render each item
|
|
100
|
+
* @returns Array of rendered lines
|
|
101
|
+
*/
|
|
102
|
+
renderItems(state: ListState, renderer: ListItemRenderer<T>): string[];
|
|
103
|
+
/**
|
|
104
|
+
* Render a subset of items (for pagination).
|
|
105
|
+
*
|
|
106
|
+
* @param state - State containing selectedIndex
|
|
107
|
+
* @param startIndex - First item index to render
|
|
108
|
+
* @param count - Number of items to render
|
|
109
|
+
* @param renderer - Function to render each item
|
|
110
|
+
* @returns Array of rendered lines
|
|
111
|
+
*/
|
|
112
|
+
renderItemsSlice(state: ListState, startIndex: number, count: number, renderer: ListItemRenderer<T>): string[];
|
|
113
|
+
/**
|
|
114
|
+
* Get the currently selected item.
|
|
115
|
+
*
|
|
116
|
+
* @param state - State containing selectedIndex
|
|
117
|
+
* @returns Selected item or undefined if out of bounds
|
|
118
|
+
*/
|
|
119
|
+
getSelectedItem(state: ListState): T | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Get the total number of items.
|
|
122
|
+
*/
|
|
123
|
+
getItemCount(): number;
|
|
124
|
+
/**
|
|
125
|
+
* Ensure selectedIndex is within bounds.
|
|
126
|
+
* Call this after items change.
|
|
127
|
+
*
|
|
128
|
+
* @param state - State to adjust
|
|
129
|
+
*/
|
|
130
|
+
clampSelection(state: ListState): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a simple list renderer with standard styling.
|
|
134
|
+
*
|
|
135
|
+
* @param styles - Theme styles object
|
|
136
|
+
* @param getLabel - Function to get display label from item
|
|
137
|
+
* @returns Renderer function
|
|
138
|
+
*/
|
|
139
|
+
export declare function createSimpleListRenderer<T>(styles: {
|
|
140
|
+
primary: (s: string) => string;
|
|
141
|
+
muted: (s: string) => string;
|
|
142
|
+
}, getLabel: (item: T) => string): ListItemRenderer<T>;
|
|
143
|
+
/**
|
|
144
|
+
* Create a list renderer with label and description.
|
|
145
|
+
*
|
|
146
|
+
* @param styles - Theme styles object
|
|
147
|
+
* @param getLabel - Function to get display label
|
|
148
|
+
* @param getDescription - Function to get description
|
|
149
|
+
* @param labelWidth - Fixed width for label column (for alignment)
|
|
150
|
+
* @returns Renderer function
|
|
151
|
+
*/
|
|
152
|
+
export declare function createLabelDescriptionRenderer<T>(styles: {
|
|
153
|
+
primary: (s: string) => string;
|
|
154
|
+
muted: (s: string) => string;
|
|
155
|
+
}, getLabel: (item: T) => string, getDescription: (item: T) => string, labelWidth?: number): ListItemRenderer<T>;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Feature
|
|
3
|
+
*
|
|
4
|
+
* Composable feature for list selection and navigation.
|
|
5
|
+
* Handles up/down navigation, selection, and rendering.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* interface MyState extends OverlayState, ListState {
|
|
10
|
+
* items: string[];
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* class MyOverlay extends BaseOverlay<MyState, string | null> {
|
|
14
|
+
* private listFeature = new ListFeature<string>(
|
|
15
|
+
* () => this.state.items,
|
|
16
|
+
* (item) => this.onSelect(item)
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* handleKey(data: Buffer): void {
|
|
20
|
+
* const result = this.listFeature.handleKey(data, this.state);
|
|
21
|
+
* if (result?.handled) {
|
|
22
|
+
* if (result.action === 'render') this.update();
|
|
23
|
+
* return;
|
|
24
|
+
* }
|
|
25
|
+
* // ... other key handling
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* render(): string[] {
|
|
29
|
+
* return [
|
|
30
|
+
* ...this.renderHeader('Select Item'),
|
|
31
|
+
* ...this.listFeature.renderItems(this.state, (opts) => {
|
|
32
|
+
* const prefix = opts.isSelected ? ' > ' : ' ';
|
|
33
|
+
* return opts.isSelected
|
|
34
|
+
* ? this.styles.primary(`${prefix}${opts.item}`)
|
|
35
|
+
* : this.styles.muted(`${prefix}${opts.item}`);
|
|
36
|
+
* }),
|
|
37
|
+
* ...this.renderFooter('↑↓/jk Navigate · Enter Select')
|
|
38
|
+
* ];
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
import { isNavigateUp, isNavigateDown, isEnter, } from '../base/key-utils.js';
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// ListFeature Class
|
|
46
|
+
// =============================================================================
|
|
47
|
+
/**
|
|
48
|
+
* Feature for list selection and navigation.
|
|
49
|
+
*
|
|
50
|
+
* Handles:
|
|
51
|
+
* - Up/Down arrows and j/k for navigation
|
|
52
|
+
* - Enter for selection (if onSelect provided)
|
|
53
|
+
*
|
|
54
|
+
* @template T - Type of items in the list
|
|
55
|
+
*/
|
|
56
|
+
export class ListFeature {
|
|
57
|
+
config;
|
|
58
|
+
/**
|
|
59
|
+
* Create a new ListFeature.
|
|
60
|
+
*
|
|
61
|
+
* @param getItems - Function to get current items
|
|
62
|
+
* @param onSelect - Optional callback for selection
|
|
63
|
+
*/
|
|
64
|
+
constructor(getItems, onSelect) {
|
|
65
|
+
this.config = { getItems, onSelect };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create from config object (alternative constructor).
|
|
69
|
+
*/
|
|
70
|
+
static fromConfig(config) {
|
|
71
|
+
const feature = new ListFeature(config.getItems, config.onSelect);
|
|
72
|
+
if (config.wrapNavigation !== undefined) {
|
|
73
|
+
feature.config.wrapNavigation = config.wrapNavigation;
|
|
74
|
+
}
|
|
75
|
+
return feature;
|
|
76
|
+
}
|
|
77
|
+
// ===========================================================================
|
|
78
|
+
// Key Handling
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
/**
|
|
81
|
+
* Handle a key press.
|
|
82
|
+
*
|
|
83
|
+
* @param data - Raw key buffer
|
|
84
|
+
* @param state - State containing selectedIndex
|
|
85
|
+
* @returns KeyResult if handled, null otherwise
|
|
86
|
+
*/
|
|
87
|
+
handleKey(data, state) {
|
|
88
|
+
const items = this.config.getItems();
|
|
89
|
+
const itemCount = items.length;
|
|
90
|
+
if (itemCount === 0) {
|
|
91
|
+
return null; // Nothing to navigate
|
|
92
|
+
}
|
|
93
|
+
// Navigate up
|
|
94
|
+
if (isNavigateUp(data)) {
|
|
95
|
+
if (state.selectedIndex > 0) {
|
|
96
|
+
state.selectedIndex--;
|
|
97
|
+
return { handled: true, action: 'render' };
|
|
98
|
+
}
|
|
99
|
+
else if (this.config.wrapNavigation) {
|
|
100
|
+
state.selectedIndex = itemCount - 1;
|
|
101
|
+
return { handled: true, action: 'render' };
|
|
102
|
+
}
|
|
103
|
+
return { handled: true }; // At top, consume but no change
|
|
104
|
+
}
|
|
105
|
+
// Navigate down
|
|
106
|
+
if (isNavigateDown(data)) {
|
|
107
|
+
if (state.selectedIndex < itemCount - 1) {
|
|
108
|
+
state.selectedIndex++;
|
|
109
|
+
return { handled: true, action: 'render' };
|
|
110
|
+
}
|
|
111
|
+
else if (this.config.wrapNavigation) {
|
|
112
|
+
state.selectedIndex = 0;
|
|
113
|
+
return { handled: true, action: 'render' };
|
|
114
|
+
}
|
|
115
|
+
return { handled: true }; // At bottom, consume but no change
|
|
116
|
+
}
|
|
117
|
+
// Select (Enter)
|
|
118
|
+
if (isEnter(data) && this.config.onSelect) {
|
|
119
|
+
const item = items[state.selectedIndex];
|
|
120
|
+
if (item !== undefined) {
|
|
121
|
+
this.config.onSelect(item, state.selectedIndex);
|
|
122
|
+
return { handled: true };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null; // Not handled
|
|
126
|
+
}
|
|
127
|
+
// ===========================================================================
|
|
128
|
+
// Rendering
|
|
129
|
+
// ===========================================================================
|
|
130
|
+
/**
|
|
131
|
+
* Render list items using a custom renderer.
|
|
132
|
+
*
|
|
133
|
+
* @param state - State containing selectedIndex
|
|
134
|
+
* @param renderer - Function to render each item
|
|
135
|
+
* @returns Array of rendered lines
|
|
136
|
+
*/
|
|
137
|
+
renderItems(state, renderer) {
|
|
138
|
+
const items = this.config.getItems();
|
|
139
|
+
return items.map((item, index) => renderer({
|
|
140
|
+
item,
|
|
141
|
+
isSelected: index === state.selectedIndex,
|
|
142
|
+
index,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Render a subset of items (for pagination).
|
|
147
|
+
*
|
|
148
|
+
* @param state - State containing selectedIndex
|
|
149
|
+
* @param startIndex - First item index to render
|
|
150
|
+
* @param count - Number of items to render
|
|
151
|
+
* @param renderer - Function to render each item
|
|
152
|
+
* @returns Array of rendered lines
|
|
153
|
+
*/
|
|
154
|
+
renderItemsSlice(state, startIndex, count, renderer) {
|
|
155
|
+
const items = this.config.getItems();
|
|
156
|
+
const endIndex = Math.min(startIndex + count, items.length);
|
|
157
|
+
const lines = [];
|
|
158
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
159
|
+
const item = items[i];
|
|
160
|
+
lines.push(renderer({
|
|
161
|
+
item,
|
|
162
|
+
isSelected: i === state.selectedIndex,
|
|
163
|
+
index: i,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
return lines;
|
|
167
|
+
}
|
|
168
|
+
// ===========================================================================
|
|
169
|
+
// Utilities
|
|
170
|
+
// ===========================================================================
|
|
171
|
+
/**
|
|
172
|
+
* Get the currently selected item.
|
|
173
|
+
*
|
|
174
|
+
* @param state - State containing selectedIndex
|
|
175
|
+
* @returns Selected item or undefined if out of bounds
|
|
176
|
+
*/
|
|
177
|
+
getSelectedItem(state) {
|
|
178
|
+
const items = this.config.getItems();
|
|
179
|
+
return items[state.selectedIndex];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the total number of items.
|
|
183
|
+
*/
|
|
184
|
+
getItemCount() {
|
|
185
|
+
return this.config.getItems().length;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Ensure selectedIndex is within bounds.
|
|
189
|
+
* Call this after items change.
|
|
190
|
+
*
|
|
191
|
+
* @param state - State to adjust
|
|
192
|
+
*/
|
|
193
|
+
clampSelection(state) {
|
|
194
|
+
const itemCount = this.getItemCount();
|
|
195
|
+
if (itemCount === 0) {
|
|
196
|
+
state.selectedIndex = 0;
|
|
197
|
+
}
|
|
198
|
+
else if (state.selectedIndex >= itemCount) {
|
|
199
|
+
state.selectedIndex = itemCount - 1;
|
|
200
|
+
}
|
|
201
|
+
else if (state.selectedIndex < 0) {
|
|
202
|
+
state.selectedIndex = 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// Helper Functions
|
|
208
|
+
// =============================================================================
|
|
209
|
+
/**
|
|
210
|
+
* Create a simple list renderer with standard styling.
|
|
211
|
+
*
|
|
212
|
+
* @param styles - Theme styles object
|
|
213
|
+
* @param getLabel - Function to get display label from item
|
|
214
|
+
* @returns Renderer function
|
|
215
|
+
*/
|
|
216
|
+
export function createSimpleListRenderer(styles, getLabel) {
|
|
217
|
+
return ({ item, isSelected }) => {
|
|
218
|
+
const prefix = isSelected ? ' > ' : ' ';
|
|
219
|
+
const label = getLabel(item);
|
|
220
|
+
return isSelected
|
|
221
|
+
? styles.primary(`${prefix}${label}`)
|
|
222
|
+
: styles.muted(`${prefix}${label}`);
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Create a list renderer with label and description.
|
|
227
|
+
*
|
|
228
|
+
* @param styles - Theme styles object
|
|
229
|
+
* @param getLabel - Function to get display label
|
|
230
|
+
* @param getDescription - Function to get description
|
|
231
|
+
* @param labelWidth - Fixed width for label column (for alignment)
|
|
232
|
+
* @returns Renderer function
|
|
233
|
+
*/
|
|
234
|
+
export function createLabelDescriptionRenderer(styles, getLabel, getDescription, labelWidth = 20) {
|
|
235
|
+
return ({ item, isSelected }) => {
|
|
236
|
+
const prefix = isSelected ? ' > ' : ' ';
|
|
237
|
+
const label = getLabel(item).padEnd(labelWidth);
|
|
238
|
+
const desc = getDescription(item);
|
|
239
|
+
if (isSelected) {
|
|
240
|
+
return styles.primary(`${prefix}${label}`) + styles.muted(desc);
|
|
241
|
+
}
|
|
242
|
+
return styles.muted(`${prefix}${label}${desc}`);
|
|
243
|
+
};
|
|
244
|
+
}
|