@gtkx/react 0.1.49 → 0.1.50

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
@@ -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.setup()` - Create reusable instance
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
- ### List Example
183
+ ### Todo App
190
184
 
191
- Comprehensive showcase of ListView, GridView, and ColumnView with sorting:
185
+ A todo app demonstrating `@gtkx/testing` with realistic component tests:
192
186
 
193
187
  ```bash
194
- turbo start --filter=list-example
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 a string
41
+ * @returns Generated TypeScript code as public jsx.ts and internal.ts files
33
42
  */
34
- generate(namespace: GirNamespace, classMap: Map<string, GirClass>): Promise<string>;
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;
@@ -98,7 +98,7 @@ export class JsxGenerator {
98
98
  * Generates JSX type definitions for all widgets in a namespace.
99
99
  * @param namespace - The parsed GIR namespace
100
100
  * @param classMap - Map of class names to class definitions
101
- * @returns Generated TypeScript code as a string
101
+ * @returns Generated TypeScript code as public jsx.ts and internal.ts files
102
102
  */
103
103
  async generate(namespace, classMap) {
104
104
  this.classMap = classMap;
@@ -111,18 +111,24 @@ export class JsxGenerator {
111
111
  this.widgetPropertyNames = new Set(widgetClass?.properties.map((p) => toCamelCase(p.name)) ?? []);
112
112
  this.widgetSignalNames = new Set(widgetClass?.signals.map((s) => toCamelCase(s.name)) ?? []);
113
113
  const widgetPropsInterfaces = this.generateWidgetPropsInterfaces(widgets, containerMetadata);
114
- const sections = [
114
+ const jsxSections = [
115
115
  this.generateImports(),
116
116
  this.generateCommonTypes(widgetClass),
117
117
  widgetPropsInterfaces,
118
- this.generateConstructorArgsMetadata(widgets),
119
- this.generatePropSettersMap(widgets),
120
- this.generateSetterGetterMap(widgets),
121
118
  this.generateExports(widgets, containerMetadata),
122
119
  this.generateJsxNamespace(widgets, containerMetadata),
123
120
  "export {};",
124
121
  ];
125
- return this.formatCode(sections.join("\n"));
122
+ const internalSections = [
123
+ this.generateInternalImports(),
124
+ this.generateConstructorArgsMetadata(widgets),
125
+ this.generatePropSettersMap(widgets),
126
+ this.generateSetterGetterMap(widgets),
127
+ ];
128
+ return {
129
+ jsx: await this.formatCode(jsxSections.join("\n")),
130
+ internal: await this.formatCode(internalSections.join("\n")),
131
+ };
126
132
  }
127
133
  generateImports() {
128
134
  const externalImports = [...this.usedExternalNamespaces]
@@ -138,6 +144,9 @@ export class JsxGenerator {
138
144
  "",
139
145
  ].join("\n");
140
146
  }
147
+ generateInternalImports() {
148
+ return "/** Internal metadata for the reconciler. Not part of the public API. */\n";
149
+ }
141
150
  generateCommonTypes(widgetClass) {
142
151
  const widgetPropsContent = this.generateWidgetPropsContent(widgetClass);
143
152
  return `
@@ -635,66 +644,72 @@ ${widgetPropsContent}
635
644
  const name = toPascalCase(widgetName);
636
645
  const lines = [];
637
646
  if (isListWidget(widgetName)) {
638
- // Props type for the generic Root component
647
+ lines.push(`/**`);
648
+ lines.push(` * Props for the ${name}.Root component with type-safe item rendering.`);
649
+ lines.push(` * @typeParam T - The type of items in the list.`);
650
+ lines.push(` */`);
639
651
  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. */`);
652
+ lines.push(`\t/** Render function for list items. Called with null during setup (for loading state). */`);
641
653
  lines.push(`\trenderItem: (item: T | null) => import("react").ReactElement;`);
642
654
  lines.push(`}`);
643
655
  lines.push(``);
644
- // Root wrapper component
645
656
  lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
646
657
  lines.push(`\treturn createElement("${name}.Root", props);`);
647
658
  lines.push(`}`);
648
659
  lines.push(``);
649
- // Item wrapper component
650
660
  lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
651
661
  lines.push(`\treturn createElement("${name}.Item", props);`);
652
662
  lines.push(`}`);
653
663
  }
654
664
  else if (isColumnViewWidget(widgetName)) {
665
+ lines.push(`/**`);
666
+ lines.push(` * Props for the ${name}.Root component with type-safe item and column rendering.`);
667
+ lines.push(` * @typeParam T - The type of items in the column view.`);
668
+ lines.push(` * @typeParam C - The union type of column IDs for type-safe sorting.`);
669
+ lines.push(` */`);
655
670
  lines.push(`interface ${name}RootPropsExtended<T = unknown, C extends string = string> extends ${name}Props, ColumnViewRootProps<T, C> {}`);
656
671
  lines.push(``);
657
- // Root wrapper (generic)
658
672
  lines.push(`function ${name}Root<T = unknown, C extends string = string>(props: ${name}RootPropsExtended<T, C>): import("react").ReactElement {`);
659
673
  lines.push(`\treturn createElement("${name}.Root", props);`);
660
674
  lines.push(`}`);
661
675
  lines.push(``);
662
- // Column props type - use GenericColumnProps to avoid conflict with imported ColumnViewColumnProps
676
+ lines.push(`/**`);
677
+ lines.push(` * Props for ${name}.Column with type-safe cell rendering.`);
678
+ lines.push(` * @typeParam T - The type of items passed to the renderCell function.`);
679
+ lines.push(` */`);
663
680
  lines.push(`interface ${name}GenericColumnProps<T> extends Omit<ColumnViewColumnProps, "renderCell"> {`);
664
- lines.push(`\t/** Render function for column cells. Called with null during setup. */`);
681
+ lines.push(`\t/** Render function for column cells. Called with null during setup (for loading state). */`);
665
682
  lines.push(`\trenderCell: (item: T | null) => import("react").ReactElement;`);
666
683
  lines.push(`}`);
667
684
  lines.push(``);
668
- // Column wrapper component
669
685
  lines.push(`function ${name}Column<T>(props: ${name}GenericColumnProps<T>): import("react").ReactElement {`);
670
686
  lines.push(`\treturn createElement("${name}.Column", props);`);
671
687
  lines.push(`}`);
672
688
  lines.push(``);
673
- // Item wrapper component
674
689
  lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
675
690
  lines.push(`\treturn createElement("${name}.Item", props);`);
676
691
  lines.push(`}`);
677
692
  }
678
693
  else if (isDropDownWidget(widgetName)) {
679
- // Props type for the generic Root component
694
+ lines.push(`/**`);
695
+ lines.push(` * Props for the ${name}.Root component with type-safe item handling.`);
696
+ lines.push(` * @typeParam T - The type of items in the dropdown.`);
697
+ lines.push(` */`);
680
698
  lines.push(`interface ${name}RootProps<T> extends Omit<${name}Props, "itemLabel" | "onSelectionChanged"> {`);
681
- lines.push(`\t/** Function to convert item to display label */`);
699
+ lines.push(`\t/** Function to convert an item to its display label. */`);
682
700
  lines.push(`\titemLabel?: (item: T) => string;`);
683
- lines.push(`\t/** Called when selection changes */`);
701
+ lines.push(`\t/** Called when the selected item changes. */`);
684
702
  lines.push(`\tonSelectionChanged?: (item: T, index: number) => void;`);
685
703
  lines.push(`}`);
686
704
  lines.push(``);
687
- // Root wrapper component
688
705
  lines.push(`function ${name}Root<T>(props: ${name}RootProps<T>): import("react").ReactElement {`);
689
706
  lines.push(`\treturn createElement("${name}.Root", props);`);
690
707
  lines.push(`}`);
691
708
  lines.push(``);
692
- // Item wrapper component
693
709
  lines.push(`function ${name}Item<T>(props: ListItemProps<T>): import("react").ReactElement {`);
694
710
  lines.push(`\treturn createElement("${name}.Item", props);`);
695
711
  lines.push(`}`);
696
712
  }
697
- // Generate slot wrapper components
698
713
  for (const slot of metadata.namedChildSlots) {
699
714
  lines.push(``);
700
715
  lines.push(`function ${name}${slot.slotName}(props: SlotProps): import("react").ReactElement {`);
@@ -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>>;