@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.
Files changed (82) hide show
  1. package/README.md +6 -4
  2. package/dist/adw/dialog.js +2 -2
  3. package/dist/adw/dialog.js.map +1 -1
  4. package/dist/components/application.d.ts.map +1 -1
  5. package/dist/components/application.js +3 -3
  6. package/dist/components/application.js.map +1 -1
  7. package/dist/components/window.js +3 -3
  8. package/dist/components/window.js.map +1 -1
  9. package/dist/element-behaviors.js +130 -1
  10. package/dist/element-behaviors.js.map +1 -1
  11. package/dist/element-config.js +1 -1
  12. package/dist/element-config.js.map +1 -1
  13. package/dist/hooks/use-bind-setting.d.ts.map +1 -1
  14. package/dist/hooks/use-bind-setting.js +3 -2
  15. package/dist/hooks/use-bind-setting.js.map +1 -1
  16. package/dist/hooks/use-property.d.ts +1 -1
  17. package/dist/hooks/use-property.d.ts.map +1 -1
  18. package/dist/hooks/use-property.js +5 -1
  19. package/dist/hooks/use-property.js.map +1 -1
  20. package/dist/internal.d.ts +2 -0
  21. package/dist/internal.d.ts.map +1 -1
  22. package/dist/internal.js +2 -0
  23. package/dist/internal.js.map +1 -1
  24. package/dist/prop-types.d.ts +20 -2
  25. package/dist/prop-types.d.ts.map +1 -1
  26. package/dist/prop-types.js.map +1 -1
  27. package/dist/reconciler/apply-props.d.ts +2 -1
  28. package/dist/reconciler/apply-props.d.ts.map +1 -1
  29. package/dist/reconciler/apply-props.js +60 -24
  30. package/dist/reconciler/apply-props.js.map +1 -1
  31. package/dist/reconciler/behaviors.d.ts.map +1 -1
  32. package/dist/reconciler/behaviors.js +40 -9
  33. package/dist/reconciler/behaviors.js.map +1 -1
  34. package/dist/reconciler/host-config.d.ts.map +1 -1
  35. package/dist/reconciler/host-config.js +10 -2
  36. package/dist/reconciler/host-config.js.map +1 -1
  37. package/dist/reconciler/metadata.d.ts +6 -1
  38. package/dist/reconciler/metadata.d.ts.map +1 -1
  39. package/dist/reconciler/metadata.js +39 -6
  40. package/dist/reconciler/metadata.js.map +1 -1
  41. package/dist/reconciler/placement.js +4 -4
  42. package/dist/reconciler/placement.js.map +1 -1
  43. package/dist/reconciler/registry.d.ts +2 -0
  44. package/dist/reconciler/registry.d.ts.map +1 -1
  45. package/dist/reconciler/registry.js.map +1 -1
  46. package/dist/reconciler/root.d.ts.map +1 -1
  47. package/dist/reconciler/root.js.map +1 -1
  48. package/dist/reconciler/signals.d.ts +3 -2
  49. package/dist/reconciler/signals.d.ts.map +1 -1
  50. package/dist/reconciler/signals.js +49 -16
  51. package/dist/reconciler/signals.js.map +1 -1
  52. package/dist/reconciler/style.d.ts +7 -0
  53. package/dist/reconciler/style.d.ts.map +1 -0
  54. package/dist/reconciler/style.js +61 -0
  55. package/dist/reconciler/style.js.map +1 -0
  56. package/dist/reconciler/text.js +3 -3
  57. package/dist/reconciler/text.js.map +1 -1
  58. package/dist/utils/accessible-props.d.ts.map +1 -1
  59. package/dist/utils/accessible-props.js +13 -7
  60. package/dist/utils/accessible-props.js.map +1 -1
  61. package/env.d.ts +10 -7
  62. package/package.json +6 -5
  63. package/src/adw/dialog.tsx +2 -2
  64. package/src/components/application.tsx +18 -3
  65. package/src/components/window.tsx +3 -3
  66. package/src/element-behaviors.ts +180 -1
  67. package/src/element-config.ts +1 -1
  68. package/src/hooks/use-bind-setting.ts +3 -2
  69. package/src/hooks/use-property.ts +6 -2
  70. package/src/internal.ts +2 -0
  71. package/src/prop-types.ts +96 -1
  72. package/src/reconciler/apply-props.ts +74 -26
  73. package/src/reconciler/behaviors.ts +60 -12
  74. package/src/reconciler/host-config.ts +13 -2
  75. package/src/reconciler/metadata.ts +62 -6
  76. package/src/reconciler/placement.ts +4 -4
  77. package/src/reconciler/registry.ts +2 -0
  78. package/src/reconciler/root.ts +1 -1
  79. package/src/reconciler/signals.ts +62 -17
  80. package/src/reconciler/style.ts +84 -0
  81. package/src/reconciler/text.ts +3 -3
  82. 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 { camelCase } from "@gtkx/utils";
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 writeState: { depth: number } = { depth: 0 };
11
+ const pendingWrites: (string | null)[] = [];
12
+ const canonicalNames: Map<string, string> = new Map();
8
13
 
9
- const isApplyingWrite = (): boolean => writeState.depth > 0;
14
+ const isApplyingWrite = (): boolean => pendingWrites.length > 0;
15
+ const canonicalName = (property: string): string => getOrInsert(canonicalNames, property, toCamelIdentifier);
10
16
 
11
- const applyWrite = <T>(write: () => T): T => {
12
- writeState.depth += 1;
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
- writeState.depth = Math.max(0, writeState.depth - 1);
23
+ pendingWrites.pop();
18
24
  }
19
25
  };
20
26
 
21
- const getNotifyProperty = (signal: string): string | null =>
22
- signal.startsWith(NOTIFY_DETAIL_PREFIX) ? camelCase(signal.slice(NOTIFY_DETAIL_PREFIX.length)) : null;
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
- notifyProperty: string | null,
72
+ notify: NotifyBinding | null,
31
73
  args: unknown[],
32
- ): unknown =>
33
- notifyProperty === null
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, notifyProperty), target.object);
79
+ : record.handler(Reflect.get(target.object, property), target.object);
80
+ };
36
81
 
37
- const wrapHandler = (target: SignalTarget, record: HandlerRecord, notifyProperty: string | null): SignalHandler =>
82
+ const wrapHandler = (target: SignalTarget, record: HandlerRecord, notify: NotifyBinding | null): SignalHandler =>
38
83
  (...args: unknown[]): unknown => {
39
- if (record.isBlockable && isApplyingWrite()) {
84
+ if (isSuppressed(record, notify, args)) {
40
85
  return undefined;
41
86
  }
42
87
 
43
- return target.dispatch(() => invokeHandler(target, record, notifyProperty, args));
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, notifyProperty);
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 };
@@ -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 { applyWrite } from "./signals.js";
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
- applyWrite(() => {
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
- applyWrite(() => {
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 GObject.buildValue(GObject.TYPE_STRING, (v) => {
215
+ return initValue(GObject.TYPE_STRING, (v) => {
208
216
  v.setString(jsValue as string);
209
217
  });
210
218
  }
211
219
  case "boolean": {
212
- return GObject.buildValue(GObject.TYPE_BOOLEAN, (v) => {
220
+ return initValue(GObject.TYPE_BOOLEAN, (v) => {
213
221
  v.setBoolean(jsValue as boolean);
214
222
  });
215
223
  }
216
224
  case "optionalBoolean": {
217
- return GObject.buildValue(GObject.TYPE_INT, (v) => {
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 GObject.buildValue(GObject.TYPE_INT, (v) => {
230
+ return initValue(GObject.TYPE_INT, (v) => {
223
231
  v.setInt(jsValue as number);
224
232
  });
225
233
  }
226
234
  case "double": {
227
- return GObject.buildValue(GObject.TYPE_DOUBLE, (v) => {
235
+ return initValue(GObject.TYPE_DOUBLE, (v) => {
228
236
  v.setDouble(jsValue as number);
229
237
  });
230
238
  }
231
239
  case "object": {
232
- return GObject.buildValue(GObject.TYPE_OBJECT, (v) => {
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 GObject.buildValue(Gtk.AccessibleList.prototype.__type__, (v) => {
247
+ return initValue(Gtk.AccessibleList.prototype.__type__, (v) => {
240
248
  v.setBoxed(list);
241
249
  });
242
250
  }