@gtkx/react 1.0.0-rc.2 → 1.0.0-rc.4
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 +4 -5
- 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 +1 -0
- package/dist/adw/dialog.d.ts.map +1 -1
- package/dist/adw/dialog.js +1 -0
- package/dist/adw/dialog.js.map +1 -1
- package/dist/adw/element-behaviors.js +8 -39
- package/dist/adw/element-behaviors.js.map +1 -1
- package/dist/adw/element-config.d.ts +7 -4
- package/dist/adw/element-config.d.ts.map +1 -1
- package/dist/adw/element-config.js +32 -41
- 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 +1 -1
- 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.d.ts +1 -0
- package/dist/components/application.d.ts.map +1 -1
- package/dist/components/application.js +1 -0
- package/dist/components/application.js.map +1 -1
- package/dist/components/element.d.ts +0 -1
- package/dist/components/element.d.ts.map +1 -1
- package/dist/components/element.js +0 -1
- package/dist/components/element.js.map +1 -1
- package/dist/components/window.d.ts +1 -0
- package/dist/components/window.d.ts.map +1 -1
- package/dist/components/window.js +14 -2
- package/dist/components/window.js.map +1 -1
- package/dist/config.d.ts +5 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/element-behaviors.js +0 -1
- 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 +12 -8
- 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-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-property.d.ts +2 -2
- 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 +17 -7
- package/dist/hooks/use-signal.d.ts.map +1 -1
- package/dist/hooks/use-signal.js +10 -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 +5 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +3 -3
- package/dist/internal.js.map +1 -1
- package/dist/prop-types.d.ts +60 -0
- package/dist/prop-types.d.ts.map +1 -1
- package/dist/prop-types.js.map +1 -1
- package/dist/reconciler/apply-props.d.ts.map +1 -1
- package/dist/reconciler/apply-props.js +12 -6
- package/dist/reconciler/apply-props.js.map +1 -1
- package/dist/reconciler/behaviors.d.ts +0 -15
- package/dist/reconciler/behaviors.d.ts.map +1 -1
- package/dist/reconciler/behaviors.js +12 -22
- package/dist/reconciler/behaviors.js.map +1 -1
- 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 +2 -6
- package/dist/reconciler/host-config.js.map +1 -1
- package/dist/reconciler/instance.js +1 -1
- 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 +7 -7
- package/dist/reconciler/metadata.js.map +1 -1
- package/dist/reconciler/node.d.ts +2 -2
- package/dist/reconciler/node.d.ts.map +1 -1
- package/dist/reconciler/node.js +1 -1
- package/dist/reconciler/node.js.map +1 -1
- package/dist/reconciler/placement.d.ts.map +1 -1
- package/dist/reconciler/placement.js +28 -12
- package/dist/reconciler/placement.js.map +1 -1
- package/dist/reconciler/registry.d.ts +56 -29
- package/dist/reconciler/registry.d.ts.map +1 -1
- package/dist/reconciler/registry.js +27 -26
- package/dist/reconciler/registry.js.map +1 -1
- package/dist/reconciler/root-element.d.ts +0 -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 +8 -22
- package/dist/reconciler/root.d.ts.map +1 -1
- package/dist/reconciler/root.js +5 -23
- package/dist/reconciler/root.js.map +1 -1
- package/dist/reconciler/signals.d.ts +3 -4
- package/dist/reconciler/signals.d.ts.map +1 -1
- package/dist/reconciler/signals.js +14 -26
- package/dist/reconciler/signals.js.map +1 -1
- package/dist/reconciler/text.d.ts.map +1 -1
- package/dist/reconciler/text.js +18 -13
- package/dist/reconciler/text.js.map +1 -1
- package/dist/utils/accessible-metadata.d.ts +0 -7
- package/dist/utils/accessible-metadata.d.ts.map +1 -1
- package/dist/utils/accessible-metadata.js +0 -7
- package/dist/utils/accessible-metadata.js.map +1 -1
- package/dist/utils/accessible-props.d.ts +49 -1
- package/dist/utils/accessible-props.d.ts.map +1 -1
- 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 +6 -0
- package/dist/utils/settings.d.ts.map +1 -1
- package/dist/utils/settings.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 +1 -0
- package/src/adw/element-behaviors.ts +8 -39
- package/src/adw/element-config.ts +33 -41
- package/src/adw/index.ts +1 -0
- package/src/adw/prop-types.ts +1 -1
- package/src/bootstrap.ts +1 -1
- package/src/components/application.tsx +1 -0
- package/src/components/element.tsx +0 -1
- package/src/components/window.tsx +16 -2
- package/src/config.ts +5 -2
- package/src/element-behaviors.ts +0 -1
- package/src/element-config.ts +12 -8
- package/src/hooks/use-bind-setting.ts +8 -7
- package/src/hooks/use-parent-window.ts +3 -1
- package/src/hooks/use-property.ts +2 -2
- package/src/hooks/use-setting.ts +2 -3
- package/src/hooks/use-signal.ts +20 -10
- package/src/index.ts +10 -2
- package/src/internal.ts +5 -3
- package/src/prop-types.ts +86 -5
- package/src/reconciler/apply-props.ts +12 -6
- package/src/reconciler/behaviors.ts +14 -23
- package/src/reconciler/host-config.ts +3 -8
- package/src/reconciler/instance.ts +1 -1
- package/src/reconciler/metadata.ts +8 -8
- package/src/reconciler/node.ts +3 -3
- package/src/reconciler/placement.ts +33 -13
- package/src/reconciler/registry.ts +70 -41
- package/src/reconciler/root-element.ts +0 -1
- package/src/reconciler/root.ts +9 -26
- package/src/reconciler/signals.ts +16 -37
- package/src/reconciler/text.ts +21 -14
- package/src/utils/accessible-metadata.ts +0 -7
- package/src/utils/accessible-props.ts +49 -1
- package/src/utils/ref-prop.ts +0 -6
- package/src/utils/settings.ts +6 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TYPE_INVALID, typeFromName, typeInterfaces, typeName, typeParent } from "@gtkx/runtime";
|
|
2
2
|
import { getOrInsert } from "@gtkx/utils";
|
|
3
|
-
import {
|
|
3
|
+
import { constructOnlyProps, constructProps, defaultProps, signals, userEventSignals } from "virtual:gtkx-config";
|
|
4
4
|
import { deferredProps, type ElementBehavior, ELEMENTS } from "./registry.js";
|
|
5
5
|
|
|
6
6
|
type TypeInfo = {
|
|
@@ -9,7 +9,7 @@ type TypeInfo = {
|
|
|
9
9
|
userEventSignals: Set<string>;
|
|
10
10
|
behaviors: ElementBehavior[];
|
|
11
11
|
deferred: Set<string>;
|
|
12
|
-
|
|
12
|
+
isLazy: boolean;
|
|
13
13
|
hasFlush: boolean;
|
|
14
14
|
hasMount: boolean;
|
|
15
15
|
constructOnly: Set<string>;
|
|
@@ -58,10 +58,10 @@ const addAll = <T>(target: Set<T>, source: Iterable<T> | undefined): void => {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
const accumulateAncestor = (info: TypeInfo, ancestor: string): void => {
|
|
61
|
-
Object.assign(info.signals,
|
|
61
|
+
Object.assign(info.signals, signals[ancestor] ?? {});
|
|
62
62
|
addAll(info.userEventSignals, userEventSignals[ancestor]);
|
|
63
|
-
addAll(info.constructOnly,
|
|
64
|
-
addAll(info.construct,
|
|
63
|
+
addAll(info.constructOnly, constructOnlyProps[ancestor]);
|
|
64
|
+
addAll(info.construct, constructProps[ancestor]);
|
|
65
65
|
info.behaviors.push(...(ELEMENTS[ancestor]?.behaviors ?? []));
|
|
66
66
|
};
|
|
67
67
|
|
|
@@ -92,7 +92,7 @@ const buildTypeInfo = (name: string): TypeInfo => {
|
|
|
92
92
|
userEventSignals: new Set(),
|
|
93
93
|
behaviors: [],
|
|
94
94
|
deferred: new Set(),
|
|
95
|
-
|
|
95
|
+
isLazy: false,
|
|
96
96
|
hasFlush: false,
|
|
97
97
|
hasMount: false,
|
|
98
98
|
constructOnly: new Set(),
|
|
@@ -105,10 +105,10 @@ const buildTypeInfo = (name: string): TypeInfo => {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
resolveBehaviorFlags(info);
|
|
108
|
-
info.
|
|
108
|
+
info.isLazy = chain.some((ancestor) => ELEMENTS[ancestor]?.isLazy === true);
|
|
109
109
|
|
|
110
110
|
for (const ancestor of chain.toReversed()) {
|
|
111
|
-
Object.assign(info.defaults,
|
|
111
|
+
Object.assign(info.defaults, defaultProps[ancestor] ?? {});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
return info;
|
package/src/reconciler/node.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { getOrInsert } from "@gtkx/utils";
|
|
|
5
5
|
import type { ElementBehavior, Props } from "./registry.js";
|
|
6
6
|
|
|
7
7
|
type ContentKind = "label" | "buffer" | "tag" | "anchor";
|
|
8
|
-
type HandlerRecord = { signal: string; handler: SignalHandler; wrapped: SignalHandler;
|
|
8
|
+
type HandlerRecord = { signal: string; handler: SignalHandler; wrapped: SignalHandler; isBlockable: boolean };
|
|
9
9
|
type Dispatch = (fn: () => unknown) => unknown;
|
|
10
10
|
|
|
11
11
|
type SignalTarget = {
|
|
@@ -21,7 +21,7 @@ type PlacedChild = {
|
|
|
21
21
|
adopted: GObject.Object | null;
|
|
22
22
|
slot: string;
|
|
23
23
|
behavior: ElementBehavior | null;
|
|
24
|
-
|
|
24
|
+
isAttached: boolean;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
type ElementNode = SignalTarget & {
|
|
@@ -129,7 +129,7 @@ const lazyTarget = (node: LazyNode, adopted: GObject.Object): SignalTarget => ({
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
const getOrCreateContext = (node: ElementNode, behavior: ElementBehavior): unknown =>
|
|
132
|
-
getOrInsert(node.contexts, behavior, () => behavior.
|
|
132
|
+
getOrInsert(node.contexts, behavior, () => behavior.initialize?.(node.object));
|
|
133
133
|
|
|
134
134
|
export {
|
|
135
135
|
ELEMENT_KIND,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type PlaceableNode,
|
|
16
16
|
type PlacedChild,
|
|
17
17
|
} from "./node.js";
|
|
18
|
+
import { applyWrite } from "./signals.js";
|
|
18
19
|
import { markTextDirty } from "./text.js";
|
|
19
20
|
|
|
20
21
|
type AttachContext = { parent: ElementNode; entry: PlacedChild; index: number; sibling: GObject.Object | null };
|
|
@@ -26,7 +27,7 @@ const createEntry = (slot: string, node: PlaceableNode): PlacedChild | null => {
|
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
return { node, object, adopted: null, slot, behavior: null,
|
|
30
|
+
return { node, object, adopted: null, slot, behavior: null, isAttached: false };
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
const siblingAt = (entries: PlacedChild[], index: number): GObject.Object | null =>
|
|
@@ -69,11 +70,17 @@ const wireBufferView = (node: PlaceableNode, parent: ElementNode): void => {
|
|
|
69
70
|
markTextDirty(node);
|
|
70
71
|
};
|
|
71
72
|
|
|
73
|
+
const writeSlot = (parent: ElementNode, entry: PlacedChild, value: GObject.Object | null): void => {
|
|
74
|
+
applyWrite(() => {
|
|
75
|
+
Reflect.set(parent.object, entry.slot, value);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
72
79
|
const setObjectSlot = (parent: ElementNode, entry: PlacedChild): void => {
|
|
73
|
-
|
|
80
|
+
writeSlot(parent, entry, entry.object);
|
|
74
81
|
wireBufferView(entry.node, parent);
|
|
75
82
|
entry.behavior = null;
|
|
76
|
-
entry.
|
|
83
|
+
entry.isAttached = true;
|
|
77
84
|
};
|
|
78
85
|
|
|
79
86
|
const didAttach = (ctx: AttachContext, behavior: ElementBehavior): boolean => {
|
|
@@ -92,13 +99,13 @@ const didAttach = (ctx: AttachContext, behavior: ElementBehavior): boolean => {
|
|
|
92
99
|
|
|
93
100
|
ctx.entry.behavior = behavior;
|
|
94
101
|
adoptedFrom(ctx.parent, ctx.entry, behavior, claim);
|
|
95
|
-
ctx.entry.
|
|
102
|
+
ctx.entry.isAttached = true;
|
|
96
103
|
applyLazyProps(ctx.entry);
|
|
97
104
|
|
|
98
105
|
return true;
|
|
99
106
|
};
|
|
100
107
|
|
|
101
|
-
const
|
|
108
|
+
const runAttach = (parent: ElementNode, entry: PlacedChild, index: number, sibling: GObject.Object | null): void => {
|
|
102
109
|
const ctx: AttachContext = { parent, entry, index, sibling };
|
|
103
110
|
|
|
104
111
|
for (const behavior of typeInfoFor(parent.typeName).behaviors) {
|
|
@@ -112,6 +119,12 @@ const attachEntry = (parent: ElementNode, entry: PlacedChild, index: number, sib
|
|
|
112
119
|
}
|
|
113
120
|
};
|
|
114
121
|
|
|
122
|
+
const attachEntry = (parent: ElementNode, entry: PlacedChild, index: number, sibling: GObject.Object | null): void => {
|
|
123
|
+
applyWrite(() => {
|
|
124
|
+
runAttach(parent, entry, index, sibling);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
115
128
|
const detachInfo = (entry: PlacedChild, context: unknown): DetachInfo => ({
|
|
116
129
|
slot: entry.slot,
|
|
117
130
|
adopted: entry.adopted,
|
|
@@ -119,17 +132,12 @@ const detachInfo = (entry: PlacedChild, context: unknown): DetachInfo => ({
|
|
|
119
132
|
context,
|
|
120
133
|
});
|
|
121
134
|
|
|
122
|
-
const
|
|
123
|
-
if (!entry.attached) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
entry.attached = false;
|
|
135
|
+
const runDetach = (parent: ElementNode, entry: PlacedChild): void => {
|
|
128
136
|
const behavior = entry.behavior;
|
|
129
137
|
|
|
130
138
|
if (behavior === null) {
|
|
131
139
|
if (entry.slot !== DEFAULT_SLOT) {
|
|
132
|
-
|
|
140
|
+
writeSlot(parent, entry, null);
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
return;
|
|
@@ -138,6 +146,18 @@ const detachEntry = (parent: ElementNode, entry: PlacedChild): void => {
|
|
|
138
146
|
behavior.detach?.(parent.object, entry.object, detachInfo(entry, getOrCreateContext(parent, behavior)));
|
|
139
147
|
};
|
|
140
148
|
|
|
149
|
+
const detachEntry = (parent: ElementNode, entry: PlacedChild): void => {
|
|
150
|
+
if (!entry.isAttached) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
entry.isAttached = false;
|
|
155
|
+
|
|
156
|
+
applyWrite(() => {
|
|
157
|
+
runDetach(parent, entry);
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
141
161
|
const rebuild = (parent: ElementNode, entries: PlacedChild[]): void => {
|
|
142
162
|
for (const entry of entries) {
|
|
143
163
|
detachEntry(parent, entry);
|
|
@@ -155,7 +175,7 @@ const getPosition = (entries: PlacedChild[], before: PlaceableNode | null): numb
|
|
|
155
175
|
const placeNew = (parent: ElementNode, entry: PlacedChild, entries: PlacedChild[], index: number): void => {
|
|
156
176
|
attachEntry(parent, entry, index, siblingAt(entries, index));
|
|
157
177
|
|
|
158
|
-
if (!entry.
|
|
178
|
+
if (!entry.isAttached) {
|
|
159
179
|
remove(entries, entry);
|
|
160
180
|
|
|
161
181
|
return;
|
|
@@ -5,59 +5,86 @@ type Props = Record<string, unknown>;
|
|
|
5
5
|
|
|
6
6
|
/** Per-child values a slot hook receives while placing or moving one child. */
|
|
7
7
|
type PlaceInfo = {
|
|
8
|
+
/** Name of the slot being filled: `children`, or the prop the child was nested under. */
|
|
8
9
|
slot: string;
|
|
10
|
+
/** Position of the child among the slot's children. */
|
|
9
11
|
index: number;
|
|
12
|
+
/** Object of the preceding child in the slot, or null when the child comes first. */
|
|
10
13
|
sibling: GObject.Object | null;
|
|
14
|
+
/** Object adopted for this child by an earlier attach, or null before it has one. */
|
|
11
15
|
adopted: GObject.Object | null;
|
|
16
|
+
/** Props of the child element. */
|
|
12
17
|
props: Props;
|
|
18
|
+
/** Value `initialize` returned for this behavior on the parent. */
|
|
13
19
|
context: unknown;
|
|
14
20
|
};
|
|
15
21
|
|
|
16
22
|
/** Per-child values a slot hook receives while removing one child. */
|
|
17
23
|
type DetachInfo = {
|
|
24
|
+
/** Name of the slot the child is leaving. */
|
|
18
25
|
slot: string;
|
|
26
|
+
/** Object the container adopted for this child, or null when it adopted none. */
|
|
19
27
|
adopted: GObject.Object | null;
|
|
28
|
+
/** Props of the child element. */
|
|
20
29
|
props: Props;
|
|
30
|
+
/** Value `initialize` returned for this behavior on the parent. */
|
|
21
31
|
context: unknown;
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
/**
|
|
25
|
-
* Customizes how one element type places children and applies props.
|
|
26
|
-
* GObject instance
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* are not also set as plain GObject properties. `create` builds the GObject for types whose
|
|
30
|
-
* constructor does more than set properties, and is consulted for its own type only, never
|
|
31
|
-
* inherited by subtypes.
|
|
35
|
+
* Customizes how one element type places children and applies props. Hooks other than `create` receive the
|
|
36
|
+
* GObject instance; `update`, `flush`, `mount` and `unmount` also take the private per-node context
|
|
37
|
+
* `initialize` built, and `attach`, `reorder` and `detach` read it off their info object. Subtypes inherit a
|
|
38
|
+
* type's behaviors, except for `create`, which is consulted only for the type it is registered on.
|
|
32
39
|
*/
|
|
33
40
|
type ElementBehavior<T extends GObject.Object = GObject.Object> = {
|
|
41
|
+
/** Builds the GObject from its construct props, for types whose constructor does more than set properties. */
|
|
34
42
|
create?: (props: Props) => GObject.Object;
|
|
35
|
-
|
|
43
|
+
/** Builds the private per-node context the other hooks receive, once per node. */
|
|
44
|
+
initialize?: (object: T) => unknown;
|
|
45
|
+
/** Places a child, claiming it by returning anything other than `undefined`. */
|
|
36
46
|
attach?: (object: T, child: GObject.Object, info: PlaceInfo) => unknown;
|
|
47
|
+
/** Moves an already-attached child; without it, the whole slot is detached and re-attached in order. */
|
|
37
48
|
reorder?: (object: T, child: GObject.Object, info: PlaceInfo) => unknown;
|
|
49
|
+
/** Removes a child this behavior attached. */
|
|
38
50
|
detach?: (object: T, child: GObject.Object, info: DetachInfo) => void;
|
|
51
|
+
/** Returns the object the container adopts for a child, overriding whatever `attach` returned. */
|
|
39
52
|
resolve?: (object: T, child: GObject.Object) => GObject.Object | null;
|
|
53
|
+
/** Applies changed props and returns the names it consumed, which are then not set as GObject properties. */
|
|
40
54
|
update?: (object: T, prev: Props, next: Props, context: unknown) => Iterable<string> | undefined;
|
|
55
|
+
/** Runs after the commit that touched the node, once every child has been placed. */
|
|
41
56
|
flush?: (object: T, context: unknown) => void;
|
|
57
|
+
/** Runs once the node and its initial children are attached. */
|
|
42
58
|
mount?: (object: T, context: unknown) => void;
|
|
59
|
+
/** Runs when React deletes the node. */
|
|
43
60
|
unmount?: (object: T, context: unknown) => void;
|
|
61
|
+
/** Props to withhold from the constructor, leaving them for a later hook to apply. */
|
|
44
62
|
deferred?: string[];
|
|
45
63
|
};
|
|
46
64
|
|
|
47
65
|
/** A named export in a module, referenced as plain data (the module is never imported at runtime). */
|
|
48
|
-
type ModuleExport = {
|
|
66
|
+
type ModuleExport = {
|
|
67
|
+
/** Specifier the export is imported from. */
|
|
68
|
+
module: string;
|
|
69
|
+
/** Identifier the module exports it under. */
|
|
70
|
+
export: string;
|
|
71
|
+
};
|
|
49
72
|
|
|
50
73
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* its type, an optional component that wraps the generated element, and the base props interface its
|
|
54
|
-
* generated props extend. `component` and `props` are inert at runtime; they are read only by codegen.
|
|
74
|
+
* How one GLib type is rendered. `component`, `props` and `omittedProps` are inert at runtime; they are
|
|
75
|
+
* read only by codegen.
|
|
55
76
|
*/
|
|
56
77
|
type ElementConfig<T extends GObject.Object = GObject.Object> = {
|
|
57
|
-
|
|
78
|
+
/** The element has no GObject of its own; its parent container creates one, as it does for pages. */
|
|
79
|
+
isLazy?: boolean;
|
|
80
|
+
/** Behaviors bound to the type, consulted in registration order and inherited by its subtypes. */
|
|
58
81
|
behaviors?: ElementBehavior<T>[];
|
|
82
|
+
/** Component that wraps the generated element. */
|
|
59
83
|
component?: ModuleExport;
|
|
84
|
+
/** Base props interface the generated props extend. */
|
|
60
85
|
props?: ModuleExport;
|
|
86
|
+
/** GObject properties to leave out of the generated props, such as those a behavior writes from children. */
|
|
87
|
+
omittedProps?: string[];
|
|
61
88
|
};
|
|
62
89
|
|
|
63
90
|
/**
|
|
@@ -66,7 +93,6 @@ type ElementConfig<T extends GObject.Object = GObject.Object> = {
|
|
|
66
93
|
*/
|
|
67
94
|
const ELEMENTS: Record<string, ElementConfig> = {};
|
|
68
95
|
|
|
69
|
-
/** Props a behavior applies after construction; the constructor is never given them. */
|
|
70
96
|
const deferredProps = (behavior: ElementBehavior): string[] => behavior.deferred ?? [];
|
|
71
97
|
|
|
72
98
|
const mergeBehaviors = (base: ElementConfig, added: ElementBehavior[], isPrepended: boolean): ElementBehavior[] => {
|
|
@@ -76,22 +102,19 @@ const mergeBehaviors = (base: ElementConfig, added: ElementBehavior[], isPrepend
|
|
|
76
102
|
};
|
|
77
103
|
|
|
78
104
|
const mergeConfigEntry = (base: ElementConfig, added: ElementConfig<never>, isPrepended = false): ElementConfig => {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (added.behaviors !== undefined) {
|
|
82
|
-
entry.behaviors = mergeBehaviors(entry, added.behaviors as ElementBehavior[], isPrepended);
|
|
83
|
-
}
|
|
105
|
+
const { behaviors, isLazy, omittedProps, ...rest } = added;
|
|
106
|
+
const entry: ElementConfig = { ...base, ...rest };
|
|
84
107
|
|
|
85
|
-
if (
|
|
86
|
-
entry.
|
|
108
|
+
if (behaviors !== undefined) {
|
|
109
|
+
entry.behaviors = mergeBehaviors(base, behaviors as ElementBehavior[], isPrepended);
|
|
87
110
|
}
|
|
88
111
|
|
|
89
|
-
if (
|
|
90
|
-
entry.
|
|
112
|
+
if (omittedProps !== undefined) {
|
|
113
|
+
entry.omittedProps = [...(base.omittedProps ?? []), ...omittedProps];
|
|
91
114
|
}
|
|
92
115
|
|
|
93
|
-
if (
|
|
94
|
-
entry.
|
|
116
|
+
if (isLazy === true) {
|
|
117
|
+
entry.isLazy = true;
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
return entry;
|
|
@@ -99,8 +122,9 @@ const mergeConfigEntry = (base: ElementConfig, added: ElementConfig<never>, isPr
|
|
|
99
122
|
|
|
100
123
|
/**
|
|
101
124
|
* Merges maps of {@link ElementConfig} keyed by GLib type name into one, concatenating each type's
|
|
102
|
-
* behaviors in the order the maps are given (an earlier map's behaviors come first)
|
|
103
|
-
*
|
|
125
|
+
* behaviors and omitted props in the order the maps are given (an earlier map's behaviors come first),
|
|
126
|
+
* taking the last component and props seen, and marking a type lazy if any map does. Use it to combine
|
|
127
|
+
* an app's element config with its behaviors.
|
|
104
128
|
*/
|
|
105
129
|
const mergeElementConfigs = (...maps: Record<string, ElementConfig<never>>[]): Record<string, ElementConfig> => {
|
|
106
130
|
const merged: Record<string, ElementConfig> = {};
|
|
@@ -114,34 +138,38 @@ const mergeElementConfigs = (...maps: Record<string, ElementConfig<never>>[]): R
|
|
|
114
138
|
return merged;
|
|
115
139
|
};
|
|
116
140
|
|
|
117
|
-
/**
|
|
118
|
-
* Registers a map of {@link ElementConfig} keyed by GLib type name, merging each entry into the
|
|
119
|
-
* registry. Behaviors are appended by default (the framework's built-ins register this way); pass
|
|
120
|
-
* `{ prepend: true }` for an app's own configuration so its behaviors are consulted before the
|
|
121
|
-
* built-ins for the same slot, letting it override them regardless of registration order.
|
|
122
|
-
*/
|
|
123
141
|
const registerElements = (
|
|
124
142
|
map: Record<string, ElementConfig<never>>,
|
|
125
|
-
options: {
|
|
143
|
+
options: { isPrepended?: boolean } = {},
|
|
126
144
|
): void => {
|
|
127
145
|
for (const [type, config] of Object.entries(map)) {
|
|
128
|
-
ELEMENTS[type] = mergeConfigEntry(ELEMENTS[type] ?? {}, config, options.
|
|
146
|
+
ELEMENTS[type] = mergeConfigEntry(ELEMENTS[type] ?? {}, config, options.isPrepended === true);
|
|
129
147
|
}
|
|
130
148
|
};
|
|
131
149
|
|
|
132
150
|
/**
|
|
133
|
-
* Identity helper that types the module named by
|
|
134
|
-
* editor autocompletion and type checking. Key each entry by GLib type name and
|
|
135
|
-
*
|
|
151
|
+
* Identity helper that types the module named by `elements.behaviors` in `gtkx.config.ts`, enabling
|
|
152
|
+
* editor autocompletion and type checking. Key each entry by GLib type name and write each behavior
|
|
153
|
+
* with {@link defineBehavior} so its hooks receive the concrete GObject class.
|
|
136
154
|
*/
|
|
137
155
|
const defineElements = (elements: Record<string, ElementConfig<never>>): Record<string, ElementConfig<never>> =>
|
|
138
156
|
elements;
|
|
139
157
|
|
|
140
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Types one behavior against the GObject class it applies to, so every hook's object parameter is
|
|
160
|
+
* inferred rather than annotated by hand. Pass the class as the type argument.
|
|
161
|
+
*
|
|
162
|
+
* ```ts
|
|
163
|
+
* defineElements({
|
|
164
|
+
* GtkFrame: { behaviors: [defineBehavior<Gtk.Frame>({ attach: (frame, child) => frame.setChild(child) })] },
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
const defineBehavior = <T extends GObject.Object>(hooks: ElementBehavior<T>): ElementBehavior<never> => hooks;
|
|
169
|
+
|
|
141
170
|
const forTypes = (types: string[], config: ElementConfig<never>): Record<string, ElementConfig<never>> =>
|
|
142
171
|
Object.fromEntries(types.map((type) => [type, config]));
|
|
143
172
|
|
|
144
|
-
/** References a base props interface exported from `@gtkx/react/internal`. */
|
|
145
173
|
const internal = (name: string): ModuleExport => ({ module: "@gtkx/react/internal", export: name });
|
|
146
174
|
|
|
147
175
|
export {
|
|
@@ -150,6 +178,7 @@ export {
|
|
|
150
178
|
mergeElementConfigs,
|
|
151
179
|
registerElements,
|
|
152
180
|
defineElements,
|
|
181
|
+
defineBehavior,
|
|
153
182
|
forTypes,
|
|
154
183
|
internal,
|
|
155
184
|
type Props,
|
|
@@ -5,7 +5,6 @@ const ROOT_ELEMENT_BRAND: unique symbol = Symbol.for("gtkx:root-element");
|
|
|
5
5
|
/** A shared marker value used as the default top-level container for rendering and portals. */
|
|
6
6
|
const rootElement: RootElement = { [ROOT_ELEMENT_BRAND]: true };
|
|
7
7
|
|
|
8
|
-
/** Reports whether an arbitrary value is a {@link rootElement} marker. */
|
|
9
8
|
const isRootElement = (value: unknown): value is RootElement =>
|
|
10
9
|
typeof value === "object" && value !== null && Object.hasOwn(value, ROOT_ELEMENT_BRAND);
|
|
11
10
|
|
package/src/reconciler/root.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { ErrorInfo, ReactNode } from "react";
|
|
1
|
+
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
5
|
import { type Container, reconciler } from "./host-config.js";
|
|
6
6
|
import { rootElement } from "./root-element.js";
|
|
7
|
-
import { endSuppressionNow } from "./signals.js";
|
|
8
7
|
|
|
9
8
|
type OpaqueRoot = { [opaqueRoot]: true };
|
|
10
9
|
|
|
@@ -16,7 +15,6 @@ type RootErrorCallbacks = {
|
|
|
16
15
|
|
|
17
16
|
type ReconcilerRootOptions = RootErrorCallbacks & { containerInfo: Container };
|
|
18
17
|
|
|
19
|
-
/** A root that mounts an element tree into an explicit container and reports render errors. */
|
|
20
18
|
type ReconcilerRoot = {
|
|
21
19
|
update: (element: ReactNode) => void;
|
|
22
20
|
unmount: (teardown: (root: ReconcilerRoot) => Promise<void>) => Promise<void>;
|
|
@@ -24,7 +22,9 @@ type ReconcilerRoot = {
|
|
|
24
22
|
|
|
25
23
|
/** The object {@link createRoot} returns: it renders an element tree into a container and can tear it down. */
|
|
26
24
|
type Root = {
|
|
25
|
+
/** Mounts an element tree into the container, or updates the tree already mounted there. */
|
|
27
26
|
render: (element: ReactNode) => void;
|
|
27
|
+
/** Unmounts the rendered tree and runs its effect cleanups. */
|
|
28
28
|
unmount: () => void;
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -50,21 +50,13 @@ function createErrorHandlerSlot(): ErrorHandlerSlot {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
* Installs a process-wide handler for errors thrown while rendering or applying an update.
|
|
55
|
-
*
|
|
56
|
-
* @param handler The handler to install.
|
|
57
|
-
* @returns The previously installed handler, or null.
|
|
58
|
-
*/
|
|
59
53
|
const setReconcilerErrorHandler = (handler: ErrorHandler): ErrorHandler | null => errorHandlerSlot.set(handler);
|
|
60
54
|
|
|
61
55
|
const rethrowUncaughtRenderError = (error: unknown): never => {
|
|
62
|
-
endSuppressionNow();
|
|
63
56
|
throw error;
|
|
64
57
|
};
|
|
65
58
|
|
|
66
59
|
const logCaughtRenderError = (error: unknown): void => {
|
|
67
|
-
endSuppressionNow();
|
|
68
60
|
log.error("caught render error", error);
|
|
69
61
|
};
|
|
70
62
|
|
|
@@ -100,12 +92,6 @@ const unmountContainer = (container: OpaqueRoot): void => {
|
|
|
100
92
|
activeRoots.delete(container);
|
|
101
93
|
};
|
|
102
94
|
|
|
103
|
-
/**
|
|
104
|
-
* Creates a root that mounts an element tree into a container, routing render errors to the supplied callbacks.
|
|
105
|
-
*
|
|
106
|
-
* @param options The container to render into and the error callbacks to route failures to.
|
|
107
|
-
* @returns A {@link ReconcilerRoot}.
|
|
108
|
-
*/
|
|
109
95
|
const createReconcilerRoot = (options: ReconcilerRootOptions): ReconcilerRoot => {
|
|
110
96
|
const container = openContainer(options.containerInfo, options);
|
|
111
97
|
|
|
@@ -123,10 +109,10 @@ const createReconcilerRoot = (options: ReconcilerRootOptions): ReconcilerRoot =>
|
|
|
123
109
|
};
|
|
124
110
|
|
|
125
111
|
/**
|
|
126
|
-
* Creates a render root for a GTKX application.
|
|
112
|
+
* Creates a render root for a GTKX application. Uncaught render errors are rethrown and errors caught by an
|
|
113
|
+
* error boundary are logged.
|
|
127
114
|
*
|
|
128
|
-
* @param container The
|
|
129
|
-
* @returns A {@link Root} exposing render and unmount.
|
|
115
|
+
* @param container The GObject to render into; defaults to the shared {@link rootElement}, which holds no object.
|
|
130
116
|
*/
|
|
131
117
|
const createRoot = (container: Container = rootElement): Root => {
|
|
132
118
|
const opaque = openContainer(container, {
|
|
@@ -156,13 +142,10 @@ const quit = (): typeof Gdk.EVENT_STOP => {
|
|
|
156
142
|
/**
|
|
157
143
|
* Renders children into a container other than the surrounding tree.
|
|
158
144
|
*
|
|
159
|
-
* @param
|
|
160
|
-
* @param container The GObject, application, or {@link rootElement} to render into.
|
|
161
|
-
* @param key An optional stable key.
|
|
162
|
-
* @returns A React portal.
|
|
145
|
+
* @param container The GObject to render into, or {@link rootElement} to render at the top level.
|
|
163
146
|
*/
|
|
164
|
-
const createPortal = (children: ReactNode, container: Container, key?: string):
|
|
165
|
-
|
|
147
|
+
const createPortal = (children: ReactNode, container: Container, key?: string): ReactPortal =>
|
|
148
|
+
reconciler.createPortal(children, container, null, key ?? null) as unknown as ReactPortal;
|
|
166
149
|
|
|
167
150
|
export {
|
|
168
151
|
setReconcilerErrorHandler,
|
|
@@ -3,40 +3,20 @@ import { camelCase } from "@gtkx/utils";
|
|
|
3
3
|
import type { HandlerRecord, SignalTarget } from "./node.js";
|
|
4
4
|
import { type TypeInfo, typeInfoFor } from "./metadata.js";
|
|
5
5
|
|
|
6
|
-
type SuppressionCounter = {
|
|
7
|
-
beginSuppression: () => void;
|
|
8
|
-
endSuppression: () => void;
|
|
9
|
-
endSuppressionNow: () => void;
|
|
10
|
-
isSuppressed: () => boolean;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
6
|
const NOTIFY_DETAIL_PREFIX = "notify::";
|
|
14
|
-
const
|
|
15
|
-
const beginSuppression: () => void = suppressionCounter.beginSuppression;
|
|
16
|
-
const endSuppression: () => void = suppressionCounter.endSuppression;
|
|
17
|
-
const endSuppressionNow: () => void = suppressionCounter.endSuppressionNow;
|
|
18
|
-
const isSuppressed: () => boolean = suppressionCounter.isSuppressed;
|
|
19
|
-
|
|
20
|
-
function createSuppressionCounter(): SuppressionCounter {
|
|
21
|
-
let depth = 0;
|
|
22
|
-
|
|
23
|
-
const release = (): void => {
|
|
24
|
-
if (depth > 0) {
|
|
25
|
-
depth -= 1;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
7
|
+
const writeState: { depth: number } = { depth: 0 };
|
|
28
8
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
}
|
|
9
|
+
const isApplyingWrite = (): boolean => writeState.depth > 0;
|
|
10
|
+
|
|
11
|
+
const applyWrite = <T>(write: () => T): T => {
|
|
12
|
+
writeState.depth += 1;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return write();
|
|
16
|
+
} finally {
|
|
17
|
+
writeState.depth = Math.max(0, writeState.depth - 1);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
40
20
|
|
|
41
21
|
const getNotifyProperty = (signal: string): string | null =>
|
|
42
22
|
signal.startsWith(NOTIFY_DETAIL_PREFIX) ? camelCase(signal.slice(NOTIFY_DETAIL_PREFIX.length)) : null;
|
|
@@ -56,7 +36,7 @@ const invokeHandler = (
|
|
|
56
36
|
|
|
57
37
|
const wrapHandler = (target: SignalTarget, record: HandlerRecord, notifyProperty: string | null): SignalHandler =>
|
|
58
38
|
(...args: unknown[]): unknown => {
|
|
59
|
-
if (record.
|
|
39
|
+
if (record.isBlockable && isApplyingWrite()) {
|
|
60
40
|
return undefined;
|
|
61
41
|
}
|
|
62
42
|
|
|
@@ -78,7 +58,7 @@ const connectHandler = (target: SignalTarget, prop: string, signal: string, hand
|
|
|
78
58
|
|
|
79
59
|
const isBlockable = isBlockableSignal(typeInfoFor(target.typeName), signal);
|
|
80
60
|
const notifyProperty = getNotifyProperty(signal);
|
|
81
|
-
const record: HandlerRecord = { signal, handler, wrapped: (): undefined => undefined,
|
|
61
|
+
const record: HandlerRecord = { signal, handler, wrapped: (): undefined => undefined, isBlockable };
|
|
82
62
|
record.wrapped = wrapHandler(target, record, notifyProperty);
|
|
83
63
|
target.object.on(signal, record.wrapped);
|
|
84
64
|
target.handlers.set(prop, record);
|
|
@@ -104,9 +84,8 @@ const disconnectAllHandlers = (target: SignalTarget): void => {
|
|
|
104
84
|
};
|
|
105
85
|
|
|
106
86
|
export {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
endSuppressionNow,
|
|
87
|
+
applyWrite,
|
|
88
|
+
isApplyingWrite,
|
|
110
89
|
connectHandler,
|
|
111
90
|
disconnectHandler,
|
|
112
91
|
disconnectAllHandlers,
|