@gtkx/react 0.6.1 → 0.8.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.
Files changed (84) hide show
  1. package/dist/batch.d.ts +4 -1
  2. package/dist/batch.js +19 -10
  3. package/dist/codegen/jsx-generator.d.ts +4 -4
  4. package/dist/codegen/jsx-generator.js +24 -27
  5. package/dist/container-interfaces.d.ts +19 -6
  6. package/dist/container-interfaces.js +26 -6
  7. package/dist/errors.d.ts +8 -0
  8. package/dist/errors.js +38 -0
  9. package/dist/factory.js +9 -3
  10. package/dist/generated/jsx.d.ts +38 -26
  11. package/dist/generated/jsx.js +12 -2
  12. package/dist/index.js +3 -1
  13. package/dist/node.d.ts +5 -0
  14. package/dist/node.js +62 -6
  15. package/dist/nodes/action-bar.d.ts +2 -6
  16. package/dist/nodes/action-bar.js +3 -12
  17. package/dist/nodes/column-view.d.ts +19 -44
  18. package/dist/nodes/column-view.js +70 -243
  19. package/dist/nodes/combo-row.d.ts +5 -0
  20. package/dist/nodes/combo-row.js +6 -0
  21. package/dist/nodes/drop-down.d.ts +9 -0
  22. package/dist/nodes/drop-down.js +12 -0
  23. package/dist/nodes/flow-box.d.ts +4 -3
  24. package/dist/nodes/flow-box.js +26 -10
  25. package/dist/nodes/grid.d.ts +15 -15
  26. package/dist/nodes/grid.js +21 -64
  27. package/dist/nodes/header-bar.d.ts +34 -11
  28. package/dist/nodes/header-bar.js +55 -24
  29. package/dist/nodes/indexed-child-container.d.ts +16 -0
  30. package/dist/nodes/indexed-child-container.js +22 -0
  31. package/dist/nodes/list-box.d.ts +4 -3
  32. package/dist/nodes/list-box.js +33 -6
  33. package/dist/nodes/list-item-factory.d.ts +19 -0
  34. package/dist/nodes/list-item-factory.js +58 -0
  35. package/dist/nodes/list-view.d.ts +24 -0
  36. package/dist/nodes/list-view.js +46 -0
  37. package/dist/nodes/menu.d.ts +25 -19
  38. package/dist/nodes/menu.js +30 -59
  39. package/dist/nodes/notebook.d.ts +13 -14
  40. package/dist/nodes/notebook.js +18 -56
  41. package/dist/nodes/paged-stack.d.ts +39 -0
  42. package/dist/nodes/paged-stack.js +54 -0
  43. package/dist/nodes/selectable-list.d.ts +41 -0
  44. package/dist/nodes/selectable-list.js +228 -0
  45. package/dist/nodes/stack-page-props.d.ts +11 -0
  46. package/dist/nodes/stack-page-props.js +23 -0
  47. package/dist/nodes/stack.d.ts +14 -28
  48. package/dist/nodes/stack.js +30 -142
  49. package/dist/nodes/string-list-container.d.ts +41 -0
  50. package/dist/nodes/string-list-container.js +90 -0
  51. package/dist/nodes/string-list-item.d.ts +15 -0
  52. package/dist/nodes/string-list-item.js +48 -0
  53. package/dist/nodes/string-list-store.d.ts +13 -0
  54. package/dist/nodes/string-list-store.js +44 -0
  55. package/dist/nodes/text-view.d.ts +1 -1
  56. package/dist/nodes/text-view.js +1 -5
  57. package/dist/nodes/toggle-button.d.ts +1 -1
  58. package/dist/nodes/toggle-button.js +1 -3
  59. package/dist/nodes/view-stack.d.ts +9 -0
  60. package/dist/nodes/view-stack.js +28 -0
  61. package/dist/nodes/virtual-item.d.ts +20 -0
  62. package/dist/nodes/virtual-item.js +57 -0
  63. package/dist/nodes/virtual-slot.d.ts +25 -0
  64. package/dist/nodes/virtual-slot.js +71 -0
  65. package/dist/nodes/widget.d.ts +0 -3
  66. package/dist/nodes/widget.js +0 -28
  67. package/dist/nodes/window.d.ts +1 -1
  68. package/dist/nodes/window.js +9 -15
  69. package/dist/predicates.d.ts +8 -0
  70. package/dist/predicates.js +8 -0
  71. package/dist/props.d.ts +7 -5
  72. package/dist/props.js +11 -9
  73. package/dist/reconciler/host-config.d.ts +19 -0
  74. package/dist/reconciler/host-config.js +89 -0
  75. package/dist/reconciler.d.ts +2 -26
  76. package/dist/reconciler.js +15 -106
  77. package/dist/render.d.ts +3 -4
  78. package/dist/render.js +6 -4
  79. package/dist/types.d.ts +22 -19
  80. package/package.json +6 -5
  81. package/dist/nodes/dropdown.d.ts +0 -39
  82. package/dist/nodes/dropdown.js +0 -103
  83. package/dist/nodes/list.d.ts +0 -43
  84. package/dist/nodes/list.js +0 -153
@@ -0,0 +1,57 @@
1
+ import { isItemContainer } from "../container-interfaces.js";
2
+ import { Node } from "../node.js";
3
+ const hasGetId = (node) => {
4
+ return typeof node.getId === "function";
5
+ };
6
+ /**
7
+ * Base class for virtual item nodes used in list-based containers.
8
+ * Virtual nodes don't create GTK widgets directly but represent items
9
+ * in list models (ListView, GridView, ColumnView).
10
+ */
11
+ export class VirtualItemNode extends Node {
12
+ static consumedPropNames = ["id", "item"];
13
+ isVirtual() {
14
+ return true;
15
+ }
16
+ id = "";
17
+ item;
18
+ initialize(props) {
19
+ this.id = props.id;
20
+ this.item = props.item;
21
+ super.initialize(props);
22
+ }
23
+ getId() {
24
+ return this.id;
25
+ }
26
+ getItem() {
27
+ return this.item;
28
+ }
29
+ attachToParent(parent) {
30
+ if (isItemContainer(parent)) {
31
+ parent.addItem(this.id, this.item);
32
+ }
33
+ }
34
+ attachToParentBefore(parent, before) {
35
+ if (isItemContainer(parent) && hasGetId(before)) {
36
+ parent.insertItemBefore(this.id, this.item, before.getId());
37
+ }
38
+ else {
39
+ this.attachToParent(parent);
40
+ }
41
+ }
42
+ detachFromParent(parent) {
43
+ if (isItemContainer(parent)) {
44
+ parent.removeItem(this.id);
45
+ }
46
+ }
47
+ updateProps(oldProps, newProps) {
48
+ const newId = newProps.id;
49
+ const newItem = newProps.item;
50
+ if ((oldProps.id !== newId || oldProps.item !== newItem) && this.parent && isItemContainer(this.parent)) {
51
+ this.parent.updateItem(newId, newItem);
52
+ this.id = newId;
53
+ this.item = newItem;
54
+ }
55
+ super.updateProps(oldProps, newProps);
56
+ }
57
+ }
@@ -0,0 +1,25 @@
1
+ import type * as Gtk from "@gtkx/ffi/gtk";
2
+ import type { Props } from "../factory.js";
3
+ import { Node } from "../node.js";
4
+ export declare abstract class VirtualSlotNode<TContainer, TProps> extends Node<never> {
5
+ protected isVirtual(): boolean;
6
+ protected childWidget: Gtk.Widget | null;
7
+ protected parentContainer: (Node & TContainer) | null;
8
+ private _slotProps;
9
+ protected get slotProps(): TProps;
10
+ protected set slotProps(value: TProps);
11
+ protected abstract isValidContainer(parent: Node): parent is Node & TContainer;
12
+ protected abstract addToContainer(container: TContainer, child: Gtk.Widget, props: TProps): void;
13
+ protected abstract insertBeforeInContainer(container: TContainer, child: Gtk.Widget, props: TProps, before: Gtk.Widget): void;
14
+ protected abstract removeFromContainer(container: TContainer, child: Gtk.Widget): void;
15
+ protected abstract updateInContainer(container: TContainer, child: Gtk.Widget, props: TProps): void;
16
+ protected abstract extractSlotProps(props: Props): TProps;
17
+ initialize(props: Props): void;
18
+ getChildWidget(): Gtk.Widget | null;
19
+ appendChild(child: Node): void;
20
+ attachToParent(parent: Node): void;
21
+ attachToParentBefore(parent: Node, before: Node): void;
22
+ protected getBeforeWidget(before: Node): Gtk.Widget | null;
23
+ detachFromParent(parent: Node): void;
24
+ protected updateSlotPropsIfChanged(oldProps: Props, newProps: Props, propKeys: string[]): boolean;
25
+ }
@@ -0,0 +1,71 @@
1
+ import { Node } from "../node.js";
2
+ export class VirtualSlotNode extends Node {
3
+ isVirtual() {
4
+ return true;
5
+ }
6
+ childWidget = null;
7
+ parentContainer = null;
8
+ _slotProps;
9
+ get slotProps() {
10
+ if (this._slotProps === undefined) {
11
+ throw new Error("slotProps accessed before initialization");
12
+ }
13
+ return this._slotProps;
14
+ }
15
+ set slotProps(value) {
16
+ this._slotProps = value;
17
+ }
18
+ initialize(props) {
19
+ this.slotProps = this.extractSlotProps(props);
20
+ super.initialize(props);
21
+ }
22
+ getChildWidget() {
23
+ return this.childWidget;
24
+ }
25
+ appendChild(child) {
26
+ const childWidget = child.getWidget();
27
+ if (childWidget) {
28
+ this.childWidget = childWidget;
29
+ }
30
+ }
31
+ attachToParent(parent) {
32
+ if (this.isValidContainer(parent) && this.childWidget) {
33
+ this.parentContainer = parent;
34
+ this.addToContainer(parent, this.childWidget, this.slotProps);
35
+ }
36
+ }
37
+ attachToParentBefore(parent, before) {
38
+ if (this.isValidContainer(parent) && this.childWidget) {
39
+ this.parentContainer = parent;
40
+ const beforeWidget = this.getBeforeWidget(before);
41
+ if (beforeWidget) {
42
+ this.insertBeforeInContainer(parent, this.childWidget, this.slotProps, beforeWidget);
43
+ }
44
+ else {
45
+ this.addToContainer(parent, this.childWidget, this.slotProps);
46
+ }
47
+ }
48
+ }
49
+ getBeforeWidget(before) {
50
+ if (before instanceof VirtualSlotNode) {
51
+ return before.getChildWidget();
52
+ }
53
+ return before.getWidget() ?? null;
54
+ }
55
+ detachFromParent(parent) {
56
+ if (this.isValidContainer(parent) && this.childWidget) {
57
+ this.removeFromContainer(parent, this.childWidget);
58
+ this.parentContainer = null;
59
+ }
60
+ }
61
+ updateSlotPropsIfChanged(oldProps, newProps, propKeys) {
62
+ const changed = propKeys.some((key) => oldProps[key] !== newProps[key]);
63
+ if (changed) {
64
+ this.slotProps = this.extractSlotProps(newProps);
65
+ if (this.parentContainer && this.childWidget) {
66
+ this.updateInContainer(this.parentContainer, this.childWidget, this.slotProps);
67
+ }
68
+ }
69
+ return changed;
70
+ }
71
+ }
@@ -7,7 +7,4 @@ import { Node } from "../node.js";
7
7
  */
8
8
  export declare class WidgetNode extends Node<Gtk.Widget> {
9
9
  static matches(_type: string): boolean;
10
- attachToParent(parent: Node): void;
11
- attachToParentBefore(parent: Node, before: Node): void;
12
- detachFromParent(parent: Node): void;
13
10
  }
@@ -1,4 +1,3 @@
1
- import { isChildContainer } from "../container-interfaces.js";
2
1
  import { Node } from "../node.js";
3
2
  /**
4
3
  * Catch-all node for standard GTK widgets that don't need special handling.
@@ -9,31 +8,4 @@ export class WidgetNode extends Node {
9
8
  static matches(_type) {
10
9
  return true;
11
10
  }
12
- attachToParent(parent) {
13
- if (isChildContainer(parent)) {
14
- parent.attachChild(this.widget);
15
- return;
16
- }
17
- super.attachToParent(parent);
18
- }
19
- attachToParentBefore(parent, before) {
20
- if (isChildContainer(parent)) {
21
- const beforeWidget = before.getWidget();
22
- if (beforeWidget) {
23
- parent.insertChildBefore(this.widget, beforeWidget);
24
- }
25
- else {
26
- parent.attachChild(this.widget);
27
- }
28
- return;
29
- }
30
- super.attachToParentBefore(parent, before);
31
- }
32
- detachFromParent(parent) {
33
- if (isChildContainer(parent)) {
34
- parent.detachChild(this.widget);
35
- return;
36
- }
37
- super.detachFromParent(parent);
38
- }
39
11
  }
@@ -2,10 +2,10 @@ import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { Props } from "../factory.js";
3
3
  import { Node } from "../node.js";
4
4
  export declare class WindowNode extends Node<Gtk.Window> {
5
+ static consumedPropNames: string[];
5
6
  static matches(type: string): boolean;
6
7
  protected createWidget(type: string, _props: Props): Gtk.Window;
7
8
  detachFromParent(_parent: Node): void;
8
9
  mount(): void;
9
- protected consumedProps(): Set<string>;
10
10
  updateProps(oldProps: Props, newProps: Props): void;
11
11
  }
@@ -1,22 +1,22 @@
1
1
  import { getCurrentApp } from "@gtkx/ffi";
2
2
  import * as Adw from "@gtkx/ffi/adw";
3
3
  import * as Gtk from "@gtkx/ffi/gtk";
4
- import { Node } from "../node.js";
5
- import { getNumberProp } from "../props.js";
4
+ import { Node, normalizeWidgetType } from "../node.js";
6
5
  const WINDOW_TYPES = new Set(["Window", "ApplicationWindow", "AdwWindow", "AdwApplicationWindow"]);
7
6
  export class WindowNode extends Node {
7
+ static consumedPropNames = ["defaultWidth", "defaultHeight"];
8
8
  static matches(type) {
9
- return WINDOW_TYPES.has(type.split(".")[0] || type);
9
+ return WINDOW_TYPES.has(normalizeWidgetType(type));
10
10
  }
11
11
  createWidget(type, _props) {
12
- const normalizedType = type.split(".")[0] || type;
13
- if (normalizedType === "ApplicationWindow") {
12
+ const widgetType = normalizeWidgetType(type);
13
+ if (widgetType === "ApplicationWindow") {
14
14
  return new Gtk.ApplicationWindow(getCurrentApp());
15
15
  }
16
- if (normalizedType === "AdwApplicationWindow") {
16
+ if (widgetType === "AdwApplicationWindow") {
17
17
  return new Adw.ApplicationWindow(getCurrentApp());
18
18
  }
19
- if (normalizedType === "AdwWindow") {
19
+ if (widgetType === "AdwWindow") {
20
20
  return new Adw.Window();
21
21
  }
22
22
  return new Gtk.Window();
@@ -27,18 +27,12 @@ export class WindowNode extends Node {
27
27
  mount() {
28
28
  this.widget.present();
29
29
  }
30
- consumedProps() {
31
- const consumed = super.consumedProps();
32
- consumed.add("defaultWidth");
33
- consumed.add("defaultHeight");
34
- return consumed;
35
- }
36
30
  updateProps(oldProps, newProps) {
37
31
  const widthChanged = oldProps.defaultWidth !== newProps.defaultWidth;
38
32
  const heightChanged = oldProps.defaultHeight !== newProps.defaultHeight;
39
33
  if (widthChanged || heightChanged) {
40
- const width = getNumberProp(newProps, "defaultWidth", -1);
41
- const height = getNumberProp(newProps, "defaultHeight", -1);
34
+ const width = newProps.defaultWidth ?? -1;
35
+ const height = newProps.defaultHeight ?? -1;
42
36
  this.widget.setDefaultSize(width, height);
43
37
  }
44
38
  super.updateProps(oldProps, newProps);
@@ -27,4 +27,12 @@ export declare const isSingleChild: (widget: Gtk.Widget) => widget is SingleChil
27
27
  * Type guard that checks if a GTK widget supports removing children via a remove method.
28
28
  */
29
29
  export declare const isRemovable: (widget: Gtk.Widget) => widget is Removable;
30
+ /**
31
+ * Type guard that checks if a GTK widget is a FlowBoxChild.
32
+ */
33
+ export declare const isFlowBoxChild: (widget: Gtk.Widget) => widget is Gtk.FlowBoxChild;
34
+ /**
35
+ * Type guard that checks if a GTK widget is a ListBoxRow.
36
+ */
37
+ export declare const isListBoxRow: (widget: Gtk.Widget) => widget is Gtk.ListBoxRow;
30
38
  export {};
@@ -14,3 +14,11 @@ export const isSingleChild = (widget) => "setChild" in widget && typeof widget.s
14
14
  * Type guard that checks if a GTK widget supports removing children via a remove method.
15
15
  */
16
16
  export const isRemovable = (widget) => "remove" in widget && typeof widget.remove === "function";
17
+ /**
18
+ * Type guard that checks if a GTK widget is a FlowBoxChild.
19
+ */
20
+ export const isFlowBoxChild = (widget) => "getIndex" in widget && "getChild" in widget && typeof widget.getIndex === "function";
21
+ /**
22
+ * Type guard that checks if a GTK widget is a ListBoxRow.
23
+ */
24
+ export const isListBoxRow = (widget) => "getIndex" in widget && "isSelected" in widget && typeof widget.getIndex === "function";
package/dist/props.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { Props } from "./factory.js";
2
- /** Extracts a number property from props with a default value. */
3
- export declare const getNumberProp: (props: Props, key: string, defaultValue: number) => number;
4
- /** Extracts a string property from props with a default value. */
5
- export declare const getStringProp: (props: Props, key: string, defaultValue: string) => string;
1
+ type CallbackAction = "connect" | "disconnect" | "none";
2
+ type CallbackChange<T> = {
3
+ callback: T | undefined;
4
+ action: CallbackAction;
5
+ };
6
+ export declare const getCallbackChange: <T>(oldCallback: T | undefined, newCallback: T | undefined) => CallbackChange<T>;
7
+ export {};
package/dist/props.js CHANGED
@@ -1,10 +1,12 @@
1
- /** Extracts a number property from props with a default value. */
2
- export const getNumberProp = (props, key, defaultValue) => {
3
- const value = props[key];
4
- return typeof value === "number" ? value : defaultValue;
5
- };
6
- /** Extracts a string property from props with a default value. */
7
- export const getStringProp = (props, key, defaultValue) => {
8
- const value = props[key];
9
- return typeof value === "string" ? value : defaultValue;
1
+ export const getCallbackChange = (oldCallback, newCallback) => {
2
+ if (oldCallback === newCallback) {
3
+ return { callback: newCallback, action: "none" };
4
+ }
5
+ if (oldCallback && !newCallback) {
6
+ return { callback: undefined, action: "disconnect" };
7
+ }
8
+ if (!oldCallback && newCallback) {
9
+ return { callback: newCallback, action: "connect" };
10
+ }
11
+ return { callback: newCallback, action: "none" };
10
12
  };
@@ -0,0 +1,19 @@
1
+ import type * as Gtk from "@gtkx/ffi/gtk";
2
+ import type ReactReconciler from "react-reconciler";
3
+ import { type Props, type ROOT_NODE_CONTAINER } from "../factory.js";
4
+ import type { Node } from "../node.js";
5
+ type Container = Gtk.Widget | typeof ROOT_NODE_CONTAINER;
6
+ type TextInstance = Node;
7
+ type SuspenseInstance = never;
8
+ type HydratableInstance = never;
9
+ type PublicInstance = Gtk.Widget;
10
+ type HostContext = Record<string, never>;
11
+ type ChildSet = never;
12
+ type TimeoutHandle = number;
13
+ type NoTimeout = -1;
14
+ type TransitionStatus = number;
15
+ type FormInstance = never;
16
+ type HostConfig = ReactReconciler.HostConfig<string, Props, Container, Node, TextInstance, SuspenseInstance, HydratableInstance, FormInstance, PublicInstance, HostContext, ChildSet, TimeoutHandle, NoTimeout, TransitionStatus>;
17
+ export type ReconcilerInstance = ReactReconciler.Reconciler<Container, Node, TextInstance, SuspenseInstance, FormInstance, PublicInstance>;
18
+ export declare function createHostConfig(createNodeFromContainer: (container: Container) => Node): HostConfig;
19
+ export {};
@@ -0,0 +1,89 @@
1
+ import { beginBatch, endBatch } from "@gtkx/ffi";
2
+ import React from "react";
3
+ import { beginCommit, endCommit } from "../batch.js";
4
+ import { createNode } from "../factory.js";
5
+ export function createHostConfig(createNodeFromContainer) {
6
+ return {
7
+ supportsMutation: true,
8
+ supportsPersistence: false,
9
+ supportsHydration: false,
10
+ isPrimaryRenderer: true,
11
+ noTimeout: -1,
12
+ getRootHostContext: () => ({}),
13
+ getChildHostContext: (parentHostContext) => parentHostContext,
14
+ shouldSetTextContent: () => false,
15
+ createInstance: (type, props) => createNode(type, props),
16
+ createTextInstance: (text) => createNode("Label", { label: text }),
17
+ appendInitialChild: (parent, child) => parent.appendChild(child),
18
+ finalizeInitialChildren: () => true,
19
+ commitUpdate: (instance, _type, oldProps, newProps) => {
20
+ instance.updateProps(oldProps, newProps);
21
+ },
22
+ commitMount: (instance) => {
23
+ instance.mount();
24
+ },
25
+ appendChild: (parent, child) => parent.appendChild(child),
26
+ removeChild: (parent, child) => parent.removeChild(child),
27
+ insertBefore: (parent, child, beforeChild) => parent.insertBefore(child, beforeChild),
28
+ removeChildFromContainer: (container, child) => {
29
+ const parent = createNodeFromContainer(container);
30
+ parent.removeChild(child);
31
+ },
32
+ appendChildToContainer: (container, child) => {
33
+ const parent = createNodeFromContainer(container);
34
+ parent.appendChild(child);
35
+ },
36
+ insertInContainerBefore: (container, child, beforeChild) => {
37
+ const parent = createNodeFromContainer(container);
38
+ parent.insertBefore(child, beforeChild);
39
+ },
40
+ prepareForCommit: () => {
41
+ beginBatch();
42
+ beginCommit();
43
+ return null;
44
+ },
45
+ resetAfterCommit: () => {
46
+ endCommit();
47
+ endBatch();
48
+ },
49
+ commitTextUpdate: (textInstance, oldText, newText) => {
50
+ textInstance.updateProps({ label: oldText }, { label: newText });
51
+ },
52
+ clearContainer: () => { },
53
+ preparePortalMount: () => { },
54
+ scheduleTimeout: (fn, delay) => {
55
+ const timeoutId = setTimeout(fn, delay ?? 0);
56
+ return typeof timeoutId === "number" ? timeoutId : Number(timeoutId);
57
+ },
58
+ cancelTimeout: (id) => {
59
+ clearTimeout(id);
60
+ },
61
+ getPublicInstance: (instance) => instance.getWidget(),
62
+ getCurrentUpdatePriority: () => 2,
63
+ setCurrentUpdatePriority: () => { },
64
+ resolveUpdatePriority: () => 2,
65
+ NotPendingTransition: null,
66
+ HostTransitionContext: createReconcilerContext(0),
67
+ getInstanceFromNode: () => null,
68
+ beforeActiveInstanceBlur: () => { },
69
+ afterActiveInstanceBlur: () => { },
70
+ prepareScopeUpdate: () => { },
71
+ getInstanceFromScope: () => null,
72
+ detachDeletedInstance: () => { },
73
+ resetFormInstance: () => { },
74
+ requestPostPaintCallback: () => { },
75
+ shouldAttemptEagerTransition: () => false,
76
+ trackSchedulerEvent: () => { },
77
+ resolveEventType: () => null,
78
+ resolveEventTimeStamp: () => Date.now(),
79
+ maySuspendCommit: () => false,
80
+ preloadInstance: () => false,
81
+ startSuspendingCommit: () => { },
82
+ suspendInstance: () => { },
83
+ waitForCommitToBeReady: () => null,
84
+ };
85
+ }
86
+ function createReconcilerContext(value) {
87
+ const context = React.createContext(value);
88
+ return context;
89
+ }
@@ -1,33 +1,9 @@
1
- import type * as Gtk from "@gtkx/ffi/gtk";
2
- import ReactReconciler from "react-reconciler";
3
- import { type ROOT_NODE_CONTAINER } from "./factory.js";
4
- import type { Node } from "./node.js";
5
- type Container = Gtk.Widget | typeof ROOT_NODE_CONTAINER;
6
- type TextInstance = Node;
7
- type SuspenseInstance = never;
8
- type PublicInstance = Gtk.Widget;
9
- type FormInstance = never;
10
- type ReconcilerInstance = ReactReconciler.Reconciler<Container, Node, TextInstance, SuspenseInstance, FormInstance, PublicInstance>;
11
- /**
12
- * GTKX React reconciler class.
13
- */
1
+ import { type ReconcilerInstance } from "./reconciler/host-config.js";
14
2
  declare class Reconciler {
15
3
  private instance;
16
- /** Creates a new GTK reconciler instance. */
17
4
  constructor();
18
- /**
19
- * Gets the underlying React reconciler instance.
20
- * @returns The react-reconciler instance
21
- */
22
5
  getInstance(): ReconcilerInstance;
23
- private createHostConfig;
24
- /**
25
- * Creates a React context compatible with the reconciler's internal type.
26
- * Cast is necessary because React.Context and ReactReconciler.ReactContext
27
- * are structurally identical at runtime but declared as separate types.
28
- */
29
- private createReconcilerContext;
30
- private createNodeFromContainer;
6
+ private injectDevTools;
31
7
  }
32
8
  /**
33
9
  * The singleton GTKX React reconciler instance.
@@ -1,118 +1,27 @@
1
- import { beginBatch, endBatch } from "@gtkx/ffi";
2
- import React from "react";
3
1
  import ReactReconciler from "react-reconciler";
4
- import { beginCommit, endCommit } from "./batch.js";
2
+ import packageJson from "../package.json" with { type: "json" };
5
3
  import { createNode } from "./factory.js";
6
- /**
7
- * GTKX React reconciler class.
8
- */
4
+ import { createHostConfig } from "./reconciler/host-config.js";
9
5
  class Reconciler {
10
6
  instance;
11
- /** Creates a new GTK reconciler instance. */
12
7
  constructor() {
13
- this.instance = ReactReconciler(this.createHostConfig());
8
+ const createNodeFromContainer = (container) => {
9
+ return createNode(container.constructor.name, {}, container);
10
+ };
11
+ this.instance = ReactReconciler(createHostConfig(createNodeFromContainer));
12
+ this.injectDevTools();
14
13
  }
15
- /**
16
- * Gets the underlying React reconciler instance.
17
- * @returns The react-reconciler instance
18
- */
19
14
  getInstance() {
20
15
  return this.instance;
21
16
  }
22
- createHostConfig() {
23
- return {
24
- supportsMutation: true,
25
- supportsPersistence: false,
26
- supportsHydration: false,
27
- isPrimaryRenderer: true,
28
- noTimeout: -1,
29
- getRootHostContext: () => ({}),
30
- getChildHostContext: (parentHostContext) => parentHostContext,
31
- shouldSetTextContent: () => false,
32
- createInstance: (type, props) => {
33
- return createNode(type, props);
34
- },
35
- createTextInstance: (text) => createNode("Label", { label: text }),
36
- appendInitialChild: (parent, child) => parent.appendChild(child),
37
- finalizeInitialChildren: () => true,
38
- commitUpdate: (instance, _type, oldProps, newProps) => {
39
- instance.updateProps(oldProps, newProps);
40
- },
41
- commitMount: (instance) => {
42
- instance.mount();
43
- },
44
- appendChild: (parent, child) => parent.appendChild(child),
45
- removeChild: (parent, child) => parent.removeChild(child),
46
- insertBefore: (parent, child, beforeChild) => parent.insertBefore(child, beforeChild),
47
- removeChildFromContainer: (container, child) => {
48
- const parent = this.createNodeFromContainer(container);
49
- parent.removeChild(child);
50
- },
51
- appendChildToContainer: (container, child) => {
52
- const parent = this.createNodeFromContainer(container);
53
- parent.appendChild(child);
54
- },
55
- insertInContainerBefore: (container, child, beforeChild) => {
56
- const parent = this.createNodeFromContainer(container);
57
- parent.insertBefore(child, beforeChild);
58
- },
59
- prepareForCommit: () => {
60
- beginBatch();
61
- beginCommit();
62
- return null;
63
- },
64
- resetAfterCommit: () => {
65
- endCommit();
66
- endBatch();
67
- },
68
- commitTextUpdate: (textInstance, oldText, newText) => {
69
- textInstance.updateProps({ label: oldText }, { label: newText });
70
- },
71
- clearContainer: () => { },
72
- preparePortalMount: () => { },
73
- scheduleTimeout: (fn, delay) => {
74
- const timeoutId = setTimeout(fn, delay ?? 0);
75
- return typeof timeoutId === "number" ? timeoutId : Number(timeoutId);
76
- },
77
- cancelTimeout: (id) => {
78
- clearTimeout(id);
79
- },
80
- getPublicInstance: (instance) => instance.getWidget(),
81
- getCurrentUpdatePriority: () => 2,
82
- setCurrentUpdatePriority: () => { },
83
- resolveUpdatePriority: () => 2,
84
- NotPendingTransition: null,
85
- HostTransitionContext: this.createReconcilerContext(0),
86
- getInstanceFromNode: () => null,
87
- beforeActiveInstanceBlur: () => { },
88
- afterActiveInstanceBlur: () => { },
89
- prepareScopeUpdate: () => { },
90
- getInstanceFromScope: () => null,
91
- detachDeletedInstance: () => { },
92
- resetFormInstance: () => { },
93
- requestPostPaintCallback: () => { },
94
- shouldAttemptEagerTransition: () => false,
95
- trackSchedulerEvent: () => { },
96
- resolveEventType: () => null,
97
- resolveEventTimeStamp: () => Date.now(),
98
- maySuspendCommit: () => false,
99
- preloadInstance: () => false,
100
- startSuspendingCommit: () => { },
101
- suspendInstance: () => { },
102
- waitForCommitToBeReady: () => null,
103
- };
104
- }
105
- /**
106
- * Creates a React context compatible with the reconciler's internal type.
107
- * Cast is necessary because React.Context and ReactReconciler.ReactContext
108
- * are structurally identical at runtime but declared as separate types.
109
- */
110
- createReconcilerContext(value) {
111
- const context = React.createContext(value);
112
- return context;
113
- }
114
- createNodeFromContainer(container) {
115
- return createNode(container.constructor.name, {}, container);
17
+ injectDevTools() {
18
+ if (process.env.NODE_ENV === "production")
19
+ return;
20
+ this.instance.injectIntoDevTools({
21
+ bundleType: 1,
22
+ version: packageJson.version,
23
+ rendererPackageName: "@gtkx/react",
24
+ });
116
25
  }
117
26
  }
118
27
  /**
package/dist/render.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import type { ApplicationFlags } from "@gtkx/ffi/gio";
1
+ import type * as Gio from "@gtkx/ffi/gio";
2
2
  import type { ReactNode } from "react";
3
- /** The root container for the React reconciler. */
4
- export declare let container: unknown;
3
+ export declare const getContainer: () => unknown;
5
4
  /**
6
5
  * Renders a React element tree as a GTK application.
7
6
  * This is the main entry point for GTKX applications.
@@ -20,4 +19,4 @@ export declare let container: unknown;
20
19
  * @param appId - The application ID (e.g., "com.example.myapp")
21
20
  * @param flags - Optional GIO application flags
22
21
  */
23
- export declare const render: (element: ReactNode, appId: string, flags?: ApplicationFlags) => void;
22
+ export declare const render: (element: ReactNode, appId: string, flags?: Gio.ApplicationFlags) => void;
package/dist/render.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { start } from "@gtkx/ffi";
2
+ import { formatBoundaryError, formatRenderError } from "./errors.js";
2
3
  import { ROOT_NODE_CONTAINER } from "./factory.js";
3
4
  import { reconciler } from "./reconciler.js";
4
- /** The root container for the React reconciler. */
5
- export let container = null;
5
+ let container = null;
6
+ export const getContainer = () => container;
6
7
  /**
7
8
  * Renders a React element tree as a GTK application.
8
9
  * This is the main entry point for GTKX applications.
@@ -25,9 +26,10 @@ export const render = (element, appId, flags) => {
25
26
  start(appId, flags);
26
27
  const instance = reconciler.getInstance();
27
28
  container = instance.createContainer(ROOT_NODE_CONTAINER, 0, null, false, null, "", (error) => {
28
- throw error;
29
+ throw formatRenderError(error);
29
30
  }, (error) => {
30
- console.error("Error caught by ErrorBoundary:", error);
31
+ const formattedError = formatBoundaryError(error);
32
+ console.error(formattedError.toString());
31
33
  }, () => { }, () => { }, null);
32
34
  instance.updateContainer(element, container, null, () => { });
33
35
  };