paintcannon-react 0.0.13 → 0.0.15

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 CHANGED
@@ -97,6 +97,21 @@ These components map to PaintCannon's DOM-like core API and support typed style
97
97
  props, bubbling events, forms, focus handling, controlled inputs, and mouse
98
98
  interactions. The subset of the React-DOM props they support is as follows:
99
99
 
100
+ Host component `onFocus` and `onBlur` are element focus events. Terminal
101
+ window/pane focus is exposed by the underlying PaintCannon instance as
102
+ `paintCannon.hasFocus` and app-level `focus`/`blur` events. The root returned
103
+ from `render()` includes that instance:
104
+
105
+ ```tsx
106
+ const app = render(<App />, { alternateScreen: true });
107
+
108
+ app.paintCannon.addEventListener("blur", () => {
109
+ // Dim or pause the app while the terminal is unfocused.
110
+ });
111
+ ```
112
+
113
+ Inside tmux, terminal focus reporting requires `set -g focus-events on`.
114
+
100
115
  ### Common props:
101
116
 
102
117
  All host components accept:
@@ -3,7 +3,7 @@ import { applyScrollableProps } from "../reconcile-shared.js";
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("button");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: button.type, props, children: [], node };
6
+ return { kind: "element", type: button.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyCommonProps(node, oldProps, newProps);
@@ -3,7 +3,7 @@ import { applyScrollableElementProps } from "../reconcile-shared.js";
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("div");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: div.type, props, children: [], node };
6
+ return { kind: "element", type: div.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyScrollableElementProps(node, oldProps, newProps, applyCommonProps);
@@ -3,7 +3,7 @@ import { applyScrollableElementProps } from "../reconcile-shared.js";
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("form");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: form.type, props, children: [], node };
6
+ return { kind: "element", type: form.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyScrollableElementProps(node, oldProps, newProps, applyCommonProps);
@@ -3,7 +3,7 @@ import { applyTextControlProps } from "../reconcile-shared.js";
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("input");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: input.type, props, children: [], node };
6
+ return { kind: "element", type: input.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyCommonProps(node, oldProps, newProps);
@@ -11,7 +11,8 @@ export type MountedHostElement<Type extends HostType, Props, Element extends Pai
11
11
  kind: "element";
12
12
  type: Type;
13
13
  props: Props;
14
- children: HostNode[];
14
+ children: Set<HostNode>;
15
+ parent?: HostParent;
15
16
  node: Element;
16
17
  };
17
18
  export type MountedComponent<Module> = Module extends {
@@ -22,9 +23,13 @@ export type HostElement = MountedComponent<typeof div> | MountedComponent<typeof
22
23
  export type HostText = {
23
24
  kind: "text";
24
25
  text: string;
26
+ parent?: HostParent;
25
27
  node: TextNode;
26
28
  };
27
29
  export type HostNode = HostElement | HostText;
30
+ export type HostParent = {
31
+ children: Set<HostNode>;
32
+ };
28
33
  declare const hostPropsForType: {
29
34
  "paintcannon.div": div.Props;
30
35
  "paintcannon.span": span.Props;
@@ -3,7 +3,7 @@ import { applyScrollableElementProps } from "../reconcile-shared.js";
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("span");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: span.type, props, children: [], node };
6
+ return { kind: "element", type: span.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyScrollableElementProps(node, oldProps, newProps, applyCommonProps);
@@ -3,7 +3,7 @@ import { applyScrollableProps, applyTextControlProps, } from "../reconcile-share
3
3
  export function create(paintCannon, props, applyCommonProps) {
4
4
  const node = paintCannon.createElement("textarea");
5
5
  applyProps(node, {}, props, applyCommonProps);
6
- return { kind: "element", type: textarea.type, props, children: [], node };
6
+ return { kind: "element", type: textarea.type, props, children: new Set(), node };
7
7
  }
8
8
  export function applyProps(node, oldProps, newProps, applyCommonProps) {
9
9
  applyCommonProps(node, oldProps, newProps);
@@ -21,14 +21,17 @@ const reconciler = createReconciler({
21
21
  },
22
22
  resetAfterCommit(container) {
23
23
  container.paintCannon.commitTransaction();
24
- container.paintCannon.render();
24
+ if (container.presentCommits) {
25
+ container.paintCannon.render();
26
+ }
25
27
  },
26
28
  preparePortalMount: () => null,
27
29
  clearContainer(container) {
28
30
  for (const child of container.children) {
31
+ child.parent = undefined;
29
32
  destroyHostNode(child);
30
33
  }
31
- container.children = [];
34
+ container.children.clear();
32
35
  return false;
33
36
  },
34
37
  shouldSetTextContent: () => false,
@@ -55,11 +58,11 @@ const reconciler = createReconciler({
55
58
  container.root.appendChild(child.node);
56
59
  },
57
60
  insertBefore(parent, child, before) {
58
- insertVirtualChild(parent, child, before);
61
+ insertVirtualChild(parent, child);
59
62
  insertPaintChild(parent.node, child.node, before.node);
60
63
  },
61
64
  insertInContainerBefore(container, child, before) {
62
- insertVirtualChild(container, child, before);
65
+ insertVirtualChild(container, child);
63
66
  container.root.insertBefore(child.node, before.node);
64
67
  },
65
68
  removeChild(parent, child) {
@@ -148,7 +151,8 @@ export function createRoot(options = {}) {
148
151
  const rootContainer = {
149
152
  paintCannon,
150
153
  root: container,
151
- children: [],
154
+ children: new Set(),
155
+ presentCommits: true,
152
156
  };
153
157
  const animationScheduler = new AnimationScheduler(paintCannon);
154
158
  const reactRoot = reconciler.createContainer(rootContainer, 0, null, false, null, "", reportReactError, reportReactError, reportReactError, () => { });
@@ -173,6 +177,7 @@ export function createRoot(options = {}) {
173
177
  return;
174
178
  }
175
179
  exited = true;
180
+ rootContainer.presentCommits = false;
176
181
  reconciler.updateContainer(null, reactRoot, null, () => {
177
182
  animationScheduler.stop();
178
183
  paintCannon.stop();
@@ -209,23 +214,19 @@ export function render(element, options = {}) {
209
214
  return root;
210
215
  }
211
216
  function appendVirtualChild(parent, child) {
212
- removeVirtualChild(parent, child);
213
- parent.children.push(child);
217
+ child.parent?.children.delete(child);
218
+ parent.children.add(child);
219
+ child.parent = parent;
214
220
  }
215
- function insertVirtualChild(parent, child, before) {
216
- removeVirtualChild(parent, child);
217
- const index = parent.children.indexOf(before);
218
- if (index === -1) {
219
- parent.children.push(child);
220
- }
221
- else {
222
- parent.children.splice(index, 0, child);
223
- }
221
+ function insertVirtualChild(parent, child) {
222
+ child.parent?.children.delete(child);
223
+ parent.children.add(child);
224
+ child.parent = parent;
224
225
  }
225
226
  function removeVirtualChild(parent, child) {
226
- const index = parent.children.indexOf(child);
227
- if (index !== -1) {
228
- parent.children.splice(index, 1);
227
+ if (child.parent === parent) {
228
+ parent.children.delete(child);
229
+ child.parent = undefined;
229
230
  }
230
231
  }
231
232
  function createHostElement(paintCannon, type, props) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paintcannon-react",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "license": "ISC",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,7 +36,7 @@
36
36
  "scheduler": "^0.25.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "paintcannon": "^0.0.13",
39
+ "paintcannon": "^0.0.15",
40
40
  "react": "^19.0.0"
41
41
  },
42
42
  "devDependencies": {