@gtkx/react 0.9.4 → 0.10.0
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 +55 -67
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +8 -8
- package/dist/factory.d.ts +3 -5
- package/dist/factory.js +18 -71
- package/dist/fiber-root.d.ts +1 -1
- package/dist/fiber-root.js +1 -2
- package/dist/generated/internal.d.ts +3 -6
- package/dist/generated/internal.js +10386 -13577
- package/dist/generated/jsx.d.ts +914 -808
- package/dist/generated/jsx.js +123 -358
- package/dist/generated/registry.d.ts +4 -0
- package/dist/generated/registry.js +13 -0
- package/dist/host-config.d.ts +7 -4
- package/dist/host-config.js +53 -18
- package/dist/index.d.ts +2 -22
- package/dist/index.js +2 -40
- package/dist/jsx.d.ts +719 -0
- package/dist/jsx.js +392 -0
- package/dist/node.d.ts +15 -32
- package/dist/node.js +20 -240
- package/dist/nodes/action-row-child.d.ts +21 -0
- package/dist/nodes/action-row-child.js +69 -0
- package/dist/nodes/action-row.js +33 -0
- package/dist/nodes/application.d.ts +1 -0
- package/dist/nodes/application.js +38 -0
- package/dist/nodes/autowrapped.d.ts +1 -0
- package/dist/nodes/autowrapped.js +109 -0
- package/dist/nodes/column-view-column.d.ts +16 -0
- package/dist/nodes/column-view-column.js +54 -0
- package/dist/nodes/column-view.d.ts +0 -59
- package/dist/nodes/column-view.js +107 -226
- package/dist/nodes/fixed-child.d.ts +1 -0
- package/dist/nodes/fixed-child.js +45 -0
- package/dist/nodes/grid-child.d.ts +1 -0
- package/dist/nodes/grid-child.js +54 -0
- package/dist/nodes/index.d.ts +34 -0
- package/dist/nodes/index.js +34 -0
- package/dist/nodes/internal/list-item-renderer.d.ts +18 -0
- package/dist/nodes/internal/list-item-renderer.js +67 -0
- package/dist/nodes/internal/list-store.d.ts +16 -0
- package/dist/nodes/internal/list-store.js +69 -0
- package/dist/nodes/internal/predicates.d.ts +26 -0
- package/dist/nodes/internal/predicates.js +36 -0
- package/dist/nodes/internal/signal-store.d.ts +9 -0
- package/dist/nodes/internal/signal-store.js +54 -0
- package/dist/nodes/internal/simple-list-store.d.ts +14 -0
- package/dist/nodes/internal/simple-list-store.js +60 -0
- package/dist/nodes/internal/tree-list-item-renderer.d.ts +18 -0
- package/dist/nodes/internal/tree-list-item-renderer.js +90 -0
- package/dist/nodes/internal/tree-store.d.ts +28 -0
- package/dist/nodes/internal/tree-store.js +153 -0
- package/dist/nodes/internal/utils.d.ts +3 -0
- package/dist/nodes/internal/utils.js +20 -0
- package/dist/nodes/list-item.d.ts +12 -0
- package/dist/nodes/list-item.js +24 -0
- package/dist/nodes/list-view.d.ts +0 -22
- package/dist/nodes/list-view.js +45 -38
- package/dist/nodes/menu.d.ts +6 -106
- package/dist/nodes/menu.js +16 -268
- package/dist/nodes/models/list.d.ts +24 -0
- package/dist/nodes/models/list.js +102 -0
- package/dist/nodes/models/menu.d.ts +45 -0
- package/dist/nodes/models/menu.js +265 -0
- package/dist/nodes/models/tree-list.d.ts +28 -0
- package/dist/nodes/models/tree-list.js +141 -0
- package/dist/nodes/navigation-page.d.ts +21 -0
- package/dist/nodes/navigation-page.js +95 -0
- package/dist/nodes/navigation-view.d.ts +1 -0
- package/dist/nodes/navigation-view.js +29 -0
- package/dist/nodes/notebook-page-tab.d.ts +15 -0
- package/dist/nodes/notebook-page-tab.js +42 -0
- package/dist/nodes/notebook-page.d.ts +23 -0
- package/dist/nodes/notebook-page.js +106 -0
- package/dist/nodes/notebook.d.ts +0 -32
- package/dist/nodes/notebook.js +20 -113
- package/dist/nodes/overlay-child.d.ts +1 -0
- package/dist/nodes/overlay-child.js +30 -0
- package/dist/nodes/pack-child.d.ts +21 -0
- package/dist/nodes/pack-child.js +68 -0
- package/dist/nodes/pack.d.ts +1 -0
- package/dist/nodes/pack.js +33 -0
- package/dist/nodes/popover-menu.d.ts +1 -0
- package/dist/nodes/popover-menu.js +58 -0
- package/dist/nodes/simple-list-item.d.ts +9 -0
- package/dist/nodes/simple-list-item.js +9 -0
- package/dist/nodes/simple-list-view.d.ts +1 -0
- package/dist/nodes/simple-list-view.js +75 -0
- package/dist/nodes/slot.d.ts +18 -10
- package/dist/nodes/slot.js +83 -51
- package/dist/nodes/stack-page.d.ts +1 -0
- package/dist/nodes/stack-page.js +80 -0
- package/dist/nodes/stack.d.ts +1 -22
- package/dist/nodes/stack.js +21 -60
- package/dist/nodes/toast-overlay.d.ts +1 -0
- package/dist/nodes/toast-overlay.js +35 -0
- package/dist/nodes/toast.d.ts +17 -0
- package/dist/nodes/toast.js +77 -0
- package/dist/nodes/toolbar-child.d.ts +9 -0
- package/dist/nodes/toolbar-child.js +33 -0
- package/dist/nodes/toolbar.d.ts +1 -0
- package/dist/nodes/toolbar.js +42 -0
- package/dist/nodes/tree-list-item.d.ts +20 -0
- package/dist/nodes/tree-list-item.js +102 -0
- package/dist/nodes/tree-list-view.d.ts +1 -0
- package/dist/nodes/tree-list-view.js +57 -0
- package/dist/nodes/virtual.d.ts +13 -0
- package/dist/nodes/virtual.js +21 -0
- package/dist/nodes/widget.d.ts +17 -3
- package/dist/nodes/widget.js +258 -2
- package/dist/nodes/window.d.ts +1 -12
- package/dist/nodes/window.js +66 -27
- package/dist/portal.d.ts +18 -13
- package/dist/portal.js +17 -14
- package/dist/reconciler.d.ts +0 -4
- package/dist/reconciler.js +1 -9
- package/dist/registry.d.ts +8 -0
- package/dist/registry.js +5 -0
- package/dist/render.d.ts +108 -12
- package/dist/render.js +140 -16
- package/dist/scheduler.d.ts +4 -0
- package/dist/scheduler.js +10 -0
- package/dist/types.d.ts +3 -136
- package/package.json +6 -6
- package/dist/batch.d.ts +0 -5
- package/dist/batch.js +0 -31
- package/dist/codegen/jsx-generator.d.ts +0 -56
- package/dist/codegen/jsx-generator.js +0 -959
- package/dist/containers.d.ts +0 -58
- package/dist/nodes/about-dialog.d.ts +0 -8
- package/dist/nodes/about-dialog.js +0 -16
- package/dist/nodes/action-bar.d.ts +0 -5
- package/dist/nodes/action-bar.js +0 -6
- package/dist/nodes/combo-row.d.ts +0 -5
- package/dist/nodes/combo-row.js +0 -6
- package/dist/nodes/drop-down.d.ts +0 -9
- package/dist/nodes/drop-down.js +0 -12
- package/dist/nodes/flow-box.d.ts +0 -10
- package/dist/nodes/flow-box.js +0 -41
- package/dist/nodes/grid.d.ts +0 -30
- package/dist/nodes/grid.js +0 -84
- package/dist/nodes/header-bar.d.ts +0 -43
- package/dist/nodes/header-bar.js +0 -116
- package/dist/nodes/indexed-child-container.d.ts +0 -16
- package/dist/nodes/indexed-child-container.js +0 -22
- package/dist/nodes/list-box.d.ts +0 -10
- package/dist/nodes/list-box.js +0 -48
- package/dist/nodes/list-item-factory.d.ts +0 -19
- package/dist/nodes/list-item-factory.js +0 -58
- package/dist/nodes/overlay.d.ts +0 -11
- package/dist/nodes/overlay.js +0 -50
- package/dist/nodes/paged-stack.d.ts +0 -31
- package/dist/nodes/paged-stack.js +0 -95
- package/dist/nodes/root.d.ts +0 -8
- package/dist/nodes/root.js +0 -13
- package/dist/nodes/selectable-list.d.ts +0 -45
- package/dist/nodes/selectable-list.js +0 -260
- package/dist/nodes/stack-page-props.d.ts +0 -11
- package/dist/nodes/stack-page-props.js +0 -23
- package/dist/nodes/string-list-container.d.ts +0 -34
- package/dist/nodes/string-list-container.js +0 -118
- package/dist/nodes/string-list-item.d.ts +0 -19
- package/dist/nodes/string-list-item.js +0 -50
- package/dist/nodes/string-list-store.d.ts +0 -13
- package/dist/nodes/string-list-store.js +0 -44
- package/dist/nodes/text-view.d.ts +0 -8
- package/dist/nodes/text-view.js +0 -16
- package/dist/nodes/toggle-button.d.ts +0 -14
- package/dist/nodes/toggle-button.js +0 -39
- package/dist/nodes/toolbar-view.d.ts +0 -14
- package/dist/nodes/toolbar-view.js +0 -78
- package/dist/nodes/view-stack.d.ts +0 -9
- package/dist/nodes/view-stack.js +0 -28
- package/dist/nodes/virtual-item.d.ts +0 -19
- package/dist/nodes/virtual-item.js +0 -48
- package/dist/nodes/virtual-slot.d.ts +0 -25
- package/dist/nodes/virtual-slot.js +0 -57
- package/dist/predicates.d.ts +0 -29
- package/dist/predicates.js +0 -37
- package/dist/props.d.ts +0 -7
- package/dist/props.js +0 -12
- /package/dist/{containers.js → nodes/action-row.d.ts} +0 -0
|
@@ -1,959 +0,0 @@
|
|
|
1
|
-
import { formatDoc as formatDocBase, toCamelCase, toPascalCase } from "@gtkx/gir";
|
|
2
|
-
import { format } from "prettier";
|
|
3
|
-
const LIST_WIDGETS = new Set(["ListView", "GridView"]);
|
|
4
|
-
const COLUMN_VIEW_WIDGET = "ColumnView";
|
|
5
|
-
const DROPDOWN_WIDGETS = new Set(["DropDown"]);
|
|
6
|
-
const GRID_WIDGETS = new Set(["Grid"]);
|
|
7
|
-
const NOTEBOOK_WIDGET = "Notebook";
|
|
8
|
-
const STACK_WIDGETS = new Set(["Stack", "ViewStack", "AdwViewStack"]);
|
|
9
|
-
const POPOVER_MENU_WIDGET = "PopoverMenu";
|
|
10
|
-
const TOOLBAR_VIEW_WIDGET = "ToolbarView";
|
|
11
|
-
const INTERNALLY_PROVIDED_PARAMS = {
|
|
12
|
-
ApplicationWindow: new Set(["application"]),
|
|
13
|
-
};
|
|
14
|
-
const WIDGET_REFERENCE_PROPERTIES = new Set(["mnemonic-widget"]);
|
|
15
|
-
const isPrimitive = (tsType) => {
|
|
16
|
-
const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
|
|
17
|
-
return primitives.has(tsType);
|
|
18
|
-
};
|
|
19
|
-
const toJsxPropertyTypeBase = (tsType, namespace) => {
|
|
20
|
-
let result = tsType;
|
|
21
|
-
if (result.startsWith("Ref<")) {
|
|
22
|
-
result = result.replace(/^Ref<(.+)>$/, "$1");
|
|
23
|
-
}
|
|
24
|
-
if (isPrimitive(result))
|
|
25
|
-
return result;
|
|
26
|
-
if (result.endsWith("[]")) {
|
|
27
|
-
const elementType = result.slice(0, -2);
|
|
28
|
-
if (isPrimitive(elementType))
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
if (result.includes(".") || result.includes("<") || result.includes("("))
|
|
32
|
-
return result;
|
|
33
|
-
return `${namespace}.${result}`;
|
|
34
|
-
};
|
|
35
|
-
const isListWidget = (widgetName) => LIST_WIDGETS.has(widgetName);
|
|
36
|
-
const isColumnViewWidget = (widgetName) => widgetName === COLUMN_VIEW_WIDGET;
|
|
37
|
-
const isDropDownWidget = (widgetName) => DROPDOWN_WIDGETS.has(widgetName);
|
|
38
|
-
const isGridWidget = (widgetName) => GRID_WIDGETS.has(widgetName);
|
|
39
|
-
const isNotebookWidget = (widgetName) => widgetName === NOTEBOOK_WIDGET;
|
|
40
|
-
const isStackWidget = (widgetName) => STACK_WIDGETS.has(widgetName);
|
|
41
|
-
const isPopoverMenuWidget = (widgetName) => widgetName === POPOVER_MENU_WIDGET;
|
|
42
|
-
const isToolbarViewWidget = (widgetName) => widgetName === TOOLBAR_VIEW_WIDGET;
|
|
43
|
-
const isWidgetSubclass = (typeName, classMap, visited = new Set()) => {
|
|
44
|
-
const className = typeName.includes(".") ? typeName.split(".")[1] : typeName;
|
|
45
|
-
if (!className || visited.has(className))
|
|
46
|
-
return false;
|
|
47
|
-
visited.add(className);
|
|
48
|
-
const cls = classMap.get(className);
|
|
49
|
-
if (!cls)
|
|
50
|
-
return false;
|
|
51
|
-
if (className === "Widget")
|
|
52
|
-
return true;
|
|
53
|
-
return cls.parent ? isWidgetSubclass(cls.parent, classMap, visited) : false;
|
|
54
|
-
};
|
|
55
|
-
export class JsxGenerator {
|
|
56
|
-
typeMapper;
|
|
57
|
-
typeRegistry;
|
|
58
|
-
classMap;
|
|
59
|
-
options;
|
|
60
|
-
interfaceMap = new Map();
|
|
61
|
-
usedNamespaces = new Set();
|
|
62
|
-
widgetPropertyNames = new Set();
|
|
63
|
-
widgetSignalNames = new Set();
|
|
64
|
-
currentNamespace = "";
|
|
65
|
-
widgetNamespaceMap = new Map();
|
|
66
|
-
constructor(typeMapper, typeRegistry, classMap, options = {}) {
|
|
67
|
-
this.typeMapper = typeMapper;
|
|
68
|
-
this.typeRegistry = typeRegistry;
|
|
69
|
-
this.classMap = classMap;
|
|
70
|
-
this.options = options;
|
|
71
|
-
}
|
|
72
|
-
formatDoc(doc, indent = "") {
|
|
73
|
-
return formatDocBase(doc, indent, {
|
|
74
|
-
namespace: this.currentNamespace,
|
|
75
|
-
escapeXmlTags: true,
|
|
76
|
-
linkStyle: "prefixed",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
async generate(namespaces) {
|
|
80
|
-
for (const ns of namespaces) {
|
|
81
|
-
for (const iface of ns.interfaces) {
|
|
82
|
-
this.interfaceMap.set(iface.name, iface);
|
|
83
|
-
this.interfaceMap.set(`${ns.name}.${iface.name}`, iface);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
this.usedNamespaces.clear();
|
|
87
|
-
this.widgetNamespaceMap.clear();
|
|
88
|
-
const allWidgets = [];
|
|
89
|
-
for (const ns of namespaces) {
|
|
90
|
-
const widgets = this.findWidgets(ns);
|
|
91
|
-
for (const widget of widgets) {
|
|
92
|
-
allWidgets.push({ widget, namespace: ns.name });
|
|
93
|
-
this.widgetNamespaceMap.set(widget.name, ns.name);
|
|
94
|
-
this.widgetNamespaceMap.set(`${ns.name}.${widget.name}`, ns.name);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const containerMetadata = this.buildContainerMetadata(allWidgets);
|
|
98
|
-
const widgetClass = this.classMap.get("Widget") ?? this.classMap.get("Gtk.Widget");
|
|
99
|
-
this.widgetPropertyNames = new Set(widgetClass?.properties.map((p) => toCamelCase(p.name)) ?? []);
|
|
100
|
-
this.widgetSignalNames = new Set(widgetClass?.signals.map((s) => toCamelCase(s.name)) ?? []);
|
|
101
|
-
const commonTypes = this.generateCommonTypes(widgetClass);
|
|
102
|
-
const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(allWidgets, containerMetadata);
|
|
103
|
-
const exports = this.generateExports(allWidgets, containerMetadata);
|
|
104
|
-
const jsxNamespace = this.generateJsxNamespace(allWidgets, containerMetadata);
|
|
105
|
-
const imports = this.generateImports();
|
|
106
|
-
const jsxSections = [imports, commonTypes, widgetPropsInterfaces, exports, jsxNamespace, "export {};"];
|
|
107
|
-
const internalSections = [
|
|
108
|
-
this.generateInternalImports(),
|
|
109
|
-
this.generateConstructorArgsMetadata(allWidgets),
|
|
110
|
-
this.generatePropSettersMap(allWidgets),
|
|
111
|
-
this.generateSetterGetterMap(allWidgets),
|
|
112
|
-
];
|
|
113
|
-
return {
|
|
114
|
-
jsx: await this.formatCode(jsxSections.join("\n")),
|
|
115
|
-
internal: await this.formatCode(internalSections.join("\n")),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
generateImports() {
|
|
119
|
-
this.usedNamespaces.add("Gtk");
|
|
120
|
-
const namespaceImports = [...this.usedNamespaces]
|
|
121
|
-
.sort()
|
|
122
|
-
.map((ns) => `import type * as ${ns} from "@gtkx/ffi/${ns.toLowerCase()}";`);
|
|
123
|
-
return [
|
|
124
|
-
`import "react";`,
|
|
125
|
-
`import { createElement } from "react";`,
|
|
126
|
-
`import type { ReactNode, Ref } from "react";`,
|
|
127
|
-
...namespaceImports,
|
|
128
|
-
`import type { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps, StringListItemProps } from "../types.js";`,
|
|
129
|
-
"",
|
|
130
|
-
].join("\n");
|
|
131
|
-
}
|
|
132
|
-
generateInternalImports() {
|
|
133
|
-
return "/** Internal metadata for the reconciler. Not part of the public API. */\n";
|
|
134
|
-
}
|
|
135
|
-
generateCommonTypes(widgetClass) {
|
|
136
|
-
this.currentNamespace = "Gtk";
|
|
137
|
-
this.typeMapper.setTypeRegistry(this.typeRegistry, "Gtk");
|
|
138
|
-
const widgetPropsContent = this.generateWidgetPropsContent(widgetClass);
|
|
139
|
-
return `
|
|
140
|
-
export { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps, StringListItemProps };
|
|
141
|
-
|
|
142
|
-
${widgetPropsContent}
|
|
143
|
-
`;
|
|
144
|
-
}
|
|
145
|
-
generateWidgetPropsContent(widgetClass) {
|
|
146
|
-
const lines = [];
|
|
147
|
-
const widgetDoc = widgetClass?.doc ? this.formatDoc(widgetClass.doc) : "";
|
|
148
|
-
if (widgetDoc) {
|
|
149
|
-
lines.push(widgetDoc.trimEnd());
|
|
150
|
-
}
|
|
151
|
-
lines.push("export interface WidgetProps {");
|
|
152
|
-
if (widgetClass) {
|
|
153
|
-
for (const prop of widgetClass.properties) {
|
|
154
|
-
const propName = toCamelCase(prop.name);
|
|
155
|
-
const tsType = this.toJsxPropertyType(this.typeMapper.mapType(prop.type).ts, "Gtk");
|
|
156
|
-
if (prop.doc) {
|
|
157
|
-
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
158
|
-
}
|
|
159
|
-
lines.push(`\t${propName}?: ${tsType};`);
|
|
160
|
-
}
|
|
161
|
-
if (widgetClass.signals.length > 0) {
|
|
162
|
-
lines.push("");
|
|
163
|
-
for (const signal of widgetClass.signals) {
|
|
164
|
-
if (signal.doc) {
|
|
165
|
-
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
166
|
-
}
|
|
167
|
-
lines.push(`\t${this.generateSignalHandler(signal, "Widget")}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
lines.push("");
|
|
172
|
-
lines.push("\tchildren?: ReactNode;");
|
|
173
|
-
lines.push("}");
|
|
174
|
-
return lines.join("\n");
|
|
175
|
-
}
|
|
176
|
-
buildContainerMetadata(widgets) {
|
|
177
|
-
const metadata = new Map();
|
|
178
|
-
for (const { widget, namespace } of widgets) {
|
|
179
|
-
const key = `${namespace}.${widget.name}`;
|
|
180
|
-
metadata.set(key, this.analyzeContainerCapabilities(widget));
|
|
181
|
-
}
|
|
182
|
-
return metadata;
|
|
183
|
-
}
|
|
184
|
-
findWidgets(namespace) {
|
|
185
|
-
const widgetCache = new Map();
|
|
186
|
-
const checkIsWidget = (className, ns) => {
|
|
187
|
-
const cacheKey = `${ns}.${className}`;
|
|
188
|
-
const cached = widgetCache.get(cacheKey);
|
|
189
|
-
if (cached !== undefined)
|
|
190
|
-
return cached;
|
|
191
|
-
widgetCache.set(cacheKey, false);
|
|
192
|
-
const cls = this.classMap.get(cacheKey) ?? this.classMap.get(className);
|
|
193
|
-
if (!cls)
|
|
194
|
-
return false;
|
|
195
|
-
if (cls.name === "Widget") {
|
|
196
|
-
widgetCache.set(cacheKey, true);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
if (cls.parent) {
|
|
200
|
-
const parentNs = cls.parent.includes(".") ? cls.parent.split(".")[0] : ns;
|
|
201
|
-
const parentName = cls.parent.includes(".") ? cls.parent.split(".")[1] : cls.parent;
|
|
202
|
-
const result = checkIsWidget(parentName ?? "", parentNs ?? ns);
|
|
203
|
-
widgetCache.set(cacheKey, result);
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
return false;
|
|
207
|
-
};
|
|
208
|
-
const widgets = namespace.classes.filter((cls) => checkIsWidget(cls.name, namespace.name));
|
|
209
|
-
return widgets.sort((a, b) => {
|
|
210
|
-
if (a.name === "Widget")
|
|
211
|
-
return -1;
|
|
212
|
-
if (b.name === "Widget")
|
|
213
|
-
return 1;
|
|
214
|
-
if (a.name === "Window")
|
|
215
|
-
return -1;
|
|
216
|
-
if (b.name === "Window")
|
|
217
|
-
return 1;
|
|
218
|
-
return a.name.localeCompare(b.name);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
analyzeContainerCapabilities(widget) {
|
|
222
|
-
const hasAppend = widget.methods.some((m) => m.name === "append");
|
|
223
|
-
const hasSetChild = widget.methods.some((m) => m.name === "set_child");
|
|
224
|
-
const namedChildSlots = widget.properties
|
|
225
|
-
.filter((prop) => {
|
|
226
|
-
if (!prop.writable)
|
|
227
|
-
return false;
|
|
228
|
-
if (WIDGET_REFERENCE_PROPERTIES.has(prop.name))
|
|
229
|
-
return false;
|
|
230
|
-
const typeName = prop.type.name;
|
|
231
|
-
return typeName === "Gtk.Widget" || typeName === "Widget" || isWidgetSubclass(typeName, this.classMap);
|
|
232
|
-
})
|
|
233
|
-
.map((prop) => ({
|
|
234
|
-
propertyName: prop.name,
|
|
235
|
-
slotName: toPascalCase(prop.name),
|
|
236
|
-
}));
|
|
237
|
-
if (isToolbarViewWidget(widget.name)) {
|
|
238
|
-
namedChildSlots.push({ propertyName: "top", slotName: "Top" });
|
|
239
|
-
namedChildSlots.push({ propertyName: "bottom", slotName: "Bottom" });
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
supportsMultipleChildren: hasAppend,
|
|
243
|
-
supportsSingleChild: hasSetChild,
|
|
244
|
-
namedChildSlots,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
generateWidgetPropsInterfaces(widgets, containerMetadata) {
|
|
248
|
-
const sections = [];
|
|
249
|
-
for (const { widget, namespace } of widgets) {
|
|
250
|
-
if (widget.name === "Widget")
|
|
251
|
-
continue;
|
|
252
|
-
const metadataKey = `${namespace}.${widget.name}`;
|
|
253
|
-
const metadata = containerMetadata.get(metadataKey);
|
|
254
|
-
if (!metadata)
|
|
255
|
-
throw new Error(`Missing container metadata for widget: ${metadataKey}`);
|
|
256
|
-
this.currentNamespace = namespace;
|
|
257
|
-
this.usedNamespaces.add(namespace);
|
|
258
|
-
this.typeMapper.setTypeRegistry(this.typeRegistry, namespace);
|
|
259
|
-
sections.push(this.generateWidgetProps(widget, metadata));
|
|
260
|
-
}
|
|
261
|
-
return sections.join("\n");
|
|
262
|
-
}
|
|
263
|
-
getWidgetExportName(widget) {
|
|
264
|
-
const baseName = toPascalCase(widget.name);
|
|
265
|
-
if (this.currentNamespace === "Gtk")
|
|
266
|
-
return baseName;
|
|
267
|
-
return `${this.currentNamespace}${baseName}`;
|
|
268
|
-
}
|
|
269
|
-
generateWidgetProps(widget, metadata) {
|
|
270
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
271
|
-
const parentPropsName = this.getParentPropsName(widget);
|
|
272
|
-
const namedChildPropNames = new Set(metadata.namedChildSlots.map((s) => toCamelCase(s.propertyName)));
|
|
273
|
-
const lines = [];
|
|
274
|
-
lines.push(`/** Props for the {@link ${widgetName}} widget. */`);
|
|
275
|
-
lines.push(`export interface ${widgetName}Props extends ${parentPropsName} {`);
|
|
276
|
-
const requiredCtorParams = this.getRequiredConstructorParams(widget);
|
|
277
|
-
const seenProps = new Set();
|
|
278
|
-
const allProps = [...widget.properties];
|
|
279
|
-
for (const prop of widget.properties) {
|
|
280
|
-
seenProps.add(prop.name);
|
|
281
|
-
}
|
|
282
|
-
const seenSignals = new Set();
|
|
283
|
-
const allSignals = [...widget.signals];
|
|
284
|
-
for (const signal of widget.signals) {
|
|
285
|
-
seenSignals.add(signal.name);
|
|
286
|
-
}
|
|
287
|
-
const parentInterfaces = this.getAncestorInterfaces(widget);
|
|
288
|
-
for (const ifaceName of widget.implements) {
|
|
289
|
-
if (parentInterfaces.has(ifaceName))
|
|
290
|
-
continue;
|
|
291
|
-
const iface = this.interfaceMap.get(ifaceName);
|
|
292
|
-
if (iface) {
|
|
293
|
-
for (const prop of iface.properties) {
|
|
294
|
-
if (!seenProps.has(prop.name)) {
|
|
295
|
-
seenProps.add(prop.name);
|
|
296
|
-
allProps.push(prop);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
for (const signal of iface.signals) {
|
|
300
|
-
if (!seenSignals.has(signal.name)) {
|
|
301
|
-
seenSignals.add(signal.name);
|
|
302
|
-
allSignals.push(signal);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const specificProps = allProps.filter((prop) => {
|
|
308
|
-
const propName = toCamelCase(prop.name);
|
|
309
|
-
return !this.widgetPropertyNames.has(propName) && !namedChildPropNames.has(propName);
|
|
310
|
-
});
|
|
311
|
-
const emittedProps = new Set();
|
|
312
|
-
for (const prop of specificProps) {
|
|
313
|
-
const propName = toCamelCase(prop.name);
|
|
314
|
-
emittedProps.add(prop.name);
|
|
315
|
-
const typeMapping = this.typeMapper.mapType(prop.type);
|
|
316
|
-
const tsType = this.toJsxPropertyType(typeMapping.ts, this.currentNamespace);
|
|
317
|
-
const isRequiredByConstructor = requiredCtorParams.has(prop.name);
|
|
318
|
-
const isRequired = isRequiredByConstructor;
|
|
319
|
-
if (prop.doc) {
|
|
320
|
-
lines.push(this.formatDoc(prop.doc, "\t").trimEnd());
|
|
321
|
-
}
|
|
322
|
-
lines.push(`\t${propName}${isRequired ? "" : "?"}: ${tsType};`);
|
|
323
|
-
}
|
|
324
|
-
for (const paramName of requiredCtorParams) {
|
|
325
|
-
if (emittedProps.has(paramName))
|
|
326
|
-
continue;
|
|
327
|
-
const propName = toCamelCase(paramName);
|
|
328
|
-
const inheritedProp = this.findInheritedProperty(widget, paramName);
|
|
329
|
-
if (inheritedProp) {
|
|
330
|
-
const typeMapping = this.typeMapper.mapType(inheritedProp.type);
|
|
331
|
-
const tsType = this.toJsxPropertyType(typeMapping.ts, this.currentNamespace);
|
|
332
|
-
lines.push(`\t${propName}: ${tsType};`);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
const specificSignals = allSignals.filter((signal) => {
|
|
336
|
-
const signalName = toCamelCase(signal.name);
|
|
337
|
-
return !this.widgetSignalNames.has(signalName);
|
|
338
|
-
});
|
|
339
|
-
if (specificSignals.length > 0) {
|
|
340
|
-
lines.push("");
|
|
341
|
-
for (const signal of specificSignals) {
|
|
342
|
-
if (signal.doc) {
|
|
343
|
-
lines.push(this.formatDoc(signal.doc, "\t").trimEnd());
|
|
344
|
-
}
|
|
345
|
-
lines.push(`\t${this.generateSignalHandler(signal, widget.name)}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (isListWidget(widget.name) || isColumnViewWidget(widget.name)) {
|
|
349
|
-
lines.push("");
|
|
350
|
-
lines.push(`\t/** Array of selected item IDs */`);
|
|
351
|
-
lines.push(`\tselected?: string[];`);
|
|
352
|
-
lines.push(`\t/** Called when selection changes with array of selected item IDs */`);
|
|
353
|
-
lines.push(`\tonSelectionChanged?: (ids: string[]) => void;`);
|
|
354
|
-
lines.push(`\t/** Selection mode: SINGLE (default) or MULTIPLE */`);
|
|
355
|
-
lines.push(`\tselectionMode?: import("@gtkx/ffi/gtk").SelectionMode;`);
|
|
356
|
-
}
|
|
357
|
-
if (isListWidget(widget.name)) {
|
|
358
|
-
lines.push("");
|
|
359
|
-
lines.push(`\t/**`);
|
|
360
|
-
lines.push(`\t * Render function for list items.`);
|
|
361
|
-
lines.push(`\t * Called with null during setup (for loading state) and with the actual item during bind.`);
|
|
362
|
-
lines.push(`\t */`);
|
|
363
|
-
lines.push(`\trenderItem: (item: unknown) => import("react").ReactElement;`);
|
|
364
|
-
}
|
|
365
|
-
if (isDropDownWidget(widget.name)) {
|
|
366
|
-
lines.push("");
|
|
367
|
-
lines.push(`\t/** ID of the initially selected item */`);
|
|
368
|
-
lines.push(`\tselectedId?: string;`);
|
|
369
|
-
lines.push(`\t/** Called when selection changes with the selected item's ID */`);
|
|
370
|
-
lines.push(`\tonSelectionChanged?: (id: string) => void;`);
|
|
371
|
-
}
|
|
372
|
-
lines.push("");
|
|
373
|
-
const ffiTypeName = toPascalCase(widget.name);
|
|
374
|
-
lines.push(`\tref?: Ref<${this.currentNamespace}.${ffiTypeName}>;`);
|
|
375
|
-
lines.push(`}`);
|
|
376
|
-
return `${lines.join("\n")}\n`;
|
|
377
|
-
}
|
|
378
|
-
getParentPropsName(widget) {
|
|
379
|
-
if (widget.name === "Window")
|
|
380
|
-
return "WidgetProps";
|
|
381
|
-
if (widget.name === "ApplicationWindow")
|
|
382
|
-
return "WindowProps";
|
|
383
|
-
if (!widget.parent)
|
|
384
|
-
return "WidgetProps";
|
|
385
|
-
const parentNs = widget.parent.includes(".") ? widget.parent.split(".")[0] : this.currentNamespace;
|
|
386
|
-
const parentName = widget.parent.includes(".") ? widget.parent.split(".")[1] : widget.parent;
|
|
387
|
-
if (parentName === "Widget")
|
|
388
|
-
return "WidgetProps";
|
|
389
|
-
if (parentName === "Window")
|
|
390
|
-
return "WindowProps";
|
|
391
|
-
const baseName = toPascalCase(parentName ?? "");
|
|
392
|
-
if (parentNs === "Gtk") {
|
|
393
|
-
return `${baseName}Props`;
|
|
394
|
-
}
|
|
395
|
-
return `${parentNs}${baseName}Props`;
|
|
396
|
-
}
|
|
397
|
-
getRequiredConstructorParams(widget) {
|
|
398
|
-
const required = new Set();
|
|
399
|
-
const mainCtor = widget.constructors.find((c) => c.name === "new");
|
|
400
|
-
if (!mainCtor)
|
|
401
|
-
return required;
|
|
402
|
-
const internallyProvided = INTERNALLY_PROVIDED_PARAMS[widget.name] ?? new Set();
|
|
403
|
-
for (const param of mainCtor.parameters) {
|
|
404
|
-
if (!param.nullable && !param.optional) {
|
|
405
|
-
const normalizedName = param.name.replace(/_/g, "-");
|
|
406
|
-
if (!internallyProvided.has(normalizedName)) {
|
|
407
|
-
required.add(normalizedName);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return required;
|
|
412
|
-
}
|
|
413
|
-
getConstructorParams(widget) {
|
|
414
|
-
const mainCtor = widget.constructors.find((c) => c.name === "new");
|
|
415
|
-
if (!mainCtor)
|
|
416
|
-
return [];
|
|
417
|
-
const params = mainCtor.parameters.map((param) => ({
|
|
418
|
-
name: toCamelCase(param.name),
|
|
419
|
-
hasDefault: param.nullable || param.optional || false,
|
|
420
|
-
}));
|
|
421
|
-
const required = params.filter((p) => !p.hasDefault);
|
|
422
|
-
const optional = params.filter((p) => p.hasDefault);
|
|
423
|
-
return [...required, ...optional];
|
|
424
|
-
}
|
|
425
|
-
generateConstructorArgsMetadata(widgets) {
|
|
426
|
-
const entries = [];
|
|
427
|
-
for (const { widget, namespace } of widgets) {
|
|
428
|
-
const params = this.getConstructorParams(widget);
|
|
429
|
-
if (params.length === 0)
|
|
430
|
-
continue;
|
|
431
|
-
this.currentNamespace = namespace;
|
|
432
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
433
|
-
const paramStrs = params.map((p) => `{ name: "${p.name}", hasDefault: ${p.hasDefault} }`);
|
|
434
|
-
entries.push(`\t${widgetName}: [${paramStrs.join(", ")}]`);
|
|
435
|
-
}
|
|
436
|
-
if (entries.length === 0) {
|
|
437
|
-
return `export const CONSTRUCTOR_PARAMS: Record<string, { name: string; hasDefault: boolean }[]> = {};\n`;
|
|
438
|
-
}
|
|
439
|
-
return `export const CONSTRUCTOR_PARAMS: Record<string, { name: string; hasDefault: boolean }[]> = {\n${entries.join(",\n")},\n};\n`;
|
|
440
|
-
}
|
|
441
|
-
generatePropSettersMap(widgets) {
|
|
442
|
-
const widgetEntries = [];
|
|
443
|
-
for (const { widget, namespace } of widgets) {
|
|
444
|
-
this.currentNamespace = namespace;
|
|
445
|
-
const propSetterPairs = [];
|
|
446
|
-
const allProps = this.collectAllProperties(widget);
|
|
447
|
-
for (const prop of allProps) {
|
|
448
|
-
if (prop.setter) {
|
|
449
|
-
const propName = toCamelCase(prop.name);
|
|
450
|
-
const setterName = toCamelCase(prop.setter);
|
|
451
|
-
propSetterPairs.push(`"${propName}": "${setterName}"`);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (propSetterPairs.length > 0) {
|
|
455
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
456
|
-
widgetEntries.push(`\t${widgetName}: { ${propSetterPairs.join(", ")} }`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (widgetEntries.length === 0) {
|
|
460
|
-
return `export const PROP_SETTERS: Record<string, Record<string, string>> = {};\n`;
|
|
461
|
-
}
|
|
462
|
-
return `export const PROP_SETTERS: Record<string, Record<string, string>> = {\n${widgetEntries.join(",\n")},\n};\n`;
|
|
463
|
-
}
|
|
464
|
-
generateSetterGetterMap(widgets) {
|
|
465
|
-
const widgetEntries = [];
|
|
466
|
-
for (const { widget, namespace } of widgets) {
|
|
467
|
-
this.currentNamespace = namespace;
|
|
468
|
-
const setterGetterPairs = [];
|
|
469
|
-
const allProps = this.collectAllProperties(widget);
|
|
470
|
-
const parentMethodNames = this.collectParentMethodNames(widget);
|
|
471
|
-
const widgetClassName = toPascalCase(widget.name);
|
|
472
|
-
for (const prop of allProps) {
|
|
473
|
-
if (prop.setter && prop.getter) {
|
|
474
|
-
const setterName = toCamelCase(prop.setter);
|
|
475
|
-
let getterName = toCamelCase(prop.getter);
|
|
476
|
-
if (parentMethodNames.has(prop.getter)) {
|
|
477
|
-
getterName = `${getterName}${widgetClassName}`;
|
|
478
|
-
}
|
|
479
|
-
setterGetterPairs.push(`"${setterName}": "${getterName}"`);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (setterGetterPairs.length > 0) {
|
|
483
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
484
|
-
widgetEntries.push(`\t${widgetName}: { ${setterGetterPairs.join(", ")} }`);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (widgetEntries.length === 0) {
|
|
488
|
-
return `export const SETTER_GETTERS: Record<string, Record<string, string>> = {};\n`;
|
|
489
|
-
}
|
|
490
|
-
return `export const SETTER_GETTERS: Record<string, Record<string, string>> = {\n${widgetEntries.join(",\n")},\n};\n`;
|
|
491
|
-
}
|
|
492
|
-
collectParentMethodNames(widget) {
|
|
493
|
-
const names = new Set();
|
|
494
|
-
const visited = new Set();
|
|
495
|
-
let parentRef = widget.parent;
|
|
496
|
-
while (parentRef && !visited.has(parentRef)) {
|
|
497
|
-
visited.add(parentRef);
|
|
498
|
-
const current = this.classMap.get(parentRef) ??
|
|
499
|
-
this.classMap.get(`${this.currentNamespace}.${parentRef}`) ??
|
|
500
|
-
this.classMap.get(`Gtk.${parentRef}`);
|
|
501
|
-
if (!current)
|
|
502
|
-
break;
|
|
503
|
-
for (const method of current.methods) {
|
|
504
|
-
names.add(method.name);
|
|
505
|
-
}
|
|
506
|
-
for (const ifaceName of current.implements) {
|
|
507
|
-
const iface = this.interfaceMap.get(ifaceName);
|
|
508
|
-
if (iface) {
|
|
509
|
-
for (const method of iface.methods) {
|
|
510
|
-
names.add(method.name);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
parentRef = current.parent;
|
|
515
|
-
}
|
|
516
|
-
return names;
|
|
517
|
-
}
|
|
518
|
-
collectAllProperties(widget) {
|
|
519
|
-
const props = [];
|
|
520
|
-
const seen = new Set();
|
|
521
|
-
let current = widget;
|
|
522
|
-
while (current) {
|
|
523
|
-
for (const prop of current.properties) {
|
|
524
|
-
if (!seen.has(prop.name)) {
|
|
525
|
-
seen.add(prop.name);
|
|
526
|
-
props.push({ name: prop.name, setter: prop.setter, getter: prop.getter });
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
for (const ifaceName of current.implements) {
|
|
530
|
-
const iface = this.interfaceMap.get(ifaceName);
|
|
531
|
-
if (iface) {
|
|
532
|
-
for (const prop of iface.properties) {
|
|
533
|
-
if (!seen.has(prop.name)) {
|
|
534
|
-
seen.add(prop.name);
|
|
535
|
-
props.push({ name: prop.name, setter: prop.setter, getter: prop.getter });
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
if (current.parent) {
|
|
541
|
-
current =
|
|
542
|
-
this.classMap.get(current.parent) ??
|
|
543
|
-
this.classMap.get(`${this.currentNamespace}.${current.parent}`) ??
|
|
544
|
-
this.classMap.get(`Gtk.${current.parent}`);
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
current = undefined;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return props;
|
|
551
|
-
}
|
|
552
|
-
getAncestorInterfaces(widget) {
|
|
553
|
-
const interfaces = new Set();
|
|
554
|
-
let current = widget.parent ? this.classMap.get(widget.parent) : undefined;
|
|
555
|
-
while (current) {
|
|
556
|
-
for (const ifaceName of current.implements) {
|
|
557
|
-
interfaces.add(ifaceName);
|
|
558
|
-
}
|
|
559
|
-
current = current.parent ? this.classMap.get(current.parent) : undefined;
|
|
560
|
-
}
|
|
561
|
-
return interfaces;
|
|
562
|
-
}
|
|
563
|
-
findInheritedProperty(widget, propName) {
|
|
564
|
-
let current = widget.parent ? this.classMap.get(widget.parent) : undefined;
|
|
565
|
-
while (current) {
|
|
566
|
-
const prop = current.properties.find((p) => p.name === propName);
|
|
567
|
-
if (prop)
|
|
568
|
-
return prop;
|
|
569
|
-
for (const ifaceName of current.implements) {
|
|
570
|
-
const iface = this.interfaceMap.get(ifaceName);
|
|
571
|
-
if (iface) {
|
|
572
|
-
const ifaceProp = iface.properties.find((p) => p.name === propName);
|
|
573
|
-
if (ifaceProp)
|
|
574
|
-
return ifaceProp;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
current = current.parent ? this.classMap.get(current.parent) : undefined;
|
|
578
|
-
}
|
|
579
|
-
for (const ifaceName of widget.implements) {
|
|
580
|
-
const iface = this.interfaceMap.get(ifaceName);
|
|
581
|
-
if (iface) {
|
|
582
|
-
const ifaceProp = iface.properties.find((p) => p.name === propName);
|
|
583
|
-
if (ifaceProp)
|
|
584
|
-
return ifaceProp;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return undefined;
|
|
588
|
-
}
|
|
589
|
-
generateSignalHandler(signal, widgetName) {
|
|
590
|
-
const signalName = toCamelCase(signal.name);
|
|
591
|
-
const handlerName = `on${signalName.charAt(0).toUpperCase()}${signalName.slice(1)}`;
|
|
592
|
-
const handlerType = this.buildSignalHandlerType(signal, widgetName);
|
|
593
|
-
return `${handlerName}?: ${handlerType};`;
|
|
594
|
-
}
|
|
595
|
-
getSignalParamFfiType(typeName) {
|
|
596
|
-
if (!typeName)
|
|
597
|
-
return undefined;
|
|
598
|
-
if (typeName.includes(".")) {
|
|
599
|
-
const [ns, className] = typeName.split(".", 2);
|
|
600
|
-
if (ns && className) {
|
|
601
|
-
const registered = this.typeRegistry.resolve(`${ns}.${className}`);
|
|
602
|
-
if (registered && (registered.kind === "class" || registered.kind === "interface")) {
|
|
603
|
-
this.usedNamespaces.add(ns);
|
|
604
|
-
return `${ns}.${registered.transformedName}`;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return undefined;
|
|
608
|
-
}
|
|
609
|
-
const registered = this.typeRegistry.resolveInNamespace(typeName, this.currentNamespace);
|
|
610
|
-
if (registered && (registered.kind === "class" || registered.kind === "interface")) {
|
|
611
|
-
this.usedNamespaces.add(registered.namespace);
|
|
612
|
-
if (registered.namespace === this.currentNamespace) {
|
|
613
|
-
return `${this.currentNamespace}.${registered.transformedName}`;
|
|
614
|
-
}
|
|
615
|
-
return `${registered.namespace}.${registered.transformedName}`;
|
|
616
|
-
}
|
|
617
|
-
return undefined;
|
|
618
|
-
}
|
|
619
|
-
addNamespacePrefix(tsType) {
|
|
620
|
-
const primitives = new Set(["boolean", "number", "string", "void", "unknown", "null", "undefined"]);
|
|
621
|
-
if (primitives.has(tsType))
|
|
622
|
-
return tsType;
|
|
623
|
-
if (tsType.endsWith("[]")) {
|
|
624
|
-
const elementType = tsType.slice(0, -2);
|
|
625
|
-
if (primitives.has(elementType))
|
|
626
|
-
return tsType;
|
|
627
|
-
if (elementType.includes(".") || elementType.includes("<") || elementType.includes("("))
|
|
628
|
-
return tsType;
|
|
629
|
-
return `${this.currentNamespace}.${elementType}[]`;
|
|
630
|
-
}
|
|
631
|
-
if (tsType.includes(".") || tsType.includes("<") || tsType.includes("("))
|
|
632
|
-
return tsType;
|
|
633
|
-
return `${this.currentNamespace}.${tsType}`;
|
|
634
|
-
}
|
|
635
|
-
toJsxPropertyType(tsType, namespace) {
|
|
636
|
-
const result = toJsxPropertyTypeBase(tsType, namespace);
|
|
637
|
-
if (result.includes(".") && !result.includes("<") && !result.includes("(")) {
|
|
638
|
-
const ns = result.split(".")[0];
|
|
639
|
-
if (ns) {
|
|
640
|
-
this.usedNamespaces.add(ns);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return result;
|
|
644
|
-
}
|
|
645
|
-
buildSignalHandlerType(signal, widgetName) {
|
|
646
|
-
const selfParam = `self: ${this.currentNamespace}.${toPascalCase(widgetName)}`;
|
|
647
|
-
const otherParams = signal.parameters
|
|
648
|
-
?.map((p) => {
|
|
649
|
-
const ffiType = this.getSignalParamFfiType(p.type.name);
|
|
650
|
-
if (ffiType) {
|
|
651
|
-
return `${toCamelCase(p.name)}: ${ffiType}`;
|
|
652
|
-
}
|
|
653
|
-
const paramMapping = this.typeMapper.mapParameter(p);
|
|
654
|
-
const tsType = this.addNamespacePrefix(paramMapping.ts);
|
|
655
|
-
return `${toCamelCase(p.name)}: ${tsType}`;
|
|
656
|
-
})
|
|
657
|
-
.join(", ") ?? "";
|
|
658
|
-
const returnType = signal.returnType
|
|
659
|
-
? this.addNamespacePrefix(this.typeMapper.mapType(signal.returnType).ts)
|
|
660
|
-
: "void";
|
|
661
|
-
const params = otherParams ? `${selfParam}, ${otherParams}` : selfParam;
|
|
662
|
-
return `(${params}) => ${returnType}`;
|
|
663
|
-
}
|
|
664
|
-
generateExports(widgets, containerMetadata) {
|
|
665
|
-
const lines = [];
|
|
666
|
-
for (const { widget, namespace } of widgets) {
|
|
667
|
-
this.currentNamespace = namespace;
|
|
668
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
669
|
-
const metadataKey = `${namespace}.${widget.name}`;
|
|
670
|
-
const metadata = containerMetadata.get(metadataKey);
|
|
671
|
-
if (!metadata)
|
|
672
|
-
throw new Error(`Missing container metadata for widget: ${metadataKey}`);
|
|
673
|
-
const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
|
|
674
|
-
const hasMeaningfulSlots = nonChildSlots.length > 0 ||
|
|
675
|
-
isListWidget(widget.name) ||
|
|
676
|
-
isColumnViewWidget(widget.name) ||
|
|
677
|
-
isDropDownWidget(widget.name) ||
|
|
678
|
-
isGridWidget(widget.name) ||
|
|
679
|
-
isNotebookWidget(widget.name) ||
|
|
680
|
-
isStackWidget(widget.name) ||
|
|
681
|
-
isPopoverMenuWidget(widget.name);
|
|
682
|
-
const docComment = widget.doc ? this.formatDoc(widget.doc).trimEnd() : "";
|
|
683
|
-
if (hasMeaningfulSlots) {
|
|
684
|
-
if (isListWidget(widget.name) ||
|
|
685
|
-
isColumnViewWidget(widget.name) ||
|
|
686
|
-
isDropDownWidget(widget.name) ||
|
|
687
|
-
isStackWidget(widget.name) ||
|
|
688
|
-
isPopoverMenuWidget(widget.name)) {
|
|
689
|
-
const wrapperComponents = this.generateGenericWrapperComponents(widgetName, metadata);
|
|
690
|
-
const exportMembers = this.getWrapperExportMembers(widgetName, metadata);
|
|
691
|
-
if (docComment) {
|
|
692
|
-
lines.push(`${wrapperComponents}\n${docComment}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
lines.push(`${wrapperComponents}\nexport const ${widgetName} = {\n\t${exportMembers.join(",\n\t")},\n};`);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
const valueMembers = [
|
|
700
|
-
`Root: "${widgetName}.Root" as const`,
|
|
701
|
-
...metadata.namedChildSlots.map((slot) => `${slot.slotName}: "${widgetName}.${slot.slotName}" as const`),
|
|
702
|
-
...(isGridWidget(widget.name) ? [`Child: "${widgetName}.Child" as const`] : []),
|
|
703
|
-
...(isNotebookWidget(widget.name) ? [`Page: "${widgetName}.Page" as const`] : []),
|
|
704
|
-
];
|
|
705
|
-
if (docComment) {
|
|
706
|
-
lines.push(`${docComment}\nexport const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
707
|
-
}
|
|
708
|
-
else {
|
|
709
|
-
lines.push(`export const ${widgetName} = {\n\t${valueMembers.join(",\n\t")},\n};`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
if (docComment) {
|
|
715
|
-
lines.push(`${docComment}\nexport const ${widgetName} = "${widgetName}" as const;`);
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
lines.push(`export const ${widgetName} = "${widgetName}" as const;`);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
lines.push(this.generateApplicationMenuComponents());
|
|
723
|
-
return `${lines.join("\n")}\n`;
|
|
724
|
-
}
|
|
725
|
-
generateApplicationMenuComponents() {
|
|
726
|
-
return `/**
|
|
727
|
-
* Sets the application-wide menu bar.
|
|
728
|
-
* The menu will appear in the window's title bar on supported platforms.
|
|
729
|
-
* Use Menu.Item, Menu.Section, and Menu.Submenu as children.
|
|
730
|
-
*/
|
|
731
|
-
export const ApplicationMenu = "ApplicationMenu" as const;
|
|
732
|
-
|
|
733
|
-
function MenuItem(props: MenuItemProps): import("react").ReactElement {
|
|
734
|
-
\treturn createElement("Menu.Item", props);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function MenuSection(props: MenuSectionProps): import("react").ReactElement {
|
|
738
|
-
\treturn createElement("Menu.Section", props);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
function MenuSubmenu(props: MenuSubmenuProps): import("react").ReactElement {
|
|
742
|
-
\treturn createElement("Menu.Submenu", props);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Declarative menu builder for use with PopoverMenu and ApplicationMenu.
|
|
747
|
-
* Use Menu.Item for action items, Menu.Section for groups, Menu.Submenu for nested menus.
|
|
748
|
-
*/
|
|
749
|
-
export const Menu = {
|
|
750
|
-
\tItem: MenuItem,
|
|
751
|
-
\tSection: MenuSection,
|
|
752
|
-
\tSubmenu: MenuSubmenu,
|
|
753
|
-
};
|
|
754
|
-
`;
|
|
755
|
-
}
|
|
756
|
-
getWrapperExportMembers(widgetName, metadata) {
|
|
757
|
-
const name = toPascalCase(widgetName);
|
|
758
|
-
const members = [`Root: ${name}Root`];
|
|
759
|
-
if (isListWidget(widgetName)) {
|
|
760
|
-
members.push(`Item: ${name}Item`);
|
|
761
|
-
}
|
|
762
|
-
else if (isColumnViewWidget(widgetName)) {
|
|
763
|
-
members.push(`Column: ${name}Column`);
|
|
764
|
-
members.push(`Item: ${name}Item`);
|
|
765
|
-
}
|
|
766
|
-
else if (isDropDownWidget(widgetName)) {
|
|
767
|
-
members.push(`Item: ${name}Item`);
|
|
768
|
-
}
|
|
769
|
-
else if (isStackWidget(widgetName)) {
|
|
770
|
-
members.push(`Page: ${name}Page`);
|
|
771
|
-
}
|
|
772
|
-
else if (isPopoverMenuWidget(widgetName)) {
|
|
773
|
-
members.push(`Item: ${name}Item`);
|
|
774
|
-
members.push(`Section: ${name}Section`);
|
|
775
|
-
members.push(`Submenu: ${name}Submenu`);
|
|
776
|
-
}
|
|
777
|
-
for (const slot of metadata.namedChildSlots) {
|
|
778
|
-
members.push(`${slot.slotName}: ${name}${slot.slotName}`);
|
|
779
|
-
}
|
|
780
|
-
return members;
|
|
781
|
-
}
|
|
782
|
-
generateGenericWrapperComponents(widgetName, metadata) {
|
|
783
|
-
const name = toPascalCase(widgetName);
|
|
784
|
-
const lines = [];
|
|
785
|
-
if (isListWidget(widgetName)) {
|
|
786
|
-
lines.push(`/**`);
|
|
787
|
-
lines.push(` * Props for the ${name}.Root component with type-safe item rendering.`);
|
|
788
|
-
lines.push(` * @typeParam T - The type of items in the list.`);
|
|
789
|
-
lines.push(` */`);
|
|
790
|
-
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "renderItem"> {`);
|
|
791
|
-
lines.push(`\t/** Render function for list items. Called with null during setup (for loading state). */`);
|
|
792
|
-
lines.push(`\trenderItem: (item: T | null) => import("react").ReactElement;`);
|
|
793
|
-
lines.push(`}`);
|
|
794
|
-
lines.push(``);
|
|
795
|
-
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
796
|
-
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
797
|
-
lines.push(`}`);
|
|
798
|
-
lines.push(``);
|
|
799
|
-
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
800
|
-
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
801
|
-
lines.push(`}`);
|
|
802
|
-
}
|
|
803
|
-
else if (isColumnViewWidget(widgetName)) {
|
|
804
|
-
lines.push(`/**`);
|
|
805
|
-
lines.push(` * Props for the ${name}.Root component with column sorting UI indicators.`);
|
|
806
|
-
lines.push(` * @typeParam C - The union type of column IDs for type-safe sorting indicators.`);
|
|
807
|
-
lines.push(` */`);
|
|
808
|
-
lines.push(`interface ${name}RootPropsExtended<C extends string = string> extends ${name}Props, ColumnViewRootProps<C> {}`);
|
|
809
|
-
lines.push(``);
|
|
810
|
-
lines.push(`function ${name}Root<C extends string = string>(props: ${name}RootPropsExtended<C>): import("react").ReactElement {`);
|
|
811
|
-
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
812
|
-
lines.push(`}`);
|
|
813
|
-
lines.push(``);
|
|
814
|
-
lines.push(`/**`);
|
|
815
|
-
lines.push(` * Props for ${name}.Column with type-safe cell rendering.`);
|
|
816
|
-
lines.push(` * @typeParam T - The type of items passed to the renderCell function.`);
|
|
817
|
-
lines.push(` */`);
|
|
818
|
-
lines.push(`interface ${name}GenericColumnProps<T> extends Omit<ColumnViewColumnProps, "renderCell"> {`);
|
|
819
|
-
lines.push(`\t/** Render function for column cells. Called with null during setup (for loading state). */`);
|
|
820
|
-
lines.push(`\trenderCell: (item: T | null) => import("react").ReactElement;`);
|
|
821
|
-
lines.push(`}`);
|
|
822
|
-
lines.push(``);
|
|
823
|
-
lines.push(`function ${name}Column<T>(props: ${name}GenericColumnProps<T>): import("react").ReactElement {`);
|
|
824
|
-
lines.push(`\treturn createElement("${name}.Column", props);`);
|
|
825
|
-
lines.push(`}`);
|
|
826
|
-
lines.push(``);
|
|
827
|
-
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
828
|
-
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
829
|
-
lines.push(`}`);
|
|
830
|
-
}
|
|
831
|
-
else if (isDropDownWidget(widgetName)) {
|
|
832
|
-
lines.push(`function ${name}Root(props: ${name}Props): import("react").ReactElement {`);
|
|
833
|
-
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
834
|
-
lines.push(`}`);
|
|
835
|
-
lines.push(``);
|
|
836
|
-
lines.push(`function ${name}Item(props: StringListItemProps): import("react").ReactElement {`);
|
|
837
|
-
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
838
|
-
lines.push(`}`);
|
|
839
|
-
}
|
|
840
|
-
else if (isStackWidget(widgetName)) {
|
|
841
|
-
lines.push(`function ${name}Root(props: StackRootProps & ${name}Props): import("react").ReactElement {`);
|
|
842
|
-
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
843
|
-
lines.push(`}`);
|
|
844
|
-
lines.push(``);
|
|
845
|
-
lines.push(`function ${name}Page(props: StackPageProps): import("react").ReactElement {`);
|
|
846
|
-
lines.push(`\treturn createElement("${name}.Page", props);`);
|
|
847
|
-
lines.push(`}`);
|
|
848
|
-
}
|
|
849
|
-
else if (isPopoverMenuWidget(widgetName)) {
|
|
850
|
-
lines.push(`function ${name}Root(props: MenuRootProps & ${name}Props): import("react").ReactElement {`);
|
|
851
|
-
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
852
|
-
lines.push(`}`);
|
|
853
|
-
lines.push(``);
|
|
854
|
-
lines.push(`function ${name}Item(props: MenuItemProps): import("react").ReactElement {`);
|
|
855
|
-
lines.push(`\treturn createElement("Menu.Item", props);`);
|
|
856
|
-
lines.push(`}`);
|
|
857
|
-
lines.push(``);
|
|
858
|
-
lines.push(`function ${name}Section(props: MenuSectionProps): import("react").ReactElement {`);
|
|
859
|
-
lines.push(`\treturn createElement("Menu.Section", props);`);
|
|
860
|
-
lines.push(`}`);
|
|
861
|
-
lines.push(``);
|
|
862
|
-
lines.push(`function ${name}Submenu(props: MenuSubmenuProps): import("react").ReactElement {`);
|
|
863
|
-
lines.push(`\treturn createElement("Menu.Submenu", props);`);
|
|
864
|
-
lines.push(`}`);
|
|
865
|
-
}
|
|
866
|
-
for (const slot of metadata.namedChildSlots) {
|
|
867
|
-
lines.push(``);
|
|
868
|
-
lines.push(`function ${name}${slot.slotName}(props: SlotProps): import("react").ReactElement {`);
|
|
869
|
-
lines.push(`\treturn createElement("${name}.${slot.slotName}", props);`);
|
|
870
|
-
lines.push(`}`);
|
|
871
|
-
}
|
|
872
|
-
return lines.join("\n");
|
|
873
|
-
}
|
|
874
|
-
generateJsxNamespace(widgets, containerMetadata) {
|
|
875
|
-
const elements = [];
|
|
876
|
-
for (const { widget, namespace } of widgets) {
|
|
877
|
-
if (widget.name === "Widget")
|
|
878
|
-
continue;
|
|
879
|
-
this.currentNamespace = namespace;
|
|
880
|
-
const widgetName = this.getWidgetExportName(widget);
|
|
881
|
-
const propsName = `${widgetName}Props`;
|
|
882
|
-
const metadataKey = `${namespace}.${widget.name}`;
|
|
883
|
-
const metadata = containerMetadata.get(metadataKey);
|
|
884
|
-
if (!metadata)
|
|
885
|
-
throw new Error(`Missing container metadata for widget: ${metadataKey}`);
|
|
886
|
-
const nonChildSlots = metadata.namedChildSlots.filter((slot) => slot.slotName !== "Child");
|
|
887
|
-
const hasMeaningfulSlots = nonChildSlots.length > 0 ||
|
|
888
|
-
isListWidget(widget.name) ||
|
|
889
|
-
isColumnViewWidget(widget.name) ||
|
|
890
|
-
isDropDownWidget(widget.name) ||
|
|
891
|
-
isGridWidget(widget.name) ||
|
|
892
|
-
isNotebookWidget(widget.name) ||
|
|
893
|
-
isStackWidget(widget.name) ||
|
|
894
|
-
isPopoverMenuWidget(widget.name);
|
|
895
|
-
if (hasMeaningfulSlots) {
|
|
896
|
-
elements.push(`"${widgetName}.Root": ${propsName};`);
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
elements.push(`${widgetName}: ${propsName};`);
|
|
900
|
-
}
|
|
901
|
-
for (const slot of metadata.namedChildSlots) {
|
|
902
|
-
elements.push(`"${widgetName}.${slot.slotName}": SlotProps;`);
|
|
903
|
-
}
|
|
904
|
-
if (isListWidget(widget.name)) {
|
|
905
|
-
elements.push(`"${widgetName}.Item": ListItemProps;`);
|
|
906
|
-
}
|
|
907
|
-
if (isColumnViewWidget(widget.name)) {
|
|
908
|
-
elements.push(`"${widgetName}.Column": ColumnViewColumnProps;`);
|
|
909
|
-
elements.push(`"${widgetName}.Item": ListItemProps;`);
|
|
910
|
-
}
|
|
911
|
-
if (isDropDownWidget(widget.name)) {
|
|
912
|
-
elements.push(`"${widgetName}.Item": StringListItemProps;`);
|
|
913
|
-
}
|
|
914
|
-
if (isGridWidget(widget.name)) {
|
|
915
|
-
elements.push(`"${widgetName}.Child": GridChildProps;`);
|
|
916
|
-
}
|
|
917
|
-
if (isNotebookWidget(widget.name)) {
|
|
918
|
-
elements.push(`"${widgetName}.Page": NotebookPageProps;`);
|
|
919
|
-
}
|
|
920
|
-
if (isStackWidget(widget.name)) {
|
|
921
|
-
elements.push(`"${widgetName}.Page": StackPageProps;`);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
this.addMenuIntrinsicElements(elements);
|
|
925
|
-
elements.push(`ApplicationMenu: MenuRootProps;`);
|
|
926
|
-
return `
|
|
927
|
-
declare global {
|
|
928
|
-
\tnamespace React {
|
|
929
|
-
\t\tnamespace JSX {
|
|
930
|
-
\t\t\tinterface IntrinsicElements {
|
|
931
|
-
\t\t\t\t${elements.join("\n\t\t\t\t")}
|
|
932
|
-
\t\t\t}
|
|
933
|
-
\t\t}
|
|
934
|
-
\t}
|
|
935
|
-
}
|
|
936
|
-
`;
|
|
937
|
-
}
|
|
938
|
-
addMenuIntrinsicElements(elements) {
|
|
939
|
-
elements.push(`"Menu.Item": MenuItemProps;`);
|
|
940
|
-
elements.push(`"Menu.Section": MenuSectionProps;`);
|
|
941
|
-
elements.push(`"Menu.Submenu": MenuSubmenuProps;`);
|
|
942
|
-
}
|
|
943
|
-
async formatCode(code) {
|
|
944
|
-
try {
|
|
945
|
-
return await format(code, {
|
|
946
|
-
parser: "typescript",
|
|
947
|
-
...(this.options.prettierConfig &&
|
|
948
|
-
typeof this.options.prettierConfig === "object" &&
|
|
949
|
-
this.options.prettierConfig !== null
|
|
950
|
-
? this.options.prettierConfig
|
|
951
|
-
: {}),
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
catch (error) {
|
|
955
|
-
console.warn("Failed to format code:", error);
|
|
956
|
-
return code;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|