paintcannon-react
v0.0.15
Published
A React reconciler for `paintcannon`, built for fast terminal UIs.
Readme
paintcannon-react
A React reconciler for paintcannon, built for fast terminal UIs.
paintcannon-react lets you render React components into PaintCannon's
Rust-backed terminal renderer. It is intended as a faster, no-flicker
alternative for terminal React interfaces that would otherwise use Ink.

Installation
paintcannon and react are peer dependencies, so install them alongside the
reconciler:
npm install --save paintcannon paintcannon-react reactUsage
import React, { useState } from "react";
import { Button, Div, Span, render } from "paintcannon-react";
function Counter(): React.ReactElement {
const [count, setCount] = useState(0);
return (
<Div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 1,
backgroundColor: "#020617",
color: "#e2e8f0",
}}
>
<Span style={{ color: "#38bdf8" }}>paintcannon-react</Span>
<Button
style={{
border: "chunky-rounded",
borderColor: "#fb923c",
backgroundColor: count % 2 === 0 ? "#0f172a" : "#7c2d12",
color: "#f8fafc",
padding: "1 2",
cursor: "pointer",
}}
onClick={() => {
setCount(value => value + 1);
}}
>
Clicked {count} times
</Button>
</Div>
);
}
const app = render(<Counter />, {
alternateScreen: true,
captureMouse: true,
});
await app.waitUntilExit();API differences from Ink
PaintCannon-React isn't 100% drop-in compatible with Ink: the primary
difference is that PaintCannon expects you to use PaintCannon's native
<input> and <textarea> components for input, rather than relying on custom
JS reimplementations of input handling on top of React. Although this is
different thank Ink, this comes with a significant performance win: typing in
PaintCannon is much lower latency as a result.
PaintCannon is also somewhat less restrictive thank Ink: you aren't required to
wrap text content in <Text> nodes. Text content Just Works inside PaintCannon
components, just like with a regular browser.
Host Components
PaintCannon exposes the following host components that mirror a subset of the DOM API:
DivSpanInputTextareaButtonForm
These components map to PaintCannon's DOM-like core API and support typed style props, bubbling events, forms, focus handling, controlled inputs, and mouse interactions. The subset of the React-DOM props they support is as follows:
Host component onFocus and onBlur are element focus events. Terminal
window/pane focus is exposed by the underlying PaintCannon instance as
paintCannon.hasFocus and app-level focus/blur events. The root returned
from render() includes that instance:
const app = render(<App />, { alternateScreen: true });
app.paintCannon.addEventListener("blur", () => {
// Dim or pause the app while the terminal is unfocused.
});Inside tmux, terminal focus reporting requires set -g focus-events on.
Common props:
All host components accept:
children?: React.ReactNodestyle?: CSSStylePropertiesref?: React.Ref<Element>
All host components accept these event props:
onKeyDownonKeyUponClickonMouseEnteronMouseLeaveonMouseMoveonFocusonBluronSubmitonChangeonTransitionStartonTransitionEndonScroll
style accepts the following CSS property names. Kebab-case and camelCase are
both supported:
displayvisibility(accepts"visible"or"hidden"; hidden elements keep their layout space but do not paint or receive hit tests)overflowoverflow-x/overflowXoverflow-y/overflowYscrollbar-color/scrollbarColorscrollbar-gutter/scrollbarGutterimage-rendering/imageRenderingflex-direction/flexDirectionflex-wrap/flexWrapflex-flow/flexFlowflex-basis/flexBasisflex-grow/flexGrowflex-shrink/flexShrinkflexjustify-content/justifyContentalign-items/alignItemsalign-self/alignSelfalign-content/alignContentjustify-items/justifyItemsjustify-self/justifySelfgaprow-gap/rowGapcolumn-gap/columnGappaddingpadding-top/paddingToppadding-right/paddingRightpadding-bottom/paddingBottompadding-left/paddingLeftmarginmargin-top/marginTopmargin-right/marginRightmargin-bottom/marginBottommargin-left/marginLeftwidthheightmin-height/minHeightmax-height/maxHeightwhite-space/whiteSpaceborderborder-top/borderTopborder-right/borderRightborder-bottom/borderBottomborder-left/borderLeftborder-color/borderColorcolorplaceholder-color/placeholderColortransitionbackgroundbackground-color/backgroundColorselection-background-color/selectionBackgroundColorcursorgrid-template-columns/gridTemplateColumnsgrid-template-rows/gridTemplateRowsgrid-auto-columns/gridAutoColumnsgrid-auto-rows/gridAutoRowsgrid-auto-flow/gridAutoFlowgrid-column/gridColumngrid-row/gridRowgrid-column-start/gridColumnStartgrid-column-end/gridColumnEndgrid-row-start/gridRowStartgrid-row-end/gridRowEndfont-style/fontStyle(accepts"italic"or"normal")font-weight/fontWeight(accepts"bold"or"normal")text-decoration/textDecoration(accepts"underline"or"none")
Per-component props:
Div: shared props, plusscrollLeft?: numberandscrollTop?: number.Span: shared props, plusscrollLeft?: numberandscrollTop?: number.Form: shared props, plusscrollLeft?: numberandscrollTop?: number.Button: shared props, plustype?: "submit" | "button",scrollLeft?: number, andscrollTop?: number.Input: shared props, plustype?: "text",value?: string,placeholder?: string,cursorPosition?: number, andautoFocus?: boolean.Textarea: shared props, plusvalue?: string,placeholder?: string,cursorPosition?: number,autoFocus?: boolean,scrollLeft?: number, andscrollTop?: number.Textareadoes not accept atypeprop.
Hooks
paintcannon-react includes:
useApp()for exiting the rendered app from inside React.useAnimation()for requestAnimationFrame-driven animations that share a single render cycle.
Core Runtime
This package depends on paintcannon, which provides the NAPI-RS native
renderer and DOM-like API.
