paintcannon-react 0.0.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 +213 -0
- package/dist/src/hooks/app-context.d.ts +8 -0
- package/dist/src/hooks/app-context.js +2 -0
- package/dist/src/hooks/index.d.ts +2 -0
- package/dist/src/hooks/index.js +2 -0
- package/dist/src/hooks/use-animation.d.ts +35 -0
- package/dist/src/hooks/use-animation.js +120 -0
- package/dist/src/hooks/use-app.d.ts +9 -0
- package/dist/src/hooks/use-app.js +9 -0
- package/dist/src/host-components/button/button.d.ts +8 -0
- package/dist/src/host-components/button/button.js +3 -0
- package/dist/src/host-components/button/reconcile-button.d.ts +6 -0
- package/dist/src/host-components/button/reconcile-button.js +14 -0
- package/dist/src/host-components/button.d.ts +8 -0
- package/dist/src/host-components/button.js +3 -0
- package/dist/src/host-components/div/div.d.ts +6 -0
- package/dist/src/host-components/div/div.js +3 -0
- package/dist/src/host-components/div/reconcile-div.d.ts +6 -0
- package/dist/src/host-components/div/reconcile-div.js +10 -0
- package/dist/src/host-components/div.d.ts +6 -0
- package/dist/src/host-components/div.js +3 -0
- package/dist/src/host-components/form/form.d.ts +6 -0
- package/dist/src/host-components/form/form.js +3 -0
- package/dist/src/host-components/form/reconcile-form.d.ts +6 -0
- package/dist/src/host-components/form/reconcile-form.js +10 -0
- package/dist/src/host-components/form.d.ts +6 -0
- package/dist/src/host-components/form.js +3 -0
- package/dist/src/host-components/index.d.ts +14 -0
- package/dist/src/host-components/index.js +14 -0
- package/dist/src/host-components/input/input.d.ts +8 -0
- package/dist/src/host-components/input/input.js +3 -0
- package/dist/src/host-components/input/reconcile-input.d.ts +6 -0
- package/dist/src/host-components/input/reconcile-input.js +14 -0
- package/dist/src/host-components/input.d.ts +8 -0
- package/dist/src/host-components/input.js +3 -0
- package/dist/src/host-components/mounted.d.ts +38 -0
- package/dist/src/host-components/mounted.js +17 -0
- package/dist/src/host-components/reconcile-shared.d.ts +8 -0
- package/dist/src/host-components/reconcile-shared.js +26 -0
- package/dist/src/host-components/shared.d.ts +40 -0
- package/dist/src/host-components/shared.js +18 -0
- package/dist/src/host-components/span/reconcile-span.d.ts +6 -0
- package/dist/src/host-components/span/reconcile-span.js +10 -0
- package/dist/src/host-components/span/span.d.ts +6 -0
- package/dist/src/host-components/span/span.js +3 -0
- package/dist/src/host-components/span.d.ts +6 -0
- package/dist/src/host-components/span.js +3 -0
- package/dist/src/host-components/textarea/reconcile-textarea.d.ts +6 -0
- package/dist/src/host-components/textarea/reconcile-textarea.js +12 -0
- package/dist/src/host-components/textarea/textarea.d.ts +8 -0
- package/dist/src/host-components/textarea/textarea.js +3 -0
- package/dist/src/host-components/textarea.d.ts +8 -0
- package/dist/src/host-components/textarea.js +3 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +9 -0
- package/dist/src/reconciler.d.ts +17 -0
- package/dist/src/reconciler.js +403 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# paintcannon-react
|
|
2
|
+
|
|
3
|
+
A React reconciler for `paintcannon`, built for fast terminal UIs.
|
|
4
|
+
|
|
5
|
+
`paintcannon-react` lets you render React components into PaintCannon's
|
|
6
|
+
Rust-backed terminal renderer. It is intended as a faster, no-flicker
|
|
7
|
+
alternative for terminal React interfaces that would otherwise use Ink.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
`paintcannon` and `react` are peer dependencies, so install them alongside the
|
|
14
|
+
reconciler:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install --save paintcannon paintcannon-react react
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import React, { useState } from "react";
|
|
24
|
+
import { Button, Div, Span, render } from "paintcannon-react";
|
|
25
|
+
|
|
26
|
+
function Counter(): React.ReactElement {
|
|
27
|
+
const [count, setCount] = useState(0);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Div
|
|
31
|
+
style={{
|
|
32
|
+
width: "100%",
|
|
33
|
+
height: "100%",
|
|
34
|
+
display: "flex",
|
|
35
|
+
flexDirection: "column",
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
justifyContent: "center",
|
|
38
|
+
gap: 1,
|
|
39
|
+
backgroundColor: "#020617",
|
|
40
|
+
color: "#e2e8f0",
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<Span style={{ color: "#38bdf8" }}>paintcannon-react</Span>
|
|
44
|
+
<Button
|
|
45
|
+
style={{
|
|
46
|
+
border: "chunky-rounded",
|
|
47
|
+
borderColor: "#fb923c",
|
|
48
|
+
backgroundColor: count % 2 === 0 ? "#0f172a" : "#7c2d12",
|
|
49
|
+
color: "#f8fafc",
|
|
50
|
+
padding: "1 2",
|
|
51
|
+
cursor: "pointer",
|
|
52
|
+
}}
|
|
53
|
+
onClick={() => {
|
|
54
|
+
setCount(value => value + 1);
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
Clicked {count} times
|
|
58
|
+
</Button>
|
|
59
|
+
</Div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const app = render(<Counter />, {
|
|
64
|
+
alternateScreen: true,
|
|
65
|
+
captureMouse: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await app.waitUntilExit();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API differences from Ink
|
|
72
|
+
|
|
73
|
+
PaintCannon-React isn't 100% drop-in compatible with Ink: the primary
|
|
74
|
+
difference is that PaintCannon expects you to use PaintCannon's native
|
|
75
|
+
`<input>` and `<textarea>` components for input, rather than relying on custom
|
|
76
|
+
JS reimplementations of input handling on top of React. Although this is
|
|
77
|
+
_different_ thank Ink, this comes with a significant performance win: typing in
|
|
78
|
+
PaintCannon is much lower latency as a result.
|
|
79
|
+
|
|
80
|
+
PaintCannon is also somewhat less restrictive thank Ink: you aren't required to
|
|
81
|
+
wrap text content in `<Text>` nodes. Text content Just Works inside PaintCannon
|
|
82
|
+
components, just like with a regular browser.
|
|
83
|
+
|
|
84
|
+
## Host Components
|
|
85
|
+
|
|
86
|
+
PaintCannon exposes the following host components that mirror a subset of the
|
|
87
|
+
DOM API:
|
|
88
|
+
|
|
89
|
+
- `Div`
|
|
90
|
+
- `Span`
|
|
91
|
+
- `Input`
|
|
92
|
+
- `Textarea`
|
|
93
|
+
- `Button`
|
|
94
|
+
- `Form`
|
|
95
|
+
|
|
96
|
+
These components map to PaintCannon's DOM-like core API and support typed style
|
|
97
|
+
props, bubbling events, forms, focus handling, controlled inputs, and mouse
|
|
98
|
+
interactions. The subset of the React-DOM props they support is as follows:
|
|
99
|
+
|
|
100
|
+
### Common props:
|
|
101
|
+
|
|
102
|
+
All host components accept:
|
|
103
|
+
|
|
104
|
+
- `children?: React.ReactNode`
|
|
105
|
+
- `style?: CSSStyleProperties`
|
|
106
|
+
- `ref?: React.Ref<Element>`
|
|
107
|
+
|
|
108
|
+
All host components accept these event props:
|
|
109
|
+
|
|
110
|
+
- `onKeyDown`
|
|
111
|
+
- `onKeyUp`
|
|
112
|
+
- `onClick`
|
|
113
|
+
- `onMouseEnter`
|
|
114
|
+
- `onMouseLeave`
|
|
115
|
+
- `onMouseMove`
|
|
116
|
+
- `onFocus`
|
|
117
|
+
- `onBlur`
|
|
118
|
+
- `onSubmit`
|
|
119
|
+
- `onChange`
|
|
120
|
+
- `onTransitionStart`
|
|
121
|
+
- `onTransitionEnd`
|
|
122
|
+
- `onScroll`
|
|
123
|
+
|
|
124
|
+
`style` accepts the following CSS property names. Kebab-case and camelCase are
|
|
125
|
+
both supported:
|
|
126
|
+
|
|
127
|
+
- `display`
|
|
128
|
+
- `overflow`
|
|
129
|
+
- `overflow-x` / `overflowX`
|
|
130
|
+
- `overflow-y` / `overflowY`
|
|
131
|
+
- `image-rendering` / `imageRendering`
|
|
132
|
+
- `flex-direction` / `flexDirection`
|
|
133
|
+
- `flex-wrap` / `flexWrap`
|
|
134
|
+
- `flex-flow` / `flexFlow`
|
|
135
|
+
- `flex-basis` / `flexBasis`
|
|
136
|
+
- `flex-grow` / `flexGrow`
|
|
137
|
+
- `flex-shrink` / `flexShrink`
|
|
138
|
+
- `flex`
|
|
139
|
+
- `justify-content` / `justifyContent`
|
|
140
|
+
- `align-items` / `alignItems`
|
|
141
|
+
- `align-self` / `alignSelf`
|
|
142
|
+
- `align-content` / `alignContent`
|
|
143
|
+
- `justify-items` / `justifyItems`
|
|
144
|
+
- `justify-self` / `justifySelf`
|
|
145
|
+
- `gap`
|
|
146
|
+
- `row-gap` / `rowGap`
|
|
147
|
+
- `column-gap` / `columnGap`
|
|
148
|
+
- `padding`
|
|
149
|
+
- `padding-top` / `paddingTop`
|
|
150
|
+
- `padding-right` / `paddingRight`
|
|
151
|
+
- `padding-bottom` / `paddingBottom`
|
|
152
|
+
- `padding-left` / `paddingLeft`
|
|
153
|
+
- `margin`
|
|
154
|
+
- `margin-top` / `marginTop`
|
|
155
|
+
- `margin-right` / `marginRight`
|
|
156
|
+
- `margin-bottom` / `marginBottom`
|
|
157
|
+
- `margin-left` / `marginLeft`
|
|
158
|
+
- `width`
|
|
159
|
+
- `height`
|
|
160
|
+
- `min-height` / `minHeight`
|
|
161
|
+
- `max-height` / `maxHeight`
|
|
162
|
+
- `white-space` / `whiteSpace`
|
|
163
|
+
- `border`
|
|
164
|
+
- `border-top` / `borderTop`
|
|
165
|
+
- `border-right` / `borderRight`
|
|
166
|
+
- `border-bottom` / `borderBottom`
|
|
167
|
+
- `border-left` / `borderLeft`
|
|
168
|
+
- `border-color` / `borderColor`
|
|
169
|
+
- `color`
|
|
170
|
+
- `placeholder-color` / `placeholderColor`
|
|
171
|
+
- `transition`
|
|
172
|
+
- `background`
|
|
173
|
+
- `background-color` / `backgroundColor`
|
|
174
|
+
- `selection-background-color` / `selectionBackgroundColor`
|
|
175
|
+
- `cursor`
|
|
176
|
+
- `grid-template-columns` / `gridTemplateColumns`
|
|
177
|
+
- `grid-template-rows` / `gridTemplateRows`
|
|
178
|
+
- `grid-auto-columns` / `gridAutoColumns`
|
|
179
|
+
- `grid-auto-rows` / `gridAutoRows`
|
|
180
|
+
- `grid-auto-flow` / `gridAutoFlow`
|
|
181
|
+
- `grid-column` / `gridColumn`
|
|
182
|
+
- `grid-row` / `gridRow`
|
|
183
|
+
- `grid-column-start` / `gridColumnStart`
|
|
184
|
+
- `grid-column-end` / `gridColumnEnd`
|
|
185
|
+
- `grid-row-start` / `gridRowStart`
|
|
186
|
+
- `grid-row-end` / `gridRowEnd`
|
|
187
|
+
|
|
188
|
+
### Per-component props:
|
|
189
|
+
|
|
190
|
+
- `Div`: shared props, plus `scrollLeft?: number` and `scrollTop?: number`.
|
|
191
|
+
- `Span`: shared props, plus `scrollLeft?: number` and `scrollTop?: number`.
|
|
192
|
+
- `Form`: shared props, plus `scrollLeft?: number` and `scrollTop?: number`.
|
|
193
|
+
- `Button`: shared props, plus `type?: "submit" | "button"`,
|
|
194
|
+
`scrollLeft?: number`, and `scrollTop?: number`.
|
|
195
|
+
- `Input`: shared props, plus `type?: "text"`, `value?: string`,
|
|
196
|
+
`placeholder?: string`, `cursorPosition?: number`, and
|
|
197
|
+
`autoFocus?: boolean`.
|
|
198
|
+
- `Textarea`: shared props, plus `value?: string`, `placeholder?: string`,
|
|
199
|
+
`cursorPosition?: number`, `autoFocus?: boolean`, `scrollLeft?: number`, and
|
|
200
|
+
`scrollTop?: number`. `Textarea` does not accept a `type` prop.
|
|
201
|
+
|
|
202
|
+
## Hooks
|
|
203
|
+
|
|
204
|
+
`paintcannon-react` includes:
|
|
205
|
+
|
|
206
|
+
- `useApp()` for exiting the rendered app from inside React.
|
|
207
|
+
- `useAnimation()` for requestAnimationFrame-driven animations that share a
|
|
208
|
+
single render cycle.
|
|
209
|
+
|
|
210
|
+
## Core Runtime
|
|
211
|
+
|
|
212
|
+
This package depends on `paintcannon`, which provides the NAPI-RS native
|
|
213
|
+
renderer and DOM-like API.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PaintCannon } from 'paintcannon';
|
|
3
|
+
export type PaintCannonReactApp = {
|
|
4
|
+
readonly paintCannon: PaintCannon;
|
|
5
|
+
exit(errorOrResult?: unknown): void;
|
|
6
|
+
waitUntilExit(): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
export declare const AppContext: React.Context<PaintCannonReactApp | undefined>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { PaintCannon } from "paintcannon";
|
|
3
|
+
type AnimationCallback = (currentTime: number) => void;
|
|
4
|
+
type AnimationSubscription = {
|
|
5
|
+
startTime: number;
|
|
6
|
+
unsubscribe(): void;
|
|
7
|
+
};
|
|
8
|
+
type AnimationContextValue = {
|
|
9
|
+
subscribe(callback: AnimationCallback, interval: number | undefined): AnimationSubscription;
|
|
10
|
+
};
|
|
11
|
+
export type AnimationOptions = {
|
|
12
|
+
interval?: number;
|
|
13
|
+
isActive?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type AnimationResult = {
|
|
16
|
+
readonly frame: number;
|
|
17
|
+
readonly time: number;
|
|
18
|
+
readonly delta: number;
|
|
19
|
+
readonly reset: () => void;
|
|
20
|
+
};
|
|
21
|
+
export declare const AnimationContext: React.Context<AnimationContextValue | undefined>;
|
|
22
|
+
export declare function useAnimation(options?: AnimationOptions): AnimationResult;
|
|
23
|
+
export declare class AnimationScheduler implements AnimationContextValue {
|
|
24
|
+
private readonly paintCannon;
|
|
25
|
+
private nextId;
|
|
26
|
+
private animationFrameId;
|
|
27
|
+
private readonly subscribers;
|
|
28
|
+
subscribe(callback: AnimationCallback, interval: number | undefined): AnimationSubscription;
|
|
29
|
+
constructor(paintCannon: PaintCannon);
|
|
30
|
+
stop(): void;
|
|
31
|
+
private scheduleAnimationFrame;
|
|
32
|
+
private cancelAnimationFrame;
|
|
33
|
+
private tick;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export const AnimationContext = React.createContext(undefined);
|
|
3
|
+
const maximumTimerInterval = 2_147_483_647;
|
|
4
|
+
const zeroAnimationState = { frame: 0, time: 0, delta: 0 };
|
|
5
|
+
export function useAnimation(options = {}) {
|
|
6
|
+
const { interval, isActive = true } = options;
|
|
7
|
+
const safeInterval = interval === undefined ? undefined : normalizeAnimationInterval(interval);
|
|
8
|
+
const animation = React.useContext(AnimationContext);
|
|
9
|
+
if (animation === undefined) {
|
|
10
|
+
throw new Error("useAnimation() must be used inside a paintcannon-react render tree");
|
|
11
|
+
}
|
|
12
|
+
const [resetKey, setResetKey] = React.useState(0);
|
|
13
|
+
const [state, setState] = React.useState(zeroAnimationState);
|
|
14
|
+
const lastRenderTimeRef = React.useRef(0);
|
|
15
|
+
const previousOptionsRef = React.useRef({ isActive, safeInterval, resetKey });
|
|
16
|
+
const previousOptions = previousOptionsRef.current;
|
|
17
|
+
const shouldReset = isActive &&
|
|
18
|
+
(safeInterval !== previousOptions.safeInterval ||
|
|
19
|
+
!previousOptions.isActive ||
|
|
20
|
+
resetKey !== previousOptions.resetKey);
|
|
21
|
+
const reset = React.useCallback(() => {
|
|
22
|
+
setResetKey(value => value + 1);
|
|
23
|
+
}, []);
|
|
24
|
+
React.useLayoutEffect(() => {
|
|
25
|
+
if (!isActive) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
setState(zeroAnimationState);
|
|
29
|
+
let startTime = 0;
|
|
30
|
+
const subscription = animation.subscribe(currentTime => {
|
|
31
|
+
const elapsed = currentTime - startTime;
|
|
32
|
+
const delta = currentTime - lastRenderTimeRef.current;
|
|
33
|
+
lastRenderTimeRef.current = currentTime;
|
|
34
|
+
setState(previous => ({
|
|
35
|
+
frame: safeInterval === undefined ? previous.frame + 1 : Math.floor(elapsed / safeInterval),
|
|
36
|
+
time: elapsed,
|
|
37
|
+
delta,
|
|
38
|
+
}));
|
|
39
|
+
}, safeInterval);
|
|
40
|
+
startTime = subscription.startTime;
|
|
41
|
+
lastRenderTimeRef.current = subscription.startTime;
|
|
42
|
+
return subscription.unsubscribe;
|
|
43
|
+
}, [animation, isActive, safeInterval, resetKey]);
|
|
44
|
+
React.useLayoutEffect(() => {
|
|
45
|
+
previousOptionsRef.current = { isActive, safeInterval, resetKey };
|
|
46
|
+
}, [isActive, safeInterval, resetKey]);
|
|
47
|
+
if (shouldReset) {
|
|
48
|
+
return { ...zeroAnimationState, reset };
|
|
49
|
+
}
|
|
50
|
+
return { ...state, reset };
|
|
51
|
+
}
|
|
52
|
+
export class AnimationScheduler {
|
|
53
|
+
paintCannon;
|
|
54
|
+
nextId = 1;
|
|
55
|
+
animationFrameId;
|
|
56
|
+
subscribers = new Map();
|
|
57
|
+
subscribe(callback, interval) {
|
|
58
|
+
const id = this.nextId;
|
|
59
|
+
this.nextId += 1;
|
|
60
|
+
const startTime = performance.now();
|
|
61
|
+
this.subscribers.set(id, {
|
|
62
|
+
callback,
|
|
63
|
+
interval,
|
|
64
|
+
nextTime: interval === undefined ? startTime : startTime + interval,
|
|
65
|
+
});
|
|
66
|
+
this.scheduleAnimationFrame();
|
|
67
|
+
return {
|
|
68
|
+
startTime,
|
|
69
|
+
unsubscribe: () => {
|
|
70
|
+
this.subscribers.delete(id);
|
|
71
|
+
if (this.subscribers.size === 0) {
|
|
72
|
+
this.cancelAnimationFrame();
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
constructor(paintCannon) {
|
|
78
|
+
this.paintCannon = paintCannon;
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
this.cancelAnimationFrame();
|
|
82
|
+
this.subscribers.clear();
|
|
83
|
+
}
|
|
84
|
+
scheduleAnimationFrame() {
|
|
85
|
+
if (this.animationFrameId !== undefined || this.subscribers.size === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.animationFrameId = this.paintCannon.requestAnimationFrame(timestamp => {
|
|
89
|
+
this.animationFrameId = undefined;
|
|
90
|
+
this.tick(timestamp);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
cancelAnimationFrame() {
|
|
94
|
+
if (this.animationFrameId === undefined) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.paintCannon.cancelAnimationFrame(this.animationFrameId);
|
|
98
|
+
this.animationFrameId = undefined;
|
|
99
|
+
}
|
|
100
|
+
tick(currentTime) {
|
|
101
|
+
for (const subscriber of this.subscribers.values()) {
|
|
102
|
+
if (subscriber.interval === undefined) {
|
|
103
|
+
subscriber.callback(currentTime);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (subscriber.nextTime <= currentTime) {
|
|
107
|
+
const intervalsElapsed = Math.max(1, Math.floor((currentTime - subscriber.nextTime) / subscriber.interval) + 1);
|
|
108
|
+
subscriber.nextTime += subscriber.interval * intervalsElapsed;
|
|
109
|
+
subscriber.callback(currentTime);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
this.scheduleAnimationFrame();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function normalizeAnimationInterval(interval) {
|
|
116
|
+
if (!Number.isFinite(interval)) {
|
|
117
|
+
return maximumTimerInterval;
|
|
118
|
+
}
|
|
119
|
+
return Math.min(maximumTimerInterval, Math.max(1, interval));
|
|
120
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { PaintCannon } from "paintcannon";
|
|
3
|
+
export type PaintCannonReactApp = {
|
|
4
|
+
readonly paintCannon: PaintCannon;
|
|
5
|
+
exit(errorOrResult?: unknown): void;
|
|
6
|
+
waitUntilExit(): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
export declare const AppContext: React.Context<PaintCannonReactApp | undefined>;
|
|
9
|
+
export declare function useApp(): PaintCannonReactApp;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export const AppContext = React.createContext(undefined);
|
|
3
|
+
export function useApp() {
|
|
4
|
+
const app = React.useContext(AppContext);
|
|
5
|
+
if (app === undefined) {
|
|
6
|
+
throw new Error("useApp() must be used inside a paintcannon-react render tree");
|
|
7
|
+
}
|
|
8
|
+
return app;
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ButtonElement as PaintButtonElement } from "paintcannon";
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from "../shared.ts";
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable> & {
|
|
4
|
+
type?: "submit" | "button";
|
|
5
|
+
};
|
|
6
|
+
export type Element = PaintButtonElement;
|
|
7
|
+
export declare const type: "paintcannon.button";
|
|
8
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaintCannon } from "paintcannon";
|
|
2
|
+
import * as button from "./button.ts";
|
|
3
|
+
import type { MountedComponent } from "../mounted.ts";
|
|
4
|
+
import { type ApplyCommonProps } from "../reconcile-shared.ts";
|
|
5
|
+
export declare function create(paintCannon: PaintCannon, props: button.Props, applyCommonProps: ApplyCommonProps): MountedComponent<typeof button>;
|
|
6
|
+
export declare function applyProps(node: button.Element, oldProps: Partial<button.Props>, newProps: button.Props, applyCommonProps: ApplyCommonProps): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as button from "./button.js";
|
|
2
|
+
import { applyScrollableProps } from "../reconcile-shared.js";
|
|
3
|
+
export function create(paintCannon, props, applyCommonProps) {
|
|
4
|
+
const node = paintCannon.createElement("button");
|
|
5
|
+
applyProps(node, {}, props, applyCommonProps);
|
|
6
|
+
return { kind: "element", type: button.type, props, children: [], node };
|
|
7
|
+
}
|
|
8
|
+
export function applyProps(node, oldProps, newProps, applyCommonProps) {
|
|
9
|
+
applyCommonProps(node, oldProps, newProps);
|
|
10
|
+
if (newProps.type !== undefined) {
|
|
11
|
+
node.type = newProps.type;
|
|
12
|
+
}
|
|
13
|
+
applyScrollableProps(node, newProps);
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ButtonElement as PaintButtonElement } from 'paintcannon';
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from './shared.ts';
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable> & {
|
|
4
|
+
type?: 'submit' | 'button';
|
|
5
|
+
};
|
|
6
|
+
export type Element = PaintButtonElement;
|
|
7
|
+
export declare const type: "paintcannon.button";
|
|
8
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DivElement as PaintDivElement } from "paintcannon";
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from "../shared.ts";
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable>;
|
|
4
|
+
export type Element = PaintDivElement;
|
|
5
|
+
export declare const type: "paintcannon.div";
|
|
6
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaintCannon } from "paintcannon";
|
|
2
|
+
import * as div from "./div.ts";
|
|
3
|
+
import type { MountedComponent } from "../mounted.ts";
|
|
4
|
+
import { type ApplyCommonProps } from "../reconcile-shared.ts";
|
|
5
|
+
export declare function create(paintCannon: PaintCannon, props: div.Props, applyCommonProps: ApplyCommonProps): MountedComponent<typeof div>;
|
|
6
|
+
export declare function applyProps(node: div.Element, oldProps: Partial<div.Props>, newProps: div.Props, applyCommonProps: ApplyCommonProps): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as div from "./div.js";
|
|
2
|
+
import { applyScrollableElementProps } from "../reconcile-shared.js";
|
|
3
|
+
export function create(paintCannon, props, applyCommonProps) {
|
|
4
|
+
const node = paintCannon.createElement("div");
|
|
5
|
+
applyProps(node, {}, props, applyCommonProps);
|
|
6
|
+
return { kind: "element", type: div.type, props, children: [], node };
|
|
7
|
+
}
|
|
8
|
+
export function applyProps(node, oldProps, newProps, applyCommonProps) {
|
|
9
|
+
applyScrollableElementProps(node, oldProps, newProps, applyCommonProps);
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DivElement as PaintDivElement } from 'paintcannon';
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from './shared.ts';
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable>;
|
|
4
|
+
export type Element = PaintDivElement;
|
|
5
|
+
export declare const type: "paintcannon.div";
|
|
6
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FormElement as PaintFormElement } from "paintcannon";
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from "../shared.ts";
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable>;
|
|
4
|
+
export type Element = PaintFormElement;
|
|
5
|
+
export declare const type: "paintcannon.form";
|
|
6
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaintCannon } from "paintcannon";
|
|
2
|
+
import * as form from "./form.ts";
|
|
3
|
+
import type { MountedComponent } from "../mounted.ts";
|
|
4
|
+
import { type ApplyCommonProps } from "../reconcile-shared.ts";
|
|
5
|
+
export declare function create(paintCannon: PaintCannon, props: form.Props, applyCommonProps: ApplyCommonProps): MountedComponent<typeof form>;
|
|
6
|
+
export declare function applyProps(node: form.Element, oldProps: Partial<form.Props>, newProps: form.Props, applyCommonProps: ApplyCommonProps): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as form from "./form.js";
|
|
2
|
+
import { applyScrollableElementProps } from "../reconcile-shared.js";
|
|
3
|
+
export function create(paintCannon, props, applyCommonProps) {
|
|
4
|
+
const node = paintCannon.createElement("form");
|
|
5
|
+
applyProps(node, {}, props, applyCommonProps);
|
|
6
|
+
return { kind: "element", type: form.type, props, children: [], node };
|
|
7
|
+
}
|
|
8
|
+
export function applyProps(node, oldProps, newProps, applyCommonProps) {
|
|
9
|
+
applyScrollableElementProps(node, oldProps, newProps, applyCommonProps);
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FormElement as PaintFormElement } from 'paintcannon';
|
|
2
|
+
import type { CommonProps, HostComponent, Scrollable } from './shared.ts';
|
|
3
|
+
export type Props = CommonProps & Partial<Scrollable>;
|
|
4
|
+
export type Element = PaintFormElement;
|
|
5
|
+
export declare const type: "paintcannon.form";
|
|
6
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./shared.ts";
|
|
2
|
+
export * from "./mounted.ts";
|
|
3
|
+
export * as button from "./button/button.ts";
|
|
4
|
+
export * as div from "./div/div.ts";
|
|
5
|
+
export * as form from "./form/form.ts";
|
|
6
|
+
export * as input from "./input/input.ts";
|
|
7
|
+
export * as reconcileButton from "./button/reconcile-button.ts";
|
|
8
|
+
export * as reconcileDiv from "./div/reconcile-div.ts";
|
|
9
|
+
export * as reconcileForm from "./form/reconcile-form.ts";
|
|
10
|
+
export * as reconcileInput from "./input/reconcile-input.ts";
|
|
11
|
+
export * as reconcileSpan from "./span/reconcile-span.ts";
|
|
12
|
+
export * as reconcileTextarea from "./textarea/reconcile-textarea.ts";
|
|
13
|
+
export * as span from "./span/span.ts";
|
|
14
|
+
export * as textarea from "./textarea/textarea.ts";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./shared.js";
|
|
2
|
+
export * from "./mounted.js";
|
|
3
|
+
export * as button from "./button/button.js";
|
|
4
|
+
export * as div from "./div/div.js";
|
|
5
|
+
export * as form from "./form/form.js";
|
|
6
|
+
export * as input from "./input/input.js";
|
|
7
|
+
export * as reconcileButton from "./button/reconcile-button.js";
|
|
8
|
+
export * as reconcileDiv from "./div/reconcile-div.js";
|
|
9
|
+
export * as reconcileForm from "./form/reconcile-form.js";
|
|
10
|
+
export * as reconcileInput from "./input/reconcile-input.js";
|
|
11
|
+
export * as reconcileSpan from "./span/reconcile-span.js";
|
|
12
|
+
export * as reconcileTextarea from "./textarea/reconcile-textarea.js";
|
|
13
|
+
export * as span from "./span/span.js";
|
|
14
|
+
export * as textarea from "./textarea/textarea.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { InputElement as PaintInputElement } from "paintcannon";
|
|
2
|
+
import type { HostComponent, TextControlProps } from "../shared.ts";
|
|
3
|
+
export type Props = TextControlProps & {
|
|
4
|
+
type?: "text";
|
|
5
|
+
};
|
|
6
|
+
export type Element = PaintInputElement;
|
|
7
|
+
export declare const type: "paintcannon.input";
|
|
8
|
+
export declare const Component: HostComponent<Props, Element>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaintCannon } from "paintcannon";
|
|
2
|
+
import * as input from "./input.ts";
|
|
3
|
+
import type { MountedComponent } from "../mounted.ts";
|
|
4
|
+
import { type ApplyCommonProps } from "../reconcile-shared.ts";
|
|
5
|
+
export declare function create(paintCannon: PaintCannon, props: input.Props, applyCommonProps: ApplyCommonProps): MountedComponent<typeof input>;
|
|
6
|
+
export declare function applyProps(node: input.Element, oldProps: Partial<input.Props>, newProps: input.Props, applyCommonProps: ApplyCommonProps): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as input from "./input.js";
|
|
2
|
+
import { applyTextControlProps } from "../reconcile-shared.js";
|
|
3
|
+
export function create(paintCannon, props, applyCommonProps) {
|
|
4
|
+
const node = paintCannon.createElement("input");
|
|
5
|
+
applyProps(node, {}, props, applyCommonProps);
|
|
6
|
+
return { kind: "element", type: input.type, props, children: [], node };
|
|
7
|
+
}
|
|
8
|
+
export function applyProps(node, oldProps, newProps, applyCommonProps) {
|
|
9
|
+
applyCommonProps(node, oldProps, newProps);
|
|
10
|
+
if (newProps.type !== undefined) {
|
|
11
|
+
node.type = newProps.type;
|
|
12
|
+
}
|
|
13
|
+
applyTextControlProps(node, oldProps, newProps);
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { InputElement as PaintInputElement } from 'paintcannon';
|
|
2
|
+
import type { HostComponent, TextControlProps } from './shared.ts';
|
|
3
|
+
export type Props = TextControlProps & {
|
|
4
|
+
type?: 'text';
|
|
5
|
+
};
|
|
6
|
+
export type Element = PaintInputElement;
|
|
7
|
+
export declare const type: "paintcannon.input";
|
|
8
|
+
export declare const Component: HostComponent<Props, Element>;
|