@gtkx/react 1.4.0 → 1.6.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.
@@ -14,14 +14,15 @@ import type {
14
14
  } from "./prop-types.js";
15
15
  import { BUILTIN_ELEMENTS, SINGLE_CHILD_TYPES } from "./element-config.js";
16
16
  import {
17
- addRemoveSlot,
18
- adoptedChildrenSlot,
19
17
  applicationCreator,
20
18
  boxSlot,
21
19
  childSetterSlot,
22
20
  controlledText,
23
21
  deferred,
22
+ deferredWith,
24
23
  list,
24
+ methodSlot,
25
+ setterSlot,
25
26
  slot,
26
27
  value,
27
28
  wrappingIndexedSlot,
@@ -36,8 +37,6 @@ import {
36
37
  import { applyWrite } from "./reconciler/signals.js";
37
38
  import { applyStyle, CSS_CLASSES_PROP, styleClass } from "./reconciler/style.js";
38
39
 
39
- type SelectedIndexState = { desired: number | undefined; isScheduled: boolean; disconnect: (() => void) | null };
40
-
41
40
  const SELECTED_INDEX_PROP = "selectedIndex";
42
41
  const SELECTION_SIGNAL = "selected-rows-changed";
43
42
  const NO_SELECTION = -1;
@@ -48,26 +47,8 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
48
47
  }),
49
48
  ...forTypes(["GtkHeaderBar", "GtkActionBar"], {
50
49
  behaviors: [
51
- addRemoveSlot<Gtk.Widget, Gtk.HeaderBar | Gtk.ActionBar>(
52
- "start",
53
- "GtkWidget",
54
- (bar, child) => {
55
- bar.packStart(child);
56
- },
57
- (bar, child) => {
58
- bar.remove(child);
59
- },
60
- ),
61
- addRemoveSlot<Gtk.Widget, Gtk.HeaderBar | Gtk.ActionBar>(
62
- "end",
63
- "GtkWidget",
64
- (bar, child) => {
65
- bar.packEnd(child);
66
- },
67
- (bar, child) => {
68
- bar.remove(child);
69
- },
70
- ),
50
+ methodSlot<Gtk.HeaderBar | Gtk.ActionBar, Gtk.Widget>("start", Gtk.Widget, "packStart", "remove"),
51
+ methodSlot<Gtk.HeaderBar | Gtk.ActionBar, Gtk.Widget>("end", Gtk.Widget, "packEnd", "remove"),
71
52
  ],
72
53
  }),
73
54
  GtkWindow: {
@@ -75,7 +56,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
75
56
  },
76
57
  GtkWidget: {
77
58
  behaviors: [
78
- slot<Gtk.Widget, Gtk.Popover>("children", "GtkPopover", {
59
+ slot<Gtk.Widget, Gtk.Popover>("children", Gtk.Popover, {
79
60
  attach: (parent, popover) => {
80
61
  popover.setParent(parent);
81
62
  },
@@ -83,25 +64,11 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
83
64
  popover.unparent();
84
65
  },
85
66
  }),
86
- addRemoveSlot<Gtk.EventController, Gtk.Widget>(
87
- "controllers",
88
- "GtkEventController",
89
- (widget, controller) => {
90
- widget.addController(controller);
91
- },
92
- (widget, controller) => {
93
- widget.removeController(controller);
94
- },
67
+ methodSlot<Gtk.Widget, Gtk.EventController>(
68
+ "controllers", Gtk.EventController, "addController", "removeController",
95
69
  ),
96
- slot<Gtk.Widget, Gtk.LayoutManager>("layoutManager", "GtkLayoutManager", {
97
- attach: (widget, manager) => {
98
- widget.setLayoutManager(manager);
99
- },
100
- detach: (widget) => {
101
- widget.setLayoutManager(null);
102
- },
103
- }),
104
- slot<Gtk.Widget, Gio.ActionGroup>("actionGroups", "GActionGroup", {
70
+ setterSlot<Gtk.Widget, Gtk.LayoutManager>("layoutManager", Gtk.LayoutManager, "setLayoutManager"),
71
+ slot<Gtk.Widget, Gio.ActionGroup>("actionGroups", Gio.ActionGroup, {
105
72
  attach: (widget, group, info) => {
106
73
  widget.insertActionGroup((info.props.prefix as string | null) ?? "", group);
107
74
  },
@@ -133,7 +100,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
133
100
  GtkOverlay: {
134
101
  behaviors: [
135
102
  childSetterSlot<Gtk.Overlay>(),
136
- slot<Gtk.Overlay, Gtk.Widget>("overlays", "GtkWidget", {
103
+ slot<Gtk.Overlay, Gtk.Widget>("overlays", Gtk.Widget, {
137
104
  attach: (overlay, child) => {
138
105
  overlay.addOverlay(child);
139
106
  },
@@ -146,15 +113,8 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
146
113
  },
147
114
  GtkShortcutController: {
148
115
  behaviors: [
149
- addRemoveSlot<Gtk.Shortcut, Gtk.ShortcutController>(
150
- "shortcuts",
151
- "GtkShortcut",
152
- (controller, shortcut) => {
153
- controller.addShortcut(shortcut);
154
- },
155
- (controller, shortcut) => {
156
- controller.removeShortcut(shortcut);
157
- },
116
+ methodSlot<Gtk.ShortcutController, Gtk.Shortcut>(
117
+ "shortcuts", Gtk.Shortcut, "addShortcut", "removeShortcut",
158
118
  ),
159
119
  ],
160
120
  },
@@ -162,20 +122,11 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
162
122
  behaviors: [{ create: () => Gtk.TextChildAnchor.new() }],
163
123
  },
164
124
  GtkTextView: {
165
- behaviors: [
166
- slot<Gtk.TextView, Gtk.TextBuffer>("children", "GtkTextBuffer", {
167
- attach: (view, buffer) => {
168
- view.setBuffer(buffer);
169
- },
170
- detach: (view) => {
171
- view.setBuffer(null);
172
- },
173
- }),
174
- ],
125
+ behaviors: [setterSlot<Gtk.TextView, Gtk.TextBuffer>("children", Gtk.TextBuffer, "setBuffer")],
175
126
  },
176
127
  GActionMap: {
177
128
  behaviors: [
178
- slot<Gio.ActionMap, Gio.Action>("actions", "GAction", {
129
+ slot<Gio.ActionMap, Gio.Action>("actions", Gio.Action, {
179
130
  attach: (map, action) => {
180
131
  map.addAction(action);
181
132
  },
@@ -199,7 +150,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
199
150
  },
200
151
  GtkColumnView: {
201
152
  behaviors: [
202
- slot<Gtk.ColumnView, Gtk.ColumnViewColumn>("children", "GtkColumnViewColumn", {
153
+ slot<Gtk.ColumnView, Gtk.ColumnViewColumn>("children", Gtk.ColumnViewColumn, {
203
154
  attach: (view, column, info) => {
204
155
  view.insertColumn(info.index, column);
205
156
  },
@@ -211,7 +162,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
211
162
  },
212
163
  GtkGrid: {
213
164
  behaviors: [
214
- slot<Gtk.Grid, Gtk.Widget>("children", "GtkWidget", {
165
+ slot<Gtk.Grid, Gtk.Widget>("children", Gtk.Widget, {
215
166
  attach: (grid, child) => {
216
167
  grid.attach(child, 0, 0, 1, 1);
217
168
  },
@@ -224,7 +175,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
224
175
  },
225
176
  GtkFixed: {
226
177
  behaviors: [
227
- slot<Gtk.Fixed, Gtk.Widget>("children", "GtkWidget", {
178
+ slot<Gtk.Fixed, Gtk.Widget>("children", Gtk.Widget, {
228
179
  attach: (fixed, child) => {
229
180
  fixed.put(child, 0, 0);
230
181
  },
@@ -249,25 +200,11 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
249
200
  },
250
201
  GtkConstraintLayout: {
251
202
  behaviors: [
252
- addRemoveSlot<Gtk.Constraint, Gtk.ConstraintLayout>(
253
- "constraints",
254
- "GtkConstraint",
255
- (layout, constraint) => {
256
- layout.addConstraint(constraint);
257
- },
258
- (layout, constraint) => {
259
- layout.removeConstraint(constraint);
260
- },
203
+ methodSlot<Gtk.ConstraintLayout, Gtk.Constraint>(
204
+ "constraints", Gtk.Constraint, "addConstraint", "removeConstraint",
261
205
  ),
262
- addRemoveSlot<Gtk.ConstraintGuide, Gtk.ConstraintLayout>(
263
- "guides",
264
- "GtkConstraintGuide",
265
- (layout, guide) => {
266
- layout.addGuide(guide);
267
- },
268
- (layout, guide) => {
269
- layout.removeGuide(guide);
270
- },
206
+ methodSlot<Gtk.ConstraintLayout, Gtk.ConstraintGuide>(
207
+ "guides", Gtk.ConstraintGuide, "addGuide", "removeGuide",
271
208
  ),
272
209
  list<Gtk.ConstraintLayout, VflConstraints, Gtk.Constraint[]>("vfl", {
273
210
  add: (layout, item) => [
@@ -288,19 +225,13 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
288
225
  },
289
226
  GtkStack: {
290
227
  behaviors: [
291
- adoptedChildrenSlot<Gtk.Stack, Gtk.Widget>(
292
- "GtkWidget",
293
- (stack, child) => stack.addChild(child),
294
- (stack, child) => {
295
- stack.remove(child);
296
- },
297
- ),
228
+ methodSlot<Gtk.Stack, Gtk.Widget>("children", Gtk.Widget, "addChild", "remove"),
298
229
  deferred<Gtk.Stack, string>("visibleChildName", (stack, name) => stack.getChildByName(name) !== null),
299
230
  ],
300
231
  },
301
232
  GtkNotebook: {
302
233
  behaviors: [
303
- slot<Gtk.Notebook, Gtk.Widget>("children", "GtkWidget", {
234
+ slot<Gtk.Notebook, Gtk.Widget>("children", Gtk.Widget, {
304
235
  attach: (notebook, child, info) => notebook.insertPage(child, null, info.index),
305
236
  reorder: (notebook, child, info) => {
306
237
  notebook.reorderChild(child, info.index);
@@ -315,16 +246,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
315
246
  GtkApplication: {
316
247
  behaviors: [
317
248
  applicationCreator(Gtk.Application),
318
- addRemoveSlot<Gtk.Window, Gtk.Application>(
319
- "children",
320
- "GtkWindow",
321
- (application, window) => {
322
- application.addWindow(window);
323
- },
324
- (application, window) => {
325
- application.removeWindow(window);
326
- },
327
- ),
249
+ methodSlot<Gtk.Application, Gtk.Window>("children", Gtk.Window, "addWindow", "removeWindow"),
328
250
  list<Gtk.Application, ActionAccel>("actionAccels", {
329
251
  add: (application, item) => {
330
252
  application.setAccelsForAction(item.detailedActionName, item.accels);
@@ -494,89 +416,27 @@ function desiredIndex(value: unknown): number | undefined {
494
416
  return value;
495
417
  }
496
418
 
497
- function selectRowAt(box: Gtk.ListBox, row: Gtk.ListBoxRow): void {
498
- applyWrite(SELECTED_INDEX_PROP, () => {
499
- box.selectRow(row);
500
- });
501
- }
502
-
503
- function clearSelection(box: Gtk.ListBox): void {
504
- applyWrite(SELECTED_INDEX_PROP, () => {
419
+ function applySelectedIndex(box: Gtk.ListBox, index: number): void {
420
+ if (index < 0) {
505
421
  box.unselectAll();
506
- });
507
- }
508
-
509
- function applySelectedIndex(box: Gtk.ListBox, state: SelectedIndexState): void {
510
- const { desired } = state;
511
422
 
512
- if (desired === undefined || selectedRowIndex(box) === desired) {
513
423
  return;
514
424
  }
515
425
 
516
- if (desired < 0) {
517
- clearSelection(box);
518
-
519
- return;
520
- }
521
-
522
- const row = box.getRowAtIndex(desired);
426
+ const row = box.getRowAtIndex(index);
523
427
 
524
428
  if (row !== null) {
525
- selectRowAt(box, row);
526
- }
527
- }
528
-
529
- function scheduleSelectedIndex(box: Gtk.ListBox, state: SelectedIndexState): void {
530
- if (state.isScheduled) {
531
- return;
532
- }
533
-
534
- state.isScheduled = true;
535
-
536
- queueMicrotask(() => {
537
- state.isScheduled = false;
538
-
539
- if (state.disconnect !== null) {
540
- applySelectedIndex(box, state);
541
- }
542
- });
543
- }
544
-
545
- function watchSelectionDrift(box: Gtk.ListBox, state: SelectedIndexState): void {
546
- if (state.disconnect !== null) {
547
- return;
429
+ box.selectRow(row);
548
430
  }
549
-
550
- const handler = (): undefined => {
551
- scheduleSelectedIndex(box, state);
552
- };
553
-
554
- box.on(SELECTION_SIGNAL, handler);
555
-
556
- state.disconnect = (): void => {
557
- box.off(SELECTION_SIGNAL, handler);
558
- };
559
431
  }
560
432
 
561
433
  function selectedIndexBehavior(): ElementBehavior<Gtk.ListBox> {
562
- return {
563
- deferred: [SELECTED_INDEX_PROP],
564
- initialize: (): SelectedIndexState => ({ desired: undefined, isScheduled: false, disconnect: null }),
565
- update: (_box, _prev, next, context) => {
566
- (context as SelectedIndexState).desired = desiredIndex(next[SELECTED_INDEX_PROP]);
567
-
568
- return [SELECTED_INDEX_PROP];
569
- },
570
- flush: (box, context) => {
571
- applySelectedIndex(box, context as SelectedIndexState);
572
- watchSelectionDrift(box, context as SelectedIndexState);
573
- },
574
- teardown: (_box, context) => {
575
- const state = context as SelectedIndexState;
576
- state.disconnect?.();
577
- state.disconnect = null;
578
- },
579
- };
434
+ return deferredWith<Gtk.ListBox, number>(SELECTED_INDEX_PROP, {
435
+ parse: desiredIndex,
436
+ read: selectedRowIndex,
437
+ write: applySelectedIndex,
438
+ signal: SELECTION_SIGNAL,
439
+ });
580
440
  }
581
441
 
582
442
  const addMainOption = (application: Gtk.Application, option: MainOption): void => {
@@ -1,16 +1,15 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
- import type * as Gtk from "@gtkx/gi/gtk";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
3
  import {
4
- type AnyClass,
5
4
  type ApplicationClass,
6
5
  type CommandLineApplication,
7
6
  createApplication,
8
7
  getClassType,
8
+ getInstanceType,
9
9
  TYPE_INVALID,
10
- typeFromName,
11
10
  typeIsA,
12
11
  } from "@gtkx/runtime";
13
- import { getOrInsert, isDeepEqual, kebabCase, structuredClone, unsanitizeIdentifier } from "@gtkx/utils";
12
+ import { isDeepEqual, kebabCase, structuredClone, unsanitizeIdentifier } from "@gtkx/utils";
14
13
  import type { DetachInfo, ElementBehavior, PlaceInfo, Props } from "./registry.js";
15
14
  import { getPropertyName } from "./metadata.js";
16
15
  import { applyWrite } from "./signals.js";
@@ -35,8 +34,24 @@ type ListEntry = { item: unknown; handle: unknown };
35
34
  type ListState = { snapshot: unknown[]; entries: ListEntry[] };
36
35
  type DeferredState = { desired: unknown; isPresent: boolean; isScheduled: boolean; disconnect: (() => void) | null };
37
36
  type CanApply<P extends GObject.Object, V> = (object: P, value: V) => boolean;
37
+
38
+ type DeferredOps<P extends GObject.Object, V> = {
39
+ canApply?: CanApply<P, V> | undefined;
40
+ parse?: ((value: unknown) => V | undefined) | undefined;
41
+ read: (object: P) => unknown;
42
+ write: (object: P, value: V) => void;
43
+ signal?: string | undefined;
44
+ };
45
+
46
+ type DeferredHooks<P extends GObject.Object, V> = Omit<DeferredOps<P, V>, "read" | "write"> & {
47
+ read?: ((object: P) => unknown) | undefined;
48
+ write?: ((object: P, value: V) => void) | undefined;
49
+ };
50
+
38
51
  type ChildSetter = GObject.Object & { setChild: (child: Gtk.Widget | null) => void };
39
52
  type ContentSetter<C extends Gtk.Widget> = GObject.Object & { setContent: (content: C | null) => void };
53
+ type MatchingKey<P, F> = keyof P & string & { [K in keyof P]: P[K] extends F ? K : never }[keyof P];
54
+ type MethodKey<P, A> = MatchingKey<P, (argument: A) => unknown>;
40
55
 
41
56
  type BoxLike = GObject.Object & {
42
57
  remove: (child: Gtk.Widget) => void;
@@ -51,22 +66,32 @@ type IndexedChildHost<C extends GObject.Object> = GObject.Object & {
51
66
  insert: (child: C, position: number) => unknown;
52
67
  };
53
68
 
54
- const childTypeCache: Map<string, bigint> = new Map();
69
+ type ChildClass<C extends GObject.Object> =
70
+ (abstract new (...args: never[]) => C) |
71
+ { [Symbol.hasInstance]: (value: unknown) => value is C };
55
72
 
56
- const childTypeFor = (name: string): bigint => getOrInsert(childTypeCache, name, typeFromName);
73
+ const childClassType = (cls: ChildClass<GObject.Object>): bigint =>
74
+ typeof cls === "function" ? getClassType(cls) : TYPE_INVALID;
57
75
 
58
- const childMatcher =
59
- (name: string): ((child: GObject.Object) => boolean) =>
60
- (child) => {
61
- const type = childTypeFor(name);
76
+ const isChildInstance = <C extends GObject.Object>(child: GObject.Object, cls: ChildClass<C>): child is C => {
77
+ if (child instanceof cls) {
78
+ return true;
79
+ }
62
80
 
63
- return type === TYPE_INVALID || typeIsA(getClassType(child.constructor as AnyClass), type);
64
- };
81
+ const type = childClassType(cls);
82
+
83
+ return type !== TYPE_INVALID && typeIsA(getInstanceType(child), type);
84
+ };
85
+
86
+ const childMatcher =
87
+ <C extends GObject.Object>(cls: ChildClass<C> | undefined): ((child: GObject.Object) => child is C) =>
88
+ (child): child is C =>
89
+ cls !== undefined && isChildInstance(child, cls);
65
90
 
66
91
  const slotAttach =
67
92
  <P extends GObject.Object, C extends GObject.Object>(
68
93
  slotName: string,
69
- isMatch: (child: GObject.Object) => boolean,
94
+ isMatch: (child: GObject.Object) => child is C,
70
95
  attach: SlotHooks<P, C>["attach"],
71
96
  ): NonNullable<ElementBehavior["attach"]> =>
72
97
  (object, child, info) => {
@@ -74,15 +99,15 @@ const slotAttach =
74
99
  return;
75
100
  }
76
101
 
77
- return attach(object as P, child as C, info) ?? true;
102
+ return attach(object as P, child, info) ?? true;
78
103
  };
79
104
 
80
105
  const slot = <P extends GObject.Object, C extends GObject.Object>(
81
106
  slotName: string,
82
- childType: string,
107
+ childClass: ChildClass<C> | undefined,
83
108
  hooks: SlotHooks<P, C>,
84
109
  ): ElementBehavior => {
85
- const matches = childMatcher(childType);
110
+ const matches = childMatcher(childClass);
86
111
  const { attach, detach, reorder, resolve } = hooks;
87
112
  const behavior: ElementBehavior = { attach: slotAttach(slotName, matches, attach) };
88
113
 
@@ -172,29 +197,31 @@ const list = <P extends GObject.Object, I, H = void>(
172
197
  };
173
198
 
174
199
  const settleDeferred = <P extends GObject.Object, V>(
175
- object: GObject.Object,
200
+ object: P,
176
201
  state: DeferredState,
177
202
  prop: string,
178
- canApply: CanApply<P, V> | undefined,
203
+ ops: DeferredOps<P, V>,
179
204
  ): void => {
180
- if (!state.isPresent || Object.is(Reflect.get(object, prop), state.desired)) {
205
+ if (!state.isPresent || Object.is(ops.read(object), state.desired)) {
181
206
  return;
182
207
  }
183
208
 
184
- if (canApply !== undefined && !canApply(object as P, state.desired as V)) {
209
+ const desired = state.desired as V;
210
+
211
+ if (ops.canApply !== undefined && !ops.canApply(object, desired)) {
185
212
  return;
186
213
  }
187
214
 
188
215
  applyWrite(prop, () => {
189
- Reflect.set(object, prop, state.desired);
216
+ ops.write(object, desired);
190
217
  });
191
218
  };
192
219
 
193
220
  const scheduleSettle = <P extends GObject.Object, V>(
194
- object: GObject.Object,
221
+ object: P,
195
222
  state: DeferredState,
196
223
  prop: string,
197
- canApply: CanApply<P, V> | undefined,
224
+ ops: DeferredOps<P, V>,
198
225
  ): void => {
199
226
  if (state.isScheduled) {
200
227
  return;
@@ -206,25 +233,25 @@ const scheduleSettle = <P extends GObject.Object, V>(
206
233
  state.isScheduled = false;
207
234
 
208
235
  if (state.disconnect !== null) {
209
- settleDeferred(object, state, prop, canApply);
236
+ settleDeferred(object, state, prop, ops);
210
237
  }
211
238
  });
212
239
  };
213
240
 
214
241
  const watchDrift = <P extends GObject.Object, V>(
215
- object: GObject.Object,
242
+ object: P,
216
243
  state: DeferredState,
217
244
  prop: string,
218
- canApply: CanApply<P, V> | undefined,
245
+ ops: DeferredOps<P, V>,
219
246
  ): void => {
220
247
  if (state.disconnect !== null) {
221
248
  return;
222
249
  }
223
250
 
224
- const signal = `notify::${getPropertyName(object, prop) ?? unsanitizeIdentifier(kebabCase(prop))}`;
251
+ const signal = ops.signal ?? `notify::${getPropertyName(object, prop) ?? unsanitizeIdentifier(kebabCase(prop))}`;
225
252
 
226
253
  const handler = (): undefined => {
227
- scheduleSettle(object, state, prop, canApply);
254
+ scheduleSettle(object, state, prop, ops);
228
255
  };
229
256
 
230
257
  object.on(signal, handler);
@@ -234,23 +261,20 @@ const watchDrift = <P extends GObject.Object, V>(
234
261
  };
235
262
  };
236
263
 
237
- const deferred = <P extends GObject.Object, V>(
238
- prop: string,
239
- canApply?: CanApply<P, V>,
240
- ): ElementBehavior<P> => ({
264
+ const deferredBehavior = <P extends GObject.Object, V>(prop: string, ops: DeferredOps<P, V>): ElementBehavior<P> => ({
241
265
  deferred: [prop],
242
266
  initialize: (): DeferredState => ({ desired: undefined, isPresent: false, isScheduled: false, disconnect: null }),
243
267
  update: (_object, _prev, next, context) => {
244
268
  const state = context as DeferredState;
245
- state.desired = next[prop];
246
- state.isPresent = next[prop] !== undefined;
269
+ state.desired = ops.parse === undefined ? next[prop] : ops.parse(next[prop]);
270
+ state.isPresent = state.desired !== undefined;
247
271
 
248
272
  return [prop];
249
273
  },
250
274
  flush: (object, context) => {
251
275
  const state = context as DeferredState;
252
- settleDeferred(object, state, prop, canApply);
253
- watchDrift(object, state, prop, canApply);
276
+ settleDeferred(object, state, prop, ops);
277
+ watchDrift(object, state, prop, ops);
254
278
  },
255
279
  teardown: (_object, context) => {
256
280
  const state = context as DeferredState;
@@ -259,6 +283,18 @@ const deferred = <P extends GObject.Object, V>(
259
283
  },
260
284
  });
261
285
 
286
+ const deferredWith = <P extends GObject.Object, V>(prop: string, hooks: DeferredHooks<P, V>): ElementBehavior<P> =>
287
+ deferredBehavior<P, V>(prop, {
288
+ ...hooks,
289
+ read: hooks.read ?? ((object) => Reflect.get(object, prop)),
290
+ write: hooks.write ?? ((object, value) => {
291
+ Reflect.set(object, prop, value);
292
+ }),
293
+ });
294
+
295
+ const deferred = <P extends GObject.Object, V>(prop: string, canApply?: CanApply<P, V>): ElementBehavior<P> =>
296
+ deferredWith<P, V>(prop, { canApply });
297
+
262
298
  const controlledText = (prop: string): ElementBehavior =>
263
299
  value(prop, (object, next) => {
264
300
  if (!hasSameText(object, prop, next)) {
@@ -269,7 +305,7 @@ const controlledText = (prop: string): ElementBehavior =>
269
305
  });
270
306
 
271
307
  const childSetterSlot = <P extends ChildSetter>(): ElementBehavior<P> =>
272
- slot<P, Gtk.Widget>("children", "GtkWidget", {
308
+ slot<P, Gtk.Widget>("children", Gtk.Widget, {
273
309
  attach: (parent, child) => {
274
310
  parent.setChild(child);
275
311
  },
@@ -279,9 +315,9 @@ const childSetterSlot = <P extends ChildSetter>(): ElementBehavior<P> =>
279
315
  });
280
316
 
281
317
  const contentSetterSlot = <P extends ContentSetter<C>, C extends Gtk.Widget = Gtk.Widget>(
282
- childType = "GtkWidget",
318
+ childClass: ChildClass<C>,
283
319
  ): ElementBehavior<P> =>
284
- slot<P, C>("children", childType, {
320
+ slot<P, C>("children", childClass, {
285
321
  attach: (parent, child) => {
286
322
  parent.setContent(child);
287
323
  },
@@ -291,7 +327,7 @@ const contentSetterSlot = <P extends ContentSetter<C>, C extends Gtk.Widget = Gt
291
327
  });
292
328
 
293
329
  const boxSlot = <P extends BoxLike>(): ElementBehavior<P> =>
294
- slot<P, Gtk.Widget>("children", "GtkWidget", {
330
+ slot<P, Gtk.Widget>("children", Gtk.Widget, {
295
331
  attach: (box, child, info) => box.insertChildAfter(child, info.sibling as Gtk.Widget | null),
296
332
  detach: (box, child) => {
297
333
  box.remove(child);
@@ -301,18 +337,45 @@ const boxSlot = <P extends BoxLike>(): ElementBehavior<P> =>
301
337
  },
302
338
  });
303
339
 
304
- const addRemoveSlot = <C extends GObject.Object, P extends GObject.Object>(
340
+ const callMethod = <P extends GObject.Object, A>(parent: P, method: MethodKey<P, A>, argument: A): unknown =>
341
+ (parent[method] as (argument: A) => unknown)(argument);
342
+
343
+ const methodSlot = <P extends GObject.Object, C extends GObject.Object>(
344
+ slotName: string,
345
+ childClass: ChildClass<C> | undefined,
346
+ add: MethodKey<P, C>,
347
+ remove?: MethodKey<P, C>,
348
+ ): ElementBehavior => {
349
+ const hooks: SlotHooks<P, C> = { attach: (parent, child) => callMethod(parent, add, child) };
350
+
351
+ if (remove !== undefined) {
352
+ hooks.detach = (parent, child) => {
353
+ callMethod(parent, remove, child);
354
+ };
355
+ }
356
+
357
+ return slot<P, C>(slotName, childClass, hooks);
358
+ };
359
+
360
+ const setterSlot = <P extends GObject.Object, C extends GObject.Object>(
305
361
  slotName: string,
306
- childType: string,
307
- add: (parent: P, child: C) => unknown,
308
- remove: (parent: P, child: C) => void,
309
- ): ElementBehavior => slot<P, C>(slotName, childType, { attach: add, detach: remove });
362
+ childClass: ChildClass<C> | undefined,
363
+ setter: MethodKey<P, C | null>,
364
+ ): ElementBehavior =>
365
+ slot<P, C>(slotName, childClass, {
366
+ attach: (parent, child) => {
367
+ callMethod<P, C | null>(parent, setter, child);
368
+ },
369
+ detach: (parent) => {
370
+ callMethod<P, C | null>(parent, setter, null);
371
+ },
372
+ });
310
373
 
311
374
  const indexedSlot = <P extends IndexedChildHost<C>, C extends GObject.Object>(
312
375
  slotName: string,
313
- childType: string,
376
+ childClass: ChildClass<C> | undefined,
314
377
  ): ElementBehavior<P> =>
315
- slot<P, C>(slotName, childType, {
378
+ slot<P, C>(slotName, childClass, {
316
379
  attach: (parent, child, info) => {
317
380
  parent.insert(child, info.index);
318
381
  },
@@ -325,12 +388,6 @@ const indexedSlot = <P extends IndexedChildHost<C>, C extends GObject.Object>(
325
388
  },
326
389
  });
327
390
 
328
- const adoptedChildrenSlot = <P extends GObject.Object, C extends GObject.Object>(
329
- childType: string,
330
- add: (parent: P, item: C) => unknown,
331
- remove: (parent: P, item: C) => void,
332
- ): ElementBehavior => addRemoveSlot<C, P>("children", childType, add, remove);
333
-
334
391
  const wrappedRow = <W extends Gtk.Widget>(
335
392
  Wrapper: new (props: Props) => W,
336
393
  setChild: (wrapper: W, inner: Gtk.Widget) => void,
@@ -371,7 +428,7 @@ const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedChildHost<Gt
371
428
  Wrapper: new (props: Props) => W,
372
429
  setChild: (wrapper: W, inner: Gtk.Widget) => void,
373
430
  ): ElementBehavior<P> => ({
374
- ...slot<P, Gtk.Widget>("children", "GtkWidget", {
431
+ ...slot<P, Gtk.Widget>("children", Gtk.Widget, {
375
432
  attach: (parent, child, info) =>
376
433
  parent.insert(wrappedRow(Wrapper, setChild, info.context as RowCache, child), info.index),
377
434
  detach: (parent, child, info) => {
@@ -394,12 +451,13 @@ export {
394
451
  value,
395
452
  list,
396
453
  deferred,
454
+ deferredWith,
397
455
  controlledText,
398
456
  childSetterSlot,
399
457
  contentSetterSlot,
400
458
  boxSlot,
401
- addRemoveSlot,
402
- adoptedChildrenSlot,
459
+ methodSlot,
460
+ setterSlot,
403
461
  indexedSlot,
404
462
  wrappingIndexedSlot,
405
463
  };