@gtkx/react 1.0.0-rc.4 → 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 +5 -5
- package/dist/adw/dialog.d.ts +3 -4
- package/dist/adw/dialog.d.ts.map +1 -1
- package/dist/adw/dialog.js +15 -21
- package/dist/adw/dialog.js.map +1 -1
- package/dist/adw/element-behaviors.js +102 -54
- package/dist/adw/element-behaviors.js.map +1 -1
- package/dist/adw/element-config.d.ts +0 -6
- package/dist/adw/element-config.d.ts.map +1 -1
- package/dist/adw/element-config.js +15 -17
- package/dist/adw/element-config.js.map +1 -1
- package/dist/adw/prop-types.d.ts +34 -3
- package/dist/adw/prop-types.d.ts.map +1 -1
- package/dist/adw/prop-types.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 +2 -8
- package/dist/components/application.d.ts.map +1 -1
- package/dist/components/application.js +21 -6
- package/dist/components/application.js.map +1 -1
- package/dist/components/element.d.ts +13 -1
- package/dist/components/element.d.ts.map +1 -1
- package/dist/components/element.js +39 -14
- 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 +5 -5
- package/dist/components/window.d.ts.map +1 -1
- package/dist/components/window.js +16 -16
- package/dist/components/window.js.map +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/element-behaviors.js +11 -1
- package/dist/element-behaviors.js.map +1 -1
- package/dist/element-config.d.ts.map +1 -1
- package/dist/element-config.js +7 -4
- package/dist/element-config.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-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 +2 -1
- package/dist/hooks/use-property.d.ts.map +1 -1
- package/dist/hooks/use-property.js.map +1 -1
- package/dist/hooks/use-signal.d.ts +2 -1
- package/dist/hooks/use-signal.d.ts.map +1 -1
- package/dist/hooks/use-signal.js.map +1 -1
- package/dist/internal.d.ts +5 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -2
- package/dist/internal.js.map +1 -1
- package/dist/prop-types.d.ts +29 -5
- 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 -5
- package/dist/reconciler/behaviors.d.ts.map +1 -1
- package/dist/reconciler/behaviors.js +40 -22
- 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.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 +5 -3
- package/dist/reconciler/instance.js.map +1 -1
- package/dist/reconciler/metadata.d.ts +1 -1
- package/dist/reconciler/metadata.d.ts.map +1 -1
- package/dist/reconciler/metadata.js +2 -4
- package/dist/reconciler/metadata.js.map +1 -1
- package/dist/reconciler/node.d.ts +3 -2
- package/dist/reconciler/node.d.ts.map +1 -1
- package/dist/reconciler/node.js +7 -6
- 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 +9 -7
- package/dist/reconciler/registry.d.ts.map +1 -1
- package/dist/reconciler/registry.js.map +1 -1
- package/dist/reconciler/root-element.d.ts +1 -0
- package/dist/reconciler/root-element.d.ts.map +1 -1
- package/dist/reconciler/root-element.js.map +1 -1
- package/dist/reconciler/root.d.ts +3 -0
- package/dist/reconciler/root.d.ts.map +1 -1
- package/dist/reconciler/root.js +2 -0
- 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 +1 -1
- package/dist/reconciler/signals.js.map +1 -1
- package/dist/utils/accessible-props.d.ts +8 -8
- 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/settings.d.ts +2 -1
- 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/package.json +4 -4
- package/src/adw/dialog.tsx +22 -25
- package/src/adw/element-behaviors.ts +129 -57
- package/src/adw/element-config.ts +15 -17
- package/src/adw/prop-types.ts +35 -2
- package/src/components/application-window.tsx +10 -0
- package/src/components/application.tsx +31 -5
- package/src/components/element.tsx +52 -16
- package/src/components/portaled.tsx +14 -0
- package/src/components/window.tsx +24 -23
- package/src/config.ts +10 -1
- package/src/element-behaviors.ts +20 -0
- package/src/element-config.ts +7 -4
- package/src/hooks/use-merged-refs.ts +1 -1
- package/src/hooks/use-presented-instance.tsx +50 -0
- package/src/hooks/use-property.ts +2 -1
- package/src/hooks/use-signal.ts +2 -1
- package/src/internal.ts +5 -3
- package/src/prop-types.ts +34 -7
- package/src/reconciler/apply-props.ts +77 -24
- package/src/reconciler/behaviors.ts +72 -30
- package/src/reconciler/devtools.ts +18 -0
- package/src/reconciler/host-config.ts +19 -31
- package/src/reconciler/instance.ts +6 -3
- package/src/reconciler/metadata.ts +3 -6
- package/src/reconciler/node.ts +10 -7
- package/src/reconciler/placement.ts +25 -25
- package/src/reconciler/registry.ts +9 -7
- package/src/reconciler/root-element.ts +4 -1
- package/src/reconciler/root.ts +6 -0
- package/src/reconciler/signals.ts +0 -1
- package/src/utils/accessible-props.ts +21 -24
- package/src/utils/settings.ts +2 -1
- package/src/utils/variant.ts +33 -1
- package/dist/utils/accessible-metadata.d.ts +0 -5
- package/dist/utils/accessible-metadata.d.ts.map +0 -1
- package/dist/utils/accessible-metadata.js +0 -18
- package/dist/utils/accessible-metadata.js.map +0 -1
- package/src/utils/accessible-metadata.ts +0 -24
|
@@ -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";
|
|
@@ -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 =
|
|
@@ -117,43 +126,50 @@ const teardownList = <P extends GObject.Object, I, H>(
|
|
|
117
126
|
return;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
const remove = hooks.remove;
|
|
121
|
-
|
|
122
|
-
if (remove === undefined) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
129
|
for (const entry of entries) {
|
|
127
|
-
remove(object, entry.item as I, entry.handle as H);
|
|
130
|
+
hooks.remove?.(object, entry.item as I, entry.handle as H);
|
|
128
131
|
}
|
|
129
132
|
};
|
|
130
133
|
|
|
131
|
-
const
|
|
134
|
+
const listUpdate = <P extends GObject.Object, I, H>(
|
|
132
135
|
prop: string,
|
|
133
136
|
hooks: ListHooks<P, I, H>,
|
|
134
|
-
): ElementBehavior<P> => {
|
|
137
|
+
): NonNullable<ElementBehavior<P>["update"]> => {
|
|
135
138
|
const { add } = hooks;
|
|
136
139
|
|
|
137
|
-
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const raw = next[prop];
|
|
142
|
-
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 : [];
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
if (isDeepEqual(state.snapshot, items)) {
|
|
146
|
+
return [prop];
|
|
147
|
+
}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
teardownList(object, state.entries, hooks);
|
|
150
|
+
state.entries = items.map((item) => ({ item, handle: add?.(object, item as I) }));
|
|
151
|
+
state.snapshot = structuredClone(items);
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
},
|
|
153
|
+
return [prop];
|
|
154
154
|
};
|
|
155
155
|
};
|
|
156
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
|
+
|
|
157
173
|
const flushDeferred = <P extends GObject.Object, V>(
|
|
158
174
|
object: GObject.Object,
|
|
159
175
|
context: unknown,
|
|
@@ -244,6 +260,23 @@ const addRemoveSlot = <C extends GObject.Object, P extends GObject.Object>(
|
|
|
244
260
|
remove: (parent: P, child: C) => void,
|
|
245
261
|
): ElementBehavior => slot<P, C>(slotName, childType, { attach: add, detach: remove });
|
|
246
262
|
|
|
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
|
+
|
|
247
280
|
const adoptedChildrenSlot = <P extends GObject.Object, C extends GObject.Object>(
|
|
248
281
|
childType: string,
|
|
249
282
|
add: (parent: P, item: C) => unknown,
|
|
@@ -275,7 +308,7 @@ const wrappedRow = <W extends Gtk.Widget>(
|
|
|
275
308
|
|
|
276
309
|
const removeWrappedRow = (
|
|
277
310
|
Wrapper: new (props: Props) => Gtk.Widget,
|
|
278
|
-
parent:
|
|
311
|
+
parent: IndexedChildHost<Gtk.Widget>,
|
|
279
312
|
child: Gtk.Widget,
|
|
280
313
|
rows: RowCache,
|
|
281
314
|
): void => {
|
|
@@ -286,7 +319,7 @@ const removeWrappedRow = (
|
|
|
286
319
|
}
|
|
287
320
|
};
|
|
288
321
|
|
|
289
|
-
const wrappingIndexedSlot = <W extends Gtk.Widget, P extends
|
|
322
|
+
const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedChildHost<Gtk.Widget>>(
|
|
290
323
|
Wrapper: new (props: Props) => W,
|
|
291
324
|
setChild: (wrapper: W, inner: Gtk.Widget) => void,
|
|
292
325
|
): ElementBehavior<P> => ({
|
|
@@ -300,7 +333,15 @@ const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedInserter>(
|
|
|
300
333
|
initialize: (): RowCache => new WeakMap(),
|
|
301
334
|
});
|
|
302
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),
|
|
340
|
+
});
|
|
341
|
+
|
|
303
342
|
export {
|
|
343
|
+
applicationCreator,
|
|
344
|
+
childMatcher,
|
|
304
345
|
slot,
|
|
305
346
|
value,
|
|
306
347
|
list,
|
|
@@ -311,5 +352,6 @@ export {
|
|
|
311
352
|
boxSlot,
|
|
312
353
|
addRemoveSlot,
|
|
313
354
|
adoptedChildrenSlot,
|
|
355
|
+
indexedSlot,
|
|
314
356
|
wrappingIndexedSlot,
|
|
315
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,
|
|
@@ -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,
|
|
@@ -76,7 +79,7 @@ const resolveElementNode = (typeName: string, props: Props, dispatch: Dispatch):
|
|
|
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 };
|
|
@@ -9,9 +9,9 @@ type TypeInfo = {
|
|
|
9
9
|
userEventSignals: Set<string>;
|
|
10
10
|
behaviors: ElementBehavior[];
|
|
11
11
|
deferred: Set<string>;
|
|
12
|
+
declaredConstructOnly: Set<string>;
|
|
12
13
|
isLazy: boolean;
|
|
13
14
|
hasFlush: boolean;
|
|
14
|
-
hasMount: boolean;
|
|
15
15
|
constructOnly: Set<string>;
|
|
16
16
|
construct: Set<string>;
|
|
17
17
|
defaults: Record<string, unknown>;
|
|
@@ -70,11 +70,8 @@ const applyBehaviorFlags = (info: TypeInfo, behavior: ElementBehavior): void =>
|
|
|
70
70
|
info.hasFlush = true;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
if (behavior.mount !== undefined) {
|
|
74
|
-
info.hasMount = true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
73
|
addAll(info.deferred, deferredProps(behavior));
|
|
74
|
+
addAll(info.declaredConstructOnly, behavior.constructOnly);
|
|
78
75
|
};
|
|
79
76
|
|
|
80
77
|
const resolveBehaviorFlags = (info: TypeInfo): void => {
|
|
@@ -92,9 +89,9 @@ const buildTypeInfo = (name: string): TypeInfo => {
|
|
|
92
89
|
userEventSignals: new Set(),
|
|
93
90
|
behaviors: [],
|
|
94
91
|
deferred: new Set(),
|
|
92
|
+
declaredConstructOnly: new Set(),
|
|
95
93
|
isLazy: false,
|
|
96
94
|
hasFlush: false,
|
|
97
|
-
hasMount: false,
|
|
98
95
|
constructOnly: new Set(),
|
|
99
96
|
construct: new Set(),
|
|
100
97
|
defaults: {},
|
package/src/reconciler/node.ts
CHANGED
|
@@ -18,10 +18,10 @@ type SignalTarget = {
|
|
|
18
18
|
type PlacedChild = {
|
|
19
19
|
node: PlaceableNode;
|
|
20
20
|
object: GObject.Object;
|
|
21
|
+
typeName: string;
|
|
21
22
|
adopted: GObject.Object | null;
|
|
22
23
|
slot: string;
|
|
23
24
|
behavior: ElementBehavior | null;
|
|
24
|
-
isAttached: boolean;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
type ElementNode = SignalTarget & {
|
|
@@ -111,16 +111,18 @@ const createElementNode = (
|
|
|
111
111
|
|
|
112
112
|
const createTextNode = (text: string): TextNode => ({ kind: TEXT_KIND, text, parent: null });
|
|
113
113
|
|
|
114
|
-
const
|
|
115
|
-
if (node.kind
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return child === undefined ? null : nodeObject(child);
|
|
114
|
+
const leafElement = (node: PlaceableNode): ElementNode | null => {
|
|
115
|
+
if (node.kind !== LAZY_KIND) {
|
|
116
|
+
return node;
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
|
|
119
|
+
const [child] = node.children;
|
|
120
|
+
|
|
121
|
+
return child === undefined ? null : leafElement(child);
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
+
const nodeObject = (node: PlaceableNode): GObject.Object | null => leafElement(node)?.object ?? null;
|
|
125
|
+
|
|
124
126
|
const lazyTarget = (node: LazyNode, adopted: GObject.Object): SignalTarget => ({
|
|
125
127
|
object: adopted,
|
|
126
128
|
handlers: node.handlers,
|
|
@@ -141,6 +143,7 @@ export {
|
|
|
141
143
|
createLazyNode,
|
|
142
144
|
createElementNode,
|
|
143
145
|
createTextNode,
|
|
146
|
+
leafElement,
|
|
144
147
|
nodeObject,
|
|
145
148
|
lazyTarget,
|
|
146
149
|
getOrCreateContext,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as GObject from "@gtkx/gi/gobject";
|
|
2
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import { getOrInsert, indexBeforeOrEnd
|
|
3
|
+
import { getOrInsert, indexBeforeOrEnd } from "@gtkx/utils";
|
|
4
4
|
import type { DetachInfo, ElementBehavior, PlaceInfo } from "./registry.js";
|
|
5
5
|
import { applyAdoptedProps, markFlush } from "./apply-props.js";
|
|
6
6
|
import { typeInfoFor } from "./metadata.js";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getOrCreateContext,
|
|
12
12
|
LAZY_KIND,
|
|
13
13
|
lazyTarget,
|
|
14
|
-
|
|
14
|
+
leafElement,
|
|
15
15
|
type PlaceableNode,
|
|
16
16
|
type PlacedChild,
|
|
17
17
|
} from "./node.js";
|
|
@@ -21,13 +21,20 @@ import { markTextDirty } from "./text.js";
|
|
|
21
21
|
type AttachContext = { parent: ElementNode; entry: PlacedChild; index: number; sibling: GObject.Object | null };
|
|
22
22
|
|
|
23
23
|
const createEntry = (slot: string, node: PlaceableNode): PlacedChild | null => {
|
|
24
|
-
const
|
|
24
|
+
const leaf = leafElement(node);
|
|
25
25
|
|
|
26
|
-
if (
|
|
26
|
+
if (leaf === null) {
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
node,
|
|
32
|
+
object: leaf.object,
|
|
33
|
+
typeName: leaf.typeName,
|
|
34
|
+
adopted: null,
|
|
35
|
+
slot,
|
|
36
|
+
behavior: null,
|
|
37
|
+
};
|
|
31
38
|
};
|
|
32
39
|
|
|
33
40
|
const siblingAt = (entries: PlacedChild[], index: number): GObject.Object | null =>
|
|
@@ -80,7 +87,6 @@ const setObjectSlot = (parent: ElementNode, entry: PlacedChild): void => {
|
|
|
80
87
|
writeSlot(parent, entry, entry.object);
|
|
81
88
|
wireBufferView(entry.node, parent);
|
|
82
89
|
entry.behavior = null;
|
|
83
|
-
entry.isAttached = true;
|
|
84
90
|
};
|
|
85
91
|
|
|
86
92
|
const didAttach = (ctx: AttachContext, behavior: ElementBehavior): boolean => {
|
|
@@ -99,12 +105,19 @@ const didAttach = (ctx: AttachContext, behavior: ElementBehavior): boolean => {
|
|
|
99
105
|
|
|
100
106
|
ctx.entry.behavior = behavior;
|
|
101
107
|
adoptedFrom(ctx.parent, ctx.entry, behavior, claim);
|
|
102
|
-
ctx.entry.isAttached = true;
|
|
103
108
|
applyLazyProps(ctx.entry);
|
|
104
109
|
|
|
105
110
|
return true;
|
|
106
111
|
};
|
|
107
112
|
|
|
113
|
+
const unclaimedChildError = (parentTypeName: string, childTypeName: string): Error =>
|
|
114
|
+
new Error(
|
|
115
|
+
`<${childTypeName}> cannot be a child of <${parentTypeName}>. Pass it to the ` +
|
|
116
|
+
`<${parentTypeName}> prop that takes it, if there is one, portal it to rootElement with createPortal ` +
|
|
117
|
+
`if it does not belong inside <${parentTypeName}>, or register an attach behavior for ` +
|
|
118
|
+
`<${parentTypeName}> with defineElements from "@gtkx/react/config" if it belongs among its children.`,
|
|
119
|
+
);
|
|
120
|
+
|
|
108
121
|
const runAttach = (parent: ElementNode, entry: PlacedChild, index: number, sibling: GObject.Object | null): void => {
|
|
109
122
|
const ctx: AttachContext = { parent, entry, index, sibling };
|
|
110
123
|
|
|
@@ -114,9 +127,11 @@ const runAttach = (parent: ElementNode, entry: PlacedChild, index: number, sibli
|
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
129
|
|
|
117
|
-
if (entry.slot
|
|
118
|
-
|
|
130
|
+
if (entry.slot === DEFAULT_SLOT) {
|
|
131
|
+
throw unclaimedChildError(parent.typeName, entry.typeName);
|
|
119
132
|
}
|
|
133
|
+
|
|
134
|
+
setObjectSlot(parent, entry);
|
|
120
135
|
};
|
|
121
136
|
|
|
122
137
|
const attachEntry = (parent: ElementNode, entry: PlacedChild, index: number, sibling: GObject.Object | null): void => {
|
|
@@ -136,9 +151,7 @@ const runDetach = (parent: ElementNode, entry: PlacedChild): void => {
|
|
|
136
151
|
const behavior = entry.behavior;
|
|
137
152
|
|
|
138
153
|
if (behavior === null) {
|
|
139
|
-
|
|
140
|
-
writeSlot(parent, entry, null);
|
|
141
|
-
}
|
|
154
|
+
writeSlot(parent, entry, null);
|
|
142
155
|
|
|
143
156
|
return;
|
|
144
157
|
}
|
|
@@ -147,12 +160,6 @@ const runDetach = (parent: ElementNode, entry: PlacedChild): void => {
|
|
|
147
160
|
};
|
|
148
161
|
|
|
149
162
|
const detachEntry = (parent: ElementNode, entry: PlacedChild): void => {
|
|
150
|
-
if (!entry.isAttached) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
entry.isAttached = false;
|
|
155
|
-
|
|
156
163
|
applyWrite(() => {
|
|
157
164
|
runDetach(parent, entry);
|
|
158
165
|
});
|
|
@@ -174,13 +181,6 @@ const getPosition = (entries: PlacedChild[], before: PlaceableNode | null): numb
|
|
|
174
181
|
|
|
175
182
|
const placeNew = (parent: ElementNode, entry: PlacedChild, entries: PlacedChild[], index: number): void => {
|
|
176
183
|
attachEntry(parent, entry, index, siblingAt(entries, index));
|
|
177
|
-
|
|
178
|
-
if (!entry.isAttached) {
|
|
179
|
-
remove(entries, entry);
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
184
|
const isInsertedBeforeEnd = index < entries.length - 1;
|
|
185
185
|
const isCannotReorderInPlace = entry.behavior !== null && entry.behavior.reorder === undefined;
|
|
186
186
|
|
|
@@ -33,9 +33,9 @@ type DetachInfo = {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Customizes how one element type places children and applies props. Hooks other than `create` receive the
|
|
36
|
-
* GObject instance; `update
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
36
|
+
* GObject instance; `update` and `flush` also take the private per-node context `initialize` built, and
|
|
37
|
+
* `attach`, `reorder` and `detach` read it off their info object. Subtypes inherit a type's behaviors,
|
|
38
|
+
* except for `create`, which is consulted only for the type it is registered on.
|
|
39
39
|
*/
|
|
40
40
|
type ElementBehavior<T extends GObject.Object = GObject.Object> = {
|
|
41
41
|
/** Builds the GObject from its construct props, for types whose constructor does more than set properties. */
|
|
@@ -54,12 +54,14 @@ type ElementBehavior<T extends GObject.Object = GObject.Object> = {
|
|
|
54
54
|
update?: (object: T, prev: Props, next: Props, context: unknown) => Iterable<string> | undefined;
|
|
55
55
|
/** Runs after the commit that touched the node, once every child has been placed. */
|
|
56
56
|
flush?: (object: T, context: unknown) => void;
|
|
57
|
-
/** Runs once the node and its initial children are attached. */
|
|
58
|
-
mount?: (object: T, context: unknown) => void;
|
|
59
|
-
/** Runs when React deletes the node. */
|
|
60
|
-
unmount?: (object: T, context: unknown) => void;
|
|
61
57
|
/** Props to withhold from the constructor, leaving them for a later hook to apply. */
|
|
62
58
|
deferred?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Props the behavior can only apply while the element is being built, because the library exposes no
|
|
61
|
+
* counterpart to whatever applied them. Changing one after it has been applied throws instead of
|
|
62
|
+
* silently applying the new value on top of the old.
|
|
63
|
+
*/
|
|
64
|
+
constructOnly?: string[];
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
/** A named export in a module, referenced as plain data (the module is never imported at runtime). */
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** The type of the shared {@link rootElement} marker used as a default top-level container. */
|
|
2
|
-
type RootElement = {
|
|
2
|
+
type RootElement = {
|
|
3
|
+
/** Marks a container as the top level rather than a GObject; `isRootElement` tests for it. */
|
|
4
|
+
[ROOT_ELEMENT_BRAND]: true;
|
|
5
|
+
};
|
|
3
6
|
|
|
4
7
|
const ROOT_ELEMENT_BRAND: unique symbol = Symbol.for("gtkx:root-element");
|
|
5
8
|
/** A shared marker value used as the default top-level container for rendering and portals. */
|
package/src/reconciler/root.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ErrorInfo, ReactNode, ReactPortal } from "react";
|
|
|
2
2
|
import * as Gdk from "@gtkx/gi/gdk";
|
|
3
3
|
import { createLogger, type Logger } from "@gtkx/utils";
|
|
4
4
|
import { ConcurrentRoot } from "react-reconciler/constants.js";
|
|
5
|
+
import { injectIntoDevTools } from "./devtools.js";
|
|
5
6
|
import { type Container, reconciler } from "./host-config.js";
|
|
6
7
|
import { rootElement } from "./root-element.js";
|
|
7
8
|
|
|
@@ -15,8 +16,11 @@ type RootErrorCallbacks = {
|
|
|
15
16
|
|
|
16
17
|
type ReconcilerRootOptions = RootErrorCallbacks & { containerInfo: Container };
|
|
17
18
|
|
|
19
|
+
/** A render root whose updates and teardown the caller drives, with error handling left to it. */
|
|
18
20
|
type ReconcilerRoot = {
|
|
21
|
+
/** Mounts an element tree into the container, or updates the tree already mounted there. */
|
|
19
22
|
update: (element: ReactNode) => void;
|
|
23
|
+
/** Hands the root to `teardown`, then stops {@link quit} from unmounting the container. */
|
|
20
24
|
unmount: (teardown: (root: ReconcilerRoot) => Promise<void>) => Promise<void>;
|
|
21
25
|
};
|
|
22
26
|
|
|
@@ -61,6 +65,8 @@ const logCaughtRenderError = (error: unknown): void => {
|
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
const openContainer = (containerInfo: Container, callbacks: RootErrorCallbacks): OpaqueRoot => {
|
|
68
|
+
injectIntoDevTools(reconciler);
|
|
69
|
+
|
|
64
70
|
const container = reconciler.createContainer(
|
|
65
71
|
containerInfo,
|
|
66
72
|
ConcurrentRoot,
|