@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
@@ -1,11 +1,13 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import { type ElementType, type ReactNode, type Ref, useLayoutEffect, useState } from "react";
3
- import { useMergedRef } from "../hooks/use-merged-refs.js";
2
+ import { type ElementType, type ReactElement, type ReactNode, use } from "react";
4
3
  import { ParentWindowContext } from "../hooks/use-parent-window.js";
4
+ import { createPresentedComponent, type PresentedProps } from "../hooks/use-presented-instance.js";
5
5
  import { applyWrite } from "../reconciler/signals.js";
6
+ import { createPortaledComponent } from "./portaled.js";
6
7
 
7
- type WindowComponentProps = {
8
- ref?: Ref<Gtk.Window | null> | undefined;
8
+ type WindowComponentProps = PresentedProps<Gtk.Window> & {
9
+ // eslint-disable-next-line gtkx/accessor-naming
10
+ transientFor?: Gtk.Window | ReactElement | null | undefined;
9
11
  };
10
12
 
11
13
  const presentWindow = (window: Gtk.Window): void => {
@@ -20,30 +22,29 @@ const destroyWindow = (window: Gtk.Window): void => {
20
22
  });
21
23
  };
22
24
 
23
- const createWindowComponent = (Component: ElementType): ((props: WindowComponentProps) => ReactNode) => {
24
- return ({ ref, ...rest }: WindowComponentProps): ReactNode => {
25
- const [window, setWindow] = useState<Gtk.Window | null>(null);
26
- const mergedRef = useMergedRef(ref, setWindow);
25
+ const usePresentWindow = (): ((window: Gtk.Window) => void) => presentWindow;
27
26
 
28
- useLayoutEffect(() => {
29
- if (!window) {
30
- return;
31
- }
32
-
33
- presentWindow(window);
27
+ const createPresentedWindowComponent = (Component: ElementType): ((props: PresentedProps<Gtk.Window>) => ReactNode) =>
28
+ createPresentedComponent<Gtk.Window>(Component, {
29
+ usePresent: usePresentWindow,
30
+ dismiss: destroyWindow,
31
+ wrap: (element, window) => (
32
+ <ParentWindowContext.Provider value={window}>{element}</ParentWindowContext.Provider>
33
+ ),
34
+ });
34
35
 
35
- return () => {
36
- destroyWindow(window);
37
- };
38
- }, [window]);
36
+ const withDefaultTransientFor = (Component: ElementType): ((props: WindowComponentProps) => ReactNode) => {
37
+ return (props: WindowComponentProps): ReactNode => {
38
+ const parent = use(ParentWindowContext);
39
+ const isDefaulted = props.transientFor === undefined && parent !== null;
39
40
 
40
- return (
41
- <ParentWindowContext.Provider value={window}>
42
- <Component ref={mergedRef} {...rest} />
43
- </ParentWindowContext.Provider>
44
- );
41
+ return <Component {...(isDefaulted ? { ...props, transientFor: parent } : props)} />;
45
42
  };
46
43
  };
47
44
 
45
+ const createWindowComponent = (Component: ElementType): ((props: unknown) => ReactNode) =>
46
+ createPortaledComponent(withDefaultTransientFor(createPresentedWindowComponent(Component)));
47
+
48
+ export { createPresentedWindowComponent };
48
49
  /** @internal */
49
50
  export { createWindowComponent };
package/src/config.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  /** @public */
2
+ export { createElementComponent } from "./components/element.js";
3
+ /** @public */
2
4
  export { BUILTIN_ELEMENTS } from "./element-config.js";
3
5
  /** @public */
4
- export type { DetachInfo, ElementBehavior, ElementConfig, PlaceInfo, Props } from "./reconciler/registry.js";
6
+ export type {
7
+ DetachInfo,
8
+ ElementBehavior,
9
+ ElementConfig,
10
+ ModuleExport,
11
+ PlaceInfo,
12
+ Props,
13
+ } from "./reconciler/registry.js";
5
14
  /** @public */
6
15
  export { defineBehavior, defineElements, ELEMENTS, mergeElementConfigs } from "./reconciler/registry.js";
@@ -1,11 +1,13 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import * as Gio from "@gtkx/gi/gio";
3
+ import * as GLib from "@gtkx/gi/glib";
3
4
  import * as Gtk from "@gtkx/gi/gtk";
4
5
  import type {
5
6
  ActionAccel,
6
7
  CreditSection,
7
8
  DragSourceIcon,
8
9
  LevelBarOffset,
10
+ MainOption,
9
11
  MenuItem,
10
12
  ScaleMark,
11
13
  VflConstraints,
@@ -14,6 +16,7 @@ import { BUILTIN_ELEMENTS, SINGLE_CHILD_TYPES } from "./element-config.js";
14
16
  import {
15
17
  addRemoveSlot,
16
18
  adoptedChildrenSlot,
19
+ applicationCreator,
17
20
  boxSlot,
18
21
  childSetterSlot,
19
22
  controlledText,
@@ -295,6 +298,7 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
295
298
  },
296
299
  GtkApplication: {
297
300
  behaviors: [
301
+ applicationCreator(Gtk.Application),
298
302
  addRemoveSlot<Gtk.Window, Gtk.Application>(
299
303
  "children",
300
304
  "GtkWindow",
@@ -313,6 +317,11 @@ const BUILTIN_BEHAVIORS: Record<string, ElementConfig<never>> = {
313
317
  application.setAccelsForAction(item.detailedActionName, []);
314
318
  },
315
319
  }),
320
+ list<Gtk.Application, MainOption>("mainOptions", {
321
+ add: (application, option) => {
322
+ addMainOption(application, option);
323
+ },
324
+ }),
316
325
  ],
317
326
  },
318
327
  GtkAboutDialog: {
@@ -391,6 +400,17 @@ function layoutChild(parent: Gtk.Widget, child: Gtk.Widget): GObject.Object | nu
391
400
  return parent.getLayoutManager()?.getLayoutChild(child) ?? null;
392
401
  }
393
402
 
403
+ const addMainOption = (application: Gtk.Application, option: MainOption): void => {
404
+ application.addMainOption(
405
+ option.longName,
406
+ option.shortName?.codePointAt(0) ?? 0,
407
+ option.flags ?? GLib.OptionFlags.NONE,
408
+ option.arg ?? GLib.OptionArg.NONE,
409
+ option.description,
410
+ option.argDescription ?? null,
411
+ );
412
+ };
413
+
394
414
  const buildMenu = (items: MenuItem[]): Gio.Menu => {
395
415
  const menu = Gio.Menu.new();
396
416
 
@@ -4,7 +4,6 @@ const SINGLE_CHILD_TYPES: string[] = [
4
4
  "GtkAspectFrame",
5
5
  "GtkButton",
6
6
  "GtkCheckButton",
7
- "GtkComboBox",
8
7
  "GtkDragIcon",
9
8
  "GtkExpander",
10
9
  "GtkFlowBoxChild",
@@ -43,6 +42,9 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
43
42
  component: internal("createWindowComponent"),
44
43
  omittedProps: ["child"],
45
44
  },
45
+ GtkApplicationWindow: {
46
+ component: internal("createApplicationWindowComponent"),
47
+ },
46
48
  GtkLabel: {
47
49
  props: internal("ChildrenProps"),
48
50
  },
@@ -71,7 +73,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
71
73
  isLazy: true,
72
74
  },
73
75
  GActionGroup: {
74
- props: internal("GActionGroupProps"),
76
+ props: internal("ActionGroupProps"),
75
77
  },
76
78
  GtkWidget: {
77
79
  props: internal("GtkWidgetProps"),
@@ -96,10 +98,10 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
96
98
  props: internal("ChildrenProps"),
97
99
  },
98
100
  GActionMap: {
99
- props: internal("GActionMapProps"),
101
+ props: internal("ActionMapProps"),
100
102
  },
101
103
  GMenu: {
102
- props: internal("GMenuProps"),
104
+ props: internal("MenuProps"),
103
105
  },
104
106
  GtkColumnView: {
105
107
  props: internal("ChildrenProps"),
@@ -112,6 +114,7 @@ const BUILTIN_ELEMENTS: Record<string, ElementConfig> = {
112
114
  },
113
115
  GtkSizeGroup: {
114
116
  props: internal("GtkSizeGroupProps"),
117
+ component: internal("createPortaledComponent"),
115
118
  },
116
119
  GtkConstraintLayout: {
117
120
  props: internal("GtkConstraintLayoutProps"),
@@ -61,4 +61,4 @@ function useMergedRef<T>(first: PossibleRef<T>, second: PossibleRef<T>): RefCall
61
61
  return useMemo(() => mergeRefs(first, second), [first, second]);
62
62
  }
63
63
 
64
- export { assignRef, mergeRefs, useMergedRef };
64
+ export { useMergedRef };
@@ -0,0 +1,50 @@
1
+ import { type ElementType, type ReactNode, type Ref, type RefCallback, useLayoutEffect, useState } from "react";
2
+ import { useMergedRef } from "./use-merged-refs.js";
3
+
4
+ type PresentedProps<T> = {
5
+ ref?: Ref<T | null> | undefined;
6
+ };
7
+
8
+ type PresentedOptions<T> = {
9
+ usePresent: () => (instance: T) => void;
10
+ dismiss: (instance: T) => void;
11
+ wrap?: (element: ReactNode, instance: T | null) => ReactNode;
12
+ };
13
+
14
+ const usePresentedInstance = <T,>(
15
+ ref: Ref<T | null> | undefined,
16
+ present: (instance: T) => void,
17
+ dismiss: (instance: T) => void,
18
+ ): [T | null, RefCallback<T>] => {
19
+ const [instance, setInstance] = useState<T | null>(null);
20
+ const mergedRef = useMergedRef(ref, setInstance);
21
+
22
+ useLayoutEffect(() => {
23
+ if (!instance) {
24
+ return;
25
+ }
26
+
27
+ present(instance);
28
+
29
+ return () => {
30
+ dismiss(instance);
31
+ };
32
+ }, [instance, present, dismiss]);
33
+
34
+ return [instance, mergedRef];
35
+ };
36
+
37
+ const createPresentedComponent = <T,>(
38
+ Component: ElementType,
39
+ options: PresentedOptions<T>,
40
+ ): ((props: PresentedProps<T>) => ReactNode) => {
41
+ return ({ ref, ...rest }: PresentedProps<T>): ReactNode => {
42
+ const present = options.usePresent();
43
+ const [instance, mergedRef] = usePresentedInstance(ref, present, options.dismiss);
44
+ const element = <Component ref={mergedRef} {...rest} />;
45
+
46
+ return options.wrap ? options.wrap(element, instance) : element;
47
+ };
48
+ };
49
+
50
+ export { createPresentedComponent, type PresentedProps };
@@ -3,6 +3,7 @@ import { kebabCase } from "@gtkx/utils";
3
3
  import type { RefProp } from "../utils/ref-prop.js";
4
4
  import { useObjectValue } from "./use-object-value.js";
5
5
 
6
+ /** The property map `T` declares, from camelCase property name to value type. */
6
7
  type Properties<T extends GObject.Object> = NonNullable<T["__properties__"]>;
7
8
  /** Every property `T` declares that is also readable off the instance, in camelCase. */
8
9
  type PropertyName<T extends GObject.Object> = keyof Properties<T> & keyof T;
@@ -20,4 +21,4 @@ function useProperty<T extends GObject.Object, P extends PropertyName<T>>(
20
21
  return useObjectValue(object, `notify::${kebabCase(propertyName)}`, (obj) => obj?.[propertyName]);
21
22
  }
22
23
 
23
- export { useProperty, type PropertyName };
24
+ export { useProperty };
@@ -3,6 +3,7 @@ import type { SignalHandler } from "@gtkx/runtime";
3
3
  import { useEffectEvent, useLayoutEffect } from "react";
4
4
  import { type RefProp, resolveRefProp } from "../utils/ref-prop.js";
5
5
 
6
+ /** The signal map `T` declares, from signal name to handler signature. */
6
7
  type Signals<T extends GObject.Object> = NonNullable<T["__signals__"]>;
7
8
  /** Every signal name `T` declares, on its own or narrowed by a `::detail` suffix. */
8
9
  type SignalName<T extends GObject.Object> = keyof Signals<T> | `${keyof Signals<T> & string}::${string}`;
@@ -67,4 +68,4 @@ function useSignal<T extends GObject.Object, S extends SignalName<T> & string>(
67
68
  }, [object, signal, isAfter, isImmediate]);
68
69
  }
69
70
 
70
- export { useSignal, type SignalName, type TypedSignalHandler };
71
+ export { useSignal };
package/src/internal.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import "./bootstrap.js";
2
2
 
3
+ /** @internal */
4
+ export { createApplicationWindowComponent } from "./components/application-window.js";
3
5
  /** @internal */
4
6
  export { createApplicationComponent } from "./components/application.js";
5
7
  export { createElementComponent } from "./components/element.js";
6
8
  /** @internal */
9
+ export { createPortaledComponent } from "./components/portaled.js";
10
+ /** @internal */
7
11
  export { createWindowComponent } from "./components/window.js";
8
12
  export { useMergedRef } from "./hooks/use-merged-refs.js";
9
13
  export type * from "./prop-types.js";
10
- export type { ModuleExport } from "./reconciler/registry.js";
14
+ export { settleAccessible } from "./reconciler/apply-props.js";
11
15
  export { isRootElement } from "./reconciler/root-element.js";
12
16
  export { createReconcilerRoot, type ReconcilerRoot, setReconcilerErrorHandler } from "./reconciler/root.js";
13
- export { applyWrite } from "./reconciler/signals.js";
14
- export { getAccessibleMetadata } from "./utils/accessible-metadata.js";
15
17
  export type { SettingsSchema, SettingsSchemaKeys, SettingValue } from "./utils/settings.js";
package/src/prop-types.ts CHANGED
@@ -1,4 +1,6 @@
1
+ /* eslint-disable gtkx/no-library-prefix */
1
2
  import type * as Gdk from "@gtkx/gi/gdk";
3
+ import type * as GLib from "@gtkx/gi/glib";
2
4
  import type * as GObject from "@gtkx/gi/gobject";
3
5
  import type * as Gtk from "@gtkx/gi/gtk";
4
6
  import type { ReactNode } from "react";
@@ -53,6 +55,22 @@ type CreditSection = {
53
55
  people: string[];
54
56
  };
55
57
 
58
+ /** One command-line option a `Gtk.Application` accepts. */
59
+ type MainOption = {
60
+ /** Name the option is spelled with after two dashes, such as `verbose` for `--verbose`. */
61
+ longName: string;
62
+ /** Single character the option is spelled with after one dash, such as `v` for `-v`; defaults to none. */
63
+ shortName?: string | null;
64
+ /** How the option itself is parsed and listed; defaults to `GLib.OptionFlags.NONE`. */
65
+ flags?: GLib.OptionFlags;
66
+ /** Type of argument the option takes; defaults to `GLib.OptionArg.NONE`, meaning it takes none. */
67
+ arg?: GLib.OptionArg;
68
+ /** Text describing the option in `--help`. */
69
+ description: string;
70
+ /** Placeholder standing for the option's argument in `--help`; defaults to none. */
71
+ argDescription?: string | null;
72
+ };
73
+
56
74
  /** One accelerator binding on a `Gtk.Application`. */
57
75
  type ActionAccel = {
58
76
  /** Action the accelerators activate, such as `app.quit` or `win.open('file')`. */
@@ -86,19 +104,19 @@ type GtkWidgetProps = {
86
104
  } & ChildrenProps;
87
105
 
88
106
  /** Props of an action group placed in a widget's `actionGroups` slot. */
89
- type GActionGroupProps = {
107
+ type ActionGroupProps = {
90
108
  /** Prefix the group's actions are addressed by, such as `win`; defaults to the empty string. */
91
109
  prefix?: string | null | undefined;
92
110
  };
93
111
 
94
112
  /** Props of an element implementing `Gio.ActionMap`. */
95
- type GActionMapProps = {
113
+ type ActionMapProps = {
96
114
  /** `Gio.Action` elements added to the map, removed again by their `name`. */
97
115
  actions?: ReactNode | null | undefined;
98
116
  };
99
117
 
100
118
  /** Props of a `Gio.Menu` element. */
101
- type GMenuProps = {
119
+ type MenuProps = {
102
120
  /** Entries the menu is rebuilt from whenever they change. */
103
121
  items?: MenuItem[] | null | undefined;
104
122
  };
@@ -165,7 +183,10 @@ type GtkSizeGroupProps = {
165
183
 
166
184
  /** Props of a `Gtk.AboutDialog` element. */
167
185
  type GtkAboutDialogProps = {
168
- /** Extra sections appended to the dialog's credits, which GTK offers no way to remove. */
186
+ /**
187
+ * Extra sections appended to the dialog's credits. GTK offers no way to remove one, so the list
188
+ * cannot change once it has been applied.
189
+ */
169
190
  creditSections?: CreditSection[] | null | undefined;
170
191
  };
171
192
 
@@ -173,6 +194,11 @@ type GtkAboutDialogProps = {
173
194
  type GtkApplicationProps = {
174
195
  /** Accelerators bound to the application's actions. */
175
196
  actionAccels?: ActionAccel[] | null | undefined;
197
+ /**
198
+ * Command-line options the application parses, registered before it starts. GLib offers no way to
199
+ * unregister one, so the list cannot change once it has been applied.
200
+ */
201
+ mainOptions?: MainOption[] | null | undefined;
176
202
  } & ChildrenProps;
177
203
 
178
204
  /** Props of a `Gtk.DropTarget` element. */
@@ -199,13 +225,14 @@ export {
199
225
  type ScaleMark,
200
226
  type LevelBarOffset,
201
227
  type CreditSection,
228
+ type MainOption,
202
229
  type ActionAccel,
203
230
  type DragSourceIcon,
204
231
  type ChildrenProps,
205
232
  type GtkWidgetProps,
206
- type GActionGroupProps,
207
- type GActionMapProps,
208
- type GMenuProps,
233
+ type ActionGroupProps,
234
+ type ActionMapProps,
235
+ type MenuProps,
209
236
  type GtkShortcutControllerProps,
210
237
  type GtkOverlayProps,
211
238
  type GtkTextChildAnchorProps,
@@ -1,7 +1,7 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import type { SignalHandler } from "@gtkx/runtime";
3
3
  import * as Gtk from "@gtkx/gi/gtk";
4
- import { drain, kebabCase } from "@gtkx/utils";
4
+ import { drain, isDeepEqual, kebabCase } from "@gtkx/utils";
5
5
  import type { ElementBehavior, Props } from "./registry.js";
6
6
  import { applyAccessibleProps, isAccessibleProp } from "../utils/accessible-props.js";
7
7
  import { type TypeInfo, typeInfoFor } from "./metadata.js";
@@ -16,9 +16,13 @@ type PropChange = { prev: Props; next: Props };
16
16
  const REACT_RESERVED_PROPS = new Set(["children", "ref", "key"]);
17
17
  const NOTIFY_PREFIX = "onNotify";
18
18
  const HANDLER_PREFIX = "on";
19
+ const HANDLER_NAME = /^on[A-Z]/;
19
20
  const flushDirty: Set<ElementNode> = new Set();
21
+ const accessibleDirty: Map<ElementNode, Props> = new Map();
22
+ const mapWatched: WeakSet<ElementNode> = new WeakSet();
23
+ const pendingMap: Set<ElementNode> = new Set();
20
24
 
21
- const isHandlerName = (name: string): boolean => /^on[A-Z]/.test(name);
25
+ const isHandlerName = (name: string): boolean => HANDLER_NAME.test(name);
22
26
 
23
27
  const signalForProp = (info: TypeInfo, name: string): string => {
24
28
  if (name === NOTIFY_PREFIX) {
@@ -102,29 +106,42 @@ const applyEntry = (node: ElementNode, info: TypeInfo, delta: PropDelta): void =
102
106
  };
103
107
 
104
108
  const eachChangedName = (prev: Props, next: Props, visit: (name: string) => void): void => {
105
- const names = new Set([...Object.keys(prev), ...Object.keys(next)]);
106
-
107
- for (const name of names) {
109
+ for (const name in prev) {
108
110
  visit(name);
109
111
  }
112
+
113
+ for (const name in next) {
114
+ if (!Object.hasOwn(prev, name)) {
115
+ visit(name);
116
+ }
117
+ }
110
118
  };
111
119
 
112
- const constructOnlyChangeError = (typeName: string, name: string): Error =>
120
+ const propChangeError = (typeName: string, name: string): Error =>
113
121
  new Error(
114
122
  `Cannot change the construct-only prop '${name}' of <${typeName}> after it is created. ` +
115
- "GTK accepts it only while the object is being built, so give the element a key that " +
123
+ "It is only accepted while the element is being built, so give the element a key that " +
116
124
  `changes with '${name}' and React will build a new one.`,
117
125
  );
118
126
 
119
- const assertConstructOnlyUnchanged = (typeName: string, prev: Props, next: Props): void => {
127
+ const hasAppliedValue = (value: unknown): boolean => (Array.isArray(value) ? value.length > 0 : value !== undefined);
128
+
129
+ const isConstructOnlyChange = (info: TypeInfo, name: string, change: PropChange): boolean =>
130
+ info.constructOnly.has(name) && !Object.is(change.prev[name], change.next[name]);
131
+
132
+ const isDeclaredConstructOnlyChange = (info: TypeInfo, name: string, change: PropChange): boolean =>
133
+ info.declaredConstructOnly.has(name) &&
134
+ hasAppliedValue(change.prev[name]) &&
135
+ !isDeepEqual(change.prev[name], change.next[name]);
136
+
137
+ const assertPropsCanChange = (typeName: string, prev: Props, next: Props): void => {
120
138
  const info = typeInfoFor(typeName);
139
+ const change: PropChange = { prev, next };
121
140
 
122
141
  eachChangedName(prev, next, (name) => {
123
- if (!info.constructOnly.has(name) || Object.is(prev[name], next[name])) {
124
- return;
142
+ if (isConstructOnlyChange(info, name, change) || isDeclaredConstructOnlyChange(info, name, change)) {
143
+ throw propChangeError(typeName, name);
125
144
  }
126
-
127
- throw constructOnlyChangeError(typeName, name);
128
145
  });
129
146
  };
130
147
 
@@ -213,22 +230,58 @@ const flushBehaviors = (): void => {
213
230
  });
214
231
  };
215
232
 
216
- const mountBehaviors = (node: ElementNode): void => {
217
- if (!typeInfoFor(node.typeName).hasMount) {
233
+ const applyAccessible = (object: GObject.Object, prev: Props | null, next: Props): void => {
234
+ if (object instanceof Gtk.Accessible) {
235
+ applyAccessibleProps(object, prev, next);
236
+ }
237
+ };
238
+
239
+ const markAccessible = (node: ElementNode, prev: Props): void => {
240
+ if (!accessibleDirty.has(node)) {
241
+ accessibleDirty.set(node, prev);
242
+ }
243
+ };
244
+
245
+ const hasAccessibleProp = (props: Props): boolean => {
246
+ for (const name in props) {
247
+ if (isAccessibleProp(name)) {
248
+ return true;
249
+ }
250
+ }
251
+
252
+ return false;
253
+ };
254
+
255
+ const watchMap = (node: ElementNode): void => {
256
+ const { object } = node;
257
+
258
+ if (mapWatched.has(node) || !(object instanceof Gtk.Widget) || !hasAccessibleProp(node.props)) {
218
259
  return;
219
260
  }
220
261
 
221
- eachBehavior(node, (behavior, context) => behavior.mount?.(node.object, context));
262
+ mapWatched.add(node);
263
+
264
+ object.connect("map", () => {
265
+ pendingMap.add(node);
266
+ setTimeout(settleAccessible, 0);
267
+ });
222
268
  };
223
269
 
224
- const unmountBehaviors = (node: ElementNode): void => {
225
- eachBehavior(node, (behavior, context) => behavior.unmount?.(node.object, context));
270
+ const settleAccessible = (): void => {
271
+ drain(pendingMap, (node) => {
272
+ if (node.object instanceof Gtk.Widget && node.object.getMapped()) {
273
+ applyAccessible(node.object, null, node.props);
274
+ }
275
+ });
226
276
  };
227
277
 
228
- const applyAccessible = (object: GObject.Object, prev: Props, next: Props): void => {
229
- if (object instanceof Gtk.Widget) {
230
- applyAccessibleProps(object, prev, next);
278
+ const flushAccessible = (): void => {
279
+ for (const [node, prev] of accessibleDirty) {
280
+ applyAccessible(node.object, prev, node.props);
281
+ watchMap(node);
231
282
  }
283
+
284
+ accessibleDirty.clear();
232
285
  };
233
286
 
234
287
  const applyElementProps = (node: ElementNode, prev: Props, next: Props): void => {
@@ -236,8 +289,8 @@ const applyElementProps = (node: ElementNode, prev: Props, next: Props): void =>
236
289
  const consumed = runBehaviorUpdates(node, info, prev, next);
237
290
  applyValueEntries(node, info, { prev, next }, consumed);
238
291
  restoreActionableSensitivity(node, info, prev, next);
239
- applyAccessible(node.object, prev, next);
240
292
  applyHandlers(node, info, prev, next);
293
+ markAccessible(node, prev);
241
294
  markFlush(node);
242
295
  node.props = next;
243
296
  };
@@ -262,10 +315,10 @@ const applyAdoptedProps = (target: SignalTarget, prev: Props, next: Props): void
262
315
 
263
316
  export {
264
317
  markFlush,
318
+ flushAccessible,
319
+ settleAccessible,
265
320
  flushBehaviors,
266
- mountBehaviors,
267
- unmountBehaviors,
268
321
  applyElementProps,
269
322
  applyAdoptedProps,
270
- assertConstructOnlyUnchanged,
323
+ assertPropsCanChange,
271
324
  };