@gtkx/react 0.1.44 → 0.1.46
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/dist/codegen/jsx-generator.d.ts +21 -0
- package/dist/codegen/jsx-generator.js +132 -15
- package/dist/fiber-root.d.ts +5 -0
- package/dist/fiber-root.js +5 -0
- package/dist/generated/jsx.d.ts +10279 -9
- package/dist/generated/jsx.js +5613 -9
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/node.js +1 -0
- package/dist/nodes/text-view.js +14 -0
- package/dist/portal.d.ts +18 -0
- package/dist/portal.js +18 -0
- package/dist/reconciler.d.ts +9 -0
- package/dist/reconciler.js +9 -0
- package/dist/render.d.ts +19 -0
- package/dist/render.js +19 -0
- package/dist/types.d.ts +9 -0
- package/package.json +3 -3
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { GirClass, GirNamespace, TypeMapper } from "@gtkx/gir";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the JSX type generator.
|
|
4
|
+
*/
|
|
2
5
|
interface JsxGeneratorOptions {
|
|
6
|
+
/** Optional Prettier configuration for formatting output. */
|
|
3
7
|
prettierConfig?: unknown;
|
|
4
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Generates JSX type definitions for React components from GTK widget classes.
|
|
11
|
+
* Creates TypeScript interfaces for props and augments React's JSX namespace.
|
|
12
|
+
*/
|
|
5
13
|
export declare class JsxGenerator {
|
|
6
14
|
private typeMapper;
|
|
7
15
|
private options;
|
|
@@ -11,7 +19,18 @@ export declare class JsxGenerator {
|
|
|
11
19
|
private usedExternalNamespaces;
|
|
12
20
|
private widgetPropertyNames;
|
|
13
21
|
private widgetSignalNames;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new JSX generator.
|
|
24
|
+
* @param typeMapper - TypeMapper for converting GIR types to TypeScript
|
|
25
|
+
* @param options - Generator configuration options
|
|
26
|
+
*/
|
|
14
27
|
constructor(typeMapper: TypeMapper, options?: JsxGeneratorOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Generates JSX type definitions for all widgets in a namespace.
|
|
30
|
+
* @param namespace - The parsed GIR namespace
|
|
31
|
+
* @param classMap - Map of class names to class definitions
|
|
32
|
+
* @returns Generated TypeScript code as a string
|
|
33
|
+
*/
|
|
15
34
|
generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<string>;
|
|
16
35
|
private generateImports;
|
|
17
36
|
private generateCommonTypes;
|
|
@@ -34,6 +53,8 @@ export declare class JsxGenerator {
|
|
|
34
53
|
private addNamespacePrefix;
|
|
35
54
|
private buildSignalHandlerType;
|
|
36
55
|
private generateExports;
|
|
56
|
+
private getWrapperExportMembers;
|
|
57
|
+
private generateGenericWrapperComponents;
|
|
37
58
|
private generateJsxNamespace;
|
|
38
59
|
private formatCode;
|
|
39
60
|
}
|
|
@@ -74,6 +74,10 @@ const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
|
|
|
74
74
|
return true;
|
|
75
75
|
return cls.parent ? isWidgetSubclass(cls.parent, classMap, visited) : false;
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Generates JSX type definitions for React components from GTK widget classes.
|
|
79
|
+
* Creates TypeScript interfaces for props and augments React's JSX namespace.
|
|
80
|
+
*/
|
|
77
81
|
export class JsxGenerator {
|
|
78
82
|
typeMapper;
|
|
79
83
|
options;
|
|
@@ -83,10 +87,21 @@ export class JsxGenerator {
|
|
|
83
87
|
usedExternalNamespaces = new Set();
|
|
84
88
|
widgetPropertyNames = new Set();
|
|
85
89
|
widgetSignalNames = new Set();
|
|
90
|
+
/**
|
|
91
|
+
* Creates a new JSX generator.
|
|
92
|
+
* @param typeMapper - TypeMapper for converting GIR types to TypeScript
|
|
93
|
+
* @param options - Generator configuration options
|
|
94
|
+
*/
|
|
86
95
|
constructor(typeMapper, options = {}) {
|
|
87
96
|
this.typeMapper = typeMapper;
|
|
88
97
|
this.options = options;
|
|
89
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Generates JSX type definitions for all widgets in a namespace.
|
|
101
|
+
* @param namespace - The parsed GIR namespace
|
|
102
|
+
* @param classMap - Map of class names to class definitions
|
|
103
|
+
* @returns Generated TypeScript code as a string
|
|
104
|
+
*/
|
|
90
105
|
async generate(namespace, classMap) {
|
|
91
106
|
this.classMap = classMap;
|
|
92
107
|
this.interfaceMap = new Map(namespace.interfaces.map((iface) => [iface.name, iface]));
|
|
@@ -116,6 +131,7 @@ export class JsxGenerator {
|
|
|
116
131
|
.map((ns) => `import type * as ${ns} from "@gtkx/ffi/${ns.toLowerCase()}";`);
|
|
117
132
|
return [
|
|
118
133
|
`import "react";`,
|
|
134
|
+
`import { createElement } from "react";`,
|
|
119
135
|
`import type { ReactNode, Ref } from "react";`,
|
|
120
136
|
...externalImports,
|
|
121
137
|
`import type * as Gtk from "@gtkx/ffi/gtk";`,
|
|
@@ -319,18 +335,22 @@ ${widgetPropsContent}
|
|
|
319
335
|
lines.push(`\t * Render function for list items.`);
|
|
320
336
|
lines.push(`\t * Called with null during setup (for loading state) and with the actual item during bind.`);
|
|
321
337
|
lines.push(`\t */`);
|
|
322
|
-
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny:
|
|
338
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic ListView<T> export`);
|
|
323
339
|
lines.push(`\trenderItem: (item: any) => import("react").ReactElement;`);
|
|
324
340
|
}
|
|
325
341
|
if (isDropDownWidget(widget.name)) {
|
|
326
342
|
lines.push("");
|
|
327
343
|
lines.push(`\t/** Function to convert item to display label */`);
|
|
344
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic DropDown<T> export`);
|
|
328
345
|
lines.push(`\titemLabel?: (item: any) => string;`);
|
|
329
346
|
lines.push(`\t/** Called when selection changes */`);
|
|
347
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic DropDown<T> export`);
|
|
330
348
|
lines.push(`\tonSelectionChanged?: (item: any, index: number) => void;`);
|
|
331
349
|
}
|
|
332
350
|
if (isTextViewWidget(widget.name)) {
|
|
333
351
|
lines.push("");
|
|
352
|
+
lines.push(`\t/** The contents of the text buffer. */`);
|
|
353
|
+
lines.push(`\ttext?: string;`);
|
|
334
354
|
lines.push(`\t/** Called when the text buffer content changes */`);
|
|
335
355
|
lines.push(`\tonChanged?: (text: string) => void;`);
|
|
336
356
|
}
|
|
@@ -544,22 +564,30 @@ ${widgetPropsContent}
|
|
|
544
564
|
isNotebookWidget(widget.name);
|
|
545
565
|
const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
|
|
546
566
|
if (hasMeaningfulSlots) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
];
|
|
558
|
-
if (docComment) {
|
|
559
|
-
lines.push(`${docComment}\nexport const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
567
|
+
// For list widgets, generate wrapper components with proper generics
|
|
568
|
+
if (isListWidget(widget.name) || isColumnViewWidget(widget.name) || isDropDownWidget(widget.name)) {
|
|
569
|
+
const wrapperComponents = this.generateGenericWrapperComponents(widget.name, metadata);
|
|
570
|
+
const exportMembers = this.getWrapperExportMembers(widget.name, metadata);
|
|
571
|
+
if (docComment) {
|
|
572
|
+
lines.push(`${wrapperComponents}\n${docComment}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
lines.push(`${wrapperComponents}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
576
|
+
}
|
|
560
577
|
}
|
|
561
578
|
else {
|
|
562
|
-
|
|
579
|
+
const valueMembers = [
|
|
580
|
+
`Root: "${widgetName}.Root" as const`,
|
|
581
|
+
...metadata.namedChildSlots.map((slot) => `${slot.slotName}: "${widgetName}.${slot.slotName}" as const`),
|
|
582
|
+
...(isGridWidget(widget.name) ? [`Child: "${widgetName}.Child" as const`] : []),
|
|
583
|
+
...(isNotebookWidget(widget.name) ? [`Page: "${widgetName}.Page" as const`] : []),
|
|
584
|
+
];
|
|
585
|
+
if (docComment) {
|
|
586
|
+
lines.push(`${docComment}\nexport const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
lines.push(`export const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
590
|
+
}
|
|
563
591
|
}
|
|
564
592
|
}
|
|
565
593
|
else {
|
|
@@ -573,6 +601,95 @@ ${widgetPropsContent}
|
|
|
573
601
|
}
|
|
574
602
|
return `${lines.join("\n")}\n`;
|
|
575
603
|
}
|
|
604
|
+
getWrapperExportMembers(widgetName, metadata) {
|
|
605
|
+
const name = toPascalCase(widgetName);
|
|
606
|
+
const members = [`Root: ${name}Root`];
|
|
607
|
+
if (isListWidget(widgetName)) {
|
|
608
|
+
members.push(`Item: ${name}Item`);
|
|
609
|
+
}
|
|
610
|
+
else if (isColumnViewWidget(widgetName)) {
|
|
611
|
+
members.push(`Column: ${name}Column`);
|
|
612
|
+
members.push(`Item: ${name}Item`);
|
|
613
|
+
}
|
|
614
|
+
else if (isDropDownWidget(widgetName)) {
|
|
615
|
+
members.push(`Item: ${name}Item`);
|
|
616
|
+
}
|
|
617
|
+
// Add named child slots
|
|
618
|
+
for (const slot of metadata.namedChildSlots) {
|
|
619
|
+
members.push(`${slot.slotName}: ${name}${slot.slotName}`);
|
|
620
|
+
}
|
|
621
|
+
return members;
|
|
622
|
+
}
|
|
623
|
+
generateGenericWrapperComponents(widgetName, metadata) {
|
|
624
|
+
const name = toPascalCase(widgetName);
|
|
625
|
+
const lines = [];
|
|
626
|
+
if (isListWidget(widgetName)) {
|
|
627
|
+
// Props type for the generic Root component
|
|
628
|
+
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "renderItem"> {`);
|
|
629
|
+
lines.push(`\t/** Render function for list items. Called with null during setup. */`);
|
|
630
|
+
lines.push(`\trenderItem: (item: T | null) => import("react").ReactElement;`);
|
|
631
|
+
lines.push(`}`);
|
|
632
|
+
lines.push(``);
|
|
633
|
+
// Root wrapper component
|
|
634
|
+
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
635
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
636
|
+
lines.push(`}`);
|
|
637
|
+
lines.push(``);
|
|
638
|
+
// Item wrapper component
|
|
639
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
640
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
641
|
+
lines.push(`}`);
|
|
642
|
+
}
|
|
643
|
+
else if (isColumnViewWidget(widgetName)) {
|
|
644
|
+
// Root wrapper (non-generic)
|
|
645
|
+
lines.push(`function ${name}Root(props: ${name}Props): import("react").ReactElement {`);
|
|
646
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
647
|
+
lines.push(`}`);
|
|
648
|
+
lines.push(``);
|
|
649
|
+
// Column props type - use GenericColumnProps to avoid conflict with imported ColumnViewColumnProps
|
|
650
|
+
lines.push(`interface ${name}GenericColumnProps<T> extends Omit<ColumnViewColumnProps, "renderCell"> {`);
|
|
651
|
+
lines.push(`\t/** Render function for column cells. Called with null during setup. */`);
|
|
652
|
+
lines.push(`\trenderCell: (item: T | null) => import("react").ReactElement;`);
|
|
653
|
+
lines.push(`}`);
|
|
654
|
+
lines.push(``);
|
|
655
|
+
// Column wrapper component
|
|
656
|
+
lines.push(`function ${name}Column<T>(props: ${name}GenericColumnProps<T>): import("react").ReactElement {`);
|
|
657
|
+
lines.push(`\treturn createElement("${name}.Column", props);`);
|
|
658
|
+
lines.push(`}`);
|
|
659
|
+
lines.push(``);
|
|
660
|
+
// Item wrapper component
|
|
661
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
662
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
663
|
+
lines.push(`}`);
|
|
664
|
+
}
|
|
665
|
+
else if (isDropDownWidget(widgetName)) {
|
|
666
|
+
// Props type for the generic Root component
|
|
667
|
+
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "itemLabel" | "onSelectionChanged"> {`);
|
|
668
|
+
lines.push(`\t/** Function to convert item to display label */`);
|
|
669
|
+
lines.push(`\titemLabel?: (item: T) => string;`);
|
|
670
|
+
lines.push(`\t/** Called when selection changes */`);
|
|
671
|
+
lines.push(`\tonSelectionChanged?: (item: T, index: number) => void;`);
|
|
672
|
+
lines.push(`}`);
|
|
673
|
+
lines.push(``);
|
|
674
|
+
// Root wrapper component
|
|
675
|
+
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
676
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
677
|
+
lines.push(`}`);
|
|
678
|
+
lines.push(``);
|
|
679
|
+
// Item wrapper component
|
|
680
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
681
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
682
|
+
lines.push(`}`);
|
|
683
|
+
}
|
|
684
|
+
// Generate slot wrapper components
|
|
685
|
+
for (const slot of metadata.namedChildSlots) {
|
|
686
|
+
lines.push(``);
|
|
687
|
+
lines.push(`function ${name}${slot.slotName}(props: SlotProps): import("react").ReactElement {`);
|
|
688
|
+
lines.push(`\treturn createElement("${name}.${slot.slotName}", props);`);
|
|
689
|
+
lines.push(`}`);
|
|
690
|
+
}
|
|
691
|
+
return lines.join("\n");
|
|
692
|
+
}
|
|
576
693
|
generateJsxNamespace(widgets, containerMetadata) {
|
|
577
694
|
const elements = [];
|
|
578
695
|
for (const widget of widgets) {
|
package/dist/fiber-root.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
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
|
+
*/
|
|
3
8
|
export declare const createFiberRoot: (container?: Gtk.Widget) => Reconciler.FiberRoot;
|
package/dist/fiber-root.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
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
|
+
*/
|
|
3
8
|
export const createFiberRoot = (container) => {
|
|
4
9
|
const instance = reconciler.getInstance();
|
|
5
10
|
return instance.createContainer(container ?? ROOT_NODE_CONTAINER, 0, null, false, null, "", (error) => console.error("Fiber root render error:", error), () => { }, () => { }, () => { }, null);
|