@gtkx/react 0.9.1 → 0.9.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/README.md CHANGED
@@ -22,7 +22,7 @@ GTKX lets you build native Linux desktop applications using React and TypeScript
22
22
  ## Features
23
23
 
24
24
  - **React** — Hooks, state, props, and components you already know
25
- - **Hot Reload** — Edit code and see changes instantly via Vite
25
+ - **HMR** — Edit code and see changes instantly via Vite
26
26
  - **Native** — Direct FFI bindings to GTK4 via Rust and libffi
27
27
  - **CLI** — `npx @gtkx/cli@latest create` scaffolds a ready-to-go project
28
28
  - **CSS-in-JS** — Emotion-style `css` template literals for GTK styling
@@ -41,14 +41,7 @@ Edit your code and see changes instantly—no restart needed.
41
41
  ### Example
42
42
 
43
43
  ```tsx
44
- import {
45
- render,
46
- ApplicationWindow,
47
- Box,
48
- Button,
49
- Label,
50
- quit,
51
- } from "@gtkx/react";
44
+ import { render, ApplicationWindow, Box, Button, quit } from "@gtkx/react";
52
45
  import * as Gtk from "@gtkx/ffi/gtk";
53
46
  import { useState } from "react";
54
47
 
@@ -58,7 +51,7 @@ const App = () => {
58
51
  return (
59
52
  <ApplicationWindow title="Counter" onCloseRequest={quit}>
60
53
  <Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
61
- <Label label={`Count: ${count}`} />
54
+ {`Count: ${count}`}
62
55
  <Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
63
56
  </Box>
64
57
  </ApplicationWindow>
@@ -84,8 +77,6 @@ const primary = css`
84
77
  <Button label="Click me" cssClasses={[primary]} />;
85
78
  ```
86
79
 
87
- GTK also provides built-in classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
88
-
89
80
  ## Testing
90
81
 
91
82
  ```tsx
@@ -109,9 +100,8 @@ test("increments count", async () => {
109
100
 
110
101
  ## Requirements
111
102
 
112
- - Node.js 20+
103
+ - Node.js 20+ (Deno support experimental)
113
104
  - GTK4 Runtime (`gtk4` on Fedora, `libgtk-4-1` on Ubuntu)
114
- - Linux
115
105
 
116
106
  ## Contributing
117
107
 
package/dist/batch.d.ts CHANGED
@@ -1,20 +1,5 @@
1
1
  type FlushCallback = () => void;
2
- /**
3
- * Marks the beginning of a React reconciler commit phase.
4
- * While in commit, flush callbacks are deferred until endCommit is called.
5
- * Supports nested commits through depth tracking.
6
- */
7
2
  export declare const beginCommit: () => void;
8
- /**
9
- * Marks the end of a React reconciler commit phase.
10
- * Executes all pending flush callbacks that were deferred during the commit
11
- * only when the outermost commit ends (depth reaches 0).
12
- * If called without a matching beginCommit, resets the state (useful for test cleanup).
13
- */
14
3
  export declare const endCommit: () => void;
15
- /**
16
- * Schedules a callback to be executed, deferring it if currently in a commit phase.
17
- * This ensures GTK state updates happen after React has finished its batch of changes.
18
- */
19
4
  export declare const scheduleFlush: (callback: FlushCallback) => void;
20
5
  export {};
package/dist/batch.js CHANGED
@@ -2,20 +2,9 @@ const state = {
2
2
  depth: 0,
3
3
  pendingFlushes: new Set(),
4
4
  };
5
- /**
6
- * Marks the beginning of a React reconciler commit phase.
7
- * While in commit, flush callbacks are deferred until endCommit is called.
8
- * Supports nested commits through depth tracking.
9
- */
10
5
  export const beginCommit = () => {
11
6
  state.depth++;
12
7
  };
13
- /**
14
- * Marks the end of a React reconciler commit phase.
15
- * Executes all pending flush callbacks that were deferred during the commit
16
- * only when the outermost commit ends (depth reaches 0).
17
- * If called without a matching beginCommit, resets the state (useful for test cleanup).
18
- */
19
8
  export const endCommit = () => {
20
9
  if (state.depth <= 0) {
21
10
  state.depth = 0;
@@ -32,10 +21,6 @@ export const endCommit = () => {
32
21
  });
33
22
  }
34
23
  };
35
- /**
36
- * Schedules a callback to be executed, deferring it if currently in a commit phase.
37
- * This ensures GTK state updates happen after React has finished its batch of changes.
38
- */
39
24
  export const scheduleFlush = (callback) => {
40
25
  if (state.depth > 0) {
41
26
  state.pendingFlushes.add(callback);
@@ -1,24 +1,11 @@
1
1
  import type { GirClass, GirNamespace, TypeMapper, TypeRegistry } from "@gtkx/gir";
2
- /**
3
- * Configuration options for the JSX type generator.
4
- */
5
2
  type JsxGeneratorOptions = {
6
- /** Optional Prettier configuration for formatting output. */
7
3
  prettierConfig?: unknown;
8
4
  };
9
- /**
10
- * Result of the JSX generation containing both public and internal files.
11
- */
12
5
  type JsxGeneratorResult = {
13
- /** Public JSX types and components for user consumption. */
14
6
  jsx: string;
15
- /** Internal metadata for reconciler use (not exported to users). */
16
7
  internal: string;
17
8
  };
18
- /**
19
- * Generates JSX type definitions for React components from GTK widget classes.
20
- * Creates TypeScript interfaces for props and augments React's JSX namespace.
21
- */
22
9
  export declare class JsxGenerator {
23
10
  private typeMapper;
24
11
  private typeRegistry;
@@ -30,19 +17,8 @@ export declare class JsxGenerator {
30
17
  private widgetSignalNames;
31
18
  private currentNamespace;
32
19
  private widgetNamespaceMap;
33
- /**
34
- * Creates a new JSX generator.
35
- * @param typeMapper - TypeMapper for converting GIR types to TypeScript
36
- * @param typeRegistry - TypeRegistry for cross-namespace type resolution
37
- * @param classMap - Combined class map with fully qualified names
38
- * @param options - Generator configuration options
39
- */
40
20
  constructor(typeMapper: TypeMapper, typeRegistry: TypeRegistry, classMap: Map<string, GirClass>, options?: JsxGeneratorOptions);
41
- /**
42
- * Generates JSX type definitions for all widgets in the given namespaces.
43
- * @param namespaces - The parsed GIR namespaces (GTK must be first)
44
- * @returns Generated TypeScript code as public jsx.ts and internal.ts files
45
- */
21
+ private formatDoc;
46
22
  generate(namespaces: GirNamespace[]): Promise<JsxGeneratorResult>;
47
23
  private generateImports;
48
24
  private generateInternalImports;
@@ -1,4 +1,4 @@
1
- import { toCamelCase, toPascalCase } from "@gtkx/gir";
1
+ import { formatDoc as formatDocBase, toCamelCase, toPascalCase } from "@gtkx/gir";
2
2
  import { format } from "prettier";
3
3
  const LIST_WIDGETS = new Set(["ListView", "GridView"]);
4
4
  const COLUMN_VIEW_WIDGET = "ColumnView";
@@ -40,34 +40,6 @@ const isNotebookWidget = (widgetName) => widgetName === NOTEBOOK_WIDGET;
40
40
  const isStackWidget = (widgetName) => STACK_WIDGETS.has(widgetName);
41
41
  const isPopoverMenuWidget = (widgetName) => widgetName === POPOVER_MENU_WIDGET;
42
42
  const isToolbarViewWidget = (widgetName) => widgetName === TOOLBAR_VIEW_WIDGET;
43
- const sanitizeDoc = (doc) => {
44
- let result = doc;
45
- result = result.replace(/<picture[^>]*>[\s\S]*?<\/picture>/gi, "");
46
- result = result.replace(/<img[^>]*>/gi, "");
47
- result = result.replace(/<source[^>]*>/gi, "");
48
- result = result.replace(/!\[[^\]]*\]\([^)]+\.png\)/gi, "");
49
- result = result.replace(/<kbd>([^<]*)<\/kbd>/gi, "`$1`");
50
- result = result.replace(/<kbd>/gi, "`");
51
- result = result.replace(/<\/kbd>/gi, "`");
52
- result = result.replace(/\[([^\]]+)\]\([^)]+\.html[^)]*\)/gi, "$1");
53
- result = result.replace(/@(\w+)\s/g, "`$1` ");
54
- result = result.replace(/<(\/?)(child|object|property|signal|template)>/gi, "`<$1$2>`");
55
- return result.trim();
56
- };
57
- const formatDoc = (doc, indent = "") => {
58
- if (!doc)
59
- return "";
60
- const sanitized = sanitizeDoc(doc);
61
- if (!sanitized)
62
- return "";
63
- const lines = sanitized.split("\n").map((line) => line.trim());
64
- const firstLine = lines[0] ?? "";
65
- if (lines.length === 1 && firstLine.length < 80) {
66
- return `${indent}/** ${firstLine} */\n`;
67
- }
68
- const formattedLines = lines.map((line) => `${indent} * ${line}`);
69
- return `${indent}/**\n${formattedLines.join("\n")}\n${indent} */\n`;
70
- };
71
43
  const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
72
44
  const className = typeName.includes(".") ? typeName.split(".")[1] : typeName;
73
45
  if (!className || visited.has(className))
@@ -80,10 +52,6 @@ const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
80
52
  return true;
81
53
  return cls.parent ? isWidgetSubclass(cls.parent, classMap, visited) : false;
82
54
  };
83
- /**
84
- * Generates JSX type definitions for React components from GTK widget classes.
85
- * Creates TypeScript interfaces for props and augments React's JSX namespace.
86
- */
87
55
  export class JsxGenerator {
88
56
  typeMapper;
89
57
  typeRegistry;
@@ -95,24 +63,18 @@ export class JsxGenerator {
95
63
  widgetSignalNames = new Set();
96
64
  currentNamespace = "";
97
65
  widgetNamespaceMap = new Map();
98
- /**
99
- * Creates a new JSX generator.
100
- * @param typeMapper - TypeMapper for converting GIR types to TypeScript
101
- * @param typeRegistry - TypeRegistry for cross-namespace type resolution
102
- * @param classMap - Combined class map with fully qualified names
103
- * @param options - Generator configuration options
104
- */
105
66
  constructor(typeMapper, typeRegistry, classMap, options = {}) {
106
67
  this.typeMapper = typeMapper;
107
68
  this.typeRegistry = typeRegistry;
108
69
  this.classMap = classMap;
109
70
  this.options = options;
110
71
  }
111
- /**
112
- * Generates JSX type definitions for all widgets in the given namespaces.
113
- * @param namespaces - The parsed GIR namespaces (GTK must be first)
114
- * @returns Generated TypeScript code as public jsx.ts and internal.ts files
115
- */
72
+ formatDoc(doc, indent = "") {
73
+ return formatDocBase(doc, indent, {
74
+ namespace: this.currentNamespace,
75
+ escapeXmlTags: true,
76
+ });
77
+ }
116
78
  async generate(namespaces) {
117
79
  for (const ns of namespaces) {
118
80
  for (const iface of ns.interfaces) {
@@ -181,7 +143,7 @@ ${widgetPropsContent}
181
143
  }
182
144
  generateWidgetPropsContent(widgetClass) {
183
145
  const lines = [];
184
- const widgetDoc = widgetClass?.doc ? formatDoc(widgetClass.doc) : "";
146
+ const widgetDoc = widgetClass?.doc ? this.formatDoc(widgetClass.doc) : "";
185
147
  if (widgetDoc) {
186
148
  lines.push(widgetDoc.trimEnd());
187
149
  }
@@ -191,7 +153,7 @@ ${widgetPropsContent}
191
153
  const propName = toCamelCase(prop.name);
192
154
  const tsType = this.toJsxPropertyType(this.typeMapper.mapType(prop.type).ts, "Gtk");
193
155
  if (prop.doc) {
194
- lines.push(formatDoc(prop.doc, "\t").trimEnd());
156
+ lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
195
157
  }
196
158
  lines.push(`\t${propName}?: ${tsType};`);
197
159
  }
@@ -199,7 +161,7 @@ ${widgetPropsContent}
199
161
  lines.push("");
200
162
  for (const signal of widgetClass.signals) {
201
163
  if (signal.doc) {
202
- lines.push(formatDoc(signal.doc, "\t").trimEnd());
164
+ lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
203
165
  }
204
166
  lines.push(`\t${this.generateSignalHandler(signal, "Widget")}`);
205
167
  }
@@ -354,7 +316,7 @@ ${widgetPropsContent}
354
316
  const isRequiredByConstructor = requiredCtorParams.has(prop.name);
355
317
  const isRequired = isRequiredByConstructor;
356
318
  if (prop.doc) {
357
- lines.push(formatDoc(prop.doc, "\t").trimEnd());
319
+ lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
358
320
  }
359
321
  lines.push(`\t${propName}${isRequired ? "" : "?"}: ${tsType};`);
360
322
  }
@@ -377,7 +339,7 @@ ${widgetPropsContent}
377
339
  lines.push("");
378
340
  for (const signal of specificSignals) {
379
341
  if (signal.doc) {
380
- lines.push(formatDoc(signal.doc, "\t").trimEnd());
342
+ lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
381
343
  }
382
344
  lines.push(`\t${this.generateSignalHandler(signal, widget.name)}`);
383
345
  }
@@ -716,7 +678,7 @@ ${widgetPropsContent}
716
678
  isNotebookWidget(widget.name) ||
717
679
  isStackWidget(widget.name) ||
718
680
  isPopoverMenuWidget(widget.name);
719
- const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
681
+ const docComment = widget.doc ? this.formatDoc(widget.doc).trimEnd() : "";
720
682
  if (hasMeaningfulSlots) {
721
683
  if (isListWidget(widget.name) ||
722
684
  isColumnViewWidget(widget.name) ||
package/dist/factory.d.ts CHANGED
@@ -1,13 +1,6 @@
1
1
  import type * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { Node } from "./node.js";
3
3
  import { type ROOT_NODE_CONTAINER } from "./nodes/root.js";
4
- /**
5
- * Generic props type for React component properties passed to GTK nodes.
6
- */
7
4
  export type Props = Record<string, unknown>;
8
5
  export { ROOT_NODE_CONTAINER } from "./nodes/root.js";
9
- /**
10
- * Creates a Node instance for the given JSX element type.
11
- * Matches the type against registered node classes and initializes with props.
12
- */
13
6
  export declare const createNode: (type: string, props: Props, widget?: Gtk.Widget | typeof ROOT_NODE_CONTAINER) => Node;
package/dist/factory.js CHANGED
@@ -63,10 +63,6 @@ const CONTAINER_NODES = [
63
63
  HeaderBarNode,
64
64
  ];
65
65
  const NODE_CLASSES = [RootNode, ...VIRTUAL_NODES, ...SPECIALIZED_NODES, ...CONTAINER_NODES, WidgetNode];
66
- /**
67
- * Creates a Node instance for the given JSX element type.
68
- * Matches the type against registered node classes and initializes with props.
69
- */
70
66
  export const createNode = (type, props, widget) => {
71
67
  for (const NodeClass of NODE_CLASSES) {
72
68
  if (NodeClass.matches(type, widget)) {
@@ -1,8 +1,3 @@
1
1
  import type * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type Reconciler from "react-reconciler";
3
- /**
4
- * Creates a new fiber root container for rendering React elements.
5
- * @param container - Optional GTK widget to use as the container. If not provided,
6
- * uses the ROOT_NODE_CONTAINER sentinel for virtual roots.
7
- */
8
3
  export declare const createFiberRoot: (container?: Gtk.Widget) => Reconciler.FiberRoot;
@@ -1,10 +1,5 @@
1
1
  import { ROOT_NODE_CONTAINER } from "./factory.js";
2
2
  import { reconciler } from "./reconciler.js";
3
- /**
4
- * Creates a new fiber root container for rendering React elements.
5
- * @param container - Optional GTK widget to use as the container. If not provided,
6
- * uses the ROOT_NODE_CONTAINER sentinel for virtual roots.
7
- */
8
3
  export const createFiberRoot = (container) => {
9
4
  const instance = reconciler.getInstance();
10
5
  return instance.createContainer(container ?? ROOT_NODE_CONTAINER, 0, null, false, null, "", (error) => console.error("Fiber root render error:", error), () => { }, () => { }, () => { }, null);