@gtkx/react 0.15.0 → 0.16.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 (186) hide show
  1. package/README.md +1 -0
  2. package/dist/errors.js +3 -0
  3. package/dist/factory.d.ts +3 -2
  4. package/dist/factory.js +1 -1
  5. package/dist/generated/internal.d.ts +28 -1
  6. package/dist/generated/internal.js +93 -18
  7. package/dist/generated/jsx.d.ts +1489 -1300
  8. package/dist/generated/jsx.js +475 -0
  9. package/dist/host-config.d.ts +3 -1
  10. package/dist/host-config.js +31 -11
  11. package/dist/jsx.d.ts +107 -166
  12. package/dist/jsx.js +58 -69
  13. package/dist/node.d.ts +3 -1
  14. package/dist/node.js +5 -3
  15. package/dist/nodes/abstract/positional-child.d.ts +9 -0
  16. package/dist/nodes/abstract/positional-child.js +29 -0
  17. package/dist/nodes/abstract/positional-parent.d.ts +18 -0
  18. package/dist/nodes/abstract/positional-parent.js +48 -0
  19. package/dist/nodes/abstract/virtual-container.d.ts +17 -0
  20. package/dist/nodes/abstract/virtual-container.js +59 -0
  21. package/dist/nodes/abstract/virtual-single-child.d.ts +18 -0
  22. package/dist/nodes/abstract/virtual-single-child.js +54 -0
  23. package/dist/nodes/action-row-child.d.ts +0 -13
  24. package/dist/nodes/action-row-child.js +14 -12
  25. package/dist/nodes/action-row.d.ts +6 -1
  26. package/dist/nodes/action-row.js +4 -37
  27. package/dist/nodes/adjustable.d.ts +23 -0
  28. package/dist/nodes/adjustable.js +62 -0
  29. package/dist/nodes/alert-dialog-response.d.ts +1 -0
  30. package/dist/nodes/alert-dialog-response.js +86 -0
  31. package/dist/nodes/animation/animation-controller.d.ts +17 -0
  32. package/dist/nodes/animation/animation-controller.js +107 -0
  33. package/dist/nodes/animation/animation-factory.d.ts +15 -0
  34. package/dist/nodes/animation/animation-factory.js +25 -0
  35. package/dist/nodes/animation/animation-node.d.ts +9 -0
  36. package/dist/nodes/animation/animation-node.js +126 -0
  37. package/dist/nodes/animation/animation-style-sheet.d.ts +16 -0
  38. package/dist/nodes/animation/animation-style-sheet.js +74 -0
  39. package/dist/nodes/animation/index.d.ts +4 -0
  40. package/dist/nodes/animation/index.js +1 -0
  41. package/dist/nodes/animation/property-mapper.d.ts +11 -0
  42. package/dist/nodes/animation/property-mapper.js +36 -0
  43. package/dist/nodes/animation/transform-state.d.ts +11 -0
  44. package/dist/nodes/animation/transform-state.js +57 -0
  45. package/dist/nodes/animation/widget-registry.d.ts +5 -0
  46. package/dist/nodes/animation/widget-registry.js +42 -0
  47. package/dist/nodes/application.js +17 -7
  48. package/dist/nodes/autowrapped.js +37 -43
  49. package/dist/nodes/calendar.js +17 -43
  50. package/dist/nodes/color-dialog-button.d.ts +1 -0
  51. package/dist/nodes/color-dialog-button.js +70 -0
  52. package/dist/nodes/column-view-column.d.ts +3 -3
  53. package/dist/nodes/column-view-column.js +1 -1
  54. package/dist/nodes/column-view.js +36 -39
  55. package/dist/nodes/dialog.d.ts +11 -0
  56. package/dist/nodes/dialog.js +20 -0
  57. package/dist/nodes/drawing-area.js +24 -7
  58. package/dist/nodes/event-controller.d.ts +1 -0
  59. package/dist/nodes/event-controller.js +96 -0
  60. package/dist/nodes/expander-row-child.d.ts +0 -14
  61. package/dist/nodes/expander-row-child.js +14 -12
  62. package/dist/nodes/expander-row.d.ts +6 -1
  63. package/dist/nodes/expander-row.js +11 -48
  64. package/dist/nodes/fixed-child.js +48 -36
  65. package/dist/nodes/font-dialog-button.d.ts +1 -0
  66. package/dist/nodes/font-dialog-button.js +90 -0
  67. package/dist/nodes/grid-child.js +39 -45
  68. package/dist/nodes/grid.d.ts +1 -0
  69. package/dist/nodes/grid.js +41 -0
  70. package/dist/nodes/index.d.ts +17 -9
  71. package/dist/nodes/index.js +17 -9
  72. package/dist/nodes/internal/base-item-renderer.d.ts +29 -0
  73. package/dist/nodes/internal/base-item-renderer.js +88 -0
  74. package/dist/nodes/internal/base-store.d.ts +9 -0
  75. package/dist/nodes/internal/base-store.js +20 -0
  76. package/dist/nodes/internal/child-attachment.d.ts +26 -0
  77. package/dist/nodes/internal/child-attachment.js +48 -0
  78. package/dist/nodes/internal/deferred-action.d.ts +8 -0
  79. package/dist/nodes/internal/deferred-action.js +19 -0
  80. package/dist/nodes/internal/list-item-renderer.d.ts +14 -15
  81. package/dist/nodes/internal/list-item-renderer.js +51 -77
  82. package/dist/nodes/internal/list-store.d.ts +7 -6
  83. package/dist/nodes/internal/list-store.js +20 -24
  84. package/dist/nodes/internal/predicates.d.ts +25 -2
  85. package/dist/nodes/internal/predicates.js +53 -41
  86. package/dist/nodes/internal/selection-model.d.ts +30 -0
  87. package/dist/nodes/internal/selection-model.js +91 -0
  88. package/dist/nodes/internal/signal-store.d.ts +5 -4
  89. package/dist/nodes/internal/signal-store.js +30 -28
  90. package/dist/nodes/internal/simple-list-store.js +6 -9
  91. package/dist/nodes/internal/text-buffer-controller.d.ts +43 -0
  92. package/dist/nodes/internal/text-buffer-controller.js +287 -0
  93. package/dist/nodes/internal/text-tag-styles.d.ts +43 -0
  94. package/dist/nodes/internal/text-tag-styles.js +52 -0
  95. package/dist/nodes/internal/tree-list-item-renderer.d.ts +15 -14
  96. package/dist/nodes/internal/tree-list-item-renderer.js +85 -96
  97. package/dist/nodes/internal/tree-store.d.ts +10 -9
  98. package/dist/nodes/internal/tree-store.js +31 -35
  99. package/dist/nodes/internal/utils.d.ts +7 -4
  100. package/dist/nodes/internal/utils.js +50 -5
  101. package/dist/nodes/level-bar.js +19 -54
  102. package/dist/nodes/list-item.d.ts +6 -3
  103. package/dist/nodes/list-item.js +7 -4
  104. package/dist/nodes/list-view.js +15 -11
  105. package/dist/nodes/menu.d.ts +3 -3
  106. package/dist/nodes/menu.js +3 -3
  107. package/dist/nodes/models/list.d.ts +11 -13
  108. package/dist/nodes/models/list.js +16 -73
  109. package/dist/nodes/models/menu.d.ts +8 -7
  110. package/dist/nodes/models/menu.js +43 -50
  111. package/dist/nodes/models/tree-list.d.ts +6 -12
  112. package/dist/nodes/models/tree-list.js +30 -93
  113. package/dist/nodes/navigation-page.d.ts +1 -0
  114. package/dist/nodes/navigation-page.js +27 -32
  115. package/dist/nodes/navigation-view.js +17 -28
  116. package/dist/nodes/notebook-page-tab.d.ts +3 -3
  117. package/dist/nodes/notebook-page-tab.js +11 -14
  118. package/dist/nodes/notebook-page.d.ts +7 -5
  119. package/dist/nodes/notebook-page.js +45 -25
  120. package/dist/nodes/notebook.js +2 -2
  121. package/dist/nodes/overlay-child.js +90 -30
  122. package/dist/nodes/pack-child.d.ts +0 -13
  123. package/dist/nodes/pack-child.js +14 -12
  124. package/dist/nodes/pack.d.ts +6 -1
  125. package/dist/nodes/pack.js +4 -37
  126. package/dist/nodes/popover-menu.js +2 -2
  127. package/dist/nodes/scale.js +15 -45
  128. package/dist/nodes/scrolled-window.js +7 -6
  129. package/dist/nodes/search-bar.d.ts +1 -0
  130. package/dist/nodes/search-bar.js +40 -0
  131. package/dist/nodes/shortcut-controller.d.ts +1 -37
  132. package/dist/nodes/shortcut-controller.js +24 -8
  133. package/dist/nodes/shortcut.d.ts +5 -4
  134. package/dist/nodes/shortcut.js +11 -5
  135. package/dist/nodes/simple-list-view.js +2 -3
  136. package/dist/nodes/slot.d.ts +6 -9
  137. package/dist/nodes/slot.js +27 -42
  138. package/dist/nodes/source-view.js +80 -29
  139. package/dist/nodes/stack-page.js +20 -22
  140. package/dist/nodes/stack.js +19 -5
  141. package/dist/nodes/text-anchor.d.ts +41 -0
  142. package/dist/nodes/text-anchor.js +59 -0
  143. package/dist/nodes/text-content.d.ts +10 -0
  144. package/dist/nodes/text-content.js +1 -0
  145. package/dist/nodes/text-paintable.d.ts +17 -0
  146. package/dist/nodes/text-paintable.js +34 -0
  147. package/dist/nodes/text-segment.d.ts +15 -0
  148. package/dist/nodes/text-segment.js +29 -0
  149. package/dist/nodes/text-tag.d.ts +136 -0
  150. package/dist/nodes/text-tag.js +202 -0
  151. package/dist/nodes/text-view.d.ts +30 -0
  152. package/dist/nodes/text-view.js +49 -21
  153. package/dist/nodes/toggle-group.js +24 -32
  154. package/dist/nodes/toggle.d.ts +1 -15
  155. package/dist/nodes/toggle.js +40 -32
  156. package/dist/nodes/toolbar-child.js +14 -16
  157. package/dist/nodes/tree-list-item.d.ts +7 -5
  158. package/dist/nodes/tree-list-item.js +24 -36
  159. package/dist/nodes/tree-list-view.js +7 -6
  160. package/dist/nodes/virtual.d.ts +1 -1
  161. package/dist/nodes/widget.d.ts +2 -16
  162. package/dist/nodes/widget.js +105 -294
  163. package/dist/nodes/window.d.ts +9 -3
  164. package/dist/nodes/window.js +29 -15
  165. package/dist/registry.d.ts +1 -1
  166. package/dist/render.js +8 -6
  167. package/dist/scheduler.d.ts +11 -1
  168. package/dist/scheduler.js +16 -4
  169. package/dist/types.d.ts +2 -136
  170. package/package.json +3 -3
  171. package/dist/nodes/adjustment.d.ts +0 -48
  172. package/dist/nodes/adjustment.js +0 -70
  173. package/dist/nodes/calendar-mark.d.ts +0 -15
  174. package/dist/nodes/calendar-mark.js +0 -29
  175. package/dist/nodes/internal/constants.d.ts +0 -1
  176. package/dist/nodes/internal/constants.js +0 -24
  177. package/dist/nodes/level-bar-offset.d.ts +0 -13
  178. package/dist/nodes/level-bar-offset.js +0 -35
  179. package/dist/nodes/scale-mark.d.ts +0 -17
  180. package/dist/nodes/scale-mark.js +0 -38
  181. package/dist/nodes/source-buffer.d.ts +0 -73
  182. package/dist/nodes/source-buffer.js +0 -149
  183. package/dist/nodes/text-buffer.d.ts +0 -43
  184. package/dist/nodes/text-buffer.js +0 -81
  185. package/dist/nodes/virtual-child.d.ts +0 -18
  186. package/dist/nodes/virtual-child.js +0 -62
@@ -1,41 +1,47 @@
1
- import { batch, isObjectEqual } from "@gtkx/ffi";
1
+ import { isObjectEqual } from "@gtkx/ffi";
2
2
  import * as Adw from "@gtkx/ffi/adw";
3
3
  import { registerNodeClass } from "../registry.js";
4
+ import { hasChanged } from "./internal/utils.js";
4
5
  import { SlotNode } from "./slot.js";
5
6
  class StackPageNode extends SlotNode {
6
7
  static priority = 1;
7
- page;
8
+ page = null;
8
9
  static matches(type) {
9
10
  return type === "StackPage";
10
11
  }
11
12
  updateProps(oldProps, newProps) {
12
13
  super.updateProps(oldProps, newProps);
14
+ this.applyOwnProps(oldProps, newProps);
15
+ }
16
+ applyOwnProps(oldProps, newProps) {
13
17
  if (!this.page) {
14
18
  return;
15
19
  }
16
- if (!oldProps || oldProps.title !== newProps.title) {
17
- if (newProps.title !== undefined) {
18
- this.page.setTitle(newProps.title);
19
- }
20
+ if (hasChanged(oldProps, newProps, "title") && newProps.title !== undefined) {
21
+ this.page.setTitle(newProps.title);
20
22
  }
21
- if (!oldProps || oldProps.iconName !== newProps.iconName) {
22
- if (newProps.iconName !== undefined) {
23
- this.page.setIconName(newProps.iconName);
24
- }
23
+ if (hasChanged(oldProps, newProps, "iconName") && newProps.iconName !== undefined) {
24
+ this.page.setIconName(newProps.iconName);
25
25
  }
26
- if (!oldProps || oldProps.needsAttention !== newProps.needsAttention) {
26
+ if (hasChanged(oldProps, newProps, "needsAttention")) {
27
27
  this.page.setNeedsAttention(newProps.needsAttention ?? false);
28
28
  }
29
- if (!oldProps || oldProps.visible !== newProps.visible) {
29
+ if (hasChanged(oldProps, newProps, "visible")) {
30
30
  this.page.setVisible(newProps.visible ?? true);
31
31
  }
32
- if (!oldProps || oldProps.useUnderline !== newProps.useUnderline) {
32
+ if (hasChanged(oldProps, newProps, "useUnderline")) {
33
33
  this.page.setUseUnderline(newProps.useUnderline ?? false);
34
34
  }
35
- if ("setBadgeNumber" in this.page && (!oldProps || oldProps.badgeNumber !== newProps.badgeNumber)) {
35
+ if ("setBadgeNumber" in this.page && hasChanged(oldProps, newProps, "badgeNumber")) {
36
36
  this.page.setBadgeNumber?.(newProps.badgeNumber ?? 0);
37
37
  }
38
38
  }
39
+ onChildChange(oldChild) {
40
+ this.removePage(oldChild);
41
+ if (this.child) {
42
+ this.addPage();
43
+ }
44
+ }
39
45
  addPage() {
40
46
  const child = this.getChild();
41
47
  const parent = this.getParent();
@@ -78,13 +84,5 @@ class StackPageNode extends SlotNode {
78
84
  parent.remove(oldChild);
79
85
  }
80
86
  }
81
- onChildChange(oldChild) {
82
- batch(() => {
83
- this.removePage(oldChild);
84
- if (this.child) {
85
- this.addPage();
86
- }
87
- });
88
- }
89
87
  }
90
88
  registerNodeClass(StackPageNode);
@@ -1,24 +1,38 @@
1
1
  import { STACK_CLASSES } from "../generated/internal.js";
2
2
  import { registerNodeClass } from "../registry.js";
3
- import { scheduleAfterCommit } from "../scheduler.js";
4
- import { filterProps, matchesAnyClass } from "./internal/utils.js";
3
+ import { CommitPriority, scheduleAfterCommit } from "../scheduler.js";
4
+ import { filterProps, hasChanged, matchesAnyClass } from "./internal/utils.js";
5
5
  import { WidgetNode } from "./widget.js";
6
- const PROPS = ["page"];
6
+ const OWN_PROPS = ["page", "onPageChanged"];
7
7
  class StackNode extends WidgetNode {
8
8
  static priority = 1;
9
9
  static matches(_type, containerOrClass) {
10
10
  return matchesAnyClass(STACK_CLASSES, containerOrClass);
11
11
  }
12
12
  updateProps(oldProps, newProps) {
13
+ super.updateProps(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
14
+ this.applyOwnProps(oldProps, newProps);
15
+ }
16
+ applyOwnProps(oldProps, newProps) {
13
17
  if (newProps.page && this.container.getVisibleChildName() !== newProps.page) {
14
18
  const page = newProps.page;
15
19
  scheduleAfterCommit(() => {
16
20
  if (this.container.getChildByName(page)) {
17
21
  this.container.setVisibleChildName(page);
18
22
  }
19
- });
23
+ }, CommitPriority.NORMAL);
24
+ }
25
+ if (hasChanged(oldProps, newProps, "onPageChanged")) {
26
+ const { onPageChanged } = newProps;
27
+ if (onPageChanged) {
28
+ this.signalStore.set(this, this.container, "notify::visible-child-name", (self) => {
29
+ onPageChanged(self.getVisibleChildName(), self);
30
+ });
31
+ }
32
+ else {
33
+ this.signalStore.set(this, this.container, "notify::visible-child-name", null);
34
+ }
20
35
  }
21
- super.updateProps(filterProps(oldProps ?? {}, PROPS), filterProps(newProps, PROPS));
22
36
  }
23
37
  }
24
38
  registerNodeClass(StackNode);
@@ -0,0 +1,41 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import type { ReactNode } from "react";
3
+ import type { Node } from "../node.js";
4
+ import { VirtualNode } from "./virtual.js";
5
+ /**
6
+ * Props for the TextAnchor virtual element.
7
+ *
8
+ * Used to declaratively embed widgets within text content in a TextBuffer.
9
+ * The anchor is placed at the current position in the text flow.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * <GtkTextView>
14
+ * <x.TextBuffer>
15
+ * Click here: <x.TextAnchor>
16
+ * <GtkButton label="Click me" />
17
+ * </x.TextAnchor> to continue.
18
+ * </x.TextBuffer>
19
+ * </GtkTextView>
20
+ * ```
21
+ */
22
+ export type TextAnchorProps = {
23
+ /** The widget to embed at this anchor position */
24
+ children?: ReactNode;
25
+ };
26
+ export declare class TextAnchorNode extends VirtualNode<TextAnchorProps> {
27
+ static priority: number;
28
+ private textView;
29
+ private buffer;
30
+ private anchor;
31
+ private widgetChild;
32
+ bufferOffset: number;
33
+ static matches(type: string): boolean;
34
+ getLength(): number;
35
+ getText(): string;
36
+ setTextViewAndBuffer(textView: Gtk.TextView, buffer: Gtk.TextBuffer): void;
37
+ private setupAnchor;
38
+ appendChild(child: Node): void;
39
+ removeChild(child: Node): void;
40
+ unmount(): void;
41
+ }
@@ -0,0 +1,59 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { registerNodeClass } from "../registry.js";
3
+ import { VirtualNode } from "./virtual.js";
4
+ import { WidgetNode } from "./widget.js";
5
+ const PLACEHOLDER = "\uFFFC";
6
+ export class TextAnchorNode extends VirtualNode {
7
+ static priority = 1;
8
+ textView = null;
9
+ buffer = null;
10
+ anchor = null;
11
+ widgetChild = null;
12
+ bufferOffset = 0;
13
+ static matches(type) {
14
+ return type === "TextAnchor";
15
+ }
16
+ getLength() {
17
+ return 1;
18
+ }
19
+ getText() {
20
+ return PLACEHOLDER;
21
+ }
22
+ setTextViewAndBuffer(textView, buffer) {
23
+ this.textView = textView;
24
+ this.buffer = buffer;
25
+ this.setupAnchor();
26
+ }
27
+ setupAnchor() {
28
+ if (!this.textView || !this.buffer)
29
+ return;
30
+ const iter = new Gtk.TextIter();
31
+ this.buffer.getIterAtOffset(iter, this.bufferOffset);
32
+ this.anchor = this.buffer.createChildAnchor(iter);
33
+ if (this.widgetChild?.container && this.anchor) {
34
+ this.textView.addChildAtAnchor(this.widgetChild.container, this.anchor);
35
+ }
36
+ }
37
+ appendChild(child) {
38
+ if (!(child instanceof WidgetNode)) {
39
+ throw new Error(`TextAnchor can only contain widget children, got '${child.typeName}'`);
40
+ }
41
+ this.widgetChild = child;
42
+ if (this.textView && this.anchor && child.container) {
43
+ this.textView.addChildAtAnchor(child.container, this.anchor);
44
+ }
45
+ }
46
+ removeChild(child) {
47
+ if (child === this.widgetChild) {
48
+ this.widgetChild = null;
49
+ }
50
+ }
51
+ unmount() {
52
+ this.anchor = null;
53
+ this.widgetChild = null;
54
+ this.buffer = null;
55
+ this.textView = null;
56
+ super.unmount();
57
+ }
58
+ }
59
+ registerNodeClass(TextAnchorNode);
@@ -0,0 +1,10 @@
1
+ import type { TextAnchorNode } from "./text-anchor.js";
2
+ import type { TextPaintableNode } from "./text-paintable.js";
3
+ import type { TextSegmentNode } from "./text-segment.js";
4
+ import type { TextTagNode } from "./text-tag.js";
5
+ export type TextContentChild = TextSegmentNode | TextTagNode | TextAnchorNode | TextPaintableNode;
6
+ export type TextContentParent = {
7
+ onChildInserted(child: TextContentChild): void;
8
+ onChildRemoved(child: TextContentChild): void;
9
+ onChildTextChanged(child: TextSegmentNode, oldLength: number, newLength: number): void;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type * as Gdk from "@gtkx/ffi/gdk";
2
+ import * as Gtk from "@gtkx/ffi/gtk";
3
+ import { VirtualNode } from "./virtual.js";
4
+ export type TextPaintableProps = {
5
+ paintable: Gdk.Paintable;
6
+ };
7
+ export declare class TextPaintableNode extends VirtualNode<TextPaintableProps> {
8
+ static priority: number;
9
+ private buffer;
10
+ bufferOffset: number;
11
+ static matches(type: string): boolean;
12
+ getLength(): number;
13
+ getText(): string;
14
+ setTextViewAndBuffer(_textView: Gtk.TextView, buffer: Gtk.TextBuffer): void;
15
+ private insertPaintable;
16
+ unmount(): void;
17
+ }
@@ -0,0 +1,34 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { registerNodeClass } from "../registry.js";
3
+ import { VirtualNode } from "./virtual.js";
4
+ const PLACEHOLDER = "\uFFFC";
5
+ export class TextPaintableNode extends VirtualNode {
6
+ static priority = 1;
7
+ buffer = null;
8
+ bufferOffset = 0;
9
+ static matches(type) {
10
+ return type === "TextPaintable";
11
+ }
12
+ getLength() {
13
+ return 1;
14
+ }
15
+ getText() {
16
+ return PLACEHOLDER;
17
+ }
18
+ setTextViewAndBuffer(_textView, buffer) {
19
+ this.buffer = buffer;
20
+ this.insertPaintable();
21
+ }
22
+ insertPaintable() {
23
+ if (!this.buffer || !this.props.paintable)
24
+ return;
25
+ const iter = new Gtk.TextIter();
26
+ this.buffer.getIterAtOffset(iter, this.bufferOffset);
27
+ this.buffer.insertPaintable(iter, this.props.paintable);
28
+ }
29
+ unmount() {
30
+ this.buffer = null;
31
+ super.unmount();
32
+ }
33
+ }
34
+ registerNodeClass(TextPaintableNode);
@@ -0,0 +1,15 @@
1
+ import type { TextContentParent } from "./text-content.js";
2
+ import { VirtualNode } from "./virtual.js";
3
+ export type TextSegmentProps = {
4
+ text: string;
5
+ };
6
+ export declare class TextSegmentNode extends VirtualNode<TextSegmentProps> {
7
+ static priority: number;
8
+ private parent;
9
+ bufferOffset: number;
10
+ static matches(type: string): boolean;
11
+ setParent(parent: TextContentParent): void;
12
+ getText(): string;
13
+ getLength(): number;
14
+ updateProps(oldProps: TextSegmentProps | null, newProps: TextSegmentProps): void;
15
+ }
@@ -0,0 +1,29 @@
1
+ import { registerNodeClass } from "../registry.js";
2
+ import { hasChanged } from "./internal/utils.js";
3
+ import { VirtualNode } from "./virtual.js";
4
+ export class TextSegmentNode extends VirtualNode {
5
+ static priority = 1;
6
+ parent = null;
7
+ bufferOffset = 0;
8
+ static matches(type) {
9
+ return type === "TextSegment";
10
+ }
11
+ setParent(parent) {
12
+ this.parent = parent;
13
+ }
14
+ getText() {
15
+ return this.props.text;
16
+ }
17
+ getLength() {
18
+ return this.props.text.length;
19
+ }
20
+ updateProps(oldProps, newProps) {
21
+ const oldText = oldProps?.text ?? "";
22
+ const newText = newProps.text;
23
+ super.updateProps(oldProps, newProps);
24
+ if (hasChanged(oldProps, newProps, "text") && this.parent) {
25
+ this.parent.onChildTextChanged(this, oldText.length, newText.length);
26
+ }
27
+ }
28
+ }
29
+ registerNodeClass(TextSegmentNode);
@@ -0,0 +1,136 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import type * as Pango from "@gtkx/ffi/pango";
3
+ import type { ReactNode } from "react";
4
+ import type { Node } from "../node.js";
5
+ import type { TextContentChild, TextContentParent } from "./text-content.js";
6
+ import { TextSegmentNode } from "./text-segment.js";
7
+ import { VirtualNode } from "./virtual.js";
8
+ /**
9
+ * Props for the TextTag virtual element.
10
+ *
11
+ * Used to declaratively define and apply text formatting to content within a TextBuffer.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <GtkTextView>
16
+ * <x.TextBuffer>
17
+ * Hello <x.TextTag id="bold" weight={Pango.Weight.BOLD}>bold</x.TextTag> world
18
+ * </x.TextBuffer>
19
+ * </GtkTextView>
20
+ * ```
21
+ */
22
+ export type TextTagProps = {
23
+ /** Unique identifier for this tag in the tag table */
24
+ id: string;
25
+ /** Priority of this tag (higher wins when multiple tags affect same property) */
26
+ priority?: number;
27
+ /** Background color as a string (e.g., "red", "#ff0000") */
28
+ background?: string;
29
+ /** Whether the background fills the entire line height */
30
+ backgroundFullHeight?: boolean;
31
+ /** Foreground (text) color as a string */
32
+ foreground?: string;
33
+ /** Font family name (e.g., "Sans", "Monospace") */
34
+ family?: string;
35
+ /** Font description string (e.g., "Sans Italic 12") */
36
+ font?: string;
37
+ /** Font size in points */
38
+ sizePoints?: number;
39
+ /** Font size in Pango units */
40
+ size?: number;
41
+ /** Font size scale factor relative to default */
42
+ scale?: number;
43
+ /** Font weight (use Pango.Weight constants) */
44
+ weight?: Pango.Weight | number;
45
+ /** Font style (use Pango.Style constants) */
46
+ style?: Pango.Style;
47
+ /** Font stretch (use Pango.Stretch constants) */
48
+ stretch?: Pango.Stretch;
49
+ /** Font variant (use Pango.Variant constants) */
50
+ variant?: Pango.Variant;
51
+ /** Whether to strike through the text */
52
+ strikethrough?: boolean;
53
+ /** Underline style (use Pango.Underline constants) */
54
+ underline?: Pango.Underline;
55
+ /** Overline style (use Pango.Overline constants) */
56
+ overline?: Pango.Overline;
57
+ /** Offset of text above baseline in Pango units (negative = below) */
58
+ rise?: number;
59
+ /** Extra spacing between characters in Pango units */
60
+ letterSpacing?: number;
61
+ /** Factor to scale line height by */
62
+ lineHeight?: number;
63
+ /** Left margin in pixels */
64
+ leftMargin?: number;
65
+ /** Right margin in pixels */
66
+ rightMargin?: number;
67
+ /** Paragraph indent in pixels (negative = hanging) */
68
+ indent?: number;
69
+ /** Pixels of blank space above paragraphs */
70
+ pixelsAboveLines?: number;
71
+ /** Pixels of blank space below paragraphs */
72
+ pixelsBelowLines?: number;
73
+ /** Pixels of blank space between wrapped lines */
74
+ pixelsInsideWrap?: number;
75
+ /** Text justification */
76
+ justification?: Gtk.Justification;
77
+ /** Text direction */
78
+ direction?: Gtk.TextDirection;
79
+ /** Wrap mode for line breaks */
80
+ wrapMode?: Gtk.WrapMode;
81
+ /** Whether the text can be modified */
82
+ editable?: boolean;
83
+ /** Whether the text is invisible/hidden */
84
+ invisible?: boolean;
85
+ /** Whether breaks are allowed */
86
+ allowBreaks?: boolean;
87
+ /** Whether to insert hyphens at breaks */
88
+ insertHyphens?: boolean;
89
+ /** Whether font fallback is enabled */
90
+ fallback?: boolean;
91
+ /** Whether margins accumulate */
92
+ accumulativeMargin?: boolean;
93
+ /** Paragraph background color as a string */
94
+ paragraphBackground?: string;
95
+ /** How to render invisible characters */
96
+ showSpaces?: Pango.ShowFlags;
97
+ /** How to transform text for display */
98
+ textTransform?: Pango.TextTransform;
99
+ /** OpenType font features as a string */
100
+ fontFeatures?: string;
101
+ /** Language code (e.g., "en-US") */
102
+ language?: string;
103
+ /** Text content and nested TextTag children */
104
+ children?: ReactNode;
105
+ };
106
+ export declare class TextTagNode extends VirtualNode<TextTagProps> implements TextContentParent {
107
+ static priority: number;
108
+ private buffer;
109
+ private tag;
110
+ private children;
111
+ private parent;
112
+ bufferOffset: number;
113
+ static matches(type: string): boolean;
114
+ setParent(parent: TextContentParent): void;
115
+ setBuffer(buffer: Gtk.TextBuffer): void;
116
+ hasBuffer(): boolean;
117
+ private setupTag;
118
+ private applyStyleProps;
119
+ getText(): string;
120
+ getLength(): number;
121
+ getChildren(): TextContentChild[];
122
+ private applyTagToRange;
123
+ private removeTagFromBuffer;
124
+ reapplyTag(): void;
125
+ private updateChildOffsets;
126
+ onChildInserted(child: TextContentChild): void;
127
+ onChildRemoved(child: TextContentChild): void;
128
+ onChildTextChanged(child: TextSegmentNode, oldLength: number, newLength: number): void;
129
+ appendChild(child: Node): void;
130
+ removeChild(child: Node): void;
131
+ insertBefore(child: Node, before: Node): void;
132
+ private isTextContentChild;
133
+ private setChildParent;
134
+ updateProps(oldProps: TextTagProps | null, newProps: TextTagProps): void;
135
+ unmount(): void;
136
+ }
@@ -0,0 +1,202 @@
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
+ import { registerNodeClass } from "../registry.js";
3
+ import { applyStyleChanges } from "./internal/text-tag-styles.js";
4
+ import { hasChanged } from "./internal/utils.js";
5
+ import { TextAnchorNode } from "./text-anchor.js";
6
+ import { TextSegmentNode } from "./text-segment.js";
7
+ import { VirtualNode } from "./virtual.js";
8
+ export class TextTagNode extends VirtualNode {
9
+ static priority = 1;
10
+ buffer = null;
11
+ tag = null;
12
+ children = [];
13
+ parent = null;
14
+ bufferOffset = 0;
15
+ static matches(type) {
16
+ return type === "TextTag";
17
+ }
18
+ setParent(parent) {
19
+ this.parent = parent;
20
+ }
21
+ setBuffer(buffer) {
22
+ this.buffer = buffer;
23
+ this.updateChildOffsets(0);
24
+ this.setupTag();
25
+ for (const child of this.children) {
26
+ if (child instanceof TextTagNode) {
27
+ child.setBuffer(buffer);
28
+ }
29
+ }
30
+ }
31
+ hasBuffer() {
32
+ return this.buffer !== null;
33
+ }
34
+ setupTag() {
35
+ if (!this.buffer)
36
+ return;
37
+ const tagTable = this.buffer.getTagTable();
38
+ this.tag = new Gtk.TextTag(this.props.id);
39
+ this.applyStyleProps(null, this.props);
40
+ tagTable.add(this.tag);
41
+ if (this.props.priority !== undefined) {
42
+ this.tag.setPriority(this.props.priority);
43
+ }
44
+ this.applyTagToRange();
45
+ }
46
+ applyStyleProps(oldProps, newProps) {
47
+ if (!this.tag)
48
+ return;
49
+ applyStyleChanges(this.tag, oldProps, newProps);
50
+ }
51
+ getText() {
52
+ let text = "";
53
+ for (const child of this.children) {
54
+ text += child.getText();
55
+ }
56
+ return text;
57
+ }
58
+ getLength() {
59
+ let length = 0;
60
+ for (const child of this.children) {
61
+ length += child.getLength();
62
+ }
63
+ return length;
64
+ }
65
+ getChildren() {
66
+ return this.children;
67
+ }
68
+ applyTagToRange() {
69
+ const buffer = this.buffer;
70
+ const tag = this.tag;
71
+ if (!buffer || !tag)
72
+ return;
73
+ const length = this.getLength();
74
+ if (length === 0)
75
+ return;
76
+ const startIter = new Gtk.TextIter();
77
+ const endIter = new Gtk.TextIter();
78
+ buffer.getIterAtOffset(startIter, this.bufferOffset);
79
+ buffer.getIterAtOffset(endIter, this.bufferOffset + length);
80
+ buffer.applyTag(tag, startIter, endIter);
81
+ }
82
+ removeTagFromBuffer() {
83
+ const buffer = this.buffer;
84
+ const tag = this.tag;
85
+ if (!buffer || !tag)
86
+ return;
87
+ const startIter = new Gtk.TextIter();
88
+ const endIter = new Gtk.TextIter();
89
+ buffer.getStartIter(startIter);
90
+ buffer.getEndIter(endIter);
91
+ buffer.removeTag(tag, startIter, endIter);
92
+ }
93
+ reapplyTag() {
94
+ if (!this.buffer || !this.tag)
95
+ return;
96
+ this.removeTagFromBuffer();
97
+ this.applyTagToRange();
98
+ }
99
+ updateChildOffsets(startIndex) {
100
+ let offset = this.bufferOffset;
101
+ for (let i = 0; i < startIndex; i++) {
102
+ const child = this.children[i];
103
+ if (child)
104
+ offset += child.getLength();
105
+ }
106
+ for (let i = startIndex; i < this.children.length; i++) {
107
+ const child = this.children[i];
108
+ if (child) {
109
+ child.bufferOffset = offset;
110
+ offset += child.getLength();
111
+ }
112
+ }
113
+ }
114
+ onChildInserted(child) {
115
+ const index = this.children.indexOf(child);
116
+ if (index !== -1) {
117
+ this.updateChildOffsets(index);
118
+ }
119
+ this.parent?.onChildInserted(child);
120
+ }
121
+ onChildRemoved(child) {
122
+ this.parent?.onChildRemoved(child);
123
+ }
124
+ onChildTextChanged(child, oldLength, newLength) {
125
+ const index = this.children.indexOf(child);
126
+ if (index !== -1) {
127
+ this.updateChildOffsets(index + 1);
128
+ }
129
+ this.parent?.onChildTextChanged(child, oldLength, newLength);
130
+ }
131
+ appendChild(child) {
132
+ if (this.isTextContentChild(child)) {
133
+ const index = this.children.length;
134
+ this.children.push(child);
135
+ this.setChildParent(child);
136
+ if (child instanceof TextTagNode && this.buffer) {
137
+ child.setBuffer(this.buffer);
138
+ }
139
+ this.updateChildOffsets(index);
140
+ this.parent?.onChildInserted(child);
141
+ return;
142
+ }
143
+ super.appendChild(child);
144
+ }
145
+ removeChild(child) {
146
+ const index = this.children.indexOf(child);
147
+ if (index !== -1) {
148
+ this.children.splice(index, 1);
149
+ this.updateChildOffsets(index);
150
+ this.parent?.onChildRemoved(child);
151
+ return;
152
+ }
153
+ super.removeChild(child);
154
+ }
155
+ insertBefore(child, before) {
156
+ if (this.isTextContentChild(child)) {
157
+ const beforeIndex = this.children.indexOf(before);
158
+ const insertIndex = beforeIndex !== -1 ? beforeIndex : this.children.length;
159
+ this.children.splice(insertIndex, 0, child);
160
+ this.setChildParent(child);
161
+ if (child instanceof TextTagNode && this.buffer) {
162
+ child.setBuffer(this.buffer);
163
+ }
164
+ this.updateChildOffsets(insertIndex);
165
+ this.parent?.onChildInserted(child);
166
+ return;
167
+ }
168
+ super.insertBefore(child, before);
169
+ }
170
+ isTextContentChild(child) {
171
+ return child instanceof TextSegmentNode || child instanceof TextTagNode || child instanceof TextAnchorNode;
172
+ }
173
+ setChildParent(child) {
174
+ if (child instanceof TextSegmentNode || child instanceof TextTagNode) {
175
+ child.setParent(this);
176
+ }
177
+ }
178
+ updateProps(oldProps, newProps) {
179
+ super.updateProps(oldProps, newProps);
180
+ if (oldProps && oldProps.id !== newProps.id) {
181
+ throw new Error("TextTag id cannot be changed after creation");
182
+ }
183
+ if (this.tag) {
184
+ this.applyStyleProps(oldProps, newProps);
185
+ if (hasChanged(oldProps, newProps, "priority") && newProps.priority !== undefined) {
186
+ this.tag.setPriority(newProps.priority);
187
+ }
188
+ }
189
+ }
190
+ unmount() {
191
+ if (this.buffer && this.tag) {
192
+ this.removeTagFromBuffer();
193
+ const tagTable = this.buffer.getTagTable();
194
+ tagTable.remove(this.tag);
195
+ }
196
+ this.tag = null;
197
+ this.buffer = null;
198
+ this.children = [];
199
+ super.unmount();
200
+ }
201
+ }
202
+ registerNodeClass(TextTagNode);