@gtkx/react 0.1.45 → 0.1.47
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 +1 -1
- package/dist/codegen/jsx-generator.d.ts +22 -0
- package/dist/codegen/jsx-generator.js +155 -17
- 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 +9498 -9
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/node.js +5 -2
- package/dist/nodes/text-view.js +1 -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
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ export const App = () => {
|
|
|
68
68
|
defaultHeight={300}
|
|
69
69
|
onCloseRequest={quit}
|
|
70
70
|
>
|
|
71
|
-
<Box orientation={Orientation.VERTICAL} spacing={12}
|
|
71
|
+
<Box orientation={Orientation.VERTICAL} spacing={12} marginStart={20} marginEnd={20} marginTop={20} marginBottom={20}>
|
|
72
72
|
<Label.Root label={`Count: ${count}`} />
|
|
73
73
|
<Button
|
|
74
74
|
label="Increment"
|
|
@@ -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;
|
|
@@ -25,6 +44,7 @@ export declare class JsxGenerator {
|
|
|
25
44
|
private getRequiredConstructorParams;
|
|
26
45
|
private getConstructorParams;
|
|
27
46
|
private generateConstructorArgsMetadata;
|
|
47
|
+
private generatePropSettersMap;
|
|
28
48
|
private generateSetterGetterMap;
|
|
29
49
|
private collectAllProperties;
|
|
30
50
|
private getAncestorInterfaces;
|
|
@@ -34,6 +54,8 @@ export declare class JsxGenerator {
|
|
|
34
54
|
private addNamespacePrefix;
|
|
35
55
|
private buildSignalHandlerType;
|
|
36
56
|
private generateExports;
|
|
57
|
+
private getWrapperExportMembers;
|
|
58
|
+
private generateGenericWrapperComponents;
|
|
37
59
|
private generateJsxNamespace;
|
|
38
60
|
private formatCode;
|
|
39
61
|
}
|
|
@@ -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]));
|
|
@@ -103,6 +118,7 @@ export class JsxGenerator {
|
|
|
103
118
|
this.generateCommonTypes(widgetClass),
|
|
104
119
|
widgetPropsInterfaces,
|
|
105
120
|
this.generateConstructorArgsMetadata(widgets),
|
|
121
|
+
this.generatePropSettersMap(widgets),
|
|
106
122
|
this.generateSetterGetterMap(widgets),
|
|
107
123
|
this.generateExports(widgets, containerMetadata),
|
|
108
124
|
this.generateJsxNamespace(widgets, containerMetadata),
|
|
@@ -116,6 +132,7 @@ export class JsxGenerator {
|
|
|
116
132
|
.map((ns) => `import type * as ${ns} from "@gtkx/ffi/${ns.toLowerCase()}";`);
|
|
117
133
|
return [
|
|
118
134
|
`import "react";`,
|
|
135
|
+
`import { createElement } from "react";`,
|
|
119
136
|
`import type { ReactNode, Ref } from "react";`,
|
|
120
137
|
...externalImports,
|
|
121
138
|
`import type * as Gtk from "@gtkx/ffi/gtk";`,
|
|
@@ -319,14 +336,16 @@ ${widgetPropsContent}
|
|
|
319
336
|
lines.push(`\t * Render function for list items.`);
|
|
320
337
|
lines.push(`\t * Called with null during setup (for loading state) and with the actual item during bind.`);
|
|
321
338
|
lines.push(`\t */`);
|
|
322
|
-
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny:
|
|
339
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic ListView<T> export`);
|
|
323
340
|
lines.push(`\trenderItem: (item: any) => import("react").ReactElement;`);
|
|
324
341
|
}
|
|
325
342
|
if (isDropDownWidget(widget.name)) {
|
|
326
343
|
lines.push("");
|
|
327
344
|
lines.push(`\t/** Function to convert item to display label */`);
|
|
345
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic DropDown<T> export`);
|
|
328
346
|
lines.push(`\titemLabel?: (item: any) => string;`);
|
|
329
347
|
lines.push(`\t/** Called when selection changes */`);
|
|
348
|
+
lines.push(`\t// biome-ignore lint/suspicious/noExplicitAny: Internal type, use generic DropDown<T> export`);
|
|
330
349
|
lines.push(`\tonSelectionChanged?: (item: any, index: number) => void;`);
|
|
331
350
|
}
|
|
332
351
|
if (isTextViewWidget(widget.name)) {
|
|
@@ -392,6 +411,28 @@ ${widgetPropsContent}
|
|
|
392
411
|
}
|
|
393
412
|
return `export const CONSTRUCTOR_PARAMS: Record<string, { name: string; hasDefault: boolean }[]> = {\n${entries.join(",\n")},\n};\n`;
|
|
394
413
|
}
|
|
414
|
+
generatePropSettersMap(widgets) {
|
|
415
|
+
const widgetEntries = [];
|
|
416
|
+
for (const widget of widgets) {
|
|
417
|
+
const propSetterPairs = [];
|
|
418
|
+
const allProps = this.collectAllProperties(widget);
|
|
419
|
+
for (const prop of allProps) {
|
|
420
|
+
if (prop.setter) {
|
|
421
|
+
const propName = toCamelCase(prop.name);
|
|
422
|
+
const setterName = toCamelCase(prop.setter);
|
|
423
|
+
propSetterPairs.push(`"${propName}": "${setterName}"`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (propSetterPairs.length > 0) {
|
|
427
|
+
const widgetName = toPascalCase(widget.name);
|
|
428
|
+
widgetEntries.push(`\t${widgetName}: { ${propSetterPairs.join(", ")} }`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (widgetEntries.length === 0) {
|
|
432
|
+
return `export const PROP_SETTERS: Record<string, Record<string, string>> = {};\n`;
|
|
433
|
+
}
|
|
434
|
+
return `export const PROP_SETTERS: Record<string, Record<string, string>> = {\n${widgetEntries.join(",\n")},\n};\n`;
|
|
435
|
+
}
|
|
395
436
|
generateSetterGetterMap(widgets) {
|
|
396
437
|
const widgetEntries = [];
|
|
397
438
|
for (const widget of widgets) {
|
|
@@ -422,7 +463,7 @@ ${widgetPropsContent}
|
|
|
422
463
|
for (const prop of current.properties) {
|
|
423
464
|
if (!seen.has(prop.name)) {
|
|
424
465
|
seen.add(prop.name);
|
|
425
|
-
props.push({ setter: prop.setter, getter: prop.getter });
|
|
466
|
+
props.push({ name: prop.name, setter: prop.setter, getter: prop.getter });
|
|
426
467
|
}
|
|
427
468
|
}
|
|
428
469
|
for (const ifaceName of current.implements) {
|
|
@@ -431,7 +472,7 @@ ${widgetPropsContent}
|
|
|
431
472
|
for (const prop of iface.properties) {
|
|
432
473
|
if (!seen.has(prop.name)) {
|
|
433
474
|
seen.add(prop.name);
|
|
434
|
-
props.push({ setter: prop.setter, getter: prop.getter });
|
|
475
|
+
props.push({ name: prop.name, setter: prop.setter, getter: prop.getter });
|
|
435
476
|
}
|
|
436
477
|
}
|
|
437
478
|
}
|
|
@@ -546,22 +587,30 @@ ${widgetPropsContent}
|
|
|
546
587
|
isNotebookWidget(widget.name);
|
|
547
588
|
const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
|
|
548
589
|
if (hasMeaningfulSlots) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
];
|
|
560
|
-
if (docComment) {
|
|
561
|
-
lines.push(`${docComment}\nexport const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
590
|
+
// For list widgets, generate wrapper components with proper generics
|
|
591
|
+
if (isListWidget(widget.name) || isColumnViewWidget(widget.name) || isDropDownWidget(widget.name)) {
|
|
592
|
+
const wrapperComponents = this.generateGenericWrapperComponents(widget.name, metadata);
|
|
593
|
+
const exportMembers = this.getWrapperExportMembers(widget.name, metadata);
|
|
594
|
+
if (docComment) {
|
|
595
|
+
lines.push(`${wrapperComponents}\n${docComment}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
lines.push(`${wrapperComponents}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
599
|
+
}
|
|
562
600
|
}
|
|
563
601
|
else {
|
|
564
|
-
|
|
602
|
+
const valueMembers = [
|
|
603
|
+
`Root: "${widgetName}.Root" as const`,
|
|
604
|
+
...metadata.namedChildSlots.map((slot) => `${slot.slotName}: "${widgetName}.${slot.slotName}" as const`),
|
|
605
|
+
...(isGridWidget(widget.name) ? [`Child: "${widgetName}.Child" as const`] : []),
|
|
606
|
+
...(isNotebookWidget(widget.name) ? [`Page: "${widgetName}.Page" as const`] : []),
|
|
607
|
+
];
|
|
608
|
+
if (docComment) {
|
|
609
|
+
lines.push(`${docComment}\nexport const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
lines.push(`export const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
613
|
+
}
|
|
565
614
|
}
|
|
566
615
|
}
|
|
567
616
|
else {
|
|
@@ -575,6 +624,95 @@ ${widgetPropsContent}
|
|
|
575
624
|
}
|
|
576
625
|
return `${lines.join("\n")}\n`;
|
|
577
626
|
}
|
|
627
|
+
getWrapperExportMembers(widgetName, metadata) {
|
|
628
|
+
const name = toPascalCase(widgetName);
|
|
629
|
+
const members = [`Root: ${name}Root`];
|
|
630
|
+
if (isListWidget(widgetName)) {
|
|
631
|
+
members.push(`Item: ${name}Item`);
|
|
632
|
+
}
|
|
633
|
+
else if (isColumnViewWidget(widgetName)) {
|
|
634
|
+
members.push(`Column: ${name}Column`);
|
|
635
|
+
members.push(`Item: ${name}Item`);
|
|
636
|
+
}
|
|
637
|
+
else if (isDropDownWidget(widgetName)) {
|
|
638
|
+
members.push(`Item: ${name}Item`);
|
|
639
|
+
}
|
|
640
|
+
// Add named child slots
|
|
641
|
+
for (const slot of metadata.namedChildSlots) {
|
|
642
|
+
members.push(`${slot.slotName}: ${name}${slot.slotName}`);
|
|
643
|
+
}
|
|
644
|
+
return members;
|
|
645
|
+
}
|
|
646
|
+
generateGenericWrapperComponents(widgetName, metadata) {
|
|
647
|
+
const name = toPascalCase(widgetName);
|
|
648
|
+
const lines = [];
|
|
649
|
+
if (isListWidget(widgetName)) {
|
|
650
|
+
// Props type for the generic Root component
|
|
651
|
+
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "renderItem"> {`);
|
|
652
|
+
lines.push(`\t/** Render function for list items. Called with null during setup. */`);
|
|
653
|
+
lines.push(`\trenderItem: (item: T | null) => import("react").ReactElement;`);
|
|
654
|
+
lines.push(`}`);
|
|
655
|
+
lines.push(``);
|
|
656
|
+
// Root wrapper component
|
|
657
|
+
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
658
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
659
|
+
lines.push(`}`);
|
|
660
|
+
lines.push(``);
|
|
661
|
+
// Item wrapper component
|
|
662
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
663
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
664
|
+
lines.push(`}`);
|
|
665
|
+
}
|
|
666
|
+
else if (isColumnViewWidget(widgetName)) {
|
|
667
|
+
// Root wrapper (non-generic)
|
|
668
|
+
lines.push(`function ${name}Root(props: ${name}Props): import("react").ReactElement {`);
|
|
669
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
670
|
+
lines.push(`}`);
|
|
671
|
+
lines.push(``);
|
|
672
|
+
// Column props type - use GenericColumnProps to avoid conflict with imported ColumnViewColumnProps
|
|
673
|
+
lines.push(`interface ${name}GenericColumnProps<T> extends Omit<ColumnViewColumnProps, "renderCell"> {`);
|
|
674
|
+
lines.push(`\t/** Render function for column cells. Called with null during setup. */`);
|
|
675
|
+
lines.push(`\trenderCell: (item: T | null) => import("react").ReactElement;`);
|
|
676
|
+
lines.push(`}`);
|
|
677
|
+
lines.push(``);
|
|
678
|
+
// Column wrapper component
|
|
679
|
+
lines.push(`function ${name}Column<T>(props: ${name}GenericColumnProps<T>): import("react").ReactElement {`);
|
|
680
|
+
lines.push(`\treturn createElement("${name}.Column", props);`);
|
|
681
|
+
lines.push(`}`);
|
|
682
|
+
lines.push(``);
|
|
683
|
+
// Item wrapper component
|
|
684
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
685
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
686
|
+
lines.push(`}`);
|
|
687
|
+
}
|
|
688
|
+
else if (isDropDownWidget(widgetName)) {
|
|
689
|
+
// Props type for the generic Root component
|
|
690
|
+
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "itemLabel" | "onSelectionChanged"> {`);
|
|
691
|
+
lines.push(`\t/** Function to convert item to display label */`);
|
|
692
|
+
lines.push(`\titemLabel?: (item: T) => string;`);
|
|
693
|
+
lines.push(`\t/** Called when selection changes */`);
|
|
694
|
+
lines.push(`\tonSelectionChanged?: (item: T, index: number) => void;`);
|
|
695
|
+
lines.push(`}`);
|
|
696
|
+
lines.push(``);
|
|
697
|
+
// Root wrapper component
|
|
698
|
+
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
699
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
700
|
+
lines.push(`}`);
|
|
701
|
+
lines.push(``);
|
|
702
|
+
// Item wrapper component
|
|
703
|
+
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
704
|
+
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
705
|
+
lines.push(`}`);
|
|
706
|
+
}
|
|
707
|
+
// Generate slot wrapper components
|
|
708
|
+
for (const slot of metadata.namedChildSlots) {
|
|
709
|
+
lines.push(``);
|
|
710
|
+
lines.push(`function ${name}${slot.slotName}(props: SlotProps): import("react").ReactElement {`);
|
|
711
|
+
lines.push(`\treturn createElement("${name}.${slot.slotName}", props);`);
|
|
712
|
+
lines.push(`}`);
|
|
713
|
+
}
|
|
714
|
+
return lines.join("\n");
|
|
715
|
+
}
|
|
578
716
|
generateJsxNamespace(widgets, containerMetadata) {
|
|
579
717
|
const elements = [];
|
|
580
718
|
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);
|