@juanibiapina/pi-extension-settings 0.4.0 → 0.5.1
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 +29 -6
- package/dist/components/ordered-multi-select.d.ts +26 -0
- package/dist/components/ordered-multi-select.d.ts.map +1 -0
- package/dist/components/ordered-multi-select.js +141 -0
- package/dist/components/ordered-multi-select.js.map +1 -0
- package/dist/components/settings-list.d.ts +2 -0
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +3 -0
- package/dist/components/settings-list.js.map +1 -1
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +24 -8
- package/dist/extension.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/settings/types.d.ts +14 -0
- package/dist/settings/types.d.ts.map +1 -1
- package/dist/settings/types.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A [pi](https://github.com/badlogic/pi-mono) extension that provides centralized
|
|
|
6
6
|
|
|
7
7
|
- **`/extension-settings` command** - Interactive UI to configure all registered extension settings
|
|
8
8
|
- **Helpers for reading/writing** - `getSetting()` and `setSetting()` functions
|
|
9
|
+
- **Ordered multi-select** - Settings where users pick and reorder items from a list
|
|
9
10
|
- **Persistent storage** - Settings stored in `~/.pi/agent/settings-extensions.json`
|
|
10
11
|
|
|
11
12
|
## For Users
|
|
@@ -16,6 +17,8 @@ Install the extension to get the `/extension-settings` command, which provides a
|
|
|
16
17
|
pi install npm:@juanibiapina/pi-extension-settings
|
|
17
18
|
```
|
|
18
19
|
|
|
20
|
+
> **⚠️ Load Order:** `pi-extension-settings` must appear **before** any extension that registers settings in your `packages` array in `~/.pi/settings.json`. Extensions register via the event bus at load time, so if `pi-extension-settings` hasn't loaded yet, those registrations are silently lost.
|
|
21
|
+
|
|
19
22
|
Then use `/extension-settings` in pi:
|
|
20
23
|
|
|
21
24
|
- Settings are grouped by extension with headers
|
|
@@ -31,7 +34,7 @@ If you're developing an extension and want to use the settings system, add this
|
|
|
31
34
|
```json
|
|
32
35
|
{
|
|
33
36
|
"dependencies": {
|
|
34
|
-
"@juanibiapina/pi-extension-settings": "^0.
|
|
37
|
+
"@juanibiapina/pi-extension-settings": "^0.5.0"
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
```
|
|
@@ -71,6 +74,18 @@ export default function myExtension(pi: ExtensionAPI) {
|
|
|
71
74
|
defaultValue: "",
|
|
72
75
|
// No 'values' = free-form string input
|
|
73
76
|
},
|
|
77
|
+
{
|
|
78
|
+
id: "enabledModels",
|
|
79
|
+
label: "Enabled Models",
|
|
80
|
+
description: "Pick and reorder your preferred models",
|
|
81
|
+
defaultValue: "",
|
|
82
|
+
// Ordered multi-select: opens a submenu to toggle and reorder items
|
|
83
|
+
options: [
|
|
84
|
+
{ id: "model-a", label: "Model A" },
|
|
85
|
+
{ id: "model-b", label: "Model B" },
|
|
86
|
+
{ id: "model-c", label: "Model C" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
74
89
|
] satisfies SettingDefinition[]
|
|
75
90
|
});
|
|
76
91
|
}
|
|
@@ -114,14 +129,22 @@ Type for settings registration (use with `satisfies` for type checking):
|
|
|
114
129
|
|
|
115
130
|
```typescript
|
|
116
131
|
interface SettingDefinition {
|
|
117
|
-
id: string;
|
|
118
|
-
label: string;
|
|
119
|
-
description?: string;
|
|
120
|
-
defaultValue: string;
|
|
121
|
-
values?: string[];
|
|
132
|
+
id: string; // Unique ID within the extension
|
|
133
|
+
label: string; // Display label in UI
|
|
134
|
+
description?: string; // Optional help text shown when selected
|
|
135
|
+
defaultValue: string; // Default value if not set
|
|
136
|
+
values?: string[]; // Values to cycle through (omit for free-form string input)
|
|
137
|
+
options?: OrderedListOption[]; // Ordered multi-select options (mutually exclusive with values)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface OrderedListOption {
|
|
141
|
+
id: string; // Value stored in the comma-separated setting
|
|
142
|
+
label: string; // Display label in the menu
|
|
122
143
|
}
|
|
123
144
|
```
|
|
124
145
|
|
|
146
|
+
When `options` is set, Enter opens a submenu where items can be toggled (Space), reordered (Shift+↑/↓), confirmed (Enter), or cancelled (Esc). The value is stored as comma-separated IDs.
|
|
147
|
+
|
|
125
148
|
### Event: `pi-extension-settings:register`
|
|
126
149
|
|
|
127
150
|
Emit this event to register settings for the UI:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered multi-select component.
|
|
3
|
+
*
|
|
4
|
+
* Displays a list of options that can be toggled on/off and reordered.
|
|
5
|
+
* Selected items appear at the top with their position number.
|
|
6
|
+
* Value is stored as a comma-separated string of selected IDs in order.
|
|
7
|
+
*/
|
|
8
|
+
import { type Component } from "@mariozechner/pi-tui";
|
|
9
|
+
import type { OrderedListOption } from "../settings/types.js";
|
|
10
|
+
import type { SettingsListTheme } from "./settings-list.js";
|
|
11
|
+
export declare class OrderedMultiSelect implements Component {
|
|
12
|
+
private options;
|
|
13
|
+
private selected;
|
|
14
|
+
private cursorIndex;
|
|
15
|
+
private theme;
|
|
16
|
+
private done;
|
|
17
|
+
constructor(options: OrderedListOption[], currentValue: string, theme: SettingsListTheme, done: (selectedValue?: string) => void);
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
render(width: number): string[];
|
|
20
|
+
handleInput(data: string): void;
|
|
21
|
+
private buildDisplayItems;
|
|
22
|
+
private toggleCurrent;
|
|
23
|
+
private moveUp;
|
|
24
|
+
private moveDown;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=ordered-multi-select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ordered-multi-select.d.ts","sourceRoot":"","sources":["../../src/components/ordered-multi-select.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAqD,MAAM,sBAAsB,CAAC;AACzG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAS5D,qBAAa,kBAAmB,YAAW,SAAS;IACnD,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,IAAI,CAAmC;gBAG9C,OAAO,EAAE,iBAAiB,EAAE,EAC5B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,KAAK,IAAI;IAWvC,UAAU,IAAI,IAAI;IAElB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAkC/B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IA2B/B,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,QAAQ;CAYhB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered multi-select component.
|
|
3
|
+
*
|
|
4
|
+
* Displays a list of options that can be toggled on/off and reordered.
|
|
5
|
+
* Selected items appear at the top with their position number.
|
|
6
|
+
* Value is stored as a comma-separated string of selected IDs in order.
|
|
7
|
+
*/
|
|
8
|
+
import { getEditorKeybindings, matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
9
|
+
export class OrderedMultiSelect {
|
|
10
|
+
options;
|
|
11
|
+
selected;
|
|
12
|
+
cursorIndex = 0;
|
|
13
|
+
theme;
|
|
14
|
+
done;
|
|
15
|
+
constructor(options, currentValue, theme, done) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.selected = currentValue
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((s) => s.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
this.theme = theme;
|
|
22
|
+
this.done = done;
|
|
23
|
+
}
|
|
24
|
+
invalidate() { }
|
|
25
|
+
render(width) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
const items = this.buildDisplayItems();
|
|
28
|
+
for (let i = 0; i < items.length; i++) {
|
|
29
|
+
const item = items[i];
|
|
30
|
+
const isCursor = i === this.cursorIndex;
|
|
31
|
+
const prefix = isCursor ? this.theme.cursor : " ";
|
|
32
|
+
let marker;
|
|
33
|
+
if (item.selected) {
|
|
34
|
+
marker = this.theme.value(`✓ ${String(item.position).padStart(2)}`, isCursor);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
marker = this.theme.label(" ", isCursor);
|
|
38
|
+
}
|
|
39
|
+
const label = this.theme.label(item.option.label, isCursor);
|
|
40
|
+
const line = `${prefix}${marker} ${label}`;
|
|
41
|
+
lines.push(truncateToWidth(line, width, "…"));
|
|
42
|
+
}
|
|
43
|
+
// Scroll indicator
|
|
44
|
+
if (items.length > 0) {
|
|
45
|
+
const scrollText = ` (${this.cursorIndex + 1}/${items.length})`;
|
|
46
|
+
lines.push(this.theme.hint(scrollText));
|
|
47
|
+
}
|
|
48
|
+
// Hint line
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push(this.theme.hint(" Space toggle · Shift+↑/↓ reorder · Enter confirm · Esc cancel"));
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
53
|
+
handleInput(data) {
|
|
54
|
+
const kb = getEditorKeybindings();
|
|
55
|
+
const items = this.buildDisplayItems();
|
|
56
|
+
if (items.length === 0) {
|
|
57
|
+
if (kb.matches(data, "selectCancel")) {
|
|
58
|
+
this.done(undefined);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (kb.matches(data, "selectUp") || matchesKey(data, "up")) {
|
|
63
|
+
this.cursorIndex = this.cursorIndex === 0 ? items.length - 1 : this.cursorIndex - 1;
|
|
64
|
+
}
|
|
65
|
+
else if (kb.matches(data, "selectDown") || matchesKey(data, "down")) {
|
|
66
|
+
this.cursorIndex = this.cursorIndex === items.length - 1 ? 0 : this.cursorIndex + 1;
|
|
67
|
+
}
|
|
68
|
+
else if (data === " " || matchesKey(data, "space")) {
|
|
69
|
+
this.toggleCurrent(items);
|
|
70
|
+
}
|
|
71
|
+
else if (matchesKey(data, "shift+up")) {
|
|
72
|
+
this.moveUp(items);
|
|
73
|
+
}
|
|
74
|
+
else if (matchesKey(data, "shift+down")) {
|
|
75
|
+
this.moveDown(items);
|
|
76
|
+
}
|
|
77
|
+
else if (kb.matches(data, "selectConfirm")) {
|
|
78
|
+
this.done(this.selected.join(","));
|
|
79
|
+
}
|
|
80
|
+
else if (kb.matches(data, "selectCancel")) {
|
|
81
|
+
this.done(undefined);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
buildDisplayItems() {
|
|
85
|
+
const items = [];
|
|
86
|
+
// Selected items first, in order
|
|
87
|
+
for (let i = 0; i < this.selected.length; i++) {
|
|
88
|
+
const id = this.selected[i];
|
|
89
|
+
const option = this.options.find((o) => o.id === id);
|
|
90
|
+
if (option) {
|
|
91
|
+
items.push({ option, selected: true, position: i + 1 });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Unselected items, in original options order
|
|
95
|
+
for (const option of this.options) {
|
|
96
|
+
if (!this.selected.includes(option.id)) {
|
|
97
|
+
items.push({ option, selected: false, position: 0 });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return items;
|
|
101
|
+
}
|
|
102
|
+
toggleCurrent(items) {
|
|
103
|
+
const item = items[this.cursorIndex];
|
|
104
|
+
if (!item)
|
|
105
|
+
return;
|
|
106
|
+
const id = item.option.id;
|
|
107
|
+
if (item.selected) {
|
|
108
|
+
// Remove from selected
|
|
109
|
+
this.selected = this.selected.filter((s) => s !== id);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Add to end of selected
|
|
113
|
+
this.selected.push(id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
moveUp(items) {
|
|
117
|
+
const item = items[this.cursorIndex];
|
|
118
|
+
if (!item?.selected)
|
|
119
|
+
return;
|
|
120
|
+
const idx = this.selected.indexOf(item.option.id);
|
|
121
|
+
if (idx <= 0)
|
|
122
|
+
return;
|
|
123
|
+
// Swap with previous in selected array
|
|
124
|
+
[this.selected[idx - 1], this.selected[idx]] = [this.selected[idx], this.selected[idx - 1]];
|
|
125
|
+
// Move cursor up to follow the item
|
|
126
|
+
this.cursorIndex--;
|
|
127
|
+
}
|
|
128
|
+
moveDown(items) {
|
|
129
|
+
const item = items[this.cursorIndex];
|
|
130
|
+
if (!item?.selected)
|
|
131
|
+
return;
|
|
132
|
+
const idx = this.selected.indexOf(item.option.id);
|
|
133
|
+
if (idx < 0 || idx >= this.selected.length - 1)
|
|
134
|
+
return;
|
|
135
|
+
// Swap with next in selected array
|
|
136
|
+
[this.selected[idx], this.selected[idx + 1]] = [this.selected[idx + 1], this.selected[idx]];
|
|
137
|
+
// Move cursor down to follow the item
|
|
138
|
+
this.cursorIndex++;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=ordered-multi-select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ordered-multi-select.js","sourceRoot":"","sources":["../../src/components/ordered-multi-select.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ordered multi-select component.\n *\n * Displays a list of options that can be toggled on/off and reordered.\n * Selected items appear at the top with their position number.\n * Value is stored as a comma-separated string of selected IDs in order.\n */\n\nimport { type Component, getEditorKeybindings, matchesKey, truncateToWidth } from \"@mariozechner/pi-tui\";\nimport type { OrderedListOption } from \"../settings/types.js\";\nimport type { SettingsListTheme } from \"./settings-list.js\";\n\ninterface DisplayItem {\n\toption: OrderedListOption;\n\tselected: boolean;\n\t/** 1-based position within selected items, or 0 if not selected */\n\tposition: number;\n}\n\nexport class OrderedMultiSelect implements Component {\n\tprivate options: OrderedListOption[];\n\tprivate selected: string[];\n\tprivate cursorIndex = 0;\n\tprivate theme: SettingsListTheme;\n\tprivate done: (selectedValue?: string) => void;\n\n\tconstructor(\n\t\toptions: OrderedListOption[],\n\t\tcurrentValue: string,\n\t\ttheme: SettingsListTheme,\n\t\tdone: (selectedValue?: string) => void,\n\t) {\n\t\tthis.options = options;\n\t\tthis.selected = currentValue\n\t\t\t.split(\",\")\n\t\t\t.map((s) => s.trim())\n\t\t\t.filter(Boolean);\n\t\tthis.theme = theme;\n\t\tthis.done = done;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst items = this.buildDisplayItems();\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst isCursor = i === this.cursorIndex;\n\t\t\tconst prefix = isCursor ? this.theme.cursor : \" \";\n\n\t\t\tlet marker: string;\n\t\t\tif (item.selected) {\n\t\t\t\tmarker = this.theme.value(`✓ ${String(item.position).padStart(2)}`, isCursor);\n\t\t\t} else {\n\t\t\t\tmarker = this.theme.label(\" \", isCursor);\n\t\t\t}\n\n\t\t\tconst label = this.theme.label(item.option.label, isCursor);\n\t\t\tconst line = `${prefix}${marker} ${label}`;\n\t\t\tlines.push(truncateToWidth(line, width, \"…\"));\n\t\t}\n\n\t\t// Scroll indicator\n\t\tif (items.length > 0) {\n\t\t\tconst scrollText = ` (${this.cursorIndex + 1}/${items.length})`;\n\t\t\tlines.push(this.theme.hint(scrollText));\n\t\t}\n\n\t\t// Hint line\n\t\tlines.push(\"\");\n\t\tlines.push(this.theme.hint(\" Space toggle · Shift+↑/↓ reorder · Enter confirm · Esc cancel\"));\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tconst items = this.buildDisplayItems();\n\t\tif (items.length === 0) {\n\t\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\t\tthis.done(undefined);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"selectUp\") || matchesKey(data, \"up\")) {\n\t\t\tthis.cursorIndex = this.cursorIndex === 0 ? items.length - 1 : this.cursorIndex - 1;\n\t\t} else if (kb.matches(data, \"selectDown\") || matchesKey(data, \"down\")) {\n\t\t\tthis.cursorIndex = this.cursorIndex === items.length - 1 ? 0 : this.cursorIndex + 1;\n\t\t} else if (data === \" \" || matchesKey(data, \"space\")) {\n\t\t\tthis.toggleCurrent(items);\n\t\t} else if (matchesKey(data, \"shift+up\")) {\n\t\t\tthis.moveUp(items);\n\t\t} else if (matchesKey(data, \"shift+down\")) {\n\t\t\tthis.moveDown(items);\n\t\t} else if (kb.matches(data, \"selectConfirm\")) {\n\t\t\tthis.done(this.selected.join(\",\"));\n\t\t} else if (kb.matches(data, \"selectCancel\")) {\n\t\t\tthis.done(undefined);\n\t\t}\n\t}\n\n\tprivate buildDisplayItems(): DisplayItem[] {\n\t\tconst items: DisplayItem[] = [];\n\n\t\t// Selected items first, in order\n\t\tfor (let i = 0; i < this.selected.length; i++) {\n\t\t\tconst id = this.selected[i];\n\t\t\tconst option = this.options.find((o) => o.id === id);\n\t\t\tif (option) {\n\t\t\t\titems.push({ option, selected: true, position: i + 1 });\n\t\t\t}\n\t\t}\n\n\t\t// Unselected items, in original options order\n\t\tfor (const option of this.options) {\n\t\t\tif (!this.selected.includes(option.id)) {\n\t\t\t\titems.push({ option, selected: false, position: 0 });\n\t\t\t}\n\t\t}\n\n\t\treturn items;\n\t}\n\n\tprivate toggleCurrent(items: DisplayItem[]): void {\n\t\tconst item = items[this.cursorIndex];\n\t\tif (!item) return;\n\n\t\tconst id = item.option.id;\n\t\tif (item.selected) {\n\t\t\t// Remove from selected\n\t\t\tthis.selected = this.selected.filter((s) => s !== id);\n\t\t} else {\n\t\t\t// Add to end of selected\n\t\t\tthis.selected.push(id);\n\t\t}\n\t}\n\n\tprivate moveUp(items: DisplayItem[]): void {\n\t\tconst item = items[this.cursorIndex];\n\t\tif (!item?.selected) return;\n\n\t\tconst idx = this.selected.indexOf(item.option.id);\n\t\tif (idx <= 0) return;\n\n\t\t// Swap with previous in selected array\n\t\t[this.selected[idx - 1], this.selected[idx]] = [this.selected[idx], this.selected[idx - 1]];\n\t\t// Move cursor up to follow the item\n\t\tthis.cursorIndex--;\n\t}\n\n\tprivate moveDown(items: DisplayItem[]): void {\n\t\tconst item = items[this.cursorIndex];\n\t\tif (!item?.selected) return;\n\n\t\tconst idx = this.selected.indexOf(item.option.id);\n\t\tif (idx < 0 || idx >= this.selected.length - 1) return;\n\n\t\t// Swap with next in selected array\n\t\t[this.selected[idx], this.selected[idx + 1]] = [this.selected[idx + 1], this.selected[idx]];\n\t\t// Move cursor down to follow the item\n\t\tthis.cursorIndex++;\n\t}\n}\n"]}
|
|
@@ -19,6 +19,8 @@ export interface SettingItem {
|
|
|
19
19
|
values?: string[];
|
|
20
20
|
/** If provided, Enter opens this submenu. Receives current value and done callback. */
|
|
21
21
|
submenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;
|
|
22
|
+
/** If false, item is selectable but cannot be edited or changed. */
|
|
23
|
+
editable?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface SettingsListTheme {
|
|
24
26
|
label: (text: string, selected: boolean) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-list.d.ts","sourceRoot":"","sources":["../../src/components/settings-list.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,KAAK,SAAS,EAQd,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,WAAW;IAC3B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uFAAuF;IACvF,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,KAAK,IAAI,KAAK,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"settings-list.d.ts","sourceRoot":"","sources":["../../src/components/settings-list.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,KAAK,SAAS,EAQd,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,WAAW;IAC3B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uFAAuF;IACvF,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,KAAK,IAAI,KAAK,SAAS,CAAC;IACtF,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;IACnD,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,qBAAa,YAAa,YAAW,SAAS;IAC7C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAyC;IACzD,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAU;IAG/B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,gBAAgB,CAAuB;IAG/C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,gBAAgB,CAAuB;gBAG9C,KAAK,EAAE,WAAW,EAAE,EACpB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,EAChD,QAAQ,EAAE,MAAM,IAAI,EACpB,OAAO,GAAE,mBAAwB;IAclC,oCAAoC;IACpC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO/C,UAAU,IAAI,IAAI;IAIlB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAS/B,OAAO,CAAC,cAAc;IAqFtB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAqC/B,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;CAUnB"}
|
|
@@ -194,6 +194,9 @@ export class SettingsList {
|
|
|
194
194
|
const item = displayItems[this.selectedIndex];
|
|
195
195
|
if (!item)
|
|
196
196
|
return;
|
|
197
|
+
if (item.editable === false) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
197
200
|
if (item.submenu) {
|
|
198
201
|
// Open submenu, passing current value so it can pre-select correctly
|
|
199
202
|
this.submenuItemIndex = this.selectedIndex;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-list.js","sourceRoot":"","sources":["../../src/components/settings-list.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Settings list component with support for cycling values and string input.\n * Based on @mariozechner/pi-tui SettingsList, extended with string editing.\n */\n\nimport {\n\ttype Component,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tmatchesKey,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"@mariozechner/pi-tui\";\n\nexport interface SettingItem {\n\t/** Unique identifier for this setting */\n\tid: string;\n\t/** Display label (left side) */\n\tlabel: string;\n\t/** Optional description shown when selected */\n\tdescription?: string;\n\t/** Current value to display (right side) */\n\tcurrentValue: string;\n\t/**\n\t * If provided, Enter/Space cycles through these values.\n\t * If undefined/empty, the setting is treated as a string input.\n\t */\n\tvalues?: string[];\n\t/** If provided, Enter opens this submenu. Receives current value and done callback. */\n\tsubmenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;\n}\n\nexport interface SettingsListTheme {\n\tlabel: (text: string, selected: boolean) => string;\n\tvalue: (text: string, selected: boolean) => string;\n\tdescription: (text: string) => string;\n\tcursor: string;\n\thint: (text: string) => string;\n}\n\nexport interface SettingsListOptions {\n\tenableSearch?: boolean;\n}\n\nexport class SettingsList implements Component {\n\tprivate items: SettingItem[];\n\tprivate filteredItems: SettingItem[];\n\tprivate theme: SettingsListTheme;\n\tprivate selectedIndex = 0;\n\tprivate maxVisible: number;\n\tprivate onChange: (id: string, newValue: string) => void;\n\tprivate onCancel: () => void;\n\tprivate searchInput?: Input;\n\tprivate searchEnabled: boolean;\n\n\t// Submenu state\n\tprivate submenuComponent: Component | null = null;\n\tprivate submenuItemIndex: number | null = null;\n\n\t// String editing state\n\tprivate editingInput: Input | null = null;\n\tprivate editingItemIndex: number | null = null;\n\n\tconstructor(\n\t\titems: SettingItem[],\n\t\tmaxVisible: number,\n\t\ttheme: SettingsListTheme,\n\t\tonChange: (id: string, newValue: string) => void,\n\t\tonCancel: () => void,\n\t\toptions: SettingsListOptions = {},\n\t) {\n\t\tthis.items = items;\n\t\tthis.filteredItems = items;\n\t\tthis.maxVisible = maxVisible;\n\t\tthis.theme = theme;\n\t\tthis.onChange = onChange;\n\t\tthis.onCancel = onCancel;\n\t\tthis.searchEnabled = options.enableSearch ?? false;\n\t\tif (this.searchEnabled) {\n\t\t\tthis.searchInput = new Input();\n\t\t}\n\t}\n\n\t/** Update an item's currentValue */\n\tupdateValue(id: string, newValue: string): void {\n\t\tconst item = this.items.find((i) => i.id === id);\n\t\tif (item) {\n\t\t\titem.currentValue = newValue;\n\t\t}\n\t}\n\n\tinvalidate(): void {\n\t\tthis.submenuComponent?.invalidate?.();\n\t}\n\n\trender(width: number): string[] {\n\t\t// If submenu is active, render it instead\n\t\tif (this.submenuComponent) {\n\t\t\treturn this.submenuComponent.render(width);\n\t\t}\n\n\t\treturn this.renderMainList(width);\n\t}\n\n\tprivate renderMainList(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tif (this.searchEnabled && this.searchInput && !this.editingInput) {\n\t\t\tlines.push(...this.searchInput.render(width));\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tif (this.items.length === 0) {\n\t\t\tlines.push(this.theme.hint(\" No settings available\"));\n\t\t\tif (this.searchEnabled) {\n\t\t\t\tthis.addHintLine(lines);\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tif (displayItems.length === 0) {\n\t\t\tlines.push(this.theme.hint(\" No matching settings\"));\n\t\t\tthis.addHintLine(lines);\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), displayItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, displayItems.length);\n\n\t\t// Calculate max label width for alignment\n\t\tconst maxLabelWidth = Math.min(30, Math.max(...this.items.map((item) => visibleWidth(item.label))));\n\n\t\t// Render visible items\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = displayItems[i];\n\t\t\tif (!item) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst isEditing = this.editingInput && this.editingItemIndex === i;\n\t\t\tconst prefix = isSelected ? this.theme.cursor : \" \";\n\t\t\tconst prefixWidth = visibleWidth(prefix);\n\n\t\t\t// Pad label to align values\n\t\t\tconst labelPadded = item.label + \" \".repeat(Math.max(0, maxLabelWidth - visibleWidth(item.label)));\n\t\t\tconst labelText = this.theme.label(labelPadded, isSelected);\n\n\t\t\t// Calculate space for value\n\t\t\tconst separator = \" \";\n\t\t\tconst usedWidth = prefixWidth + maxLabelWidth + visibleWidth(separator);\n\t\t\tconst valueMaxWidth = width - usedWidth - 2;\n\n\t\t\tif (isEditing && this.editingInput) {\n\t\t\t\t// Render inline input for string editing\n\t\t\t\tconst inputLines = this.editingInput.render(valueMaxWidth);\n\t\t\t\tconst inputLine = inputLines[0] ?? \"\";\n\t\t\t\tlines.push(prefix + labelText + separator + inputLine);\n\t\t\t} else {\n\t\t\t\tconst valueText = this.theme.value(truncateToWidth(item.currentValue, valueMaxWidth, \"\"), isSelected);\n\t\t\t\tlines.push(prefix + labelText + separator + valueText);\n\t\t\t}\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < displayItems.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${displayItems.length})`;\n\t\t\tlines.push(this.theme.hint(truncateToWidth(scrollText, width - 2, \"\")));\n\t\t}\n\n\t\t// Add description for selected item\n\t\tconst selectedItem = displayItems[this.selectedIndex];\n\t\tif (selectedItem?.description && !this.editingInput) {\n\t\t\tlines.push(\"\");\n\t\t\tconst wrappedDesc = wrapTextWithAnsi(selectedItem.description, width - 4);\n\t\t\tfor (const line of wrappedDesc) {\n\t\t\t\tlines.push(this.theme.description(` ${line}`));\n\t\t\t}\n\t\t}\n\n\t\t// Add hint\n\t\tthis.addHintLine(lines);\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\t// If editing a string value, handle input for the editor\n\t\tif (this.editingInput) {\n\t\t\tthis.handleEditingInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// If submenu is active, delegate all input to it\n\t\tif (this.submenuComponent) {\n\t\t\tthis.submenuComponent.handleInput?.(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Main list input handling\n\t\tconst kb = getEditorKeybindings();\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\n\t\tif (kb.matches(data, \"selectUp\")) {\n\t\t\tif (displayItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? displayItems.length - 1 : this.selectedIndex - 1;\n\t\t} else if (kb.matches(data, \"selectDown\")) {\n\t\t\tif (displayItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === displayItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t} else if (kb.matches(data, \"selectConfirm\") || data === \" \") {\n\t\t\tthis.activateItem();\n\t\t} else if (kb.matches(data, \"selectCancel\")) {\n\t\t\tthis.onCancel();\n\t\t} else if (this.searchEnabled && this.searchInput) {\n\t\t\tconst sanitized = data.replace(/ /g, \"\");\n\t\t\tif (!sanitized) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.searchInput.handleInput(sanitized);\n\t\t\tthis.applyFilter(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate handleEditingInput(data: string): void {\n\t\tif (!this.editingInput) return;\n\n\t\t// Enter: confirm edit\n\t\tif (matchesKey(data, \"enter\")) {\n\t\t\tthis.confirmEdit();\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape: cancel edit\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.cancelEdit();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass other input to the editor\n\t\tthis.editingInput.handleInput(data);\n\t}\n\n\tprivate confirmEdit(): void {\n\t\tif (!this.editingInput || this.editingItemIndex === null) return;\n\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tconst item = displayItems[this.editingItemIndex];\n\t\tif (item) {\n\t\t\tconst newValue = this.editingInput.getValue();\n\t\t\titem.currentValue = newValue;\n\t\t\tthis.onChange(item.id, newValue);\n\t\t}\n\n\t\tthis.editingInput = null;\n\t\tthis.editingItemIndex = null;\n\t}\n\n\tprivate cancelEdit(): void {\n\t\tthis.editingInput = null;\n\t\tthis.editingItemIndex = null;\n\t}\n\n\tprivate activateItem(): void {\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tconst item = displayItems[this.selectedIndex];\n\t\tif (!item) return;\n\n\t\tif (item.submenu) {\n\t\t\t// Open submenu, passing current value so it can pre-select correctly\n\t\t\tthis.submenuItemIndex = this.selectedIndex;\n\t\t\tthis.submenuComponent = item.submenu(item.currentValue, (selectedValue?: string) => {\n\t\t\t\tif (selectedValue !== undefined) {\n\t\t\t\t\titem.currentValue = selectedValue;\n\t\t\t\t\tthis.onChange(item.id, selectedValue);\n\t\t\t\t}\n\t\t\t\tthis.closeSubmenu();\n\t\t\t});\n\t\t} else if (item.values && item.values.length > 0) {\n\t\t\t// Cycle through values\n\t\t\tconst currentIndex = item.values.indexOf(item.currentValue);\n\t\t\tconst nextIndex = (currentIndex + 1) % item.values.length;\n\t\t\tconst newValue = item.values[nextIndex];\n\t\t\titem.currentValue = newValue;\n\t\t\tthis.onChange(item.id, newValue);\n\t\t} else {\n\t\t\t// String input - start editing\n\t\t\tthis.editingItemIndex = this.selectedIndex;\n\t\t\tthis.editingInput = new Input();\n\t\t\tthis.editingInput.setValue(item.currentValue);\n\t\t}\n\t}\n\n\tprivate closeSubmenu(): void {\n\t\tthis.submenuComponent = null;\n\t\t// Restore selection to the item that opened the submenu\n\t\tif (this.submenuItemIndex !== null) {\n\t\t\tthis.selectedIndex = this.submenuItemIndex;\n\t\t\tthis.submenuItemIndex = null;\n\t\t}\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tthis.filteredItems = fuzzyFilter(this.items, query, (item) => item.label);\n\t\tthis.selectedIndex = 0;\n\t}\n\n\tprivate addHintLine(lines: string[]): void {\n\t\tlines.push(\"\");\n\t\tif (this.editingInput) {\n\t\t\tlines.push(this.theme.hint(\" Enter to confirm · Esc to cancel\"));\n\t\t} else if (this.searchEnabled) {\n\t\t\tlines.push(this.theme.hint(\" Type to search · Enter/Space to change · Esc to cancel\"));\n\t\t} else {\n\t\t\tlines.push(this.theme.hint(\" Enter/Space to change · Esc to cancel\"));\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"settings-list.js","sourceRoot":"","sources":["../../src/components/settings-list.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Settings list component with support for cycling values and string input.\n * Based on @mariozechner/pi-tui SettingsList, extended with string editing.\n */\n\nimport {\n\ttype Component,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tmatchesKey,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"@mariozechner/pi-tui\";\n\nexport interface SettingItem {\n\t/** Unique identifier for this setting */\n\tid: string;\n\t/** Display label (left side) */\n\tlabel: string;\n\t/** Optional description shown when selected */\n\tdescription?: string;\n\t/** Current value to display (right side) */\n\tcurrentValue: string;\n\t/**\n\t * If provided, Enter/Space cycles through these values.\n\t * If undefined/empty, the setting is treated as a string input.\n\t */\n\tvalues?: string[];\n\t/** If provided, Enter opens this submenu. Receives current value and done callback. */\n\tsubmenu?: (currentValue: string, done: (selectedValue?: string) => void) => Component;\n\t/** If false, item is selectable but cannot be edited or changed. */\n\teditable?: boolean;\n}\n\nexport interface SettingsListTheme {\n\tlabel: (text: string, selected: boolean) => string;\n\tvalue: (text: string, selected: boolean) => string;\n\tdescription: (text: string) => string;\n\tcursor: string;\n\thint: (text: string) => string;\n}\n\nexport interface SettingsListOptions {\n\tenableSearch?: boolean;\n}\n\nexport class SettingsList implements Component {\n\tprivate items: SettingItem[];\n\tprivate filteredItems: SettingItem[];\n\tprivate theme: SettingsListTheme;\n\tprivate selectedIndex = 0;\n\tprivate maxVisible: number;\n\tprivate onChange: (id: string, newValue: string) => void;\n\tprivate onCancel: () => void;\n\tprivate searchInput?: Input;\n\tprivate searchEnabled: boolean;\n\n\t// Submenu state\n\tprivate submenuComponent: Component | null = null;\n\tprivate submenuItemIndex: number | null = null;\n\n\t// String editing state\n\tprivate editingInput: Input | null = null;\n\tprivate editingItemIndex: number | null = null;\n\n\tconstructor(\n\t\titems: SettingItem[],\n\t\tmaxVisible: number,\n\t\ttheme: SettingsListTheme,\n\t\tonChange: (id: string, newValue: string) => void,\n\t\tonCancel: () => void,\n\t\toptions: SettingsListOptions = {},\n\t) {\n\t\tthis.items = items;\n\t\tthis.filteredItems = items;\n\t\tthis.maxVisible = maxVisible;\n\t\tthis.theme = theme;\n\t\tthis.onChange = onChange;\n\t\tthis.onCancel = onCancel;\n\t\tthis.searchEnabled = options.enableSearch ?? false;\n\t\tif (this.searchEnabled) {\n\t\t\tthis.searchInput = new Input();\n\t\t}\n\t}\n\n\t/** Update an item's currentValue */\n\tupdateValue(id: string, newValue: string): void {\n\t\tconst item = this.items.find((i) => i.id === id);\n\t\tif (item) {\n\t\t\titem.currentValue = newValue;\n\t\t}\n\t}\n\n\tinvalidate(): void {\n\t\tthis.submenuComponent?.invalidate?.();\n\t}\n\n\trender(width: number): string[] {\n\t\t// If submenu is active, render it instead\n\t\tif (this.submenuComponent) {\n\t\t\treturn this.submenuComponent.render(width);\n\t\t}\n\n\t\treturn this.renderMainList(width);\n\t}\n\n\tprivate renderMainList(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tif (this.searchEnabled && this.searchInput && !this.editingInput) {\n\t\t\tlines.push(...this.searchInput.render(width));\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tif (this.items.length === 0) {\n\t\t\tlines.push(this.theme.hint(\" No settings available\"));\n\t\t\tif (this.searchEnabled) {\n\t\t\t\tthis.addHintLine(lines);\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tif (displayItems.length === 0) {\n\t\t\tlines.push(this.theme.hint(\" No matching settings\"));\n\t\t\tthis.addHintLine(lines);\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), displayItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, displayItems.length);\n\n\t\t// Calculate max label width for alignment\n\t\tconst maxLabelWidth = Math.min(30, Math.max(...this.items.map((item) => visibleWidth(item.label))));\n\n\t\t// Render visible items\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = displayItems[i];\n\t\t\tif (!item) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst isEditing = this.editingInput && this.editingItemIndex === i;\n\t\t\tconst prefix = isSelected ? this.theme.cursor : \" \";\n\t\t\tconst prefixWidth = visibleWidth(prefix);\n\n\t\t\t// Pad label to align values\n\t\t\tconst labelPadded = item.label + \" \".repeat(Math.max(0, maxLabelWidth - visibleWidth(item.label)));\n\t\t\tconst labelText = this.theme.label(labelPadded, isSelected);\n\n\t\t\t// Calculate space for value\n\t\t\tconst separator = \" \";\n\t\t\tconst usedWidth = prefixWidth + maxLabelWidth + visibleWidth(separator);\n\t\t\tconst valueMaxWidth = width - usedWidth - 2;\n\n\t\t\tif (isEditing && this.editingInput) {\n\t\t\t\t// Render inline input for string editing\n\t\t\t\tconst inputLines = this.editingInput.render(valueMaxWidth);\n\t\t\t\tconst inputLine = inputLines[0] ?? \"\";\n\t\t\t\tlines.push(prefix + labelText + separator + inputLine);\n\t\t\t} else {\n\t\t\t\tconst valueText = this.theme.value(truncateToWidth(item.currentValue, valueMaxWidth, \"\"), isSelected);\n\t\t\t\tlines.push(prefix + labelText + separator + valueText);\n\t\t\t}\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < displayItems.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${displayItems.length})`;\n\t\t\tlines.push(this.theme.hint(truncateToWidth(scrollText, width - 2, \"\")));\n\t\t}\n\n\t\t// Add description for selected item\n\t\tconst selectedItem = displayItems[this.selectedIndex];\n\t\tif (selectedItem?.description && !this.editingInput) {\n\t\t\tlines.push(\"\");\n\t\t\tconst wrappedDesc = wrapTextWithAnsi(selectedItem.description, width - 4);\n\t\t\tfor (const line of wrappedDesc) {\n\t\t\t\tlines.push(this.theme.description(` ${line}`));\n\t\t\t}\n\t\t}\n\n\t\t// Add hint\n\t\tthis.addHintLine(lines);\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\t// If editing a string value, handle input for the editor\n\t\tif (this.editingInput) {\n\t\t\tthis.handleEditingInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// If submenu is active, delegate all input to it\n\t\tif (this.submenuComponent) {\n\t\t\tthis.submenuComponent.handleInput?.(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Main list input handling\n\t\tconst kb = getEditorKeybindings();\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\n\t\tif (kb.matches(data, \"selectUp\")) {\n\t\t\tif (displayItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? displayItems.length - 1 : this.selectedIndex - 1;\n\t\t} else if (kb.matches(data, \"selectDown\")) {\n\t\t\tif (displayItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === displayItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t} else if (kb.matches(data, \"selectConfirm\") || data === \" \") {\n\t\t\tthis.activateItem();\n\t\t} else if (kb.matches(data, \"selectCancel\")) {\n\t\t\tthis.onCancel();\n\t\t} else if (this.searchEnabled && this.searchInput) {\n\t\t\tconst sanitized = data.replace(/ /g, \"\");\n\t\t\tif (!sanitized) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.searchInput.handleInput(sanitized);\n\t\t\tthis.applyFilter(this.searchInput.getValue());\n\t\t}\n\t}\n\n\tprivate handleEditingInput(data: string): void {\n\t\tif (!this.editingInput) return;\n\n\t\t// Enter: confirm edit\n\t\tif (matchesKey(data, \"enter\")) {\n\t\t\tthis.confirmEdit();\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape: cancel edit\n\t\tif (matchesKey(data, \"escape\")) {\n\t\t\tthis.cancelEdit();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass other input to the editor\n\t\tthis.editingInput.handleInput(data);\n\t}\n\n\tprivate confirmEdit(): void {\n\t\tif (!this.editingInput || this.editingItemIndex === null) return;\n\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tconst item = displayItems[this.editingItemIndex];\n\t\tif (item) {\n\t\t\tconst newValue = this.editingInput.getValue();\n\t\t\titem.currentValue = newValue;\n\t\t\tthis.onChange(item.id, newValue);\n\t\t}\n\n\t\tthis.editingInput = null;\n\t\tthis.editingItemIndex = null;\n\t}\n\n\tprivate cancelEdit(): void {\n\t\tthis.editingInput = null;\n\t\tthis.editingItemIndex = null;\n\t}\n\n\tprivate activateItem(): void {\n\t\tconst displayItems = this.searchEnabled ? this.filteredItems : this.items;\n\t\tconst item = displayItems[this.selectedIndex];\n\t\tif (!item) return;\n\n\t\tif (item.editable === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (item.submenu) {\n\t\t\t// Open submenu, passing current value so it can pre-select correctly\n\t\t\tthis.submenuItemIndex = this.selectedIndex;\n\t\t\tthis.submenuComponent = item.submenu(item.currentValue, (selectedValue?: string) => {\n\t\t\t\tif (selectedValue !== undefined) {\n\t\t\t\t\titem.currentValue = selectedValue;\n\t\t\t\t\tthis.onChange(item.id, selectedValue);\n\t\t\t\t}\n\t\t\t\tthis.closeSubmenu();\n\t\t\t});\n\t\t} else if (item.values && item.values.length > 0) {\n\t\t\t// Cycle through values\n\t\t\tconst currentIndex = item.values.indexOf(item.currentValue);\n\t\t\tconst nextIndex = (currentIndex + 1) % item.values.length;\n\t\t\tconst newValue = item.values[nextIndex];\n\t\t\titem.currentValue = newValue;\n\t\t\tthis.onChange(item.id, newValue);\n\t\t} else {\n\t\t\t// String input - start editing\n\t\t\tthis.editingItemIndex = this.selectedIndex;\n\t\t\tthis.editingInput = new Input();\n\t\t\tthis.editingInput.setValue(item.currentValue);\n\t\t}\n\t}\n\n\tprivate closeSubmenu(): void {\n\t\tthis.submenuComponent = null;\n\t\t// Restore selection to the item that opened the submenu\n\t\tif (this.submenuItemIndex !== null) {\n\t\t\tthis.selectedIndex = this.submenuItemIndex;\n\t\t\tthis.submenuItemIndex = null;\n\t\t}\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tthis.filteredItems = fuzzyFilter(this.items, query, (item) => item.label);\n\t\tthis.selectedIndex = 0;\n\t}\n\n\tprivate addHintLine(lines: string[]): void {\n\t\tlines.push(\"\");\n\t\tif (this.editingInput) {\n\t\t\tlines.push(this.theme.hint(\" Enter to confirm · Esc to cancel\"));\n\t\t} else if (this.searchEnabled) {\n\t\t\tlines.push(this.theme.hint(\" Type to search · Enter/Space to change · Esc to cancel\"));\n\t\t} else {\n\t\t\tlines.push(this.theme.hint(\" Enter/Space to change · Esc to cancel\"));\n\t\t}\n\t}\n}\n"]}
|
package/dist/extension.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAalE,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,EAAE,YAAY,QA2GtD"}
|
package/dist/extension.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
|
|
5
5
|
import { Container, Text } from "@mariozechner/pi-tui";
|
|
6
|
+
import { OrderedMultiSelect } from "./components/ordered-multi-select.js";
|
|
6
7
|
import { SettingsList } from "./components/settings-list.js";
|
|
7
8
|
import { getSetting, setSetting } from "./settings/storage.js";
|
|
8
9
|
export default function piLibExtension(pi) {
|
|
@@ -17,7 +18,7 @@ export default function piLibExtension(pi) {
|
|
|
17
18
|
description: "Configure settings for all extensions",
|
|
18
19
|
handler: async (_args, ctx) => {
|
|
19
20
|
if (registry.size === 0) {
|
|
20
|
-
ctx.ui.notify("No extensions have registered settings", "info");
|
|
21
|
+
ctx.ui.notify("No extensions have registered settings. Ensure pi-extension-settings is listed before consumer extensions in your packages array in ~/.pi/settings.json.", "info");
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
// Sort extensions by name
|
|
@@ -35,17 +36,32 @@ export default function piLibExtension(pi) {
|
|
|
35
36
|
label: theme.bold(extName),
|
|
36
37
|
currentValue: "",
|
|
37
38
|
values: undefined, // No cycling - acts as header
|
|
39
|
+
editable: false,
|
|
38
40
|
});
|
|
39
41
|
// Add each setting
|
|
40
42
|
for (const setting of settings) {
|
|
41
43
|
const currentValue = getSetting(extName, setting.id, setting.defaultValue) ?? setting.defaultValue;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
if (setting.options && setting.options.length > 0) {
|
|
45
|
+
// Ordered multi-select: opens a submenu
|
|
46
|
+
items.push({
|
|
47
|
+
id: `${extName}::${setting.id}`,
|
|
48
|
+
label: ` ${setting.label}`,
|
|
49
|
+
description: setting.description,
|
|
50
|
+
currentValue,
|
|
51
|
+
submenu: (val, submenuDone) => {
|
|
52
|
+
return new OrderedMultiSelect(setting.options, val, getSettingsListTheme(), submenuDone);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
items.push({
|
|
58
|
+
id: `${extName}::${setting.id}`,
|
|
59
|
+
label: ` ${setting.label}`,
|
|
60
|
+
description: setting.description,
|
|
61
|
+
currentValue,
|
|
62
|
+
values: setting.values,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
const settingsList = new SettingsList(items, Math.min(items.length + 2, 20), getSettingsListTheme(), (id, newValue) => {
|
package/dist/extension.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAoB,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAQ/D,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAgB;IACtD,yDAAyD;IACzD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;IAExD,uDAAuD;IACvD,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,gCAAgC,EAAE,CAAC,IAAI,EAAE,EAAE;QACvD,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,IAA2B,CAAC;QACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,CAAC,oBAAoB,EAAE;QACxC,WAAW,EAAE,uCAAuC;QACpD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACzB,GAAG,CAAC,EAAE,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pi extension that provides /extension-settings command.\n */\n\nimport type { ExtensionAPI } from \"@mariozechner/pi-coding-agent\";\nimport { getSettingsListTheme } from \"@mariozechner/pi-coding-agent\";\nimport { Container, Text } from \"@mariozechner/pi-tui\";\nimport { OrderedMultiSelect } from \"./components/ordered-multi-select.js\";\nimport { type SettingItem, SettingsList } from \"./components/settings-list.js\";\nimport { getSetting, setSetting } from \"./settings/storage.js\";\nimport type { SettingDefinition } from \"./settings/types.js\";\n\ninterface RegistrationPayload {\n\tname: string;\n\tsettings: SettingDefinition[];\n}\n\nexport default function piLibExtension(pi: ExtensionAPI) {\n\t// Local registry - stores settings registered via events\n\tconst registry = new Map<string, SettingDefinition[]>();\n\n\t// Listen for registration events from other extensions\n\tpi.events.on(\"pi-extension-settings:register\", (data) => {\n\t\tconst { name, settings } = data as RegistrationPayload;\n\t\tregistry.set(name, settings);\n\t});\n\n\tpi.registerCommand(\"extension-settings\", {\n\t\tdescription: \"Configure settings for all extensions\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (registry.size === 0) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"No extensions have registered settings. Ensure pi-extension-settings is listed before consumer extensions in your packages array in ~/.pi/settings.json.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Sort extensions by name\n\t\t\tconst sortedExtensions = Array.from(registry.entries()).sort(([a], [b]) => a.localeCompare(b));\n\n\t\t\tawait ctx.ui.custom((tui, theme, _kb, done) => {\n\t\t\t\tconst container = new Container();\n\n\t\t\t\t// Title\n\t\t\t\tcontainer.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Extension Settings\")), 1, 1));\n\n\t\t\t\t// Build items grouped by extension\n\t\t\t\tconst items: SettingItem[] = [];\n\n\t\t\t\tfor (const [extName, settings] of sortedExtensions) {\n\t\t\t\t\t// Add extension header as a non-interactive item\n\t\t\t\t\titems.push({\n\t\t\t\t\t\tid: `__header__${extName}`,\n\t\t\t\t\t\tlabel: theme.bold(extName),\n\t\t\t\t\t\tcurrentValue: \"\",\n\t\t\t\t\t\tvalues: undefined, // No cycling - acts as header\n\t\t\t\t\t\teditable: false,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Add each setting\n\t\t\t\t\tfor (const setting of settings) {\n\t\t\t\t\t\tconst currentValue = getSetting(extName, setting.id, setting.defaultValue) ?? setting.defaultValue;\n\n\t\t\t\t\t\tif (setting.options && setting.options.length > 0) {\n\t\t\t\t\t\t\t// Ordered multi-select: opens a submenu\n\t\t\t\t\t\t\titems.push({\n\t\t\t\t\t\t\t\tid: `${extName}::${setting.id}`,\n\t\t\t\t\t\t\t\tlabel: ` ${setting.label}`,\n\t\t\t\t\t\t\t\tdescription: setting.description,\n\t\t\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t\t\tsubmenu: (val, submenuDone) => {\n\t\t\t\t\t\t\t\t\treturn new OrderedMultiSelect(setting.options!, val, getSettingsListTheme(), submenuDone);\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\titems.push({\n\t\t\t\t\t\t\t\tid: `${extName}::${setting.id}`,\n\t\t\t\t\t\t\t\tlabel: ` ${setting.label}`,\n\t\t\t\t\t\t\t\tdescription: setting.description,\n\t\t\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t\t\tvalues: setting.values,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst settingsList = new SettingsList(\n\t\t\t\t\titems,\n\t\t\t\t\tMath.min(items.length + 2, 20),\n\t\t\t\t\tgetSettingsListTheme(),\n\t\t\t\t\t(id, newValue) => {\n\t\t\t\t\t\t// Skip headers\n\t\t\t\t\t\tif (id.startsWith(\"__header__\")) return;\n\n\t\t\t\t\t\t// Parse extension::settingId\n\t\t\t\t\t\tconst [extensionName, settingId] = id.split(\"::\");\n\t\t\t\t\t\tif (extensionName && settingId) {\n\t\t\t\t\t\t\tsetSetting(extensionName, settingId, newValue);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone(undefined);\n\t\t\t\t\t},\n\t\t\t\t\t{ enableSearch: true },\n\t\t\t\t);\n\n\t\t\t\tcontainer.addChild(settingsList);\n\n\t\t\t\treturn {\n\t\t\t\t\trender(width: number) {\n\t\t\t\t\t\treturn container.render(width);\n\t\t\t\t\t},\n\t\t\t\t\tinvalidate() {\n\t\t\t\t\t\tcontainer.invalidate();\n\t\t\t\t\t},\n\t\t\t\t\thandleInput(data: string) {\n\t\t\t\t\t\tsettingsList.handleInput?.(data);\n\t\t\t\t\t\ttui.requestRender();\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
*/
|
|
36
36
|
export { default } from "./extension.js";
|
|
37
37
|
export { getSetting, setSetting } from "./settings/storage.js";
|
|
38
|
-
export type { SettingDefinition } from "./settings/types.js";
|
|
38
|
+
export type { OrderedListOption, SettingDefinition } from "./settings/types.js";
|
|
39
39
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE/D,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,wBAAwB;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,iDAAiD;AACjD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * Pi extension settings library.\n *\n * Provides helpers for reading/writing extension settings and a UI\n * for configuring all registered extension settings.\n *\n * ## For Extension Authors\n *\n * ### Register Settings (for the UI)\n *\n * Emit the `pi-extension-settings:register` event during extension load:\n *\n * ```typescript\n * import type { SettingDefinition } from \"@juanibiapina/pi-extension-settings\";\n *\n * export default function(pi: ExtensionAPI) {\n * pi.events.emit(\"pi-extension-settings:register\", {\n * name: \"my-extension\",\n * settings: [\n * { id: \"timeout\", label: \"Timeout\", defaultValue: \"30\", values: [\"10\", \"30\", \"60\"] },\n * { id: \"projectName\", label: \"Project Name\", defaultValue: \"\" },\n * ] satisfies SettingDefinition[]\n * });\n * }\n * ```\n *\n * ### Read/Write Settings\n *\n * ```typescript\n * import { getSetting, setSetting } from \"@juanibiapina/pi-extension-settings\";\n *\n * const timeout = getSetting(\"my-extension\", \"timeout\", \"30\");\n * setSetting(\"my-extension\", \"timeout\", \"60\");\n * ```\n */\n\n// Extension entry point\nexport { default } from \"./extension.js\";\n\n// Stateless helpers for reading/writing settings\nexport { getSetting, setSetting } from \"./settings/storage.js\";\n//
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,wBAAwB;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,iDAAiD;AACjD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * Pi extension settings library.\n *\n * Provides helpers for reading/writing extension settings and a UI\n * for configuring all registered extension settings.\n *\n * ## For Extension Authors\n *\n * ### Register Settings (for the UI)\n *\n * Emit the `pi-extension-settings:register` event during extension load:\n *\n * ```typescript\n * import type { SettingDefinition } from \"@juanibiapina/pi-extension-settings\";\n *\n * export default function(pi: ExtensionAPI) {\n * pi.events.emit(\"pi-extension-settings:register\", {\n * name: \"my-extension\",\n * settings: [\n * { id: \"timeout\", label: \"Timeout\", defaultValue: \"30\", values: [\"10\", \"30\", \"60\"] },\n * { id: \"projectName\", label: \"Project Name\", defaultValue: \"\" },\n * ] satisfies SettingDefinition[]\n * });\n * }\n * ```\n *\n * ### Read/Write Settings\n *\n * ```typescript\n * import { getSetting, setSetting } from \"@juanibiapina/pi-extension-settings\";\n *\n * const timeout = getSetting(\"my-extension\", \"timeout\", \"30\");\n * setSetting(\"my-extension\", \"timeout\", \"60\");\n * ```\n */\n\n// Extension entry point\nexport { default } from \"./extension.js\";\n\n// Stateless helpers for reading/writing settings\nexport { getSetting, setSetting } from \"./settings/storage.js\";\n// Types for documentation/type-safety when emitting events\nexport type { OrderedListOption, SettingDefinition } from \"./settings/types.js\";\n"]}
|
package/dist/settings/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for extension settings.
|
|
3
3
|
*/
|
|
4
|
+
export interface OrderedListOption {
|
|
5
|
+
/** Value stored in the comma-separated setting */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Display label in the menu */
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
4
10
|
export interface SettingDefinition {
|
|
5
11
|
/** Unique identifier for this setting within the extension */
|
|
6
12
|
id: string;
|
|
@@ -13,7 +19,15 @@ export interface SettingDefinition {
|
|
|
13
19
|
/**
|
|
14
20
|
* Values to cycle through (Enter/Space cycles).
|
|
15
21
|
* If undefined or empty, the setting is treated as a free-form string input.
|
|
22
|
+
* Mutually exclusive with `options`.
|
|
16
23
|
*/
|
|
17
24
|
values?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Available options for ordered multi-select.
|
|
27
|
+
* When present, Enter opens an ordered list submenu where items can be
|
|
28
|
+
* toggled on/off and reordered. Value is stored as comma-separated IDs.
|
|
29
|
+
* Mutually exclusive with `values`.
|
|
30
|
+
*/
|
|
31
|
+
options?: OrderedListOption[];
|
|
18
32
|
}
|
|
19
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/settings/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,iBAAiB;IACjC,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/settings/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,iBAAiB;IACjC,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IACjC,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/settings/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Type definitions for extension settings.\n */\n\nexport interface SettingDefinition {\n\t/** Unique identifier for this setting within the extension */\n\tid: string;\n\t/** Display label */\n\tlabel: string;\n\t/** Optional description shown when selected */\n\tdescription?: string;\n\t/** Default value if not set in config file */\n\tdefaultValue: string;\n\t/**\n\t * Values to cycle through (Enter/Space cycles).\n\t * If undefined or empty, the setting is treated as a free-form string input.\n\t */\n\tvalues?: string[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/settings/types.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Type definitions for extension settings.\n */\n\nexport interface OrderedListOption {\n\t/** Value stored in the comma-separated setting */\n\tid: string;\n\t/** Display label in the menu */\n\tlabel: string;\n}\n\nexport interface SettingDefinition {\n\t/** Unique identifier for this setting within the extension */\n\tid: string;\n\t/** Display label */\n\tlabel: string;\n\t/** Optional description shown when selected */\n\tdescription?: string;\n\t/** Default value if not set in config file */\n\tdefaultValue: string;\n\t/**\n\t * Values to cycle through (Enter/Space cycles).\n\t * If undefined or empty, the setting is treated as a free-form string input.\n\t * Mutually exclusive with `options`.\n\t */\n\tvalues?: string[];\n\t/**\n\t * Available options for ordered multi-select.\n\t * When present, Enter opens an ordered list submenu where items can be\n\t * toggled on/off and reordered. Value is stored as comma-separated IDs.\n\t * Mutually exclusive with `values`.\n\t */\n\toptions?: OrderedListOption[];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juanibiapina/pi-extension-settings",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Pi extension for centralized settings management across extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,13 +25,15 @@
|
|
|
25
25
|
"utilities"
|
|
26
26
|
],
|
|
27
27
|
"pi": {
|
|
28
|
-
"extensions": [
|
|
28
|
+
"extensions": [
|
|
29
|
+
"./dist/index.js"
|
|
30
|
+
]
|
|
29
31
|
},
|
|
30
32
|
"author": "Juan Ibiapina",
|
|
31
33
|
"license": "MIT",
|
|
32
34
|
"repository": {
|
|
33
35
|
"type": "git",
|
|
34
|
-
"url": "
|
|
36
|
+
"url": "https://github.com/juanibiapina/pi-extension-settings"
|
|
35
37
|
},
|
|
36
38
|
"engines": {
|
|
37
39
|
"node": ">=20.0.0"
|