@gtkx/react 0.20.0 → 0.21.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 (157) hide show
  1. package/README.md +26 -62
  2. package/dist/components/compound.d.ts +40 -0
  3. package/dist/components/compound.d.ts.map +1 -0
  4. package/dist/components/compound.js +46 -0
  5. package/dist/components/compound.js.map +1 -0
  6. package/dist/components/list.d.ts +42 -2
  7. package/dist/components/list.d.ts.map +1 -1
  8. package/dist/components/list.js +42 -1
  9. package/dist/components/list.js.map +1 -1
  10. package/dist/components/slot-widget.d.ts +15 -0
  11. package/dist/components/slot-widget.d.ts.map +1 -0
  12. package/dist/components/slot-widget.js +37 -0
  13. package/dist/components/slot-widget.js.map +1 -0
  14. package/dist/errors.d.ts +6 -0
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +8 -6
  17. package/dist/errors.js.map +1 -1
  18. package/dist/generated/compounds.d.ts +2672 -0
  19. package/dist/generated/compounds.d.ts.map +1 -0
  20. package/dist/generated/compounds.js +2624 -0
  21. package/dist/generated/compounds.js.map +1 -0
  22. package/dist/generated/internal.d.ts +2 -2
  23. package/dist/generated/internal.d.ts.map +1 -1
  24. package/dist/generated/internal.js +2751 -4748
  25. package/dist/generated/internal.js.map +1 -1
  26. package/dist/generated/jsx.d.ts +2042 -4592
  27. package/dist/generated/jsx.d.ts.map +1 -1
  28. package/dist/generated/jsx.js +919 -3478
  29. package/dist/generated/jsx.js.map +1 -1
  30. package/dist/generated/registry.d.ts +1 -0
  31. package/dist/generated/registry.d.ts.map +1 -1
  32. package/dist/generated/registry.js +0 -1
  33. package/dist/generated/registry.js.map +1 -1
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/jsx.d.ts +116 -414
  39. package/dist/jsx.d.ts.map +1 -1
  40. package/dist/jsx.js +5 -328
  41. package/dist/jsx.js.map +1 -1
  42. package/dist/metadata.d.ts +1 -1
  43. package/dist/metadata.d.ts.map +1 -1
  44. package/dist/metadata.js.map +1 -1
  45. package/dist/nodes/alert-dialog.d.ts +14 -0
  46. package/dist/nodes/alert-dialog.d.ts.map +1 -0
  47. package/dist/nodes/alert-dialog.js +41 -0
  48. package/dist/nodes/alert-dialog.js.map +1 -0
  49. package/dist/nodes/animation.d.ts +5 -4
  50. package/dist/nodes/animation.d.ts.map +1 -1
  51. package/dist/nodes/animation.js +65 -49
  52. package/dist/nodes/animation.js.map +1 -1
  53. package/dist/nodes/column-view-column.js +2 -2
  54. package/dist/nodes/column-view-column.js.map +1 -1
  55. package/dist/nodes/container-slot.d.ts +3 -1
  56. package/dist/nodes/container-slot.d.ts.map +1 -1
  57. package/dist/nodes/container-slot.js +28 -16
  58. package/dist/nodes/container-slot.js.map +1 -1
  59. package/dist/nodes/drawing-area.d.ts +3 -1
  60. package/dist/nodes/drawing-area.d.ts.map +1 -1
  61. package/dist/nodes/drawing-area.js +20 -22
  62. package/dist/nodes/drawing-area.js.map +1 -1
  63. package/dist/nodes/event-controller.d.ts.map +1 -1
  64. package/dist/nodes/event-controller.js +3 -7
  65. package/dist/nodes/event-controller.js.map +1 -1
  66. package/dist/nodes/fixed-child.d.ts +1 -0
  67. package/dist/nodes/fixed-child.d.ts.map +1 -1
  68. package/dist/nodes/fixed-child.js +13 -0
  69. package/dist/nodes/fixed-child.js.map +1 -1
  70. package/dist/nodes/grid-child.d.ts +1 -0
  71. package/dist/nodes/grid-child.d.ts.map +1 -1
  72. package/dist/nodes/grid-child.js +13 -0
  73. package/dist/nodes/grid-child.js.map +1 -1
  74. package/dist/nodes/internal/construct.js +2 -2
  75. package/dist/nodes/internal/construct.js.map +1 -1
  76. package/dist/nodes/internal/widget.d.ts.map +1 -1
  77. package/dist/nodes/internal/widget.js +5 -9
  78. package/dist/nodes/internal/widget.js.map +1 -1
  79. package/dist/nodes/list-item-node.d.ts +6 -6
  80. package/dist/nodes/list-item-node.d.ts.map +1 -1
  81. package/dist/nodes/list-item-node.js +27 -5
  82. package/dist/nodes/list-item-node.js.map +1 -1
  83. package/dist/nodes/notebook-page.js +2 -2
  84. package/dist/nodes/notebook-page.js.map +1 -1
  85. package/dist/nodes/overlay-child.d.ts +2 -0
  86. package/dist/nodes/overlay-child.d.ts.map +1 -1
  87. package/dist/nodes/overlay-child.js +29 -8
  88. package/dist/nodes/overlay-child.js.map +1 -1
  89. package/dist/nodes/spin-row.d.ts +14 -0
  90. package/dist/nodes/spin-row.d.ts.map +1 -0
  91. package/dist/nodes/spin-row.js +46 -0
  92. package/dist/nodes/spin-row.js.map +1 -0
  93. package/dist/nodes/switch-row.d.ts +11 -0
  94. package/dist/nodes/switch-row.d.ts.map +1 -0
  95. package/dist/nodes/switch-row.js +15 -0
  96. package/dist/nodes/switch-row.js.map +1 -0
  97. package/dist/nodes/text-anchor.d.ts.map +1 -1
  98. package/dist/nodes/text-anchor.js +10 -0
  99. package/dist/nodes/text-anchor.js.map +1 -1
  100. package/dist/nodes/text-tag.d.ts.map +1 -1
  101. package/dist/nodes/text-tag.js +45 -39
  102. package/dist/nodes/text-tag.js.map +1 -1
  103. package/dist/nodes/toggle-group.d.ts +12 -6
  104. package/dist/nodes/toggle-group.d.ts.map +1 -1
  105. package/dist/nodes/toggle-group.js +53 -4
  106. package/dist/nodes/toggle-group.js.map +1 -1
  107. package/dist/nodes/widget.d.ts.map +1 -1
  108. package/dist/nodes/widget.js +7 -14
  109. package/dist/nodes/widget.js.map +1 -1
  110. package/dist/registry.d.ts.map +1 -1
  111. package/dist/registry.js +7 -5
  112. package/dist/registry.js.map +1 -1
  113. package/dist/types.d.ts +1 -0
  114. package/dist/types.d.ts.map +1 -1
  115. package/dist/use-property.d.ts +29 -0
  116. package/dist/use-property.d.ts.map +1 -0
  117. package/dist/use-property.js +44 -0
  118. package/dist/use-property.js.map +1 -0
  119. package/dist/use-setting.d.ts +36 -0
  120. package/dist/use-setting.d.ts.map +1 -0
  121. package/dist/use-setting.js +68 -0
  122. package/dist/use-setting.js.map +1 -0
  123. package/package.json +3 -3
  124. package/src/components/compound.tsx +57 -0
  125. package/src/components/list.tsx +59 -2
  126. package/src/components/slot-widget.tsx +46 -0
  127. package/src/errors.ts +8 -7
  128. package/src/generated/compounds.ts +2741 -0
  129. package/src/generated/internal.ts +2752 -4754
  130. package/src/generated/jsx.ts +2495 -5012
  131. package/src/generated/registry.ts +2 -1
  132. package/src/index.ts +2 -0
  133. package/src/jsx.ts +121 -443
  134. package/src/metadata.ts +1 -1
  135. package/src/nodes/alert-dialog.ts +55 -0
  136. package/src/nodes/animation.ts +67 -60
  137. package/src/nodes/column-view-column.ts +2 -2
  138. package/src/nodes/container-slot.ts +30 -17
  139. package/src/nodes/drawing-area.ts +23 -32
  140. package/src/nodes/event-controller.ts +3 -7
  141. package/src/nodes/fixed-child.ts +13 -0
  142. package/src/nodes/grid-child.ts +13 -0
  143. package/src/nodes/internal/construct.ts +2 -2
  144. package/src/nodes/internal/widget.ts +6 -12
  145. package/src/nodes/list-item-node.ts +33 -9
  146. package/src/nodes/notebook-page.ts +2 -2
  147. package/src/nodes/overlay-child.ts +30 -9
  148. package/src/nodes/spin-row.ts +72 -0
  149. package/src/nodes/switch-row.ts +26 -0
  150. package/src/nodes/text-anchor.ts +9 -0
  151. package/src/nodes/text-tag.ts +45 -40
  152. package/src/nodes/toggle-group.ts +63 -9
  153. package/src/nodes/widget.ts +6 -13
  154. package/src/registry.ts +7 -5
  155. package/src/types.ts +1 -0
  156. package/src/use-property.ts +58 -0
  157. package/src/use-setting.ts +96 -0
package/src/metadata.ts CHANGED
@@ -27,7 +27,7 @@ const walkPrototypeChain = <T>(instance: Container, lookup: (typeName: string) =
27
27
  return null;
28
28
  };
29
29
 
30
- export const resolvePropMeta = (instance: Container, key: string): [string | null, string] | null =>
30
+ export const resolvePropMeta = (instance: Container, key: string): string | null =>
31
31
  walkPrototypeChain(instance, (typeName) => PROPS[typeName]?.[key] ?? null);
32
32
 
33
33
  export const isConstructOnlyProp = (instance: Container, key: string): boolean =>
@@ -0,0 +1,55 @@
1
+ import type * as Adw from "@gtkx/ffi/adw";
2
+ import type { AdwAlertDialogProps, AlertDialogResponseProps } from "../jsx.js";
3
+ import { DialogNode } from "./dialog.js";
4
+ import { filterProps, hasChanged } from "./internal/props.js";
5
+
6
+ const OWN_PROPS = ["responses"] as const;
7
+
8
+ type Props = Pick<AdwAlertDialogProps, (typeof OWN_PROPS)[number]>;
9
+
10
+ export class AlertDialogNode extends DialogNode {
11
+ private managedResponseIds: string[] = [];
12
+
13
+ public override commitUpdate(oldProps: Props | null, newProps: Props): void {
14
+ super.commitUpdate(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
15
+ this.applyOwnProps(oldProps, newProps);
16
+ }
17
+
18
+ public override detachDeletedInstance(): void {
19
+ this.clearResponses();
20
+ super.detachDeletedInstance();
21
+ }
22
+
23
+ private applyOwnProps(oldProps: Props | null, newProps: Props): void {
24
+ if (hasChanged(oldProps, newProps, "responses")) {
25
+ this.syncResponses(newProps.responses ?? []);
26
+ }
27
+ }
28
+
29
+ private syncResponses(newResponses: AlertDialogResponseProps[]): void {
30
+ const dialog = this.container as Adw.AlertDialog;
31
+ this.clearResponses();
32
+
33
+ for (const response of newResponses) {
34
+ dialog.addResponse(response.id, response.label);
35
+
36
+ if (response.appearance !== undefined) {
37
+ dialog.setResponseAppearance(response.id, response.appearance);
38
+ }
39
+
40
+ if (response.enabled !== undefined) {
41
+ dialog.setResponseEnabled(response.id, response.enabled);
42
+ }
43
+
44
+ this.managedResponseIds.push(response.id);
45
+ }
46
+ }
47
+
48
+ private clearResponses(): void {
49
+ const dialog = this.container as Adw.AlertDialog;
50
+ for (const id of this.managedResponseIds) {
51
+ dialog.removeResponse(id);
52
+ }
53
+ this.managedResponseIds = [];
54
+ }
55
+ }
@@ -1,13 +1,19 @@
1
1
  import * as Adw from "@gtkx/ffi/adw";
2
2
  import * as Gdk from "@gtkx/ffi/gdk";
3
3
  import * as Gtk from "@gtkx/ffi/gtk";
4
- import type { AnimatableProperties, AnimationProps, SpringTransition, TimedTransition } from "../jsx.js";
4
+ import type { AdwSpringAnimationProps, AdwTimedAnimationProps, AnimatableProperties, AnimationProps } from "../jsx.js";
5
5
  import type { Node } from "../node.js";
6
6
  import type { Container } from "../types.js";
7
7
  import { attachChild, detachChild, isAttachedTo } from "./internal/widget.js";
8
8
  import { VirtualNode } from "./virtual.js";
9
9
  import { WidgetNode } from "./widget.js";
10
10
 
11
+ type SetChildContainer = { setChild: (child: Gtk.Widget | null) => void };
12
+
13
+ function hasSetChild(obj: unknown): obj is SetChildContainer {
14
+ return obj !== null && typeof obj === "object" && "setChild" in obj && typeof obj.setChild === "function";
15
+ }
16
+
11
17
  let animationCounter = 0;
12
18
 
13
19
  const DEFAULT_TIMED_DURATION = 300;
@@ -15,14 +21,14 @@ const DEFAULT_SPRING_DAMPING = 1;
15
21
  const DEFAULT_SPRING_MASS = 1;
16
22
  const DEFAULT_SPRING_STIFFNESS = 100;
17
23
 
18
- export class AnimationNode extends VirtualNode<AnimationProps, WidgetNode, WidgetNode> {
24
+ export class AnimationNode extends VirtualNode<AnimationProps, Node, WidgetNode> {
19
25
  private className: string;
20
26
  private provider: Gtk.CssProvider | null = null;
21
27
  private display: Gdk.Display | null = null;
22
28
  private currentAnimation: Adw.Animation | null = null;
23
29
  private currentValues: AnimatableProperties = {};
24
30
  private isExiting = false;
25
- private detachedParentWidget: Gtk.Widget | null = null;
31
+ private detachedParentContainer: unknown = null;
26
32
 
27
33
  constructor(typeName: string, props: AnimationProps, container: undefined, rootContainer: Container) {
28
34
  super(typeName, props, container, rootContainer);
@@ -33,13 +39,13 @@ export class AnimationNode extends VirtualNode<AnimationProps, WidgetNode, Widge
33
39
  return child instanceof WidgetNode;
34
40
  }
35
41
 
36
- public override isValidParent(parent: Node): boolean {
37
- return parent instanceof WidgetNode;
42
+ public override isValidParent(_parent: Node): boolean {
43
+ return true;
38
44
  }
39
45
 
40
- public override setParent(parent: WidgetNode | null): void {
46
+ public override setParent(parent: Node | null): void {
41
47
  if (!parent && this.parent) {
42
- this.detachedParentWidget = this.parent.container;
48
+ this.detachedParentContainer = this.parent.container;
43
49
  }
44
50
 
45
51
  super.setParent(parent);
@@ -118,45 +124,55 @@ export class AnimationNode extends VirtualNode<AnimationProps, WidgetNode, Widge
118
124
  }
119
125
 
120
126
  private onChildChange(oldChild: Gtk.Widget | null): void {
121
- const parentWidget = this.parent?.container ?? null;
127
+ const parentContainer = this.parent?.container ?? null;
122
128
  const childWidget = this.children[0]?.container ?? null;
123
129
 
124
130
  if (oldChild && this.provider) {
125
131
  oldChild.removeCssClass(this.className);
126
132
  }
127
133
 
128
- if (oldChild && parentWidget && isAttachedTo(oldChild, parentWidget)) {
129
- detachChild(oldChild, parentWidget);
134
+ if (oldChild && parentContainer instanceof Gtk.Widget && isAttachedTo(oldChild, parentContainer)) {
135
+ detachChild(oldChild, parentContainer);
136
+ } else if (oldChild && parentContainer && hasSetChild(parentContainer)) {
137
+ parentContainer.setChild(null);
130
138
  }
131
139
 
132
- if (childWidget && parentWidget) {
133
- attachChild(childWidget, parentWidget);
140
+ if (childWidget && parentContainer instanceof Gtk.Widget) {
141
+ attachChild(childWidget, parentContainer);
142
+ this.setupAnimatedChild(childWidget);
143
+ } else if (childWidget && parentContainer && hasSetChild(parentContainer)) {
144
+ parentContainer.setChild(childWidget);
145
+ this.setupAnimatedChild(childWidget);
146
+ }
147
+ }
134
148
 
135
- this.setupCssProvider();
136
- childWidget.addCssClass(this.className);
149
+ private setupAnimatedChild(childWidget: Gtk.Widget): void {
150
+ this.setupCssProvider();
151
+ childWidget.addCssClass(this.className);
137
152
 
138
- const initial = this.props.initial;
139
- const animate = this.props.animate;
153
+ const initial = this.props.initial;
154
+ const animate = this.props.animate;
140
155
 
141
- if (initial === false || !this.props.animateOnMount) {
142
- if (animate) {
143
- this.currentValues = { ...animate };
144
- this.applyValues(this.currentValues);
145
- }
146
- } else {
147
- const initialValues = initial ?? animate ?? {};
148
- this.currentValues = { ...initialValues };
156
+ if (initial === false || !this.props.animateOnMount) {
157
+ if (animate) {
158
+ this.currentValues = { ...animate };
149
159
  this.applyValues(this.currentValues);
150
160
  }
161
+ } else {
162
+ const initialValues = initial ?? animate ?? {};
163
+ this.currentValues = { ...initialValues };
164
+ this.applyValues(this.currentValues);
151
165
  }
152
166
  }
153
167
 
154
168
  private detachChildFromParentWidget(): void {
155
- const parentWidget = this.parent?.container ?? this.detachedParentWidget;
169
+ const parentContainer = this.parent?.container ?? this.detachedParentContainer;
156
170
  const childWidget = this.children[0]?.container ?? null;
157
171
 
158
- if (childWidget && parentWidget && isAttachedTo(childWidget, parentWidget)) {
159
- detachChild(childWidget, parentWidget);
172
+ if (childWidget && parentContainer instanceof Gtk.Widget && isAttachedTo(childWidget, parentContainer)) {
173
+ detachChild(childWidget, parentContainer);
174
+ } else if (childWidget && parentContainer && hasSetChild(parentContainer)) {
175
+ parentContainer.setChild(null);
160
176
  }
161
177
  }
162
178
 
@@ -227,8 +243,7 @@ export class AnimationNode extends VirtualNode<AnimationProps, WidgetNode, Widge
227
243
 
228
244
  this.currentAnimation = animation;
229
245
 
230
- const transition = this.props.transition;
231
- const delay = transition?.delay ?? 0;
246
+ const delay = (this.props as AdwTimedAnimationProps | AdwSpringAnimationProps).delay ?? 0;
232
247
 
233
248
  if (delay > 0) {
234
249
  setTimeout(() => {
@@ -242,61 +257,53 @@ export class AnimationNode extends VirtualNode<AnimationProps, WidgetNode, Widge
242
257
  }
243
258
 
244
259
  private createAnimation(widget: Gtk.Widget, target: Adw.CallbackAnimationTarget): Adw.Animation {
245
- const transition = this.props.transition;
246
-
247
- if (transition?.mode === "spring") {
248
- return this.createSpringAnimation(widget, target, transition);
260
+ if (this.typeName === "AdwSpringAnimation") {
261
+ return this.createSpringAnimation(widget, target);
249
262
  }
250
263
 
251
- return this.createTimedAnimation(widget, target, transition);
264
+ return this.createTimedAnimation(widget, target);
252
265
  }
253
266
 
254
- private createTimedAnimation(
255
- widget: Gtk.Widget,
256
- target: Adw.CallbackAnimationTarget,
257
- transition: TimedTransition | undefined,
258
- ): Adw.TimedAnimation {
259
- const duration = transition?.duration ?? DEFAULT_TIMED_DURATION;
267
+ private createTimedAnimation(widget: Gtk.Widget, target: Adw.CallbackAnimationTarget): Adw.TimedAnimation {
268
+ const props = this.props as AdwTimedAnimationProps;
269
+ const duration = props.duration ?? DEFAULT_TIMED_DURATION;
260
270
 
261
271
  const animation = new Adw.TimedAnimation(widget, 0, 1, duration, target);
262
272
 
263
- if (transition?.easing !== undefined) {
264
- animation.setEasing(transition.easing);
273
+ if (props.easing !== undefined) {
274
+ animation.setEasing(props.easing);
265
275
  }
266
276
 
267
- if (transition?.repeat !== undefined) {
268
- animation.setRepeatCount(transition.repeat);
277
+ if (props.repeat !== undefined) {
278
+ animation.setRepeatCount(props.repeat);
269
279
  }
270
280
 
271
- if (transition?.reverse !== undefined) {
272
- animation.setReverse(transition.reverse);
281
+ if (props.reverse !== undefined) {
282
+ animation.setReverse(props.reverse);
273
283
  }
274
284
 
275
- if (transition?.alternate !== undefined) {
276
- animation.setAlternate(transition.alternate);
285
+ if (props.alternate !== undefined) {
286
+ animation.setAlternate(props.alternate);
277
287
  }
278
288
 
279
289
  return animation;
280
290
  }
281
291
 
282
- private createSpringAnimation(
283
- widget: Gtk.Widget,
284
- target: Adw.CallbackAnimationTarget,
285
- transition: SpringTransition,
286
- ): Adw.SpringAnimation {
287
- const damping = transition.damping ?? DEFAULT_SPRING_DAMPING;
288
- const mass = transition.mass ?? DEFAULT_SPRING_MASS;
289
- const stiffness = transition.stiffness ?? DEFAULT_SPRING_STIFFNESS;
292
+ private createSpringAnimation(widget: Gtk.Widget, target: Adw.CallbackAnimationTarget): Adw.SpringAnimation {
293
+ const props = this.props as AdwSpringAnimationProps;
294
+ const damping = props.damping ?? DEFAULT_SPRING_DAMPING;
295
+ const mass = props.mass ?? DEFAULT_SPRING_MASS;
296
+ const stiffness = props.stiffness ?? DEFAULT_SPRING_STIFFNESS;
290
297
 
291
298
  const springParams = new Adw.SpringParams(damping, mass, stiffness);
292
299
  const animation = new Adw.SpringAnimation(widget, 0, 1, springParams, target);
293
300
 
294
- if (transition.initialVelocity !== undefined) {
295
- animation.setInitialVelocity(transition.initialVelocity);
301
+ if (props.initialVelocity !== undefined) {
302
+ animation.setInitialVelocity(props.initialVelocity);
296
303
  }
297
304
 
298
- if (transition.clamp !== undefined) {
299
- animation.setClamp(transition.clamp);
305
+ if (props.clamp !== undefined) {
306
+ animation.setClamp(props.clamp);
300
307
  }
301
308
 
302
309
  return animation;
@@ -145,7 +145,7 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
145
145
  if (props.resizable !== undefined) this.column.setResizable(props.resizable);
146
146
  if (props.fixedWidth !== undefined) this.column.setFixedWidth(props.fixedWidth);
147
147
  if (props.visible !== undefined) this.column.setVisible(props.visible);
148
- if (props.sortable) this.column.setSorter(new Gtk.Sorter());
148
+ if (props.sortable) this.column.setSorter(new Gtk.CustomSorter());
149
149
  }
150
150
 
151
151
  private applyColumnProps(oldProps: ColumnViewColumnProps, newProps: ColumnViewColumnProps): void {
@@ -157,7 +157,7 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
157
157
  if (hasChanged(oldProps, newProps, "fixedWidth")) this.column.setFixedWidth(newProps.fixedWidth ?? -1);
158
158
  if (hasChanged(oldProps, newProps, "visible")) this.column.setVisible(newProps.visible ?? true);
159
159
  if (hasChanged(oldProps, newProps, "sortable")) {
160
- this.column.setSorter(newProps.sortable ? new Gtk.Sorter() : null);
160
+ this.column.setSorter(newProps.sortable ? new Gtk.CustomSorter() : null);
161
161
  }
162
162
  if (hasChanged(oldProps, newProps, "renderCell")) this.scheduleParentUpdate();
163
163
  }
@@ -16,7 +16,7 @@ export class ContainerSlotNode extends VirtualNode<ContainerSlotProps, WidgetNod
16
16
 
17
17
  public override setParent(parent: WidgetNode | null): void {
18
18
  if (!parent && this.parent) {
19
- this.detachAllChildren(this.parent.container);
19
+ this.detachAllFromGtkParent();
20
20
  }
21
21
 
22
22
  super.setParent(parent);
@@ -32,6 +32,7 @@ export class ContainerSlotNode extends VirtualNode<ContainerSlotProps, WidgetNod
32
32
  super.appendChild(child);
33
33
 
34
34
  if (this.parent) {
35
+ this.detachFromGtkParent(child);
35
36
  this.attachToParent(this.parent.container, child.container);
36
37
  }
37
38
  }
@@ -40,25 +41,18 @@ export class ContainerSlotNode extends VirtualNode<ContainerSlotProps, WidgetNod
40
41
  super.insertBefore(child, before);
41
42
 
42
43
  if (this.parent) {
43
- this.attachToParent(this.parent.container, child.container);
44
+ this.reinsertAllChildren();
44
45
  }
45
46
  }
46
47
 
47
48
  public override removeChild(child: WidgetNode): void {
48
- if (this.parent && isRemovable(this.parent.container)) {
49
- const widget = child.container;
50
- const currentParent = widget.getParent();
51
- if (currentParent && currentParent === this.parent.container) {
52
- this.parent.container.remove(widget);
53
- }
54
- }
55
-
49
+ this.detachFromGtkParent(child);
56
50
  super.removeChild(child);
57
51
  }
58
52
 
59
53
  public override detachDeletedInstance(): void {
60
54
  if (this.parent) {
61
- this.detachAllChildren(this.parent.container);
55
+ this.detachAllFromGtkParent();
62
56
  }
63
57
  super.detachDeletedInstance();
64
58
  }
@@ -74,14 +68,33 @@ export class ContainerSlotNode extends VirtualNode<ContainerSlotProps, WidgetNod
74
68
  (method as (child: Gtk.Widget) => void).call(parent, child);
75
69
  }
76
70
 
77
- private detachAllChildren(parent: Gtk.Widget): void {
78
- if (!isRemovable(parent)) return;
71
+ private detachFromGtkParent(child: WidgetNode): void {
72
+ const currentParent = child.container.getParent();
73
+ if (currentParent !== null) {
74
+ if (isRemovable(currentParent)) {
75
+ currentParent.remove(child.container);
76
+ } else {
77
+ child.container.unparent();
78
+ }
79
+ }
80
+ }
81
+
82
+ private reinsertAllChildren(): void {
83
+ if (!this.parent) return;
84
+ const parent = this.parent.container;
79
85
 
80
86
  for (const child of this.children) {
81
- const currentParent = child.container.getParent();
82
- if (currentParent && currentParent === parent) {
83
- parent.remove(child.container);
84
- }
87
+ this.detachFromGtkParent(child);
88
+ }
89
+
90
+ for (const child of this.children) {
91
+ this.attachToParent(parent, child.container);
92
+ }
93
+ }
94
+
95
+ private detachAllFromGtkParent(): void {
96
+ for (const child of this.children) {
97
+ this.detachFromGtkParent(child);
85
98
  }
86
99
  }
87
100
  }
@@ -7,40 +7,16 @@ import { filterProps, hasChanged } from "./internal/props.js";
7
7
  import { SlotNode } from "./slot.js";
8
8
  import { WidgetNode } from "./widget.js";
9
9
 
10
- const OWN_PROPS = ["onDraw"] as const;
10
+ const OWN_PROPS = ["render"] as const;
11
11
 
12
12
  type DrawFunc = (cr: import("@gtkx/ffi/cairo").Context, width: number, height: number, self: Gtk.DrawingArea) => void;
13
13
  type DrawingAreaProps = Pick<GtkDrawingAreaProps, (typeof OWN_PROPS)[number]>;
14
14
 
15
- type PendingDrawFuncEntry = { container: Gtk.DrawingArea; fn: DrawFunc };
16
-
17
- const pendingDrawFuncs: PendingDrawFuncEntry[] = [];
18
-
19
- function wrapDrawFunc(
20
- fn: DrawFunc,
21
- ): (self: Gtk.DrawingArea, cr: import("@gtkx/ffi/cairo").Context, width: number, height: number) => void {
22
- return (self, cr, width, height) => fn(cr, width, height, self);
23
- }
24
-
25
- function ensurePendingBatch(): PendingDrawFuncEntry[] {
26
- if (pendingDrawFuncs.length === 0) {
27
- queueMicrotask(flushPendingDrawFuncs);
28
- }
29
-
30
- return pendingDrawFuncs;
31
- }
32
-
33
- function flushPendingDrawFuncs(): void {
34
- const batch = pendingDrawFuncs.splice(0);
35
-
36
- for (const { container, fn } of batch) {
37
- container.setDrawFunc(wrapDrawFunc(fn));
38
- }
39
- }
40
-
41
15
  type DrawingAreaChild = EventControllerNode | SlotNode | ContainerSlotNode;
42
16
 
43
17
  export class DrawingAreaNode extends WidgetNode<Gtk.DrawingArea, DrawingAreaProps, DrawingAreaChild> {
18
+ private currentDrawFunc: DrawFunc | null = null;
19
+
44
20
  public override isValidChild(child: Node): boolean {
45
21
  return child instanceof EventControllerNode || child instanceof SlotNode || child instanceof ContainerSlotNode;
46
22
  }
@@ -50,12 +26,27 @@ export class DrawingAreaNode extends WidgetNode<Gtk.DrawingArea, DrawingAreaProp
50
26
  this.applyOwnProps(oldProps, newProps);
51
27
  }
52
28
 
29
+ public override detachDeletedInstance(): void {
30
+ this.currentDrawFunc = null;
31
+ this.container.setDrawFunc(undefined);
32
+ super.detachDeletedInstance();
33
+ }
34
+
53
35
  private applyOwnProps(oldProps: DrawingAreaProps | null, newProps: DrawingAreaProps): void {
54
- if (hasChanged(oldProps, newProps, "onDraw")) {
55
- if (this.container.getAllocatedWidth() > 0) {
56
- this.container.setDrawFunc(newProps.onDraw ? wrapDrawFunc(newProps.onDraw) : null);
57
- } else if (newProps.onDraw) {
58
- ensurePendingBatch().push({ container: this.container, fn: newProps.onDraw });
36
+ if (hasChanged(oldProps, newProps, "render")) {
37
+ const hadDraw = !!oldProps?.render;
38
+ const hasDraw = !!newProps.render;
39
+
40
+ this.currentDrawFunc = newProps.render ?? null;
41
+
42
+ if (hasDraw && !hadDraw) {
43
+ this.container.setDrawFunc((self, cr, width, height) => {
44
+ this.currentDrawFunc?.(cr, width, height, self);
45
+ });
46
+ } else if (!hasDraw && hadDraw) {
47
+ this.container.setDrawFunc(undefined);
48
+ } else if (hasDraw) {
49
+ this.container.queueDraw();
59
50
  }
60
51
  }
61
52
  }
@@ -87,14 +87,10 @@ export class EventControllerNode<
87
87
  return;
88
88
  }
89
89
 
90
- const propMeta = resolvePropMeta(this.container, name);
90
+ const propName = resolvePropMeta(this.container, name);
91
91
 
92
- if (propMeta) {
93
- const [, setterName] = propMeta;
94
- const setterFn = (this.container as unknown as Record<string, (v: unknown) => void>)[setterName];
95
- if (typeof setterFn === "function") {
96
- setterFn.call(this.container, value);
97
- }
92
+ if (propName) {
93
+ (this.container as unknown as Record<string, unknown>)[propName] = value;
98
94
  }
99
95
  }
100
96
  }
@@ -3,6 +3,7 @@ import * as Gsk from "@gtkx/ffi/gsk";
3
3
  import * as Gtk from "@gtkx/ffi/gtk";
4
4
  import type { FixedChildProps } from "../jsx.js";
5
5
  import type { Node } from "../node.js";
6
+ import { isRemovable } from "./internal/predicates.js";
6
7
  import { hasChanged } from "./internal/props.js";
7
8
  import { VirtualNode } from "./virtual.js";
8
9
  import { WidgetNode } from "./widget.js";
@@ -33,6 +34,7 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
33
34
  super.appendChild(child);
34
35
 
35
36
  if (this.parent) {
37
+ this.detachFromGtkParent(child.container);
36
38
  this.attachToParent(this.parent.container, child.container);
37
39
  this.applyLayoutTransform();
38
40
  }
@@ -82,6 +84,17 @@ export class FixedChildNode extends VirtualNode<FixedChildProps, WidgetNode<Gtk.
82
84
  }
83
85
  }
84
86
 
87
+ private detachFromGtkParent(child: Gtk.Widget): void {
88
+ const currentParent = child.getParent();
89
+ if (currentParent !== null) {
90
+ if (isRemovable(currentParent)) {
91
+ currentParent.remove(child);
92
+ } else {
93
+ child.unparent();
94
+ }
95
+ }
96
+ }
97
+
85
98
  private applyLayoutTransform(): void {
86
99
  if (!this.parent || !this.children[0]) return;
87
100
 
@@ -1,6 +1,7 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { GridChildProps } from "../jsx.js";
3
3
  import type { Node } from "../node.js";
4
+ import { isRemovable } from "./internal/predicates.js";
4
5
  import { hasChanged } from "./internal/props.js";
5
6
  import { VirtualNode } from "./virtual.js";
6
7
  import { WidgetNode } from "./widget.js";
@@ -30,6 +31,7 @@ export class GridChildNode extends VirtualNode<GridChildProps, WidgetNode<Gtk.Gr
30
31
  super.appendChild(child);
31
32
 
32
33
  if (this.parent) {
34
+ this.detachFromGtkParent(child.container);
33
35
  this.attachToParent(this.parent.container, child.container);
34
36
  }
35
37
  }
@@ -84,6 +86,17 @@ export class GridChildNode extends VirtualNode<GridChildProps, WidgetNode<Gtk.Gr
84
86
  }
85
87
  }
86
88
 
89
+ private detachFromGtkParent(child: Gtk.Widget): void {
90
+ const currentParent = child.getParent();
91
+ if (currentParent !== null) {
92
+ if (isRemovable(currentParent)) {
93
+ currentParent.remove(child);
94
+ } else {
95
+ child.unparent();
96
+ }
97
+ }
98
+ }
99
+
87
100
  private reattachChild(): void {
88
101
  if (!this.parent || !this.children[0]) return;
89
102
 
@@ -1,5 +1,5 @@
1
1
  import type { NativeClass, Type } from "@gtkx/ffi";
2
- import { Object as GObject, toValue, type Value } from "@gtkx/ffi/gobject";
2
+ import { Object as GObject, Value } from "@gtkx/ffi/gobject";
3
3
  import { CONSTRUCTION_META } from "../../generated/internal.js";
4
4
  import type { Container, ContainerClass, Props } from "../../types.js";
5
5
 
@@ -53,7 +53,7 @@ export function createContainerWithProperties(containerClass: ContainerClass, pr
53
53
 
54
54
  for (const { girName, ffiType, value } of constructionProps) {
55
55
  names.push(girName);
56
- values.push(toValue(ffiType, value));
56
+ values.push(Value.newFrom(ffiType, value));
57
57
  }
58
58
 
59
59
  return GObject.newWithProperties(gtype, names, values) as unknown as Container;
@@ -1,6 +1,6 @@
1
1
  import type * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { resolvePropMeta } from "../../metadata.js";
3
- import type { ContainerClass } from "../../types.js";
3
+
4
4
  import { isAddable, isAppendable, isContentWidget, isRemovable, isSingleChild } from "./predicates.js";
5
5
 
6
6
  export function detachChild(child: Gtk.Widget, container: Gtk.Widget): void {
@@ -62,19 +62,13 @@ export function resolvePropertySetter(
62
62
  parentWidget: Gtk.Widget,
63
63
  propId: string,
64
64
  ): ((child: Gtk.Widget | null) => void) | null {
65
- const propMeta = resolvePropMeta(parentWidget, propId);
65
+ const propName = resolvePropMeta(parentWidget, propId);
66
66
 
67
- if (!propMeta) {
67
+ if (!propName) {
68
68
  return null;
69
69
  }
70
70
 
71
- const [, setterName] = propMeta;
72
- const setter = parentWidget[setterName as keyof Gtk.Widget];
73
-
74
- if (typeof setter !== "function") {
75
- const parentType = (parentWidget.constructor as ContainerClass).glibTypeName;
76
- throw new Error(`Expected setter function for property '${propId}' on type '${parentType}'`);
77
- }
78
-
79
- return setter.bind(parentWidget) as (child: Gtk.Widget | null) => void;
71
+ return (child: Gtk.Widget | null) => {
72
+ (parentWidget as unknown as Record<string, unknown>)[propName] = child;
73
+ };
80
74
  }
@@ -1,26 +1,50 @@
1
1
  import type * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { Node } from "../node.js";
3
3
  import type { Container, Props } from "../types.js";
4
+ import { isRemovable } from "./internal/predicates.js";
4
5
  import { WidgetNode } from "./widget.js";
5
6
 
6
- export class ListItemNode extends Node<Gtk.ListItem | Gtk.ListHeader, Props, Node, WidgetNode> {
7
- public override isValidChild(child: Node): boolean {
8
- return child instanceof WidgetNode;
7
+ export class ListItemNode extends Node<Gtk.ListItem | Gtk.ListHeader, Props, Node, Node> {
8
+ public override isValidChild(_child: Node): boolean {
9
+ return true;
9
10
  }
10
11
 
11
- public override appendChild(child: WidgetNode): void {
12
+ public override appendChild(child: Node): void {
12
13
  super.appendChild(child);
13
- this.container.setChild(child.container);
14
+
15
+ if (child instanceof WidgetNode) {
16
+ this.detachFromGtkParent(child);
17
+ this.container.setChild(child.container);
18
+ }
14
19
  }
15
20
 
16
- public override removeChild(child: WidgetNode): void {
17
- this.container.setChild(null);
21
+ public override removeChild(child: Node): void {
22
+ if (child instanceof WidgetNode) {
23
+ this.container.setChild(null);
24
+ }
25
+
18
26
  super.removeChild(child);
19
27
  }
20
28
 
21
- public override insertBefore(child: WidgetNode, before: WidgetNode): void {
29
+ public override insertBefore(child: Node, before: Node): void {
22
30
  super.insertBefore(child, before);
23
- this.container.setChild(child.container);
31
+
32
+ if (child instanceof WidgetNode) {
33
+ this.detachFromGtkParent(child);
34
+ this.container.setChild(child.container);
35
+ }
36
+ }
37
+
38
+ private detachFromGtkParent(child: Node): void {
39
+ if (!(child instanceof WidgetNode)) return;
40
+ const currentParent = child.container.getParent();
41
+ if (currentParent !== null) {
42
+ if (isRemovable(currentParent)) {
43
+ currentParent.remove(child.container);
44
+ } else {
45
+ child.container.unparent();
46
+ }
47
+ }
24
48
  }
25
49
 
26
50
  public static override createContainer(): Container {