@gtkx/react 0.5.2 → 0.6.1

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
@@ -58,7 +58,7 @@ const App = () => {
58
58
  return (
59
59
  <ApplicationWindow title="Counter" onCloseRequest={quit}>
60
60
  <Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
61
- <Label.Root label={`Count: ${count}`} />
61
+ <Label label={`Count: ${count}`} />
62
62
  <Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
63
63
  </Box>
64
64
  </ApplicationWindow>
@@ -1,4 +1,4 @@
1
- import type { GirClass, GirNamespace, TypeMapper } from "@gtkx/gir";
1
+ import type { GirClass, GirNamespace, TypeMapper, TypeRegistry } from "@gtkx/gir";
2
2
  /**
3
3
  * Configuration options for the JSX type generator.
4
4
  */
@@ -21,26 +21,29 @@ interface JsxGeneratorResult {
21
21
  */
22
22
  export declare class JsxGenerator {
23
23
  private typeMapper;
24
- private options;
24
+ private typeRegistry;
25
25
  private classMap;
26
+ private options;
26
27
  private interfaceMap;
27
- private namespace;
28
- private usedExternalNamespaces;
28
+ private usedNamespaces;
29
29
  private widgetPropertyNames;
30
30
  private widgetSignalNames;
31
+ private currentNamespace;
32
+ private widgetNamespaceMap;
31
33
  /**
32
34
  * Creates a new JSX generator.
33
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
34
38
  * @param options - Generator configuration options
35
39
  */
36
- constructor(typeMapper: TypeMapper, options?: JsxGeneratorOptions);
40
+ constructor(typeMapper: TypeMapper, typeRegistry: TypeRegistry, classMap: Map<string, GirClass>, options?: JsxGeneratorOptions);
37
41
  /**
38
- * Generates JSX type definitions for all widgets in a namespace.
39
- * @param namespace - The parsed GIR namespace
40
- * @param classMap - Map of class names to class definitions
42
+ * Generates JSX type definitions for all widgets in the given namespaces.
43
+ * @param namespaces - The parsed GIR namespaces (GTK must be first)
41
44
  * @returns Generated TypeScript code as public jsx.ts and internal.ts files
42
45
  */
43
- generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<JsxGeneratorResult>;
46
+ generate(namespaces: GirNamespace[]): Promise<JsxGeneratorResult>;
44
47
  private generateImports;
45
48
  private generateInternalImports;
46
49
  private generateCommonTypes;
@@ -49,6 +52,7 @@ export declare class JsxGenerator {
49
52
  private findWidgets;
50
53
  private analyzeContainerCapabilities;
51
54
  private generateWidgetPropsInterfaces;
55
+ private getWidgetExportName;
52
56
  private generateWidgetProps;
53
57
  private getParentPropsName;
54
58
  private getRequiredConstructorParams;
@@ -56,12 +60,14 @@ export declare class JsxGenerator {
56
60
  private generateConstructorArgsMetadata;
57
61
  private generatePropSettersMap;
58
62
  private generateSetterGetterMap;
63
+ private collectParentMethodNames;
59
64
  private collectAllProperties;
60
65
  private getAncestorInterfaces;
61
66
  private findInheritedProperty;
62
67
  private generateSignalHandler;
63
68
  private getSignalParamFfiType;
64
69
  private addNamespacePrefix;
70
+ private toJsxPropertyType;
65
71
  private buildSignalHandlerType;
66
72
  private generateExports;
67
73
  private generateApplicationMenuComponents;
@@ -7,14 +7,16 @@ const GRID_WIDGETS = new Set(["Grid"]);
7
7
  const NOTEBOOK_WIDGET = "Notebook";
8
8
  const STACK_WIDGET = "Stack";
9
9
  const POPOVER_MENU_WIDGET = "PopoverMenu";
10
+ const TOOLBAR_VIEW_WIDGET = "ToolbarView";
10
11
  const INTERNALLY_PROVIDED_PARAMS = {
11
12
  ApplicationWindow: new Set(["application"]),
12
13
  };
14
+ const WIDGET_REFERENCE_PROPERTIES = new Set(["mnemonic-widget"]);
13
15
  const isPrimitive = (tsType) => {
14
16
  const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
15
17
  return primitives.has(tsType);
16
18
  };
17
- const toJsxPropertyType = (tsType) => {
19
+ const toJsxPropertyTypeBase = (tsType, namespace) => {
18
20
  let result = tsType;
19
21
  if (result.startsWith("Ref<")) {
20
22
  result = result.replace(/^Ref<(.+)>$/, "$1");
@@ -28,7 +30,7 @@ const toJsxPropertyType = (tsType) => {
28
30
  }
29
31
  if (result.includes(".") || result.includes("<") || result.includes("("))
30
32
  return result;
31
- return `Gtk.${result}`;
33
+ return `${namespace}.${result}`;
32
34
  };
33
35
  const isListWidget = (widgetName) => LIST_WIDGETS.has(widgetName);
34
36
  const isColumnViewWidget = (widgetName) => widgetName === COLUMN_VIEW_WIDGET;
@@ -37,9 +39,10 @@ const isGridWidget = (widgetName) => GRID_WIDGETS.has(widgetName);
37
39
  const isNotebookWidget = (widgetName) => widgetName === NOTEBOOK_WIDGET;
38
40
  const isStackWidget = (widgetName) => widgetName === STACK_WIDGET;
39
41
  const isPopoverMenuWidget = (widgetName) => widgetName === POPOVER_MENU_WIDGET;
42
+ const isToolbarViewWidget = (widgetName) => widgetName === TOOLBAR_VIEW_WIDGET;
40
43
  const sanitizeDoc = (doc) => {
41
44
  let result = doc;
42
- result = result.replace(/<picture>[\s\S]*?<\/picture>/gi, "");
45
+ result = result.replace(/<picture[^>]*>[\s\S]*?<\/picture>/gi, "");
43
46
  result = result.replace(/<img[^>]*>/gi, "");
44
47
  result = result.replace(/<source[^>]*>/gi, "");
45
48
  result = result.replace(/!\[[^\]]*\]\([^)]+\.png\)/gi, "");
@@ -48,6 +51,7 @@ const sanitizeDoc = (doc) => {
48
51
  result = result.replace(/<\/kbd>/gi, "`");
49
52
  result = result.replace(/\[([^\]]+)\]\([^)]+\.html[^)]*\)/gi, "$1");
50
53
  result = result.replace(/@(\w+)\s/g, "`$1` ");
54
+ result = result.replace(/<(\/?)(child|object|property|signal|template)>/gi, "`<$1$2>`");
51
55
  return result.trim();
52
56
  };
53
57
  const formatDoc = (doc, indent = "") => {
@@ -82,52 +86,66 @@ const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
82
86
  */
83
87
  export class JsxGenerator {
84
88
  typeMapper;
89
+ typeRegistry;
90
+ classMap;
85
91
  options;
86
- classMap = new Map();
87
92
  interfaceMap = new Map();
88
- namespace = "";
89
- usedExternalNamespaces = new Set();
93
+ usedNamespaces = new Set();
90
94
  widgetPropertyNames = new Set();
91
95
  widgetSignalNames = new Set();
96
+ currentNamespace = "";
97
+ widgetNamespaceMap = new Map();
92
98
  /**
93
99
  * Creates a new JSX generator.
94
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
95
103
  * @param options - Generator configuration options
96
104
  */
97
- constructor(typeMapper, options = {}) {
105
+ constructor(typeMapper, typeRegistry, classMap, options = {}) {
98
106
  this.typeMapper = typeMapper;
107
+ this.typeRegistry = typeRegistry;
108
+ this.classMap = classMap;
99
109
  this.options = options;
100
110
  }
101
111
  /**
102
- * Generates JSX type definitions for all widgets in a namespace.
103
- * @param namespace - The parsed GIR namespace
104
- * @param classMap - Map of class names to class definitions
112
+ * Generates JSX type definitions for all widgets in the given namespaces.
113
+ * @param namespaces - The parsed GIR namespaces (GTK must be first)
105
114
  * @returns Generated TypeScript code as public jsx.ts and internal.ts files
106
115
  */
107
- async generate(namespace, classMap) {
108
- this.classMap = classMap;
109
- this.interfaceMap = new Map(namespace.interfaces.map((iface) => [iface.name, iface]));
110
- this.namespace = namespace.name;
111
- this.usedExternalNamespaces.clear();
112
- const widgets = this.findWidgets(namespace, classMap);
113
- const containerMetadata = this.buildContainerMetadata(widgets, classMap);
114
- const widgetClass = classMap.get("Widget");
116
+ async generate(namespaces) {
117
+ for (const ns of namespaces) {
118
+ for (const iface of ns.interfaces) {
119
+ this.interfaceMap.set(iface.name, iface);
120
+ this.interfaceMap.set(`${ns.name}.${iface.name}`, iface);
121
+ }
122
+ }
123
+ this.usedNamespaces.clear();
124
+ this.widgetNamespaceMap.clear();
125
+ const allWidgets = [];
126
+ for (const ns of namespaces) {
127
+ const widgets = this.findWidgets(ns);
128
+ for (const widget of widgets) {
129
+ allWidgets.push({ widget, namespace: ns.name });
130
+ this.widgetNamespaceMap.set(widget.name, ns.name);
131
+ this.widgetNamespaceMap.set(`${ns.name}.${widget.name}`, ns.name);
132
+ }
133
+ }
134
+ const containerMetadata = this.buildContainerMetadata(allWidgets);
135
+ const widgetClass = this.classMap.get("Widget") ?? this.classMap.get("Gtk.Widget");
115
136
  this.widgetPropertyNames = new Set(widgetClass?.properties.map((p) => toCamelCase(p.name)) ?? []);
116
137
  this.widgetSignalNames = new Set(widgetClass?.signals.map((s) => toCamelCase(s.name)) ?? []);
117
- const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(widgets, containerMetadata);
118
- const jsxSections = [
119
- this.generateImports(),
120
- this.generateCommonTypes(widgetClass),
121
- widgetPropsInterfaces,
122
- this.generateExports(widgets, containerMetadata),
123
- this.generateJsxNamespace(widgets, containerMetadata),
124
- "export {};",
125
- ];
138
+ const commonTypes = this.generateCommonTypes(widgetClass);
139
+ const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(allWidgets, containerMetadata);
140
+ const exports = this.generateExports(allWidgets, containerMetadata);
141
+ const jsxNamespace = this.generateJsxNamespace(allWidgets, containerMetadata);
142
+ const imports = this.generateImports();
143
+ const jsxSections = [imports, commonTypes, widgetPropsInterfaces, exports, jsxNamespace, "export {};"];
126
144
  const internalSections = [
127
145
  this.generateInternalImports(),
128
- this.generateConstructorArgsMetadata(widgets),
129
- this.generatePropSettersMap(widgets),
130
- this.generateSetterGetterMap(widgets),
146
+ this.generateConstructorArgsMetadata(allWidgets),
147
+ this.generatePropSettersMap(allWidgets),
148
+ this.generateSetterGetterMap(allWidgets),
131
149
  ];
132
150
  return {
133
151
  jsx: await this.formatCode(jsxSections.join("\n")),
@@ -135,15 +153,15 @@ export class JsxGenerator {
135
153
  };
136
154
  }
137
155
  generateImports() {
138
- const externalImports = [...this.usedExternalNamespaces]
156
+ this.usedNamespaces.add("Gtk");
157
+ const namespaceImports = [...this.usedNamespaces]
139
158
  .sort()
140
159
  .map((ns) => `import type * as ${ns} from "@gtkx/ffi/${ns.toLowerCase()}";`);
141
160
  return [
142
161
  `import "react";`,
143
162
  `import { createElement } from "react";`,
144
163
  `import type { ReactNode, Ref } from "react";`,
145
- ...externalImports,
146
- `import type * as Gtk from "@gtkx/ffi/gtk";`,
164
+ ...namespaceImports,
147
165
  `import type { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps } from "../types.js";`,
148
166
  "",
149
167
  ].join("\n");
@@ -152,6 +170,8 @@ export class JsxGenerator {
152
170
  return "/** Internal metadata for the reconciler. Not part of the public API. */\n";
153
171
  }
154
172
  generateCommonTypes(widgetClass) {
173
+ this.currentNamespace = "Gtk";
174
+ this.typeMapper.setTypeRegistry(this.typeRegistry, "Gtk");
155
175
  const widgetPropsContent = this.generateWidgetPropsContent(widgetClass);
156
176
  return `
157
177
  export { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps };
@@ -169,7 +189,7 @@ ${widgetPropsContent}
169
189
  if (widgetClass) {
170
190
  for (const prop of widgetClass.properties) {
171
191
  const propName = toCamelCase(prop.name);
172
- const tsType = toJsxPropertyType(this.typeMapper.mapType(prop.type).ts);
192
+ const tsType = this.toJsxPropertyType(this.typeMapper.mapType(prop.type).ts, "Gtk");
173
193
  if (prop.doc) {
174
194
  lines.push(formatDoc(prop.doc, "\t").trimEnd());
175
195
  }
@@ -190,33 +210,39 @@ ${widgetPropsContent}
190
210
  lines.push("}");
191
211
  return lines.join("\n");
192
212
  }
193
- buildContainerMetadata(widgets, classMap) {
213
+ buildContainerMetadata(widgets) {
194
214
  const metadata = new Map();
195
- for (const widget of widgets) {
196
- metadata.set(widget.name, this.analyzeContainerCapabilities(widget, classMap));
215
+ for (const { widget, namespace } of widgets) {
216
+ const key = `${namespace}.${widget.name}`;
217
+ metadata.set(key, this.analyzeContainerCapabilities(widget));
197
218
  }
198
219
  return metadata;
199
220
  }
200
- findWidgets(namespace, classMap) {
221
+ findWidgets(namespace) {
201
222
  const widgetCache = new Map();
202
- const checkIsWidget = (className) => {
203
- const cached = widgetCache.get(className);
223
+ const checkIsWidget = (className, ns) => {
224
+ const cacheKey = `${ns}.${className}`;
225
+ const cached = widgetCache.get(cacheKey);
204
226
  if (cached !== undefined)
205
227
  return cached;
206
- const cls = classMap.get(className);
207
- if (!cls) {
208
- widgetCache.set(className, false);
228
+ widgetCache.set(cacheKey, false);
229
+ const cls = this.classMap.get(cacheKey) ?? this.classMap.get(className);
230
+ if (!cls)
209
231
  return false;
210
- }
211
232
  if (cls.name === "Widget") {
212
- widgetCache.set(className, true);
233
+ widgetCache.set(cacheKey, true);
213
234
  return true;
214
235
  }
215
- const result = cls.parent ? checkIsWidget(cls.parent) : false;
216
- widgetCache.set(className, result);
217
- return result;
236
+ if (cls.parent) {
237
+ const parentNs = cls.parent.includes(".") ? cls.parent.split(".")[0] : ns;
238
+ const parentName = cls.parent.includes(".") ? cls.parent.split(".")[1] : cls.parent;
239
+ const result = checkIsWidget(parentName ?? "", parentNs ?? ns);
240
+ widgetCache.set(cacheKey, result);
241
+ return result;
242
+ }
243
+ return false;
218
244
  };
219
- const widgets = namespace.classes.filter((cls) => checkIsWidget(cls.name));
245
+ const widgets = namespace.classes.filter((cls) => checkIsWidget(cls.name, namespace.name));
220
246
  return widgets.sort((a, b) => {
221
247
  if (a.name === "Widget")
222
248
  return -1;
@@ -229,20 +255,26 @@ ${widgetPropsContent}
229
255
  return a.name.localeCompare(b.name);
230
256
  });
231
257
  }
232
- analyzeContainerCapabilities(widget, classMap) {
258
+ analyzeContainerCapabilities(widget) {
233
259
  const hasAppend = widget.methods.some((m) => m.name === "append");
234
260
  const hasSetChild = widget.methods.some((m) => m.name === "set_child");
235
261
  const namedChildSlots = widget.properties
236
262
  .filter((prop) => {
237
263
  if (!prop.writable)
238
264
  return false;
265
+ if (WIDGET_REFERENCE_PROPERTIES.has(prop.name))
266
+ return false;
239
267
  const typeName = prop.type.name;
240
- return typeName === "Gtk.Widget" || typeName === "Widget" || isWidgetSubclass(typeName, classMap);
268
+ return typeName === "Gtk.Widget" || typeName === "Widget" || isWidgetSubclass(typeName, this.classMap);
241
269
  })
242
270
  .map((prop) => ({
243
271
  propertyName: prop.name,
244
272
  slotName: toPascalCase(prop.name),
245
273
  }));
274
+ if (isToolbarViewWidget(widget.name)) {
275
+ namedChildSlots.push({ propertyName: "top", slotName: "Top" });
276
+ namedChildSlots.push({ propertyName: "bottom", slotName: "Bottom" });
277
+ }
246
278
  return {
247
279
  supportsMultipleChildren: hasAppend,
248
280
  supportsSingleChild: hasSetChild,
@@ -251,18 +283,28 @@ ${widgetPropsContent}
251
283
  }
252
284
  generateWidgetPropsInterfaces(widgets, containerMetadata) {
253
285
  const sections = [];
254
- for (const widget of widgets) {
286
+ for (const { widget, namespace } of widgets) {
255
287
  if (widget.name === "Widget")
256
288
  continue;
257
- const metadata = containerMetadata.get(widget.name);
289
+ const metadataKey = `${namespace}.${widget.name}`;
290
+ const metadata = containerMetadata.get(metadataKey);
258
291
  if (!metadata)
259
- throw new Error(`Missing container metadata for widget: ${widget.name}`);
292
+ throw new Error(`Missing container metadata for widget: ${metadataKey}`);
293
+ this.currentNamespace = namespace;
294
+ this.usedNamespaces.add(namespace);
295
+ this.typeMapper.setTypeRegistry(this.typeRegistry, namespace);
260
296
  sections.push(this.generateWidgetProps(widget, metadata));
261
297
  }
262
298
  return sections.join("\n");
263
299
  }
300
+ getWidgetExportName(widget) {
301
+ const baseName = toPascalCase(widget.name);
302
+ if (this.currentNamespace === "Gtk")
303
+ return baseName;
304
+ return `${this.currentNamespace}${baseName}`;
305
+ }
264
306
  generateWidgetProps(widget, metadata) {
265
- const widgetName = toPascalCase(widget.name);
307
+ const widgetName = this.getWidgetExportName(widget);
266
308
  const parentPropsName = this.getParentPropsName(widget);
267
309
  const namedChildPropNames = new Set(metadata.namedChildSlots.map((s) => toCamelCase(s.propertyName)));
268
310
  const lines = [];
@@ -308,10 +350,9 @@ ${widgetPropsContent}
308
350
  const propName = toCamelCase(prop.name);
309
351
  emittedProps.add(prop.name);
310
352
  const typeMapping = this.typeMapper.mapType(prop.type);
311
- const tsType = toJsxPropertyType(typeMapping.ts);
312
- const isRequiredByProperty = prop.constructOnly && !prop.hasDefault;
353
+ const tsType = this.toJsxPropertyType(typeMapping.ts, this.currentNamespace);
313
354
  const isRequiredByConstructor = requiredCtorParams.has(prop.name);
314
- const isRequired = isRequiredByProperty || isRequiredByConstructor;
355
+ const isRequired = isRequiredByConstructor;
315
356
  if (prop.doc) {
316
357
  lines.push(formatDoc(prop.doc, "\t").trimEnd());
317
358
  }
@@ -324,7 +365,7 @@ ${widgetPropsContent}
324
365
  const inheritedProp = this.findInheritedProperty(widget, paramName);
325
366
  if (inheritedProp) {
326
367
  const typeMapping = this.typeMapper.mapType(inheritedProp.type);
327
- const tsType = toJsxPropertyType(typeMapping.ts);
368
+ const tsType = this.toJsxPropertyType(typeMapping.ts, this.currentNamespace);
328
369
  lines.push(`\t${propName}: ${tsType};`);
329
370
  }
330
371
  }
@@ -338,7 +379,7 @@ ${widgetPropsContent}
338
379
  if (signal.doc) {
339
380
  lines.push(formatDoc(signal.doc, "\t").trimEnd());
340
381
  }
341
- lines.push(`\t${this.generateSignalHandler(signal, widgetName)}`);
382
+ lines.push(`\t${this.generateSignalHandler(signal, widget.name)}`);
342
383
  }
343
384
  }
344
385
  if (isListWidget(widget.name)) {
@@ -357,7 +398,8 @@ ${widgetPropsContent}
357
398
  lines.push(`\tonSelectionChanged?: (item: unknown, index: number) => void;`);
358
399
  }
359
400
  lines.push("");
360
- lines.push(`\tref?: Ref<Gtk.${widgetName}>;`);
401
+ const ffiTypeName = toPascalCase(widget.name);
402
+ lines.push(`\tref?: Ref<${this.currentNamespace}.${ffiTypeName}>;`);
361
403
  lines.push(`}`);
362
404
  return `${lines.join("\n")}\n`;
363
405
  }
@@ -366,11 +408,19 @@ ${widgetPropsContent}
366
408
  return "WidgetProps";
367
409
  if (widget.name === "ApplicationWindow")
368
410
  return "WindowProps";
369
- if (widget.parent === "Widget")
411
+ if (!widget.parent)
370
412
  return "WidgetProps";
371
- if (widget.parent === "Window")
413
+ const parentNs = widget.parent.includes(".") ? widget.parent.split(".")[0] : this.currentNamespace;
414
+ const parentName = widget.parent.includes(".") ? widget.parent.split(".")[1] : widget.parent;
415
+ if (parentName === "Widget")
416
+ return "WidgetProps";
417
+ if (parentName === "Window")
372
418
  return "WindowProps";
373
- return widget.parent ? `${toPascalCase(widget.parent)}Props` : "WidgetProps";
419
+ const baseName = toPascalCase(parentName ?? "");
420
+ if (parentNs === "Gtk") {
421
+ return `${baseName}Props`;
422
+ }
423
+ return `${parentNs}${baseName}Props`;
374
424
  }
375
425
  getRequiredConstructorParams(widget) {
376
426
  const required = new Set();
@@ -392,18 +442,22 @@ ${widgetPropsContent}
392
442
  const mainCtor = widget.constructors.find((c) => c.name === "new");
393
443
  if (!mainCtor)
394
444
  return [];
395
- return mainCtor.parameters.map((param) => ({
445
+ const params = mainCtor.parameters.map((param) => ({
396
446
  name: toCamelCase(param.name),
397
447
  hasDefault: param.nullable || param.optional || false,
398
448
  }));
449
+ const required = params.filter((p) => !p.hasDefault);
450
+ const optional = params.filter((p) => p.hasDefault);
451
+ return [...required, ...optional];
399
452
  }
400
453
  generateConstructorArgsMetadata(widgets) {
401
454
  const entries = [];
402
- for (const widget of widgets) {
455
+ for (const { widget, namespace } of widgets) {
403
456
  const params = this.getConstructorParams(widget);
404
457
  if (params.length === 0)
405
458
  continue;
406
- const widgetName = toPascalCase(widget.name);
459
+ this.currentNamespace = namespace;
460
+ const widgetName = this.getWidgetExportName(widget);
407
461
  const paramStrs = params.map((p) => `{ name: "${p.name}", hasDefault: ${p.hasDefault} }`);
408
462
  entries.push(`\t${widgetName}: [${paramStrs.join(", ")}]`);
409
463
  }
@@ -414,7 +468,8 @@ ${widgetPropsContent}
414
468
  }
415
469
  generatePropSettersMap(widgets) {
416
470
  const widgetEntries = [];
417
- for (const widget of widgets) {
471
+ for (const { widget, namespace } of widgets) {
472
+ this.currentNamespace = namespace;
418
473
  const propSetterPairs = [];
419
474
  const allProps = this.collectAllProperties(widget);
420
475
  for (const prop of allProps) {
@@ -425,7 +480,7 @@ ${widgetPropsContent}
425
480
  }
426
481
  }
427
482
  if (propSetterPairs.length > 0) {
428
- const widgetName = toPascalCase(widget.name);
483
+ const widgetName = this.getWidgetExportName(widget);
429
484
  widgetEntries.push(`\t${widgetName}: { ${propSetterPairs.join(", ")} }`);
430
485
  }
431
486
  }
@@ -436,18 +491,24 @@ ${widgetPropsContent}
436
491
  }
437
492
  generateSetterGetterMap(widgets) {
438
493
  const widgetEntries = [];
439
- for (const widget of widgets) {
494
+ for (const { widget, namespace } of widgets) {
495
+ this.currentNamespace = namespace;
440
496
  const setterGetterPairs = [];
441
497
  const allProps = this.collectAllProperties(widget);
498
+ const parentMethodNames = this.collectParentMethodNames(widget);
499
+ const widgetClassName = toPascalCase(widget.name);
442
500
  for (const prop of allProps) {
443
501
  if (prop.setter && prop.getter) {
444
502
  const setterName = toCamelCase(prop.setter);
445
- const getterName = toCamelCase(prop.getter);
503
+ let getterName = toCamelCase(prop.getter);
504
+ if (parentMethodNames.has(prop.getter)) {
505
+ getterName = `${getterName}${widgetClassName}`;
506
+ }
446
507
  setterGetterPairs.push(`"${setterName}": "${getterName}"`);
447
508
  }
448
509
  }
449
510
  if (setterGetterPairs.length > 0) {
450
- const widgetName = toPascalCase(widget.name);
511
+ const widgetName = this.getWidgetExportName(widget);
451
512
  widgetEntries.push(`\t${widgetName}: { ${setterGetterPairs.join(", ")} }`);
452
513
  }
453
514
  }
@@ -456,6 +517,32 @@ ${widgetPropsContent}
456
517
  }
457
518
  return `export const SETTER_GETTERS: Record<string, Record<string, string>> = {\n${widgetEntries.join(",\n")},\n};\n`;
458
519
  }
520
+ collectParentMethodNames(widget) {
521
+ const names = new Set();
522
+ const visited = new Set();
523
+ let parentRef = widget.parent;
524
+ while (parentRef && !visited.has(parentRef)) {
525
+ visited.add(parentRef);
526
+ const current = this.classMap.get(parentRef) ??
527
+ this.classMap.get(`${this.currentNamespace}.${parentRef}`) ??
528
+ this.classMap.get(`Gtk.${parentRef}`);
529
+ if (!current)
530
+ break;
531
+ for (const method of current.methods) {
532
+ names.add(method.name);
533
+ }
534
+ for (const ifaceName of current.implements) {
535
+ const iface = this.interfaceMap.get(ifaceName);
536
+ if (iface) {
537
+ for (const method of iface.methods) {
538
+ names.add(method.name);
539
+ }
540
+ }
541
+ }
542
+ parentRef = current.parent;
543
+ }
544
+ return names;
545
+ }
459
546
  collectAllProperties(widget) {
460
547
  const props = [];
461
548
  const seen = new Set();
@@ -478,7 +565,15 @@ ${widgetPropsContent}
478
565
  }
479
566
  }
480
567
  }
481
- current = current.parent ? this.classMap.get(current.parent) : undefined;
568
+ if (current.parent) {
569
+ current =
570
+ this.classMap.get(current.parent) ??
571
+ this.classMap.get(`${this.currentNamespace}.${current.parent}`) ??
572
+ this.classMap.get(`Gtk.${current.parent}`);
573
+ }
574
+ else {
575
+ current = undefined;
576
+ }
482
577
  }
483
578
  return props;
484
579
  }
@@ -530,18 +625,22 @@ ${widgetPropsContent}
530
625
  return undefined;
531
626
  if (typeName.includes(".")) {
532
627
  const [ns, className] = typeName.split(".", 2);
533
- if (ns === this.namespace && className && this.classMap.has(className)) {
534
- return `Gtk.${toPascalCase(className)}`;
535
- }
536
- if (ns) {
537
- this.usedExternalNamespaces.add(ns);
538
- return typeName;
628
+ if (ns && className) {
629
+ const registered = this.typeRegistry.resolve(`${ns}.${className}`);
630
+ if (registered && (registered.kind === "class" || registered.kind === "interface")) {
631
+ this.usedNamespaces.add(ns);
632
+ return `${ns}.${registered.transformedName}`;
633
+ }
539
634
  }
540
635
  return undefined;
541
636
  }
542
- const normalizedName = toPascalCase(typeName);
543
- if (this.classMap.has(typeName) || this.classMap.has(normalizedName)) {
544
- return `Gtk.${normalizedName}`;
637
+ const registered = this.typeRegistry.resolveInNamespace(typeName, this.currentNamespace);
638
+ if (registered && (registered.kind === "class" || registered.kind === "interface")) {
639
+ this.usedNamespaces.add(registered.namespace);
640
+ if (registered.namespace === this.currentNamespace) {
641
+ return `${this.currentNamespace}.${registered.transformedName}`;
642
+ }
643
+ return `${registered.namespace}.${registered.transformedName}`;
545
644
  }
546
645
  return undefined;
547
646
  }
@@ -549,12 +648,30 @@ ${widgetPropsContent}
549
648
  const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
550
649
  if (primitives.has(tsType))
551
650
  return tsType;
651
+ if (tsType.endsWith("[]")) {
652
+ const elementType = tsType.slice(0, -2);
653
+ if (primitives.has(elementType))
654
+ return tsType;
655
+ if (elementType.includes(".") || elementType.includes("<") || elementType.includes("("))
656
+ return tsType;
657
+ return `${this.currentNamespace}.${elementType}[]`;
658
+ }
552
659
  if (tsType.includes(".") || tsType.includes("<") || tsType.includes("("))
553
660
  return tsType;
554
- return `Gtk.${tsType}`;
661
+ return `${this.currentNamespace}.${tsType}`;
662
+ }
663
+ toJsxPropertyType(tsType, namespace) {
664
+ const result = toJsxPropertyTypeBase(tsType, namespace);
665
+ if (result.includes(".") && !result.includes("<") && !result.includes("(")) {
666
+ const ns = result.split(".")[0];
667
+ if (ns) {
668
+ this.usedNamespaces.add(ns);
669
+ }
670
+ }
671
+ return result;
555
672
  }
556
673
  buildSignalHandlerType(signal, widgetName) {
557
- const selfParam = `self: Gtk.${toPascalCase(widgetName)}`;
674
+ const selfParam = `self: ${this.currentNamespace}.${toPascalCase(widgetName)}`;
558
675
  const otherParams = signal.parameters
559
676
  ?.map((p) => {
560
677
  const ffiType = this.getSignalParamFfiType(p.type.name);
@@ -574,11 +691,13 @@ ${widgetPropsContent}
574
691
  }
575
692
  generateExports(widgets, containerMetadata) {
576
693
  const lines = [];
577
- for (const widget of widgets) {
578
- const widgetName = toPascalCase(widget.name);
579
- const metadata = containerMetadata.get(widget.name);
694
+ for (const { widget, namespace } of widgets) {
695
+ this.currentNamespace = namespace;
696
+ const widgetName = this.getWidgetExportName(widget);
697
+ const metadataKey = `${namespace}.${widget.name}`;
698
+ const metadata = containerMetadata.get(metadataKey);
580
699
  if (!metadata)
581
- throw new Error(`Missing container metadata for widget: ${widget.name}`);
700
+ throw new Error(`Missing container metadata for widget: ${metadataKey}`);
582
701
  const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
583
702
  const hasMeaningfulSlots = nonChildSlots.length > 0 ||
584
703
  isListWidget(widget.name) ||
@@ -595,8 +714,8 @@ ${widgetPropsContent}
595
714
  isDropDownWidget(widget.name) ||
596
715
  isStackWidget(widget.name) ||
597
716
  isPopoverMenuWidget(widget.name)) {
598
- const wrapperComponents = this.generateGenericWrapperComponents(widget.name, metadata);
599
- const exportMembers = this.getWrapperExportMembers(widget.name, metadata);
717
+ const wrapperComponents = this.generateGenericWrapperComponents(widgetName, metadata);
718
+ const exportMembers = this.getWrapperExportMembers(widgetName, metadata);
600
719
  if (docComment) {
601
720
  lines.push(`${wrapperComponents}\n${docComment}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
602
721
  }
@@ -794,14 +913,16 @@ export const Menu = {
794
913
  }
795
914
  generateJsxNamespace(widgets, containerMetadata) {
796
915
  const elements = [];
797
- for (const widget of widgets) {
916
+ for (const { widget, namespace } of widgets) {
798
917
  if (widget.name === "Widget")
799
918
  continue;
800
- const widgetName = toPascalCase(widget.name);
919
+ this.currentNamespace = namespace;
920
+ const widgetName = this.getWidgetExportName(widget);
801
921
  const propsName = `${widgetName}Props`;
802
- const metadata = containerMetadata.get(widget.name);
922
+ const metadataKey = `${namespace}.${widget.name}`;
923
+ const metadata = containerMetadata.get(metadataKey);
803
924
  if (!metadata)
804
- throw new Error(`Missing container metadata for widget: ${widget.name}`);
925
+ throw new Error(`Missing container metadata for widget: ${metadataKey}`);
805
926
  const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
806
927
  const hasMeaningfulSlots = nonChildSlots.length > 0 ||
807
928
  isListWidget(widget.name) ||