@gtkx/components 0.21.0 → 1.0.0-rc.2

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 (192) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +173 -0
  3. package/dist/adw/index.d.ts +3 -0
  4. package/dist/adw/index.d.ts.map +1 -0
  5. package/dist/adw/index.js +2 -0
  6. package/dist/adw/index.js.map +1 -0
  7. package/dist/adw/toast.d.ts +10 -0
  8. package/dist/adw/toast.d.ts.map +1 -0
  9. package/dist/adw/toast.js +51 -0
  10. package/dist/adw/toast.js.map +1 -0
  11. package/dist/adw/types.d.ts +28 -0
  12. package/dist/adw/types.d.ts.map +1 -0
  13. package/dist/adw/types.js +2 -0
  14. package/dist/adw/types.js.map +1 -0
  15. package/dist/column-view.d.ts +6 -14
  16. package/dist/column-view.d.ts.map +1 -1
  17. package/dist/column-view.js +98 -100
  18. package/dist/column-view.js.map +1 -1
  19. package/dist/drop-down.d.ts +8 -37
  20. package/dist/drop-down.d.ts.map +1 -1
  21. package/dist/drop-down.js +109 -118
  22. package/dist/drop-down.js.map +1 -1
  23. package/dist/grid-view.d.ts +4 -11
  24. package/dist/grid-view.d.ts.map +1 -1
  25. package/dist/grid-view.js +25 -19
  26. package/dist/grid-view.js.map +1 -1
  27. package/dist/index.d.ts +6 -18
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -14
  30. package/dist/index.js.map +1 -1
  31. package/dist/internal/cells.d.ts +50 -0
  32. package/dist/internal/cells.d.ts.map +1 -0
  33. package/dist/internal/cells.js +185 -0
  34. package/dist/internal/cells.js.map +1 -0
  35. package/dist/internal/collection-index.d.ts +21 -0
  36. package/dist/internal/collection-index.d.ts.map +1 -0
  37. package/dist/internal/collection-index.js +106 -0
  38. package/dist/internal/collection-index.js.map +1 -0
  39. package/dist/internal/collection-model.d.ts +12 -0
  40. package/dist/internal/collection-model.d.ts.map +1 -0
  41. package/dist/internal/collection-model.js +164 -0
  42. package/dist/internal/collection-model.js.map +1 -0
  43. package/dist/internal/collection.d.ts +18 -0
  44. package/dist/internal/collection.d.ts.map +1 -0
  45. package/dist/internal/collection.js +64 -0
  46. package/dist/internal/collection.js.map +1 -0
  47. package/dist/internal/expansion.d.ts +9 -0
  48. package/dist/internal/expansion.d.ts.map +1 -0
  49. package/dist/internal/expansion.js +75 -0
  50. package/dist/internal/expansion.js.map +1 -0
  51. package/dist/internal/selection.d.ts +12 -0
  52. package/dist/internal/selection.d.ts.map +1 -0
  53. package/dist/internal/selection.js +99 -0
  54. package/dist/internal/selection.js.map +1 -0
  55. package/dist/internal/use-collection.d.ts +18 -0
  56. package/dist/internal/use-collection.d.ts.map +1 -0
  57. package/dist/internal/use-collection.js +42 -0
  58. package/dist/internal/use-collection.js.map +1 -0
  59. package/dist/internal/use-widget-ref.d.ts +4 -0
  60. package/dist/internal/use-widget-ref.d.ts.map +1 -0
  61. package/dist/internal/use-widget-ref.js +8 -0
  62. package/dist/internal/use-widget-ref.js.map +1 -0
  63. package/dist/list-view.d.ts +4 -11
  64. package/dist/list-view.d.ts.map +1 -1
  65. package/dist/list-view.js +32 -28
  66. package/dist/list-view.js.map +1 -1
  67. package/dist/size-group.d.ts +12 -0
  68. package/dist/size-group.d.ts.map +1 -0
  69. package/dist/size-group.js +41 -0
  70. package/dist/size-group.js.map +1 -0
  71. package/dist/types.d.ts +104 -161
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js.map +1 -1
  74. package/package.json +62 -52
  75. package/src/adw/index.ts +2 -0
  76. package/src/adw/toast.tsx +74 -0
  77. package/src/adw/types.ts +32 -0
  78. package/src/column-view.tsx +184 -184
  79. package/src/drop-down.tsx +173 -227
  80. package/src/grid-view.tsx +29 -65
  81. package/src/index.ts +16 -25
  82. package/src/internal/cells.tsx +345 -0
  83. package/src/internal/collection-index.ts +168 -0
  84. package/src/internal/collection-model.ts +246 -0
  85. package/src/internal/collection.ts +104 -0
  86. package/src/internal/expansion.ts +123 -0
  87. package/src/internal/selection.tsx +159 -0
  88. package/src/internal/use-collection.tsx +70 -0
  89. package/src/internal/use-widget-ref.ts +11 -0
  90. package/src/list-view.tsx +45 -78
  91. package/src/size-group.tsx +77 -0
  92. package/src/types.ts +171 -191
  93. package/dist/collection-view.d.ts +0 -30
  94. package/dist/collection-view.d.ts.map +0 -1
  95. package/dist/collection-view.js +0 -52
  96. package/dist/collection-view.js.map +0 -1
  97. package/dist/column-view-column.d.ts +0 -17
  98. package/dist/column-view-column.d.ts.map +0 -1
  99. package/dist/column-view-column.js +0 -51
  100. package/dist/column-view-column.js.map +0 -1
  101. package/dist/constraint-layout-apply.d.ts +0 -6
  102. package/dist/constraint-layout-apply.d.ts.map +0 -1
  103. package/dist/constraint-layout-apply.js +0 -107
  104. package/dist/constraint-layout-apply.js.map +0 -1
  105. package/dist/constraint-layout.d.ts +0 -13
  106. package/dist/constraint-layout.d.ts.map +0 -1
  107. package/dist/constraint-layout.js +0 -85
  108. package/dist/constraint-layout.js.map +0 -1
  109. package/dist/contexts/column-view-context.d.ts +0 -17
  110. package/dist/contexts/column-view-context.d.ts.map +0 -1
  111. package/dist/contexts/column-view-context.js +0 -10
  112. package/dist/contexts/column-view-context.js.map +0 -1
  113. package/dist/hooks/use-drop-down-selection.d.ts +0 -15
  114. package/dist/hooks/use-drop-down-selection.d.ts.map +0 -1
  115. package/dist/hooks/use-drop-down-selection.js +0 -35
  116. package/dist/hooks/use-drop-down-selection.js.map +0 -1
  117. package/dist/hooks/use-header-menu.d.ts +0 -4
  118. package/dist/hooks/use-header-menu.d.ts.map +0 -1
  119. package/dist/hooks/use-header-menu.js +0 -32
  120. package/dist/hooks/use-header-menu.js.map +0 -1
  121. package/dist/hooks/use-list-model.d.ts +0 -20
  122. package/dist/hooks/use-list-model.d.ts.map +0 -1
  123. package/dist/hooks/use-list-model.js +0 -70
  124. package/dist/hooks/use-list-model.js.map +0 -1
  125. package/dist/hooks/use-model-installation.d.ts +0 -9
  126. package/dist/hooks/use-model-installation.d.ts.map +0 -1
  127. package/dist/hooks/use-model-installation.js +0 -20
  128. package/dist/hooks/use-model-installation.js.map +0 -1
  129. package/dist/hooks/use-realized-slots.d.ts +0 -20
  130. package/dist/hooks/use-realized-slots.d.ts.map +0 -1
  131. package/dist/hooks/use-realized-slots.js +0 -66
  132. package/dist/hooks/use-realized-slots.js.map +0 -1
  133. package/dist/hooks/use-selection-model.d.ts +0 -12
  134. package/dist/hooks/use-selection-model.d.ts.map +0 -1
  135. package/dist/hooks/use-selection-model.js +0 -111
  136. package/dist/hooks/use-selection-model.js.map +0 -1
  137. package/dist/hooks/use-sort-handler.d.ts +0 -12
  138. package/dist/hooks/use-sort-handler.d.ts.map +0 -1
  139. package/dist/hooks/use-sort-handler.js +0 -87
  140. package/dist/hooks/use-sort-handler.js.map +0 -1
  141. package/dist/list-portal-host.d.ts +0 -11
  142. package/dist/list-portal-host.d.ts.map +0 -1
  143. package/dist/list-portal-host.js +0 -8
  144. package/dist/list-portal-host.js.map +0 -1
  145. package/dist/list-slot.d.ts +0 -14
  146. package/dist/list-slot.d.ts.map +0 -1
  147. package/dist/list-slot.js +0 -29
  148. package/dist/list-slot.js.map +0 -1
  149. package/dist/menu-items.d.ts +0 -5
  150. package/dist/menu-items.d.ts.map +0 -1
  151. package/dist/menu-items.js +0 -48
  152. package/dist/menu-items.js.map +0 -1
  153. package/dist/menu.d.ts +0 -18
  154. package/dist/menu.d.ts.map +0 -1
  155. package/dist/menu.js +0 -26
  156. package/dist/menu.js.map +0 -1
  157. package/dist/utils/item-resolver.d.ts +0 -28
  158. package/dist/utils/item-resolver.d.ts.map +0 -1
  159. package/dist/utils/item-resolver.js +0 -111
  160. package/dist/utils/item-resolver.js.map +0 -1
  161. package/dist/utils/list-item-flatten.d.ts +0 -32
  162. package/dist/utils/list-item-flatten.d.ts.map +0 -1
  163. package/dist/utils/list-item-flatten.js +0 -88
  164. package/dist/utils/list-item-flatten.js.map +0 -1
  165. package/dist/utils/position-only-model.d.ts +0 -10
  166. package/dist/utils/position-only-model.d.ts.map +0 -1
  167. package/dist/utils/position-only-model.js +0 -83
  168. package/dist/utils/position-only-model.js.map +0 -1
  169. package/dist/utils/realized-slot-store.d.ts +0 -30
  170. package/dist/utils/realized-slot-store.d.ts.map +0 -1
  171. package/dist/utils/realized-slot-store.js +0 -103
  172. package/dist/utils/realized-slot-store.js.map +0 -1
  173. package/src/collection-view.tsx +0 -125
  174. package/src/column-view-column.tsx +0 -73
  175. package/src/constraint-layout-apply.ts +0 -121
  176. package/src/constraint-layout.tsx +0 -130
  177. package/src/contexts/column-view-context.ts +0 -28
  178. package/src/hooks/use-drop-down-selection.ts +0 -53
  179. package/src/hooks/use-header-menu.ts +0 -33
  180. package/src/hooks/use-list-model.ts +0 -145
  181. package/src/hooks/use-model-installation.ts +0 -25
  182. package/src/hooks/use-realized-slots.ts +0 -93
  183. package/src/hooks/use-selection-model.ts +0 -121
  184. package/src/hooks/use-sort-handler.ts +0 -102
  185. package/src/list-portal-host.tsx +0 -27
  186. package/src/list-slot.tsx +0 -56
  187. package/src/menu-items.ts +0 -48
  188. package/src/menu.tsx +0 -34
  189. package/src/utils/item-resolver.ts +0 -143
  190. package/src/utils/list-item-flatten.ts +0 -113
  191. package/src/utils/position-only-model.ts +0 -115
  192. package/src/utils/realized-slot-store.ts +0 -126
@@ -1,125 +0,0 @@
1
- import type * as Gio from "@gtkx/gi/gio";
2
- import type * as Gtk from "@gtkx/gi/gtk";
3
- import { useForwardedRef } from "@gtkx/react";
4
- import {
5
- createElement,
6
- type ElementType,
7
- type ReactElement,
8
- type ReactNode,
9
- type Ref,
10
- useCallback,
11
- useRef,
12
- } from "react";
13
- import { useListModel } from "./hooks/use-list-model.js";
14
- import { useModelInstallation } from "./hooks/use-model-installation.js";
15
- import { type FactoryBinding, useRealizedSlots } from "./hooks/use-realized-slots.js";
16
- import { useSelectionModel } from "./hooks/use-selection-model.js";
17
- import { ListPortalHost } from "./list-portal-host.js";
18
- import type { SlotRenderer } from "./list-slot.js";
19
- import type { ListItem } from "./types.js";
20
- import type { ItemResolver } from "./utils/item-resolver.js";
21
- import type { RealizedSlotStore } from "./utils/realized-slot-store.js";
22
-
23
- export interface ModelBinding<W extends Gtk.Widget> {
24
- install(widget: W, model: Gio.ListModel): void;
25
- }
26
-
27
- export interface CollectionViewProps<T, S, W extends Gtk.Widget> {
28
- element: ElementType;
29
- intrinsicProps: Record<string, unknown>;
30
- ref: Ref<W | null> | undefined;
31
- items: ListItem<T, S>[] | undefined;
32
- model: Gio.ListModel | undefined;
33
- renderItem: SlotRenderer<T, S>;
34
- autoexpand: boolean | undefined;
35
- renderHeader: ((value: S | undefined) => ReactNode) | undefined;
36
- estimatedHeight: number | undefined;
37
- estimatedWidth: number | undefined;
38
- selected: string[] | null | undefined;
39
- selectionMode: Gtk.SelectionMode | null | undefined;
40
- onSelectionChanged: ((ids: string[]) => void) | null | undefined;
41
- factoryBinding: FactoryBinding<W>;
42
- headerFactoryBinding: FactoryBinding<W> | undefined;
43
- modelBinding: ModelBinding<W>;
44
- children?: ReactNode;
45
- }
46
-
47
- const headerRenderer =
48
- <T, S>(renderHeader: ((value: S | undefined) => ReactNode) | undefined): SlotRenderer<T, S> =>
49
- (value) =>
50
- renderHeader ? renderHeader(value as S | undefined) : null;
51
-
52
- interface CollectionWiring<T, S, W extends Gtk.Widget> {
53
- setRef: (value: W | null) => void;
54
- resolver: ItemResolver<T, S>;
55
- headerResolver: ItemResolver<T, S>;
56
- itemStore: RealizedSlotStore;
57
- headerStore: RealizedSlotStore;
58
- }
59
-
60
- const useCollectionWiring = <T, S, W extends Gtk.Widget>(
61
- props: CollectionViewProps<T, S, W>,
62
- ): CollectionWiring<T, S, W> => {
63
- const widgetRef = useRef<W | null>(null);
64
- const captureWidget = useCallback((value: W | null) => {
65
- widgetRef.current = value;
66
- }, []);
67
- const [, setRef] = useForwardedRef<W>(props.ref, captureWidget);
68
-
69
- const externalModel = props.model;
70
- const listModel = useListModel<T, S>(
71
- externalModel === undefined ? { items: props.items, autoexpand: props.autoexpand } : { model: externalModel },
72
- );
73
-
74
- const controlledSelection = useSelectionModel<T, S>({
75
- base: listModel.model,
76
- selectionMode: props.selectionMode,
77
- selected: props.selected,
78
- onSelectionChanged: props.onSelectionChanged,
79
- resolver: listModel.resolver,
80
- });
81
- const installedModel: Gio.ListModel = externalModel === undefined ? controlledSelection : externalModel;
82
-
83
- const items = useRealizedSlots<W>({
84
- target: widgetRef,
85
- binding: props.factoryBinding,
86
- estimatedHeight: props.estimatedHeight,
87
- estimatedWidth: props.estimatedWidth,
88
- });
89
- const headers = useRealizedSlots<W>({
90
- target: props.headerFactoryBinding ? widgetRef : null,
91
- binding: props.headerFactoryBinding ?? props.factoryBinding,
92
- estimatedHeight: props.estimatedHeight,
93
- estimatedWidth: props.estimatedWidth,
94
- });
95
-
96
- useModelInstallation(widgetRef, installedModel, (widget, model) => props.modelBinding.install(widget, model));
97
-
98
- return {
99
- setRef,
100
- resolver: listModel.resolver,
101
- headerResolver: listModel.headerResolver,
102
- itemStore: items.store,
103
- headerStore: headers.store,
104
- };
105
- };
106
-
107
- export const CollectionView = <T, S, W extends Gtk.Widget>(props: CollectionViewProps<T, S, W>): ReactNode => {
108
- const { setRef, resolver, headerResolver, itemStore, headerStore } = useCollectionWiring(props);
109
- const intrinsic: ReactElement = createElement(props.element, { ...props.intrinsicProps, ref: setRef });
110
-
111
- return (
112
- <>
113
- {intrinsic}
114
- <ListPortalHost store={itemStore} resolver={resolver} render={props.renderItem} />
115
- {props.headerFactoryBinding ? (
116
- <ListPortalHost
117
- store={headerStore}
118
- resolver={headerResolver}
119
- render={headerRenderer<T, S>(props.renderHeader)}
120
- />
121
- ) : null}
122
- {props.children}
123
- </>
124
- );
125
- };
@@ -1,73 +0,0 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { GtkColumnViewColumn, type GtkColumnViewColumnProps } from "@gtkx/jsx/gtk";
3
- import { createElement, type ReactNode, useLayoutEffect, useRef, useState } from "react";
4
- import { useColumnViewContext } from "./contexts/column-view-context.js";
5
- import { useHeaderMenu } from "./hooks/use-header-menu.js";
6
- import { type FactoryBinding, useRealizedSlots } from "./hooks/use-realized-slots.js";
7
- import { ListPortalHost } from "./list-portal-host.js";
8
- import type { SlotRenderer } from "./list-slot.js";
9
- import type { ColumnViewColumnProps } from "./types.js";
10
-
11
- const factoryBinding: FactoryBinding<Gtk.ColumnViewColumn> = {
12
- install: (column, factory) => column.setFactory(factory),
13
- uninstall: (column) => column.setFactory(null),
14
- };
15
-
16
- /**
17
- * Props for the {@link ColumnViewColumn} component: the raw
18
- * `GtkColumnViewColumn` element surface (minus its imperative `factory` and
19
- * `sorter` properties) with its cell wiring replaced by the declarative
20
- * {@link ColumnViewColumnProps} API.
21
- */
22
- export type ColumnViewColumnComponentProps<T = unknown> = Omit<GtkColumnViewColumnProps, "factory" | "sorter"> &
23
- ColumnViewColumnProps<T>;
24
-
25
- /**
26
- * A single column of a {@link ColumnView}, rendering each row's cell through
27
- * the declarative `renderCell` callback and optionally attaching a header
28
- * context menu.
29
- */
30
- export const ColumnViewColumn = <T = unknown>(props: ColumnViewColumnComponentProps<T>): ReactNode => {
31
- const { id, title, expand, resizable, fixedWidth, visible, sortable, renderCell, headerMenu } = props;
32
- const context = useColumnViewContext();
33
- const [column, setColumn] = useState<Gtk.ColumnViewColumn | null>(null);
34
-
35
- const captureColumn = useRef((value: Gtk.ColumnViewColumn | null) => {
36
- setColumn(value);
37
- }).current;
38
-
39
- const { store } = useRealizedSlots<Gtk.ColumnViewColumn>({
40
- target: column,
41
- binding: factoryBinding,
42
- });
43
-
44
- const registerRef = useRef(context.register);
45
- registerRef.current = context.register;
46
- const unregisterRef = useRef(context.unregister);
47
- unregisterRef.current = context.unregister;
48
-
49
- useLayoutEffect(() => {
50
- if (column === null) return;
51
- registerRef.current({ id, column, sortable: sortable ?? false });
52
- return () => unregisterRef.current(id);
53
- }, [column, id, sortable]);
54
-
55
- const headerMenuPortal = useHeaderMenu(column, headerMenu);
56
-
57
- const slotRenderer: SlotRenderer<unknown, unknown> = (value, _treeRow, isHeader) =>
58
- isHeader ? null : renderCell(value as T);
59
-
60
- const intrinsicProps: Record<string, unknown> = { id, title, ref: captureColumn };
61
- if (expand !== undefined) intrinsicProps["expand"] = expand;
62
- if (resizable !== undefined) intrinsicProps["resizable"] = resizable;
63
- if (fixedWidth !== undefined) intrinsicProps["fixedWidth"] = fixedWidth;
64
- if (visible !== undefined) intrinsicProps["visible"] = visible;
65
-
66
- return (
67
- <>
68
- {createElement(GtkColumnViewColumn, intrinsicProps)}
69
- <ListPortalHost store={store} resolver={context.resolver} render={slotRenderer} />
70
- {headerMenuPortal}
71
- </>
72
- );
73
- };
@@ -1,121 +0,0 @@
1
- import * as GObject from "@gtkx/gi/gobject";
2
- import * as Gtk from "@gtkx/gi/gtk";
3
- import type { ConstraintGuideProps, ConstraintProps, ConstraintVflProps } from "./types.js";
4
-
5
- const SUPER_ID = "super";
6
-
7
- const unknownIdMessage = (role: string, id: string): string =>
8
- `<ConstraintLayout.Constraint> references unknown id '${id}'. ` +
9
- `Set name="${id}" on the ${role} widget, or add a <ConstraintLayout.Guide id="${id}">.`;
10
-
11
- const guideRegistry = new WeakMap<Gtk.ConstraintLayout, Map<string, Gtk.ConstraintGuide>>();
12
-
13
- const guidesFor = (layout: Gtk.ConstraintLayout): Map<string, Gtk.ConstraintGuide> => {
14
- let map = guideRegistry.get(layout);
15
- if (!map) {
16
- map = new Map<string, Gtk.ConstraintGuide>();
17
- guideRegistry.set(layout, map);
18
- }
19
- return map;
20
- };
21
-
22
- const explicitWidgetName = (widget: Gtk.Widget): string | null => {
23
- const value = new GObject.Value();
24
- value.init(GObject.TYPE_STRING);
25
- widget.getProperty("name", value);
26
- const name = value.getString();
27
- return name !== null && name.length > 0 ? name : null;
28
- };
29
-
30
- function* namedChildren(layout: Gtk.ConstraintLayout): Generator<[string, Gtk.Widget]> {
31
- const host = layout.getWidget();
32
- if (!host) return;
33
- for (let child = host.getFirstChild(); child; child = child.getNextSibling()) {
34
- const name = explicitWidgetName(child);
35
- if (name !== null) yield [name, child];
36
- }
37
- }
38
-
39
- const findChildByName = (layout: Gtk.ConstraintLayout, id: string): Gtk.Widget | undefined => {
40
- for (const [name, child] of namedChildren(layout)) {
41
- if (name === id) return child;
42
- }
43
- return undefined;
44
- };
45
-
46
- const resolveConstraintTarget = (
47
- layout: Gtk.ConstraintLayout,
48
- id: string | undefined,
49
- ): Gtk.ConstraintTarget | null | undefined => {
50
- if (id === undefined || id === SUPER_ID) return null;
51
- return findChildByName(layout, id) ?? guidesFor(layout).get(id);
52
- };
53
-
54
- const constraintViews = (layout: Gtk.ConstraintLayout): Map<string, Gtk.ConstraintTarget> => {
55
- const views = new Map<string, Gtk.ConstraintTarget>();
56
- for (const [name, child] of namedChildren(layout)) views.set(name, child);
57
- for (const [name, guide] of guidesFor(layout)) {
58
- if (!views.has(name)) views.set(name, guide);
59
- }
60
- return views;
61
- };
62
-
63
- const isLayoutLive = (layout: Gtk.ConstraintLayout): boolean => layout.getWidget() !== null;
64
-
65
- export function applyConstraint(layout: Gtk.ConstraintLayout, props: ConstraintProps): () => void {
66
- const target = resolveConstraintTarget(layout, props.target);
67
- if (target === undefined) throw new Error(unknownIdMessage("target", String(props.target)));
68
- const source = resolveConstraintTarget(layout, props.source);
69
- if (source === undefined) throw new Error(unknownIdMessage("source", String(props.source)));
70
-
71
- const constraint = new Gtk.Constraint({
72
- target,
73
- targetAttribute: props.targetAttribute,
74
- relation: props.relation ?? Gtk.ConstraintRelation.EQ,
75
- source,
76
- sourceAttribute: props.sourceAttribute ?? Gtk.ConstraintAttribute.NONE,
77
- multiplier: props.multiplier ?? 1,
78
- constant: props.constant ?? 0,
79
- strength: props.strength ?? Gtk.ConstraintStrength.REQUIRED,
80
- });
81
- layout.addConstraint(constraint);
82
-
83
- return () => {
84
- if (isLayoutLive(layout)) layout.removeConstraint(constraint);
85
- };
86
- }
87
-
88
- export function applyVfl(layout: Gtk.ConstraintLayout, props: ConstraintVflProps): () => void {
89
- const views = constraintViews(layout);
90
- const constraints = layout.addConstraintsFromDescription(
91
- props.lines,
92
- props.hspacing ?? 0,
93
- props.vspacing ?? 0,
94
- views,
95
- );
96
-
97
- return () => {
98
- if (!isLayoutLive(layout)) return;
99
- for (const constraint of constraints) layout.removeConstraint(constraint);
100
- };
101
- }
102
-
103
- export function applyGuide(layout: Gtk.ConstraintLayout, props: ConstraintGuideProps): () => void {
104
- const guide = new Gtk.ConstraintGuide({
105
- name: props.id,
106
- minWidth: props.minWidth,
107
- minHeight: props.minHeight,
108
- natWidth: props.natWidth,
109
- natHeight: props.natHeight,
110
- maxWidth: props.maxWidth,
111
- maxHeight: props.maxHeight,
112
- strength: props.strength,
113
- });
114
- layout.addGuide(guide);
115
- guidesFor(layout).set(props.id, guide);
116
-
117
- return () => {
118
- guidesFor(layout).delete(props.id);
119
- if (isLayoutLive(layout)) layout.removeGuide(guide);
120
- };
121
- }
@@ -1,130 +0,0 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { GtkConstraintLayout } from "@gtkx/jsx/gtk";
3
- import { useForwardedRef } from "@gtkx/react";
4
- import {
5
- type Context,
6
- createContext,
7
- createElement,
8
- type ReactNode,
9
- type Ref,
10
- type RefObject,
11
- useCallback,
12
- useContext,
13
- useEffect,
14
- useLayoutEffect,
15
- useRef,
16
- } from "react";
17
- import { applyConstraint, applyGuide, applyVfl } from "./constraint-layout-apply.js";
18
- import type { ConstraintGuideProps, ConstraintProps, ConstraintVflProps } from "./types.js";
19
-
20
- const ORPHAN_MESSAGE = "<ConstraintLayout.Constraint> / <Guide> / <Vfl> must be a child of <ConstraintLayout>";
21
-
22
- const ConstraintLayoutContext: Context<RefObject<Gtk.ConstraintLayout | null> | null> =
23
- createContext<RefObject<Gtk.ConstraintLayout | null> | null>(null);
24
-
25
- const useConstraintLayoutRef = (): RefObject<Gtk.ConstraintLayout | null> => {
26
- const ref = useContext(ConstraintLayoutContext);
27
- if (!ref) throw new Error(ORPHAN_MESSAGE);
28
- return ref;
29
- };
30
-
31
- export type ConstraintLayoutProps = {
32
- children?: ReactNode;
33
- ref?: Ref<Gtk.ConstraintLayout | null>;
34
- };
35
-
36
- type ContributionCleanupRef = { current: (() => void) | null };
37
-
38
- const cleanupContribution = (cleanupRef: ContributionCleanupRef): void => {
39
- const cleanup = cleanupRef.current;
40
- if (!cleanup) return;
41
- cleanupRef.current = null;
42
- cleanup();
43
- };
44
-
45
- const useLayoutContribution = (apply: (layout: Gtk.ConstraintLayout) => () => void, deps: unknown[]): void => {
46
- const layoutRef = useConstraintLayoutRef();
47
- useLayoutEffect(() => {
48
- const layout = layoutRef.current;
49
- if (!layout) return;
50
- return apply(layout);
51
- }, deps);
52
- };
53
-
54
- const useDeferredContribution = (apply: (layout: Gtk.ConstraintLayout) => () => void, deps: unknown[]): void => {
55
- const layoutRef = useConstraintLayoutRef();
56
- const cleanupRef = useRef<(() => void) | null>(null);
57
-
58
- useLayoutEffect(() => () => cleanupContribution(cleanupRef), deps);
59
-
60
- useEffect(() => {
61
- const layout = layoutRef.current;
62
- if (!layout) return;
63
- const cleanup = apply(layout);
64
- cleanupRef.current = cleanup;
65
- return () => {
66
- if (cleanupRef.current === cleanup) cleanupContribution(cleanupRef);
67
- };
68
- }, deps);
69
- };
70
-
71
- export const ConstraintLayout: ((props: ConstraintLayoutProps) => ReactNode) & {
72
- Guide: (props: ConstraintGuideProps) => ReactNode;
73
- Constraint: (props: ConstraintProps) => ReactNode;
74
- Vfl: (props: ConstraintVflProps) => ReactNode;
75
- } = Object.assign(
76
- ({ children, ref }: ConstraintLayoutProps): ReactNode => {
77
- const layoutRef = useRef<Gtk.ConstraintLayout | null>(null);
78
- const captureLayout = useCallback(
79
- (layout: Gtk.ConstraintLayout | null): void => {
80
- layoutRef.current = layout;
81
- },
82
- [layoutRef],
83
- );
84
- const [, mergedRef] = useForwardedRef<Gtk.ConstraintLayout>(ref, captureLayout);
85
- return (
86
- <>
87
- {createElement(GtkConstraintLayout, { ref: mergedRef })}
88
- <ConstraintLayoutContext.Provider value={layoutRef}>{children}</ConstraintLayoutContext.Provider>
89
- </>
90
- );
91
- },
92
- {
93
- Guide: (props: ConstraintGuideProps): ReactNode => {
94
- useLayoutContribution(
95
- (layout) => applyGuide(layout, props),
96
- [
97
- props.id,
98
- props.minWidth,
99
- props.minHeight,
100
- props.natWidth,
101
- props.natHeight,
102
- props.maxWidth,
103
- props.maxHeight,
104
- props.strength,
105
- ],
106
- );
107
- return null;
108
- },
109
- Constraint: (props: ConstraintProps): ReactNode => {
110
- useDeferredContribution(
111
- (layout) => applyConstraint(layout, props),
112
- [
113
- props.target,
114
- props.targetAttribute,
115
- props.relation,
116
- props.source,
117
- props.sourceAttribute,
118
- props.multiplier,
119
- props.constant,
120
- props.strength,
121
- ],
122
- );
123
- return null;
124
- },
125
- Vfl: (props: ConstraintVflProps): ReactNode => {
126
- useDeferredContribution((layout) => applyVfl(layout, props), [props.lines, props.hspacing, props.vspacing]);
127
- return null;
128
- },
129
- },
130
- );
@@ -1,28 +0,0 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
- import { type Context, createContext, type RefObject, useContext } from "react";
3
- import type { ItemResolver } from "../utils/item-resolver.js";
4
-
5
- export interface ColumnRegistration {
6
- id: string;
7
- column: Gtk.ColumnViewColumn;
8
- sortable: boolean;
9
- }
10
-
11
- export interface ColumnViewContextValue {
12
- columnView: RefObject<Gtk.ColumnView | null>;
13
- resolver: ItemResolver<unknown, unknown>;
14
- register(registration: ColumnRegistration): void;
15
- unregister(id: string): void;
16
- }
17
-
18
- export const ColumnViewContext: Context<ColumnViewContextValue | null> = createContext<ColumnViewContextValue | null>(
19
- null,
20
- );
21
-
22
- export const useColumnViewContext = (): ColumnViewContextValue => {
23
- const context = useContext(ColumnViewContext);
24
- if (context === null) {
25
- throw new Error("<ColumnViewColumn> must be rendered inside a <ColumnView>.");
26
- }
27
- return context;
28
- };
@@ -1,53 +0,0 @@
1
- import type * as GObject from "@gtkx/gi/gobject";
2
- import { useSignal } from "@gtkx/react";
3
- import { type GObjectTarget, resolveGobjectTarget, useGObjectSnapshot } from "@gtkx/react/internal";
4
- import { useLayoutEffect, useRef } from "react";
5
- import type { ItemResolver } from "../utils/item-resolver.js";
6
-
7
- export interface DropDownSelectionTarget extends GObject.Object {
8
- getSelected(): number;
9
- setSelected(position: number): void;
10
- }
11
-
12
- export interface DropDownSelectionOptions<T, S> {
13
- widget: GObjectTarget<DropDownSelectionTarget>;
14
- resolver: ItemResolver<T, S>;
15
- selectedId: string | null | undefined;
16
- onSelectionChanged: ((id: string) => void) | null | undefined;
17
- }
18
-
19
- const GTK_INVALID_LIST_POSITION = 4294967295;
20
-
21
- const normalizeSelected = (position: number): number =>
22
- position === GTK_INVALID_LIST_POSITION || position < 0 ? -1 : position;
23
-
24
- export const useDropDownSelection = <T, S>(options: DropDownSelectionOptions<T, S>): number => {
25
- const { widget, resolver, selectedId, onSelectionChanged } = options;
26
- const resolved = resolveGobjectTarget(widget);
27
-
28
- const selectedPosition = useGObjectSnapshot(widget, "notify::selected", (target) =>
29
- target === null ? -1 : normalizeSelected(target.getSelected()),
30
- );
31
-
32
- const resolverRef = useRef(resolver);
33
- resolverRef.current = resolver;
34
- const onSelectionChangedRef = useRef(onSelectionChanged);
35
- onSelectionChangedRef.current = onSelectionChanged;
36
-
37
- useLayoutEffect(() => {
38
- if (resolved === null || selectedId === undefined || selectedId === null) return;
39
- const position = resolverRef.current.positionOf(selectedId);
40
- if (position < 0) return;
41
- if (normalizeSelected(resolved.getSelected()) !== position) resolved.setSelected(position);
42
- }, [resolved, selectedId, resolver]);
43
-
44
- useSignal(widget, "notify::selected", () => {
45
- if (resolved === null) return;
46
- const position = normalizeSelected(resolved.getSelected());
47
- if (position < 0) return;
48
- const id = resolverRef.current.idOf(position);
49
- if (id !== undefined) onSelectionChangedRef.current?.(id);
50
- });
51
-
52
- return selectedPosition;
53
- };
@@ -1,33 +0,0 @@
1
- import * as Gio from "@gtkx/gi/gio";
2
- import * as GObject from "@gtkx/gi/gobject";
3
- import type * as Gtk from "@gtkx/gi/gtk";
4
- import { createPortal } from "@gtkx/react";
5
- import { stateOf } from "@gtkx/react/internal";
6
- import { type ReactNode, useLayoutEffect, useRef } from "react";
7
-
8
- const findMenuModel = (container: GObject.Object): Gio.MenuModel | null => {
9
- for (const child of stateOf(container).children) {
10
- if (child instanceof Gio.MenuModel) return child;
11
- }
12
- return null;
13
- };
14
-
15
- export const useHeaderMenu = (column: Gtk.ColumnViewColumn | null, headerMenu: ReactNode): ReactNode => {
16
- const containerRef = useRef<GObject.Object | null>(null);
17
- if (containerRef.current === null) {
18
- containerRef.current = new GObject.Object();
19
- }
20
- const container = containerRef.current;
21
-
22
- useLayoutEffect(() => {
23
- if (column === null) return;
24
- if (headerMenu === undefined || headerMenu === null) {
25
- column.setHeaderMenu(null);
26
- return;
27
- }
28
- column.setHeaderMenu(findMenuModel(container));
29
- });
30
-
31
- if (headerMenu === undefined || headerMenu === null) return null;
32
- return createPortal(headerMenu, container, "header-menu");
33
- };