@gtkx/react 0.1.11

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.
Files changed (45) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +390 -0
  3. package/dist/codegen/jsx-generator.d.ts +37 -0
  4. package/dist/codegen/jsx-generator.js +554 -0
  5. package/dist/factory.d.ts +3 -0
  6. package/dist/factory.js +59 -0
  7. package/dist/generated/jsx.d.ts +1598 -0
  8. package/dist/generated/jsx.js +264 -0
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +14 -0
  11. package/dist/node.d.ts +13 -0
  12. package/dist/node.js +1 -0
  13. package/dist/nodes/action-bar.d.ts +27 -0
  14. package/dist/nodes/action-bar.js +88 -0
  15. package/dist/nodes/dialog.d.ts +19 -0
  16. package/dist/nodes/dialog.js +87 -0
  17. package/dist/nodes/dropdown.d.ts +41 -0
  18. package/dist/nodes/dropdown.js +163 -0
  19. package/dist/nodes/grid.d.ts +41 -0
  20. package/dist/nodes/grid.js +140 -0
  21. package/dist/nodes/list.d.ts +46 -0
  22. package/dist/nodes/list.js +165 -0
  23. package/dist/nodes/notebook.d.ts +25 -0
  24. package/dist/nodes/notebook.js +88 -0
  25. package/dist/nodes/overlay.d.ts +29 -0
  26. package/dist/nodes/overlay.js +109 -0
  27. package/dist/nodes/slot.d.ts +17 -0
  28. package/dist/nodes/slot.js +55 -0
  29. package/dist/nodes/text.d.ts +16 -0
  30. package/dist/nodes/text.js +31 -0
  31. package/dist/nodes/widget.d.ts +19 -0
  32. package/dist/nodes/widget.js +136 -0
  33. package/dist/portal.d.ts +3 -0
  34. package/dist/portal.js +11 -0
  35. package/dist/reconciler.d.ts +20 -0
  36. package/dist/reconciler.js +111 -0
  37. package/dist/render.d.ts +5 -0
  38. package/dist/render.js +12 -0
  39. package/dist/signal-utils.d.ts +4 -0
  40. package/dist/signal-utils.js +7 -0
  41. package/dist/types.d.ts +13 -0
  42. package/dist/types.js +1 -0
  43. package/dist/widget-capabilities.d.ts +46 -0
  44. package/dist/widget-capabilities.js +32 -0
  45. package/package.json +52 -0
@@ -0,0 +1,554 @@
1
+ import { toCamelCase, toPascalCase } from "@gtkx/gir";
2
+ import { format } from "prettier";
3
+ const LIST_WIDGETS = new Set(["ListView", "ColumnView", "GridView"]);
4
+ const DROPDOWN_WIDGETS = new Set(["DropDown"]);
5
+ const GRID_WIDGETS = new Set(["Grid"]);
6
+ const INTERNALLY_PROVIDED_PARAMS = {
7
+ ApplicationWindow: new Set(["application"]),
8
+ };
9
+ const isPrimitive = (tsType) => {
10
+ const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
11
+ return primitives.has(tsType);
12
+ };
13
+ const toJsxPropertyType = (tsType) => {
14
+ let result = tsType;
15
+ if (result.startsWith("Ref<")) {
16
+ result = result.replace(/^Ref<(.+)>$/, "$1");
17
+ }
18
+ if (isPrimitive(result))
19
+ return result;
20
+ if (result.endsWith("[]")) {
21
+ const elementType = result.slice(0, -2);
22
+ if (isPrimitive(elementType))
23
+ return result;
24
+ }
25
+ if (result.includes(".") || result.includes("<") || result.includes("("))
26
+ return result;
27
+ return `Gtk.${result}`;
28
+ };
29
+ const isListWidget = (widgetName) => LIST_WIDGETS.has(widgetName);
30
+ const isDropDownWidget = (widgetName) => DROPDOWN_WIDGETS.has(widgetName);
31
+ const isGridWidget = (widgetName) => GRID_WIDGETS.has(widgetName);
32
+ const sanitizeDoc = (doc) => {
33
+ let result = doc;
34
+ result = result.replace(/<picture>[\s\S]*?<\/picture>/gi, "");
35
+ result = result.replace(/<img[^>]*>/gi, "");
36
+ result = result.replace(/<source[^>]*>/gi, "");
37
+ result = result.replace(/!\[[^\]]*\]\([^)]+\.png\)/gi, "");
38
+ result = result.replace(/<kbd>([^<]*)<\/kbd>/gi, "`$1`");
39
+ result = result.replace(/<kbd>/gi, "`");
40
+ result = result.replace(/<\/kbd>/gi, "`");
41
+ result = result.replace(/\[([^\]]+)\]\([^)]+\.html[^)]*\)/gi, "$1");
42
+ result = result.replace(/@(\w+)\s/g, "`$1` ");
43
+ return result.trim();
44
+ };
45
+ const formatDoc = (doc, indent = "") => {
46
+ if (!doc)
47
+ return "";
48
+ const sanitized = sanitizeDoc(doc);
49
+ if (!sanitized)
50
+ return "";
51
+ const lines = sanitized.split("\n").map((line) => line.trim());
52
+ const firstLine = lines[0] ?? "";
53
+ if (lines.length === 1 && firstLine.length < 80) {
54
+ return `${indent}/** ${firstLine} */\n`;
55
+ }
56
+ const formattedLines = lines.map((line) => `${indent} * ${line}`);
57
+ return `${indent}/**\n${formattedLines.join("\n")}\n${indent} */\n`;
58
+ };
59
+ const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
60
+ const className = typeName.includes(".") ? typeName.split(".")[1] : typeName;
61
+ if (!className || visited.has(className))
62
+ return false;
63
+ visited.add(className);
64
+ const cls = classMap.get(className);
65
+ if (!cls)
66
+ return false;
67
+ if (className === "Widget")
68
+ return true;
69
+ return cls.parent ? isWidgetSubclass(cls.parent, classMap, visited) : false;
70
+ };
71
+ export class JsxGenerator {
72
+ typeMapper;
73
+ options;
74
+ classMap = new Map();
75
+ interfaceMap = new Map();
76
+ namespace = "";
77
+ usedExternalNamespaces = new Set();
78
+ widgetPropertyNames = new Set();
79
+ widgetSignalNames = new Set();
80
+ constructor(typeMapper, options = {}) {
81
+ this.typeMapper = typeMapper;
82
+ this.options = options;
83
+ }
84
+ async generate(namespace, classMap) {
85
+ this.classMap = classMap;
86
+ this.interfaceMap = new Map(namespace.interfaces.map((iface) => [iface.name, iface]));
87
+ this.namespace = namespace.name;
88
+ this.usedExternalNamespaces.clear();
89
+ const widgets = this.findWidgets(namespace, classMap);
90
+ const containerMetadata = this.buildContainerMetadata(widgets, classMap);
91
+ const widgetClass = classMap.get("Widget");
92
+ this.widgetPropertyNames = new Set(widgetClass?.properties.map((p) => toCamelCase(p.name)) ?? []);
93
+ this.widgetSignalNames = new Set(widgetClass?.signals.map((s) => toCamelCase(s.name)) ?? []);
94
+ const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(widgets, containerMetadata);
95
+ const sections = [
96
+ this.generateImports(),
97
+ this.generateCommonTypes(widgetClass),
98
+ widgetPropsInterfaces,
99
+ this.generateConstructorArgsMetadata(widgets),
100
+ this.generateExports(widgets, containerMetadata),
101
+ this.generateJsxNamespace(widgets, containerMetadata),
102
+ "export {};",
103
+ ];
104
+ return this.formatCode(sections.join("\n"));
105
+ }
106
+ generateImports() {
107
+ const externalImports = [...this.usedExternalNamespaces]
108
+ .sort()
109
+ .map((ns) => `import type * as ${ns} from "@gtkx/ffi/${ns.toLowerCase()}";`);
110
+ return [
111
+ `import type { ReactNode, Ref } from "react";`,
112
+ ...externalImports,
113
+ `import type * as Gtk from "@gtkx/ffi/gtk";`,
114
+ `import type { GridChildProps, ItemProps, SlotProps } from "../types.js";`,
115
+ "",
116
+ ].join("\n");
117
+ }
118
+ generateCommonTypes(widgetClass) {
119
+ const widgetPropsContent = this.generateWidgetPropsContent(widgetClass);
120
+ return `
121
+ export { SlotProps, GridChildProps, ItemProps };
122
+
123
+ ${widgetPropsContent}
124
+ `;
125
+ }
126
+ generateWidgetPropsContent(widgetClass) {
127
+ const lines = [];
128
+ const widgetDoc = widgetClass?.doc ? formatDoc(widgetClass.doc) : "";
129
+ if (widgetDoc) {
130
+ lines.push(widgetDoc.trimEnd());
131
+ }
132
+ lines.push("export interface WidgetProps {");
133
+ if (widgetClass) {
134
+ for (const prop of widgetClass.properties) {
135
+ const propName = toCamelCase(prop.name);
136
+ const tsType = toJsxPropertyType(this.typeMapper.mapType(prop.type).ts);
137
+ if (prop.doc) {
138
+ lines.push(formatDoc(prop.doc, "\t").trimEnd());
139
+ }
140
+ lines.push(`\t${propName}?: ${tsType};`);
141
+ }
142
+ if (widgetClass.signals.length > 0) {
143
+ lines.push("");
144
+ for (const signal of widgetClass.signals) {
145
+ if (signal.doc) {
146
+ lines.push(formatDoc(signal.doc, "\t").trimEnd());
147
+ }
148
+ lines.push(`\t${this.generateSignalHandler(signal)}`);
149
+ }
150
+ }
151
+ }
152
+ lines.push("");
153
+ lines.push("\tchildren?: ReactNode;");
154
+ lines.push("}");
155
+ return lines.join("\n");
156
+ }
157
+ buildContainerMetadata(widgets, classMap) {
158
+ const metadata = new Map();
159
+ for (const widget of widgets) {
160
+ metadata.set(widget.name, this.analyzeContainerCapabilities(widget, classMap));
161
+ }
162
+ return metadata;
163
+ }
164
+ findWidgets(namespace, classMap) {
165
+ const widgetCache = new Map();
166
+ const checkIsWidget = (className) => {
167
+ const cached = widgetCache.get(className);
168
+ if (cached !== undefined)
169
+ return cached;
170
+ const cls = classMap.get(className);
171
+ if (!cls) {
172
+ widgetCache.set(className, false);
173
+ return false;
174
+ }
175
+ if (cls.name === "Widget") {
176
+ widgetCache.set(className, true);
177
+ return true;
178
+ }
179
+ const result = cls.parent ? checkIsWidget(cls.parent) : false;
180
+ widgetCache.set(className, result);
181
+ return result;
182
+ };
183
+ const widgets = namespace.classes.filter((cls) => checkIsWidget(cls.name));
184
+ return widgets.sort((a, b) => {
185
+ if (a.name === "Widget")
186
+ return -1;
187
+ if (b.name === "Widget")
188
+ return 1;
189
+ if (a.name === "Window")
190
+ return -1;
191
+ if (b.name === "Window")
192
+ return 1;
193
+ return a.name.localeCompare(b.name);
194
+ });
195
+ }
196
+ analyzeContainerCapabilities(widget, classMap) {
197
+ const hasAppend = widget.methods.some((m) => m.name === "append");
198
+ const hasSetChild = widget.methods.some((m) => m.name === "set_child");
199
+ const namedChildSlots = widget.properties
200
+ .filter((prop) => {
201
+ if (!prop.writable)
202
+ return false;
203
+ const typeName = prop.type.name;
204
+ return typeName === "Gtk.Widget" || typeName === "Widget" || isWidgetSubclass(typeName, classMap);
205
+ })
206
+ .map((prop) => ({
207
+ propertyName: prop.name,
208
+ slotName: toPascalCase(prop.name),
209
+ }));
210
+ return {
211
+ supportsMultipleChildren: hasAppend,
212
+ supportsSingleChild: hasSetChild,
213
+ namedChildSlots,
214
+ };
215
+ }
216
+ generateWidgetPropsInterfaces(widgets, containerMetadata) {
217
+ const sections = [];
218
+ for (const widget of widgets) {
219
+ if (widget.name === "Widget")
220
+ continue;
221
+ const metadata = containerMetadata.get(widget.name);
222
+ if (!metadata)
223
+ throw new Error(`Missing container metadata for widget: ${widget.name}`);
224
+ sections.push(this.generateWidgetProps(widget, metadata));
225
+ }
226
+ return sections.join("\n");
227
+ }
228
+ generateWidgetProps(widget, metadata) {
229
+ const widgetName = toPascalCase(widget.name);
230
+ const parentPropsName = this.getParentPropsName(widget);
231
+ const namedChildPropNames = new Set(metadata.namedChildSlots.map((s) => toCamelCase(s.propertyName)));
232
+ const lines = [];
233
+ if (widget.doc) {
234
+ lines.push(formatDoc(widget.doc).trimEnd());
235
+ }
236
+ lines.push(`export interface ${widgetName}Props extends ${parentPropsName} {`);
237
+ const requiredCtorParams = this.getRequiredConstructorParams(widget);
238
+ const seenProps = new Set();
239
+ const allProps = [...widget.properties];
240
+ for (const prop of widget.properties) {
241
+ seenProps.add(prop.name);
242
+ }
243
+ const seenSignals = new Set();
244
+ const allSignals = [...widget.signals];
245
+ for (const signal of widget.signals) {
246
+ seenSignals.add(signal.name);
247
+ }
248
+ const parentInterfaces = this.getAncestorInterfaces(widget);
249
+ for (const ifaceName of widget.implements) {
250
+ if (parentInterfaces.has(ifaceName))
251
+ continue;
252
+ const iface = this.interfaceMap.get(ifaceName);
253
+ if (iface) {
254
+ for (const prop of iface.properties) {
255
+ if (!seenProps.has(prop.name)) {
256
+ seenProps.add(prop.name);
257
+ allProps.push(prop);
258
+ }
259
+ }
260
+ for (const signal of iface.signals) {
261
+ if (!seenSignals.has(signal.name)) {
262
+ seenSignals.add(signal.name);
263
+ allSignals.push(signal);
264
+ }
265
+ }
266
+ }
267
+ }
268
+ const specificProps = allProps.filter((prop) => {
269
+ const propName = toCamelCase(prop.name);
270
+ return !this.widgetPropertyNames.has(propName) && !namedChildPropNames.has(propName);
271
+ });
272
+ const emittedProps = new Set();
273
+ for (const prop of specificProps) {
274
+ const propName = toCamelCase(prop.name);
275
+ emittedProps.add(prop.name);
276
+ const typeMapping = this.typeMapper.mapType(prop.type);
277
+ const tsType = toJsxPropertyType(typeMapping.ts);
278
+ const isRequiredByProperty = prop.constructOnly && !prop.hasDefault;
279
+ const isRequiredByConstructor = requiredCtorParams.has(prop.name);
280
+ const isRequired = isRequiredByProperty || isRequiredByConstructor;
281
+ if (prop.doc) {
282
+ lines.push(formatDoc(prop.doc, "\t").trimEnd());
283
+ }
284
+ lines.push(`\t${propName}${isRequired ? "" : "?"}: ${tsType};`);
285
+ }
286
+ for (const paramName of requiredCtorParams) {
287
+ if (emittedProps.has(paramName))
288
+ continue;
289
+ const propName = toCamelCase(paramName);
290
+ const inheritedProp = this.findInheritedProperty(widget, paramName);
291
+ if (inheritedProp) {
292
+ const typeMapping = this.typeMapper.mapType(inheritedProp.type);
293
+ const tsType = toJsxPropertyType(typeMapping.ts);
294
+ lines.push(`\t${propName}: ${tsType};`);
295
+ }
296
+ }
297
+ const specificSignals = allSignals.filter((signal) => {
298
+ const signalName = toCamelCase(signal.name);
299
+ return !this.widgetSignalNames.has(signalName);
300
+ });
301
+ if (specificSignals.length > 0) {
302
+ lines.push("");
303
+ for (const signal of specificSignals) {
304
+ if (signal.doc) {
305
+ lines.push(formatDoc(signal.doc, "\t").trimEnd());
306
+ }
307
+ lines.push(`\t${this.generateSignalHandler(signal)}`);
308
+ }
309
+ }
310
+ if (isListWidget(widget.name)) {
311
+ lines.push("");
312
+ lines.push(`\t/** Function to render each item as a GTK widget */`);
313
+ lines.push(`\trenderItem?: (item: any) => Gtk.Widget;`);
314
+ }
315
+ if (isDropDownWidget(widget.name)) {
316
+ lines.push("");
317
+ lines.push(`\t/** Function to convert item to display label */`);
318
+ lines.push(`\titemLabel?: (item: any) => string;`);
319
+ lines.push(`\t/** Called when selection changes */`);
320
+ lines.push(`\tonSelectionChanged?: (item: any, index: number) => void;`);
321
+ }
322
+ lines.push("");
323
+ lines.push(`\tref?: Ref<Gtk.${widgetName}>;`);
324
+ lines.push(`}`);
325
+ return `${lines.join("\n")}\n`;
326
+ }
327
+ getParentPropsName(widget) {
328
+ if (widget.name === "Window")
329
+ return "WidgetProps";
330
+ if (widget.name === "ApplicationWindow")
331
+ return "WindowProps";
332
+ if (widget.parent === "Widget")
333
+ return "WidgetProps";
334
+ if (widget.parent === "Window")
335
+ return "WindowProps";
336
+ return widget.parent ? `${toPascalCase(widget.parent)}Props` : "WidgetProps";
337
+ }
338
+ getRequiredConstructorParams(widget) {
339
+ const required = new Set();
340
+ const mainCtor = widget.constructors.find((c) => c.name === "new");
341
+ if (!mainCtor)
342
+ return required;
343
+ const internallyProvided = INTERNALLY_PROVIDED_PARAMS[widget.name] ?? new Set();
344
+ for (const param of mainCtor.parameters) {
345
+ if (!param.nullable && !param.optional) {
346
+ const normalizedName = param.name.replace(/_/g, "-");
347
+ if (!internallyProvided.has(normalizedName)) {
348
+ required.add(normalizedName);
349
+ }
350
+ }
351
+ }
352
+ return required;
353
+ }
354
+ getConstructorParams(widget) {
355
+ const mainCtor = widget.constructors.find((c) => c.name === "new");
356
+ if (!mainCtor)
357
+ return [];
358
+ return mainCtor.parameters.map((param) => ({
359
+ name: toCamelCase(param.name),
360
+ hasDefault: param.nullable || param.optional || false,
361
+ }));
362
+ }
363
+ generateConstructorArgsMetadata(widgets) {
364
+ const entries = [];
365
+ for (const widget of widgets) {
366
+ const params = this.getConstructorParams(widget);
367
+ if (params.length === 0)
368
+ continue;
369
+ const widgetName = toPascalCase(widget.name);
370
+ const paramStrs = params.map((p) => `{ name: "${p.name}", hasDefault: ${p.hasDefault} }`);
371
+ entries.push(`\t${widgetName}: [${paramStrs.join(", ")}]`);
372
+ }
373
+ if (entries.length === 0) {
374
+ return `export const CONSTRUCTOR_PARAMS: Record<string, { name: string; hasDefault: boolean }[]> = {};\n`;
375
+ }
376
+ return `export const CONSTRUCTOR_PARAMS: Record<string, { name: string; hasDefault: boolean }[]> = {\n${entries.join(",\n")},\n};\n`;
377
+ }
378
+ getAncestorInterfaces(widget) {
379
+ const interfaces = new Set();
380
+ let current = widget.parent ? this.classMap.get(widget.parent) : undefined;
381
+ while (current) {
382
+ for (const ifaceName of current.implements) {
383
+ interfaces.add(ifaceName);
384
+ }
385
+ current = current.parent ? this.classMap.get(current.parent) : undefined;
386
+ }
387
+ return interfaces;
388
+ }
389
+ findInheritedProperty(widget, propName) {
390
+ let current = widget.parent ? this.classMap.get(widget.parent) : undefined;
391
+ while (current) {
392
+ const prop = current.properties.find((p) => p.name === propName);
393
+ if (prop)
394
+ return prop;
395
+ for (const ifaceName of current.implements) {
396
+ const iface = this.interfaceMap.get(ifaceName);
397
+ if (iface) {
398
+ const ifaceProp = iface.properties.find((p) => p.name === propName);
399
+ if (ifaceProp)
400
+ return ifaceProp;
401
+ }
402
+ }
403
+ current = current.parent ? this.classMap.get(current.parent) : undefined;
404
+ }
405
+ for (const ifaceName of widget.implements) {
406
+ const iface = this.interfaceMap.get(ifaceName);
407
+ if (iface) {
408
+ const ifaceProp = iface.properties.find((p) => p.name === propName);
409
+ if (ifaceProp)
410
+ return ifaceProp;
411
+ }
412
+ }
413
+ return undefined;
414
+ }
415
+ generateSignalHandler(signal) {
416
+ const signalName = toCamelCase(signal.name);
417
+ const handlerName = `on${signalName.charAt(0).toUpperCase()}${signalName.slice(1)}`;
418
+ const handlerType = this.buildSignalHandlerType(signal);
419
+ return `${handlerName}?: ${handlerType};`;
420
+ }
421
+ getSignalParamFfiType(typeName) {
422
+ if (!typeName)
423
+ return undefined;
424
+ if (typeName.includes(".")) {
425
+ const [ns, className] = typeName.split(".", 2);
426
+ if (ns === this.namespace && className && this.classMap.has(className)) {
427
+ return `Gtk.${toPascalCase(className)}`;
428
+ }
429
+ if (ns) {
430
+ this.usedExternalNamespaces.add(ns);
431
+ return typeName;
432
+ }
433
+ return undefined;
434
+ }
435
+ const normalizedName = toPascalCase(typeName);
436
+ if (this.classMap.has(typeName) || this.classMap.has(normalizedName)) {
437
+ return `Gtk.${normalizedName}`;
438
+ }
439
+ return undefined;
440
+ }
441
+ addNamespacePrefix(tsType) {
442
+ const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
443
+ if (primitives.has(tsType))
444
+ return tsType;
445
+ if (tsType.includes(".") || tsType.includes("<") || tsType.includes("("))
446
+ return tsType;
447
+ return `Gtk.${tsType}`;
448
+ }
449
+ buildSignalHandlerType(signal) {
450
+ const params = signal.parameters
451
+ ?.map((p) => {
452
+ const ffiType = this.getSignalParamFfiType(p.type.name);
453
+ if (ffiType) {
454
+ return `${toCamelCase(p.name)}: ${ffiType}`;
455
+ }
456
+ const paramMapping = this.typeMapper.mapParameter(p);
457
+ const tsType = this.addNamespacePrefix(paramMapping.ts);
458
+ return `${toCamelCase(p.name)}: ${tsType}`;
459
+ })
460
+ .join(", ") ?? "";
461
+ const returnType = signal.returnType
462
+ ? this.addNamespacePrefix(this.typeMapper.mapType(signal.returnType).ts)
463
+ : "void";
464
+ return params ? `(${params}) => ${returnType}` : `() => ${returnType}`;
465
+ }
466
+ generateExports(widgets, containerMetadata) {
467
+ const lines = [];
468
+ for (const widget of widgets) {
469
+ const widgetName = toPascalCase(widget.name);
470
+ const metadata = containerMetadata.get(widget.name);
471
+ if (!metadata)
472
+ throw new Error(`Missing container metadata for widget: ${widget.name}`);
473
+ const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
474
+ const hasMeaningfulSlots = nonChildSlots.length > 0 ||
475
+ isListWidget(widget.name) ||
476
+ isDropDownWidget(widget.name) ||
477
+ isGridWidget(widget.name);
478
+ if (hasMeaningfulSlots) {
479
+ const valueMembers = [
480
+ `Root: "${widgetName}.Root" as const`,
481
+ ...metadata.namedChildSlots.map((slot) => `${slot.slotName}: "${widgetName}.${slot.slotName}" as const`),
482
+ ...(isListWidget(widget.name) ? [`Item: "${widgetName}.Item" as const`] : []),
483
+ ...(isDropDownWidget(widget.name) ? [`Item: "${widgetName}.Item" as const`] : []),
484
+ ...(isGridWidget(widget.name) ? [`Child: "${widgetName}.Child" as const`] : []),
485
+ ];
486
+ lines.push(`export const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
487
+ }
488
+ else {
489
+ lines.push(`export const ${widgetName} = "${widgetName}" as const;`);
490
+ }
491
+ }
492
+ return `${lines.join("\n")}\n`;
493
+ }
494
+ generateJsxNamespace(widgets, containerMetadata) {
495
+ const elements = [];
496
+ for (const widget of widgets) {
497
+ if (widget.name === "Widget")
498
+ continue;
499
+ const widgetName = toPascalCase(widget.name);
500
+ const propsName = `${widgetName}Props`;
501
+ const metadata = containerMetadata.get(widget.name);
502
+ if (!metadata)
503
+ throw new Error(`Missing container metadata for widget: ${widget.name}`);
504
+ const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
505
+ const hasMeaningfulSlots = nonChildSlots.length > 0 ||
506
+ isListWidget(widget.name) ||
507
+ isDropDownWidget(widget.name) ||
508
+ isGridWidget(widget.name);
509
+ if (hasMeaningfulSlots) {
510
+ elements.push(`"${widgetName}.Root": ${propsName};`);
511
+ }
512
+ else {
513
+ elements.push(`${widgetName}: ${propsName};`);
514
+ }
515
+ for (const slot of metadata.namedChildSlots) {
516
+ elements.push(`"${widgetName}.${slot.slotName}": SlotProps;`);
517
+ }
518
+ if (isListWidget(widget.name)) {
519
+ elements.push(`"${widgetName}.Item": ItemProps<any>;`);
520
+ }
521
+ if (isDropDownWidget(widget.name)) {
522
+ elements.push(`"${widgetName}.Item": ItemProps<any>;`);
523
+ }
524
+ if (isGridWidget(widget.name)) {
525
+ elements.push(`"${widgetName}.Child": GridChildProps;`);
526
+ }
527
+ }
528
+ return `
529
+ declare module "react" {
530
+ \tnamespace JSX {
531
+ \t\tinterface IntrinsicElements {
532
+ \t\t\t${elements.join("\n\t\t\t")}
533
+ \t\t}
534
+ \t}
535
+ }
536
+ `;
537
+ }
538
+ async formatCode(code) {
539
+ try {
540
+ return await format(code, {
541
+ parser: "typescript",
542
+ ...(this.options.prettierConfig &&
543
+ typeof this.options.prettierConfig === "object" &&
544
+ this.options.prettierConfig !== null
545
+ ? this.options.prettierConfig
546
+ : {}),
547
+ });
548
+ }
549
+ catch (error) {
550
+ console.warn("Failed to format code:", error);
551
+ return code;
552
+ }
553
+ }
554
+ }
@@ -0,0 +1,3 @@
1
+ import type { Node } from "./node.js";
2
+ export type Props = Record<string, unknown>;
3
+ export declare const createNode: (type: string, props: Props, currentApp: unknown) => Node;
@@ -0,0 +1,59 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { CONSTRUCTOR_PARAMS } from "./generated/jsx.js";
3
+ import { ActionBarNode } from "./nodes/action-bar.js";
4
+ import { DialogNode } from "./nodes/dialog.js";
5
+ import { DropDownItemNode, DropDownNode } from "./nodes/dropdown.js";
6
+ import { GridChildNode, GridNode } from "./nodes/grid.js";
7
+ import { ListItemNode, ListViewNode } from "./nodes/list.js";
8
+ import { NotebookNode } from "./nodes/notebook.js";
9
+ import { OverlayNode } from "./nodes/overlay.js";
10
+ import { SlotNode } from "./nodes/slot.js";
11
+ import { WidgetNode } from "./nodes/widget.js";
12
+ const NODE_CLASSES = [
13
+ SlotNode,
14
+ ListItemNode,
15
+ DropDownItemNode,
16
+ DropDownNode,
17
+ GridChildNode,
18
+ GridNode,
19
+ NotebookNode,
20
+ OverlayNode,
21
+ ActionBarNode,
22
+ ListViewNode,
23
+ DialogNode,
24
+ WidgetNode,
25
+ ];
26
+ const extractConstructorArgs = (type, props) => {
27
+ const params = CONSTRUCTOR_PARAMS[type];
28
+ if (!params)
29
+ return [];
30
+ return params.map((p) => props[p.name]);
31
+ };
32
+ const createWidget = (type, props, currentApp) => {
33
+ const WidgetClass = Gtk[type];
34
+ if (!WidgetClass)
35
+ throw new Error(`Unknown GTK widget type: ${type}`);
36
+ if (type === "ApplicationWindow") {
37
+ return new WidgetClass(currentApp);
38
+ }
39
+ const args = extractConstructorArgs(type, props);
40
+ return new WidgetClass(...args);
41
+ };
42
+ const normalizeType = (type) => (type.endsWith(".Root") ? type.slice(0, -5) : type);
43
+ export const createNode = (type, props, currentApp) => {
44
+ const normalizedType = normalizeType(type);
45
+ let widget = null;
46
+ for (const NodeClass of NODE_CLASSES) {
47
+ if (NodeClass.needsWidget && !widget) {
48
+ widget = createWidget(normalizedType, props, currentApp);
49
+ }
50
+ if (NodeClass.matches(type, widget)) {
51
+ const node = new NodeClass(type, widget, props);
52
+ if (NodeClass.needsWidget) {
53
+ node.updateProps({}, props);
54
+ }
55
+ return node;
56
+ }
57
+ }
58
+ throw new Error(`No matching node class for type: ${type}`);
59
+ };