@gtkx/react 0.1.49 → 0.1.51
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 +7 -12
- package/dist/codegen/jsx-generator.d.ts +12 -2
- package/dist/codegen/jsx-generator.js +128 -26
- package/dist/container-interfaces.d.ts +21 -0
- package/dist/container-interfaces.js +1 -0
- package/dist/factory.js +13 -1
- package/dist/generated/internal.d.ts +7 -0
- package/dist/generated/internal.js +7818 -0
- package/dist/generated/jsx.d.ts +68 -17
- package/dist/generated/jsx.js +56 -7820
- package/dist/node.d.ts +11 -2
- package/dist/node.js +24 -5
- package/dist/nodes/column-view.d.ts +36 -26
- package/dist/nodes/column-view.js +122 -125
- package/dist/nodes/dropdown.d.ts +2 -2
- package/dist/nodes/dropdown.js +4 -4
- package/dist/nodes/grid.d.ts +1 -1
- package/dist/nodes/grid.js +6 -6
- package/dist/nodes/list.d.ts +19 -10
- package/dist/nodes/list.js +45 -40
- package/dist/nodes/menu.d.ts +84 -0
- package/dist/nodes/menu.js +279 -0
- package/dist/nodes/notebook.d.ts +1 -1
- package/dist/nodes/notebook.js +3 -3
- package/dist/nodes/root.js +1 -1
- package/dist/nodes/slot.d.ts +1 -2
- package/dist/nodes/slot.js +2 -2
- package/dist/nodes/stack.d.ts +35 -0
- package/dist/nodes/stack.js +178 -0
- package/dist/nodes/text-view.d.ts +0 -1
- package/dist/nodes/text-view.js +0 -7
- package/dist/reconciler.d.ts +4 -1
- package/dist/reconciler.js +4 -1
- package/dist/types.d.ts +76 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -159,7 +159,9 @@ Query types: `ByRole`, `ByText`, `ByLabelText`, `ByTestId`
|
|
|
159
159
|
- `userEvent.activate(element)` - Activate element (e.g., press Enter in input)
|
|
160
160
|
- `userEvent.type(element, text)` - Type text into input
|
|
161
161
|
- `userEvent.clear(element)` - Clear input text
|
|
162
|
-
- `userEvent.
|
|
162
|
+
- `userEvent.tab(element, options?)` - Simulate Tab navigation
|
|
163
|
+
- `userEvent.selectOptions(element, values)` - Select options in ComboBox/ListBox
|
|
164
|
+
- `userEvent.deselectOptions(element, values)` - Deselect options in ListBox
|
|
163
165
|
|
|
164
166
|
**Low-level Events**:
|
|
165
167
|
- `fireEvent(element, signalName, ...args)` - Emit any GTK signal with optional arguments
|
|
@@ -170,14 +172,6 @@ Query types: `ByRole`, `ByText`, `ByLabelText`, `ByTestId`
|
|
|
170
172
|
|
|
171
173
|
## Examples
|
|
172
174
|
|
|
173
|
-
### Counter
|
|
174
|
-
|
|
175
|
-
A minimal counter app demonstrating state management:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
turbo start --filter=counter-example
|
|
179
|
-
```
|
|
180
|
-
|
|
181
175
|
### GTK4 Demo
|
|
182
176
|
|
|
183
177
|
A comprehensive showcase of GTK4 widgets and features:
|
|
@@ -186,12 +180,13 @@ A comprehensive showcase of GTK4 widgets and features:
|
|
|
186
180
|
turbo start --filter=gtk4-demo
|
|
187
181
|
```
|
|
188
182
|
|
|
189
|
-
###
|
|
183
|
+
### Todo App
|
|
190
184
|
|
|
191
|
-
|
|
185
|
+
A todo app demonstrating `@gtkx/testing` with realistic component tests:
|
|
192
186
|
|
|
193
187
|
```bash
|
|
194
|
-
turbo start --filter=
|
|
188
|
+
turbo start --filter=todo
|
|
189
|
+
turbo test --filter=todo
|
|
195
190
|
```
|
|
196
191
|
|
|
197
192
|
## Packages
|
|
@@ -6,6 +6,15 @@ interface JsxGeneratorOptions {
|
|
|
6
6
|
/** Optional Prettier configuration for formatting output. */
|
|
7
7
|
prettierConfig?: unknown;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Result of the JSX generation containing both public and internal files.
|
|
11
|
+
*/
|
|
12
|
+
interface JsxGeneratorResult {
|
|
13
|
+
/** Public JSX types and components for user consumption. */
|
|
14
|
+
jsx: string;
|
|
15
|
+
/** Internal metadata for reconciler use (not exported to users). */
|
|
16
|
+
internal: string;
|
|
17
|
+
}
|
|
9
18
|
/**
|
|
10
19
|
* Generates JSX type definitions for React components from GTK widget classes.
|
|
11
20
|
* Creates TypeScript interfaces for props and augments React's JSX namespace.
|
|
@@ -29,10 +38,11 @@ export declare class JsxGenerator {
|
|
|
29
38
|
* Generates JSX type definitions for all widgets in a namespace.
|
|
30
39
|
* @param namespace - The parsed GIR namespace
|
|
31
40
|
* @param classMap - Map of class names to class definitions
|
|
32
|
-
* @returns Generated TypeScript code as
|
|
41
|
+
* @returns Generated TypeScript code as public jsx.ts and internal.ts files
|
|
33
42
|
*/
|
|
34
|
-
generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<
|
|
43
|
+
generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<JsxGeneratorResult>;
|
|
35
44
|
private generateImports;
|
|
45
|
+
private generateInternalImports;
|
|
36
46
|
private generateCommonTypes;
|
|
37
47
|
private generateWidgetPropsContent;
|
|
38
48
|
private buildContainerMetadata;
|
|
@@ -5,6 +5,8 @@ const COLUMN_VIEW_WIDGET = "ColumnView";
|
|
|
5
5
|
const DROPDOWN_WIDGETS = new Set(["DropDown"]);
|
|
6
6
|
const GRID_WIDGETS = new Set(["Grid"]);
|
|
7
7
|
const NOTEBOOK_WIDGET = "Notebook";
|
|
8
|
+
const STACK_WIDGET = "Stack";
|
|
9
|
+
const POPOVER_MENU_WIDGET = "PopoverMenu";
|
|
8
10
|
const INTERNALLY_PROVIDED_PARAMS = {
|
|
9
11
|
ApplicationWindow: new Set(["application"]),
|
|
10
12
|
};
|
|
@@ -33,6 +35,8 @@ const isColumnViewWidget = (widgetName) => widgetName === COLUMN_VIEW_WIDGET;
|
|
|
33
35
|
const isDropDownWidget = (widgetName) => DROPDOWN_WIDGETS.has(widgetName);
|
|
34
36
|
const isGridWidget = (widgetName) => GRID_WIDGETS.has(widgetName);
|
|
35
37
|
const isNotebookWidget = (widgetName) => widgetName === NOTEBOOK_WIDGET;
|
|
38
|
+
const isStackWidget = (widgetName) => widgetName === STACK_WIDGET;
|
|
39
|
+
const isPopoverMenuWidget = (widgetName) => widgetName === POPOVER_MENU_WIDGET;
|
|
36
40
|
const sanitizeDoc = (doc) => {
|
|
37
41
|
let result = doc;
|
|
38
42
|
result = result.replace(/<picture>[\s\S]*?<\/picture>/gi, "");
|
|
@@ -98,7 +102,7 @@ export class JsxGenerator {
|
|
|
98
102
|
* Generates JSX type definitions for all widgets in a namespace.
|
|
99
103
|
* @param namespace - The parsed GIR namespace
|
|
100
104
|
* @param classMap - Map of class names to class definitions
|
|
101
|
-
* @returns Generated TypeScript code as
|
|
105
|
+
* @returns Generated TypeScript code as public jsx.ts and internal.ts files
|
|
102
106
|
*/
|
|
103
107
|
async generate(namespace, classMap) {
|
|
104
108
|
this.classMap = classMap;
|
|
@@ -111,18 +115,24 @@ export class JsxGenerator {
|
|
|
111
115
|
this.widgetPropertyNames = new Set(widgetClass?.properties.map((p) => toCamelCase(p.name)) ?? []);
|
|
112
116
|
this.widgetSignalNames = new Set(widgetClass?.signals.map((s) => toCamelCase(s.name)) ?? []);
|
|
113
117
|
const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(widgets, containerMetadata);
|
|
114
|
-
const
|
|
118
|
+
const jsxSections = [
|
|
115
119
|
this.generateImports(),
|
|
116
120
|
this.generateCommonTypes(widgetClass),
|
|
117
121
|
widgetPropsInterfaces,
|
|
118
|
-
this.generateConstructorArgsMetadata(widgets),
|
|
119
|
-
this.generatePropSettersMap(widgets),
|
|
120
|
-
this.generateSetterGetterMap(widgets),
|
|
121
122
|
this.generateExports(widgets, containerMetadata),
|
|
122
123
|
this.generateJsxNamespace(widgets, containerMetadata),
|
|
123
124
|
"export {};",
|
|
124
125
|
];
|
|
125
|
-
|
|
126
|
+
const internalSections = [
|
|
127
|
+
this.generateInternalImports(),
|
|
128
|
+
this.generateConstructorArgsMetadata(widgets),
|
|
129
|
+
this.generatePropSettersMap(widgets),
|
|
130
|
+
this.generateSetterGetterMap(widgets),
|
|
131
|
+
];
|
|
132
|
+
return {
|
|
133
|
+
jsx: await this.formatCode(jsxSections.join("\n")),
|
|
134
|
+
internal: await this.formatCode(internalSections.join("\n")),
|
|
135
|
+
};
|
|
126
136
|
}
|
|
127
137
|
generateImports() {
|
|
128
138
|
const externalImports = [...this.usedExternalNamespaces]
|
|
@@ -134,14 +144,17 @@ export class JsxGenerator {
|
|
|
134
144
|
`import type { ReactNode, Ref } from "react";`,
|
|
135
145
|
...externalImports,
|
|
136
146
|
`import type * as Gtk from "@gtkx/ffi/gtk";`,
|
|
137
|
-
`import type { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, NotebookPageProps, SlotProps } from "../types.js";`,
|
|
147
|
+
`import type { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps } from "../types.js";`,
|
|
138
148
|
"",
|
|
139
149
|
].join("\n");
|
|
140
150
|
}
|
|
151
|
+
generateInternalImports() {
|
|
152
|
+
return "/** Internal metadata for the reconciler. Not part of the public API. */\n";
|
|
153
|
+
}
|
|
141
154
|
generateCommonTypes(widgetClass) {
|
|
142
155
|
const widgetPropsContent = this.generateWidgetPropsContent(widgetClass);
|
|
143
156
|
return `
|
|
144
|
-
export { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, NotebookPageProps, SlotProps };
|
|
157
|
+
export { ColumnViewColumnProps, ColumnViewRootProps, GridChildProps, ListItemProps, ListViewRenderProps, MenuItemProps, MenuRootProps, MenuSectionProps, MenuSubmenuProps, NotebookPageProps, SlotProps, StackPageProps, StackRootProps };
|
|
145
158
|
|
|
146
159
|
${widgetPropsContent}
|
|
147
160
|
`;
|
|
@@ -572,11 +585,17 @@ ${widgetPropsContent}
|
|
|
572
585
|
isColumnViewWidget(widget.name) ||
|
|
573
586
|
isDropDownWidget(widget.name) ||
|
|
574
587
|
isGridWidget(widget.name) ||
|
|
575
|
-
isNotebookWidget(widget.name)
|
|
588
|
+
isNotebookWidget(widget.name) ||
|
|
589
|
+
isStackWidget(widget.name) ||
|
|
590
|
+
isPopoverMenuWidget(widget.name);
|
|
576
591
|
const docComment = widget.doc ? formatDoc(widget.doc).trimEnd() : "";
|
|
577
592
|
if (hasMeaningfulSlots) {
|
|
578
593
|
// For list widgets, generate wrapper components with proper generics
|
|
579
|
-
if (isListWidget(widget.name) ||
|
|
594
|
+
if (isListWidget(widget.name) ||
|
|
595
|
+
isColumnViewWidget(widget.name) ||
|
|
596
|
+
isDropDownWidget(widget.name) ||
|
|
597
|
+
isStackWidget(widget.name) ||
|
|
598
|
+
isPopoverMenuWidget(widget.name)) {
|
|
580
599
|
const wrapperComponents = this.generateGenericWrapperComponents(widget.name, metadata);
|
|
581
600
|
const exportMembers = this.getWrapperExportMembers(widget.name, metadata);
|
|
582
601
|
if (docComment) {
|
|
@@ -610,6 +629,38 @@ ${widgetPropsContent}
|
|
|
610
629
|
}
|
|
611
630
|
}
|
|
612
631
|
}
|
|
632
|
+
// Add ApplicationMenu and Menu namespace components
|
|
633
|
+
lines.push(`/**
|
|
634
|
+
* Sets the application-wide menu bar.
|
|
635
|
+
* The menu will appear in the window's title bar on supported platforms.
|
|
636
|
+
* Use Menu.Item, Menu.Section, and Menu.Submenu as children.
|
|
637
|
+
*/
|
|
638
|
+
export function ApplicationMenu(props: MenuRootProps): import("react").ReactElement {
|
|
639
|
+
\treturn createElement("ApplicationMenu", props);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function MenuItem(props: MenuItemProps): import("react").ReactElement {
|
|
643
|
+
\treturn createElement("Menu.Item", props);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function MenuSection(props: MenuSectionProps): import("react").ReactElement {
|
|
647
|
+
\treturn createElement("Menu.Section", props);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function MenuSubmenu(props: MenuSubmenuProps): import("react").ReactElement {
|
|
651
|
+
\treturn createElement("Menu.Submenu", props);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Declarative menu builder for use with PopoverMenu and ApplicationMenu.
|
|
656
|
+
* Use Menu.Item for action items, Menu.Section for groups, Menu.Submenu for nested menus.
|
|
657
|
+
*/
|
|
658
|
+
export const Menu = {
|
|
659
|
+
\tItem: MenuItem,
|
|
660
|
+
\tSection: MenuSection,
|
|
661
|
+
\tSubmenu: MenuSubmenu,
|
|
662
|
+
};
|
|
663
|
+
`);
|
|
613
664
|
return `${lines.join("\n")}\n`;
|
|
614
665
|
}
|
|
615
666
|
getWrapperExportMembers(widgetName, metadata) {
|
|
@@ -625,6 +676,14 @@ ${widgetPropsContent}
|
|
|
625
676
|
else if (isDropDownWidget(widgetName)) {
|
|
626
677
|
members.push(`Item: ${name}Item`);
|
|
627
678
|
}
|
|
679
|
+
else if (isStackWidget(widgetName)) {
|
|
680
|
+
members.push(`Page: ${name}Page`);
|
|
681
|
+
}
|
|
682
|
+
else if (isPopoverMenuWidget(widgetName)) {
|
|
683
|
+
members.push(`Item: ${name}Item`);
|
|
684
|
+
members.push(`Section: ${name}Section`);
|
|
685
|
+
members.push(`Submenu: ${name}Submenu`);
|
|
686
|
+
}
|
|
628
687
|
// Add named child slots
|
|
629
688
|
for (const slot of metadata.namedChildSlots) {
|
|
630
689
|
members.push(`${slot.slotName}: ${name}${slot.slotName}`);
|
|
@@ -635,66 +694,98 @@ ${widgetPropsContent}
|
|
|
635
694
|
const name = toPascalCase(widgetName);
|
|
636
695
|
const lines = [];
|
|
637
696
|
if (isListWidget(widgetName)) {
|
|
638
|
-
|
|
697
|
+
lines.push(`/**`);
|
|
698
|
+
lines.push(` * Props for the ${name}.Root component with type-safe item rendering.`);
|
|
699
|
+
lines.push(` * @typeParam T - The type of items in the list.`);
|
|
700
|
+
lines.push(` */`);
|
|
639
701
|
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "renderItem"> {`);
|
|
640
|
-
lines.push(`\t/** Render function for list items. Called with null during setup. */`);
|
|
702
|
+
lines.push(`\t/** Render function for list items. Called with null during setup (for loading state). */`);
|
|
641
703
|
lines.push(`\trenderItem: (item: T | null) => import("react").ReactElement;`);
|
|
642
704
|
lines.push(`}`);
|
|
643
705
|
lines.push(``);
|
|
644
|
-
// Root wrapper component
|
|
645
706
|
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
646
707
|
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
647
708
|
lines.push(`}`);
|
|
648
709
|
lines.push(``);
|
|
649
|
-
// Item wrapper component
|
|
650
710
|
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
651
711
|
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
652
712
|
lines.push(`}`);
|
|
653
713
|
}
|
|
654
714
|
else if (isColumnViewWidget(widgetName)) {
|
|
715
|
+
lines.push(`/**`);
|
|
716
|
+
lines.push(` * Props for the ${name}.Root component with type-safe item and column rendering.`);
|
|
717
|
+
lines.push(` * @typeParam T - The type of items in the column view.`);
|
|
718
|
+
lines.push(` * @typeParam C - The union type of column IDs for type-safe sorting.`);
|
|
719
|
+
lines.push(` */`);
|
|
655
720
|
lines.push(`interface ${name}RootPropsExtended<T = unknown, C extends string = string> extends ${name}Props, ColumnViewRootProps<T, C> {}`);
|
|
656
721
|
lines.push(``);
|
|
657
|
-
// Root wrapper (generic)
|
|
658
722
|
lines.push(`function ${name}Root<T = unknown, C extends string = string>(props: ${name}RootPropsExtended<T, C>): import("react").ReactElement {`);
|
|
659
723
|
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
660
724
|
lines.push(`}`);
|
|
661
725
|
lines.push(``);
|
|
662
|
-
|
|
726
|
+
lines.push(`/**`);
|
|
727
|
+
lines.push(` * Props for ${name}.Column with type-safe cell rendering.`);
|
|
728
|
+
lines.push(` * @typeParam T - The type of items passed to the renderCell function.`);
|
|
729
|
+
lines.push(` */`);
|
|
663
730
|
lines.push(`interface ${name}GenericColumnProps<T> extends Omit<ColumnViewColumnProps, "renderCell"> {`);
|
|
664
|
-
lines.push(`\t/** Render function for column cells. Called with null during setup. */`);
|
|
731
|
+
lines.push(`\t/** Render function for column cells. Called with null during setup (for loading state). */`);
|
|
665
732
|
lines.push(`\trenderCell: (item: T | null) => import("react").ReactElement;`);
|
|
666
733
|
lines.push(`}`);
|
|
667
734
|
lines.push(``);
|
|
668
|
-
// Column wrapper component
|
|
669
735
|
lines.push(`function ${name}Column<T>(props: ${name}GenericColumnProps<T>): import("react").ReactElement {`);
|
|
670
736
|
lines.push(`\treturn createElement("${name}.Column", props);`);
|
|
671
737
|
lines.push(`}`);
|
|
672
738
|
lines.push(``);
|
|
673
|
-
// Item wrapper component
|
|
674
739
|
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
675
740
|
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
676
741
|
lines.push(`}`);
|
|
677
742
|
}
|
|
678
743
|
else if (isDropDownWidget(widgetName)) {
|
|
679
|
-
|
|
744
|
+
lines.push(`/**`);
|
|
745
|
+
lines.push(` * Props for the ${name}.Root component with type-safe item handling.`);
|
|
746
|
+
lines.push(` * @typeParam T - The type of items in the dropdown.`);
|
|
747
|
+
lines.push(` */`);
|
|
680
748
|
lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "itemLabel" | "onSelectionChanged"> {`);
|
|
681
|
-
lines.push(`\t/** Function to convert item to display label */`);
|
|
749
|
+
lines.push(`\t/** Function to convert an item to its display label. */`);
|
|
682
750
|
lines.push(`\titemLabel?: (item: T) => string;`);
|
|
683
|
-
lines.push(`\t/** Called when
|
|
751
|
+
lines.push(`\t/** Called when the selected item changes. */`);
|
|
684
752
|
lines.push(`\tonSelectionChanged?: (item: T, index: number) => void;`);
|
|
685
753
|
lines.push(`}`);
|
|
686
754
|
lines.push(``);
|
|
687
|
-
// Root wrapper component
|
|
688
755
|
lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
|
|
689
756
|
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
690
757
|
lines.push(`}`);
|
|
691
758
|
lines.push(``);
|
|
692
|
-
// Item wrapper component
|
|
693
759
|
lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
|
|
694
760
|
lines.push(`\treturn createElement("${name}.Item", props);`);
|
|
695
761
|
lines.push(`}`);
|
|
696
762
|
}
|
|
697
|
-
|
|
763
|
+
else if (isStackWidget(widgetName)) {
|
|
764
|
+
lines.push(`function ${name}Root(props: StackRootProps & ${name}Props): import("react").ReactElement {`);
|
|
765
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
766
|
+
lines.push(`}`);
|
|
767
|
+
lines.push(``);
|
|
768
|
+
lines.push(`function ${name}Page(props: StackPageProps): import("react").ReactElement {`);
|
|
769
|
+
lines.push(`\treturn createElement("${name}.Page", props);`);
|
|
770
|
+
lines.push(`}`);
|
|
771
|
+
}
|
|
772
|
+
else if (isPopoverMenuWidget(widgetName)) {
|
|
773
|
+
lines.push(`function ${name}Root(props: MenuRootProps & ${name}Props): import("react").ReactElement {`);
|
|
774
|
+
lines.push(`\treturn createElement("${name}.Root", props);`);
|
|
775
|
+
lines.push(`}`);
|
|
776
|
+
lines.push(``);
|
|
777
|
+
lines.push(`function ${name}Item(props: MenuItemProps): import("react").ReactElement {`);
|
|
778
|
+
lines.push(`\treturn createElement("Menu.Item", props);`);
|
|
779
|
+
lines.push(`}`);
|
|
780
|
+
lines.push(``);
|
|
781
|
+
lines.push(`function ${name}Section(props: MenuSectionProps): import("react").ReactElement {`);
|
|
782
|
+
lines.push(`\treturn createElement("Menu.Section", props);`);
|
|
783
|
+
lines.push(`}`);
|
|
784
|
+
lines.push(``);
|
|
785
|
+
lines.push(`function ${name}Submenu(props: MenuSubmenuProps): import("react").ReactElement {`);
|
|
786
|
+
lines.push(`\treturn createElement("Menu.Submenu", props);`);
|
|
787
|
+
lines.push(`}`);
|
|
788
|
+
}
|
|
698
789
|
for (const slot of metadata.namedChildSlots) {
|
|
699
790
|
lines.push(``);
|
|
700
791
|
lines.push(`function ${name}${slot.slotName}(props: SlotProps): import("react").ReactElement {`);
|
|
@@ -719,7 +810,9 @@ ${widgetPropsContent}
|
|
|
719
810
|
isColumnViewWidget(widget.name) ||
|
|
720
811
|
isDropDownWidget(widget.name) ||
|
|
721
812
|
isGridWidget(widget.name) ||
|
|
722
|
-
isNotebookWidget(widget.name)
|
|
813
|
+
isNotebookWidget(widget.name) ||
|
|
814
|
+
isStackWidget(widget.name) ||
|
|
815
|
+
isPopoverMenuWidget(widget.name);
|
|
723
816
|
if (hasMeaningfulSlots) {
|
|
724
817
|
elements.push(`"${widgetName}.Root": ${propsName};`);
|
|
725
818
|
}
|
|
@@ -745,7 +838,16 @@ ${widgetPropsContent}
|
|
|
745
838
|
if (isNotebookWidget(widget.name)) {
|
|
746
839
|
elements.push(`"${widgetName}.Page": NotebookPageProps;`);
|
|
747
840
|
}
|
|
841
|
+
if (isStackWidget(widget.name)) {
|
|
842
|
+
elements.push(`"${widgetName}.Page": StackPageProps;`);
|
|
843
|
+
}
|
|
748
844
|
}
|
|
845
|
+
// Add shared Menu elements (used by PopoverMenu, PopoverMenuBar, and ApplicationMenu)
|
|
846
|
+
elements.push(`"Menu.Item": MenuItemProps;`);
|
|
847
|
+
elements.push(`"Menu.Section": MenuSectionProps;`);
|
|
848
|
+
elements.push(`"Menu.Submenu": MenuSubmenuProps;`);
|
|
849
|
+
// Add ApplicationMenu element
|
|
850
|
+
elements.push(`ApplicationMenu: MenuRootProps;`);
|
|
749
851
|
return `
|
|
750
852
|
declare global {
|
|
751
853
|
\tnamespace React {
|
|
@@ -18,6 +18,26 @@ export interface PageContainer {
|
|
|
18
18
|
removePage(child: Gtk.Widget): void;
|
|
19
19
|
updatePageLabel(child: Gtk.Widget, label: string): void;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Props for Stack pages.
|
|
23
|
+
*/
|
|
24
|
+
export interface StackPageProps {
|
|
25
|
+
name?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
iconName?: string;
|
|
28
|
+
needsAttention?: boolean;
|
|
29
|
+
visible?: boolean;
|
|
30
|
+
useUnderline?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Interface for Stack containers.
|
|
34
|
+
*/
|
|
35
|
+
export interface StackPageContainer {
|
|
36
|
+
addStackPage(child: Gtk.Widget, props: StackPageProps): void;
|
|
37
|
+
insertStackPageBefore(child: Gtk.Widget, props: StackPageProps, beforeChild: Gtk.Widget): void;
|
|
38
|
+
removeStackPage(child: Gtk.Widget): void;
|
|
39
|
+
updateStackPageProps(child: Gtk.Widget, props: StackPageProps): void;
|
|
40
|
+
}
|
|
21
41
|
/**
|
|
22
42
|
* Interface for grid-based containers.
|
|
23
43
|
*/
|
|
@@ -46,6 +66,7 @@ export interface ColumnContainer {
|
|
|
46
66
|
}
|
|
47
67
|
export declare const isChildContainer: (node: Node) => node is Node & ChildContainer;
|
|
48
68
|
export declare const isPageContainer: (node: Node) => node is Node & PageContainer;
|
|
69
|
+
export declare const isStackPageContainer: (node: Node) => node is Node & StackPageContainer;
|
|
49
70
|
export declare const isGridContainer: (node: Node) => node is Node & GridContainer;
|
|
50
71
|
export declare const isItemContainer: <T>(node: Node) => node is Node & ItemContainer<T>;
|
|
51
72
|
export declare const isColumnContainer: (node: Node) => node is Node & ColumnContainer;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const isChildContainer = (node) => "attachChild" in node && "detachChild" in node && "insertChildBefore" in node;
|
|
2
2
|
export const isPageContainer = (node) => "addPage" in node && "removePage" in node && "insertPageBefore" in node && "updatePageLabel" in node;
|
|
3
|
+
export const isStackPageContainer = (node) => "addStackPage" in node && "removeStackPage" in node && "updateStackPageProps" in node;
|
|
3
4
|
export const isGridContainer = (node) => "attachToGrid" in node && "removeFromGrid" in node;
|
|
4
5
|
export const isItemContainer = (node) => "addItem" in node && "insertItemBefore" in node && "removeItem" in node;
|
|
5
6
|
export const isColumnContainer = (node) => "addColumn" in node && "removeColumn" in node && "getSortFn" in node;
|
package/dist/factory.js
CHANGED
|
@@ -6,10 +6,12 @@ import { FlowBoxNode } from "./nodes/flow-box.js";
|
|
|
6
6
|
import { GridChildNode, GridNode } from "./nodes/grid.js";
|
|
7
7
|
import { ListItemNode, ListViewNode } from "./nodes/list.js";
|
|
8
8
|
import { ListBoxNode } from "./nodes/list-box.js";
|
|
9
|
+
import { ApplicationMenuNode, MenuItemNode, MenuSectionNode, MenuSubmenuNode, PopoverMenuBarNode, PopoverMenuRootNode, } from "./nodes/menu.js";
|
|
9
10
|
import { NotebookNode, NotebookPageNode } from "./nodes/notebook.js";
|
|
10
11
|
import { OverlayNode } from "./nodes/overlay.js";
|
|
11
12
|
import { RootNode } from "./nodes/root.js";
|
|
12
13
|
import { SlotNode } from "./nodes/slot.js";
|
|
14
|
+
import { StackNode, StackPageNode } from "./nodes/stack.js";
|
|
13
15
|
import { TextViewNode } from "./nodes/text-view.js";
|
|
14
16
|
import { WidgetNode } from "./nodes/widget.js";
|
|
15
17
|
import { WindowNode } from "./nodes/window.js";
|
|
@@ -23,11 +25,18 @@ const NODE_CLASSES = [
|
|
|
23
25
|
DropDownItemNode,
|
|
24
26
|
GridChildNode,
|
|
25
27
|
NotebookPageNode,
|
|
28
|
+
StackPageNode,
|
|
29
|
+
MenuItemNode,
|
|
30
|
+
MenuSectionNode,
|
|
31
|
+
MenuSubmenuNode,
|
|
26
32
|
SlotNode,
|
|
27
33
|
// Specialized widget nodes
|
|
28
34
|
WindowNode,
|
|
29
35
|
AboutDialogNode,
|
|
30
36
|
TextViewNode,
|
|
37
|
+
ApplicationMenuNode,
|
|
38
|
+
PopoverMenuRootNode,
|
|
39
|
+
PopoverMenuBarNode,
|
|
31
40
|
// Container nodes
|
|
32
41
|
ActionBarNode,
|
|
33
42
|
FlowBoxNode,
|
|
@@ -38,13 +47,16 @@ const NODE_CLASSES = [
|
|
|
38
47
|
ColumnViewNode,
|
|
39
48
|
ListViewNode,
|
|
40
49
|
NotebookNode,
|
|
50
|
+
StackNode,
|
|
41
51
|
// Catch-all (must be last)
|
|
42
52
|
WidgetNode,
|
|
43
53
|
];
|
|
44
54
|
export const createNode = (type, props, existingWidget) => {
|
|
45
55
|
for (const NodeClass of NODE_CLASSES) {
|
|
46
56
|
if (NodeClass.matches(type, existingWidget)) {
|
|
47
|
-
|
|
57
|
+
const node = new NodeClass(type, existingWidget);
|
|
58
|
+
node.initialize(props);
|
|
59
|
+
return node;
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
throw new Error(`No matching node class for type: ${type}`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Internal metadata for the reconciler. Not part of the public API. */
|
|
2
|
+
export declare const CONSTRUCTOR_PARAMS: Record<string, {
|
|
3
|
+
name: string;
|
|
4
|
+
hasDefault: boolean;
|
|
5
|
+
}[]>;
|
|
6
|
+
export declare const PROP_SETTERS: Record<string, Record<string, string>>;
|
|
7
|
+
export declare const SETTER_GETTERS: Record<string, Record<string, string>>;
|