@gtkx/react 1.0.0-rc.3 → 1.0.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 (203) hide show
  1. package/README.md +8 -9
  2. package/dist/adw/config.d.ts +1 -0
  3. package/dist/adw/config.d.ts.map +1 -1
  4. package/dist/adw/config.js +1 -0
  5. package/dist/adw/config.js.map +1 -1
  6. package/dist/adw/dialog.d.ts +4 -4
  7. package/dist/adw/dialog.d.ts.map +1 -1
  8. package/dist/adw/dialog.js +16 -21
  9. package/dist/adw/dialog.js.map +1 -1
  10. package/dist/adw/element-behaviors.js +104 -57
  11. package/dist/adw/element-behaviors.js.map +1 -1
  12. package/dist/adw/element-config.d.ts +0 -5
  13. package/dist/adw/element-config.d.ts.map +1 -1
  14. package/dist/adw/element-config.js +25 -26
  15. package/dist/adw/element-config.js.map +1 -1
  16. package/dist/adw/index.d.ts +1 -0
  17. package/dist/adw/index.d.ts.map +1 -1
  18. package/dist/adw/index.js +1 -0
  19. package/dist/adw/index.js.map +1 -1
  20. package/dist/adw/prop-types.d.ts +35 -4
  21. package/dist/adw/prop-types.d.ts.map +1 -1
  22. package/dist/adw/prop-types.js.map +1 -1
  23. package/dist/bootstrap.js +1 -1
  24. package/dist/bootstrap.js.map +1 -1
  25. package/dist/components/application-window.d.ts +5 -0
  26. package/dist/components/application-window.d.ts.map +1 -0
  27. package/dist/components/application-window.js +7 -0
  28. package/dist/components/application-window.js.map +1 -0
  29. package/dist/components/application.d.ts +3 -8
  30. package/dist/components/application.d.ts.map +1 -1
  31. package/dist/components/application.js +22 -6
  32. package/dist/components/application.js.map +1 -1
  33. package/dist/components/element.d.ts +13 -2
  34. package/dist/components/element.d.ts.map +1 -1
  35. package/dist/components/element.js +39 -15
  36. package/dist/components/element.js.map +1 -1
  37. package/dist/components/portaled.d.ts +5 -0
  38. package/dist/components/portaled.d.ts.map +1 -0
  39. package/dist/components/portaled.js +8 -0
  40. package/dist/components/portaled.js.map +1 -0
  41. package/dist/components/window.d.ts +6 -5
  42. package/dist/components/window.d.ts.map +1 -1
  43. package/dist/components/window.js +17 -16
  44. package/dist/components/window.js.map +1 -1
  45. package/dist/config.d.ts +7 -2
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +5 -1
  48. package/dist/config.js.map +1 -1
  49. package/dist/element-behaviors.js +11 -2
  50. package/dist/element-behaviors.js.map +1 -1
  51. package/dist/element-config.d.ts +4 -3
  52. package/dist/element-config.d.ts.map +1 -1
  53. package/dist/element-config.js +19 -15
  54. package/dist/element-config.js.map +1 -1
  55. package/dist/hooks/use-bind-setting.d.ts +8 -7
  56. package/dist/hooks/use-bind-setting.d.ts.map +1 -1
  57. package/dist/hooks/use-bind-setting.js +2 -7
  58. package/dist/hooks/use-bind-setting.js.map +1 -1
  59. package/dist/hooks/use-merged-refs.d.ts +1 -4
  60. package/dist/hooks/use-merged-refs.d.ts.map +1 -1
  61. package/dist/hooks/use-merged-refs.js +1 -1
  62. package/dist/hooks/use-merged-refs.js.map +1 -1
  63. package/dist/hooks/use-parent-window.d.ts +3 -1
  64. package/dist/hooks/use-parent-window.d.ts.map +1 -1
  65. package/dist/hooks/use-parent-window.js +3 -1
  66. package/dist/hooks/use-parent-window.js.map +1 -1
  67. package/dist/hooks/use-presented-instance.d.ts +12 -0
  68. package/dist/hooks/use-presented-instance.d.ts.map +1 -0
  69. package/dist/hooks/use-presented-instance.js +27 -0
  70. package/dist/hooks/use-presented-instance.js.map +1 -0
  71. package/dist/hooks/use-property.d.ts +4 -3
  72. package/dist/hooks/use-property.d.ts.map +1 -1
  73. package/dist/hooks/use-property.js +1 -2
  74. package/dist/hooks/use-property.js.map +1 -1
  75. package/dist/hooks/use-setting.d.ts +2 -3
  76. package/dist/hooks/use-setting.d.ts.map +1 -1
  77. package/dist/hooks/use-setting.js +2 -3
  78. package/dist/hooks/use-setting.js.map +1 -1
  79. package/dist/hooks/use-signal.d.ts +15 -8
  80. package/dist/hooks/use-signal.d.ts.map +1 -1
  81. package/dist/hooks/use-signal.js +6 -8
  82. package/dist/hooks/use-signal.js.map +1 -1
  83. package/dist/index.d.ts +10 -2
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +8 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/internal.d.ts +8 -5
  88. package/dist/internal.d.ts.map +1 -1
  89. package/dist/internal.js +7 -5
  90. package/dist/internal.js.map +1 -1
  91. package/dist/prop-types.d.ts +88 -4
  92. package/dist/prop-types.d.ts.map +1 -1
  93. package/dist/prop-types.js.map +1 -1
  94. package/dist/reconciler/apply-props.d.ts +4 -4
  95. package/dist/reconciler/apply-props.d.ts.map +1 -1
  96. package/dist/reconciler/apply-props.js +62 -20
  97. package/dist/reconciler/apply-props.js.map +1 -1
  98. package/dist/reconciler/behaviors.d.ts +9 -20
  99. package/dist/reconciler/behaviors.d.ts.map +1 -1
  100. package/dist/reconciler/behaviors.js +44 -41
  101. package/dist/reconciler/behaviors.js.map +1 -1
  102. package/dist/reconciler/devtools.d.ts +6 -0
  103. package/dist/reconciler/devtools.d.ts.map +1 -0
  104. package/dist/reconciler/devtools.js +11 -0
  105. package/dist/reconciler/devtools.js.map +1 -0
  106. package/dist/reconciler/host-config.d.ts +1 -1
  107. package/dist/reconciler/host-config.d.ts.map +1 -1
  108. package/dist/reconciler/host-config.js +17 -26
  109. package/dist/reconciler/host-config.js.map +1 -1
  110. package/dist/reconciler/instance.d.ts.map +1 -1
  111. package/dist/reconciler/instance.js +6 -4
  112. package/dist/reconciler/instance.js.map +1 -1
  113. package/dist/reconciler/metadata.d.ts +2 -2
  114. package/dist/reconciler/metadata.d.ts.map +1 -1
  115. package/dist/reconciler/metadata.js +9 -11
  116. package/dist/reconciler/metadata.js.map +1 -1
  117. package/dist/reconciler/node.d.ts +4 -3
  118. package/dist/reconciler/node.d.ts.map +1 -1
  119. package/dist/reconciler/node.js +8 -7
  120. package/dist/reconciler/node.js.map +1 -1
  121. package/dist/reconciler/placement.js +20 -20
  122. package/dist/reconciler/placement.js.map +1 -1
  123. package/dist/reconciler/registry.d.ts +46 -32
  124. package/dist/reconciler/registry.d.ts.map +1 -1
  125. package/dist/reconciler/registry.js +10 -19
  126. package/dist/reconciler/registry.js.map +1 -1
  127. package/dist/reconciler/root-element.d.ts +1 -1
  128. package/dist/reconciler/root-element.d.ts.map +1 -1
  129. package/dist/reconciler/root-element.js +0 -1
  130. package/dist/reconciler/root-element.js.map +1 -1
  131. package/dist/reconciler/root.d.ts +9 -20
  132. package/dist/reconciler/root.d.ts.map +1 -1
  133. package/dist/reconciler/root.js +6 -19
  134. package/dist/reconciler/root.js.map +1 -1
  135. package/dist/reconciler/signals.d.ts +1 -2
  136. package/dist/reconciler/signals.d.ts.map +1 -1
  137. package/dist/reconciler/signals.js +3 -3
  138. package/dist/reconciler/signals.js.map +1 -1
  139. package/dist/reconciler/text.js +5 -5
  140. package/dist/reconciler/text.js.map +1 -1
  141. package/dist/utils/accessible-props.d.ts +57 -9
  142. package/dist/utils/accessible-props.d.ts.map +1 -1
  143. package/dist/utils/accessible-props.js +11 -13
  144. package/dist/utils/accessible-props.js.map +1 -1
  145. package/dist/utils/ref-prop.d.ts +0 -6
  146. package/dist/utils/ref-prop.d.ts.map +1 -1
  147. package/dist/utils/ref-prop.js +0 -6
  148. package/dist/utils/ref-prop.js.map +1 -1
  149. package/dist/utils/settings.d.ts +7 -0
  150. package/dist/utils/settings.d.ts.map +1 -1
  151. package/dist/utils/settings.js.map +1 -1
  152. package/dist/utils/variant.d.ts +33 -1
  153. package/dist/utils/variant.d.ts.map +1 -1
  154. package/dist/utils/variant.js.map +1 -1
  155. package/env.d.ts +11 -4
  156. package/package.json +5 -5
  157. package/src/adw/config.ts +1 -0
  158. package/src/adw/dialog.tsx +23 -25
  159. package/src/adw/element-behaviors.ts +131 -60
  160. package/src/adw/element-config.ts +25 -26
  161. package/src/adw/index.ts +1 -0
  162. package/src/adw/prop-types.ts +36 -3
  163. package/src/bootstrap.ts +1 -1
  164. package/src/components/application-window.tsx +10 -0
  165. package/src/components/application.tsx +32 -5
  166. package/src/components/element.tsx +52 -17
  167. package/src/components/portaled.tsx +14 -0
  168. package/src/components/window.tsx +25 -23
  169. package/src/config.ts +13 -8
  170. package/src/element-behaviors.ts +20 -1
  171. package/src/element-config.ts +19 -15
  172. package/src/hooks/use-bind-setting.ts +8 -7
  173. package/src/hooks/use-merged-refs.ts +1 -1
  174. package/src/hooks/use-parent-window.ts +3 -1
  175. package/src/hooks/use-presented-instance.tsx +50 -0
  176. package/src/hooks/use-property.ts +4 -3
  177. package/src/hooks/use-setting.ts +2 -3
  178. package/src/hooks/use-signal.ts +18 -11
  179. package/src/index.ts +10 -2
  180. package/src/internal.ts +8 -5
  181. package/src/prop-types.ts +119 -11
  182. package/src/reconciler/apply-props.ts +77 -24
  183. package/src/reconciler/behaviors.ts +77 -50
  184. package/src/reconciler/devtools.ts +18 -0
  185. package/src/reconciler/host-config.ts +20 -32
  186. package/src/reconciler/instance.ts +7 -4
  187. package/src/reconciler/metadata.ts +11 -14
  188. package/src/reconciler/node.ts +12 -9
  189. package/src/reconciler/placement.ts +25 -25
  190. package/src/reconciler/registry.ts +56 -39
  191. package/src/reconciler/root-element.ts +4 -2
  192. package/src/reconciler/root.ts +12 -20
  193. package/src/reconciler/signals.ts +2 -3
  194. package/src/reconciler/text.ts +6 -6
  195. package/src/utils/accessible-props.ts +70 -25
  196. package/src/utils/ref-prop.ts +0 -6
  197. package/src/utils/settings.ts +7 -0
  198. package/src/utils/variant.ts +33 -1
  199. package/dist/utils/accessible-metadata.d.ts +0 -12
  200. package/dist/utils/accessible-metadata.d.ts.map +0 -1
  201. package/dist/utils/accessible-metadata.js +0 -25
  202. package/dist/utils/accessible-metadata.js.map +0 -1
  203. package/src/utils/accessible-metadata.ts +0 -31
@@ -3,7 +3,6 @@ import { type ElementConfig, forTypes, internal, type ModuleExport } from "../re
3
3
  const CHILD_SETTER_TYPES: string[] = [
4
4
  "AdwBin",
5
5
  "AdwClamp",
6
- "AdwClampScrollable",
7
6
  "AdwNavigationPage",
8
7
  "AdwSplitButton",
9
8
  "AdwStatusPage",
@@ -12,30 +11,38 @@ const CHILD_SETTER_TYPES: string[] = [
12
11
  "AdwToggle",
13
12
  ];
14
13
 
15
- const CONTENT_SETTER_TYPES: string[] = ["AdwBottomSheet", "AdwFlap", "AdwOverlaySplitView"];
14
+ const CONTENT_SETTER_TYPES: string[] = ["AdwBottomSheet", "AdwOverlaySplitView"];
16
15
  const childrenProps = internal("ChildrenProps");
17
16
  const breakpointsProps = adw("AdwBreakpointsProps");
18
17
  const preferencesRowProps = adw("AdwPreferencesRowProps");
19
18
 
20
- /**
21
- * The static, runtime-free half of the built-in element configuration: which base props interface each
22
- * generated element extends, which component wraps it, and whether its GObject is created lazily by its
23
- * parent. Codegen imports this module, so it must never reach the GObject bindings.
24
- */
25
19
  const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
26
20
  ...forTypes(CHILD_SETTER_TYPES, {
27
21
  props: childrenProps,
28
- omitProps: ["child"],
22
+ omittedProps: ["child"],
29
23
  }),
30
24
  ...forTypes(CONTENT_SETTER_TYPES, {
31
25
  props: childrenProps,
32
- omitProps: ["content"],
26
+ omittedProps: ["content"],
33
27
  }),
28
+ AdwClampScrollable: {
29
+ props: childrenProps,
30
+ omittedProps: ["child"],
31
+ },
34
32
  AdwViewStackPage: {
35
- lazy: true,
33
+ isLazy: true,
36
34
  },
37
35
  AdwTabPage: {
38
- lazy: true,
36
+ isLazy: true,
37
+ },
38
+ AdwLayout: {
39
+ isLazy: true,
40
+ },
41
+ AdwSidebarSection: {
42
+ props: childrenProps,
43
+ },
44
+ AdwMultiLayoutView: {
45
+ props: adw("AdwMultiLayoutViewProps"),
39
46
  },
40
47
  AdwPreferencesRow: {
41
48
  props: preferencesRowProps,
@@ -43,19 +50,19 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
43
50
  AdwDialog: {
44
51
  props: breakpointsProps,
45
52
  component: adw("createDialogComponent"),
46
- omitProps: ["child"],
53
+ omittedProps: ["child"],
47
54
  },
48
55
  AdwApplicationWindow: {
49
56
  props: breakpointsProps,
50
- omitProps: ["content"],
57
+ omittedProps: ["content"],
51
58
  },
52
59
  AdwWindow: {
53
60
  props: breakpointsProps,
54
- omitProps: ["content"],
61
+ omittedProps: ["content"],
55
62
  },
56
63
  AdwBreakpointBin: {
57
64
  props: breakpointsProps,
58
- omitProps: ["child"],
65
+ omittedProps: ["child"],
59
66
  },
60
67
  AdwActionRow: {
61
68
  props: preferencesRowProps,
@@ -68,10 +75,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
68
75
  },
69
76
  AdwNavigationSplitView: {
70
77
  props: childrenProps,
71
- omitProps: ["content"],
72
- },
73
- AdwLeaflet: {
74
- props: childrenProps,
78
+ omittedProps: ["content"],
75
79
  },
76
80
  AdwWrapBox: {
77
81
  props: childrenProps,
@@ -85,15 +89,9 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
85
89
  AdwPreferencesDialog: {
86
90
  props: childrenProps,
87
91
  },
88
- AdwPreferencesWindow: {
89
- props: childrenProps,
90
- },
91
92
  AdwPreferencesGroup: {
92
93
  props: childrenProps,
93
94
  },
94
- AdwSqueezer: {
95
- props: childrenProps,
96
- },
97
95
  AdwTabView: {
98
96
  props: childrenProps,
99
97
  },
@@ -105,7 +103,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
105
103
  },
106
104
  AdwToolbarView: {
107
105
  props: adw("AdwToolbarViewProps"),
108
- omitProps: ["content"],
106
+ omittedProps: ["content"],
109
107
  },
110
108
  AdwHeaderBar: {
111
109
  props: internal("GtkHeaderBarProps"),
@@ -121,6 +119,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
121
119
  },
122
120
  AdwAlertDialog: {
123
121
  props: adw("AdwAlertDialogProps"),
122
+ omittedProps: ["extraChild"],
124
123
  },
125
124
  };
126
125
 
package/src/adw/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import "./element-behaviors.js";
2
2
 
3
+ /** @internal */
3
4
  export { createDialogComponent } from "./dialog.js";
4
5
  export type * from "./prop-types.js";
@@ -2,36 +2,68 @@ import type * as Adw from "@gtkx/gi/adw";
2
2
  import type { ReactNode } from "react";
3
3
  import type { ChildrenProps } from "../prop-types.js";
4
4
 
5
+ /** One button in an `Adw.AlertDialog`'s `responses` prop. */
5
6
  type AlertDialogResponse = {
7
+ /** Name the dialog reports on `onResponse` when this button is chosen. */
6
8
  id: string;
9
+ /** Text shown on the button. */
7
10
  label: string;
11
+ /** Styling the button is given, such as suggested or destructive; defaults to the plain appearance. */
8
12
  appearance?: Adw.ResponseAppearance;
9
- enabled?: boolean;
13
+ /** Whether the button can be activated; defaults to true. */
14
+ isEnabled?: boolean;
10
15
  };
11
16
 
17
+ /** Props of an `Adw.AlertDialog` element. */
12
18
  type AdwAlertDialogProps = {
19
+ /** Buttons the dialog offers, added and removed as the list changes. */
13
20
  responses?: AlertDialogResponse[] | null | undefined;
14
- } & ChildrenProps;
21
+ /**
22
+ * Widget set as the dialog's extra child, shown below the `heading` and `body` and above the
23
+ * response buttons, leaving the dialog's own chrome in place.
24
+ */
25
+ children?: ReactNode;
26
+ };
15
27
 
28
+ /** Props of an `Adw.PreferencesRow` element. */
16
29
  type AdwPreferencesRowProps = {
30
+ /** Widgets added at the start of the row, before its title. */
17
31
  prefix?: ReactNode | null | undefined;
32
+ /** Widgets added at the end of the row. */
18
33
  suffix?: ReactNode | null | undefined;
19
34
  };
20
35
 
36
+ /** Props of an `Adw.ExpanderRow` element. */
21
37
  type AdwExpanderRowProps = {
38
+ /** Widgets added to the area the row reveals when it expands. */
22
39
  rows?: ReactNode | null | undefined;
23
- actions?: ReactNode | null | undefined;
24
40
  } & AdwPreferencesRowProps;
25
41
 
42
+ /** Props of an `Adw.ToolbarView` element, whose `children` is the content the bars surround. */
26
43
  type AdwToolbarViewProps = {
44
+ /** Widgets stacked above the content. */
27
45
  topBar?: ReactNode | null | undefined;
46
+ /** Widgets stacked below the content. */
28
47
  bottomBar?: ReactNode | null | undefined;
29
48
  } & ChildrenProps;
30
49
 
50
+ /** Props of an element that hosts breakpoints, such as `Adw.Window` or `Adw.BreakpointBin`. */
31
51
  type AdwBreakpointsProps = {
52
+ /** `Adw.Breakpoint` elements added to the element, each applying while its condition holds. */
32
53
  breakpoints?: ReactNode | null | undefined;
33
54
  } & ChildrenProps;
34
55
 
56
+ /** Props of an `Adw.MultiLayoutView` element. */
57
+ type AdwMultiLayoutViewProps = {
58
+ /** `Adw.Layout` elements added to the view, each holding the content it lays out. */
59
+ layouts?: ReactNode | null | undefined;
60
+ /**
61
+ * Widget the view places in the `Adw.LayoutSlot` whose `id` is the prop name without its `Slot`
62
+ * suffix, so `sidebarSlot` fills the slot with id `sidebar` in whichever layout is current.
63
+ */
64
+ [slot: `${string}Slot`]: ReactNode | null | undefined;
65
+ };
66
+
35
67
  export {
36
68
  type AlertDialogResponse,
37
69
  type AdwAlertDialogProps,
@@ -39,4 +71,5 @@ export {
39
71
  type AdwExpanderRowProps,
40
72
  type AdwToolbarViewProps,
41
73
  type AdwBreakpointsProps,
74
+ type AdwMultiLayoutViewProps,
42
75
  };
package/src/bootstrap.ts CHANGED
@@ -2,4 +2,4 @@ import { elements } from "virtual:gtkx-config";
2
2
  import "./element-behaviors.js";
3
3
  import { registerElements } from "./reconciler/registry.js";
4
4
 
5
- registerElements(elements, { prepend: true });
5
+ registerElements(elements, { isPrepended: true });
@@ -0,0 +1,10 @@
1
+ import type { ElementType, ReactNode } from "react";
2
+ import { useApplication } from "../hooks/use-application.js";
3
+ import { createPortaledComponent } from "./portaled.js";
4
+ import { createPresentedWindowComponent } from "./window.js";
5
+
6
+ const createApplicationWindowComponent = (Component: ElementType): ((props: unknown) => ReactNode) =>
7
+ createPortaledComponent(createPresentedWindowComponent(Component), useApplication);
8
+
9
+ /** @internal */
10
+ export { createApplicationWindowComponent };
@@ -1,10 +1,12 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
2
  import { quitApplication, runApplication } from "@gtkx/runtime";
3
3
  import { pickBy } from "@gtkx/utils";
4
+ import process from "node:process";
4
5
  import { type ElementType, type ReactNode, type Ref, useLayoutEffect, useState } from "react";
5
6
  import { applicationId as defaultApplicationId } from "virtual:gtkx-config";
6
7
  import { ApplicationContext } from "../hooks/use-application.js";
7
8
  import { useMergedRef } from "../hooks/use-merged-refs.js";
9
+ import { createPortaledComponent } from "./portaled.js";
8
10
 
9
11
  type ApplicationComponentProps = {
10
12
  applicationId?: string | null | undefined;
@@ -14,23 +16,44 @@ type ApplicationComponentProps = {
14
16
 
15
17
  const POST_ACTIVATE_PROPS = new Set(["menubar"]);
16
18
 
19
+ const commandLine = (applicationId: string | null): string[] => [
20
+ applicationId?.split(".").at(-1) ?? "gtkx",
21
+ ...process.argv.slice(2),
22
+ ];
23
+
24
+ const startApplication = (
25
+ application: Gtk.Application,
26
+ setActivated: (isActivated: boolean) => void,
27
+ applicationId: string | null,
28
+ ): void => {
29
+ application.on("activate", () => {
30
+ setActivated(true);
31
+ });
32
+
33
+ const { exitStatus } = runApplication(application, commandLine(applicationId));
34
+
35
+ if (exitStatus !== 0) {
36
+ process.exitCode = exitStatus;
37
+ }
38
+ };
39
+
17
40
  const useApplicationLifecycle = (
18
41
  application: Gtk.Application | null,
19
42
  setActivated: (isActivated: boolean) => void,
43
+ applicationId: string | null,
20
44
  ): void => {
21
45
  useLayoutEffect(() => {
22
46
  if (!application) {
23
47
  return;
24
48
  }
25
49
 
26
- runApplication(application);
27
- setActivated(true);
50
+ startApplication(application, setActivated, applicationId);
28
51
 
29
52
  return () => {
30
53
  quitApplication(application);
31
54
  setActivated(false);
32
55
  };
33
- }, [application, setActivated]);
56
+ }, [application, setActivated, applicationId]);
34
57
  };
35
58
 
36
59
  const applicationChildren = (application: Gtk.Application | null, children: ReactNode): ReactNode => {
@@ -41,13 +64,13 @@ const applicationChildren = (application: Gtk.Application | null, children: Reac
41
64
  return <ApplicationContext.Provider value={application}>{children}</ApplicationContext.Provider>;
42
65
  };
43
66
 
44
- const createApplicationComponent = (
67
+ const createApplicationElement = (
45
68
  Component: ElementType,
46
69
  ): ((props: ApplicationComponentProps) => ReactNode) => {
47
70
  return ({ applicationId = defaultApplicationId, children, ref, ...rest }: ApplicationComponentProps): ReactNode => {
48
71
  const [application, setApplication] = useState<Gtk.Application | null>(null);
49
72
  const [activated, setActivated] = useState(false);
50
- useApplicationLifecycle(application, setActivated);
73
+ useApplicationLifecycle(application, setActivated, applicationId);
51
74
  const mergedRef = useMergedRef(ref, setApplication);
52
75
  const appliedProps = activated ? rest : pickBy(rest, (_value, key) => !POST_ACTIVATE_PROPS.has(key));
53
76
 
@@ -59,4 +82,8 @@ const createApplicationComponent = (
59
82
  };
60
83
  };
61
84
 
85
+ const createApplicationComponent = (Component: ElementType): ((props: unknown) => ReactNode) =>
86
+ createPortaledComponent(createApplicationElement(Component));
87
+
88
+ /** @internal */
62
89
  export { createApplicationComponent };
@@ -2,46 +2,81 @@ import { isRecord } from "@gtkx/utils";
2
2
  import { createElement, type ElementType, isValidElement, type ReactElement, type ReactNode } from "react";
3
3
  import type { Props } from "../reconciler/registry.js";
4
4
 
5
- /** The intrinsic element that carries an object-valued prop's element into its parent's named property. */
6
5
  const Prop = "gtkx:prop";
6
+ const NO_PROP_CHILDREN: ReactNode[] = [];
7
7
 
8
8
  const hasElement = (value: unknown): boolean =>
9
9
  isValidElement(value) || (Array.isArray(value) && value.some((item: unknown) => hasElement(item)));
10
10
 
11
- const routeProp = (key: string, value: unknown, hostProps: Props, propChildren: ReactNode[]): void => {
12
- if (key !== "ref" && hasElement(value)) {
11
+ const isRoutedProp = (key: string, value: unknown): boolean =>
12
+ key !== "children" && key !== "ref" && hasElement(value);
13
+
14
+ const collectPropChildren = (record: Props): ReactNode[] | null => {
15
+ let propChildren: ReactNode[] | null = null;
16
+
17
+ for (const key in record) {
18
+ if (!isRoutedProp(key, record[key])) {
19
+ continue;
20
+ }
21
+
22
+ propChildren ??= [];
23
+
13
24
  propChildren.push(
14
- createElement(Prop, { propName: key, key: `${Prop}:${key}` }, value as ReactNode),
25
+ createElement(Prop, { propName: key, key: `${Prop}:${key}` }, record[key] as ReactNode),
15
26
  );
16
- } else {
17
- hostProps[key] = value;
18
27
  }
28
+
29
+ return propChildren;
19
30
  };
20
31
 
21
- const buildElement = (typeName: string, record: Props): ReactElement => {
22
- const Host = typeName as ElementType;
32
+ const hostPropsWithout = (record: Props): Props => {
23
33
  const hostProps: Props = {};
24
- const propChildren: ReactNode[] = [];
25
34
 
26
35
  for (const key in record) {
27
- if (key === "children") {
28
- continue;
36
+ if (key !== "children" && !isRoutedProp(key, record[key])) {
37
+ hostProps[key] = record[key];
29
38
  }
39
+ }
40
+
41
+ return hostProps;
42
+ };
30
43
 
31
- routeProp(key, record[key], hostProps, propChildren);
44
+ const buildElement = (typeName: string, record: Props): ReactElement => {
45
+ const Host = typeName as ElementType;
46
+ const propChildren = collectPropChildren(record);
47
+
48
+ if (propChildren === null) {
49
+ return (
50
+ <Host {...record}>
51
+ {NO_PROP_CHILDREN}
52
+ {record.children as ReactNode}
53
+ </Host>
54
+ );
32
55
  }
33
56
 
34
57
  return (
35
- <Host {...hostProps}>
58
+ <Host {...hostPropsWithout(record)}>
36
59
  {propChildren}
37
60
  {record.children as ReactNode}
38
61
  </Host>
39
62
  );
40
63
  };
41
64
 
42
- const createElementComponent =
43
- (typeName: string): ((props: unknown) => ReactNode) =>
44
- (props: unknown): ReactNode =>
45
- buildElement(typeName, isRecord(props) ? props : {});
65
+ /**
66
+ * Builds the component that renders the element of a GLib type, which is how the generated `@gtkx/jsx`
67
+ * store exposes every widget. Reach for it to render a type codegen does not cover, such as one
68
+ * `registerClass` created: the element name is the GType name, and the component routes each prop whose
69
+ * value is an element into that prop's slot, which a bare intrinsic element cannot do.
70
+ *
71
+ * Name the props the element takes as the type argument, since the GType name says nothing about them.
72
+ * Left out, the component takes `unknown`, which accepts no attributes at all.
73
+ *
74
+ * @param typeName GType name to render, such as `GtkButton` or the `typeName` given to `registerClass`.
75
+ * @returns A component taking that type's props.
76
+ */
77
+ /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callers name the props */
78
+ function createElementComponent<P = unknown>(typeName: string): (props: P) => ReactNode {
79
+ return (props: P): ReactNode => buildElement(typeName, isRecord(props) ? props : {});
80
+ }
46
81
 
47
82
  export { Prop, createElementComponent };
@@ -0,0 +1,14 @@
1
+ import type { ElementType, ReactNode } from "react";
2
+ import { isRecord } from "@gtkx/utils";
3
+ import type { Container } from "../reconciler/host-config.js";
4
+ import { rootElement } from "../reconciler/root-element.js";
5
+ import { createPortal } from "../reconciler/root.js";
6
+
7
+ const useRootContainer = (): Container => rootElement;
8
+
9
+ const createPortaledComponent =
10
+ (Component: ElementType, useContainer: () => Container = useRootContainer): ((props: unknown) => ReactNode) =>
11
+ (props: unknown): ReactNode =>
12
+ createPortal(<Component {...(isRecord(props) ? props : {})} />, useContainer());
13
+
14
+ export { createPortaledComponent };
@@ -1,11 +1,13 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import { type ElementType, type ReactNode, type Ref, useLayoutEffect, useState } from "react";
3
- import { useMergedRef } from "../hooks/use-merged-refs.js";
2
+ import { type ElementType, type ReactElement, type ReactNode, use } from "react";
4
3
  import { ParentWindowContext } from "../hooks/use-parent-window.js";
4
+ import { createPresentedComponent, type PresentedProps } from "../hooks/use-presented-instance.js";
5
5
  import { applyWrite } from "../reconciler/signals.js";
6
+ import { createPortaledComponent } from "./portaled.js";
6
7
 
7
- type WindowComponentProps = {
8
- ref?: Ref<Gtk.Window | null> | undefined;
8
+ type WindowComponentProps = PresentedProps<Gtk.Window> & {
9
+ // eslint-disable-next-line gtkx/accessor-naming
10
+ transientFor?: Gtk.Window | ReactElement | null | undefined;
9
11
  };
10
12
 
11
13
  const presentWindow = (window: Gtk.Window): void => {
@@ -20,29 +22,29 @@ const destroyWindow = (window: Gtk.Window): void => {
20
22
  });
21
23
  };
22
24
 
23
- const createWindowComponent = (Component: ElementType): ((props: WindowComponentProps) => ReactNode) => {
24
- return ({ ref, ...rest }: WindowComponentProps): ReactNode => {
25
- const [window, setWindow] = useState<Gtk.Window | null>(null);
26
- const mergedRef = useMergedRef(ref, setWindow);
25
+ const usePresentWindow = (): ((window: Gtk.Window) => void) => presentWindow;
27
26
 
28
- useLayoutEffect(() => {
29
- if (!window) {
30
- return;
31
- }
32
-
33
- presentWindow(window);
27
+ const createPresentedWindowComponent = (Component: ElementType): ((props: PresentedProps<Gtk.Window>) => ReactNode) =>
28
+ createPresentedComponent<Gtk.Window>(Component, {
29
+ usePresent: usePresentWindow,
30
+ dismiss: destroyWindow,
31
+ wrap: (element, window) => (
32
+ <ParentWindowContext.Provider value={window}>{element}</ParentWindowContext.Provider>
33
+ ),
34
+ });
34
35
 
35
- return () => {
36
- destroyWindow(window);
37
- };
38
- }, [window]);
36
+ const withDefaultTransientFor = (Component: ElementType): ((props: WindowComponentProps) => ReactNode) => {
37
+ return (props: WindowComponentProps): ReactNode => {
38
+ const parent = use(ParentWindowContext);
39
+ const isDefaulted = props.transientFor === undefined && parent !== null;
39
40
 
40
- return (
41
- <ParentWindowContext.Provider value={window}>
42
- <Component ref={mergedRef} {...rest} />
43
- </ParentWindowContext.Provider>
44
- );
41
+ return <Component {...(isDefaulted ? { ...props, transientFor: parent } : props)} />;
45
42
  };
46
43
  };
47
44
 
45
+ const createWindowComponent = (Component: ElementType): ((props: unknown) => ReactNode) =>
46
+ createPortaledComponent(withDefaultTransientFor(createPresentedWindowComponent(Component)));
47
+
48
+ export { createPresentedWindowComponent };
49
+ /** @internal */
48
50
  export { createWindowComponent };
package/src/config.ts CHANGED
@@ -1,10 +1,15 @@
1
+ /** @public */
2
+ export { createElementComponent } from "./components/element.js";
3
+ /** @public */
1
4
  export { BUILTIN_ELEMENTS } from "./element-config.js";
2
- export type { DetachInfo, ElementBehavior, ElementConfig, ModuleExport, PlaceInfo } from "./reconciler/registry.js";
3
- export {
4
- defineBehavior,
5
- defineElements,
6
- ELEMENTS,
7
- forTypes,
8
- internal,
9
- mergeElementConfigs,
5
+ /** @public */
6
+ export type {
7
+ DetachInfo,
8
+ ElementBehavior,
9
+ ElementConfig,
10
+ ModuleExport,
11
+ PlaceInfo,
12
+ Props,
10
13
  } from "./reconciler/registry.js";
14
+ /** @public */
15
+ export { defineBehavior, defineElements, ELEMENTS, mergeElementConfigs } from "./reconciler/registry.js";
@@ -1,11 +1,13 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import * as Gio from "@gtkx/gi/gio";
3
+ import * as GLib from "@gtkx/gi/glib";
3
4
  import * as Gtk from "@gtkx/gi/gtk";
4
5
  import type {
5
6
  ActionAccel,
6
7
  CreditSection,
7
8
  DragSourceIcon,
8
9
  LevelBarOffset,
10
+ MainOption,
9
11
  MenuItem,
10
12
  ScaleMark,
11
13
  VflConstraints,
@@ -14,6 +16,7 @@ import { BUILTIN_ELEMENTS, SINGLE_CHILD_TYPES } from "./element-config.js";
14
16
  import {
15
17
  addRemoveSlot,
16
18
  adoptedChildrenSlot,
19
+ applicationCreator,
17
20
  boxSlot,
18
21
  childSetterSlot,
19
22
  controlledText,
@@ -25,7 +28,6 @@ import {
25
28
  } from "./reconciler/behaviors.js";
26
29
  import { type ElementConfig, forTypes, registerElements } from "./reconciler/registry.js";
27
30
 
28
- /** The runtime half of the built-in element configuration: the behaviors bound to each GObject type. */
29
31
  const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
30
32
  ...forTypes(SINGLE_CHILD_TYPES, {
31
33
  behaviors: [childSetterSlot()],
@@ -296,6 +298,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
296
298
  },
297
299
  GtkApplication: {
298
300
  behaviors: [
301
+ applicationCreator(Gtk.Application),
299
302
  addRemoveSlot<Gtk.Window, Gtk.Application>(
300
303
  "children",
301
304
  "GtkWindow",
@@ -314,6 +317,11 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
314
317
  application.setAccelsForAction(item.detailedActionName, []);
315
318
  },
316
319
  }),
320
+ list<Gtk.Application, MainOption>("mainOptions", {
321
+ add: (application, option) => {
322
+ addMainOption(application, option);
323
+ },
324
+ }),
317
325
  ],
318
326
  },
319
327
  GtkAboutDialog: {
@@ -392,6 +400,17 @@ function layoutChild(parent: Gtk.Widget, child: Gtk.Widget): GObject.Object | nu
392
400
  return parent.getLayoutManager()?.getLayoutChild(child) ?? null;
393
401
  }
394
402
 
403
+ const addMainOption = (application: Gtk.Application, option: MainOption): void => {
404
+ application.addMainOption(
405
+ option.longName,
406
+ option.shortName?.codePointAt(0) ?? 0,
407
+ option.flags ?? GLib.OptionFlags.NONE,
408
+ option.arg ?? GLib.OptionArg.NONE,
409
+ option.description,
410
+ option.argDescription ?? null,
411
+ );
412
+ };
413
+
395
414
  const buildMenu = (items: MenuItem[]): Gio.Menu => {
396
415
  const menu = Gio.Menu.new();
397
416