@gtkx/react 1.0.0-rc.3 → 1.0.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.
- package/README.md +8 -9
- package/dist/adw/config.d.ts +1 -0
- package/dist/adw/config.d.ts.map +1 -1
- package/dist/adw/config.js +1 -0
- package/dist/adw/config.js.map +1 -1
- package/dist/adw/dialog.d.ts +4 -4
- package/dist/adw/dialog.d.ts.map +1 -1
- package/dist/adw/dialog.js +16 -21
- package/dist/adw/dialog.js.map +1 -1
- package/dist/adw/element-behaviors.js +104 -57
- package/dist/adw/element-behaviors.js.map +1 -1
- package/dist/adw/element-config.d.ts +0 -5
- package/dist/adw/element-config.d.ts.map +1 -1
- package/dist/adw/element-config.js +25 -26
- package/dist/adw/element-config.js.map +1 -1
- package/dist/adw/index.d.ts +1 -0
- package/dist/adw/index.d.ts.map +1 -1
- package/dist/adw/index.js +1 -0
- package/dist/adw/index.js.map +1 -1
- package/dist/adw/prop-types.d.ts +35 -4
- package/dist/adw/prop-types.d.ts.map +1 -1
- package/dist/adw/prop-types.js.map +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/bootstrap.js.map +1 -1
- package/dist/components/application-window.d.ts +5 -0
- package/dist/components/application-window.d.ts.map +1 -0
- package/dist/components/application-window.js +7 -0
- package/dist/components/application-window.js.map +1 -0
- package/dist/components/application.d.ts +3 -8
- package/dist/components/application.d.ts.map +1 -1
- package/dist/components/application.js +22 -6
- package/dist/components/application.js.map +1 -1
- package/dist/components/element.d.ts +13 -2
- package/dist/components/element.d.ts.map +1 -1
- package/dist/components/element.js +39 -15
- package/dist/components/element.js.map +1 -1
- package/dist/components/portaled.d.ts +5 -0
- package/dist/components/portaled.d.ts.map +1 -0
- package/dist/components/portaled.js +8 -0
- package/dist/components/portaled.js.map +1 -0
- package/dist/components/window.d.ts +6 -5
- package/dist/components/window.d.ts.map +1 -1
- package/dist/components/window.js +17 -16
- package/dist/components/window.js.map +1 -1
- package/dist/config.d.ts +7 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/element-behaviors.js +11 -2
- package/dist/element-behaviors.js.map +1 -1
- package/dist/element-config.d.ts +4 -3
- package/dist/element-config.d.ts.map +1 -1
- package/dist/element-config.js +19 -15
- package/dist/element-config.js.map +1 -1
- package/dist/hooks/use-bind-setting.d.ts +8 -7
- package/dist/hooks/use-bind-setting.d.ts.map +1 -1
- package/dist/hooks/use-bind-setting.js +2 -7
- package/dist/hooks/use-bind-setting.js.map +1 -1
- package/dist/hooks/use-merged-refs.d.ts +1 -4
- package/dist/hooks/use-merged-refs.d.ts.map +1 -1
- package/dist/hooks/use-merged-refs.js +1 -1
- package/dist/hooks/use-merged-refs.js.map +1 -1
- package/dist/hooks/use-parent-window.d.ts +3 -1
- package/dist/hooks/use-parent-window.d.ts.map +1 -1
- package/dist/hooks/use-parent-window.js +3 -1
- package/dist/hooks/use-parent-window.js.map +1 -1
- package/dist/hooks/use-presented-instance.d.ts +12 -0
- package/dist/hooks/use-presented-instance.d.ts.map +1 -0
- package/dist/hooks/use-presented-instance.js +27 -0
- package/dist/hooks/use-presented-instance.js.map +1 -0
- package/dist/hooks/use-property.d.ts +4 -3
- package/dist/hooks/use-property.d.ts.map +1 -1
- package/dist/hooks/use-property.js +1 -2
- package/dist/hooks/use-property.js.map +1 -1
- package/dist/hooks/use-setting.d.ts +2 -3
- package/dist/hooks/use-setting.d.ts.map +1 -1
- package/dist/hooks/use-setting.js +2 -3
- package/dist/hooks/use-setting.js.map +1 -1
- package/dist/hooks/use-signal.d.ts +15 -8
- package/dist/hooks/use-signal.d.ts.map +1 -1
- package/dist/hooks/use-signal.js +6 -8
- package/dist/hooks/use-signal.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +8 -5
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +7 -5
- package/dist/internal.js.map +1 -1
- package/dist/prop-types.d.ts +88 -4
- package/dist/prop-types.d.ts.map +1 -1
- package/dist/prop-types.js.map +1 -1
- package/dist/reconciler/apply-props.d.ts +4 -4
- package/dist/reconciler/apply-props.d.ts.map +1 -1
- package/dist/reconciler/apply-props.js +62 -20
- package/dist/reconciler/apply-props.js.map +1 -1
- package/dist/reconciler/behaviors.d.ts +9 -20
- package/dist/reconciler/behaviors.d.ts.map +1 -1
- package/dist/reconciler/behaviors.js +44 -41
- package/dist/reconciler/behaviors.js.map +1 -1
- package/dist/reconciler/devtools.d.ts +6 -0
- package/dist/reconciler/devtools.d.ts.map +1 -0
- package/dist/reconciler/devtools.js +11 -0
- package/dist/reconciler/devtools.js.map +1 -0
- package/dist/reconciler/host-config.d.ts +1 -1
- package/dist/reconciler/host-config.d.ts.map +1 -1
- package/dist/reconciler/host-config.js +17 -26
- package/dist/reconciler/host-config.js.map +1 -1
- package/dist/reconciler/instance.d.ts.map +1 -1
- package/dist/reconciler/instance.js +6 -4
- package/dist/reconciler/instance.js.map +1 -1
- package/dist/reconciler/metadata.d.ts +2 -2
- package/dist/reconciler/metadata.d.ts.map +1 -1
- package/dist/reconciler/metadata.js +9 -11
- package/dist/reconciler/metadata.js.map +1 -1
- package/dist/reconciler/node.d.ts +4 -3
- package/dist/reconciler/node.d.ts.map +1 -1
- package/dist/reconciler/node.js +8 -7
- package/dist/reconciler/node.js.map +1 -1
- package/dist/reconciler/placement.js +20 -20
- package/dist/reconciler/placement.js.map +1 -1
- package/dist/reconciler/registry.d.ts +46 -32
- package/dist/reconciler/registry.d.ts.map +1 -1
- package/dist/reconciler/registry.js +10 -19
- package/dist/reconciler/registry.js.map +1 -1
- package/dist/reconciler/root-element.d.ts +1 -1
- package/dist/reconciler/root-element.d.ts.map +1 -1
- package/dist/reconciler/root-element.js +0 -1
- package/dist/reconciler/root-element.js.map +1 -1
- package/dist/reconciler/root.d.ts +9 -20
- package/dist/reconciler/root.d.ts.map +1 -1
- package/dist/reconciler/root.js +6 -19
- package/dist/reconciler/root.js.map +1 -1
- package/dist/reconciler/signals.d.ts +1 -2
- package/dist/reconciler/signals.d.ts.map +1 -1
- package/dist/reconciler/signals.js +3 -3
- package/dist/reconciler/signals.js.map +1 -1
- package/dist/reconciler/text.js +5 -5
- package/dist/reconciler/text.js.map +1 -1
- package/dist/utils/accessible-props.d.ts +57 -9
- package/dist/utils/accessible-props.d.ts.map +1 -1
- package/dist/utils/accessible-props.js +11 -13
- package/dist/utils/accessible-props.js.map +1 -1
- package/dist/utils/ref-prop.d.ts +0 -6
- package/dist/utils/ref-prop.d.ts.map +1 -1
- package/dist/utils/ref-prop.js +0 -6
- package/dist/utils/ref-prop.js.map +1 -1
- package/dist/utils/settings.d.ts +7 -0
- package/dist/utils/settings.d.ts.map +1 -1
- package/dist/utils/settings.js.map +1 -1
- package/dist/utils/variant.d.ts +33 -1
- package/dist/utils/variant.d.ts.map +1 -1
- package/dist/utils/variant.js.map +1 -1
- package/env.d.ts +11 -4
- package/package.json +5 -5
- package/src/adw/config.ts +1 -0
- package/src/adw/dialog.tsx +23 -25
- package/src/adw/element-behaviors.ts +131 -60
- package/src/adw/element-config.ts +25 -26
- package/src/adw/index.ts +1 -0
- package/src/adw/prop-types.ts +36 -3
- package/src/bootstrap.ts +1 -1
- package/src/components/application-window.tsx +10 -0
- package/src/components/application.tsx +32 -5
- package/src/components/element.tsx +52 -17
- package/src/components/portaled.tsx +14 -0
- package/src/components/window.tsx +25 -23
- package/src/config.ts +13 -8
- package/src/element-behaviors.ts +20 -1
- package/src/element-config.ts +19 -15
- package/src/hooks/use-bind-setting.ts +8 -7
- package/src/hooks/use-merged-refs.ts +1 -1
- package/src/hooks/use-parent-window.ts +3 -1
- package/src/hooks/use-presented-instance.tsx +50 -0
- package/src/hooks/use-property.ts +4 -3
- package/src/hooks/use-setting.ts +2 -3
- package/src/hooks/use-signal.ts +18 -11
- package/src/index.ts +10 -2
- package/src/internal.ts +8 -5
- package/src/prop-types.ts +119 -11
- package/src/reconciler/apply-props.ts +77 -24
- package/src/reconciler/behaviors.ts +77 -50
- package/src/reconciler/devtools.ts +18 -0
- package/src/reconciler/host-config.ts +20 -32
- package/src/reconciler/instance.ts +7 -4
- package/src/reconciler/metadata.ts +11 -14
- package/src/reconciler/node.ts +12 -9
- package/src/reconciler/placement.ts +25 -25
- package/src/reconciler/registry.ts +56 -39
- package/src/reconciler/root-element.ts +4 -2
- package/src/reconciler/root.ts +12 -20
- package/src/reconciler/signals.ts +2 -3
- package/src/reconciler/text.ts +6 -6
- package/src/utils/accessible-props.ts +70 -25
- package/src/utils/ref-prop.ts +0 -6
- package/src/utils/settings.ts +7 -0
- package/src/utils/variant.ts +33 -1
- package/dist/utils/accessible-metadata.d.ts +0 -12
- package/dist/utils/accessible-metadata.d.ts.map +0 -1
- package/dist/utils/accessible-metadata.js +0 -25
- package/dist/utils/accessible-metadata.js.map +0 -1
- package/src/utils/accessible-metadata.ts +0 -31
|
@@ -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 =>
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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 (
|
|
124
|
-
|
|
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
|
|
217
|
-
if (
|
|
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
|
-
|
|
262
|
+
mapWatched.add(node);
|
|
263
|
+
|
|
264
|
+
object.connect("map", () => {
|
|
265
|
+
pendingMap.add(node);
|
|
266
|
+
setTimeout(settleAccessible, 0);
|
|
267
|
+
});
|
|
222
268
|
};
|
|
223
269
|
|
|
224
|
-
const
|
|
225
|
-
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
323
|
+
assertPropsCanChange,
|
|
271
324
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
2
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyClass,
|
|
5
|
+
type ApplicationClass,
|
|
6
|
+
type CommandLineApplication,
|
|
7
|
+
createApplication,
|
|
8
|
+
getClassType,
|
|
9
|
+
TYPE_INVALID,
|
|
10
|
+
typeFromName,
|
|
11
|
+
typeIsA,
|
|
12
|
+
} from "@gtkx/runtime";
|
|
4
13
|
import { getOrInsert, isDeepEqual, structuredClone } from "@gtkx/utils";
|
|
5
14
|
import type { DetachInfo, ElementBehavior, PlaceInfo, Props } from "./registry.js";
|
|
6
15
|
import { applyWrite } from "./signals.js";
|
|
@@ -23,7 +32,7 @@ type ListHooks<P extends GObject.Object, I, H> = {
|
|
|
23
32
|
|
|
24
33
|
type ListEntry = { item: unknown; handle: unknown };
|
|
25
34
|
type ListState = { snapshot: unknown[]; entries: ListEntry[] };
|
|
26
|
-
type DeferredState = { desired: unknown;
|
|
35
|
+
type DeferredState = { desired: unknown; isPresent: boolean; applied: unknown };
|
|
27
36
|
type CanApply<P extends GObject.Object, V> = (object: P, value: V) => boolean;
|
|
28
37
|
type ChildSetter = GObject.Object & { setChild: (child: Gtk.Widget | null) => void };
|
|
29
38
|
type ContentSetter<C extends Gtk.Widget> = GObject.Object & { setContent: (content: C | null) => void };
|
|
@@ -36,9 +45,9 @@ type BoxLike = GObject.Object & {
|
|
|
36
45
|
|
|
37
46
|
type RowCache = WeakMap<GObject.Object, Gtk.Widget>;
|
|
38
47
|
|
|
39
|
-
type
|
|
40
|
-
remove: (child:
|
|
41
|
-
insert: (child:
|
|
48
|
+
type IndexedChildHost<C extends GObject.Object> = GObject.Object & {
|
|
49
|
+
remove: (child: C) => void;
|
|
50
|
+
insert: (child: C, position: number) => unknown;
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
const childTypeCache: Map<string, bigint> = new Map();
|
|
@@ -50,7 +59,7 @@ const childMatcher =
|
|
|
50
59
|
(child) => {
|
|
51
60
|
const type = childTypeFor(name);
|
|
52
61
|
|
|
53
|
-
return type === TYPE_INVALID || typeIsA(
|
|
62
|
+
return type === TYPE_INVALID || typeIsA(getClassType(child.constructor as AnyClass), type);
|
|
54
63
|
};
|
|
55
64
|
|
|
56
65
|
const slotAttach =
|
|
@@ -67,7 +76,6 @@ const slotAttach =
|
|
|
67
76
|
return attach(object as P, child as C, info) ?? true;
|
|
68
77
|
};
|
|
69
78
|
|
|
70
|
-
/** Builds a behavior for a named child slot holding children of `childType`, claiming matches only. */
|
|
71
79
|
const slot = <P extends GObject.Object, C extends GObject.Object>(
|
|
72
80
|
slotName: string,
|
|
73
81
|
childType: string,
|
|
@@ -94,7 +102,6 @@ const slot = <P extends GObject.Object, C extends GObject.Object>(
|
|
|
94
102
|
return behavior;
|
|
95
103
|
};
|
|
96
104
|
|
|
97
|
-
/** Builds a scalar-prop behavior that invokes `apply` whenever the value changes, and claims the prop. */
|
|
98
105
|
const value = <P extends GObject.Object, V>(
|
|
99
106
|
prop: string,
|
|
100
107
|
apply: ValueApply<P, V>,
|
|
@@ -119,48 +126,50 @@ const teardownList = <P extends GObject.Object, I, H>(
|
|
|
119
126
|
return;
|
|
120
127
|
}
|
|
121
128
|
|
|
122
|
-
const remove = hooks.remove;
|
|
123
|
-
|
|
124
|
-
if (remove === undefined) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
129
|
for (const entry of entries) {
|
|
129
|
-
remove(object, entry.item as I, entry.handle as H);
|
|
130
|
+
hooks.remove?.(object, entry.item as I, entry.handle as H);
|
|
130
131
|
}
|
|
131
132
|
};
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
* Builds an array-prop behavior that adds, removes, and clears its items, reapplying on structural
|
|
135
|
-
* change. `add` may return a handle that the same item's later `remove` receives, for items whose
|
|
136
|
-
* teardown needs what `add` produced (as VFL constraints need the objects the layout created).
|
|
137
|
-
*/
|
|
138
|
-
const list = <P extends GObject.Object, I, H = void>(
|
|
134
|
+
const listUpdate = <P extends GObject.Object, I, H>(
|
|
139
135
|
prop: string,
|
|
140
136
|
hooks: ListHooks<P, I, H>,
|
|
141
|
-
): ElementBehavior<P> => {
|
|
137
|
+
): NonNullable<ElementBehavior<P>["update"]> => {
|
|
142
138
|
const { add } = hooks;
|
|
143
139
|
|
|
144
|
-
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const raw = next[prop];
|
|
149
|
-
const items: unknown[] = Array.isArray(raw) ? raw : [];
|
|
140
|
+
return (object, _prev, next, context) => {
|
|
141
|
+
const state = context as ListState;
|
|
142
|
+
const raw = next[prop];
|
|
143
|
+
const items: unknown[] = Array.isArray(raw) ? raw : [];
|
|
150
144
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
if (isDeepEqual(state.snapshot, items)) {
|
|
146
|
+
return [prop];
|
|
147
|
+
}
|
|
154
148
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
149
|
+
teardownList(object, state.entries, hooks);
|
|
150
|
+
state.entries = items.map((item) => ({ item, handle: add?.(object, item as I) }));
|
|
151
|
+
state.snapshot = structuredClone(items);
|
|
158
152
|
|
|
159
|
-
|
|
160
|
-
},
|
|
153
|
+
return [prop];
|
|
161
154
|
};
|
|
162
155
|
};
|
|
163
156
|
|
|
157
|
+
const list = <P extends GObject.Object, I, H = void>(
|
|
158
|
+
prop: string,
|
|
159
|
+
hooks: ListHooks<P, I, H>,
|
|
160
|
+
): ElementBehavior<P> => {
|
|
161
|
+
const behavior: ElementBehavior<P> = {
|
|
162
|
+
initialize: (): ListState => ({ snapshot: [], entries: [] }),
|
|
163
|
+
update: listUpdate(prop, hooks),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (hooks.remove === undefined && hooks.clear === undefined) {
|
|
167
|
+
behavior.constructOnly = [prop];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return behavior;
|
|
171
|
+
};
|
|
172
|
+
|
|
164
173
|
const flushDeferred = <P extends GObject.Object, V>(
|
|
165
174
|
object: GObject.Object,
|
|
166
175
|
context: unknown,
|
|
@@ -169,7 +178,7 @@ const flushDeferred = <P extends GObject.Object, V>(
|
|
|
169
178
|
): void => {
|
|
170
179
|
const state = context as DeferredState;
|
|
171
180
|
|
|
172
|
-
if (!state.
|
|
181
|
+
if (!state.isPresent || Object.is(state.applied, state.desired)) {
|
|
173
182
|
return;
|
|
174
183
|
}
|
|
175
184
|
|
|
@@ -184,17 +193,16 @@ const flushDeferred = <P extends GObject.Object, V>(
|
|
|
184
193
|
state.applied = state.desired;
|
|
185
194
|
};
|
|
186
195
|
|
|
187
|
-
/** Builds a behavior for a prop applied after the surrounding commit, deferred until `canApply` returns true. */
|
|
188
196
|
const deferred = <P extends GObject.Object, V>(
|
|
189
197
|
prop: string,
|
|
190
198
|
canApply?: CanApply<P, V>,
|
|
191
199
|
): ElementBehavior<P> => ({
|
|
192
200
|
deferred: [prop],
|
|
193
|
-
|
|
201
|
+
initialize: (): DeferredState => ({ desired: undefined, isPresent: false, applied: undefined }),
|
|
194
202
|
update: (_object, _prev, next, context) => {
|
|
195
203
|
const state = context as DeferredState;
|
|
196
204
|
state.desired = next[prop];
|
|
197
|
-
state.
|
|
205
|
+
state.isPresent = next[prop] !== undefined;
|
|
198
206
|
|
|
199
207
|
return [prop];
|
|
200
208
|
},
|
|
@@ -203,7 +211,6 @@ const deferred = <P extends GObject.Object, V>(
|
|
|
203
211
|
},
|
|
204
212
|
});
|
|
205
213
|
|
|
206
|
-
/** Builds a behavior for a text prop kept in controlled-input sync: set when provided, never reset. */
|
|
207
214
|
const controlledText = (prop: string): ElementBehavior =>
|
|
208
215
|
value(prop, (object, next) => {
|
|
209
216
|
if (!hasSameText(object, prop, next)) {
|
|
@@ -213,7 +220,6 @@ const controlledText = (prop: string): ElementBehavior =>
|
|
|
213
220
|
}
|
|
214
221
|
});
|
|
215
222
|
|
|
216
|
-
/** Behavior for a container that installs its single child with `setChild`. */
|
|
217
223
|
const childSetterSlot = <P extends ChildSetter>(): ElementBehavior<P> =>
|
|
218
224
|
slot<P, Gtk.Widget>("children", "GtkWidget", {
|
|
219
225
|
attach: (parent, child) => {
|
|
@@ -224,7 +230,6 @@ const childSetterSlot = <P extends ChildSetter>(): ElementBehavior<P> =>
|
|
|
224
230
|
},
|
|
225
231
|
});
|
|
226
232
|
|
|
227
|
-
/** Behavior for a container that installs its single child with `setContent`. */
|
|
228
233
|
const contentSetterSlot = <P extends ContentSetter<C>, C extends Gtk.Widget = Gtk.Widget>(
|
|
229
234
|
childType = "GtkWidget",
|
|
230
235
|
): ElementBehavior<P> =>
|
|
@@ -237,7 +242,6 @@ const contentSetterSlot = <P extends ContentSetter<C>, C extends Gtk.Widget = Gt
|
|
|
237
242
|
},
|
|
238
243
|
});
|
|
239
244
|
|
|
240
|
-
/** Behavior for a `GtkBox`-style container that orders children by sibling. */
|
|
241
245
|
const boxSlot = <P extends BoxLike>(): ElementBehavior<P> =>
|
|
242
246
|
slot<P, Gtk.Widget>("children", "GtkWidget", {
|
|
243
247
|
attach: (box, child, info) => box.insertChildAfter(child, info.sibling as Gtk.Widget | null),
|
|
@@ -249,7 +253,6 @@ const boxSlot = <P extends BoxLike>(): ElementBehavior<P> =>
|
|
|
249
253
|
},
|
|
250
254
|
});
|
|
251
255
|
|
|
252
|
-
/** Behavior for a container whose children are added and removed by a pair of methods. */
|
|
253
256
|
const addRemoveSlot = <C extends GObject.Object, P extends GObject.Object>(
|
|
254
257
|
slotName: string,
|
|
255
258
|
childType: string,
|
|
@@ -257,7 +260,23 @@ const addRemoveSlot = <C extends GObject.Object, P extends GObject.Object>(
|
|
|
257
260
|
remove: (parent: P, child: C) => void,
|
|
258
261
|
): ElementBehavior => slot<P, C>(slotName, childType, { attach: add, detach: remove });
|
|
259
262
|
|
|
260
|
-
|
|
263
|
+
const indexedSlot = <P extends IndexedChildHost<C>, C extends GObject.Object>(
|
|
264
|
+
slotName: string,
|
|
265
|
+
childType: string,
|
|
266
|
+
): ElementBehavior<P> =>
|
|
267
|
+
slot<P, C>(slotName, childType, {
|
|
268
|
+
attach: (parent, child, info) => {
|
|
269
|
+
parent.insert(child, info.index);
|
|
270
|
+
},
|
|
271
|
+
detach: (parent, child) => {
|
|
272
|
+
parent.remove(child);
|
|
273
|
+
},
|
|
274
|
+
reorder: (parent, child, info) => {
|
|
275
|
+
parent.remove(child);
|
|
276
|
+
parent.insert(child, info.index);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
261
280
|
const adoptedChildrenSlot = <P extends GObject.Object, C extends GObject.Object>(
|
|
262
281
|
childType: string,
|
|
263
282
|
add: (parent: P, item: C) => unknown,
|
|
@@ -289,7 +308,7 @@ const wrappedRow = <W extends Gtk.Widget>(
|
|
|
289
308
|
|
|
290
309
|
const removeWrappedRow = (
|
|
291
310
|
Wrapper: new (props: Props) => Gtk.Widget,
|
|
292
|
-
parent:
|
|
311
|
+
parent: IndexedChildHost<Gtk.Widget>,
|
|
293
312
|
child: Gtk.Widget,
|
|
294
313
|
rows: RowCache,
|
|
295
314
|
): void => {
|
|
@@ -300,8 +319,7 @@ const removeWrappedRow = (
|
|
|
300
319
|
}
|
|
301
320
|
};
|
|
302
321
|
|
|
303
|
-
|
|
304
|
-
const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedInserter>(
|
|
322
|
+
const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedChildHost<Gtk.Widget>>(
|
|
305
323
|
Wrapper: new (props: Props) => W,
|
|
306
324
|
setChild: (wrapper: W, inner: Gtk.Widget) => void,
|
|
307
325
|
): ElementBehavior<P> => ({
|
|
@@ -312,10 +330,18 @@ const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedInserter>(
|
|
|
312
330
|
removeWrappedRow(Wrapper, parent, child, info.context as RowCache);
|
|
313
331
|
},
|
|
314
332
|
}),
|
|
315
|
-
|
|
333
|
+
initialize: (): RowCache => new WeakMap(),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const applicationCreator = <P extends GObject.Object & CommandLineApplication, C extends Props>(
|
|
337
|
+
base: ApplicationClass<P, C>,
|
|
338
|
+
): ElementBehavior<P> => ({
|
|
339
|
+
create: (props) => createApplication(base, props as C),
|
|
316
340
|
});
|
|
317
341
|
|
|
318
342
|
export {
|
|
343
|
+
applicationCreator,
|
|
344
|
+
childMatcher,
|
|
319
345
|
slot,
|
|
320
346
|
value,
|
|
321
347
|
list,
|
|
@@ -326,5 +352,6 @@ export {
|
|
|
326
352
|
boxSlot,
|
|
327
353
|
addRemoveSlot,
|
|
328
354
|
adoptedChildrenSlot,
|
|
355
|
+
indexedSlot,
|
|
329
356
|
wrappingIndexedSlot,
|
|
330
357
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type DevToolsInjectable = {
|
|
2
|
+
injectIntoDevTools: (...args: never[]) => unknown;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
const injection = { isDone: false };
|
|
6
|
+
|
|
7
|
+
const hasDevToolsHook = (): boolean => Reflect.get(globalThis, "__REACT_DEVTOOLS_GLOBAL_HOOK__") !== undefined;
|
|
8
|
+
|
|
9
|
+
const injectIntoDevTools = (reconciler: DevToolsInjectable): void => {
|
|
10
|
+
if (injection.isDone || !hasDevToolsHook()) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
injection.isDone = true;
|
|
15
|
+
reconciler.injectIntoDevTools();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { injectIntoDevTools };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import {
|
|
4
|
-
import { getOrInsert } from "@gtkx/utils";
|
|
3
|
+
import { type AnyClass, getClassType, typeName } from "@gtkx/runtime";
|
|
4
|
+
import { getOrInsert, packageVersion } from "@gtkx/utils";
|
|
5
5
|
import { createContext } from "react";
|
|
6
6
|
import ReactReconciler from "react-reconciler";
|
|
7
7
|
import { DefaultEventPriority, DiscreteEventPriority, NoEventPriority } from "react-reconciler/constants.js";
|
|
@@ -10,20 +10,17 @@ import { Prop } from "../components/element.js";
|
|
|
10
10
|
import {
|
|
11
11
|
applyAdoptedProps,
|
|
12
12
|
applyElementProps,
|
|
13
|
-
|
|
13
|
+
assertPropsCanChange,
|
|
14
|
+
flushAccessible,
|
|
14
15
|
flushBehaviors,
|
|
15
|
-
mountBehaviors,
|
|
16
|
-
unmountBehaviors,
|
|
17
16
|
} from "./apply-props.js";
|
|
18
17
|
import { attachChild, detachChild } from "./child-routing.js";
|
|
19
18
|
import { resolveElementNode } from "./instance.js";
|
|
20
|
-
import { typeInfoFor } from "./metadata.js";
|
|
21
19
|
import {
|
|
22
20
|
type AnyNode,
|
|
23
21
|
createElementNode,
|
|
24
22
|
createPropNode,
|
|
25
23
|
createTextNode,
|
|
26
|
-
type Dispatch,
|
|
27
24
|
ELEMENT_KIND,
|
|
28
25
|
type ElementNode,
|
|
29
26
|
type Instance,
|
|
@@ -41,7 +38,7 @@ import {
|
|
|
41
38
|
validateContentMix,
|
|
42
39
|
} from "./text.js";
|
|
43
40
|
|
|
44
|
-
/**
|
|
41
|
+
/** What a tree is rendered into: any GObject, or the {@link RootElement} marker to render at the top level. */
|
|
45
42
|
type Container = RootElement | GObject.Object;
|
|
46
43
|
|
|
47
44
|
type PriorityTracker = {
|
|
@@ -55,9 +52,15 @@ const containerNodes: WeakMap<object, ElementNode> = new WeakMap();
|
|
|
55
52
|
const priority = createPriorityTracker();
|
|
56
53
|
|
|
57
54
|
const hostConfig = {
|
|
55
|
+
rendererPackageName: "@gtkx/react",
|
|
56
|
+
rendererVersion: packageVersion(import.meta.url, "@gtkx/react/package.json"),
|
|
58
57
|
supportsMutation: true,
|
|
59
58
|
supportsPersistence: false,
|
|
60
59
|
supportsHydration: false,
|
|
60
|
+
supportsMicrotasks: true,
|
|
61
|
+
scheduleMicrotask: (fn: () => void): void => {
|
|
62
|
+
queueMicrotask(fn);
|
|
63
|
+
},
|
|
61
64
|
isPrimaryRenderer: true,
|
|
62
65
|
noTimeout: -1,
|
|
63
66
|
scheduleTimeout: (fn: (...args: unknown[]) => unknown, delay?: number): ReturnType<typeof setTimeout> =>
|
|
@@ -71,18 +74,11 @@ const hostConfig = {
|
|
|
71
74
|
attachChild(parent, child, null);
|
|
72
75
|
},
|
|
73
76
|
finalizeInitialChildren: (instance: Instance, _type: string, props: Props): boolean => {
|
|
74
|
-
if (instance.kind !== ELEMENT_KIND) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
validateContentMix(instance, props);
|
|
79
|
-
|
|
80
|
-
return typeInfoFor(instance.typeName).hasMount;
|
|
81
|
-
},
|
|
82
|
-
commitMount: (instance: Instance): void => {
|
|
83
77
|
if (instance.kind === ELEMENT_KIND) {
|
|
84
|
-
|
|
78
|
+
validateContentMix(instance, props);
|
|
85
79
|
}
|
|
80
|
+
|
|
81
|
+
return false;
|
|
86
82
|
},
|
|
87
83
|
shouldSetTextContent: (): boolean => false,
|
|
88
84
|
getRootHostContext: (): Record<string, never> => HOST_CONTEXT,
|
|
@@ -92,6 +88,7 @@ const hostConfig = {
|
|
|
92
88
|
resetAfterCommit: (): void => {
|
|
93
89
|
flushTextHosts();
|
|
94
90
|
flushBehaviors();
|
|
91
|
+
flushAccessible();
|
|
95
92
|
},
|
|
96
93
|
preparePortalMount: (): void => undefined,
|
|
97
94
|
clearContainer: (): void => undefined,
|
|
@@ -135,7 +132,6 @@ const hostConfig = {
|
|
|
135
132
|
detachDeletedInstance: (instance: Instance): void => {
|
|
136
133
|
if (instance.kind === ELEMENT_KIND) {
|
|
137
134
|
disconnectAllHandlers(instance);
|
|
138
|
-
unmountBehaviors(instance);
|
|
139
135
|
} else if (instance.kind === LAZY_KIND && instance.adopted !== null) {
|
|
140
136
|
disconnectAllHandlers(lazyTarget(instance, instance.adopted));
|
|
141
137
|
}
|
|
@@ -191,14 +187,14 @@ function createPriorityTracker(): PriorityTracker {
|
|
|
191
187
|
|
|
192
188
|
const updateInstance = (instance: Instance, prev: Props, next: Props): void => {
|
|
193
189
|
if (instance.kind === ELEMENT_KIND) {
|
|
194
|
-
|
|
190
|
+
assertPropsCanChange(instance.typeName, prev, next);
|
|
195
191
|
applyElementProps(instance, prev, next);
|
|
196
192
|
|
|
197
193
|
return;
|
|
198
194
|
}
|
|
199
195
|
|
|
200
196
|
if (instance.kind === LAZY_KIND && instance.adopted !== null) {
|
|
201
|
-
|
|
197
|
+
assertPropsCanChange(instance.typeName, prev, next);
|
|
202
198
|
applyAdoptedProps(lazyTarget(instance, instance.adopted), prev, next);
|
|
203
199
|
instance.props = next;
|
|
204
200
|
}
|
|
@@ -234,22 +230,14 @@ const setWidgetVisible = (instance: Instance, isVisible: boolean): void => {
|
|
|
234
230
|
}
|
|
235
231
|
};
|
|
236
232
|
|
|
237
|
-
const runDiscrete: Dispatch = (fn) => {
|
|
238
|
-
try {
|
|
239
|
-
return priority.withDiscrete(fn);
|
|
240
|
-
} finally {
|
|
241
|
-
reconciler.flushSyncWork();
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
233
|
const adoptContainer = (container: GObject.Object): ElementNode => {
|
|
246
|
-
const name = typeName(
|
|
234
|
+
const name = typeName(getClassType(container.constructor as AnyClass));
|
|
247
235
|
|
|
248
236
|
if (name === null) {
|
|
249
237
|
throw new Error("Cannot adopt a container whose GType has no registered name");
|
|
250
238
|
}
|
|
251
239
|
|
|
252
|
-
return createElementNode(name, container,
|
|
240
|
+
return createElementNode(name, container, priority.withDiscrete, null);
|
|
253
241
|
};
|
|
254
242
|
|
|
255
243
|
const getOrCreateContainerNode = (container: GObject.Object): ElementNode =>
|
|
@@ -260,7 +248,7 @@ const createNode = (type: string, props: Props): Instance => {
|
|
|
260
248
|
return createPropNode(props.propName as string);
|
|
261
249
|
}
|
|
262
250
|
|
|
263
|
-
const node = resolveElementNode(type, props,
|
|
251
|
+
const node = resolveElementNode(type, props, priority.withDiscrete);
|
|
264
252
|
|
|
265
253
|
if (node.kind === ELEMENT_KIND) {
|
|
266
254
|
containerNodes.set(node.object, node);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
2
|
import { getWrapperClass, TYPE_INVALID, typeFromName, typeIsA } from "@gtkx/runtime";
|
|
3
|
-
import { pickBy } from "@gtkx/utils";
|
|
3
|
+
import { getOrInsert, pickBy } from "@gtkx/utils";
|
|
4
4
|
import type { Props } from "./registry.js";
|
|
5
5
|
import { type TypeInfo, typeInfoFor } from "./metadata.js";
|
|
6
6
|
import {
|
|
@@ -24,6 +24,7 @@ const CONTENT_TYPE_NAMES: { kind: ContentKind; name: string }[] = [
|
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
const getContentTypes = createContentTypeCache();
|
|
27
|
+
const contentKinds: Map<bigint, ContentKind | null> = new Map();
|
|
27
28
|
|
|
28
29
|
function createContentTypeCache(): () => ContentType[] {
|
|
29
30
|
let cached: ContentType[] | null = null;
|
|
@@ -35,7 +36,7 @@ function createContentTypeCache(): () => ContentType[] {
|
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
+
const findContentKind = (type: bigint): ContentKind | null => {
|
|
39
40
|
for (const entry of getContentTypes()) {
|
|
40
41
|
if (entry.type !== TYPE_INVALID && typeIsA(type, entry.type)) {
|
|
41
42
|
return entry.kind;
|
|
@@ -45,6 +46,8 @@ const resolveContentKind = (type: bigint): ContentKind | null => {
|
|
|
45
46
|
return null;
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
const contentKindFor = (type: bigint): ContentKind | null => getOrInsert(contentKinds, type, findContentKind);
|
|
50
|
+
|
|
48
51
|
const constructInput = (info: TypeInfo, props: Props): Props =>
|
|
49
52
|
pickBy(
|
|
50
53
|
props,
|
|
@@ -69,14 +72,14 @@ const createObject = (typeName: string, type: bigint, input: Props): GObject.Obj
|
|
|
69
72
|
const resolveElementNode = (typeName: string, props: Props, dispatch: Dispatch): ElementNode | LazyNode => {
|
|
70
73
|
const info = typeInfoFor(typeName);
|
|
71
74
|
|
|
72
|
-
if (info.
|
|
75
|
+
if (info.isLazy) {
|
|
73
76
|
return createLazyNode(typeName, props, dispatch);
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
const type = typeFromName(typeName);
|
|
77
80
|
const object = createObject(typeName, type, constructInput(info, props));
|
|
78
81
|
|
|
79
|
-
return createElementNode(typeName, object, dispatch,
|
|
82
|
+
return createElementNode(typeName, object, dispatch, contentKindFor(type));
|
|
80
83
|
};
|
|
81
84
|
|
|
82
85
|
export { resolveElementNode };
|