silvery 0.17.0 → 0.17.2
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/UPNG-AVSMjiFE.mjs +5076 -0
- package/dist/UPNG-AVSMjiFE.mjs.map +1 -0
- package/dist/__vite-browser-external-2447137e-D3GdsvS_.mjs +6 -0
- package/dist/__vite-browser-external-2447137e-D3GdsvS_.mjs.map +1 -0
- package/dist/animation-C_PTO0uH.mjs +304 -0
- package/dist/animation-C_PTO0uH.mjs.map +1 -0
- package/dist/ansi-CXLE_pt1.mjs +71 -0
- package/dist/ansi-CXLE_pt1.mjs.map +1 -0
- package/dist/ansi-zmNzgkPB.d.mts +49 -0
- package/dist/ansi-zmNzgkPB.d.mts.map +1 -0
- package/dist/apng-DCWY913R.mjs +3 -0
- package/dist/apng-ENBAJk-H.mjs +70 -0
- package/dist/apng-ENBAJk-H.mjs.map +1 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backend-CkIkIHR-.mjs +13396 -0
- package/dist/backend-CkIkIHR-.mjs.map +1 -0
- package/dist/backends-CkvbG3js.mjs +1181 -0
- package/dist/backends-CkvbG3js.mjs.map +1 -0
- package/dist/backends-CyJqNLeK.mjs +3 -0
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/cli-B-k7Bm56.mjs +4 -0
- package/dist/context-QreF3UHr.mjs +64 -0
- package/dist/context-QreF3UHr.mjs.map +1 -0
- package/dist/derive-D7bFJdfU.d.mts +28 -0
- package/dist/derive-D7bFJdfU.d.mts.map +1 -0
- package/dist/devtools-CscuKaDK.mjs +89 -0
- package/dist/devtools-CscuKaDK.mjs.map +1 -0
- package/dist/devtools-D4oGc6LY.mjs +2 -0
- package/dist/eta-DLiVPaSD.mjs +110 -0
- package/dist/eta-DLiVPaSD.mjs.map +1 -0
- package/dist/flexily-zero-adapter-DmG4Ge8t.mjs +3376 -0
- package/dist/flexily-zero-adapter-DmG4Ge8t.mjs.map +1 -0
- package/dist/flexily-zero-adapter-GHwEW11s.mjs +2 -0
- package/dist/gif-BaJNREpP.mjs +3 -0
- package/dist/gif-Bp6fIyN3.mjs +73 -0
- package/dist/gif-Bp6fIyN3.mjs.map +1 -0
- package/dist/gifenc-GiVCZ9-3.mjs +730 -0
- package/dist/gifenc-GiVCZ9-3.mjs.map +1 -0
- package/dist/image-Dx7gYjkq.mjs +346 -0
- package/dist/image-Dx7gYjkq.mjs.map +1 -0
- package/dist/index-CBcSpGSM.d.mts +3416 -0
- package/dist/index-CBcSpGSM.d.mts.map +1 -0
- package/dist/index-DCVL3jHo.d.mts +634 -0
- package/dist/index-DCVL3jHo.d.mts.map +1 -0
- package/dist/index-p-wBs_wH.d.mts +175 -0
- package/dist/index-p-wBs_wH.d.mts.map +1 -0
- package/dist/index.d.mts +7296 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9399 -0
- package/dist/index.mjs.map +1 -0
- package/dist/key-mapping-BsUHe_nk.mjs +3 -0
- package/dist/key-mapping-DsyfLEdC.mjs +132 -0
- package/dist/key-mapping-DsyfLEdC.mjs.map +1 -0
- package/dist/layout-engine-B3dsnVLU.mjs +50 -0
- package/dist/layout-engine-B3dsnVLU.mjs.map +1 -0
- package/dist/layout-engine-D_lSR4i9.mjs +2 -0
- package/dist/multi-progress-C0-rkn86.d.mts +180 -0
- package/dist/multi-progress-C0-rkn86.d.mts.map +1 -0
- package/dist/multi-progress-CQVB9lES.mjs +219 -0
- package/dist/multi-progress-CQVB9lES.mjs.map +1 -0
- package/dist/node-Dedx-6xF.mjs +1085 -0
- package/dist/node-Dedx-6xF.mjs.map +1 -0
- package/dist/pipeline-DDOPrjuY.mjs +4387 -0
- package/dist/pipeline-DDOPrjuY.mjs.map +1 -0
- package/dist/progress-bar-COPSBlT9.mjs +155 -0
- package/dist/progress-bar-COPSBlT9.mjs.map +1 -0
- package/dist/reconciler-2lp5VXK7.mjs +16506 -0
- package/dist/reconciler-2lp5VXK7.mjs.map +1 -0
- package/dist/render-string-BXvxTg5P.mjs +201 -0
- package/dist/render-string-BXvxTg5P.mjs.map +1 -0
- package/dist/render-string-hvfpVtoP.mjs +2 -0
- package/dist/resvg-js-V6oMi8CY.mjs +203 -0
- package/dist/resvg-js-V6oMi8CY.mjs.map +1 -0
- package/dist/runtime-BjDHNTxJ.mjs +8723 -0
- package/dist/runtime-BjDHNTxJ.mjs.map +1 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +3 -0
- package/dist/spinner-Cgej6Vnb.d.mts +127 -0
- package/dist/spinner-Cgej6Vnb.d.mts.map +1 -0
- package/dist/spinner-DSByknyx.mjs +298 -0
- package/dist/spinner-DSByknyx.mjs.map +1 -0
- package/dist/src-9B5k0JmY.mjs +1629 -0
- package/dist/src-9B5k0JmY.mjs.map +1 -0
- package/dist/src-C9f3hiVG.mjs +3620 -0
- package/dist/src-C9f3hiVG.mjs.map +1 -0
- package/dist/src-fJVbhdn-.mjs +816 -0
- package/dist/src-fJVbhdn-.mjs.map +1 -0
- package/dist/theme.d.mts +115 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +8 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/types-Bhj5QkIQ.mjs +13 -0
- package/dist/types-Bhj5QkIQ.mjs.map +1 -0
- package/dist/types-CDgkE-Rw.d.mts +241 -0
- package/dist/types-CDgkE-Rw.d.mts.map +1 -0
- package/dist/ui/animation.d.mts +2 -0
- package/dist/ui/animation.mjs +2 -0
- package/dist/ui/ansi.d.mts +2 -0
- package/dist/ui/ansi.mjs +2 -0
- package/dist/ui/cli.d.mts +5 -0
- package/dist/ui/cli.mjs +7 -0
- package/dist/ui/display.d.mts +35 -0
- package/dist/ui/display.d.mts.map +1 -0
- package/dist/ui/display.mjs +123 -0
- package/dist/ui/display.mjs.map +1 -0
- package/dist/ui/image.d.mts +2 -0
- package/dist/ui/image.mjs +2 -0
- package/dist/ui/input.d.mts +184 -0
- package/dist/ui/input.d.mts.map +1 -0
- package/dist/ui/input.mjs +285 -0
- package/dist/ui/input.mjs.map +1 -0
- package/dist/ui/progress.d.mts +249 -0
- package/dist/ui/progress.d.mts.map +1 -0
- package/dist/ui/progress.mjs +858 -0
- package/dist/ui/progress.mjs.map +1 -0
- package/dist/ui/react.d.mts +280 -0
- package/dist/ui/react.d.mts.map +1 -0
- package/dist/ui/react.mjs +413 -0
- package/dist/ui/react.mjs.map +1 -0
- package/dist/ui/utils.d.mts +86 -0
- package/dist/ui/utils.d.mts.map +1 -0
- package/dist/ui/utils.mjs +2 -0
- package/dist/ui/wrappers.d.mts +3 -0
- package/dist/ui/wrappers.mjs +2 -0
- package/dist/ui.d.mts +6 -0
- package/dist/ui.mjs +7 -0
- package/dist/useLatest-BMIYXd6e.d.mts +154 -0
- package/dist/useLatest-BMIYXd6e.d.mts.map +1 -0
- package/dist/useLayout-BG2cGl15.mjs +139 -0
- package/dist/useLayout-BG2cGl15.mjs.map +1 -0
- package/dist/with-text-input-CmHf_9d6.d.mts +284 -0
- package/dist/with-text-input-CmHf_9d6.d.mts.map +1 -0
- package/dist/wrapper-Dqh0zi2W.mjs +3527 -0
- package/dist/wrapper-Dqh0zi2W.mjs.map +1 -0
- package/dist/wrappers-hhL8EQ_n.mjs +810 -0
- package/dist/wrappers-hhL8EQ_n.mjs.map +1 -0
- package/dist/yoga-adapter-BJ9SOhTY.mjs +245 -0
- package/dist/yoga-adapter-BJ9SOhTY.mjs.map +1 -0
- package/dist/yoga-adapter-Daq6-dw1.mjs +2 -0
- package/package.json +48 -75
- package/CHANGELOG.md +0 -319
- package/dist/chalk.js +0 -4
- package/dist/index.js +0 -270
- package/dist/ink.js +0 -142
- package/dist/runtime.js +0 -135
- package/dist/theme.js +0 -7
- package/dist/ui/animation.js +0 -3
- package/dist/ui/ansi.js +0 -3
- package/dist/ui/cli.js +0 -9
- package/dist/ui/display.js +0 -4
- package/dist/ui/image.js +0 -4
- package/dist/ui/input.js +0 -3
- package/dist/ui/progress.js +0 -9
- package/dist/ui/react.js +0 -4
- package/dist/ui/utils.js +0 -3
- package/dist/ui/wrappers.js +0 -15
- package/dist/ui.js +0 -18
- package/src/index.ts +0 -73
- package/src/runtime.ts +0 -4
- package/src/theme.ts +0 -4
- package/src/ui/animation.ts +0 -2
- package/src/ui/ansi.ts +0 -2
- package/src/ui/cli.ts +0 -3
- package/src/ui/display.ts +0 -2
- package/src/ui/image.ts +0 -2
- package/src/ui/input.ts +0 -2
- package/src/ui/progress.ts +0 -2
- package/src/ui/react.ts +0 -2
- package/src/ui/utils.ts +0 -2
- package/src/ui/wrappers.ts +0 -2
- package/src/ui.ts +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.mts","names":[],"sources":["../../packages/ag-react/src/ui/display/Table.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+CgB,KAAA,CAAA;EAAQ,OAAA;EAAS,IAAA;EAAM;AAAA,GAAkB,UAAA,GAAa,KAAA,CAAM,YAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import "react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region packages/ag-react/src/ui/display/Table.tsx
|
|
4
|
+
/**
|
|
5
|
+
* Unicode box drawing characters for borders
|
|
6
|
+
*/
|
|
7
|
+
const BOX = {
|
|
8
|
+
topLeft: "┌",
|
|
9
|
+
topRight: "┐",
|
|
10
|
+
bottomLeft: "└",
|
|
11
|
+
bottomRight: "┘",
|
|
12
|
+
horizontal: "─",
|
|
13
|
+
vertical: "│",
|
|
14
|
+
leftT: "├",
|
|
15
|
+
rightT: "┤",
|
|
16
|
+
topT: "┬",
|
|
17
|
+
bottomT: "┴",
|
|
18
|
+
cross: "┼"
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Data grid display component for React TUI apps
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* import { Table } from "@silvery/ag-react/ui/display";
|
|
26
|
+
*
|
|
27
|
+
* const columns = [
|
|
28
|
+
* { key: "name", header: "Name", width: 20 },
|
|
29
|
+
* { key: "status", header: "Status", width: 10, align: "center" },
|
|
30
|
+
* { key: "count", header: "Count", width: 8, align: "right" },
|
|
31
|
+
* ];
|
|
32
|
+
*
|
|
33
|
+
* const data = [
|
|
34
|
+
* { name: "Item 1", status: "active", count: 42 },
|
|
35
|
+
* { name: "Item 2", status: "pending", count: 7 },
|
|
36
|
+
* ];
|
|
37
|
+
*
|
|
38
|
+
* function DataView() {
|
|
39
|
+
* return <Table columns={columns} data={data} border />;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function Table({ columns, data, border = false }) {
|
|
44
|
+
const effectiveColumns = calculateColumnWidths(columns, data);
|
|
45
|
+
const lines = [];
|
|
46
|
+
if (border) lines.push(buildBorderLine(effectiveColumns, "top"));
|
|
47
|
+
lines.push(buildDataRow(effectiveColumns, getHeaderRow(effectiveColumns), border));
|
|
48
|
+
if (border) lines.push(buildBorderLine(effectiveColumns, "middle"));
|
|
49
|
+
for (const row of data) lines.push(buildDataRow(effectiveColumns, row, border));
|
|
50
|
+
if (border) lines.push(buildBorderLine(effectiveColumns, "bottom"));
|
|
51
|
+
return /* @__PURE__ */ jsx("span", {
|
|
52
|
+
"data-table": true,
|
|
53
|
+
"data-border": border,
|
|
54
|
+
children: lines.join("\n")
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Calculate effective column widths based on content if not specified
|
|
59
|
+
*/
|
|
60
|
+
function calculateColumnWidths(columns, data) {
|
|
61
|
+
return columns.map((col) => {
|
|
62
|
+
if (col.width !== void 0) return {
|
|
63
|
+
...col,
|
|
64
|
+
effectiveWidth: col.width
|
|
65
|
+
};
|
|
66
|
+
let maxWidth = col.header.length;
|
|
67
|
+
for (const row of data) {
|
|
68
|
+
const value = String(row[col.key] ?? "");
|
|
69
|
+
maxWidth = Math.max(maxWidth, value.length);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...col,
|
|
73
|
+
effectiveWidth: maxWidth
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create header row object from columns
|
|
79
|
+
*/
|
|
80
|
+
function getHeaderRow(columns) {
|
|
81
|
+
const row = {};
|
|
82
|
+
for (const col of columns) row[col.key] = col.header;
|
|
83
|
+
return row;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build a border line (top, middle, or bottom)
|
|
87
|
+
*/
|
|
88
|
+
function buildBorderLine(columns, position) {
|
|
89
|
+
const left = position === "top" ? BOX.topLeft : position === "bottom" ? BOX.bottomLeft : BOX.leftT;
|
|
90
|
+
const right = position === "top" ? BOX.topRight : position === "bottom" ? BOX.bottomRight : BOX.rightT;
|
|
91
|
+
const join = position === "top" ? BOX.topT : position === "bottom" ? BOX.bottomT : BOX.cross;
|
|
92
|
+
return left + columns.map((col) => BOX.horizontal.repeat(col.effectiveWidth + 2)).join(join) + right;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build a data row (header or content)
|
|
96
|
+
*/
|
|
97
|
+
function buildDataRow(columns, row, border) {
|
|
98
|
+
const cells = columns.map((col) => {
|
|
99
|
+
return formatCell(String(row[col.key] ?? ""), col.effectiveWidth, col.align ?? "left");
|
|
100
|
+
});
|
|
101
|
+
if (border) return BOX.vertical + " " + cells.join(" " + BOX.vertical + " ") + " " + BOX.vertical;
|
|
102
|
+
return cells.join(" ");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Format a cell value with alignment and truncation
|
|
106
|
+
*/
|
|
107
|
+
function formatCell(value, width, align) {
|
|
108
|
+
if (value.length > width) return value.slice(0, width - 1) + "…";
|
|
109
|
+
const padding = width - value.length;
|
|
110
|
+
switch (align) {
|
|
111
|
+
case "right": return " ".repeat(padding) + value;
|
|
112
|
+
case "center": {
|
|
113
|
+
const leftPad = Math.floor(padding / 2);
|
|
114
|
+
const rightPad = padding - leftPad;
|
|
115
|
+
return " ".repeat(leftPad) + value + " ".repeat(rightPad);
|
|
116
|
+
}
|
|
117
|
+
default: return value + " ".repeat(padding);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { Table };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=display.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.mjs","names":[],"sources":["../../packages/ag-react/src/ui/display/Table.tsx"],"sourcesContent":["/**\n * React Table component for silvery/Ink TUI apps\n */\n\nimport React from \"react\"\nimport type { TableProps, TableColumn } from \"../types.js\"\n\n/**\n * Unicode box drawing characters for borders\n */\nconst BOX = {\n topLeft: \"┌\",\n topRight: \"┐\",\n bottomLeft: \"└\",\n bottomRight: \"┘\",\n horizontal: \"─\",\n vertical: \"│\",\n leftT: \"├\",\n rightT: \"┤\",\n topT: \"┬\",\n bottomT: \"┴\",\n cross: \"┼\",\n} as const\n\n/**\n * Data grid display component for React TUI apps\n *\n * @example\n * ```tsx\n * import { Table } from \"@silvery/ag-react/ui/display\";\n *\n * const columns = [\n * { key: \"name\", header: \"Name\", width: 20 },\n * { key: \"status\", header: \"Status\", width: 10, align: \"center\" },\n * { key: \"count\", header: \"Count\", width: 8, align: \"right\" },\n * ];\n *\n * const data = [\n * { name: \"Item 1\", status: \"active\", count: 42 },\n * { name: \"Item 2\", status: \"pending\", count: 7 },\n * ];\n *\n * function DataView() {\n * return <Table columns={columns} data={data} border />;\n * }\n * ```\n */\nexport function Table({ columns, data, border = false }: TableProps): React.ReactElement {\n // Calculate effective column widths\n const effectiveColumns = calculateColumnWidths(columns, data)\n\n const lines: string[] = []\n\n if (border) {\n // Top border\n lines.push(buildBorderLine(effectiveColumns, \"top\"))\n }\n\n // Header row\n lines.push(buildDataRow(effectiveColumns, getHeaderRow(effectiveColumns), border))\n\n if (border) {\n // Separator after header\n lines.push(buildBorderLine(effectiveColumns, \"middle\"))\n }\n\n // Data rows\n for (const row of data) {\n lines.push(buildDataRow(effectiveColumns, row, border))\n }\n\n if (border) {\n // Bottom border\n lines.push(buildBorderLine(effectiveColumns, \"bottom\"))\n }\n\n return (\n <span data-table data-border={border}>\n {lines.join(\"\\n\")}\n </span>\n )\n}\n\n/**\n * Calculate effective column widths based on content if not specified\n */\nfunction calculateColumnWidths(\n columns: TableColumn[],\n data: Array<Record<string, unknown>>,\n): Array<TableColumn & { effectiveWidth: number }> {\n return columns.map((col) => {\n if (col.width !== undefined) {\n return { ...col, effectiveWidth: col.width }\n }\n\n // Calculate width from content\n let maxWidth = col.header.length\n\n for (const row of data) {\n const value = String(row[col.key] ?? \"\")\n maxWidth = Math.max(maxWidth, value.length)\n }\n\n return { ...col, effectiveWidth: maxWidth }\n })\n}\n\n/**\n * Create header row object from columns\n */\nfunction getHeaderRow(columns: Array<TableColumn & { effectiveWidth: number }>): Record<string, unknown> {\n const row: Record<string, unknown> = {}\n for (const col of columns) {\n row[col.key] = col.header\n }\n return row\n}\n\n/**\n * Build a border line (top, middle, or bottom)\n */\nfunction buildBorderLine(\n columns: Array<TableColumn & { effectiveWidth: number }>,\n position: \"top\" | \"middle\" | \"bottom\",\n): string {\n const left = position === \"top\" ? BOX.topLeft : position === \"bottom\" ? BOX.bottomLeft : BOX.leftT\n const right = position === \"top\" ? BOX.topRight : position === \"bottom\" ? BOX.bottomRight : BOX.rightT\n const join = position === \"top\" ? BOX.topT : position === \"bottom\" ? BOX.bottomT : BOX.cross\n\n const segments = columns.map((col) => BOX.horizontal.repeat(col.effectiveWidth + 2))\n\n return left + segments.join(join) + right\n}\n\n/**\n * Build a data row (header or content)\n */\nfunction buildDataRow(\n columns: Array<TableColumn & { effectiveWidth: number }>,\n row: Record<string, unknown>,\n border: boolean,\n): string {\n const cells = columns.map((col) => {\n const value = String(row[col.key] ?? \"\")\n return formatCell(value, col.effectiveWidth, col.align ?? \"left\")\n })\n\n if (border) {\n return BOX.vertical + \" \" + cells.join(\" \" + BOX.vertical + \" \") + \" \" + BOX.vertical\n }\n\n return cells.join(\" \")\n}\n\n/**\n * Format a cell value with alignment and truncation\n */\nfunction formatCell(value: string, width: number, align: \"left\" | \"center\" | \"right\"): string {\n // Truncate if too long\n if (value.length > width) {\n return value.slice(0, width - 1) + \"…\"\n }\n\n // Pad according to alignment\n const padding = width - value.length\n\n switch (align) {\n case \"right\":\n return \" \".repeat(padding) + value\n case \"center\": {\n const leftPad = Math.floor(padding / 2)\n const rightPad = padding - leftPad\n return \" \".repeat(leftPad) + value + \" \".repeat(rightPad)\n }\n case \"left\":\n default:\n return value + \" \".repeat(padding)\n }\n}\n"],"mappings":";;;;;;AAUA,MAAM,MAAM;CACV,SAAS;CACT,UAAU;CACV,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,UAAU;CACV,OAAO;CACP,QAAQ;CACR,MAAM;CACN,SAAS;CACT,OAAO;CACR;;;;;;;;;;;;;;;;;;;;;;;;AAyBD,SAAgB,MAAM,EAAE,SAAS,MAAM,SAAS,SAAyC;CAEvF,MAAM,mBAAmB,sBAAsB,SAAS,KAAK;CAE7D,MAAM,QAAkB,EAAE;AAE1B,KAAI,OAEF,OAAM,KAAK,gBAAgB,kBAAkB,MAAM,CAAC;AAItD,OAAM,KAAK,aAAa,kBAAkB,aAAa,iBAAiB,EAAE,OAAO,CAAC;AAElF,KAAI,OAEF,OAAM,KAAK,gBAAgB,kBAAkB,SAAS,CAAC;AAIzD,MAAK,MAAM,OAAO,KAChB,OAAM,KAAK,aAAa,kBAAkB,KAAK,OAAO,CAAC;AAGzD,KAAI,OAEF,OAAM,KAAK,gBAAgB,kBAAkB,SAAS,CAAC;AAGzD,QACE,oBAAC,QAAD;EAAM,cAAA;EAAW,eAAa;YAC3B,MAAM,KAAK,KAAK;EACZ,CAAA;;;;;AAOX,SAAS,sBACP,SACA,MACiD;AACjD,QAAO,QAAQ,KAAK,QAAQ;AAC1B,MAAI,IAAI,UAAU,KAAA,EAChB,QAAO;GAAE,GAAG;GAAK,gBAAgB,IAAI;GAAO;EAI9C,IAAI,WAAW,IAAI,OAAO;AAE1B,OAAK,MAAM,OAAO,MAAM;GACtB,MAAM,QAAQ,OAAO,IAAI,IAAI,QAAQ,GAAG;AACxC,cAAW,KAAK,IAAI,UAAU,MAAM,OAAO;;AAG7C,SAAO;GAAE,GAAG;GAAK,gBAAgB;GAAU;GAC3C;;;;;AAMJ,SAAS,aAAa,SAAmF;CACvG,MAAM,MAA+B,EAAE;AACvC,MAAK,MAAM,OAAO,QAChB,KAAI,IAAI,OAAO,IAAI;AAErB,QAAO;;;;;AAMT,SAAS,gBACP,SACA,UACQ;CACR,MAAM,OAAO,aAAa,QAAQ,IAAI,UAAU,aAAa,WAAW,IAAI,aAAa,IAAI;CAC7F,MAAM,QAAQ,aAAa,QAAQ,IAAI,WAAW,aAAa,WAAW,IAAI,cAAc,IAAI;CAChG,MAAM,OAAO,aAAa,QAAQ,IAAI,OAAO,aAAa,WAAW,IAAI,UAAU,IAAI;AAIvF,QAAO,OAFU,QAAQ,KAAK,QAAQ,IAAI,WAAW,OAAO,IAAI,iBAAiB,EAAE,CAAC,CAE7D,KAAK,KAAK,GAAG;;;;;AAMtC,SAAS,aACP,SACA,KACA,QACQ;CACR,MAAM,QAAQ,QAAQ,KAAK,QAAQ;AAEjC,SAAO,WADO,OAAO,IAAI,IAAI,QAAQ,GAAG,EACf,IAAI,gBAAgB,IAAI,SAAS,OAAO;GACjE;AAEF,KAAI,OACF,QAAO,IAAI,WAAW,MAAM,MAAM,KAAK,MAAM,IAAI,WAAW,IAAI,GAAG,MAAM,IAAI;AAG/E,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,WAAW,OAAe,OAAe,OAA4C;AAE5F,KAAI,MAAM,SAAS,MACjB,QAAO,MAAM,MAAM,GAAG,QAAQ,EAAE,GAAG;CAIrC,MAAM,UAAU,QAAQ,MAAM;AAE9B,SAAQ,OAAR;EACE,KAAK,QACH,QAAO,IAAI,OAAO,QAAQ,GAAG;EAC/B,KAAK,UAAU;GACb,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;GACvC,MAAM,WAAW,UAAU;AAC3B,UAAO,IAAI,OAAO,QAAQ,GAAG,QAAQ,IAAI,OAAO,SAAS;;EAG3D,QACE,QAAO,QAAQ,IAAI,OAAO,QAAQ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as isSixelSupported, c as encodeKittyImage, i as encodeSixel, l as isKittyGraphicsSupported, n as ImageProps, o as KittyImageOptions, r as SixelImageData, s as deleteKittyImage, t as Image } from "../index-p-wBs_wH.mjs";
|
|
2
|
+
export { Image, ImageProps, KittyImageOptions, SixelImageData, deleteKittyImage, encodeKittyImage, encodeSixel, isKittyGraphicsSupported, isSixelSupported };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as encodeKittyImage, i as deleteKittyImage, n as encodeSixel, o as isKittyGraphicsSupported, r as isSixelSupported, t as Image } from "../image-Dx7gYjkq.mjs";
|
|
2
|
+
export { Image, deleteKittyImage, encodeKittyImage, encodeSixel, isKittyGraphicsSupported, isSixelSupported };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { _ as TextInputOptions, o as SelectOption, s as SelectProps, v as TextInputProps } from "../types-CDgkE-Rw.mjs";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
//#region packages/ag-react/src/ui/input/TextInput.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Single-line text input component for React TUI apps
|
|
7
|
+
*
|
|
8
|
+
* This is a controlled component that renders the current input state.
|
|
9
|
+
* It does NOT handle keyboard input directly - that's the caller's responsibility
|
|
10
|
+
* (via useInput hook in Ink, or similar).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { TextInput } from "@silvery/ag-react/ui/input";
|
|
15
|
+
* import { useInput } from "ink";
|
|
16
|
+
*
|
|
17
|
+
* function MyForm() {
|
|
18
|
+
* const [value, setValue] = useState("");
|
|
19
|
+
*
|
|
20
|
+
* useInput((input, key) => {
|
|
21
|
+
* if (key.backspace || key.delete) {
|
|
22
|
+
* setValue(v => v.slice(0, -1));
|
|
23
|
+
* } else if (!key.ctrl && !key.meta && input) {
|
|
24
|
+
* setValue(v => v + input);
|
|
25
|
+
* }
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* return <TextInput value={value} onChange={setValue} placeholder="Type here..." />;
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // With password masking
|
|
32
|
+
* <TextInput value={password} onChange={setPassword} mask="*" />
|
|
33
|
+
*
|
|
34
|
+
* // With autocomplete
|
|
35
|
+
* <TextInput
|
|
36
|
+
* value={query}
|
|
37
|
+
* onChange={setQuery}
|
|
38
|
+
* autocomplete={["apple", "apricot", "avocado"]}
|
|
39
|
+
* />
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function TextInput({
|
|
43
|
+
value,
|
|
44
|
+
onChange,
|
|
45
|
+
placeholder,
|
|
46
|
+
mask,
|
|
47
|
+
autocomplete,
|
|
48
|
+
onAutocomplete,
|
|
49
|
+
onSubmit,
|
|
50
|
+
cursorPosition,
|
|
51
|
+
focused
|
|
52
|
+
}: TextInputProps): React.ReactElement;
|
|
53
|
+
/**
|
|
54
|
+
* Hook for managing text input state with autocomplete support
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* function SearchInput() {
|
|
59
|
+
* const { value, displayValue, suggestion, handleInput, acceptSuggestion, clear } =
|
|
60
|
+
* useTextInput({ autocomplete: ["apple", "banana", "cherry"] });
|
|
61
|
+
*
|
|
62
|
+
* useInput((input, key) => {
|
|
63
|
+
* if (key.tab && suggestion) {
|
|
64
|
+
* acceptSuggestion();
|
|
65
|
+
* } else {
|
|
66
|
+
* handleInput(input, key);
|
|
67
|
+
* }
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* return <Text>{displayValue}</Text>;
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function useTextInput(options?: {
|
|
75
|
+
initialValue?: string;
|
|
76
|
+
mask?: string;
|
|
77
|
+
autocomplete?: string[];
|
|
78
|
+
onSubmit?: (value: string) => void;
|
|
79
|
+
}): {
|
|
80
|
+
value: string;
|
|
81
|
+
setValue: (value: string) => void;
|
|
82
|
+
displayValue: string;
|
|
83
|
+
suggestion: string | undefined;
|
|
84
|
+
cursorPosition: number;
|
|
85
|
+
setCursorPosition: (pos: number) => void;
|
|
86
|
+
handleInput: (input: string, key: InputKey) => void;
|
|
87
|
+
acceptSuggestion: () => void;
|
|
88
|
+
clear: () => void;
|
|
89
|
+
};
|
|
90
|
+
/** Key object type (matches Ink's Key interface) */
|
|
91
|
+
interface InputKey {
|
|
92
|
+
return?: boolean;
|
|
93
|
+
backspace?: boolean;
|
|
94
|
+
delete?: boolean;
|
|
95
|
+
leftArrow?: boolean;
|
|
96
|
+
rightArrow?: boolean;
|
|
97
|
+
upArrow?: boolean;
|
|
98
|
+
downArrow?: boolean;
|
|
99
|
+
tab?: boolean;
|
|
100
|
+
escape?: boolean;
|
|
101
|
+
ctrl?: boolean;
|
|
102
|
+
meta?: boolean;
|
|
103
|
+
shift?: boolean;
|
|
104
|
+
text?: string;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region packages/ag-react/src/ui/input/Select.d.ts
|
|
108
|
+
/**
|
|
109
|
+
* Scrollable single-choice selection list
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* import { Select } from "@silvery/ag-react/ui/input";
|
|
114
|
+
*
|
|
115
|
+
* function SettingsView() {
|
|
116
|
+
* const [theme, setTheme] = useState("light");
|
|
117
|
+
*
|
|
118
|
+
* return (
|
|
119
|
+
* <Select
|
|
120
|
+
* options={[
|
|
121
|
+
* { label: "Light", value: "light" },
|
|
122
|
+
* { label: "Dark", value: "dark" },
|
|
123
|
+
* { label: "System", value: "system" },
|
|
124
|
+
* ]}
|
|
125
|
+
* value={theme}
|
|
126
|
+
* onChange={setTheme}
|
|
127
|
+
* />
|
|
128
|
+
* );
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function Select<T>({
|
|
133
|
+
options,
|
|
134
|
+
value,
|
|
135
|
+
onChange,
|
|
136
|
+
maxVisible,
|
|
137
|
+
highlightIndex: controlledHighlightIndex,
|
|
138
|
+
onHighlightChange
|
|
139
|
+
}: SelectProps<T>): React.ReactElement;
|
|
140
|
+
/**
|
|
141
|
+
* Hook for managing select state with keyboard navigation
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* function MySelect() {
|
|
146
|
+
* const options = [
|
|
147
|
+
* { label: "Option A", value: "a" },
|
|
148
|
+
* { label: "Option B", value: "b" },
|
|
149
|
+
* ];
|
|
150
|
+
*
|
|
151
|
+
* const { highlightIndex, moveUp, moveDown, select, value } = useSelect({
|
|
152
|
+
* options,
|
|
153
|
+
* initialValue: "a",
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* useInput((input, key) => {
|
|
157
|
+
* if (key.upArrow) moveUp();
|
|
158
|
+
* if (key.downArrow) moveDown();
|
|
159
|
+
* if (key.return) select();
|
|
160
|
+
* });
|
|
161
|
+
*
|
|
162
|
+
* return <Select options={options} value={value} highlightIndex={highlightIndex} />;
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function useSelect<T>({
|
|
167
|
+
options,
|
|
168
|
+
initialValue,
|
|
169
|
+
onChange
|
|
170
|
+
}: {
|
|
171
|
+
options: SelectOption<T>[];
|
|
172
|
+
initialValue?: T;
|
|
173
|
+
onChange?: (value: T) => void;
|
|
174
|
+
}): {
|
|
175
|
+
value: T | undefined;
|
|
176
|
+
highlightIndex: number;
|
|
177
|
+
moveUp: () => void;
|
|
178
|
+
moveDown: () => void;
|
|
179
|
+
select: () => void;
|
|
180
|
+
setHighlightIndex: (index: number) => void;
|
|
181
|
+
};
|
|
182
|
+
//#endregion
|
|
183
|
+
export { Select, type SelectOption, type SelectProps, TextInput, type TextInputOptions, type TextInputProps, useSelect, useTextInput };
|
|
184
|
+
//# sourceMappingURL=input.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.mts","names":[],"sources":["../../packages/ag-react/src/ui/input/TextInput.tsx","../../packages/ag-react/src/ui/input/Select.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4CgB,SAAA,CAAA;EACd,KAAA;EACA,QAAA;EACA,WAAA;EACA,IAAA;EACA,YAAA;EACA,cAAA;EACA,QAAA;EACA,cAAA;EACA;AAAA,GACC,cAAA,GAAiB,KAAA,CAAM,YAAA;AA2D1B;;;;;;;;;;;;;;;;;;;;;AAAA,iBAAgB,YAAA,CACd,OAAA;EACE,YAAA;EACA,IAAA;EACA,YAAA;EACA,QAAA,IAAY,KAAA;AAAA;EAGd,KAAA;EACA,QAAA,GAAW,KAAA;EACX,YAAA;EACA,UAAA;EACA,cAAA;EACA,iBAAA,GAAoB,GAAA;EACpB,WAAA,GAAc,KAAA,UAAe,GAAA,EAAK,QAAA;EAClC,gBAAA;EACA,KAAA;AAAA;;UA2EQ,QAAA;EACR,MAAA;EACA,SAAA;EACA,MAAA;EACA,SAAA;EACA,UAAA;EACA,OAAA;EACA,SAAA;EACA,GAAA;EACA,MAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,IAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCxLc,MAAA,GAAA,CAAA;EACd,OAAA;EACA,KAAA;EACA,QAAA;EACA,UAAA;EACA,cAAA,EAAgB,wBAAA;EAChB;AAAA,GACC,WAAA,CAAY,CAAA,IAAK,KAAA,CAAM,YAAA;;;;;;;;;;;;ADyE1B;;;;;;;;;;;;;;;iBCJgB,SAAA,GAAA,CAAA;EACd,OAAA;EACA,YAAA;EACA;AAAA;EAEA,OAAA,EAAS,YAAA,CAAa,CAAA;EACtB,YAAA,GAAe,CAAA;EACf,QAAA,IAAY,KAAA,EAAO,CAAA;AAAA;EAEnB,KAAA,EAAO,CAAA;EACP,cAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;EACA,iBAAA,GAAoB,KAAA;AAAA"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region packages/ag-react/src/ui/input/TextInput.tsx
|
|
4
|
+
/**
|
|
5
|
+
* React TextInput component for silvery/Ink TUI apps
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Single-line text input component for React TUI apps
|
|
9
|
+
*
|
|
10
|
+
* This is a controlled component that renders the current input state.
|
|
11
|
+
* It does NOT handle keyboard input directly - that's the caller's responsibility
|
|
12
|
+
* (via useInput hook in Ink, or similar).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { TextInput } from "@silvery/ag-react/ui/input";
|
|
17
|
+
* import { useInput } from "ink";
|
|
18
|
+
*
|
|
19
|
+
* function MyForm() {
|
|
20
|
+
* const [value, setValue] = useState("");
|
|
21
|
+
*
|
|
22
|
+
* useInput((input, key) => {
|
|
23
|
+
* if (key.backspace || key.delete) {
|
|
24
|
+
* setValue(v => v.slice(0, -1));
|
|
25
|
+
* } else if (!key.ctrl && !key.meta && input) {
|
|
26
|
+
* setValue(v => v + input);
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* return <TextInput value={value} onChange={setValue} placeholder="Type here..." />;
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* // With password masking
|
|
34
|
+
* <TextInput value={password} onChange={setPassword} mask="*" />
|
|
35
|
+
*
|
|
36
|
+
* // With autocomplete
|
|
37
|
+
* <TextInput
|
|
38
|
+
* value={query}
|
|
39
|
+
* onChange={setQuery}
|
|
40
|
+
* autocomplete={["apple", "apricot", "avocado"]}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
function TextInput({ value, onChange, placeholder, mask, autocomplete, onAutocomplete, onSubmit, cursorPosition, focused = true }) {
|
|
45
|
+
const displayValue = mask ? mask.repeat(value.length) : value;
|
|
46
|
+
const suggestion = getAutocompleteSuggestion(value, autocomplete);
|
|
47
|
+
const cursor = cursorPosition ?? value.length;
|
|
48
|
+
const beforeCursor = displayValue.slice(0, cursor);
|
|
49
|
+
const afterCursor = displayValue.slice(cursor);
|
|
50
|
+
const suggestionSuffix = suggestion ? suggestion.slice(value.length) : "";
|
|
51
|
+
const showPlaceholder = !value && placeholder;
|
|
52
|
+
return /* @__PURE__ */ jsx("span", {
|
|
53
|
+
"data-silvery-ui-text-input": true,
|
|
54
|
+
"data-focused": focused,
|
|
55
|
+
children: showPlaceholder ? /* @__PURE__ */ jsx("span", {
|
|
56
|
+
"data-color": "dim",
|
|
57
|
+
children: placeholder
|
|
58
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
59
|
+
/* @__PURE__ */ jsx("span", { children: beforeCursor }),
|
|
60
|
+
focused && /* @__PURE__ */ jsx("span", {
|
|
61
|
+
"data-cursor": true,
|
|
62
|
+
"data-inverse": true,
|
|
63
|
+
children: afterCursor[0] || " "
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ jsx("span", { children: afterCursor.slice(1) }),
|
|
66
|
+
suggestionSuffix && /* @__PURE__ */ jsx("span", {
|
|
67
|
+
"data-color": "dim",
|
|
68
|
+
children: suggestionSuffix
|
|
69
|
+
})
|
|
70
|
+
] })
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Hook for managing text input state with autocomplete support
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* function SearchInput() {
|
|
79
|
+
* const { value, displayValue, suggestion, handleInput, acceptSuggestion, clear } =
|
|
80
|
+
* useTextInput({ autocomplete: ["apple", "banana", "cherry"] });
|
|
81
|
+
*
|
|
82
|
+
* useInput((input, key) => {
|
|
83
|
+
* if (key.tab && suggestion) {
|
|
84
|
+
* acceptSuggestion();
|
|
85
|
+
* } else {
|
|
86
|
+
* handleInput(input, key);
|
|
87
|
+
* }
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* return <Text>{displayValue}</Text>;
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
function useTextInput(options = {}) {
|
|
95
|
+
const [value, setValue] = useState(options.initialValue ?? "");
|
|
96
|
+
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
97
|
+
const displayValue = options.mask ? options.mask.repeat(value.length) : value;
|
|
98
|
+
const suggestion = getAutocompleteSuggestion(value, options.autocomplete);
|
|
99
|
+
return {
|
|
100
|
+
value,
|
|
101
|
+
setValue,
|
|
102
|
+
displayValue,
|
|
103
|
+
suggestion,
|
|
104
|
+
cursorPosition,
|
|
105
|
+
setCursorPosition,
|
|
106
|
+
handleInput: useCallback((input, key) => {
|
|
107
|
+
if (key.return) {
|
|
108
|
+
options.onSubmit?.(value);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (key.backspace || key.delete) {
|
|
112
|
+
if (cursorPosition > 0) {
|
|
113
|
+
setValue((v) => v.slice(0, cursorPosition - 1) + v.slice(cursorPosition));
|
|
114
|
+
setCursorPosition((p) => Math.max(0, p - 1));
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key.leftArrow) {
|
|
119
|
+
setCursorPosition((p) => Math.max(0, p - 1));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (key.rightArrow) {
|
|
123
|
+
setCursorPosition((p) => Math.min(value.length, p + 1));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (key.ctrl || !input) return;
|
|
127
|
+
const char = key.text ?? input;
|
|
128
|
+
if (key.meta && !char) return;
|
|
129
|
+
setValue((v) => v.slice(0, cursorPosition) + char + v.slice(cursorPosition));
|
|
130
|
+
setCursorPosition((p) => p + char.length);
|
|
131
|
+
}, [
|
|
132
|
+
value,
|
|
133
|
+
cursorPosition,
|
|
134
|
+
options.onSubmit
|
|
135
|
+
]),
|
|
136
|
+
acceptSuggestion: useCallback(() => {
|
|
137
|
+
if (suggestion) {
|
|
138
|
+
setValue(suggestion);
|
|
139
|
+
setCursorPosition(suggestion.length);
|
|
140
|
+
}
|
|
141
|
+
}, [suggestion]),
|
|
142
|
+
clear: useCallback(() => {
|
|
143
|
+
setValue("");
|
|
144
|
+
setCursorPosition(0);
|
|
145
|
+
}, [])
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Find a matching autocomplete suggestion for the current input
|
|
150
|
+
*/
|
|
151
|
+
function getAutocompleteSuggestion(value, autocomplete) {
|
|
152
|
+
if (!value || !autocomplete?.length) return;
|
|
153
|
+
const lowerValue = value.toLowerCase();
|
|
154
|
+
return autocomplete.find((item) => item.toLowerCase().startsWith(lowerValue) && item.length > value.length);
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region packages/ag-react/src/ui/input/Select.tsx
|
|
158
|
+
/**
|
|
159
|
+
* React Select component for silvery/Ink TUI apps
|
|
160
|
+
*
|
|
161
|
+
* Single-choice selection list with keyboard navigation.
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* Scrollable single-choice selection list
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```tsx
|
|
168
|
+
* import { Select } from "@silvery/ag-react/ui/input";
|
|
169
|
+
*
|
|
170
|
+
* function SettingsView() {
|
|
171
|
+
* const [theme, setTheme] = useState("light");
|
|
172
|
+
*
|
|
173
|
+
* return (
|
|
174
|
+
* <Select
|
|
175
|
+
* options={[
|
|
176
|
+
* { label: "Light", value: "light" },
|
|
177
|
+
* { label: "Dark", value: "dark" },
|
|
178
|
+
* { label: "System", value: "system" },
|
|
179
|
+
* ]}
|
|
180
|
+
* value={theme}
|
|
181
|
+
* onChange={setTheme}
|
|
182
|
+
* />
|
|
183
|
+
* );
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
function Select({ options, value, onChange, maxVisible = 10, highlightIndex: controlledHighlightIndex, onHighlightChange }) {
|
|
188
|
+
const selectedIndex = options.findIndex((opt) => opt.value === value);
|
|
189
|
+
const [internalHighlightIndex, setInternalHighlightIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0);
|
|
190
|
+
const highlightIndex = controlledHighlightIndex ?? internalHighlightIndex;
|
|
191
|
+
const scrollOffset = Math.max(0, Math.min(highlightIndex - Math.floor(maxVisible / 2), options.length - maxVisible));
|
|
192
|
+
const visibleOptions = options.slice(scrollOffset, scrollOffset + maxVisible);
|
|
193
|
+
const hasMoreAbove = scrollOffset > 0;
|
|
194
|
+
const hasMoreBelow = scrollOffset + maxVisible < options.length;
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (controlledHighlightIndex === void 0 && selectedIndex >= 0) setInternalHighlightIndex(selectedIndex);
|
|
197
|
+
}, [selectedIndex, controlledHighlightIndex]);
|
|
198
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
199
|
+
"data-silvery-select": true,
|
|
200
|
+
children: [
|
|
201
|
+
hasMoreAbove && /* @__PURE__ */ jsx("div", {
|
|
202
|
+
"data-silvery-select-scroll-indicator": "up",
|
|
203
|
+
children: "..."
|
|
204
|
+
}),
|
|
205
|
+
visibleOptions.map((option, visibleIdx) => {
|
|
206
|
+
const actualIndex = scrollOffset + visibleIdx;
|
|
207
|
+
const isSelected = option.value === value;
|
|
208
|
+
const isHighlighted = actualIndex === highlightIndex;
|
|
209
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
210
|
+
"data-silvery-select-option": true,
|
|
211
|
+
"data-selected": isSelected,
|
|
212
|
+
"data-highlighted": isHighlighted,
|
|
213
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
214
|
+
"data-silvery-select-indicator": true,
|
|
215
|
+
children: isSelected ? ">" : " "
|
|
216
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
217
|
+
"data-silvery-select-label": true,
|
|
218
|
+
children: option.label
|
|
219
|
+
})]
|
|
220
|
+
}, actualIndex);
|
|
221
|
+
}),
|
|
222
|
+
hasMoreBelow && /* @__PURE__ */ jsx("div", {
|
|
223
|
+
"data-silvery-select-scroll-indicator": "down",
|
|
224
|
+
children: "..."
|
|
225
|
+
})
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Hook for managing select state with keyboard navigation
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```tsx
|
|
234
|
+
* function MySelect() {
|
|
235
|
+
* const options = [
|
|
236
|
+
* { label: "Option A", value: "a" },
|
|
237
|
+
* { label: "Option B", value: "b" },
|
|
238
|
+
* ];
|
|
239
|
+
*
|
|
240
|
+
* const { highlightIndex, moveUp, moveDown, select, value } = useSelect({
|
|
241
|
+
* options,
|
|
242
|
+
* initialValue: "a",
|
|
243
|
+
* });
|
|
244
|
+
*
|
|
245
|
+
* useInput((input, key) => {
|
|
246
|
+
* if (key.upArrow) moveUp();
|
|
247
|
+
* if (key.downArrow) moveDown();
|
|
248
|
+
* if (key.return) select();
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* return <Select options={options} value={value} highlightIndex={highlightIndex} />;
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
function useSelect({ options, initialValue, onChange }) {
|
|
256
|
+
const initialIndex = initialValue !== void 0 ? options.findIndex((opt) => opt.value === initialValue) : 0;
|
|
257
|
+
const [highlightIndex, setHighlightIndex] = useState(Math.max(0, initialIndex));
|
|
258
|
+
const [value, setValue] = useState(initialValue);
|
|
259
|
+
return {
|
|
260
|
+
value,
|
|
261
|
+
highlightIndex,
|
|
262
|
+
moveUp: useCallback(() => {
|
|
263
|
+
setHighlightIndex((i) => Math.max(0, i - 1));
|
|
264
|
+
}, []),
|
|
265
|
+
moveDown: useCallback(() => {
|
|
266
|
+
setHighlightIndex((i) => Math.min(options.length - 1, i + 1));
|
|
267
|
+
}, [options.length]),
|
|
268
|
+
select: useCallback(() => {
|
|
269
|
+
const option = options[highlightIndex];
|
|
270
|
+
if (option) {
|
|
271
|
+
setValue(option.value);
|
|
272
|
+
onChange?.(option.value);
|
|
273
|
+
}
|
|
274
|
+
}, [
|
|
275
|
+
highlightIndex,
|
|
276
|
+
options,
|
|
277
|
+
onChange
|
|
278
|
+
]),
|
|
279
|
+
setHighlightIndex
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
export { Select, TextInput, useSelect, useTextInput };
|
|
284
|
+
|
|
285
|
+
//# sourceMappingURL=input.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.mjs","names":[],"sources":["../../packages/ag-react/src/ui/input/TextInput.tsx","../../packages/ag-react/src/ui/input/Select.tsx"],"sourcesContent":["/**\n * React TextInput component for silvery/Ink TUI apps\n */\n\nimport React, { useState, useCallback } from \"react\"\nimport type { TextInputProps } from \"../types.js\"\n\n/**\n * Single-line text input component for React TUI apps\n *\n * This is a controlled component that renders the current input state.\n * It does NOT handle keyboard input directly - that's the caller's responsibility\n * (via useInput hook in Ink, or similar).\n *\n * @example\n * ```tsx\n * import { TextInput } from \"@silvery/ag-react/ui/input\";\n * import { useInput } from \"ink\";\n *\n * function MyForm() {\n * const [value, setValue] = useState(\"\");\n *\n * useInput((input, key) => {\n * if (key.backspace || key.delete) {\n * setValue(v => v.slice(0, -1));\n * } else if (!key.ctrl && !key.meta && input) {\n * setValue(v => v + input);\n * }\n * });\n *\n * return <TextInput value={value} onChange={setValue} placeholder=\"Type here...\" />;\n * }\n *\n * // With password masking\n * <TextInput value={password} onChange={setPassword} mask=\"*\" />\n *\n * // With autocomplete\n * <TextInput\n * value={query}\n * onChange={setQuery}\n * autocomplete={[\"apple\", \"apricot\", \"avocado\"]}\n * />\n * ```\n */\nexport function TextInput({\n value,\n onChange,\n placeholder,\n mask,\n autocomplete,\n onAutocomplete,\n onSubmit,\n cursorPosition,\n focused = true,\n}: TextInputProps): React.ReactElement {\n // Calculate display value (masked or plain)\n const displayValue = mask ? mask.repeat(value.length) : value\n\n // Find matching autocomplete suggestion\n const suggestion = getAutocompleteSuggestion(value, autocomplete)\n\n // Cursor position defaults to end of input\n const cursor = cursorPosition ?? value.length\n\n // Build the display: value + cursor + suggestion suffix\n const beforeCursor = displayValue.slice(0, cursor)\n const afterCursor = displayValue.slice(cursor)\n const suggestionSuffix = suggestion ? suggestion.slice(value.length) : \"\"\n\n // Show placeholder if empty and not focused or no value\n const showPlaceholder = !value && placeholder\n\n return (\n <span data-silvery-ui-text-input data-focused={focused}>\n {showPlaceholder ? (\n <span data-color=\"dim\">{placeholder}</span>\n ) : (\n <>\n <span>{beforeCursor}</span>\n {focused && (\n <span data-cursor data-inverse>\n {afterCursor[0] || \" \"}\n </span>\n )}\n <span>{afterCursor.slice(1)}</span>\n {suggestionSuffix && <span data-color=\"dim\">{suggestionSuffix}</span>}\n </>\n )}\n </span>\n )\n}\n\n/**\n * Hook for managing text input state with autocomplete support\n *\n * @example\n * ```tsx\n * function SearchInput() {\n * const { value, displayValue, suggestion, handleInput, acceptSuggestion, clear } =\n * useTextInput({ autocomplete: [\"apple\", \"banana\", \"cherry\"] });\n *\n * useInput((input, key) => {\n * if (key.tab && suggestion) {\n * acceptSuggestion();\n * } else {\n * handleInput(input, key);\n * }\n * });\n *\n * return <Text>{displayValue}</Text>;\n * }\n * ```\n */\nexport function useTextInput(\n options: {\n initialValue?: string\n mask?: string\n autocomplete?: string[]\n onSubmit?: (value: string) => void\n } = {},\n): {\n value: string\n setValue: (value: string) => void\n displayValue: string\n suggestion: string | undefined\n cursorPosition: number\n setCursorPosition: (pos: number) => void\n handleInput: (input: string, key: InputKey) => void\n acceptSuggestion: () => void\n clear: () => void\n} {\n const [value, setValue] = useState(options.initialValue ?? \"\")\n const [cursorPosition, setCursorPosition] = useState(value.length)\n\n const displayValue = options.mask ? options.mask.repeat(value.length) : value\n\n const suggestion = getAutocompleteSuggestion(value, options.autocomplete)\n\n const handleInput = useCallback(\n (input: string, key: InputKey) => {\n if (key.return) {\n options.onSubmit?.(value)\n return\n }\n\n if (key.backspace || key.delete) {\n if (cursorPosition > 0) {\n setValue((v) => v.slice(0, cursorPosition - 1) + v.slice(cursorPosition))\n setCursorPosition((p) => Math.max(0, p - 1))\n }\n return\n }\n\n if (key.leftArrow) {\n setCursorPosition((p) => Math.max(0, p - 1))\n return\n }\n\n if (key.rightArrow) {\n setCursorPosition((p) => Math.min(value.length, p + 1))\n return\n }\n\n // Ignore control characters (but allow opt+key composed chars via key.text)\n if (key.ctrl || !input) {\n return\n }\n\n // Insert the actual typed character (pre-normalization), not the keybinding key.\n // E.g., Shift+3 should insert '#', not '3'.\n const char = key.text ?? input\n if (key.meta && !char) return // opt+key with no text output\n setValue((v) => v.slice(0, cursorPosition) + char + v.slice(cursorPosition))\n setCursorPosition((p) => p + char.length)\n },\n [value, cursorPosition, options.onSubmit],\n )\n\n const acceptSuggestion = useCallback(() => {\n if (suggestion) {\n setValue(suggestion)\n setCursorPosition(suggestion.length)\n }\n }, [suggestion])\n\n const clear = useCallback(() => {\n setValue(\"\")\n setCursorPosition(0)\n }, [])\n\n return {\n value,\n setValue,\n displayValue,\n suggestion,\n cursorPosition,\n setCursorPosition,\n handleInput,\n acceptSuggestion,\n clear,\n }\n}\n\n/** Key object type (matches Ink's Key interface) */\ninterface InputKey {\n return?: boolean\n backspace?: boolean\n delete?: boolean\n leftArrow?: boolean\n rightArrow?: boolean\n upArrow?: boolean\n downArrow?: boolean\n tab?: boolean\n escape?: boolean\n ctrl?: boolean\n meta?: boolean\n shift?: boolean\n text?: string\n}\n\n/**\n * Find a matching autocomplete suggestion for the current input\n */\nfunction getAutocompleteSuggestion(value: string, autocomplete?: string[]): string | undefined {\n if (!value || !autocomplete?.length) {\n return undefined\n }\n\n const lowerValue = value.toLowerCase()\n return autocomplete.find((item) => item.toLowerCase().startsWith(lowerValue) && item.length > value.length)\n}\n","/**\n * React Select component for silvery/Ink TUI apps\n *\n * Single-choice selection list with keyboard navigation.\n */\n\nimport React, { useState, useEffect, useCallback } from \"react\"\nimport type { SelectProps, SelectOption } from \"../types.js\"\n\n/**\n * Scrollable single-choice selection list\n *\n * @example\n * ```tsx\n * import { Select } from \"@silvery/ag-react/ui/input\";\n *\n * function SettingsView() {\n * const [theme, setTheme] = useState(\"light\");\n *\n * return (\n * <Select\n * options={[\n * { label: \"Light\", value: \"light\" },\n * { label: \"Dark\", value: \"dark\" },\n * { label: \"System\", value: \"system\" },\n * ]}\n * value={theme}\n * onChange={setTheme}\n * />\n * );\n * }\n * ```\n */\nexport function Select<T>({\n options,\n value,\n onChange,\n maxVisible = 10,\n highlightIndex: controlledHighlightIndex,\n onHighlightChange,\n}: SelectProps<T>): React.ReactElement {\n // Find the index of the currently selected value\n const selectedIndex = options.findIndex((opt) => opt.value === value)\n\n // Internal highlight state (for uncontrolled mode)\n const [internalHighlightIndex, setInternalHighlightIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0)\n\n // Use controlled or internal highlight index\n const highlightIndex = controlledHighlightIndex ?? internalHighlightIndex\n\n // Calculate scroll window\n const scrollOffset = Math.max(0, Math.min(highlightIndex - Math.floor(maxVisible / 2), options.length - maxVisible))\n const visibleOptions = options.slice(scrollOffset, scrollOffset + maxVisible)\n const hasMoreAbove = scrollOffset > 0\n const hasMoreBelow = scrollOffset + maxVisible < options.length\n\n // Sync internal highlight when value changes externally\n useEffect(() => {\n if (controlledHighlightIndex === undefined && selectedIndex >= 0) {\n setInternalHighlightIndex(selectedIndex)\n }\n }, [selectedIndex, controlledHighlightIndex])\n\n return (\n <div data-silvery-select>\n {hasMoreAbove && <div data-silvery-select-scroll-indicator=\"up\">...</div>}\n {visibleOptions.map((option, visibleIdx) => {\n const actualIndex = scrollOffset + visibleIdx\n const isSelected = option.value === value\n const isHighlighted = actualIndex === highlightIndex\n\n return (\n <div key={actualIndex} data-silvery-select-option data-selected={isSelected} data-highlighted={isHighlighted}>\n <span data-silvery-select-indicator>{isSelected ? \">\" : \" \"}</span>\n <span data-silvery-select-label>{option.label}</span>\n </div>\n )\n })}\n {hasMoreBelow && <div data-silvery-select-scroll-indicator=\"down\">...</div>}\n </div>\n )\n}\n\n/**\n * Hook for managing select state with keyboard navigation\n *\n * @example\n * ```tsx\n * function MySelect() {\n * const options = [\n * { label: \"Option A\", value: \"a\" },\n * { label: \"Option B\", value: \"b\" },\n * ];\n *\n * const { highlightIndex, moveUp, moveDown, select, value } = useSelect({\n * options,\n * initialValue: \"a\",\n * });\n *\n * useInput((input, key) => {\n * if (key.upArrow) moveUp();\n * if (key.downArrow) moveDown();\n * if (key.return) select();\n * });\n *\n * return <Select options={options} value={value} highlightIndex={highlightIndex} />;\n * }\n * ```\n */\nexport function useSelect<T>({\n options,\n initialValue,\n onChange,\n}: {\n options: SelectOption<T>[]\n initialValue?: T\n onChange?: (value: T) => void\n}): {\n value: T | undefined\n highlightIndex: number\n moveUp: () => void\n moveDown: () => void\n select: () => void\n setHighlightIndex: (index: number) => void\n} {\n const initialIndex = initialValue !== undefined ? options.findIndex((opt) => opt.value === initialValue) : 0\n\n const [highlightIndex, setHighlightIndex] = useState(Math.max(0, initialIndex))\n const [value, setValue] = useState<T | undefined>(initialValue)\n\n const moveUp = useCallback(() => {\n setHighlightIndex((i) => Math.max(0, i - 1))\n }, [])\n\n const moveDown = useCallback(() => {\n setHighlightIndex((i) => Math.min(options.length - 1, i + 1))\n }, [options.length])\n\n const select = useCallback(() => {\n const option = options[highlightIndex]\n if (option) {\n setValue(option.value)\n onChange?.(option.value)\n }\n }, [highlightIndex, options, onChange])\n\n return {\n value,\n highlightIndex,\n moveUp,\n moveDown,\n select,\n setHighlightIndex,\n }\n}\n"],"mappings":"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"}
|