@gtkx/react 0.10.2 → 0.10.3

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.
@@ -5,7 +5,6 @@ import type { Container, Props } from "./types.js";
5
5
  declare global {
6
6
  var __GTKX_CONTAINER_NODE_CACHE__: Map<number, Node> | undefined;
7
7
  }
8
- export declare const isCommitting: () => boolean;
9
8
  type TextInstance = Node;
10
9
  type SuspenseInstance = never;
11
10
  type HydratableInstance = never;
@@ -1,13 +1,11 @@
1
1
  import { beginBatch, endBatch, getObjectId } from "@gtkx/ffi";
2
2
  import React from "react";
3
3
  import { createNode } from "./factory.js";
4
- import { clearFiredSignals } from "./nodes/internal/signal-store.js";
4
+ import { signalStore } from "./nodes/internal/signal-store.js";
5
5
  import { flushAfterCommit } from "./scheduler.js";
6
6
  if (!globalThis.__GTKX_CONTAINER_NODE_CACHE__) {
7
7
  globalThis.__GTKX_CONTAINER_NODE_CACHE__ = new Map();
8
8
  }
9
- let committing = false;
10
- export const isCommitting = () => committing;
11
9
  const containerNodeCache = globalThis.__GTKX_CONTAINER_NODE_CACHE__;
12
10
  const getOrCreateContainerNode = (container) => {
13
11
  const id = getObjectId(container.id);
@@ -73,15 +71,14 @@ export function createHostConfig() {
73
71
  parent.insertBefore(child, beforeChild);
74
72
  },
75
73
  prepareForCommit: () => {
76
- committing = true;
74
+ signalStore.blockAll();
77
75
  beginBatch();
78
76
  return null;
79
77
  },
80
78
  resetAfterCommit: () => {
81
79
  endBatch();
82
- clearFiredSignals();
80
+ signalStore.unblockAll();
83
81
  flushAfterCommit();
84
- committing = false;
85
82
  },
86
83
  commitTextUpdate: (textInstance, oldText, newText) => {
87
84
  textInstance.updateProps({ label: oldText }, { label: newText });
package/dist/node.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { SignalStore } from "./nodes/internal/signal-store.js";
2
1
  import type { Container, ContainerClass, Props } from "./types.js";
3
2
  export declare class Node<T = unknown, P = Props> {
4
3
  static priority: number;
@@ -6,7 +5,6 @@ export declare class Node<T = unknown, P = Props> {
6
5
  static createContainer(_props: Props, _containerClass: ContainerClass, _rootContainer?: Container): unknown;
7
6
  container: T;
8
7
  typeName: string;
9
- protected signalStore: SignalStore;
10
8
  constructor(typeName: string, _props: P | undefined, container: T, _rootContainer?: Container);
11
9
  appendChild(_child: Node): void;
12
10
  removeChild(_child: Node): void;
package/dist/node.js CHANGED
@@ -1,4 +1,4 @@
1
- import { SignalStore } from "./nodes/internal/signal-store.js";
1
+ import { signalStore } from "./nodes/internal/signal-store.js";
2
2
  export class Node {
3
3
  static priority = 0;
4
4
  static matches(_type, _containerOrClass) {
@@ -9,11 +9,9 @@ export class Node {
9
9
  }
10
10
  container;
11
11
  typeName;
12
- signalStore;
13
12
  constructor(typeName, _props = {}, container, _rootContainer) {
14
13
  this.typeName = typeName;
15
14
  this.container = container;
16
- this.signalStore = new SignalStore();
17
15
  }
18
16
  appendChild(_child) { }
19
17
  removeChild(_child) { }
@@ -21,6 +19,6 @@ export class Node {
21
19
  updateProps(_oldProps, _newProps) { }
22
20
  mount() { }
23
21
  unmount() {
24
- this.signalStore.clear();
22
+ signalStore.clear(this);
25
23
  }
26
24
  }
@@ -11,7 +11,7 @@ export class ColumnViewColumnNode extends VirtualNode {
11
11
  itemRenderer;
12
12
  constructor(typeName, props, container, rootContainer) {
13
13
  super(typeName, props, container, rootContainer);
14
- this.itemRenderer = new ListItemRenderer(this.signalStore);
14
+ this.itemRenderer = new ListItemRenderer();
15
15
  this.column = new Gtk.ColumnViewColumn();
16
16
  this.column.setFactory(this.itemRenderer.getFactory());
17
17
  }
@@ -1,6 +1,7 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { registerNodeClass } from "../registry.js";
3
3
  import { ColumnViewColumnNode } from "./column-view-column.js";
4
+ import { signalStore } from "./internal/signal-store.js";
4
5
  import { filterProps, isContainerType } from "./internal/utils.js";
5
6
  import { ListItemNode } from "./list-item.js";
6
7
  import { List } from "./models/list.js";
@@ -79,7 +80,7 @@ class ColumnViewNode extends WidgetNode {
79
80
  this.handleSortChange = () => {
80
81
  onSortChange?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
81
82
  };
82
- this.signalStore.set(sorter, "changed", this.handleSortChange);
83
+ signalStore.set(this, sorter, "changed", this.handleSortChange);
83
84
  }
84
85
  }
85
86
  if (!oldProps || oldProps.sortColumn !== newProps.sortColumn || oldProps.sortOrder !== newProps.sortOrder) {
@@ -1,15 +1,13 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { ReactNode } from "react";
3
3
  import type { ListStore } from "./list-store.js";
4
- import type { SignalStore } from "./signal-store.js";
5
4
  export type RenderItemFn<T> = (item: T | null) => ReactNode;
6
5
  export declare class ListItemRenderer {
7
6
  private factory;
8
7
  private store?;
9
8
  private fiberRoots;
10
9
  private renderFn?;
11
- private signalStore;
12
- constructor(signalStore: SignalStore);
10
+ constructor();
13
11
  getFactory(): Gtk.SignalListItemFactory;
14
12
  setRenderFn(renderFn?: RenderItemFn<unknown>): void;
15
13
  setStore(store?: ListStore): void;
@@ -2,14 +2,13 @@ import { getObjectId } from "@gtkx/ffi";
2
2
  import * as Gtk from "@gtkx/ffi/gtk";
3
3
  import { createFiberRoot } from "../../fiber-root.js";
4
4
  import { reconciler } from "../../reconciler.js";
5
+ import { signalStore } from "./signal-store.js";
5
6
  export class ListItemRenderer {
6
7
  factory;
7
8
  store;
8
9
  fiberRoots = new Map();
9
10
  renderFn = () => null;
10
- signalStore;
11
- constructor(signalStore) {
12
- this.signalStore = signalStore;
11
+ constructor() {
13
12
  this.factory = new Gtk.SignalListItemFactory();
14
13
  this.initialize();
15
14
  }
@@ -29,7 +28,7 @@ export class ListItemRenderer {
29
28
  return this.store;
30
29
  }
31
30
  initialize() {
32
- this.signalStore.set(this.factory, "setup", (_self, listItem) => {
31
+ signalStore.set(this, this.factory, "setup", (_self, listItem) => {
33
32
  const ptr = getObjectId(listItem.id);
34
33
  const box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
35
34
  listItem.setChild(box);
@@ -38,7 +37,7 @@ export class ListItemRenderer {
38
37
  const element = this.renderFn?.(null);
39
38
  reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
40
39
  });
41
- this.signalStore.set(this.factory, "bind", (_self, listItem) => {
40
+ signalStore.set(this, this.factory, "bind", (_self, listItem) => {
42
41
  const ptr = getObjectId(listItem.id);
43
42
  const fiberRoot = this.fiberRoots.get(ptr);
44
43
  if (!fiberRoot)
@@ -48,14 +47,14 @@ export class ListItemRenderer {
48
47
  const element = this.renderFn?.(item);
49
48
  reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
50
49
  });
51
- this.signalStore.set(this.factory, "unbind", (_self, listItem) => {
50
+ signalStore.set(this, this.factory, "unbind", (_self, listItem) => {
52
51
  const ptr = getObjectId(listItem.id);
53
52
  const fiberRoot = this.fiberRoots.get(ptr);
54
53
  if (!fiberRoot)
55
54
  return;
56
55
  reconciler.getInstance().updateContainer(null, fiberRoot, null, () => { });
57
56
  });
58
- this.signalStore.set(this.factory, "teardown", (_self, listItem) => {
57
+ signalStore.set(this, this.factory, "teardown", (_self, listItem) => {
59
58
  const ptr = getObjectId(listItem.id);
60
59
  const fiberRoot = this.fiberRoots.get(ptr);
61
60
  if (fiberRoot) {
@@ -1,10 +1,16 @@
1
1
  import * as GObject from "@gtkx/ffi/gobject";
2
+ type SignalOwner = object;
2
3
  export type SignalHandler = (...args: any[]) => any;
3
- export declare const clearFiredSignals: () => void;
4
- export declare class SignalStore {
5
- private signalHandlers;
4
+ declare class SignalStore {
5
+ private ownerHandlers;
6
+ private blockedHandlers;
7
+ private getOwnerMap;
6
8
  private disconnect;
7
9
  private connect;
8
- set(obj: GObject.GObject, signal: string, handler?: SignalHandler): void;
9
- clear(): void;
10
+ set(owner: SignalOwner, obj: GObject.GObject, signal: string, handler?: SignalHandler): void;
11
+ clear(owner: SignalOwner): void;
12
+ blockAll(): void;
13
+ unblockAll(): void;
10
14
  }
15
+ export declare const signalStore: SignalStore;
16
+ export {};
@@ -1,6 +1,5 @@
1
1
  import { getObjectId } from "@gtkx/ffi";
2
2
  import * as GObject from "@gtkx/ffi/gobject";
3
- import { isCommitting } from "../../host-config.js";
4
3
  const LIFECYCLE_SIGNALS = new Set([
5
4
  "realize",
6
5
  "unrealize",
@@ -16,49 +15,69 @@ const LIFECYCLE_SIGNALS = new Set([
16
15
  "unbind",
17
16
  "teardown",
18
17
  ]);
19
- const firedThisCommit = new Set();
20
- export const clearFiredSignals = () => {
21
- firedThisCommit.clear();
22
- };
23
- export class SignalStore {
24
- signalHandlers = new Map();
25
- disconnect(obj, signal) {
18
+ class SignalStore {
19
+ ownerHandlers = new Map();
20
+ blockedHandlers = new Set();
21
+ getOwnerMap(owner) {
22
+ let map = this.ownerHandlers.get(owner);
23
+ if (!map) {
24
+ map = new Map();
25
+ this.ownerHandlers.set(owner, map);
26
+ }
27
+ return map;
28
+ }
29
+ disconnect(owner, obj, signal) {
26
30
  const objectId = getObjectId(obj.id);
27
31
  const key = `${objectId}:${signal}`;
28
- const existing = this.signalHandlers.get(key);
32
+ const ownerMap = this.ownerHandlers.get(owner);
33
+ const existing = ownerMap?.get(key);
29
34
  if (existing) {
30
35
  GObject.signalHandlerDisconnect(existing.obj, existing.handlerId);
31
- this.signalHandlers.delete(key);
36
+ ownerMap?.delete(key);
32
37
  }
33
38
  }
34
- connect(obj, signal, handler) {
39
+ connect(owner, obj, signal, handler) {
35
40
  const objectId = getObjectId(obj.id);
36
41
  const key = `${objectId}:${signal}`;
37
- const wrappedHandler = (...args) => {
38
- if (LIFECYCLE_SIGNALS.has(signal)) {
39
- return handler(...args);
42
+ const handlerId = obj.connect(signal, handler);
43
+ this.getOwnerMap(owner).set(key, { obj, handlerId });
44
+ }
45
+ set(owner, obj, signal, handler) {
46
+ this.disconnect(owner, obj, signal);
47
+ if (handler) {
48
+ this.connect(owner, obj, signal, handler);
49
+ }
50
+ }
51
+ clear(owner) {
52
+ const ownerMap = this.ownerHandlers.get(owner);
53
+ if (ownerMap) {
54
+ for (const { obj, handlerId } of ownerMap.values()) {
55
+ GObject.signalHandlerDisconnect(obj, handlerId);
40
56
  }
41
- if (isCommitting()) {
42
- if (firedThisCommit.has(key)) {
43
- return;
57
+ this.ownerHandlers.delete(owner);
58
+ }
59
+ }
60
+ blockAll() {
61
+ this.blockedHandlers.clear();
62
+ for (const ownerMap of this.ownerHandlers.values()) {
63
+ for (const [key, { obj, handlerId }] of ownerMap.entries()) {
64
+ if (LIFECYCLE_SIGNALS.has(key.split(":")[1] ?? "")) {
65
+ continue;
44
66
  }
45
- firedThisCommit.add(key);
67
+ GObject.signalHandlerBlock(obj, handlerId);
68
+ this.blockedHandlers.add(handlerId);
46
69
  }
47
- return handler(...args);
48
- };
49
- const handlerId = obj.connect(signal, wrappedHandler);
50
- this.signalHandlers.set(key, { obj, handlerId });
51
- }
52
- set(obj, signal, handler) {
53
- this.disconnect(obj, signal);
54
- if (handler) {
55
- this.connect(obj, signal, handler);
56
70
  }
57
71
  }
58
- clear() {
59
- for (const [_, { obj, handlerId }] of this.signalHandlers) {
60
- GObject.signalHandlerDisconnect(obj, handlerId);
72
+ unblockAll() {
73
+ for (const ownerMap of this.ownerHandlers.values()) {
74
+ for (const { obj, handlerId } of ownerMap.values()) {
75
+ if (this.blockedHandlers.has(handlerId)) {
76
+ GObject.signalHandlerUnblock(obj, handlerId);
77
+ }
78
+ }
61
79
  }
62
- this.signalHandlers.clear();
80
+ this.blockedHandlers.clear();
63
81
  }
64
82
  }
83
+ export const signalStore = new SignalStore();
@@ -1,6 +1,5 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import type { ReactNode } from "react";
3
- import type { SignalStore } from "./signal-store.js";
4
3
  import type { TreeStore } from "./tree-store.js";
5
4
  export type TreeRenderItemFn<T> = (item: T | null, row: Gtk.TreeListRow | null) => ReactNode;
6
5
  export declare class TreeListItemRenderer {
@@ -8,8 +7,7 @@ export declare class TreeListItemRenderer {
8
7
  private store?;
9
8
  private fiberRoots;
10
9
  private renderFn?;
11
- private signalStore;
12
- constructor(signalStore: SignalStore);
10
+ constructor();
13
11
  getFactory(): Gtk.SignalListItemFactory;
14
12
  setRenderFn(renderFn?: TreeRenderItemFn<unknown>): void;
15
13
  setStore(store?: TreeStore): void;
@@ -2,14 +2,13 @@ import { getObjectId } from "@gtkx/ffi";
2
2
  import * as Gtk from "@gtkx/ffi/gtk";
3
3
  import { createFiberRoot } from "../../fiber-root.js";
4
4
  import { reconciler } from "../../reconciler.js";
5
+ import { signalStore } from "./signal-store.js";
5
6
  export class TreeListItemRenderer {
6
7
  factory;
7
8
  store;
8
9
  fiberRoots = new Map();
9
10
  renderFn = () => null;
10
- signalStore;
11
- constructor(signalStore) {
12
- this.signalStore = signalStore;
11
+ constructor() {
13
12
  this.factory = new Gtk.SignalListItemFactory();
14
13
  this.initialize();
15
14
  }
@@ -29,7 +28,7 @@ export class TreeListItemRenderer {
29
28
  return this.store;
30
29
  }
31
30
  initialize() {
32
- this.signalStore.set(this.factory, "setup", (_self, listItem) => {
31
+ signalStore.set(this, this.factory, "setup", (_self, listItem) => {
33
32
  const ptr = getObjectId(listItem.id);
34
33
  const expander = new Gtk.TreeExpander();
35
34
  const box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
@@ -40,7 +39,7 @@ export class TreeListItemRenderer {
40
39
  const element = this.renderFn?.(null, null);
41
40
  reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
42
41
  });
43
- this.signalStore.set(this.factory, "bind", (_self, listItem) => {
42
+ signalStore.set(this, this.factory, "bind", (_self, listItem) => {
44
43
  const ptr = getObjectId(listItem.id);
45
44
  const fiberRoot = this.fiberRoots.get(ptr);
46
45
  if (!fiberRoot)
@@ -69,7 +68,7 @@ export class TreeListItemRenderer {
69
68
  const element = this.renderFn?.(itemData?.value ?? null, treeListRow);
70
69
  reconciler.getInstance().updateContainer(element, fiberRoot, null, () => { });
71
70
  });
72
- this.signalStore.set(this.factory, "unbind", (_self, listItem) => {
71
+ signalStore.set(this, this.factory, "unbind", (_self, listItem) => {
73
72
  const ptr = getObjectId(listItem.id);
74
73
  const fiberRoot = this.fiberRoots.get(ptr);
75
74
  if (!fiberRoot)
@@ -78,7 +77,7 @@ export class TreeListItemRenderer {
78
77
  expander.setListRow(undefined);
79
78
  reconciler.getInstance().updateContainer(null, fiberRoot, null, () => { });
80
79
  });
81
- this.signalStore.set(this.factory, "teardown", (_self, listItem) => {
80
+ signalStore.set(this, this.factory, "teardown", (_self, listItem) => {
82
81
  const ptr = getObjectId(listItem.id);
83
82
  const fiberRoot = this.fiberRoots.get(ptr);
84
83
  if (fiberRoot) {
@@ -16,7 +16,7 @@ class ListViewNode extends WidgetNode {
16
16
  constructor(typeName, props, container, rootContainer) {
17
17
  super(typeName, props, container, rootContainer);
18
18
  this.list = new List();
19
- this.itemRenderer = new ListItemRenderer(this.signalStore);
19
+ this.itemRenderer = new ListItemRenderer();
20
20
  this.itemRenderer.setStore(this.list.getStore());
21
21
  this.container.setFactory(this.itemRenderer.getFactory());
22
22
  }
@@ -1,5 +1,6 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { ListStore } from "../internal/list-store.js";
3
+ import { signalStore } from "../internal/signal-store.js";
3
4
  import { ListItemNode } from "../list-item.js";
4
5
  import { VirtualNode } from "../virtual.js";
5
6
  export class List extends VirtualNode {
@@ -42,7 +43,7 @@ export class List extends VirtualNode {
42
43
  updateProps(oldProps, newProps) {
43
44
  super.updateProps(oldProps, newProps);
44
45
  if (!oldProps || oldProps.selectionMode !== newProps.selectionMode) {
45
- this.signalStore.set(this.selectionModel, "selection-changed", undefined);
46
+ signalStore.set(this, this.selectionModel, "selection-changed", undefined);
46
47
  this.selectionModel = this.createSelectionModel(newProps.selectionMode);
47
48
  }
48
49
  if (!oldProps ||
@@ -52,7 +53,7 @@ export class List extends VirtualNode {
52
53
  this.handleSelectionChange = () => {
53
54
  onSelectionChanged?.(this.getSelection());
54
55
  };
55
- this.signalStore.set(this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
56
+ signalStore.set(this, this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
56
57
  }
57
58
  if (!oldProps || oldProps.selected !== newProps.selected || oldProps.selectionMode !== newProps.selectionMode) {
58
59
  this.setSelection(newProps.selected);
@@ -1,6 +1,7 @@
1
1
  import { batch } from "@gtkx/ffi";
2
2
  import * as Gio from "@gtkx/ffi/gio";
3
3
  import { scheduleAfterCommit } from "../../scheduler.js";
4
+ import { signalStore } from "../internal/signal-store.js";
4
5
  import { VirtualNode } from "../virtual.js";
5
6
  export class Menu extends VirtualNode {
6
7
  actionMap;
@@ -66,10 +67,10 @@ export class Menu extends VirtualNode {
66
67
  createAction() {
67
68
  batch(() => {
68
69
  if (this.action) {
69
- this.signalStore.set(this.action, "activate", undefined);
70
+ signalStore.set(this, this.action, "activate", undefined);
70
71
  }
71
72
  this.action = new Gio.SimpleAction(this.getId());
72
- this.signalStore.set(this.action, "activate", this.getOnActivate());
73
+ signalStore.set(this, this.action, "activate", this.getOnActivate());
73
74
  this.getActionMap().addAction(this.action);
74
75
  if (this.application && this.props.accels) {
75
76
  this.application.setAccelsForAction(this.getActionName(), this.getAccels());
@@ -83,7 +84,7 @@ export class Menu extends VirtualNode {
83
84
  }
84
85
  if (this.action) {
85
86
  this.getActionMap().removeAction(this.getId());
86
- this.signalStore.set(this.action, "activate", undefined);
87
+ signalStore.set(this, this.action, "activate", undefined);
87
88
  this.action = undefined;
88
89
  }
89
90
  });
@@ -230,7 +231,7 @@ export class Menu extends VirtualNode {
230
231
  return;
231
232
  }
232
233
  if (oldProps.onActivate !== newProps.onActivate) {
233
- this.signalStore.set(this.getAction(), "activate", newProps.onActivate);
234
+ signalStore.set(this, this.getAction(), "activate", newProps.onActivate);
234
235
  }
235
236
  if (oldProps.accels !== newProps.accels) {
236
237
  if (this.application) {
@@ -1,4 +1,5 @@
1
1
  import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { signalStore } from "../internal/signal-store.js";
2
3
  import { TreeStore } from "../internal/tree-store.js";
3
4
  import { TreeListItemNode } from "../tree-list-item.js";
4
5
  import { VirtualNode } from "../virtual.js";
@@ -77,7 +78,7 @@ export class TreeList extends VirtualNode {
77
78
  this.treeListModel.setAutoexpand(newProps.autoexpand ?? false);
78
79
  }
79
80
  if (!oldProps || oldProps.selectionMode !== newProps.selectionMode) {
80
- this.signalStore.set(this.selectionModel, "selection-changed", undefined);
81
+ signalStore.set(this, this.selectionModel, "selection-changed", undefined);
81
82
  this.selectionModel = this.createSelectionModel(newProps.selectionMode);
82
83
  this.selectionModel.setModel(this.treeListModel);
83
84
  }
@@ -88,7 +89,7 @@ export class TreeList extends VirtualNode {
88
89
  this.handleSelectionChange = () => {
89
90
  onSelectionChanged?.(this.getSelection());
90
91
  };
91
- this.signalStore.set(this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
92
+ signalStore.set(this, this.selectionModel, "selection-changed", newProps.onSelectionChanged ? this.handleSelectionChange : undefined);
92
93
  }
93
94
  if (!oldProps || oldProps.selected !== newProps.selected || oldProps.selectionMode !== newProps.selectionMode) {
94
95
  this.setSelection(newProps.selected);
@@ -1,6 +1,7 @@
1
1
  import * as Adw from "@gtkx/ffi/adw";
2
2
  import * as Gtk from "@gtkx/ffi/gtk";
3
3
  import { registerNodeClass } from "../registry.js";
4
+ import { signalStore } from "./internal/signal-store.js";
4
5
  import { SimpleListStore } from "./internal/simple-list-store.js";
5
6
  import { filterProps, isContainerType } from "./internal/utils.js";
6
7
  import { SimpleListItemNode } from "./simple-list-item.js";
@@ -28,7 +29,7 @@ class SimpleListViewNode extends WidgetNode {
28
29
  }
29
30
  }
30
31
  : undefined;
31
- this.signalStore.set(this.container, "notify::selected", handleSelectionChange);
32
+ signalStore.set(this, this.container, "notify::selected", handleSelectionChange);
32
33
  }
33
34
  if (!oldProps || oldProps.selectedId !== newProps.selectedId) {
34
35
  const index = newProps.selectedId !== undefined ? this.store.getIndexById(newProps.selectedId) : undefined;
@@ -1,5 +1,6 @@
1
1
  import * as Adw from "@gtkx/ffi/adw";
2
2
  import { registerNodeClass } from "../registry.js";
3
+ import { signalStore } from "./internal/signal-store.js";
3
4
  import { VirtualNode } from "./virtual.js";
4
5
  export class ToastNode extends VirtualNode {
5
6
  static priority = 1;
@@ -35,11 +36,11 @@ export class ToastNode extends VirtualNode {
35
36
  return;
36
37
  this.toast = this.createToast();
37
38
  if (this.props.onButtonClicked) {
38
- this.signalStore.set(this.toast, "button-clicked", () => {
39
+ signalStore.set(this, this.toast, "button-clicked", () => {
39
40
  this.props.onButtonClicked?.();
40
41
  });
41
42
  }
42
- this.signalStore.set(this.toast, "dismissed", () => {
43
+ signalStore.set(this, this.toast, "dismissed", () => {
43
44
  this.props.onDismissed?.();
44
45
  });
45
46
  this.parent.addToast(this.toast);
@@ -20,7 +20,7 @@ class TreeListViewNode extends WidgetNode {
20
20
  const listView = container ?? new Gtk.ListView();
21
21
  super(typeName, props, listView, rootContainer);
22
22
  this.treeList = new TreeList(props.autoexpand, props.selectionMode);
23
- this.itemRenderer = new TreeListItemRenderer(this.signalStore);
23
+ this.itemRenderer = new TreeListItemRenderer();
24
24
  this.itemRenderer.setStore(this.treeList.getStore());
25
25
  this.container.setFactory(this.itemRenderer.getFactory());
26
26
  }
@@ -15,5 +15,6 @@ export declare class WidgetNode<T extends Gtk.Widget = Gtk.Widget, P extends Pro
15
15
  updateProps(oldProps: P | null, newProps: P): void;
16
16
  private updateEventControllerProp;
17
17
  private propNameToSignalName;
18
+ private getProperty;
18
19
  private setProperty;
19
20
  }
@@ -4,6 +4,7 @@ import { CONSTRUCTOR_PROPS, PROPS, SIGNALS } from "../generated/internal.js";
4
4
  import { Node } from "../node.js";
5
5
  import { registerNodeClass } from "../registry.js";
6
6
  import { hasSingleContent, isAddable, isAppendable, isEditable, isInsertable, isRemovable, isReorderable, isSingleChild, } from "./internal/predicates.js";
7
+ import { signalStore } from "./internal/signal-store.js";
7
8
  import { filterProps, isContainerType } from "./internal/utils.js";
8
9
  import { SlotNode } from "./slot.js";
9
10
  const EVENT_CONTROLLER_PROPS = new Set([
@@ -173,9 +174,16 @@ export class WidgetNode extends Node {
173
174
  const signalName = this.propNameToSignalName(name);
174
175
  if (signals.has(signalName)) {
175
176
  const handler = typeof newValue === "function" ? newValue : undefined;
176
- this.signalStore.set(this.container, signalName, handler);
177
+ signalStore.set(this, this.container, signalName, handler);
177
178
  }
178
179
  else if (newValue !== undefined) {
180
+ const isEditableText = name === "text" && isEditable(this.container);
181
+ if (isEditableText && oldValue !== undefined) {
182
+ const currentValue = this.getProperty(name);
183
+ if (oldValue !== currentValue) {
184
+ continue;
185
+ }
186
+ }
179
187
  this.setProperty(name, newValue);
180
188
  }
181
189
  }
@@ -190,7 +198,7 @@ export class WidgetNode extends Node {
190
198
  this.container.addController(this.motionController);
191
199
  }
192
200
  const signalName = propName === "onEnter" ? "enter" : propName === "onLeave" ? "leave" : "motion";
193
- this.signalStore.set(this.motionController, signalName, handler);
201
+ signalStore.set(this, this.motionController, signalName, handler);
194
202
  break;
195
203
  }
196
204
  case "onPressed":
@@ -200,7 +208,7 @@ export class WidgetNode extends Node {
200
208
  this.container.addController(this.clickController);
201
209
  }
202
210
  const signalName = propName === "onPressed" ? "pressed" : "released";
203
- this.signalStore.set(this.clickController, signalName, handler);
211
+ signalStore.set(this, this.clickController, signalName, handler);
204
212
  break;
205
213
  }
206
214
  case "onKeyPressed":
@@ -210,7 +218,7 @@ export class WidgetNode extends Node {
210
218
  this.container.addController(this.keyController);
211
219
  }
212
220
  const signalName = propName === "onKeyPressed" ? "key-pressed" : "key-released";
213
- this.signalStore.set(this.keyController, signalName, handler);
221
+ signalStore.set(this, this.keyController, signalName, handler);
214
222
  break;
215
223
  }
216
224
  case "onScroll": {
@@ -218,7 +226,7 @@ export class WidgetNode extends Node {
218
226
  this.scrollController = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.BOTH_AXES);
219
227
  this.container.addController(this.scrollController);
220
228
  }
221
- this.signalStore.set(this.scrollController, "scroll", handler);
229
+ signalStore.set(this, this.scrollController, "scroll", handler);
222
230
  break;
223
231
  }
224
232
  case "onNotify": {
@@ -227,7 +235,7 @@ export class WidgetNode extends Node {
227
235
  handler(obj, pspec.getName());
228
236
  }
229
237
  : undefined;
230
- this.signalStore.set(this.container, "notify", wrappedHandler);
238
+ signalStore.set(this, this.container, "notify", wrappedHandler);
231
239
  break;
232
240
  }
233
241
  }
@@ -239,6 +247,15 @@ export class WidgetNode extends Node {
239
247
  .toLowerCase()
240
248
  .replace(/^-/, "");
241
249
  }
250
+ getProperty(key) {
251
+ const WidgetClass = this.container.constructor;
252
+ const [getterName] = PROPS[WidgetClass.glibTypeName]?.[key] || [];
253
+ const getter = getterName ? this.container[getterName] : undefined;
254
+ if (getter && typeof getter === "function") {
255
+ return getter.call(this.container);
256
+ }
257
+ return undefined;
258
+ }
242
259
  setProperty(key, value) {
243
260
  const WidgetClass = this.container.constructor;
244
261
  const [getterName, setterName] = PROPS[WidgetClass.glibTypeName]?.[key] || [];
@@ -254,14 +271,7 @@ export class WidgetNode extends Node {
254
271
  }
255
272
  }
256
273
  if (setter && typeof setter === "function") {
257
- const editable = isEditable(this.container) ? this.container : null;
258
- const shouldPreserveCursor = key === "text" && editable !== null;
259
- const cursorPosition = shouldPreserveCursor ? editable.getPosition() : 0;
260
274
  setter.call(this.container, value);
261
- if (shouldPreserveCursor && editable !== null) {
262
- const textLength = editable.getText().length;
263
- editable.setPosition(Math.min(cursorPosition, textLength));
264
- }
265
275
  }
266
276
  }
267
277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/react",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "Build GTK4 desktop applications with React and TypeScript",
5
5
  "keywords": [
6
6
  "gtk",
@@ -36,8 +36,8 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "react-reconciler": "^0.33.0",
39
- "@gtkx/ffi": "0.10.2",
40
- "@gtkx/gir": "0.10.2"
39
+ "@gtkx/ffi": "0.10.3",
40
+ "@gtkx/gir": "0.10.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/react-reconciler": "^0.32.3",