@gtkx/react 1.0.0-rc.4 → 1.1.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 (155) hide show
  1. package/README.md +5 -5
  2. package/dist/adw/dialog.d.ts +3 -4
  3. package/dist/adw/dialog.d.ts.map +1 -1
  4. package/dist/adw/dialog.js +18 -21
  5. package/dist/adw/dialog.js.map +1 -1
  6. package/dist/adw/element-behaviors.js +102 -54
  7. package/dist/adw/element-behaviors.js.map +1 -1
  8. package/dist/adw/element-config.d.ts +0 -6
  9. package/dist/adw/element-config.d.ts.map +1 -1
  10. package/dist/adw/element-config.js +15 -17
  11. package/dist/adw/element-config.js.map +1 -1
  12. package/dist/adw/prop-types.d.ts +34 -3
  13. package/dist/adw/prop-types.d.ts.map +1 -1
  14. package/dist/adw/prop-types.js.map +1 -1
  15. package/dist/components/application-window.d.ts +5 -0
  16. package/dist/components/application-window.d.ts.map +1 -0
  17. package/dist/components/application-window.js +7 -0
  18. package/dist/components/application-window.js.map +1 -0
  19. package/dist/components/application.d.ts +2 -8
  20. package/dist/components/application.d.ts.map +1 -1
  21. package/dist/components/application.js +31 -7
  22. package/dist/components/application.js.map +1 -1
  23. package/dist/components/element.d.ts +13 -1
  24. package/dist/components/element.d.ts.map +1 -1
  25. package/dist/components/element.js +39 -14
  26. package/dist/components/element.js.map +1 -1
  27. package/dist/components/portaled.d.ts +5 -0
  28. package/dist/components/portaled.d.ts.map +1 -0
  29. package/dist/components/portaled.js +8 -0
  30. package/dist/components/portaled.js.map +1 -0
  31. package/dist/components/window.d.ts +5 -5
  32. package/dist/components/window.d.ts.map +1 -1
  33. package/dist/components/window.js +16 -16
  34. package/dist/components/window.js.map +1 -1
  35. package/dist/config.d.ts +3 -1
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +2 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/element-behaviors.js +11 -1
  40. package/dist/element-behaviors.js.map +1 -1
  41. package/dist/element-config.d.ts.map +1 -1
  42. package/dist/element-config.js +7 -4
  43. package/dist/element-config.js.map +1 -1
  44. package/dist/hooks/use-merged-refs.d.ts +1 -4
  45. package/dist/hooks/use-merged-refs.d.ts.map +1 -1
  46. package/dist/hooks/use-merged-refs.js +1 -1
  47. package/dist/hooks/use-merged-refs.js.map +1 -1
  48. package/dist/hooks/use-presented-instance.d.ts +12 -0
  49. package/dist/hooks/use-presented-instance.d.ts.map +1 -0
  50. package/dist/hooks/use-presented-instance.js +27 -0
  51. package/dist/hooks/use-presented-instance.js.map +1 -0
  52. package/dist/hooks/use-property.d.ts +2 -1
  53. package/dist/hooks/use-property.d.ts.map +1 -1
  54. package/dist/hooks/use-property.js.map +1 -1
  55. package/dist/hooks/use-signal.d.ts +2 -1
  56. package/dist/hooks/use-signal.d.ts.map +1 -1
  57. package/dist/hooks/use-signal.js.map +1 -1
  58. package/dist/internal.d.ts +5 -3
  59. package/dist/internal.d.ts.map +1 -1
  60. package/dist/internal.js +5 -2
  61. package/dist/internal.js.map +1 -1
  62. package/dist/prop-types.d.ts +29 -5
  63. package/dist/prop-types.d.ts.map +1 -1
  64. package/dist/prop-types.js.map +1 -1
  65. package/dist/reconciler/apply-props.d.ts +4 -4
  66. package/dist/reconciler/apply-props.d.ts.map +1 -1
  67. package/dist/reconciler/apply-props.js +62 -20
  68. package/dist/reconciler/apply-props.js.map +1 -1
  69. package/dist/reconciler/behaviors.d.ts +9 -5
  70. package/dist/reconciler/behaviors.d.ts.map +1 -1
  71. package/dist/reconciler/behaviors.js +40 -22
  72. package/dist/reconciler/behaviors.js.map +1 -1
  73. package/dist/reconciler/devtools.d.ts +6 -0
  74. package/dist/reconciler/devtools.d.ts.map +1 -0
  75. package/dist/reconciler/devtools.js +11 -0
  76. package/dist/reconciler/devtools.js.map +1 -0
  77. package/dist/reconciler/host-config.d.ts.map +1 -1
  78. package/dist/reconciler/host-config.js +18 -25
  79. package/dist/reconciler/host-config.js.map +1 -1
  80. package/dist/reconciler/instance.d.ts.map +1 -1
  81. package/dist/reconciler/instance.js +5 -3
  82. package/dist/reconciler/instance.js.map +1 -1
  83. package/dist/reconciler/metadata.d.ts +1 -1
  84. package/dist/reconciler/metadata.d.ts.map +1 -1
  85. package/dist/reconciler/metadata.js +2 -4
  86. package/dist/reconciler/metadata.js.map +1 -1
  87. package/dist/reconciler/node.d.ts +3 -2
  88. package/dist/reconciler/node.d.ts.map +1 -1
  89. package/dist/reconciler/node.js +7 -6
  90. package/dist/reconciler/node.js.map +1 -1
  91. package/dist/reconciler/placement.js +20 -20
  92. package/dist/reconciler/placement.js.map +1 -1
  93. package/dist/reconciler/registry.d.ts +9 -7
  94. package/dist/reconciler/registry.d.ts.map +1 -1
  95. package/dist/reconciler/registry.js.map +1 -1
  96. package/dist/reconciler/root-element.d.ts +1 -0
  97. package/dist/reconciler/root-element.d.ts.map +1 -1
  98. package/dist/reconciler/root-element.js.map +1 -1
  99. package/dist/reconciler/root.d.ts +17 -3
  100. package/dist/reconciler/root.d.ts.map +1 -1
  101. package/dist/reconciler/root.js +22 -8
  102. package/dist/reconciler/root.js.map +1 -1
  103. package/dist/reconciler/signals.d.ts +1 -2
  104. package/dist/reconciler/signals.d.ts.map +1 -1
  105. package/dist/reconciler/signals.js +1 -1
  106. package/dist/reconciler/signals.js.map +1 -1
  107. package/dist/utils/accessible-props.d.ts +8 -8
  108. package/dist/utils/accessible-props.d.ts.map +1 -1
  109. package/dist/utils/accessible-props.js +11 -13
  110. package/dist/utils/accessible-props.js.map +1 -1
  111. package/dist/utils/settings.d.ts +2 -1
  112. package/dist/utils/settings.d.ts.map +1 -1
  113. package/dist/utils/settings.js.map +1 -1
  114. package/dist/utils/variant.d.ts +33 -1
  115. package/dist/utils/variant.d.ts.map +1 -1
  116. package/dist/utils/variant.js.map +1 -1
  117. package/package.json +14 -4
  118. package/src/adw/dialog.tsx +25 -25
  119. package/src/adw/element-behaviors.ts +129 -57
  120. package/src/adw/element-config.ts +15 -17
  121. package/src/adw/prop-types.ts +35 -2
  122. package/src/components/application-window.tsx +10 -0
  123. package/src/components/application.tsx +45 -6
  124. package/src/components/element.tsx +52 -16
  125. package/src/components/portaled.tsx +14 -0
  126. package/src/components/window.tsx +24 -23
  127. package/src/config.ts +10 -1
  128. package/src/element-behaviors.ts +20 -0
  129. package/src/element-config.ts +7 -4
  130. package/src/hooks/use-merged-refs.ts +1 -1
  131. package/src/hooks/use-presented-instance.tsx +50 -0
  132. package/src/hooks/use-property.ts +2 -1
  133. package/src/hooks/use-signal.ts +2 -1
  134. package/src/internal.ts +5 -3
  135. package/src/prop-types.ts +34 -7
  136. package/src/reconciler/apply-props.ts +77 -24
  137. package/src/reconciler/behaviors.ts +72 -30
  138. package/src/reconciler/devtools.ts +18 -0
  139. package/src/reconciler/host-config.ts +20 -30
  140. package/src/reconciler/instance.ts +6 -3
  141. package/src/reconciler/metadata.ts +3 -6
  142. package/src/reconciler/node.ts +10 -7
  143. package/src/reconciler/placement.ts +25 -25
  144. package/src/reconciler/registry.ts +9 -7
  145. package/src/reconciler/root-element.ts +4 -1
  146. package/src/reconciler/root.ts +38 -12
  147. package/src/reconciler/signals.ts +0 -1
  148. package/src/utils/accessible-props.ts +21 -24
  149. package/src/utils/settings.ts +2 -1
  150. package/src/utils/variant.ts +33 -1
  151. package/dist/utils/accessible-metadata.d.ts +0 -5
  152. package/dist/utils/accessible-metadata.d.ts.map +0 -1
  153. package/dist/utils/accessible-metadata.js +0 -18
  154. package/dist/utils/accessible-metadata.js.map +0 -1
  155. package/src/utils/accessible-metadata.ts +0 -24
@@ -4,21 +4,23 @@ import type { AlertDialogResponse } from "./prop-types.js";
4
4
  import {
5
5
  addRemoveSlot,
6
6
  adoptedChildrenSlot,
7
+ applicationCreator,
7
8
  boxSlot,
9
+ childMatcher,
8
10
  childSetterSlot,
9
11
  contentSetterSlot,
10
12
  deferred,
13
+ indexedSlot,
11
14
  list,
12
15
  slot,
13
16
  } from "../reconciler/behaviors.js";
14
- import { type ElementConfig, forTypes, registerElements } from "../reconciler/registry.js";
17
+ import { type ElementBehavior, type ElementConfig, forTypes, registerElements } from "../reconciler/registry.js";
15
18
  import { BUILTIN_ELEMENTS, CHILD_SETTER_TYPES, CONTENT_SETTER_TYPES } from "./element-config.js";
16
19
 
17
20
  type AdwChildSetter =
18
21
  | Adw.Bin |
19
22
  Adw.BreakpointBin |
20
23
  Adw.Clamp |
21
- Adw.ClampScrollable |
22
24
  Adw.Dialog |
23
25
  Adw.NavigationPage |
24
26
  Adw.SplitButton |
@@ -27,11 +29,11 @@ type AdwChildSetter =
27
29
  Adw.ToastOverlay |
28
30
  Adw.Toggle;
29
31
 
30
- type AdwContentSetter = Adw.ApplicationWindow | Adw.BottomSheet | Adw.Flap | Adw.OverlaySplitView | Adw.Window;
32
+ type AdwContentSetter = Adw.ApplicationWindow | Adw.BottomSheet | Adw.OverlaySplitView | Adw.Window;
31
33
  type BreakpointHost = Adw.ApplicationWindow | Adw.Window | Adw.Dialog;
32
- type PageHost = Adw.PreferencesDialog | Adw.PreferencesWindow;
33
34
  type PrefixSuffixRow = Adw.ActionRow | Adw.EntryRow | Adw.ExpanderRow;
34
35
 
36
+ const SLOT_SUFFIX = "Slot";
35
37
  const childSetter = childSetterSlot<AdwChildSetter>();
36
38
  const contentSetter = contentSetterSlot<AdwContentSetter>();
37
39
 
@@ -42,36 +44,123 @@ const breakpoints = slot<BreakpointHost, Adw.Breakpoint>("breakpoints", "AdwBrea
42
44
  });
43
45
 
44
46
  const prefixSuffix = [
45
- slot<PrefixSuffixRow, Gtk.Widget>("prefix", "GtkWidget", {
46
- attach: (row, child) => {
47
+ addRemoveSlot<Gtk.Widget, PrefixSuffixRow>(
48
+ "prefix",
49
+ "GtkWidget",
50
+ (row, child) => {
47
51
  row.addPrefix(child);
48
52
  },
49
- detach: (row, child) => {
53
+ (row, child) => {
50
54
  row.remove(child);
51
55
  },
52
- }),
53
- slot<PrefixSuffixRow, Gtk.Widget>("suffix", "GtkWidget", {
54
- attach: (row, child) => {
56
+ ),
57
+ addRemoveSlot<Gtk.Widget, PrefixSuffixRow>(
58
+ "suffix",
59
+ "GtkWidget",
60
+ (row, child) => {
55
61
  row.addSuffix(child);
56
62
  },
57
- detach: (row, child) => {
63
+ (row, child) => {
58
64
  row.remove(child);
59
65
  },
60
- }),
66
+ ),
61
67
  ];
62
68
 
63
- const pageHostChildren = addRemoveSlot<Adw.PreferencesPage, PageHost>(
69
+ const preferencesDialogChildren = addRemoveSlot<Adw.PreferencesPage, Adw.PreferencesDialog>(
64
70
  "children",
65
71
  "AdwPreferencesPage",
66
- (host, page) => {
67
- host.add(page);
72
+ (dialog, page) => {
73
+ dialog.add(page);
68
74
  },
69
- (host, page) => {
70
- host.remove(page);
75
+ (dialog, page) => {
76
+ dialog.remove(page);
71
77
  },
72
78
  );
73
79
 
80
+ const alertDialogExtraChild = slot<Adw.AlertDialog, Gtk.Widget>("children", "GtkWidget", {
81
+ attach: (dialog, child) => {
82
+ dialog.setExtraChild(child);
83
+ },
84
+ detach: (dialog) => {
85
+ dialog.setExtraChild(null);
86
+ },
87
+ });
88
+
89
+ const sidebarSections = indexedSlot<Adw.Sidebar, Adw.SidebarSection>("children", "AdwSidebarSection");
90
+ const sidebarItems = indexedSlot<Adw.SidebarSection, Adw.SidebarItem>("children", "AdwSidebarItem");
91
+ const isWidget = childMatcher("GtkWidget");
92
+
93
+ const multiLayoutSlots: ElementBehavior<Adw.MultiLayoutView> = {
94
+ attach: (view, child, info) => {
95
+ const id = getSlotId(info.slot);
96
+
97
+ if (id === null || !isWidget(child)) {
98
+ return;
99
+ }
100
+
101
+ view.setChild(id, child as Gtk.Widget);
102
+
103
+ return true;
104
+ },
105
+ detach: (view, child, info) => {
106
+ const id = getSlotId(info.slot);
107
+
108
+ if (id === null || !isWidget(child)) {
109
+ return;
110
+ }
111
+
112
+ const widget = child as Gtk.Widget;
113
+
114
+ if (view.getChild(id) === widget) {
115
+ widget.unparent();
116
+ }
117
+ },
118
+ };
119
+
120
+ const multiLayoutLayouts = slot<Adw.MultiLayoutView, Gtk.Widget>("layouts", "GtkWidget", {
121
+ attach: (view, content) => {
122
+ const layout = new Adw.Layout({ content });
123
+ view.addLayout(layout);
124
+
125
+ return layout;
126
+ },
127
+ reorder: (_view, _content, info) => info.adopted,
128
+ detach: (view, _content, info) => {
129
+ if (info.adopted instanceof Adw.Layout) {
130
+ view.removeLayout(info.adopted);
131
+ }
132
+ },
133
+ });
134
+
74
135
  const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
136
+ AdwApplication: {
137
+ behaviors: [applicationCreator(Adw.Application)],
138
+ },
139
+ AdwSidebar: {
140
+ behaviors: [sidebarSections],
141
+ },
142
+ AdwSidebarSection: {
143
+ behaviors: [sidebarItems],
144
+ },
145
+ AdwMultiLayoutView: {
146
+ behaviors: [
147
+ multiLayoutLayouts,
148
+ multiLayoutSlots,
149
+ deferred<Adw.MultiLayoutView, string>("layoutName", (view, name) => view.getLayoutByName(name) !== null),
150
+ ],
151
+ },
152
+ AdwClampScrollable: {
153
+ behaviors: [
154
+ slot<Adw.ClampScrollable, Gtk.Scrollable & Gtk.Widget>("children", "GtkScrollable", {
155
+ attach: (clamp, child) => {
156
+ clamp.setChild(child);
157
+ },
158
+ detach: (clamp) => {
159
+ clamp.setChild(null);
160
+ },
161
+ }),
162
+ ],
163
+ },
75
164
  ...forTypes(CHILD_SETTER_TYPES, {
76
165
  behaviors: [childSetter],
77
166
  }),
@@ -121,26 +210,13 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
121
210
  row.remove(child);
122
211
  },
123
212
  ),
124
- addRemoveSlot<Gtk.Widget, Adw.ExpanderRow>(
125
- "actions",
126
- "GtkWidget",
127
- (row, child) => {
128
- row.addAction(child);
129
- },
130
- (row, child) => {
131
- row.remove(child);
132
- },
133
- ),
134
213
  ],
135
214
  },
136
215
  AdwNavigationSplitView: {
137
216
  behaviors: [contentSetterSlot<Adw.NavigationSplitView, Adw.NavigationPage>("AdwNavigationPage")],
138
217
  },
139
- AdwLeaflet: {
140
- behaviors: [boxSlot<Adw.Leaflet | Adw.WrapBox>()],
141
- },
142
218
  AdwWrapBox: {
143
- behaviors: [boxSlot<Adw.Leaflet | Adw.WrapBox>()],
219
+ behaviors: [boxSlot<Adw.WrapBox>()],
144
220
  },
145
221
  AdwCarousel: {
146
222
  behaviors: [
@@ -170,10 +246,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
170
246
  ],
171
247
  },
172
248
  AdwPreferencesDialog: {
173
- behaviors: [pageHostChildren],
174
- },
175
- AdwPreferencesWindow: {
176
- behaviors: [pageHostChildren],
249
+ behaviors: [preferencesDialogChildren],
177
250
  },
178
251
  AdwPreferencesGroup: {
179
252
  behaviors: [
@@ -189,20 +262,6 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
189
262
  ),
190
263
  ],
191
264
  },
192
- AdwSqueezer: {
193
- behaviors: [
194
- addRemoveSlot<Gtk.Widget, Adw.Squeezer>(
195
- "children",
196
- "GtkWidget",
197
- (squeezer, child) => {
198
- squeezer.add(child);
199
- },
200
- (squeezer, child) => {
201
- squeezer.remove(child);
202
- },
203
- ),
204
- ],
205
- },
206
265
  AdwTabView: {
207
266
  behaviors: [
208
267
  slot<Adw.TabView, Gtk.Widget>("children", "GtkWidget", {
@@ -250,22 +309,26 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
250
309
  AdwToolbarView: {
251
310
  behaviors: [
252
311
  contentSetterSlot<Adw.ToolbarView>(),
253
- slot<Adw.ToolbarView, Gtk.Widget>("topBar", "GtkWidget", {
254
- attach: (view, child) => {
312
+ addRemoveSlot<Gtk.Widget, Adw.ToolbarView>(
313
+ "topBar",
314
+ "GtkWidget",
315
+ (view, child) => {
255
316
  view.addTopBar(child);
256
317
  },
257
- detach: (view, child) => {
318
+ (view, child) => {
258
319
  view.remove(child);
259
320
  },
260
- }),
261
- slot<Adw.ToolbarView, Gtk.Widget>("bottomBar", "GtkWidget", {
262
- attach: (view, child) => {
321
+ ),
322
+ addRemoveSlot<Gtk.Widget, Adw.ToolbarView>(
323
+ "bottomBar",
324
+ "GtkWidget",
325
+ (view, child) => {
263
326
  view.addBottomBar(child);
264
327
  },
265
- detach: (view, child) => {
328
+ (view, child) => {
266
329
  view.remove(child);
267
330
  },
268
- }),
331
+ ),
269
332
  ],
270
333
  },
271
334
  AdwHeaderBar: {
@@ -324,6 +387,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
324
387
  },
325
388
  AdwAlertDialog: {
326
389
  behaviors: [
390
+ alertDialogExtraChild,
327
391
  list<Adw.AlertDialog, AlertDialogResponse>("responses", {
328
392
  add: (dialog, response) => {
329
393
  dialog.addResponse(response.id, response.label);
@@ -344,5 +408,13 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
344
408
  },
345
409
  };
346
410
 
411
+ function getSlotId(name: string): string | null {
412
+ if (name.length <= SLOT_SUFFIX.length || !name.endsWith(SLOT_SUFFIX)) {
413
+ return null;
414
+ }
415
+
416
+ return name.slice(0, -SLOT_SUFFIX.length);
417
+ }
418
+
347
419
  registerElements(BUILTIN_ELEMENTS);
348
420
  registerElements(BUILTIN_BEHAVIORS);
@@ -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,17 +11,11 @@ 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 framework's own element configuration for the Adwaita types it customizes: the base props interface each
22
- * generated element extends, the component that wraps it, the GObject properties left out of its generated
23
- * props, and whether its GObject is created by its parent. Carries no behaviors, so importing it never
24
- * reaches the GObject bindings.
25
- */
26
19
  const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
27
20
  ...forTypes(CHILD_SETTER_TYPES, {
28
21
  props: childrenProps,
@@ -32,12 +25,25 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
32
25
  props: childrenProps,
33
26
  omittedProps: ["content"],
34
27
  }),
28
+ AdwClampScrollable: {
29
+ props: childrenProps,
30
+ omittedProps: ["child"],
31
+ },
35
32
  AdwViewStackPage: {
36
33
  isLazy: true,
37
34
  },
38
35
  AdwTabPage: {
39
36
  isLazy: true,
40
37
  },
38
+ AdwLayout: {
39
+ isLazy: true,
40
+ },
41
+ AdwSidebarSection: {
42
+ props: childrenProps,
43
+ },
44
+ AdwMultiLayoutView: {
45
+ props: adw("AdwMultiLayoutViewProps"),
46
+ },
41
47
  AdwPreferencesRow: {
42
48
  props: preferencesRowProps,
43
49
  },
@@ -71,9 +77,6 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
71
77
  props: childrenProps,
72
78
  omittedProps: ["content"],
73
79
  },
74
- AdwLeaflet: {
75
- props: childrenProps,
76
- },
77
80
  AdwWrapBox: {
78
81
  props: childrenProps,
79
82
  },
@@ -86,15 +89,9 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
86
89
  AdwPreferencesDialog: {
87
90
  props: childrenProps,
88
91
  },
89
- AdwPreferencesWindow: {
90
- props: childrenProps,
91
- },
92
92
  AdwPreferencesGroup: {
93
93
  props: childrenProps,
94
94
  },
95
- AdwSqueezer: {
96
- props: childrenProps,
97
- },
98
95
  AdwTabView: {
99
96
  props: childrenProps,
100
97
  },
@@ -122,6 +119,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
122
119
  },
123
120
  AdwAlertDialog: {
124
121
  props: adw("AdwAlertDialogProps"),
122
+ omittedProps: ["extraChild"],
125
123
  },
126
124
  };
127
125
 
@@ -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;
13
+ /** Whether the button can be activated; defaults to true. */
9
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
  };
@@ -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
- import { pickBy } from "@gtkx/utils";
3
+ import { pickBy, warn } 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,57 @@ 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 reportOwnedApplicationId = (application: Gtk.Application): void => {
25
+ if (!application.getIsRegistered() || !application.getIsRemote()) {
26
+ return;
27
+ }
28
+
29
+ warn(
30
+ `Another process already owns ${application.applicationId ?? "this application ID"}, so this process ` +
31
+ "registered as a remote instance and can never show a window. Quit that instance or change " +
32
+ "applicationId, then start this application again.",
33
+ );
34
+ };
35
+
36
+ const startApplication = (
37
+ application: Gtk.Application,
38
+ setActivated: (isActivated: boolean) => void,
39
+ applicationId: string | null,
40
+ ): void => {
41
+ application.on("activate", () => {
42
+ setActivated(true);
43
+ });
44
+
45
+ const { exitStatus } = runApplication(application, commandLine(applicationId));
46
+ reportOwnedApplicationId(application);
47
+
48
+ if (exitStatus !== 0) {
49
+ process.exitCode = exitStatus;
50
+ }
51
+ };
52
+
17
53
  const useApplicationLifecycle = (
18
54
  application: Gtk.Application | null,
19
55
  setActivated: (isActivated: boolean) => void,
56
+ applicationId: string | null,
20
57
  ): void => {
21
58
  useLayoutEffect(() => {
22
59
  if (!application) {
23
60
  return;
24
61
  }
25
62
 
26
- runApplication(application);
27
- setActivated(true);
63
+ startApplication(application, setActivated, applicationId);
28
64
 
29
65
  return () => {
30
66
  quitApplication(application);
31
67
  setActivated(false);
32
68
  };
33
- }, [application, setActivated]);
69
+ }, [application, setActivated, applicationId]);
34
70
  };
35
71
 
36
72
  const applicationChildren = (application: Gtk.Application | null, children: ReactNode): ReactNode => {
@@ -41,13 +77,13 @@ const applicationChildren = (application: Gtk.Application | null, children: Reac
41
77
  return <ApplicationContext.Provider value={application}>{children}</ApplicationContext.Provider>;
42
78
  };
43
79
 
44
- const createApplicationComponent = (
80
+ const createApplicationElement = (
45
81
  Component: ElementType,
46
82
  ): ((props: ApplicationComponentProps) => ReactNode) => {
47
83
  return ({ applicationId = defaultApplicationId, children, ref, ...rest }: ApplicationComponentProps): ReactNode => {
48
84
  const [application, setApplication] = useState<Gtk.Application | null>(null);
49
85
  const [activated, setActivated] = useState(false);
50
- useApplicationLifecycle(application, setActivated);
86
+ useApplicationLifecycle(application, setActivated, applicationId);
51
87
  const mergedRef = useMergedRef(ref, setApplication);
52
88
  const appliedProps = activated ? rest : pickBy(rest, (_value, key) => !POST_ACTIVATE_PROPS.has(key));
53
89
 
@@ -59,5 +95,8 @@ const createApplicationComponent = (
59
95
  };
60
96
  };
61
97
 
98
+ const createApplicationComponent = (Component: ElementType): ((props: unknown) => ReactNode) =>
99
+ createPortaledComponent(createApplicationElement(Component));
100
+
62
101
  /** @internal */
63
102
  export { createApplicationComponent };
@@ -3,44 +3,80 @@ import { createElement, type ElementType, isValidElement, type ReactElement, typ
3
3
  import type { Props } from "../reconciler/registry.js";
4
4
 
5
5
  const Prop = "gtkx:prop";
6
+ const NO_PROP_CHILDREN: ReactNode[] = [];
6
7
 
7
8
  const hasElement = (value: unknown): boolean =>
8
9
  isValidElement(value) || (Array.isArray(value) && value.some((item: unknown) => hasElement(item)));
9
10
 
10
- const routeProp = (key: string, value: unknown, hostProps: Props, propChildren: ReactNode[]): void => {
11
- 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
+
12
24
  propChildren.push(
13
- createElement(Prop, { propName: key, key: `${Prop}:${key}` }, value as ReactNode),
25
+ createElement(Prop, { propName: key, key: `${Prop}:${key}` }, record[key] as ReactNode),
14
26
  );
15
- } else {
16
- hostProps[key] = value;
17
27
  }
28
+
29
+ return propChildren;
18
30
  };
19
31
 
20
- const buildElement = (typeName: string, record: Props): ReactElement => {
21
- const Host = typeName as ElementType;
32
+ const hostPropsWithout = (record: Props): Props => {
22
33
  const hostProps: Props = {};
23
- const propChildren: ReactNode[] = [];
24
34
 
25
35
  for (const key in record) {
26
- if (key === "children") {
27
- continue;
36
+ if (key !== "children" && !isRoutedProp(key, record[key])) {
37
+ hostProps[key] = record[key];
28
38
  }
39
+ }
40
+
41
+ return hostProps;
42
+ };
29
43
 
30
- 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
+ );
31
55
  }
32
56
 
33
57
  return (
34
- <Host {...hostProps}>
58
+ <Host {...hostPropsWithout(record)}>
35
59
  {propChildren}
36
60
  {record.children as ReactNode}
37
61
  </Host>
38
62
  );
39
63
  };
40
64
 
41
- const createElementComponent =
42
- (typeName: string): ((props: unknown) => ReactNode) =>
43
- (props: unknown): ReactNode =>
44
- 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
+ }
45
81
 
46
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 };