@gtkx/react 1.2.2 → 1.4.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 +6 -4
- package/dist/adw/dialog.js +2 -2
- package/dist/adw/dialog.js.map +1 -1
- package/dist/components/application.d.ts.map +1 -1
- package/dist/components/application.js +3 -3
- package/dist/components/application.js.map +1 -1
- package/dist/components/window.js +3 -3
- package/dist/components/window.js.map +1 -1
- package/dist/element-behaviors.js +130 -1
- package/dist/element-behaviors.js.map +1 -1
- package/dist/element-config.js +1 -1
- package/dist/element-config.js.map +1 -1
- package/dist/hooks/use-bind-setting.d.ts.map +1 -1
- package/dist/hooks/use-bind-setting.js +3 -2
- package/dist/hooks/use-bind-setting.js.map +1 -1
- package/dist/hooks/use-property.d.ts +1 -1
- package/dist/hooks/use-property.d.ts.map +1 -1
- package/dist/hooks/use-property.js +5 -1
- package/dist/hooks/use-property.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/prop-types.d.ts +20 -2
- package/dist/prop-types.d.ts.map +1 -1
- package/dist/prop-types.js.map +1 -1
- package/dist/reconciler/apply-props.d.ts +2 -1
- package/dist/reconciler/apply-props.d.ts.map +1 -1
- package/dist/reconciler/apply-props.js +60 -24
- package/dist/reconciler/apply-props.js.map +1 -1
- package/dist/reconciler/behaviors.d.ts.map +1 -1
- package/dist/reconciler/behaviors.js +40 -9
- package/dist/reconciler/behaviors.js.map +1 -1
- package/dist/reconciler/host-config.d.ts.map +1 -1
- package/dist/reconciler/host-config.js +10 -2
- package/dist/reconciler/host-config.js.map +1 -1
- package/dist/reconciler/metadata.d.ts +6 -1
- package/dist/reconciler/metadata.d.ts.map +1 -1
- package/dist/reconciler/metadata.js +39 -6
- package/dist/reconciler/metadata.js.map +1 -1
- package/dist/reconciler/placement.js +4 -4
- package/dist/reconciler/placement.js.map +1 -1
- package/dist/reconciler/registry.d.ts +2 -0
- package/dist/reconciler/registry.d.ts.map +1 -1
- package/dist/reconciler/registry.js.map +1 -1
- package/dist/reconciler/root.d.ts.map +1 -1
- package/dist/reconciler/root.js.map +1 -1
- package/dist/reconciler/signals.d.ts +3 -2
- package/dist/reconciler/signals.d.ts.map +1 -1
- package/dist/reconciler/signals.js +49 -16
- package/dist/reconciler/signals.js.map +1 -1
- package/dist/reconciler/style.d.ts +7 -0
- package/dist/reconciler/style.d.ts.map +1 -0
- package/dist/reconciler/style.js +61 -0
- package/dist/reconciler/style.js.map +1 -0
- package/dist/reconciler/text.js +3 -3
- package/dist/reconciler/text.js.map +1 -1
- package/dist/utils/accessible-props.d.ts.map +1 -1
- package/dist/utils/accessible-props.js +13 -7
- package/dist/utils/accessible-props.js.map +1 -1
- package/env.d.ts +10 -7
- package/package.json +6 -5
- package/src/adw/dialog.tsx +2 -2
- package/src/components/application.tsx +18 -3
- package/src/components/window.tsx +3 -3
- package/src/element-behaviors.ts +180 -1
- package/src/element-config.ts +1 -1
- package/src/hooks/use-bind-setting.ts +3 -2
- package/src/hooks/use-property.ts +6 -2
- package/src/internal.ts +2 -0
- package/src/prop-types.ts +96 -1
- package/src/reconciler/apply-props.ts +74 -26
- package/src/reconciler/behaviors.ts +60 -12
- package/src/reconciler/host-config.ts +13 -2
- package/src/reconciler/metadata.ts +62 -6
- package/src/reconciler/placement.ts +4 -4
- package/src/reconciler/registry.ts +2 -0
- package/src/reconciler/root.ts +1 -1
- package/src/reconciler/signals.ts +62 -17
- package/src/reconciler/style.ts +84 -0
- package/src/reconciler/text.ts +3 -3
- package/src/utils/accessible-props.ts +15 -7
|
@@ -1,25 +1,67 @@
|
|
|
1
|
+
import * as GObject from "@gtkx/gi/gobject";
|
|
1
2
|
import { getSignalBaseName, type SignalHandler } from "@gtkx/runtime";
|
|
2
|
-
import {
|
|
3
|
+
import { getOrInsert, toCamelIdentifier } from "@gtkx/utils";
|
|
3
4
|
import type { HandlerRecord, SignalTarget } from "./node.js";
|
|
4
5
|
import { type TypeInfo, typeInfoFor } from "./metadata.js";
|
|
5
6
|
|
|
7
|
+
type NotifyBinding = { property: string | null };
|
|
8
|
+
|
|
9
|
+
const NOTIFY_SIGNAL = "notify";
|
|
6
10
|
const NOTIFY_DETAIL_PREFIX = "notify::";
|
|
7
|
-
const
|
|
11
|
+
const pendingWrites: (string | null)[] = [];
|
|
12
|
+
const canonicalNames: Map<string, string> = new Map();
|
|
8
13
|
|
|
9
|
-
const isApplyingWrite = (): boolean =>
|
|
14
|
+
const isApplyingWrite = (): boolean => pendingWrites.length > 0;
|
|
15
|
+
const canonicalName = (property: string): string => getOrInsert(canonicalNames, property, toCamelIdentifier);
|
|
10
16
|
|
|
11
|
-
const
|
|
12
|
-
|
|
17
|
+
const runWrite = <T>(property: string | null, write: () => T): T => {
|
|
18
|
+
pendingWrites.push(property === null ? null : canonicalName(property));
|
|
13
19
|
|
|
14
20
|
try {
|
|
15
21
|
return write();
|
|
16
22
|
} finally {
|
|
17
|
-
|
|
23
|
+
pendingWrites.pop();
|
|
18
24
|
}
|
|
19
25
|
};
|
|
20
26
|
|
|
21
|
-
const
|
|
22
|
-
|
|
27
|
+
const applyWrite = <T>(property: string, write: () => T): T => runWrite(property, write);
|
|
28
|
+
const applyMutation = <T>(write: () => T): T => runWrite(null, write);
|
|
29
|
+
|
|
30
|
+
const notifyBindingFor = (signal: string): NotifyBinding | null => {
|
|
31
|
+
if (signal === NOTIFY_SIGNAL) {
|
|
32
|
+
return { property: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!signal.startsWith(NOTIFY_DETAIL_PREFIX)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { property: canonicalName(signal.slice(NOTIFY_DETAIL_PREFIX.length)) };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const notifiedProperty = (notify: NotifyBinding, args: unknown[]): string | null => {
|
|
43
|
+
if (notify.property !== null) {
|
|
44
|
+
return notify.property;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const [pspec] = args;
|
|
48
|
+
|
|
49
|
+
return pspec instanceof GObject.ParamSpec ? canonicalName(pspec.getName()) : null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isSuppressedNotify = (notify: NotifyBinding, args: unknown[]): boolean => {
|
|
53
|
+
const notified = notifiedProperty(notify, args);
|
|
54
|
+
|
|
55
|
+
return notified === null || pendingWrites.includes(null) || pendingWrites.includes(notified);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isSuppressed = (record: HandlerRecord, notify: NotifyBinding | null, args: unknown[]): boolean => {
|
|
59
|
+
if (!record.isBlockable || !isApplyingWrite()) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return notify === null || isSuppressedNotify(notify, args);
|
|
64
|
+
};
|
|
23
65
|
|
|
24
66
|
const isBlockableSignal = (info: TypeInfo, signal: string): boolean =>
|
|
25
67
|
info.userEventSignals.has(getSignalBaseName(signal));
|
|
@@ -27,20 +69,23 @@ const isBlockableSignal = (info: TypeInfo, signal: string): boolean =>
|
|
|
27
69
|
const invokeHandler = (
|
|
28
70
|
target: SignalTarget,
|
|
29
71
|
record: HandlerRecord,
|
|
30
|
-
|
|
72
|
+
notify: NotifyBinding | null,
|
|
31
73
|
args: unknown[],
|
|
32
|
-
): unknown =>
|
|
33
|
-
|
|
74
|
+
): unknown => {
|
|
75
|
+
const property = notify?.property ?? null;
|
|
76
|
+
|
|
77
|
+
return property === null
|
|
34
78
|
? record.handler(...args, target.object)
|
|
35
|
-
: record.handler(Reflect.get(target.object,
|
|
79
|
+
: record.handler(Reflect.get(target.object, property), target.object);
|
|
80
|
+
};
|
|
36
81
|
|
|
37
|
-
const wrapHandler = (target: SignalTarget, record: HandlerRecord,
|
|
82
|
+
const wrapHandler = (target: SignalTarget, record: HandlerRecord, notify: NotifyBinding | null): SignalHandler =>
|
|
38
83
|
(...args: unknown[]): unknown => {
|
|
39
|
-
if (record
|
|
84
|
+
if (isSuppressed(record, notify, args)) {
|
|
40
85
|
return undefined;
|
|
41
86
|
}
|
|
42
87
|
|
|
43
|
-
return target.dispatch(() => invokeHandler(target, record,
|
|
88
|
+
return target.dispatch(() => invokeHandler(target, record, notify, args));
|
|
44
89
|
};
|
|
45
90
|
|
|
46
91
|
const connectHandler = (target: SignalTarget, prop: string, signal: string, handler: SignalHandler): void => {
|
|
@@ -57,9 +102,8 @@ const connectHandler = (target: SignalTarget, prop: string, signal: string, hand
|
|
|
57
102
|
}
|
|
58
103
|
|
|
59
104
|
const isBlockable = isBlockableSignal(typeInfoFor(target.typeName), signal);
|
|
60
|
-
const notifyProperty = getNotifyProperty(signal);
|
|
61
105
|
const record: HandlerRecord = { signal, handler, wrapped: (): undefined => undefined, isBlockable };
|
|
62
|
-
record.wrapped = wrapHandler(target, record,
|
|
106
|
+
record.wrapped = wrapHandler(target, record, notifyBindingFor(signal));
|
|
63
107
|
target.object.on(signal, record.wrapped);
|
|
64
108
|
target.handlers.set(prop, record);
|
|
65
109
|
};
|
|
@@ -84,6 +128,7 @@ const disconnectAllHandlers = (target: SignalTarget): void => {
|
|
|
84
128
|
};
|
|
85
129
|
|
|
86
130
|
export {
|
|
131
|
+
applyMutation,
|
|
87
132
|
applyWrite,
|
|
88
133
|
connectHandler,
|
|
89
134
|
disconnectHandler,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { attachParsingErrorLogger, registerProviderForDefaultDisplay, scopedRule } from "@gtkx/css/internal";
|
|
3
|
+
import { STYLE_PROVIDER_PRIORITY_APPLICATION } from "@gtkx/gi/gtk";
|
|
4
|
+
import { createLogger, type Logger } from "@gtkx/utils";
|
|
5
|
+
import { applyWrite } from "./signals.js";
|
|
6
|
+
|
|
7
|
+
type StyleSlot = { provider: Gtk.CssProvider; className: string; css: string };
|
|
8
|
+
|
|
9
|
+
const CLASS_PREFIX = "gtkx-s";
|
|
10
|
+
const CSS_CLASSES_PROP = "cssClasses";
|
|
11
|
+
const STYLE_PRIORITY = STYLE_PROVIDER_PRIORITY_APPLICATION + 1;
|
|
12
|
+
const log: Logger = createLogger("react");
|
|
13
|
+
const styles: WeakMap<Gtk.Widget, StyleSlot> = new WeakMap();
|
|
14
|
+
const pool: StyleSlot[] = [];
|
|
15
|
+
const counter: { next: number } = { next: 0 };
|
|
16
|
+
|
|
17
|
+
const reclaimed: FinalizationRegistry<StyleSlot> = new FinalizationRegistry((slot) => {
|
|
18
|
+
pool.push(slot);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const createSlot = (): StyleSlot => {
|
|
22
|
+
counter.next += 1;
|
|
23
|
+
const provider = registerProviderForDefaultDisplay({ priority: STYLE_PRIORITY, followsPreferences: false });
|
|
24
|
+
attachParsingErrorLogger(provider, log, "a style prop");
|
|
25
|
+
|
|
26
|
+
return { provider, className: `${CLASS_PREFIX}${counter.next.toString()}`, css: "" };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const styleClass = (widget: Gtk.Widget): string | null => styles.get(widget)?.className ?? null;
|
|
30
|
+
|
|
31
|
+
const releaseStyle = (widget: Gtk.Widget): void => {
|
|
32
|
+
const slot = styles.get(widget);
|
|
33
|
+
|
|
34
|
+
if (slot === undefined) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
styles.delete(widget);
|
|
39
|
+
reclaimed.unregister(widget);
|
|
40
|
+
|
|
41
|
+
applyWrite(CSS_CLASSES_PROP, () => {
|
|
42
|
+
widget.removeCssClass(slot.className);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
pool.push(slot);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ensureSlot = (widget: Gtk.Widget): StyleSlot => {
|
|
49
|
+
const existing = styles.get(widget);
|
|
50
|
+
|
|
51
|
+
if (existing !== undefined) {
|
|
52
|
+
return existing;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const slot = pool.pop() ?? createSlot();
|
|
56
|
+
styles.set(widget, slot);
|
|
57
|
+
reclaimed.register(widget, slot, widget);
|
|
58
|
+
|
|
59
|
+
applyWrite(CSS_CLASSES_PROP, () => {
|
|
60
|
+
widget.addCssClass(slot.className);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return slot;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const applyStyle = (widget: Gtk.Widget, style: unknown): string | null => {
|
|
67
|
+
if (typeof style !== "object" || style === null) {
|
|
68
|
+
releaseStyle(widget);
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const slot = ensureSlot(widget);
|
|
74
|
+
const css = scopedRule(slot.className, style);
|
|
75
|
+
|
|
76
|
+
if (css !== slot.css) {
|
|
77
|
+
slot.css = css;
|
|
78
|
+
slot.provider.loadFromString(css);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return slot.className;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export { applyStyle, CSS_CLASSES_PROP, releaseStyle, styleClass };
|
package/src/reconciler/text.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { drain, indexBeforeOrEnd } from "@gtkx/utils";
|
|
|
5
5
|
import type { ContentChild, ContentKind, ElementNode, ParentNode, TextNode } from "./node.js";
|
|
6
6
|
import type { Props } from "./registry.js";
|
|
7
7
|
import { ELEMENT_KIND, TEXT_KIND } from "./node.js";
|
|
8
|
-
import {
|
|
8
|
+
import { applyMutation } from "./signals.js";
|
|
9
9
|
|
|
10
10
|
type OffsetResult = { wasFound: boolean; offset: number };
|
|
11
11
|
|
|
@@ -320,7 +320,7 @@ const rebuildBuffer = (node: ElementNode): void => {
|
|
|
320
320
|
return;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
-
|
|
323
|
+
applyMutation(() => {
|
|
324
324
|
buffer.setText("", -1);
|
|
325
325
|
const build: BufferBuild = { buffer, view: node.bufferView, marks: [] };
|
|
326
326
|
insertContent(build, node.content);
|
|
@@ -385,7 +385,7 @@ const didUpdateTextSurgically = (host: ElementNode, node: TextNode, oldText: str
|
|
|
385
385
|
|
|
386
386
|
const start = located.offset;
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
applyMutation(() => {
|
|
389
389
|
buffer.delete(buffer.getIterAtOffset(start), buffer.getIterAtOffset(start + charLength(oldText)));
|
|
390
390
|
buffer.insert(buffer.getIterAtOffset(start), newText, -1);
|
|
391
391
|
});
|
|
@@ -201,42 +201,50 @@ function buildRelation(relation: Gtk.AccessibleRelation, type: AccessibleValueTy
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
const initValue = (gtype: GObject.Type, populate: (value: GObject.Value) => void): GObject.Value => {
|
|
205
|
+
const value = new GObject.Value();
|
|
206
|
+
value.init(gtype);
|
|
207
|
+
populate(value);
|
|
208
|
+
|
|
209
|
+
return value;
|
|
210
|
+
};
|
|
211
|
+
|
|
204
212
|
const buildValue = (descriptor: AccessibleDescriptor, jsValue: unknown): GObject.Value => {
|
|
205
213
|
switch (descriptor.type) {
|
|
206
214
|
case "string": {
|
|
207
|
-
return
|
|
215
|
+
return initValue(GObject.TYPE_STRING, (v) => {
|
|
208
216
|
v.setString(jsValue as string);
|
|
209
217
|
});
|
|
210
218
|
}
|
|
211
219
|
case "boolean": {
|
|
212
|
-
return
|
|
220
|
+
return initValue(GObject.TYPE_BOOLEAN, (v) => {
|
|
213
221
|
v.setBoolean(jsValue as boolean);
|
|
214
222
|
});
|
|
215
223
|
}
|
|
216
224
|
case "optionalBoolean": {
|
|
217
|
-
return
|
|
225
|
+
return initValue(GObject.TYPE_INT, (v) => {
|
|
218
226
|
v.setInt(jsValue === true ? 1 : 0);
|
|
219
227
|
});
|
|
220
228
|
}
|
|
221
229
|
case "int": {
|
|
222
|
-
return
|
|
230
|
+
return initValue(GObject.TYPE_INT, (v) => {
|
|
223
231
|
v.setInt(jsValue as number);
|
|
224
232
|
});
|
|
225
233
|
}
|
|
226
234
|
case "double": {
|
|
227
|
-
return
|
|
235
|
+
return initValue(GObject.TYPE_DOUBLE, (v) => {
|
|
228
236
|
v.setDouble(jsValue as number);
|
|
229
237
|
});
|
|
230
238
|
}
|
|
231
239
|
case "object": {
|
|
232
|
-
return
|
|
240
|
+
return initValue(GObject.TYPE_OBJECT, (v) => {
|
|
233
241
|
v.setObject(jsValue as GObject.Object | null);
|
|
234
242
|
});
|
|
235
243
|
}
|
|
236
244
|
case "list": {
|
|
237
245
|
const list = Gtk.AccessibleList.newFromList(jsValue as Gtk.Accessible[]);
|
|
238
246
|
|
|
239
|
-
return
|
|
247
|
+
return initValue(Gtk.AccessibleList.prototype.__type__, (v) => {
|
|
240
248
|
v.setBoxed(list);
|
|
241
249
|
});
|
|
242
250
|
}
|