{"version":3,"file":"input-frame.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/input-frame.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,KAAK,gBAAgB,EAAQ,MAAM,0BAA0B,CAAC;AAe9F;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAEjE;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED,MAAM,WAAW,iBAAiB;IACjC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACpC,OAAO,CAAC,OAAO,CAAC,CAAO;IACvB,sFAAsF;IACtF,OAAO,CAAC,SAAS,CAAM;IACvB,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAO;IAExB,YAAY,OAAO,GAAE,iBAAsB,EAO1C;IAED;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE5B;IAED;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,WAAW;IAqBnB;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ1B;IAED;;;;;;OAMG;IACM,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAS5C;IAED,kEAAkE;IAClE,OAAO,CAAC,eAAe;IAKd,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvC;CACD","sourcesContent":["/**\n * The chrome every surface that asks the user for something draws.\n *\n * All of them — the pickers, the `ask_options` pane, the extension input and\n * editor, the login dialog — take the prompt editor's place in\n * `editorContainer`. They used to draw two bare `DynamicBorder` rules instead\n * of the prompt's box, each with its own idea of whether the title went inside,\n * whether the hints got a blank line above them, and whether content was inset\n * at all. So the one surface the eye returns to changed shape whenever the\n * agent needed an answer, and the `editorBorder` setting moved the prompt\n * without moving anything that stands in for it.\n *\n * One frame, one border style, one place the title goes (into the top border,\n * flush right, the slot the session chip rides on the prompt) and one place the\n * hints go (the last row inside, flush above the bottom rule — a blank line\n * next to a rule is a blank line wasted).\n */\n\nimport { type Component, Frame, type FrameBorderStyle, Text } from \"@kolisachint/hoocode-tui\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * The border style the prompt is currently drawing, so a surface standing in\n * for it draws the same one.\n *\n * Module state rather than a constructor argument because these components are\n * built deep inside command handlers that have no business knowing about\n * settings, and because it has to follow a live `/settings` edit for panes that\n * are already on screen. `interactive-mode` pushes it in `applyRuntimeSettings`\n * alongside `editor.setBorder`, and from the settings callback.\n */\nlet inputBorderStyle: FrameBorderStyle = \"box\";\n\n/**\n * Not exported from the package: this follows the user's `editorBorder`\n * setting, and an extension does not get to overrule it. `interactive-mode` is\n * the one caller.\n */\nexport function setInputFrameBorder(style: FrameBorderStyle): void {\n\tinputBorderStyle = style;\n}\n\nexport function getInputFrameBorder(): FrameBorderStyle {\n\treturn inputBorderStyle;\n}\n\nexport interface InputFrameOptions {\n\t/** The surface's name. Laid into the top border when it fits there. */\n\ttitle?: string;\n\t/** Columns of gutter inside the side borders. One, as the prompt's is. */\n\tpaddingX?: number;\n}\n\n/**\n * A `Frame` wearing the app's border colour and the current border style.\n *\n * The colour is the plain `border` token, not the prompt's: the prompt's border\n * carries the thinking level and bash mode, and a picker has neither to report.\n *\n * The colour, the style and the title are all resolved per frame, so a theme\n * switch or a `/settings` edit repaints a pane that is already open — a `Text`\n * holds its string with the escapes already in it, so a title decided once\n * would keep the colours of the theme it was built under. Re-resolving is\n * cheap: `setBorder` and `setLabel` both compare before they take, so an\n * unchanged frame still returns the same array and the renderer goes on\n * diffing whole regions by identity.\n */\nexport class InputFrame extends Frame {\n\tprivate hintRow?: Text;\n\t/** The title as one line, or \"\" for none. Where it is drawn is a render-time call. */\n\tprivate titleText = \"\";\n\t/** Holds the title when it will not fit in the border. */\n\tprivate titleRow?: Text;\n\n\tconstructor(options: InputFrameOptions = {}) {\n\t\tsuper({\n\t\t\tborder: getInputFrameBorder(),\n\t\t\tpaddingX: options.paddingX ?? 1,\n\t\t\tcolor: (str: string) => theme.fg(\"border\", str),\n\t\t});\n\t\tif (options.title !== undefined) this.setTitle(options.title);\n\t}\n\n\t/**\n\t * Name this surface. An empty title draws a plain edge.\n\t *\n\t * Collapsed to one line first, because the border *is* one line: an\n\t * extension's `confirm` passes its question and its detail as one\n\t * newline-joined string, and a newline inside a rendered row splits the\n\t * border open and throws the renderer's row count out with it.\n\t *\n\t * Where it ends up is decided per frame in `layOutTitle`, not here — it\n\t * depends on the width, which a caller does not know.\n\t */\n\tsetTitle(title: string): void {\n\t\tthis.titleText = title.replace(/\\s+/g, \" \").trim();\n\t}\n\n\t/**\n\t * Put the title where it fits: in the top border, or — when the border has\n\t * no room for a run of rule beside it — on the frame's first row.\n\t *\n\t * The fallback is what stops a title being lost. `renderFrameEdge` drops a\n\t * label it cannot draw, which is right for the session chip (the footer also\n\t * names the session) and wrong for a question the user is about to answer\n\t * yes or no to.\n\t *\n\t * Rebuilt every frame rather than at `setTitle`, so a theme switch under an\n\t * open pane repaints the title along with everything else: a `Text` holds\n\t * its string with the escapes already in it.\n\t */\n\tprivate layOutTitle(width: number): void {\n\t\tconst plain = this.titleText ? ` ${this.titleText} ` : \"\";\n\t\t// The chip's own spacing, so a title sits off the rule the way the\n\t\t// session name does on the prompt.\n\t\tconst inBorder = this.labelFits(plain, width);\n\t\tthis.setLabel(inBorder ? { plain, styled: theme.fg(\"accent\", theme.bold(plain)) } : undefined);\n\n\t\tif (inBorder || !this.titleText) {\n\t\t\tif (this.titleRow) {\n\t\t\t\tthis.removeChild(this.titleRow);\n\t\t\t\tthis.titleRow = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (!this.titleRow) {\n\t\t\tthis.titleRow = new Text(\"\", 0, 0);\n\t\t\tthis.unshiftTitleRow(this.titleRow);\n\t\t}\n\t\tthis.titleRow.setText(theme.fg(\"accent\", theme.bold(this.titleText)));\n\t}\n\n\t/**\n\t * The key hints, as the last row inside the frame.\n\t *\n\t * Owned here rather than added as a child by each caller so it always ends\n\t * up in the same place: callers that added it themselves put it above their\n\t * own `Spacer(1)` about half the time, which is the wasted row the vertical\n\t * rhythm rules name.\n\t */\n\tsetHint(hint: string): void {\n\t\tif (this.hintRow) {\n\t\t\tthis.hintRow.setText(hint);\n\t\t\treturn;\n\t\t}\n\t\tconst row = new Text(hint, 0, 0);\n\t\tsuper.addChild(row);\n\t\tthis.hintRow = row;\n\t}\n\n\t/**\n\t * Add a row above the hints.\n\t *\n\t * The hints are the frame's last row by construction, so a pane that sets\n\t * them before it finishes building its body still gets them at the bottom —\n\t * which is the ordering half the panes got wrong when each added its own.\n\t */\n\toverride addChild(component: Component): void {\n\t\tif (!this.hintRow) {\n\t\t\tsuper.addChild(component);\n\t\t\treturn;\n\t\t}\n\t\tconst hint = this.hintRow;\n\t\tthis.removeChild(hint);\n\t\tsuper.addChild(component);\n\t\tsuper.addChild(hint);\n\t}\n\n\t/** The title row, when there is one, is the frame's first row. */\n\tprivate unshiftTitleRow(row: Text): void {\n\t\tthis.children.unshift(row);\n\t\tthis.invalidate();\n\t}\n\n\toverride render(width: number): string[] {\n\t\t// The style is read per frame: a `/settings` edit has to reach a pane\n\t\t// that is already open, and the pane is what the user is looking at.\n\t\tthis.setBorder(getInputFrameBorder());\n\t\tthis.layOutTitle(width);\n\t\treturn super.render(width);\n\t}\n}\n"]}