@gtkx/react 0.18.9 → 0.19.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 (174) hide show
  1. package/dist/generated/internal.d.ts +6 -0
  2. package/dist/generated/internal.d.ts.map +1 -1
  3. package/dist/generated/internal.js +331 -44
  4. package/dist/generated/internal.js.map +1 -1
  5. package/dist/generated/jsx.d.ts +178 -2
  6. package/dist/generated/jsx.d.ts.map +1 -1
  7. package/dist/generated/jsx.js.map +1 -1
  8. package/dist/host-config.d.ts.map +1 -1
  9. package/dist/host-config.js +46 -10
  10. package/dist/host-config.js.map +1 -1
  11. package/dist/jsx.d.ts +133 -13
  12. package/dist/jsx.d.ts.map +1 -1
  13. package/dist/jsx.js +41 -2
  14. package/dist/jsx.js.map +1 -1
  15. package/dist/metadata.d.ts +1 -0
  16. package/dist/metadata.d.ts.map +1 -1
  17. package/dist/metadata.js +3 -1
  18. package/dist/metadata.js.map +1 -1
  19. package/dist/node.d.ts +2 -0
  20. package/dist/node.d.ts.map +1 -1
  21. package/dist/node.js +22 -6
  22. package/dist/node.js.map +1 -1
  23. package/dist/nodes/column-view-column.d.ts +4 -1
  24. package/dist/nodes/column-view-column.d.ts.map +1 -1
  25. package/dist/nodes/column-view-column.js +29 -8
  26. package/dist/nodes/column-view-column.js.map +1 -1
  27. package/dist/nodes/column-view.d.ts +4 -3
  28. package/dist/nodes/column-view.d.ts.map +1 -1
  29. package/dist/nodes/column-view.js +44 -14
  30. package/dist/nodes/column-view.js.map +1 -1
  31. package/dist/nodes/drop-down.d.ts +12 -2
  32. package/dist/nodes/drop-down.d.ts.map +1 -1
  33. package/dist/nodes/drop-down.js +151 -5
  34. package/dist/nodes/drop-down.js.map +1 -1
  35. package/dist/nodes/event-controller.d.ts +1 -0
  36. package/dist/nodes/event-controller.d.ts.map +1 -1
  37. package/dist/nodes/event-controller.js +11 -3
  38. package/dist/nodes/event-controller.js.map +1 -1
  39. package/dist/nodes/fixed-child.d.ts +1 -2
  40. package/dist/nodes/fixed-child.d.ts.map +1 -1
  41. package/dist/nodes/fixed-child.js +21 -21
  42. package/dist/nodes/fixed-child.js.map +1 -1
  43. package/dist/nodes/font-dialog-button.d.ts +1 -1
  44. package/dist/nodes/font-dialog-button.d.ts.map +1 -1
  45. package/dist/nodes/font-dialog-button.js +8 -0
  46. package/dist/nodes/font-dialog-button.js.map +1 -1
  47. package/dist/nodes/grid-view.d.ts +6 -5
  48. package/dist/nodes/grid-view.d.ts.map +1 -1
  49. package/dist/nodes/grid-view.js +23 -18
  50. package/dist/nodes/grid-view.js.map +1 -1
  51. package/dist/nodes/internal/accessible.d.ts +5 -0
  52. package/dist/nodes/internal/accessible.d.ts.map +1 -0
  53. package/dist/nodes/internal/accessible.js +119 -0
  54. package/dist/nodes/internal/accessible.js.map +1 -0
  55. package/dist/nodes/internal/base-item-renderer.d.ts.map +1 -1
  56. package/dist/nodes/internal/base-item-renderer.js +0 -1
  57. package/dist/nodes/internal/base-item-renderer.js.map +1 -1
  58. package/dist/nodes/internal/construct.d.ts +10 -0
  59. package/dist/nodes/internal/construct.d.ts.map +1 -0
  60. package/dist/nodes/internal/construct.js +68 -0
  61. package/dist/nodes/internal/construct.js.map +1 -0
  62. package/dist/nodes/internal/header-item-renderer.d.ts +23 -0
  63. package/dist/nodes/internal/header-item-renderer.d.ts.map +1 -0
  64. package/dist/nodes/internal/header-item-renderer.js +87 -0
  65. package/dist/nodes/internal/header-item-renderer.js.map +1 -0
  66. package/dist/nodes/internal/header-renderer-manager.d.ts +13 -0
  67. package/dist/nodes/internal/header-renderer-manager.d.ts.map +1 -0
  68. package/dist/nodes/internal/header-renderer-manager.js +20 -0
  69. package/dist/nodes/internal/header-renderer-manager.js.map +1 -0
  70. package/dist/nodes/internal/list-store.d.ts +10 -11
  71. package/dist/nodes/internal/list-store.d.ts.map +1 -1
  72. package/dist/nodes/internal/list-store.js +28 -29
  73. package/dist/nodes/internal/list-store.js.map +1 -1
  74. package/dist/nodes/internal/sectioned-list-store.d.ts +50 -0
  75. package/dist/nodes/internal/sectioned-list-store.d.ts.map +1 -0
  76. package/dist/nodes/internal/sectioned-list-store.js +250 -0
  77. package/dist/nodes/internal/sectioned-list-store.js.map +1 -0
  78. package/dist/nodes/internal/selection-helpers.d.ts +12 -0
  79. package/dist/nodes/internal/selection-helpers.d.ts.map +1 -0
  80. package/dist/nodes/internal/selection-helpers.js +25 -0
  81. package/dist/nodes/internal/selection-helpers.js.map +1 -0
  82. package/dist/nodes/internal/selection-model-controller.d.ts.map +1 -1
  83. package/dist/nodes/internal/selection-model-controller.js +3 -0
  84. package/dist/nodes/internal/selection-model-controller.js.map +1 -1
  85. package/dist/nodes/internal/simple-list-store.d.ts +7 -12
  86. package/dist/nodes/internal/simple-list-store.d.ts.map +1 -1
  87. package/dist/nodes/internal/simple-list-store.js +58 -35
  88. package/dist/nodes/internal/simple-list-store.js.map +1 -1
  89. package/dist/nodes/internal/text-buffer-controller.d.ts +4 -0
  90. package/dist/nodes/internal/text-buffer-controller.d.ts.map +1 -1
  91. package/dist/nodes/internal/text-buffer-controller.js +49 -9
  92. package/dist/nodes/internal/text-buffer-controller.js.map +1 -1
  93. package/dist/nodes/internal/tree-store.d.ts +3 -0
  94. package/dist/nodes/internal/tree-store.d.ts.map +1 -1
  95. package/dist/nodes/internal/tree-store.js +55 -10
  96. package/dist/nodes/internal/tree-store.js.map +1 -1
  97. package/dist/nodes/list-section.d.ts +27 -0
  98. package/dist/nodes/list-section.d.ts.map +1 -0
  99. package/dist/nodes/list-section.js +43 -0
  100. package/dist/nodes/list-section.js.map +1 -0
  101. package/dist/nodes/list-view.d.ts +6 -3
  102. package/dist/nodes/list-view.d.ts.map +1 -1
  103. package/dist/nodes/list-view.js +54 -14
  104. package/dist/nodes/list-view.js.map +1 -1
  105. package/dist/nodes/models/list.d.ts +13 -5
  106. package/dist/nodes/models/list.d.ts.map +1 -1
  107. package/dist/nodes/models/list.js +135 -21
  108. package/dist/nodes/models/list.js.map +1 -1
  109. package/dist/nodes/shortcut.d.ts +3 -2
  110. package/dist/nodes/shortcut.d.ts.map +1 -1
  111. package/dist/nodes/shortcut.js +19 -4
  112. package/dist/nodes/shortcut.js.map +1 -1
  113. package/dist/nodes/text-anchor.d.ts.map +1 -1
  114. package/dist/nodes/text-anchor.js +7 -1
  115. package/dist/nodes/text-anchor.js.map +1 -1
  116. package/dist/nodes/text-tag.d.ts.map +1 -1
  117. package/dist/nodes/text-tag.js +5 -1
  118. package/dist/nodes/text-tag.js.map +1 -1
  119. package/dist/nodes/text-view.d.ts +1 -0
  120. package/dist/nodes/text-view.d.ts.map +1 -1
  121. package/dist/nodes/text-view.js +4 -0
  122. package/dist/nodes/text-view.js.map +1 -1
  123. package/dist/nodes/widget.d.ts +0 -2
  124. package/dist/nodes/widget.d.ts.map +1 -1
  125. package/dist/nodes/widget.js +44 -61
  126. package/dist/nodes/widget.js.map +1 -1
  127. package/dist/registry.d.ts.map +1 -1
  128. package/dist/registry.js +2 -2
  129. package/dist/registry.js.map +1 -1
  130. package/package.json +3 -3
  131. package/src/generated/internal.ts +333 -44
  132. package/src/generated/jsx.ts +178 -2
  133. package/src/host-config.ts +41 -10
  134. package/src/jsx.ts +166 -15
  135. package/src/metadata.ts +5 -1
  136. package/src/node.ts +20 -6
  137. package/src/nodes/column-view-column.ts +32 -8
  138. package/src/nodes/column-view.ts +59 -14
  139. package/src/nodes/drop-down.ts +182 -6
  140. package/src/nodes/event-controller.ts +11 -3
  141. package/src/nodes/fixed-child.ts +24 -23
  142. package/src/nodes/font-dialog-button.ts +10 -0
  143. package/src/nodes/grid-view.ts +29 -19
  144. package/src/nodes/internal/accessible.ts +156 -0
  145. package/src/nodes/internal/base-item-renderer.ts +0 -1
  146. package/src/nodes/internal/construct.ts +90 -0
  147. package/src/nodes/internal/header-item-renderer.ts +105 -0
  148. package/src/nodes/internal/header-renderer-manager.ts +33 -0
  149. package/src/nodes/internal/list-store.ts +32 -30
  150. package/src/nodes/internal/sectioned-list-store.ts +287 -0
  151. package/src/nodes/internal/selection-helpers.ts +35 -0
  152. package/src/nodes/internal/selection-model-controller.ts +4 -0
  153. package/src/nodes/internal/simple-list-store.ts +60 -43
  154. package/src/nodes/internal/text-buffer-controller.ts +51 -8
  155. package/src/nodes/internal/tree-store.ts +61 -9
  156. package/src/nodes/list-section.ts +64 -0
  157. package/src/nodes/list-view.ts +65 -14
  158. package/src/nodes/models/list.ts +147 -37
  159. package/src/nodes/shortcut.ts +22 -5
  160. package/src/nodes/text-anchor.ts +6 -1
  161. package/src/nodes/text-tag.ts +7 -1
  162. package/src/nodes/text-view.ts +5 -0
  163. package/src/nodes/widget.ts +45 -62
  164. package/src/registry.ts +4 -2
  165. package/dist/nodes/models/grid.d.ts +0 -28
  166. package/dist/nodes/models/grid.d.ts.map +0 -1
  167. package/dist/nodes/models/grid.js +0 -69
  168. package/dist/nodes/models/grid.js.map +0 -1
  169. package/dist/nodes/shortcut-controller.d.ts +0 -10
  170. package/dist/nodes/shortcut-controller.d.ts.map +0 -1
  171. package/dist/nodes/shortcut-controller.js +0 -23
  172. package/dist/nodes/shortcut-controller.js.map +0 -1
  173. package/src/nodes/models/grid.ts +0 -105
  174. package/src/nodes/shortcut-controller.ts +0 -27
package/src/jsx.ts CHANGED
@@ -7,7 +7,14 @@ import type * as GtkSource from "@gtkx/ffi/gtksource";
7
7
  import type * as Pango from "@gtkx/ffi/pango";
8
8
  import type { ReactElement, ReactNode } from "react";
9
9
  import { createElement } from "react";
10
- import type { WidgetSlotNames } from "./generated/jsx.js";
10
+ import type {
11
+ AdwComboRowProps as IntrinsicAdwComboRowProps,
12
+ GtkColumnViewProps as IntrinsicGtkColumnViewProps,
13
+ GtkDropDownProps as IntrinsicGtkDropDownProps,
14
+ GtkGridViewProps as IntrinsicGtkGridViewProps,
15
+ GtkListViewProps as IntrinsicGtkListViewProps,
16
+ WidgetSlotNames,
17
+ } from "./generated/jsx.js";
11
18
 
12
19
  /**
13
20
  * CSS properties that can be animated on a widget.
@@ -132,15 +139,15 @@ export type AnimationProps = {
132
139
  /**
133
140
  * Props for the Shortcut virtual element.
134
141
  *
135
- * Defines a keyboard shortcut. Must be a child of `x.ShortcutController`.
142
+ * Defines a keyboard shortcut. Must be a child of `<GtkShortcutController>`.
136
143
  *
137
144
  * @example
138
145
  * ```tsx
139
- * <x.ShortcutController>
146
+ * <GtkShortcutController>
140
147
  * <x.Shortcut trigger="<Control>s" onActivate={save} />
141
148
  * <x.Shortcut trigger={["F5", "<Control>r"]} onActivate={refresh} />
142
149
  * <x.Shortcut trigger="Escape" onActivate={cancel} disabled={!canCancel} />
143
- * </x.ShortcutController>
150
+ * </GtkShortcutController>
144
151
  * ```
145
152
  */
146
153
  export type ShortcutProps = {
@@ -174,6 +181,8 @@ export type ShortcutProps = {
174
181
  * ```
175
182
  */
176
183
  export type TextAnchorProps = {
184
+ /** Replacement character displayed when the widget is not visible (e.g. in serialized text) */
185
+ replacementChar?: string;
177
186
  /** The widget to embed at this anchor position */
178
187
  children?: ReactNode;
179
188
  };
@@ -359,8 +368,6 @@ export type ContainerSlotNames = {
359
368
 
360
369
  /**
361
370
  * Props for method-based container slot child positioning.
362
- *
363
- * @see {@link x.ContainerSlot} for type-safe usage
364
371
  */
365
372
  export type ContainerSlotProps = {
366
373
  /** The method name to call on the parent widget */
@@ -393,6 +400,23 @@ export type ListItemProps<T = unknown> = {
393
400
  children?: ReactNode;
394
401
  };
395
402
 
403
+ /**
404
+ * Props for section headers in a GtkListView, GtkGridView, or GtkColumnView.
405
+ *
406
+ * Wraps ListItems to group them into sections. When used with `renderHeader`,
407
+ * the view displays a header at the top of each section.
408
+ *
409
+ * @typeParam T - The type of data associated with this section header
410
+ */
411
+ export type ListSectionProps<T = unknown> = {
412
+ /** Unique identifier for this section */
413
+ id: string;
414
+ /** The data value passed to `renderHeader` for this section */
415
+ value: T;
416
+ /** ListItem children belonging to this section */
417
+ children?: ReactNode;
418
+ };
419
+
396
420
  /**
397
421
  * Props for positioning children within a GtkGrid.
398
422
  *
@@ -447,6 +471,8 @@ export type ColumnViewColumnProps<T = unknown> = {
447
471
  id: string;
448
472
  /** Whether clicking the header sorts by this column */
449
473
  sortable?: boolean;
474
+ /** Whether this column is visible */
475
+ visible?: boolean;
450
476
  /** Function to render the cell content for each row */
451
477
  renderCell: (item: T | null) => ReactNode;
452
478
  /** Menu items for the column header context menu */
@@ -670,28 +696,34 @@ type BaseListViewProps = {
670
696
  selectionMode?: Gtk.SelectionMode | null;
671
697
  };
672
698
 
673
- export type ListViewProps = BaseListViewProps & {
699
+ export type ListViewProps<T = unknown, S = unknown> = BaseListViewProps & {
674
700
  /** Function to render each list item. The `row` parameter provides tree state for hierarchical lists. */
675
- // biome-ignore lint/suspicious/noExplicitAny: contravariant parameter requires any for typed callbacks
676
- renderItem: (item: any, row?: Gtk.TreeListRow | null) => ReactNode;
701
+ renderItem: (item: T | null, row?: Gtk.TreeListRow | null) => ReactNode;
677
702
  /** Whether to automatically expand new tree rows (default: false) */
678
703
  autoexpand?: boolean;
704
+ /** Function to render section headers when using ListSection children */
705
+ renderHeader?: ((item: S | null) => ReactNode) | null;
679
706
  };
680
707
 
681
- export type GridViewProps = BaseListViewProps & {
708
+ export type GridViewProps<T = unknown> = BaseListViewProps & {
682
709
  /** Function to render each grid item */
683
- // biome-ignore lint/suspicious/noExplicitAny: contravariant parameter requires any for typed callbacks
684
- renderItem: (item: any) => ReactNode;
710
+ renderItem: (item: T | null) => ReactNode;
685
711
  };
686
712
 
687
713
  /**
688
714
  * Props shared by single-selection dropdown widgets (GtkDropDown, AdwComboRow).
689
715
  */
690
- export type DropDownProps = {
716
+ export type DropDownProps<T = unknown, S = unknown> = {
691
717
  /** ID of the currently selected item */
692
718
  selectedId?: string | null;
693
719
  /** Callback fired when the selected item changes */
694
720
  onSelectionChanged?: ((id: string) => void) | null;
721
+ /** Function to render each item. Sets the primary factory, used for both button and popup list unless overridden by renderListItem. */
722
+ renderItem?: ((item: T | null) => ReactNode) | null;
723
+ /** Function to render items in the popup list only, overriding renderItem for the list. */
724
+ renderListItem?: ((item: T | null) => ReactNode) | null;
725
+ /** Function to render section headers when using ListSection children */
726
+ renderHeader?: ((item: S | null) => ReactNode) | null;
695
727
  };
696
728
 
697
729
  /**
@@ -854,7 +886,32 @@ export const x = {
854
886
  * </GtkListView>
855
887
  * ```
856
888
  */
857
- ListItem: "ListItem" as const,
889
+ ListItem<T = unknown>(props: ListItemProps<T>): ReactElement {
890
+ return createElement("ListItem", props, props.children);
891
+ },
892
+
893
+ /**
894
+ * Component for defining section headers in list/grid views.
895
+ *
896
+ * Wraps ListItems to group them into sections with headers.
897
+ * Requires `renderHeader` on the parent view to render the header content.
898
+ *
899
+ * @example
900
+ * ```tsx
901
+ * <GtkGridView
902
+ * renderItem={(item) => <GtkLabel label={item.name} />}
903
+ * renderHeader={(header) => <GtkLabel label={header.title} />}
904
+ * >
905
+ * <x.ListSection id="section1" value={{ title: "Section 1" }}>
906
+ * <x.ListItem id="a" value={{ name: "Item A" }} />
907
+ * <x.ListItem id="b" value={{ name: "Item B" }} />
908
+ * </x.ListSection>
909
+ * </GtkGridView>
910
+ * ```
911
+ */
912
+ ListSection<T = unknown>(props: ListSectionProps<T>): ReactElement {
913
+ return createElement("ListSection", props, props.children);
914
+ },
858
915
 
859
916
  /**
860
917
  * Component for defining columns in a ColumnView (table widget).
@@ -1078,7 +1135,7 @@ export const x = {
1078
1135
  /**
1079
1136
  * A keyboard shortcut definition.
1080
1137
  *
1081
- * Must be a child of `x.ShortcutController`.
1138
+ * Must be a child of `<GtkShortcutController>`.
1082
1139
  *
1083
1140
  * @example
1084
1141
  * ```tsx
@@ -1125,6 +1182,7 @@ declare global {
1125
1182
  FixedChild: FixedChildProps;
1126
1183
  GridChild: GridChildProps;
1127
1184
  ListItem: ListItemProps;
1185
+ ListSection: ListSectionProps;
1128
1186
  MenuItem: MenuItemProps;
1129
1187
  MenuSection: MenuSectionProps;
1130
1188
  MenuSubmenu: MenuSubmenuProps;
@@ -1144,7 +1202,61 @@ declare global {
1144
1202
  }
1145
1203
  }
1146
1204
 
1205
+ export type AccessibleProps = {
1206
+ accessibleAutocomplete?: Gtk.AccessibleAutocomplete;
1207
+ accessibleDescription?: string;
1208
+ accessibleHasPopup?: boolean;
1209
+ accessibleKeyShortcuts?: string;
1210
+ accessibleLabel?: string;
1211
+ accessibleLevel?: number;
1212
+ accessibleModal?: boolean;
1213
+ accessibleMultiLine?: boolean;
1214
+ accessibleMultiSelectable?: boolean;
1215
+ accessibleOrientation?: Gtk.Orientation;
1216
+ accessiblePlaceholder?: string;
1217
+ accessibleReadOnly?: boolean;
1218
+ accessibleRequired?: boolean;
1219
+ accessibleRoleDescription?: string;
1220
+ accessibleSort?: Gtk.AccessibleSort;
1221
+ accessibleValueMax?: number;
1222
+ accessibleValueMin?: number;
1223
+ accessibleValueNow?: number;
1224
+ accessibleValueText?: string;
1225
+ accessibleHelpText?: string;
1226
+
1227
+ accessibleBusy?: boolean;
1228
+ accessibleChecked?: Gtk.AccessibleTristate;
1229
+ accessibleDisabled?: boolean;
1230
+ accessibleExpanded?: boolean;
1231
+ accessibleHidden?: boolean;
1232
+ accessibleInvalid?: Gtk.AccessibleInvalidState;
1233
+ accessiblePressed?: Gtk.AccessibleTristate;
1234
+ accessibleSelected?: boolean;
1235
+ accessibleVisited?: boolean;
1236
+
1237
+ accessibleActiveDescendant?: Gtk.Widget;
1238
+ accessibleColCount?: number;
1239
+ accessibleColIndex?: number;
1240
+ accessibleColIndexText?: string;
1241
+ accessibleColSpan?: number;
1242
+ accessibleControls?: Gtk.Widget[];
1243
+ accessibleDescribedBy?: Gtk.Widget[];
1244
+ accessibleDetails?: Gtk.Widget[];
1245
+ accessibleErrorMessage?: Gtk.Widget[];
1246
+ accessibleFlowTo?: Gtk.Widget[];
1247
+ accessibleLabelledBy?: Gtk.Widget[];
1248
+ accessibleOwns?: Gtk.Widget[];
1249
+ accessiblePosInSet?: number;
1250
+ accessibleRowCount?: number;
1251
+ accessibleRowIndex?: number;
1252
+ accessibleRowIndexText?: string;
1253
+ accessibleRowSpan?: number;
1254
+ accessibleSetSize?: number;
1255
+ };
1256
+
1147
1257
  declare module "./generated/jsx.js" {
1258
+ interface WidgetProps extends AccessibleProps {}
1259
+
1148
1260
  interface GtkRangeProps extends Omit<AdjustableProps, "onValueChanged"> {
1149
1261
  /** Callback fired when the range value changes */
1150
1262
  onValueChanged?: ((value: number, self: Gtk.Range) => void) | null;
@@ -1218,6 +1330,8 @@ declare module "./generated/jsx.js" {
1218
1330
  onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null;
1219
1331
  /** Estimated row height in pixels for virtualization */
1220
1332
  estimatedRowHeight?: number | null;
1333
+ /** Function to render section headers when using ListSection children */
1334
+ renderHeader?: ((item: unknown) => ReactNode) | null;
1221
1335
  }
1222
1336
 
1223
1337
  interface GtkDropDownProps extends DropDownProps {}
@@ -1261,6 +1375,10 @@ declare module "./generated/jsx.js" {
1261
1375
  interface GtkFontDialogButtonProps extends DialogButtonProps {
1262
1376
  /** Callback fired when the selected font changes */
1263
1377
  onFontDescChanged?: ((fontDesc: Pango.FontDescription) => void) | null;
1378
+ /** Filter to restrict which fonts are shown in the dialog */
1379
+ filter?: Gtk.Filter | null;
1380
+ /** Custom font map to select fonts from */
1381
+ fontMap?: Pango.FontMap | null;
1264
1382
  }
1265
1383
 
1266
1384
  interface GtkAboutDialogProps {
@@ -1293,4 +1411,37 @@ declare module "./generated/jsx.js" {
1293
1411
  }
1294
1412
  }
1295
1413
 
1414
+ export function GtkListView<T = unknown, S = unknown>(
1415
+ props: Omit<IntrinsicGtkListViewProps, keyof ListViewProps> & ListViewProps<T, S> & { children?: ReactNode },
1416
+ ): ReactElement {
1417
+ return createElement("GtkListView", props);
1418
+ }
1419
+
1420
+ export function GtkGridView<T = unknown>(
1421
+ props: Omit<IntrinsicGtkGridViewProps, keyof GridViewProps> & GridViewProps<T> & { children?: ReactNode },
1422
+ ): ReactElement {
1423
+ return createElement("GtkGridView", props);
1424
+ }
1425
+
1426
+ export function GtkDropDown<T = unknown, S = unknown>(
1427
+ props: Omit<IntrinsicGtkDropDownProps, keyof DropDownProps> & DropDownProps<T, S> & { children?: ReactNode },
1428
+ ): ReactElement {
1429
+ return createElement("GtkDropDown", props);
1430
+ }
1431
+
1432
+ export function AdwComboRow<T = unknown, S = unknown>(
1433
+ props: Omit<IntrinsicAdwComboRowProps, keyof DropDownProps> & DropDownProps<T, S> & { children?: ReactNode },
1434
+ ): ReactElement {
1435
+ return createElement("AdwComboRow", props);
1436
+ }
1437
+
1438
+ export function GtkColumnView<T = unknown>(
1439
+ props: Omit<IntrinsicGtkColumnViewProps, "renderHeader"> & {
1440
+ renderHeader?: ((item: T | null) => ReactNode) | null;
1441
+ children?: ReactNode;
1442
+ },
1443
+ ): ReactElement {
1444
+ return createElement("GtkColumnView", props);
1445
+ }
1446
+
1296
1447
  export * from "./generated/jsx.js";
package/src/metadata.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { NativeClass } from "@gtkx/ffi";
2
- import { PROPS, SIGNALS } from "./generated/internal.js";
2
+ import { CONSTRUCT_ONLY_PROPS, PROPS, SIGNALS } from "./generated/internal.js";
3
3
  import type { Container } from "./types.js";
4
4
 
5
5
  const walkPrototypeChain = <T>(instance: Container, lookup: (typeName: string) => T | null): T | null => {
@@ -30,6 +30,10 @@ const walkPrototypeChain = <T>(instance: Container, lookup: (typeName: string) =
30
30
  export const resolvePropMeta = (instance: Container, key: string): [string | null, string] | null =>
31
31
  walkPrototypeChain(instance, (typeName) => PROPS[typeName]?.[key] ?? null);
32
32
 
33
+ export const isConstructOnlyProp = (instance: Container, key: string): boolean =>
34
+ walkPrototypeChain(instance, (typeName) => (CONSTRUCT_ONLY_PROPS[typeName]?.[key] !== undefined ? true : null)) ??
35
+ false;
36
+
33
37
  export const resolveSignal = (instance: Container, propName: string): string | null => {
34
38
  if (propName === "onNotify") return "notify";
35
39
  return walkPrototypeChain(instance, (typeName) => SIGNALS[typeName]?.[propName] ?? null);
package/src/node.ts CHANGED
@@ -15,6 +15,7 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
15
15
  parent: TParent | null = null;
16
16
  children: TChild[] = [];
17
17
  private childIndices = new Map<TChild, number>();
18
+ private childrenDirty = false;
18
19
 
19
20
  constructor(typeName: string, props: TProps, container: TContainer, rootContainer: Container) {
20
21
  this.typeName = typeName;
@@ -44,6 +45,7 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
44
45
  }
45
46
 
46
47
  public appendChild(child: TChild): void {
48
+ if (this.childrenDirty) this.flushChildRemovals();
47
49
  if (!this.isValidChild(child)) {
48
50
  throw new Error(`Cannot append '${child.typeName}' to '${this.typeName}'`);
49
51
  }
@@ -53,16 +55,18 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
53
55
  }
54
56
 
55
57
  public removeChild(child: TChild): void {
56
- const index = this.childIndices.get(child);
57
- if (index !== undefined) {
58
- child.setParent(null);
59
- this.children.splice(index, 1);
60
- this.childIndices.delete(child);
61
- this.rebuildChildIndices(index);
58
+ if (!this.childIndices.has(child)) return;
59
+ child.setParent(null);
60
+ this.childIndices.delete(child);
61
+ if (!this.childrenDirty) {
62
+ this.childrenDirty = true;
63
+ queueMicrotask(() => this.flushChildRemovals());
62
64
  }
63
65
  }
64
66
 
65
67
  public insertBefore(child: TChild, before: TChild): void {
68
+ if (this.childrenDirty) this.flushChildRemovals();
69
+
66
70
  const beforeIndex = this.childIndices.get(before);
67
71
  if (beforeIndex === undefined) {
68
72
  throw new Error(`Cannot find 'before' child '${before.typeName}' in '${this.typeName}'`);
@@ -86,6 +90,16 @@ export class Node<TContainer = any, TProps = any, TParent extends Node = any, TC
86
90
  child.setParent(this);
87
91
  }
88
92
 
93
+ private flushChildRemovals(): void {
94
+ if (!this.childrenDirty) return;
95
+ this.childrenDirty = false;
96
+ this.children = this.children.filter((c) => this.childIndices.has(c));
97
+ this.childIndices.clear();
98
+ for (let i = 0; i < this.children.length; i++) {
99
+ this.childIndices.set(this.children[i] as TChild, i);
100
+ }
101
+ }
102
+
89
103
  private rebuildChildIndices(fromIndex: number): void {
90
104
  for (let i = fromIndex; i < this.children.length; i++) {
91
105
  this.childIndices.set(this.children[i] as TChild, i);
@@ -3,7 +3,9 @@ import * as Gtk from "@gtkx/ffi/gtk";
3
3
  import type { ColumnViewColumnProps } from "../jsx.js";
4
4
  import type { Node } from "../node.js";
5
5
  import type { Container } from "../types.js";
6
+ import { GridItemRenderer } from "./internal/grid-item-renderer.js";
6
7
  import { ListItemRenderer } from "./internal/list-item-renderer.js";
8
+ import type { ListStore } from "./internal/list-store.js";
7
9
  import { hasChanged } from "./internal/props.js";
8
10
  import type { TreeStore } from "./internal/tree-store.js";
9
11
  import { MenuNode } from "./menu.js";
@@ -20,16 +22,17 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
20
22
  return parent instanceof WidgetNode && parent.container instanceof Gtk.ColumnView;
21
23
  }
22
24
  private column: Gtk.ColumnViewColumn;
23
- private itemRenderer: ListItemRenderer;
25
+ private treeRenderer: ListItemRenderer | null;
26
+ private flatRenderer: GridItemRenderer | null = null;
24
27
  private menu: MenuModel | null = null;
25
28
  private actionGroup: Gio.SimpleActionGroup | null = null;
26
29
  private columnView: Gtk.ColumnView | null = null;
27
30
 
28
31
  constructor(typeName: string, props: ColumnViewColumnProps, container: undefined, rootContainer: Container) {
29
32
  super(typeName, props, container, rootContainer);
30
- this.itemRenderer = new ListItemRenderer(this.signalStore);
33
+ this.treeRenderer = new ListItemRenderer(this.signalStore);
31
34
  this.column = new Gtk.ColumnViewColumn();
32
- this.column.setFactory(this.itemRenderer.getFactory());
35
+ this.column.setFactory(this.treeRenderer.getFactory());
33
36
  }
34
37
 
35
38
  public override appendChild(child: MenuNode): void {
@@ -61,7 +64,8 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
61
64
 
62
65
  public override detachDeletedInstance(): void {
63
66
  this.cleanupMenu();
64
- this.itemRenderer.dispose();
67
+ this.treeRenderer?.dispose();
68
+ this.flatRenderer?.dispose();
65
69
  super.detachDeletedInstance();
66
70
  }
67
71
 
@@ -70,15 +74,30 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
70
74
  }
71
75
 
72
76
  public rebindItem(id: string): void {
73
- this.itemRenderer.rebindItem(id);
77
+ this.treeRenderer?.rebindItem(id);
78
+ this.flatRenderer?.rebindItem(id);
74
79
  }
75
80
 
76
81
  public setStore(model: TreeStore | null): void {
77
- this.itemRenderer.setStore(model);
82
+ this.treeRenderer?.setStore(model);
83
+ }
84
+
85
+ public setFlatStore(store: ListStore): void {
86
+ if (this.treeRenderer) {
87
+ this.treeRenderer.dispose();
88
+ this.treeRenderer = null;
89
+ }
90
+ if (!this.flatRenderer) {
91
+ this.flatRenderer = new GridItemRenderer(this.signalStore);
92
+ this.flatRenderer.setRenderFn(this.props.renderCell);
93
+ this.column.setFactory(this.flatRenderer.getFactory());
94
+ }
95
+ this.flatRenderer.setStore(store);
78
96
  }
79
97
 
80
98
  public setEstimatedRowHeight(height: number | null): void {
81
- this.itemRenderer.setEstimatedItemHeight(height);
99
+ this.treeRenderer?.setEstimatedItemHeight(height);
100
+ this.flatRenderer?.setEstimatedItemHeight(height);
82
101
  }
83
102
 
84
103
  public attachToColumnView(columnView: Gtk.ColumnView): void {
@@ -125,7 +144,8 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
125
144
 
126
145
  private applyOwnProps(oldProps: ColumnViewColumnProps | null, newProps: ColumnViewColumnProps): void {
127
146
  if (hasChanged(oldProps, newProps, "renderCell")) {
128
- this.itemRenderer.setRenderFn(newProps.renderCell);
147
+ this.treeRenderer?.setRenderFn(newProps.renderCell);
148
+ this.flatRenderer?.setRenderFn(newProps.renderCell);
129
149
  }
130
150
 
131
151
  if (hasChanged(oldProps, newProps, "title")) {
@@ -157,6 +177,10 @@ export class ColumnViewColumnNode extends VirtualNode<ColumnViewColumnProps, Wid
157
177
  }
158
178
  }
159
179
 
180
+ if (hasChanged(oldProps, newProps, "visible")) {
181
+ this.column.setVisible(newProps.visible ?? true);
182
+ }
183
+
160
184
  if (hasChanged(oldProps, newProps, "sortable")) {
161
185
  if (newProps.sortable) {
162
186
  this.column.setSorter(new Gtk.StringSorter());
@@ -5,24 +5,34 @@ import type { Container } from "../types.js";
5
5
  import { ColumnViewColumnNode } from "./column-view-column.js";
6
6
  import { ContainerSlotNode } from "./container-slot.js";
7
7
  import { EventControllerNode } from "./event-controller.js";
8
+ import type { HeaderItemRenderer } from "./internal/header-item-renderer.js";
9
+ import { updateHeaderRenderer } from "./internal/header-renderer-manager.js";
8
10
  import { filterProps, hasChanged } from "./internal/props.js";
9
11
  import { ListItemNode } from "./list-item.js";
12
+ import { ListSectionNode } from "./list-section.js";
10
13
  import { ListModel, type ListModelProps } from "./models/list.js";
11
14
  import { SlotNode } from "./slot.js";
12
15
  import { WidgetNode } from "./widget.js";
13
16
 
14
- const OWN_PROPS = ["sortColumn", "sortOrder", "onSortChanged", "estimatedRowHeight"] as const;
17
+ const OWN_PROPS = ["sortColumn", "sortOrder", "onSortChanged", "estimatedRowHeight", "renderHeader"] as const;
15
18
 
16
19
  type ColumnViewProps = Pick<GtkColumnViewProps, (typeof OWN_PROPS)[number]> & ListModelProps;
17
- type ColumnViewChild = ListItemNode | ColumnViewColumnNode | EventControllerNode | SlotNode | ContainerSlotNode;
20
+ type ColumnViewChild =
21
+ | ListItemNode
22
+ | ListSectionNode
23
+ | ColumnViewColumnNode
24
+ | EventControllerNode
25
+ | SlotNode
26
+ | ContainerSlotNode;
18
27
 
19
28
  export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps, ColumnViewChild> {
20
- private handleSortChange: (() => void) | null = null;
21
29
  private list: ListModel;
30
+ private headerRenderer: HeaderItemRenderer | null = null;
22
31
 
23
32
  public override isValidChild(child: Node): boolean {
24
33
  return (
25
34
  child instanceof ListItemNode ||
35
+ child instanceof ListSectionNode ||
26
36
  child instanceof ColumnViewColumnNode ||
27
37
  child instanceof EventControllerNode ||
28
38
  child instanceof SlotNode ||
@@ -34,6 +44,9 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
34
44
 
35
45
  constructor(typeName: string, props: ColumnViewProps, container: Gtk.ColumnView, rootContainer: Container) {
36
46
  super(typeName, props, container, rootContainer);
47
+
48
+ const flat = props.renderHeader != null;
49
+
37
50
  this.list = new ListModel(
38
51
  { owner: this, signalStore: this.signalStore },
39
52
  {
@@ -41,8 +54,9 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
41
54
  selected: props.selected,
42
55
  onSelectionChanged: props.onSelectionChanged,
43
56
  },
57
+ flat,
44
58
  );
45
- this.list.getStore().setOnItemUpdated((id) => {
59
+ this.list.setOnItemUpdated((id) => {
46
60
  for (const column of this.columnNodes) {
47
61
  column.rebindItem(id);
48
62
  }
@@ -52,7 +66,7 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
52
66
  public override appendChild(child: ColumnViewChild): void {
53
67
  super.appendChild(child);
54
68
 
55
- if (child instanceof ListItemNode) {
69
+ if (child instanceof ListItemNode || child instanceof ListSectionNode) {
56
70
  this.list.appendChild(child);
57
71
  return;
58
72
  }
@@ -65,7 +79,11 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
65
79
  this.container.removeColumn(existingColumn);
66
80
  }
67
81
 
68
- child.setStore(this.list.getStore());
82
+ if (this.list.isFlatMode()) {
83
+ child.setFlatStore(this.list.getFlatStore());
84
+ } else {
85
+ child.setStore(this.list.getStore());
86
+ }
69
87
  child.setEstimatedRowHeight(this.estimatedRowHeight);
70
88
  this.container.appendColumn(child.getColumn());
71
89
  child.attachToColumnView(this.container);
@@ -75,8 +93,8 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
75
93
  public override insertBefore(child: ColumnViewChild, before: ColumnViewChild): void {
76
94
  super.insertBefore(child, before);
77
95
 
78
- if (child instanceof ListItemNode) {
79
- if (before instanceof ListItemNode) {
96
+ if (child instanceof ListItemNode || child instanceof ListSectionNode) {
97
+ if (before instanceof ListItemNode || before instanceof ListSectionNode) {
80
98
  this.list.insertBefore(child, before);
81
99
  }
82
100
  return;
@@ -90,7 +108,11 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
90
108
  this.container.removeColumn(existingColumn);
91
109
  }
92
110
 
93
- child.setStore(this.list.getStore());
111
+ if (this.list.isFlatMode()) {
112
+ child.setFlatStore(this.list.getFlatStore());
113
+ } else {
114
+ child.setStore(this.list.getStore());
115
+ }
94
116
  child.setEstimatedRowHeight(this.estimatedRowHeight);
95
117
 
96
118
  if (before instanceof ColumnViewColumnNode) {
@@ -105,7 +127,7 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
105
127
  }
106
128
 
107
129
  public override removeChild(child: ColumnViewChild): void {
108
- if (child instanceof ListItemNode) {
130
+ if (child instanceof ListItemNode || child instanceof ListSectionNode) {
109
131
  this.list.removeChild(child);
110
132
  super.removeChild(child);
111
133
  return;
@@ -135,7 +157,14 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
135
157
  public override commitUpdate(oldProps: ColumnViewProps | null, newProps: ColumnViewProps): void {
136
158
  super.commitUpdate(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
137
159
  this.applyOwnProps(oldProps, newProps);
160
+
161
+ const previousModel = this.list.getSelectionModel();
138
162
  this.list.updateProps(oldProps ? filterProps(oldProps, OWN_PROPS) : null, filterProps(newProps, OWN_PROPS));
163
+ const currentModel = this.list.getSelectionModel();
164
+
165
+ if (previousModel !== currentModel) {
166
+ this.container.setModel(currentModel);
167
+ }
139
168
  }
140
169
 
141
170
  public override commitMount(): void {
@@ -146,6 +175,7 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
146
175
 
147
176
  public override detachDeletedInstance(): void {
148
177
  this.columnNodes.clear();
178
+ this.headerRenderer?.dispose();
149
179
  super.detachDeletedInstance();
150
180
  }
151
181
 
@@ -155,11 +185,13 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
155
185
  const onSortChanged = newProps.onSortChanged;
156
186
 
157
187
  if (sorter instanceof Gtk.ColumnViewSorter) {
158
- this.handleSortChange = () => {
159
- onSortChanged?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
160
- };
188
+ const handleSortChange = onSortChanged
189
+ ? () => {
190
+ onSortChanged(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
191
+ }
192
+ : undefined;
161
193
 
162
- this.signalStore.set(this, sorter, "changed", this.handleSortChange);
194
+ this.signalStore.set(this, sorter, "changed", handleSortChange);
163
195
  }
164
196
  }
165
197
 
@@ -180,6 +212,19 @@ export class ColumnViewNode extends WidgetNode<Gtk.ColumnView, ColumnViewProps,
180
212
  column.setEstimatedRowHeight(this.estimatedRowHeight);
181
213
  }
182
214
  }
215
+
216
+ if (hasChanged(oldProps, newProps, "renderHeader")) {
217
+ this.headerRenderer = updateHeaderRenderer(
218
+ this.headerRenderer,
219
+ {
220
+ signalStore: this.signalStore,
221
+ isEnabled: () => this.list.isFlatMode(),
222
+ resolveItem: (id) => this.list.getFlatStore().getHeaderValue(id),
223
+ setFactory: (factory) => this.container.setHeaderFactory(factory),
224
+ },
225
+ newProps.renderHeader,
226
+ );
227
+ }
183
228
  }
184
229
 
185
230
  private findColumn<T>(predicate: (column: Gtk.ColumnViewColumn, index: number) => T | null): T | null {