@within-7/minto 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents/AgentsCommand.js +459 -655
- package/dist/commands/agents/AgentsCommand.js.map +2 -2
- package/dist/commands/agents/types.js +1 -0
- package/dist/commands/agents/types.js.map +2 -2
- package/dist/commands/agents/utils/fileOperations.js +96 -36
- package/dist/commands/agents/utils/fileOperations.js.map +3 -3
- package/dist/commands/agents/utils/index.js +3 -1
- package/dist/commands/agents/utils/index.js.map +2 -2
- package/dist/commands/context.js +54 -23
- package/dist/commands/context.js.map +2 -2
- package/dist/commands/export.js +673 -93
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/language.js +19 -46
- package/dist/commands/language.js.map +2 -2
- package/dist/commands/mcp-interactive.js +419 -217
- package/dist/commands/mcp-interactive.js.map +2 -2
- package/dist/commands/model.js +415 -66
- package/dist/commands/model.js.map +2 -2
- package/dist/commands/permissions.js +75 -49
- package/dist/commands/permissions.js.map +2 -2
- package/dist/commands/plugin.js +882 -185
- package/dist/commands/plugin.js.map +3 -3
- package/dist/commands/resume.js +1 -1
- package/dist/commands/resume.js.map +1 -1
- package/dist/commands/sandbox.js +168 -70
- package/dist/commands/sandbox.js.map +2 -2
- package/dist/commands/setup.js +593 -107
- package/dist/commands/setup.js.map +2 -2
- package/dist/commands/stats.js +188 -131
- package/dist/commands/stats.js.map +2 -2
- package/dist/commands/status.js +75 -13
- package/dist/commands/status.js.map +2 -2
- package/dist/commands/undo.js +138 -174
- package/dist/commands/undo.js.map +2 -2
- package/dist/commands.js.map +1 -1
- package/dist/components/Help.js +165 -32
- package/dist/components/Help.js.map +2 -2
- package/dist/components/InfoPanel/InfoPanel.js +123 -0
- package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
- package/dist/components/InfoPanel/index.js +5 -0
- package/dist/components/InfoPanel/index.js.map +7 -0
- package/dist/components/InfoPanel/types.js +1 -0
- package/dist/components/InfoPanel/types.js.map +7 -0
- package/dist/components/ModelSelector/BrandTextInput.js +43 -0
- package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
- package/dist/components/ModelSelector/ModelSelector.js +419 -501
- package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
- package/dist/components/ModelSelector/WizardContainer.js +45 -0
- package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
- package/dist/components/ModelSelector/index.js +1 -3
- package/dist/components/ModelSelector/index.js.map +2 -2
- package/dist/components/PromptInput.js +5 -5
- package/dist/components/PromptInput.js.map +2 -2
- package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
- package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
- package/dist/components/SimpleSelector/index.js +5 -0
- package/dist/components/SimpleSelector/index.js.map +7 -0
- package/dist/components/SimpleSelector/types.js +1 -0
- package/dist/components/SimpleSelector/types.js.map +7 -0
- package/dist/components/StatusOverlayContent.js +21 -0
- package/dist/components/StatusOverlayContent.js.map +7 -0
- package/dist/components/TabbedListView/ScrollableList.js +31 -5
- package/dist/components/TabbedListView/ScrollableList.js.map +2 -2
- package/dist/components/TabbedListView/TabbedListView.js +122 -47
- package/dist/components/TabbedListView/TabbedListView.js.map +2 -2
- package/dist/core/backupHook.js +29 -0
- package/dist/core/backupHook.js.map +7 -0
- package/dist/core/config/defaults.js +8 -2
- package/dist/core/config/defaults.js.map +2 -2
- package/dist/core/config/schema.js +14 -2
- package/dist/core/config/schema.js.map +2 -2
- package/dist/core/costTracker.js +0 -16
- package/dist/core/costTracker.js.map +2 -2
- package/dist/cost-tracker.js +0 -16
- package/dist/cost-tracker.js.map +2 -2
- package/dist/entrypoints/bootstrap.js +3 -1
- package/dist/entrypoints/bootstrap.js.map +2 -2
- package/dist/entrypoints/cli.js +32 -0
- package/dist/entrypoints/cli.js.map +2 -2
- package/dist/i18n/locales/en.js +300 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +301 -2
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/i18n/types.js.map +1 -1
- package/dist/services/customCommands.js +30 -8
- package/dist/services/customCommands.js.map +2 -2
- package/dist/services/plugins/lspServers.js +1 -1
- package/dist/services/plugins/lspServers.js.map +2 -2
- package/dist/services/plugins/pluginRuntime.js +2 -1
- package/dist/services/plugins/pluginRuntime.js.map +2 -2
- package/dist/services/plugins/pluginValidation.js +10 -3
- package/dist/services/plugins/pluginValidation.js.map +2 -2
- package/dist/services/plugins/skillMarketplace.js +16 -8
- package/dist/services/plugins/skillMarketplace.js.map +2 -2
- package/dist/services/systemReminder.js +17 -6
- package/dist/services/systemReminder.js.map +2 -2
- package/dist/tools/FileEditTool/FileEditTool.js +7 -0
- package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
- package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
- package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
- package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
- package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
- package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
- package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
- package/dist/tools/TaskTool/TaskTool.js +9 -6
- package/dist/tools/TaskTool/TaskTool.js.map +2 -2
- package/dist/types/PermissionMode.js.map +1 -1
- package/dist/types/plugin.js +2 -4
- package/dist/types/plugin.js.map +2 -2
- package/dist/utils/agentHookExecutor.js +1 -4
- package/dist/utils/agentHookExecutor.js.map +2 -2
- package/dist/utils/agentLoader.js +67 -13
- package/dist/utils/agentLoader.js.map +2 -2
- package/dist/utils/agentMemory.js.map +2 -2
- package/dist/utils/claudeCodeSync.js +439 -0
- package/dist/utils/claudeCodeSync.js.map +7 -0
- package/dist/utils/config.js +1 -23
- package/dist/utils/config.js.map +2 -2
- package/dist/utils/execFileNoThrow.js +2 -1
- package/dist/utils/execFileNoThrow.js.map +2 -2
- package/dist/utils/marketplaceManager.js +80 -43
- package/dist/utils/marketplaceManager.js.map +2 -2
- package/dist/utils/messages.js +2 -2
- package/dist/utils/messages.js.map +2 -2
- package/dist/utils/pluginInstaller.js +34 -24
- package/dist/utils/pluginInstaller.js.map +2 -2
- package/dist/utils/pluginLoader.js +48 -25
- package/dist/utils/pluginLoader.js.map +2 -2
- package/dist/utils/repoFetcher.js +110 -0
- package/dist/utils/repoFetcher.js.map +7 -0
- package/dist/utils/skillLoader.js +18 -6
- package/dist/utils/skillLoader.js.map +2 -2
- package/dist/utils/stringSubstitution.js +4 -5
- package/dist/utils/stringSubstitution.js.map +2 -2
- package/dist/utils/teamConfig.js +153 -13
- package/dist/utils/teamConfig.js.map +2 -2
- package/dist/utils/terminal.js +1 -1
- package/dist/utils/terminal.js.map +2 -2
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useState, useMemo, useCallback } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { BRAND_GRADIENT, SEMANTIC_COLORS } from "../../constants/colors.js";
|
|
4
|
+
import { ScrollableList } from "../TabbedListView/index.js";
|
|
5
|
+
import { StatusOverlayContent } from "../StatusOverlayContent.js";
|
|
6
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
7
|
+
import { t } from "../../i18n/index.js";
|
|
8
|
+
const PROMPT_HEIGHT = 5;
|
|
9
|
+
const CHROME_HEIGHT = 4;
|
|
10
|
+
function toListItems(items) {
|
|
11
|
+
return items.map((item) => ({
|
|
12
|
+
id: item.id,
|
|
13
|
+
label: item.label,
|
|
14
|
+
description: item.description,
|
|
15
|
+
category: item.category,
|
|
16
|
+
metadata: item.isCurrent ? `(${t("common.selected")})` : void 0,
|
|
17
|
+
status: item.isCurrent ? "enabled" : void 0,
|
|
18
|
+
data: item
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
function SelectorItemRenderer({
|
|
22
|
+
item,
|
|
23
|
+
isFocused,
|
|
24
|
+
selectorItem
|
|
25
|
+
}) {
|
|
26
|
+
const textColor = isFocused ? BRAND_GRADIENT.START : SEMANTIC_COLORS.dim;
|
|
27
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(Text, { color: isFocused ? BRAND_GRADIENT.START : void 0 }, isFocused ? "\u25C6 " : " "), selectorItem?.statusIcon && /* @__PURE__ */ React.createElement(Text, { color: selectorItem.statusColor || SEMANTIC_COLORS.dim }, selectorItem.statusIcon, " "), /* @__PURE__ */ React.createElement(Text, { color: textColor }, item.label, selectorItem?.isCurrent && /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.success }, " (", t("common.selected"), ")"), item.description && /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.muted }, " ", item.description)));
|
|
28
|
+
}
|
|
29
|
+
function SimpleSelector({
|
|
30
|
+
title,
|
|
31
|
+
subtitle,
|
|
32
|
+
items,
|
|
33
|
+
onSelect,
|
|
34
|
+
onClose,
|
|
35
|
+
groupByCategory = false,
|
|
36
|
+
statusOverlay,
|
|
37
|
+
isActive = true,
|
|
38
|
+
maxVisible
|
|
39
|
+
}) {
|
|
40
|
+
const { rows } = useTerminalSize();
|
|
41
|
+
const dynamicVisible = Math.min(
|
|
42
|
+
8,
|
|
43
|
+
Math.max(3, rows - PROMPT_HEIGHT - CHROME_HEIGHT)
|
|
44
|
+
);
|
|
45
|
+
const effectiveMaxVisible = maxVisible ?? dynamicVisible;
|
|
46
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
47
|
+
const listItems = useMemo(() => toListItems(items), [items]);
|
|
48
|
+
const selectorItemMap = useMemo(() => {
|
|
49
|
+
const map = /* @__PURE__ */ new Map();
|
|
50
|
+
items.forEach((item) => map.set(item.id, item));
|
|
51
|
+
return map;
|
|
52
|
+
}, [items]);
|
|
53
|
+
const handleSelect = useCallback(
|
|
54
|
+
(listItem) => {
|
|
55
|
+
const selectorItem = selectorItemMap.get(listItem.id);
|
|
56
|
+
if (selectorItem) {
|
|
57
|
+
onSelect(selectorItem);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[selectorItemMap, onSelect]
|
|
61
|
+
);
|
|
62
|
+
const renderItem = useCallback(
|
|
63
|
+
(item, isFocused) => /* @__PURE__ */ React.createElement(
|
|
64
|
+
SelectorItemRenderer,
|
|
65
|
+
{
|
|
66
|
+
item,
|
|
67
|
+
isFocused,
|
|
68
|
+
selectorItem: selectorItemMap.get(item.id)
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
[selectorItemMap]
|
|
72
|
+
);
|
|
73
|
+
useInput(
|
|
74
|
+
(input, key) => {
|
|
75
|
+
if (statusOverlay && statusOverlay.type !== "loading") {
|
|
76
|
+
if (key.escape || key.return) {
|
|
77
|
+
onClose();
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (statusOverlay) return;
|
|
82
|
+
if (key.escape) {
|
|
83
|
+
onClose();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key.downArrow && listItems.length > 0) {
|
|
87
|
+
setFocusedIndex((prev) => prev < listItems.length - 1 ? prev + 1 : prev);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (key.upArrow && listItems.length > 0) {
|
|
91
|
+
setFocusedIndex((prev) => prev > 0 ? prev - 1 : prev);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (key.return && listItems.length > 0) {
|
|
95
|
+
handleSelect(listItems[focusedIndex]);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{ isActive }
|
|
100
|
+
);
|
|
101
|
+
const footerHint = statusOverlay ? statusOverlay.type === "loading" ? "" : t("ui.simpleSelector.dismissHint") : t("ui.simpleSelector.navigationHint");
|
|
102
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React.createElement(
|
|
103
|
+
Box,
|
|
104
|
+
{
|
|
105
|
+
borderTop: true,
|
|
106
|
+
borderBottom: false,
|
|
107
|
+
borderLeft: false,
|
|
108
|
+
borderRight: false,
|
|
109
|
+
borderColor: BRAND_GRADIENT.START,
|
|
110
|
+
borderStyle: "single",
|
|
111
|
+
width: "100%",
|
|
112
|
+
paddingX: 1,
|
|
113
|
+
flexDirection: "column"
|
|
114
|
+
},
|
|
115
|
+
/* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: BRAND_GRADIENT.START }, "\u25C6"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: SEMANTIC_COLORS.secondary }, " ", title), subtitle && /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", subtitle))
|
|
116
|
+
), /* @__PURE__ */ React.createElement(
|
|
117
|
+
Box,
|
|
118
|
+
{
|
|
119
|
+
flexDirection: "column",
|
|
120
|
+
borderTop: false,
|
|
121
|
+
borderBottom: true,
|
|
122
|
+
borderLeft: false,
|
|
123
|
+
borderRight: false,
|
|
124
|
+
borderColor: BRAND_GRADIENT.START,
|
|
125
|
+
borderStyle: "single",
|
|
126
|
+
width: "100%",
|
|
127
|
+
paddingX: 1,
|
|
128
|
+
paddingY: 1
|
|
129
|
+
},
|
|
130
|
+
statusOverlay ? /* @__PURE__ */ React.createElement(
|
|
131
|
+
StatusOverlayContent,
|
|
132
|
+
{
|
|
133
|
+
type: statusOverlay.type,
|
|
134
|
+
message: statusOverlay.message
|
|
135
|
+
}
|
|
136
|
+
) : listItems.length === 0 ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, t("ui.tabbedList.noItems"))) : /* @__PURE__ */ React.createElement(
|
|
137
|
+
ScrollableList,
|
|
138
|
+
{
|
|
139
|
+
items: listItems,
|
|
140
|
+
focusedIndex,
|
|
141
|
+
onFocusChange: setFocusedIndex,
|
|
142
|
+
onSelect: handleSelect,
|
|
143
|
+
visibleCount: effectiveMaxVisible,
|
|
144
|
+
groupByCategory,
|
|
145
|
+
renderItem
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
footerHint && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.muted }, footerHint))
|
|
149
|
+
));
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
SimpleSelector
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=SimpleSelector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/SimpleSelector/SimpleSelector.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * SimpleSelector Component\n *\n * A lightweight single-choice selector visually matching TabbedListView\n * but without tabs or search. Reuses ScrollableList internally.\n *\n * Visual pattern:\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 (BRAND_GRADIENT.START border)\n * \u25C6 Title subtitle\n *\n * Category Name\n * \u25C6 Item A (current)\n * Item B description\n * Item C description\n *\n * \u2191\u2193 Navigate \u00B7 Enter Select \u00B7 Esc Close\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 (BRAND_GRADIENT.START border)\n */\n\nimport React, { useState, useMemo, useCallback } from 'react'\nimport { Box, Text, useInput } from 'ink'\nimport { BRAND_GRADIENT, SEMANTIC_COLORS } from '@constants/colors'\nimport { ScrollableList } from '@components/TabbedListView'\nimport { StatusOverlayContent } from '@components/StatusOverlayContent'\nimport { useTerminalSize } from '@hooks/useTerminalSize'\nimport { t } from '@i18n'\nimport type { ListItem } from '@components/TabbedListView'\nimport type { SimpleSelectorProps, SelectorItem } from './types'\n\n// Heights for dynamic visible count calculation\nconst PROMPT_HEIGHT = 5 // PromptInput borders + input + hints\nconst CHROME_HEIGHT = 4 // title + footer + borders (no tabs/search)\n\n/**\n * Convert SelectorItems to ListItems for ScrollableList\n */\nfunction toListItems(items: SelectorItem[]): ListItem[] {\n return items.map(item => ({\n id: item.id,\n label: item.label,\n description: item.description,\n category: item.category,\n metadata: item.isCurrent ? `(${t('common.selected')})` : undefined,\n status: item.isCurrent ? ('enabled' as const) : undefined,\n data: item,\n }))\n}\n\n/**\n * Custom item renderer that supports statusIcon and statusColor\n */\nfunction SelectorItemRenderer({\n item,\n isFocused,\n selectorItem,\n}: {\n item: ListItem\n isFocused: boolean\n selectorItem?: SelectorItem\n}) {\n const textColor = isFocused ? BRAND_GRADIENT.START : SEMANTIC_COLORS.dim\n\n return (\n <Box flexDirection=\"row\">\n {/* Selection indicator */}\n <Text color={isFocused ? BRAND_GRADIENT.START : undefined}>\n {isFocused ? '\\u25C6 ' : ' '}\n </Text>\n\n {/* Status icon if present */}\n {selectorItem?.statusIcon && (\n <Text color={selectorItem.statusColor || SEMANTIC_COLORS.dim}>\n {selectorItem.statusIcon}{' '}\n </Text>\n )}\n\n {/* Main content */}\n <Text color={textColor}>\n {item.label}\n {selectorItem?.isCurrent && (\n <Text color={SEMANTIC_COLORS.success}> ({t('common.selected')})</Text>\n )}\n {item.description && (\n <Text color={SEMANTIC_COLORS.muted}> {item.description}</Text>\n )}\n </Text>\n </Box>\n )\n}\n\nexport function SimpleSelector({\n title,\n subtitle,\n items,\n onSelect,\n onClose,\n groupByCategory = false,\n statusOverlay,\n isActive = true,\n maxVisible,\n}: SimpleSelectorProps) {\n // Dynamic visible count based on terminal height\n const { rows } = useTerminalSize()\n const dynamicVisible = Math.min(\n 8,\n Math.max(3, rows - PROMPT_HEIGHT - CHROME_HEIGHT),\n )\n const effectiveMaxVisible = maxVisible ?? dynamicVisible\n\n const [focusedIndex, setFocusedIndex] = useState(0)\n\n const listItems = useMemo(() => toListItems(items), [items])\n\n // Map from ListItem back to SelectorItem for custom rendering\n const selectorItemMap = useMemo(() => {\n const map = new Map<string, SelectorItem>()\n items.forEach(item => map.set(item.id, item))\n return map\n }, [items])\n\n const handleSelect = useCallback(\n (listItem: ListItem) => {\n const selectorItem = selectorItemMap.get(listItem.id)\n if (selectorItem) {\n onSelect(selectorItem)\n }\n },\n [selectorItemMap, onSelect],\n )\n\n const renderItem = useCallback(\n (item: ListItem, isFocused: boolean) => (\n <SelectorItemRenderer\n item={item}\n isFocused={isFocused}\n selectorItem={selectorItemMap.get(item.id)}\n />\n ),\n [selectorItemMap],\n )\n\n // Keyboard input handling\n useInput(\n (input, key) => {\n // Status overlay: success/error -> ESC/Enter closes\n if (statusOverlay && statusOverlay.type !== 'loading') {\n if (key.escape || key.return) {\n onClose()\n }\n return\n }\n\n // Status overlay: loading -> ignore all input\n if (statusOverlay) return\n\n // Escape closes\n if (key.escape) {\n onClose()\n return\n }\n\n // Arrow navigation\n if (key.downArrow && listItems.length > 0) {\n setFocusedIndex(prev => (prev < listItems.length - 1 ? prev + 1 : prev))\n return\n }\n\n if (key.upArrow && listItems.length > 0) {\n setFocusedIndex(prev => (prev > 0 ? prev - 1 : prev))\n return\n }\n\n // Enter to select\n if (key.return && listItems.length > 0) {\n handleSelect(listItems[focusedIndex])\n return\n }\n },\n { isActive },\n )\n\n // Build footer hint\n const footerHint = statusOverlay\n ? statusOverlay.type === 'loading'\n ? ''\n : t('ui.simpleSelector.dismissHint')\n : t('ui.simpleSelector.navigationHint')\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {/* Header bar with top border */}\n <Box\n borderTop={true}\n borderBottom={false}\n borderLeft={false}\n borderRight={false}\n borderColor={BRAND_GRADIENT.START}\n borderStyle=\"single\"\n width=\"100%\"\n paddingX={1}\n flexDirection=\"column\"\n >\n {/* Title with brand color */}\n <Box>\n <Text color={BRAND_GRADIENT.START}>{'\\u25C6'}</Text>\n <Text bold color={SEMANTIC_COLORS.secondary}>\n {' '}\n {title}\n </Text>\n {subtitle && (\n <Text color={SEMANTIC_COLORS.dim}>\n {' '}\n {subtitle}\n </Text>\n )}\n </Box>\n </Box>\n\n {/* Content container with bottom border */}\n <Box\n flexDirection=\"column\"\n borderTop={false}\n borderBottom={true}\n borderLeft={false}\n borderRight={false}\n borderColor={BRAND_GRADIENT.START}\n borderStyle=\"single\"\n width=\"100%\"\n paddingX={1}\n paddingY={1}\n >\n {statusOverlay ? (\n <StatusOverlayContent\n type={statusOverlay.type}\n message={statusOverlay.message}\n />\n ) : listItems.length === 0 ? (\n <Box>\n <Text color={SEMANTIC_COLORS.dim}>\n {t('ui.tabbedList.noItems')}\n </Text>\n </Box>\n ) : (\n <ScrollableList\n items={listItems}\n focusedIndex={focusedIndex}\n onFocusChange={setFocusedIndex}\n onSelect={handleSelect}\n visibleCount={effectiveMaxVisible}\n groupByCategory={groupByCategory}\n renderItem={renderItem}\n />\n )}\n\n {/* Footer Hint */}\n {footerHint && (\n <Box marginTop={1}>\n <Text color={SEMANTIC_COLORS.muted}>{footerHint}</Text>\n </Box>\n )}\n </Box>\n </Box>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/SimpleSelector/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * SimpleSelector Component Exports\n *\n * A lightweight single-choice selector for quick selection commands.\n */\n\nexport { SimpleSelector } from './SimpleSelector'\nexport type { SimpleSelectorProps, SelectorItem, StatusOverlay } from './types'\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,sBAAsB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { BRAND_GRADIENT, SEMANTIC_COLORS } from "../constants/colors.js";
|
|
4
|
+
import { SimpleSpinner } from "./Spinner.js";
|
|
5
|
+
function StatusOverlayContent({
|
|
6
|
+
type,
|
|
7
|
+
message
|
|
8
|
+
}) {
|
|
9
|
+
switch (type) {
|
|
10
|
+
case "loading":
|
|
11
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(SimpleSpinner, null), /* @__PURE__ */ React.createElement(Text, { color: BRAND_GRADIENT.MIDDLE }, " ", message));
|
|
12
|
+
case "success":
|
|
13
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.success }, "\u2713", " ", message));
|
|
14
|
+
case "error":
|
|
15
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.error }, "\u2717", " ", message));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
StatusOverlayContent
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=StatusOverlayContent.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/components/StatusOverlayContent.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared StatusOverlayContent component\n *\n * Renders loading/success/error overlay content used by\n * TabbedListView, InfoPanel, and SimpleSelector.\n */\n\nimport React from 'react'\nimport { Box, Text } from 'ink'\nimport { BRAND_GRADIENT, SEMANTIC_COLORS } from '@constants/colors'\nimport { SimpleSpinner } from '@components/Spinner'\n\nexport type StatusOverlay = {\n type: 'loading' | 'success' | 'error'\n message: string\n}\n\nexport function StatusOverlayContent({\n type,\n message,\n}: {\n type: 'loading' | 'success' | 'error'\n message: string\n}) {\n switch (type) {\n case 'loading':\n return (\n <Box>\n <SimpleSpinner />\n <Text color={BRAND_GRADIENT.MIDDLE}> {message}</Text>\n </Box>\n )\n case 'success':\n return (\n <Box>\n <Text color={SEMANTIC_COLORS.success}>\n {'\\u2713'} {message}\n </Text>\n </Box>\n )\n case 'error':\n return (\n <Box>\n <Text color={SEMANTIC_COLORS.error}>\n {'\\u2717'} {message}\n </Text>\n </Box>\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "AAOA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,SAAS,gBAAgB,uBAAuB;AAChD,SAAS,qBAAqB;AAOvB,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGG;AACD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aACE,oCAAC,WACC,oCAAC,mBAAc,GACf,oCAAC,QAAK,OAAO,eAAe,UAAQ,KAAE,OAAQ,CAChD;AAAA,IAEJ,KAAK;AACH,aACE,oCAAC,WACC,oCAAC,QAAK,OAAO,gBAAgB,WAC1B,UAAS,KAAE,OACd,CACF;AAAA,IAEJ,KAAK;AACH,aACE,oCAAC,WACC,oCAAC,QAAK,OAAO,gBAAgB,SAC1B,UAAS,KAAE,OACd,CACF;AAAA,EAEN;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -17,11 +17,26 @@ const getStatusIcon = (status) => {
|
|
|
17
17
|
};
|
|
18
18
|
function DefaultItemRenderer({
|
|
19
19
|
item,
|
|
20
|
-
isFocused
|
|
20
|
+
isFocused,
|
|
21
|
+
multiSelect,
|
|
22
|
+
isSelected
|
|
21
23
|
}) {
|
|
22
24
|
const statusInfo = getStatusIcon(item.status);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
+
const checkboxWidth = multiSelect ? 2 : 0;
|
|
26
|
+
const focusWidth = 2;
|
|
27
|
+
const statusWidth = statusInfo ? 2 : 0;
|
|
28
|
+
const prefixPad = checkboxWidth + focusWidth + statusWidth;
|
|
29
|
+
const labelColor = isFocused ? BRAND_GRADIENT.START : SEMANTIC_COLORS.secondary;
|
|
30
|
+
const descColor = SEMANTIC_COLORS.dim;
|
|
31
|
+
const metaColor = SEMANTIC_COLORS.muted;
|
|
32
|
+
const hasDescription = !!item.description;
|
|
33
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, multiSelect && /* @__PURE__ */ React.createElement(
|
|
34
|
+
Text,
|
|
35
|
+
{
|
|
36
|
+
color: isSelected ? SEMANTIC_COLORS.success : SEMANTIC_COLORS.dim
|
|
37
|
+
},
|
|
38
|
+
isSelected ? "\u2611 " : "\u2610 "
|
|
39
|
+
), /* @__PURE__ */ React.createElement(Text, { color: isFocused ? BRAND_GRADIENT.START : void 0 }, isFocused ? "\u25C6 " : " "), statusInfo && /* @__PURE__ */ React.createElement(Text, { color: statusInfo.color }, statusInfo.icon, " "), /* @__PURE__ */ React.createElement(Text, { bold: isFocused, color: labelColor }, item.label), item.metadata && /* @__PURE__ */ React.createElement(Text, { color: metaColor }, " \xB7 ", item.metadata)), hasDescription && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: descColor }, " ".repeat(prefixPad), item.description)));
|
|
25
40
|
}
|
|
26
41
|
function ScrollableList({
|
|
27
42
|
items,
|
|
@@ -30,7 +45,9 @@ function ScrollableList({
|
|
|
30
45
|
onSelect,
|
|
31
46
|
visibleCount,
|
|
32
47
|
groupByCategory = false,
|
|
33
|
-
renderItem
|
|
48
|
+
renderItem,
|
|
49
|
+
multiSelect,
|
|
50
|
+
selectedIds
|
|
34
51
|
}) {
|
|
35
52
|
const { visibleItems, startIndex, endIndex } = useMemo(() => {
|
|
36
53
|
if (items.length === 0) {
|
|
@@ -80,7 +97,16 @@ function ScrollableList({
|
|
|
80
97
|
);
|
|
81
98
|
}
|
|
82
99
|
const isFocused = globalIndex === focusedIndex;
|
|
83
|
-
const
|
|
100
|
+
const isSelected = selectedIds?.has(item.id) ?? false;
|
|
101
|
+
const itemElement = renderItem ? renderItem(item, isFocused, isSelected) : /* @__PURE__ */ React.createElement(
|
|
102
|
+
DefaultItemRenderer,
|
|
103
|
+
{
|
|
104
|
+
item,
|
|
105
|
+
isFocused,
|
|
106
|
+
multiSelect,
|
|
107
|
+
isSelected
|
|
108
|
+
}
|
|
109
|
+
);
|
|
84
110
|
displayElements.push(/* @__PURE__ */ React.createElement(Box, { key: item.id }, itemElement));
|
|
85
111
|
});
|
|
86
112
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, hasMoreAbove && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " \u2191 ", countAbove, " more above...")), displayElements, hasMoreBelow && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " \u2193 ", countBelow, " more below...")));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/components/TabbedListView/ScrollableList.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * ScrollableList Component\n *\n * A scrollable list matching the /command suggestion UI pattern.\n * Uses \u25C6 indicator for selection, brand colors, and scroll indicators.\n */\n\nimport React, { useMemo } from 'react'\nimport { Box, Text } from 'ink'\nimport { SEMANTIC_COLORS, BRAND_GRADIENT } from '@constants/colors'\nimport { t } from '@i18n'\nimport type { ListItem, ScrollableListProps, CategoryGroup } from './types'\n\n/** Status icon mapping */\nconst getStatusIcon = (status?: ListItem['status']) => {\n switch (status) {\n case 'enabled':\n return { icon: '\u2714', color: SEMANTIC_COLORS.success }\n case 'disabled':\n return { icon: '\u25CB', color: SEMANTIC_COLORS.dim }\n case 'running':\n return { icon: '\u25D0', color: SEMANTIC_COLORS.running }\n case 'error':\n return { icon: '\u2717', color: SEMANTIC_COLORS.error }\n default:\n return null\n }\n}\n\n/** Default item renderer -
|
|
5
|
-
"mappings": "AAOA,OAAO,SAAS,eAAe;AAC/B,SAAS,KAAK,YAAY;AAC1B,SAAS,iBAAiB,sBAAsB;AAKhD,MAAM,gBAAgB,CAAC,WAAgC;AACrD,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,EAAE,MAAM,UAAK,OAAO,gBAAgB,QAAQ;AAAA,IACrD,KAAK;AACH,aAAO,EAAE,MAAM,UAAK,OAAO,gBAAgB,IAAI;AAAA,IACjD,KAAK;AACH,aAAO,EAAE,MAAM,UAAK,OAAO,gBAAgB,QAAQ;AAAA,IACrD,KAAK;AACH,aAAO,EAAE,MAAM,UAAK,OAAO,gBAAgB,MAAM;AAAA,IACnD;AACE,aAAO;AAAA,EACX;AACF;AAGA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AACF,
|
|
4
|
+
"sourcesContent": ["/**\n * ScrollableList Component\n *\n * A scrollable list matching the /command suggestion UI pattern.\n * Uses \u25C6 indicator for selection, brand colors, and scroll indicators.\n */\n\nimport React, { useMemo } from 'react'\nimport { Box, Text } from 'ink'\nimport { SEMANTIC_COLORS, BRAND_GRADIENT } from '@constants/colors'\nimport { t } from '@i18n'\nimport type { ListItem, ScrollableListProps, CategoryGroup } from './types'\n\n/** Status icon mapping */\nconst getStatusIcon = (status?: ListItem['status']) => {\n switch (status) {\n case 'enabled':\n return { icon: '\u2714', color: SEMANTIC_COLORS.success }\n case 'disabled':\n return { icon: '\u25CB', color: SEMANTIC_COLORS.dim }\n case 'running':\n return { icon: '\u25D0', color: SEMANTIC_COLORS.running }\n case 'error':\n return { icon: '\u2717', color: SEMANTIC_COLORS.error }\n default:\n return null\n }\n}\n\n/** Default item renderer - two-line layout with clear visual hierarchy */\nfunction DefaultItemRenderer({\n item,\n isFocused,\n multiSelect,\n isSelected,\n}: {\n item: ListItem\n isFocused: boolean\n multiSelect?: boolean\n isSelected?: boolean\n}) {\n const statusInfo = getStatusIcon(item.status)\n\n // Prefix width: checkbox \"\u2611 \" (2) + \"\u25C6 \" (2) + optional status icon \"\u2714 \" (2)\n const checkboxWidth = multiSelect ? 2 : 0\n const focusWidth = 2\n const statusWidth = statusInfo ? 2 : 0\n const prefixPad = checkboxWidth + focusWidth + statusWidth\n\n // Colors: focused items use brand gradient, unfocused use secondary/dim\n const labelColor = isFocused\n ? BRAND_GRADIENT.START\n : SEMANTIC_COLORS.secondary\n const descColor = SEMANTIC_COLORS.dim\n const metaColor = SEMANTIC_COLORS.muted\n\n const hasDescription = !!item.description\n\n return (\n <Box flexDirection=\"column\">\n {/* Line 1: [checkbox] + indicator + status + label + metadata */}\n <Box flexDirection=\"row\">\n {multiSelect && (\n <Text\n color={isSelected ? SEMANTIC_COLORS.success : SEMANTIC_COLORS.dim}\n >\n {isSelected ? '\u2611 ' : '\u2610 '}\n </Text>\n )}\n <Text color={isFocused ? BRAND_GRADIENT.START : undefined}>\n {isFocused ? '\u25C6 ' : ' '}\n </Text>\n {statusInfo && <Text color={statusInfo.color}>{statusInfo.icon} </Text>}\n <Text bold={isFocused} color={labelColor}>\n {item.label}\n </Text>\n {item.metadata && <Text color={metaColor}> \u00B7 {item.metadata}</Text>}\n </Box>\n\n {/* Line 2: description (indented to align under label) */}\n {hasDescription && (\n <Box>\n <Text color={descColor}>\n {' '.repeat(prefixPad)}\n {item.description}\n </Text>\n </Box>\n )}\n </Box>\n )\n}\n\nexport function ScrollableList({\n items,\n focusedIndex,\n onFocusChange,\n onSelect,\n visibleCount,\n groupByCategory = false,\n renderItem,\n multiSelect,\n selectedIds,\n}: ScrollableListProps) {\n // Calculate scroll window to keep selected item visible (same logic as command suggestions)\n const { visibleItems, startIndex, endIndex } = useMemo(() => {\n if (items.length === 0) {\n return { visibleItems: [], startIndex: 0, endIndex: 0 }\n }\n\n // If all items fit in window, show them all\n if (items.length <= visibleCount) {\n return {\n visibleItems: items.map((item, i) => ({ item, globalIndex: i })),\n startIndex: 0,\n endIndex: items.length,\n }\n }\n\n // Calculate scroll window to keep focused item visible\n const halfWindow = Math.floor(visibleCount / 2)\n let start = Math.max(0, focusedIndex - halfWindow)\n let end = start + visibleCount\n\n // Adjust if we're near the end\n if (end > items.length) {\n end = items.length\n start = Math.max(0, end - visibleCount)\n }\n\n return {\n visibleItems: items.slice(start, end).map((item, i) => ({\n item,\n globalIndex: start + i,\n })),\n startIndex: start,\n endIndex: end,\n }\n }, [items, focusedIndex, visibleCount])\n\n // Scroll indicators\n const hasMoreAbove = startIndex > 0\n const hasMoreBelow = endIndex < items.length\n const countAbove = startIndex\n const countBelow = items.length - endIndex\n\n // Build display with optional category headers\n const displayElements: React.ReactNode[] = []\n let currentCategory = ''\n\n visibleItems.forEach(({ item, globalIndex }, visibleIndex) => {\n // Add category header if category changed and grouping is enabled\n if (groupByCategory && item.category !== currentCategory) {\n currentCategory = item.category || 'Other'\n displayElements.push(\n <Box\n key={`category-${currentCategory}-${globalIndex}`}\n marginTop={visibleIndex > 0 ? 1 : 0}\n >\n <Text bold color={SEMANTIC_COLORS.secondary}>\n {currentCategory}\n </Text>\n </Box>,\n )\n }\n\n const isFocused = globalIndex === focusedIndex\n const isSelected = selectedIds?.has(item.id) ?? false\n const itemElement = renderItem ? (\n renderItem(item, isFocused, isSelected)\n ) : (\n <DefaultItemRenderer\n item={item}\n isFocused={isFocused}\n multiSelect={multiSelect}\n isSelected={isSelected}\n />\n )\n\n displayElements.push(<Box key={item.id}>{itemElement}</Box>)\n })\n\n return (\n <Box flexDirection=\"column\">\n {/* Scroll up indicator */}\n {hasMoreAbove && (\n <Box>\n <Text color={SEMANTIC_COLORS.dim}> \u2191 {countAbove} more above...</Text>\n </Box>\n )}\n\n {/* List items */}\n {displayElements}\n\n {/* Scroll down indicator */}\n {hasMoreBelow && (\n <Box>\n <Text color={SEMANTIC_COLORS.dim}> \u2193 {countBelow} more below...</Text>\n </Box>\n )}\n </Box>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,7 +5,10 @@ import { TabBar } from "./TabBar.js";
|
|
|
5
5
|
import { SearchInput } from "./SearchInput.js";
|
|
6
6
|
import { ScrollableList } from "./ScrollableList.js";
|
|
7
7
|
import { SimpleSpinner } from "../Spinner.js";
|
|
8
|
-
|
|
8
|
+
import { StatusOverlayContent } from "../StatusOverlayContent.js";
|
|
9
|
+
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
10
|
+
const PROMPT_HEIGHT = 5;
|
|
11
|
+
const CHROME_HEIGHT = 6;
|
|
9
12
|
function TabbedListView({
|
|
10
13
|
title,
|
|
11
14
|
tabs,
|
|
@@ -16,15 +19,31 @@ function TabbedListView({
|
|
|
16
19
|
searchPlaceholder = "Search...",
|
|
17
20
|
searchQuery = "",
|
|
18
21
|
onSearchChange,
|
|
22
|
+
onSearchSubmit,
|
|
19
23
|
onSelect,
|
|
20
24
|
onClose,
|
|
25
|
+
onBack,
|
|
21
26
|
footerHint,
|
|
22
27
|
isLoading = false,
|
|
23
28
|
loadingText = "Loading...",
|
|
24
29
|
emptyText = "No items found",
|
|
25
30
|
groupByCategory = false,
|
|
26
|
-
renderItem
|
|
31
|
+
renderItem,
|
|
32
|
+
statusOverlay,
|
|
33
|
+
isActive = true,
|
|
34
|
+
showItemCount = true,
|
|
35
|
+
statusDismissHint,
|
|
36
|
+
multiSelect,
|
|
37
|
+
selectedIds,
|
|
38
|
+
onSelectionChange,
|
|
39
|
+
onMultiSelect,
|
|
40
|
+
multiSelectActionLabel
|
|
27
41
|
}) {
|
|
42
|
+
const { rows } = useTerminalSize();
|
|
43
|
+
const visibleCount = Math.min(
|
|
44
|
+
8,
|
|
45
|
+
Math.max(3, rows - PROMPT_HEIGHT - CHROME_HEIGHT)
|
|
46
|
+
);
|
|
28
47
|
const [focusArea, setFocusArea] = useState(
|
|
29
48
|
searchEnabled ? "search" : "list"
|
|
30
49
|
);
|
|
@@ -60,57 +79,105 @@ function TabbedListView({
|
|
|
60
79
|
},
|
|
61
80
|
[onSelect]
|
|
62
81
|
);
|
|
63
|
-
useInput(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
cycleTab(key.shift ? "left" : "right");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (focusArea === "tabs" || focusArea === "search") {
|
|
73
|
-
if (key.leftArrow) {
|
|
74
|
-
cycleTab("left");
|
|
82
|
+
useInput(
|
|
83
|
+
(input, key) => {
|
|
84
|
+
if (statusOverlay && statusOverlay.type !== "loading") {
|
|
85
|
+
if (key.escape || key.return) {
|
|
86
|
+
onClose();
|
|
87
|
+
}
|
|
75
88
|
return;
|
|
76
89
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
90
|
+
if (statusOverlay) return;
|
|
91
|
+
if (key.escape) {
|
|
92
|
+
if (onBack) {
|
|
93
|
+
onBack();
|
|
94
|
+
} else {
|
|
95
|
+
onClose();
|
|
96
|
+
}
|
|
79
97
|
return;
|
|
80
98
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (focusArea === "search") {
|
|
84
|
-
setFocusArea("list");
|
|
99
|
+
if (key.tab) {
|
|
100
|
+
cycleTab(key.shift ? "left" : "right");
|
|
85
101
|
return;
|
|
86
102
|
}
|
|
87
|
-
if (focusArea === "
|
|
88
|
-
|
|
89
|
-
(
|
|
90
|
-
|
|
103
|
+
if (focusArea === "tabs" || focusArea === "search") {
|
|
104
|
+
if (key.leftArrow) {
|
|
105
|
+
cycleTab("left");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (key.rightArrow) {
|
|
109
|
+
cycleTab("right");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (key.downArrow) {
|
|
114
|
+
if (focusArea === "search") {
|
|
115
|
+
setFocusArea("list");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (focusArea === "list" && filteredItems.length > 0) {
|
|
119
|
+
setFocusedIndex(
|
|
120
|
+
(prev) => prev < filteredItems.length - 1 ? prev + 1 : prev
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (key.upArrow) {
|
|
126
|
+
if (focusArea === "list") {
|
|
127
|
+
if (focusedIndex > 0) {
|
|
128
|
+
setFocusedIndex((prev) => prev - 1);
|
|
129
|
+
} else if (searchEnabled) {
|
|
130
|
+
setFocusArea("search");
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (key.return && focusArea === "search" && onSearchSubmit) {
|
|
136
|
+
onSearchSubmit(searchQuery);
|
|
91
137
|
return;
|
|
92
138
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else if (searchEnabled) {
|
|
99
|
-
setFocusArea("search");
|
|
139
|
+
if (input === " " && multiSelect && focusArea === "list" && filteredItems.length > 0) {
|
|
140
|
+
const item = filteredItems[focusedIndex];
|
|
141
|
+
if (item && onSelectionChange) {
|
|
142
|
+
const isCurrentlySelected = selectedIds?.has(item.id) ?? false;
|
|
143
|
+
onSelectionChange(item.id, !isCurrentlySelected);
|
|
100
144
|
}
|
|
101
145
|
return;
|
|
102
146
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
147
|
+
if (input === "a" && multiSelect && focusArea === "list" && filteredItems.length > 0) {
|
|
148
|
+
if (onSelectionChange) {
|
|
149
|
+
const allSelected = filteredItems.every(
|
|
150
|
+
(item) => selectedIds?.has(item.id)
|
|
151
|
+
);
|
|
152
|
+
for (const item of filteredItems) {
|
|
153
|
+
onSelectionChange(item.id, !allSelected);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (key.return && focusArea === "list" && filteredItems.length > 0) {
|
|
159
|
+
if (multiSelect && selectedIds && selectedIds.size > 0 && onMultiSelect) {
|
|
160
|
+
const selectedItems = filteredItems.filter(
|
|
161
|
+
(item) => selectedIds.has(item.id)
|
|
162
|
+
);
|
|
163
|
+
if (selectedItems.length > 0) {
|
|
164
|
+
onMultiSelect(selectedItems);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
handleSelect(filteredItems[focusedIndex]);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (input === "/" && searchEnabled && focusArea !== "search") {
|
|
172
|
+
setFocusArea("search");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{ isActive }
|
|
177
|
+
);
|
|
113
178
|
const defaultHint = "\u2191\u2193 Navigate \xB7 Enter Select \xB7 Tab/\u2190\u2192 Switch \xB7 Esc Close";
|
|
179
|
+
const multiSelectHint = "\u2191\u2193 Navigate \xB7 Space Select \xB7 a All \xB7 Enter Action \xB7 Tab Switch \xB7 Esc Close";
|
|
180
|
+
const displayHint = statusOverlay ? statusOverlay.type === "loading" ? "" : statusDismissHint || "Esc Close" : multiSelect ? selectedIds && selectedIds.size > 0 && multiSelectActionLabel ? `${multiSelectActionLabel} \xB7 ${footerHint || multiSelectHint}` : footerHint || multiSelectHint : footerHint || defaultHint;
|
|
114
181
|
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", width: "100%" }, /* @__PURE__ */ React.createElement(
|
|
115
182
|
Box,
|
|
116
183
|
{
|
|
@@ -124,7 +191,7 @@ function TabbedListView({
|
|
|
124
191
|
paddingX: 1,
|
|
125
192
|
flexDirection: "column"
|
|
126
193
|
},
|
|
127
|
-
/* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: BRAND_GRADIENT.START }, "\u25C6"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: SEMANTIC_COLORS.secondary }, " ", title), filteredItems.length > 0 && /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " (", filteredItems.length, ")")),
|
|
194
|
+
/* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: BRAND_GRADIENT.START }, "\u25C6"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: SEMANTIC_COLORS.secondary }, " ", title), showItemCount && !statusOverlay && filteredItems.length > 0 && /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " (", filteredItems.length, ")")),
|
|
128
195
|
/* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(TabBar, { tabs, activeTab, onTabChange }))
|
|
129
196
|
), /* @__PURE__ */ React.createElement(
|
|
130
197
|
Box,
|
|
@@ -140,7 +207,7 @@ function TabbedListView({
|
|
|
140
207
|
paddingX: 1,
|
|
141
208
|
paddingY: 1
|
|
142
209
|
},
|
|
143
|
-
searchEnabled && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(
|
|
210
|
+
searchEnabled && !statusOverlay && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(
|
|
144
211
|
SearchInput,
|
|
145
212
|
{
|
|
146
213
|
value: searchQuery,
|
|
@@ -150,19 +217,27 @@ function TabbedListView({
|
|
|
150
217
|
isActive: focusArea === "search"
|
|
151
218
|
}
|
|
152
219
|
)),
|
|
153
|
-
/* @__PURE__ */ React.createElement(Box, { key: `content-${activeTab}`, flexDirection: "column" },
|
|
220
|
+
/* @__PURE__ */ React.createElement(Box, { key: `content-${activeTab}`, flexDirection: "column" }, statusOverlay ? /* @__PURE__ */ React.createElement(
|
|
221
|
+
StatusOverlayContent,
|
|
222
|
+
{
|
|
223
|
+
type: statusOverlay.type,
|
|
224
|
+
message: statusOverlay.message
|
|
225
|
+
}
|
|
226
|
+
) : isLoading ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(SimpleSpinner, null), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", loadingText)) : filteredItems.length === 0 ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, emptyText)) : /* @__PURE__ */ React.createElement(
|
|
154
227
|
ScrollableList,
|
|
155
228
|
{
|
|
156
229
|
items: filteredItems,
|
|
157
230
|
focusedIndex,
|
|
158
231
|
onFocusChange: setFocusedIndex,
|
|
159
232
|
onSelect: handleSelect,
|
|
160
|
-
visibleCount
|
|
233
|
+
visibleCount,
|
|
161
234
|
groupByCategory,
|
|
162
|
-
renderItem
|
|
235
|
+
renderItem,
|
|
236
|
+
multiSelect,
|
|
237
|
+
selectedIds
|
|
163
238
|
}
|
|
164
239
|
)),
|
|
165
|
-
/* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.muted },
|
|
240
|
+
displayHint && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.muted }, displayHint))
|
|
166
241
|
));
|
|
167
242
|
}
|
|
168
243
|
export {
|