@gtkx/components 0.21.0 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +173 -0
- package/dist/cell.d.ts +30 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +45 -0
- package/dist/cell.js.map +1 -0
- package/dist/column-view-column.d.ts +24 -14
- package/dist/column-view-column.d.ts.map +1 -1
- package/dist/column-view-column.js +19 -40
- package/dist/column-view-column.js.map +1 -1
- package/dist/column-view.d.ts +25 -11
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +53 -83
- package/dist/column-view.js.map +1 -1
- package/dist/constraint-layout-apply.d.ts +50 -1
- package/dist/constraint-layout-apply.d.ts.map +1 -1
- package/dist/constraint-layout-apply.js.map +1 -1
- package/dist/constraint-layout.d.ts +8 -1
- package/dist/constraint-layout.d.ts.map +1 -1
- package/dist/constraint-layout.js +12 -9
- package/dist/constraint-layout.js.map +1 -1
- package/dist/drop-down.d.ts +26 -36
- package/dist/drop-down.d.ts.map +1 -1
- package/dist/drop-down.js +47 -87
- package/dist/drop-down.js.map +1 -1
- package/dist/fixed.d.ts +26 -0
- package/dist/fixed.d.ts.map +1 -0
- package/dist/fixed.js +40 -0
- package/dist/fixed.js.map +1 -0
- package/dist/grid-view.d.ts +13 -10
- package/dist/grid-view.d.ts.map +1 -1
- package/dist/grid-view.js +30 -16
- package/dist/grid-view.js.map +1 -1
- package/dist/grid.d.ts +27 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +48 -0
- package/dist/grid.js.map +1 -0
- package/dist/hooks/use-cell-containers.d.ts +18 -0
- package/dist/hooks/use-cell-containers.d.ts.map +1 -0
- package/dist/hooks/{use-realized-slots.js → use-cell-containers.js} +18 -17
- package/dist/hooks/use-cell-containers.js.map +1 -0
- package/dist/hooks/use-collection-model.d.ts +26 -0
- package/dist/hooks/use-collection-model.d.ts.map +1 -0
- package/dist/hooks/use-collection-model.js +35 -0
- package/dist/hooks/use-collection-model.js.map +1 -0
- package/dist/hooks/use-collection-widget.d.ts +28 -0
- package/dist/hooks/use-collection-widget.d.ts.map +1 -0
- package/dist/hooks/use-collection-widget.js +21 -0
- package/dist/hooks/use-collection-widget.js.map +1 -0
- package/dist/hooks/use-drop-down-selection.d.ts +7 -6
- package/dist/hooks/use-drop-down-selection.d.ts.map +1 -1
- package/dist/hooks/use-drop-down-selection.js +6 -6
- package/dist/hooks/use-drop-down-selection.js.map +1 -1
- package/dist/hooks/use-expansion-model.d.ts +12 -0
- package/dist/hooks/use-expansion-model.d.ts.map +1 -0
- package/dist/hooks/use-expansion-model.js +71 -0
- package/dist/hooks/use-expansion-model.js.map +1 -0
- package/dist/hooks/use-header-menu.d.ts.map +1 -1
- package/dist/hooks/use-header-menu.js +12 -24
- package/dist/hooks/use-header-menu.js.map +1 -1
- package/dist/hooks/use-installed-model.d.ts +4 -0
- package/dist/hooks/use-installed-model.d.ts.map +1 -0
- package/dist/hooks/use-installed-model.js +15 -0
- package/dist/hooks/use-installed-model.js.map +1 -0
- package/dist/hooks/use-list-model.d.ts +14 -15
- package/dist/hooks/use-list-model.d.ts.map +1 -1
- package/dist/hooks/use-list-model.js +61 -34
- package/dist/hooks/use-list-model.js.map +1 -1
- package/dist/hooks/use-placed-child.d.ts +18 -0
- package/dist/hooks/use-placed-child.d.ts.map +1 -0
- package/dist/hooks/use-placed-child.js +47 -0
- package/dist/hooks/use-placed-child.js.map +1 -0
- package/dist/hooks/use-selection-model.d.ts +4 -3
- package/dist/hooks/use-selection-model.d.ts.map +1 -1
- package/dist/hooks/use-selection-model.js +8 -16
- package/dist/hooks/use-selection-model.js.map +1 -1
- package/dist/hooks/use-sort-handler.d.ts +10 -5
- package/dist/hooks/use-sort-handler.d.ts.map +1 -1
- package/dist/hooks/use-sort-handler.js +2 -2
- package/dist/hooks/use-sort-handler.js.map +1 -1
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -13
- package/dist/index.js.map +1 -1
- package/dist/list-view.d.ts +17 -9
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +49 -24
- package/dist/list-view.js.map +1 -1
- package/dist/menu-items.d.ts.map +1 -1
- package/dist/menu-items.js.map +1 -1
- package/dist/menu.d.ts +6 -10
- package/dist/menu.d.ts.map +1 -1
- package/dist/menu.js +7 -9
- package/dist/menu.js.map +1 -1
- package/dist/overlay.d.ts +24 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +49 -0
- package/dist/overlay.js.map +1 -0
- package/dist/size-group.d.ts +20 -0
- package/dist/size-group.d.ts.map +1 -0
- package/dist/size-group.js +57 -0
- package/dist/size-group.js.map +1 -0
- package/dist/types.d.ts +43 -173
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/{realized-slot-store.d.ts → cell-container-store.d.ts} +7 -9
- package/dist/utils/cell-container-store.d.ts.map +1 -0
- package/dist/utils/{realized-slot-store.js → cell-container-store.js} +9 -14
- package/dist/utils/cell-container-store.js.map +1 -0
- package/dist/utils/item-models.d.ts +10 -0
- package/dist/utils/item-models.d.ts.map +1 -0
- package/dist/utils/{position-only-model.js → item-models.js} +13 -14
- package/dist/utils/item-models.js.map +1 -0
- package/dist/utils/item-resolver.d.ts +14 -17
- package/dist/utils/item-resolver.d.ts.map +1 -1
- package/dist/utils/item-resolver.js +34 -43
- package/dist/utils/item-resolver.js.map +1 -1
- package/dist/utils/list-item-flatten.d.ts +16 -24
- package/dist/utils/list-item-flatten.d.ts.map +1 -1
- package/dist/utils/list-item-flatten.js +16 -36
- package/dist/utils/list-item-flatten.js.map +1 -1
- package/package.json +52 -53
- package/src/cell.tsx +144 -0
- package/src/column-view-column.tsx +57 -48
- package/src/column-view.tsx +130 -146
- package/src/constraint-layout-apply.ts +53 -1
- package/src/constraint-layout.tsx +18 -13
- package/src/drop-down.tsx +129 -196
- package/src/fixed.tsx +86 -0
- package/src/grid-view.tsx +52 -44
- package/src/grid.tsx +88 -0
- package/src/hooks/{use-realized-slots.ts → use-cell-containers.ts} +30 -30
- package/src/hooks/use-collection-model.ts +60 -0
- package/src/hooks/use-collection-widget.ts +47 -0
- package/src/hooks/use-drop-down-selection.ts +12 -13
- package/src/hooks/use-expansion-model.ts +82 -0
- package/src/hooks/use-header-menu.ts +24 -23
- package/src/hooks/use-installed-model.ts +20 -0
- package/src/hooks/use-list-model.ts +103 -87
- package/src/hooks/use-placed-child.ts +79 -0
- package/src/hooks/use-selection-model.ts +11 -18
- package/src/hooks/use-sort-handler.ts +13 -8
- package/src/index.ts +25 -27
- package/src/list-view.tsx +120 -57
- package/src/menu.tsx +12 -14
- package/src/overlay.tsx +88 -0
- package/src/size-group.tsx +96 -0
- package/src/types.ts +55 -206
- package/src/utils/{realized-slot-store.ts → cell-container-store.ts} +13 -25
- package/src/utils/{position-only-model.ts → item-models.ts} +26 -28
- package/src/utils/item-resolver.ts +54 -59
- package/src/utils/list-item-flatten.ts +33 -56
- package/dist/collection-view.d.ts +0 -30
- package/dist/collection-view.d.ts.map +0 -1
- package/dist/collection-view.js +0 -52
- package/dist/collection-view.js.map +0 -1
- package/dist/contexts/column-view-context.d.ts +0 -17
- package/dist/contexts/column-view-context.d.ts.map +0 -1
- package/dist/contexts/column-view-context.js +0 -10
- package/dist/contexts/column-view-context.js.map +0 -1
- package/dist/hooks/use-model-installation.d.ts +0 -9
- package/dist/hooks/use-model-installation.d.ts.map +0 -1
- package/dist/hooks/use-model-installation.js +0 -20
- package/dist/hooks/use-model-installation.js.map +0 -1
- package/dist/hooks/use-realized-slots.d.ts +0 -20
- package/dist/hooks/use-realized-slots.d.ts.map +0 -1
- package/dist/hooks/use-realized-slots.js.map +0 -1
- package/dist/list-portal-host.d.ts +0 -11
- package/dist/list-portal-host.d.ts.map +0 -1
- package/dist/list-portal-host.js +0 -8
- package/dist/list-portal-host.js.map +0 -1
- package/dist/list-slot.d.ts +0 -14
- package/dist/list-slot.d.ts.map +0 -1
- package/dist/list-slot.js +0 -29
- package/dist/list-slot.js.map +0 -1
- package/dist/utils/position-only-model.d.ts +0 -10
- package/dist/utils/position-only-model.d.ts.map +0 -1
- package/dist/utils/position-only-model.js.map +0 -1
- package/dist/utils/realized-slot-store.d.ts.map +0 -1
- package/dist/utils/realized-slot-store.js.map +0 -1
- package/src/collection-view.tsx +0 -125
- package/src/contexts/column-view-context.ts +0 -28
- package/src/hooks/use-model-installation.ts +0 -25
- package/src/list-portal-host.tsx +0 -27
- package/src/list-slot.tsx +0 -56
package/src/list-view.tsx
CHANGED
|
@@ -1,85 +1,148 @@
|
|
|
1
|
-
import type * as Gio from "@gtkx/gi/gio";
|
|
2
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
3
2
|
import { GtkListView, type GtkListViewProps } from "@gtkx/jsx/gtk";
|
|
4
3
|
import type { ReactNode, Ref } from "react";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
type CellRenderer,
|
|
6
|
+
CellRenderHost,
|
|
7
|
+
createTreeRenderContext,
|
|
8
|
+
HeaderRenderHost,
|
|
9
|
+
itemRenderer,
|
|
10
|
+
type TreeRenderContext,
|
|
11
|
+
} from "./cell.js";
|
|
12
|
+
import { makeFactoryInstaller, useCellContainers } from "./hooks/use-cell-containers.js";
|
|
13
|
+
import { useCollectionWidget } from "./hooks/use-collection-widget.js";
|
|
14
|
+
import type {
|
|
15
|
+
CollectionItemSizeProps,
|
|
16
|
+
ControlledExpansionProps,
|
|
17
|
+
ControlledSelectionProps,
|
|
18
|
+
ItemNode,
|
|
19
|
+
RenderItemProps,
|
|
20
|
+
SectionNode,
|
|
21
|
+
} from "./types.js";
|
|
22
|
+
import type { CellContainerStore } from "./utils/cell-container-store.js";
|
|
23
|
+
import type { ItemResolver } from "./utils/item-resolver.js";
|
|
8
24
|
|
|
9
|
-
const
|
|
10
|
-
install: (widget: Gtk.ListView, factory: Gtk.SignalListItemFactory) => widget.setFactory(factory),
|
|
11
|
-
uninstall: (widget: Gtk.ListView) => widget.setFactory(null),
|
|
12
|
-
};
|
|
25
|
+
const factoryInstaller = makeFactoryInstaller<Gtk.ListView>((widget, factory) => widget.setFactory(factory));
|
|
13
26
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
27
|
+
const headerFactoryInstaller = makeFactoryInstaller<Gtk.ListView>((widget, factory) =>
|
|
28
|
+
widget.setHeaderFactory(factory),
|
|
29
|
+
);
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
export type ListViewDeclarativeProps<T = unknown, S = unknown> = CollectionItemSizeProps &
|
|
32
|
+
ControlledSelectionProps &
|
|
33
|
+
ControlledExpansionProps & {
|
|
34
|
+
items?: ItemNode<T>[] | undefined;
|
|
35
|
+
sections?: SectionNode<S, T>[] | undefined;
|
|
36
|
+
renderItem: (props: RenderItemProps<T>) => ReactNode;
|
|
37
|
+
renderHeader?: ((info: { section: S }) => ReactNode) | null | undefined;
|
|
38
|
+
};
|
|
22
39
|
|
|
23
40
|
/**
|
|
24
|
-
* Props for
|
|
25
|
-
*
|
|
26
|
-
*
|
|
41
|
+
* Props for {@link ListView}. Combines the underlying Gtk.ListView props with
|
|
42
|
+
* declarative collection props: flat items or grouped sections, a per-row renderItem,
|
|
43
|
+
* an optional section header renderer, controlled selection and expansion, and
|
|
44
|
+
* estimated item sizing.
|
|
27
45
|
*/
|
|
28
|
-
export type
|
|
29
|
-
|
|
46
|
+
export type ListViewProps<T = unknown, S = unknown> = Omit<
|
|
47
|
+
GtkListViewProps,
|
|
48
|
+
"model" | "factory" | "headerFactory" | keyof ListViewDeclarativeProps<T, S>
|
|
49
|
+
> &
|
|
50
|
+
ListViewDeclarativeProps<T, S>;
|
|
51
|
+
|
|
52
|
+
interface ListViewWiring<T, S> {
|
|
53
|
+
setRef: (value: Gtk.ListView | null) => void;
|
|
54
|
+
resolver: ItemResolver<T, S>;
|
|
55
|
+
headerResolver: ItemResolver<T, S>;
|
|
56
|
+
itemStore: CellContainerStore;
|
|
57
|
+
headerStore: CellContainerStore;
|
|
58
|
+
useHeader: boolean;
|
|
59
|
+
rowId: (row: Gtk.TreeListRow) => string | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const useListViewWiring = <T, S>(props: NormalizedListViewProps<T, S>): ListViewWiring<T, S> => {
|
|
63
|
+
const { widgetRef, setRef, collection } = useCollectionWidget<Gtk.ListView, T, S>(props);
|
|
64
|
+
|
|
65
|
+
const useHeader = collection.useHeader;
|
|
66
|
+
|
|
67
|
+
const itemStore = useCellContainers<Gtk.ListView>({
|
|
68
|
+
object: widgetRef,
|
|
69
|
+
installer: factoryInstaller,
|
|
70
|
+
estimatedHeight: props.estimatedItemHeight,
|
|
71
|
+
estimatedWidth: props.estimatedItemWidth,
|
|
72
|
+
});
|
|
73
|
+
const headerStore = useCellContainers<Gtk.ListView>({
|
|
74
|
+
object: useHeader ? widgetRef : null,
|
|
75
|
+
installer: headerFactoryInstaller,
|
|
76
|
+
estimatedHeight: props.estimatedItemHeight,
|
|
77
|
+
estimatedWidth: props.estimatedItemWidth,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
setRef,
|
|
82
|
+
resolver: collection.resolver,
|
|
83
|
+
headerResolver: collection.headerResolver,
|
|
84
|
+
itemStore,
|
|
85
|
+
headerStore,
|
|
86
|
+
useHeader,
|
|
87
|
+
rowId: collection.rowId,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type NormalizedListViewProps<T, S> = ListViewDeclarativeProps<T, S> & {
|
|
92
|
+
ref?: Ref<Gtk.ListView | null>;
|
|
93
|
+
estimatedItemHeight?: number;
|
|
94
|
+
estimatedItemWidth?: number;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
};
|
|
30
97
|
|
|
31
98
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Supplying an external `model` switches to the uncontrolled form.
|
|
99
|
+
* Renders a Gtk.ListView: a scrollable, single-column list backed by a collection
|
|
100
|
+
* model, with each row drawn by renderItem and optional section headers.
|
|
35
101
|
*/
|
|
36
|
-
export const ListView = <T = unknown, S = unknown>(props:
|
|
102
|
+
export const ListView = <T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNode => {
|
|
37
103
|
const {
|
|
38
104
|
ref,
|
|
39
105
|
items,
|
|
40
|
-
|
|
106
|
+
sections,
|
|
41
107
|
renderItem,
|
|
42
|
-
autoexpand,
|
|
43
108
|
renderHeader,
|
|
44
|
-
|
|
109
|
+
selectedIds,
|
|
45
110
|
selectionMode,
|
|
46
111
|
onSelectionChanged,
|
|
112
|
+
expandedIds,
|
|
113
|
+
onExpandedChange,
|
|
47
114
|
estimatedItemHeight,
|
|
48
115
|
estimatedItemWidth,
|
|
49
116
|
...intrinsicProps
|
|
50
|
-
} = props as
|
|
51
|
-
ref?: Ref<Gtk.ListView | null>;
|
|
52
|
-
estimatedItemHeight?: number;
|
|
53
|
-
estimatedItemWidth?: number;
|
|
54
|
-
[key: string]: unknown;
|
|
55
|
-
};
|
|
117
|
+
} = props as NormalizedListViewProps<T, S>;
|
|
56
118
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
119
|
+
const wiring = useListViewWiring<T, S>({
|
|
120
|
+
ref,
|
|
121
|
+
items,
|
|
122
|
+
sections,
|
|
123
|
+
renderHeader,
|
|
124
|
+
selectedIds,
|
|
125
|
+
selectionMode,
|
|
126
|
+
onSelectionChanged,
|
|
127
|
+
expandedIds,
|
|
128
|
+
onExpandedChange,
|
|
129
|
+
estimatedItemHeight,
|
|
130
|
+
estimatedItemWidth,
|
|
131
|
+
} as NormalizedListViewProps<T, S>);
|
|
62
132
|
|
|
63
|
-
const
|
|
133
|
+
const treeContext: TreeRenderContext = createTreeRenderContext(expandedIds, wiring.rowId);
|
|
134
|
+
const cellRenderer: CellRenderer<T, S> = itemRenderer<T, S>(renderItem, treeContext);
|
|
64
135
|
|
|
65
136
|
return (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
estimatedWidth={estimatedItemWidth}
|
|
77
|
-
selected={selected}
|
|
78
|
-
selectionMode={selectionMode}
|
|
79
|
-
onSelectionChanged={onSelectionChanged}
|
|
80
|
-
factoryBinding={factoryBinding}
|
|
81
|
-
headerFactoryBinding={useHeader ? headerFactoryBinding : undefined}
|
|
82
|
-
modelBinding={modelBinding}
|
|
83
|
-
/>
|
|
137
|
+
<>
|
|
138
|
+
<GtkListView {...intrinsicProps} ref={wiring.setRef} />
|
|
139
|
+
<CellRenderHost store={wiring.itemStore} resolver={wiring.resolver} render={cellRenderer} />
|
|
140
|
+
<HeaderRenderHost
|
|
141
|
+
useHeader={wiring.useHeader}
|
|
142
|
+
store={wiring.headerStore}
|
|
143
|
+
resolver={wiring.headerResolver}
|
|
144
|
+
renderHeader={renderHeader}
|
|
145
|
+
/>
|
|
146
|
+
</>
|
|
84
147
|
);
|
|
85
148
|
};
|
package/src/menu.tsx
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import type * as Gio from "@gtkx/gi/gio";
|
|
2
2
|
import { GMenu, type GMenuProps } from "@gtkx/jsx/gio";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useMergeRefs } from "@gtkx/react/internal";
|
|
4
|
+
import { type ReactNode, type Ref, useLayoutEffect, useRef } from "react";
|
|
5
5
|
import { applyMenuItems, menuItemsEqual } from "./menu-items.js";
|
|
6
|
-
import type { MenuEntry
|
|
6
|
+
import type { MenuEntry } from "./types.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export type MenuItemsProps = {
|
|
9
|
+
items?: MenuEntry[] | null | undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Props for {@link Menu}, combining Gio.Menu props with a declarative items array. */
|
|
12
13
|
export type MenuProps = Omit<GMenuProps, keyof MenuItemsProps> & MenuItemsProps & { ref?: Ref<Gio.Menu | null> };
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* A declarative `Gio.Menu` built from an `items` tree of {@link MenuEntry}
|
|
16
|
-
* values. Rebuilds the underlying menu model whenever the item structure
|
|
17
|
-
* changes.
|
|
18
|
-
*/
|
|
15
|
+
/** Builds a Gio.Menu model from a declarative array of {@link MenuEntry} items. */
|
|
19
16
|
export const Menu = (props: MenuProps): ReactNode => {
|
|
20
17
|
const { items, ref, ...rest } = props;
|
|
21
|
-
const
|
|
18
|
+
const menuRef = useRef<Gio.Menu | null>(null);
|
|
19
|
+
const mergedRef = useMergeRefs(ref, menuRef);
|
|
22
20
|
const appliedRef = useRef<MenuEntry[] | null>(null);
|
|
23
21
|
|
|
24
22
|
useLayoutEffect(() => {
|
|
@@ -30,5 +28,5 @@ export const Menu = (props: MenuProps): ReactNode => {
|
|
|
30
28
|
applyMenuItems(menu, entries);
|
|
31
29
|
});
|
|
32
30
|
|
|
33
|
-
return
|
|
31
|
+
return <GMenu {...rest} ref={mergedRef} />;
|
|
34
32
|
};
|
package/src/overlay.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { GtkOverlay, type GtkOverlayProps } from "@gtkx/jsx/gtk";
|
|
3
|
+
import { useMergeRefs } from "@gtkx/react/internal";
|
|
4
|
+
import { Children, type ElementType, isValidElement, type ReactNode, type Ref, useRef } from "react";
|
|
5
|
+
import { createParentContext, usePlacedChild } from "./hooks/use-placed-child.js";
|
|
6
|
+
import type { ChildProps } from "./types.js";
|
|
7
|
+
|
|
8
|
+
const { Context: OverlayContext, useParentRef: useOverlayRef } = createParentContext<Gtk.Overlay>(
|
|
9
|
+
"<Overlay.Child> must be a child of <Overlay>",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/** Props for {@link Overlay}. */
|
|
13
|
+
export type OverlayProps = GtkOverlayProps & { ref?: Ref<Gtk.Overlay | null> };
|
|
14
|
+
|
|
15
|
+
export type OverlayPlacementProps = {
|
|
16
|
+
/** Whether this overlay contributes to the Overlay's measured size. */
|
|
17
|
+
measure?: boolean | null | undefined;
|
|
18
|
+
/** Whether the overlay is clipped to the main child's allocation. */
|
|
19
|
+
clipOverlay?: boolean | null | undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Adds a single widget as an overlay on top of an {@link Overlay}'s main child. */
|
|
23
|
+
export type OverlayChildProps<C extends ElementType> = ChildProps<C, OverlayPlacementProps>;
|
|
24
|
+
|
|
25
|
+
type OverlayPlacement = { measure: boolean; clipOverlay: boolean };
|
|
26
|
+
|
|
27
|
+
const placementOf = (props: OverlayPlacementProps): OverlayPlacement => ({
|
|
28
|
+
measure: props.measure ?? false,
|
|
29
|
+
clipOverlay: props.clipOverlay ?? false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const samePlacement = (a: OverlayPlacement, b: OverlayPlacement): boolean =>
|
|
33
|
+
a.measure === b.measure && a.clipOverlay === b.clipOverlay;
|
|
34
|
+
|
|
35
|
+
const OverlayChild = <C extends ElementType>({
|
|
36
|
+
component,
|
|
37
|
+
measure,
|
|
38
|
+
clipOverlay,
|
|
39
|
+
ref,
|
|
40
|
+
...rest
|
|
41
|
+
}: OverlayChildProps<C>): ReactNode => {
|
|
42
|
+
const overlayRef = useOverlayRef();
|
|
43
|
+
const Component: ElementType = component;
|
|
44
|
+
return usePlacedChild<Gtk.Widget, OverlayPlacement>({
|
|
45
|
+
render: (placeRef) => <Component {...rest} ref={placeRef} />,
|
|
46
|
+
ref,
|
|
47
|
+
placement: placementOf({ measure, clipOverlay }),
|
|
48
|
+
samePlacement,
|
|
49
|
+
place: (widget, placement) => {
|
|
50
|
+
const overlay = overlayRef.current;
|
|
51
|
+
if (!overlay) return;
|
|
52
|
+
if (widget.getParent() !== overlay) overlay.addOverlay(widget);
|
|
53
|
+
overlay.setMeasureOverlay(widget, placement.measure);
|
|
54
|
+
overlay.setClipOverlay(widget, placement.clipOverlay);
|
|
55
|
+
},
|
|
56
|
+
release: (widget) => {
|
|
57
|
+
const overlay = overlayRef.current;
|
|
58
|
+
if (overlay && widget.getParent() === overlay) overlay.removeOverlay(widget);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isOverlayChild = (node: ReactNode): boolean => isValidElement(node) && node.type === OverlayChild;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Renders a Gtk.Overlay: a main child with one or more widgets stacked on top,
|
|
67
|
+
* declared via {@link Overlay.Child}. Non-Child children form the main content.
|
|
68
|
+
*/
|
|
69
|
+
export const Overlay: ((props: OverlayProps) => ReactNode) & {
|
|
70
|
+
Child: <C extends ElementType>(props: OverlayChildProps<C>) => ReactNode;
|
|
71
|
+
} = Object.assign(
|
|
72
|
+
({ children, ref, ...rest }: OverlayProps): ReactNode => {
|
|
73
|
+
const overlayRef = useRef<Gtk.Overlay | null>(null);
|
|
74
|
+
const mergedRef = useMergeRefs<Gtk.Overlay>(ref, overlayRef);
|
|
75
|
+
const items = Children.toArray(children);
|
|
76
|
+
const overlays = items.filter(isOverlayChild);
|
|
77
|
+
const base = items.filter((node) => !isOverlayChild(node));
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<GtkOverlay {...rest} ref={mergedRef}>
|
|
81
|
+
{base}
|
|
82
|
+
</GtkOverlay>
|
|
83
|
+
<OverlayContext.Provider value={overlayRef}>{overlays}</OverlayContext.Provider>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
{ Child: OverlayChild },
|
|
88
|
+
);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { GtkSizeGroup } from "@gtkx/jsx/gtk";
|
|
3
|
+
import { useMergeRefs } from "@gtkx/react/internal";
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
type ElementType,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
type Ref,
|
|
9
|
+
type RefCallback,
|
|
10
|
+
useCallback,
|
|
11
|
+
useContext,
|
|
12
|
+
useRef,
|
|
13
|
+
} from "react";
|
|
14
|
+
import type { ChildProps } from "./types.js";
|
|
15
|
+
|
|
16
|
+
type SizeGroupRegistry = {
|
|
17
|
+
addWidget: (widget: Gtk.Widget) => void;
|
|
18
|
+
removeWidget: (widget: Gtk.Widget) => void;
|
|
19
|
+
setGroup: (group: Gtk.SizeGroup | null) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const SizeGroupContext = createContext<SizeGroupRegistry | null>(null);
|
|
23
|
+
|
|
24
|
+
const useSizeGroupRegistry = (): SizeGroupRegistry => {
|
|
25
|
+
const registry = useContext(SizeGroupContext);
|
|
26
|
+
if (!registry) throw new Error("<SizeGroup.Child> must be a child of <SizeGroup>");
|
|
27
|
+
return registry;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Props for {@link SizeGroup}. */
|
|
31
|
+
export type SizeGroupProps = {
|
|
32
|
+
/** How the group equalizes sizes: horizontal, vertical, or both. */
|
|
33
|
+
mode?: Gtk.SizeGroupMode | null | undefined;
|
|
34
|
+
ref?: Ref<Gtk.SizeGroup | null>;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Adds a single widget, rendered by the given component, to the enclosing {@link SizeGroup}. */
|
|
39
|
+
export type SizeGroupChildProps<C extends ElementType> = ChildProps<C>;
|
|
40
|
+
|
|
41
|
+
const SizeGroupChild = <C extends ElementType>({ component, ref, ...rest }: SizeGroupChildProps<C>): ReactNode => {
|
|
42
|
+
const registry = useSizeGroupRegistry();
|
|
43
|
+
const joinGroup = useCallback<RefCallback<Gtk.Widget>>(
|
|
44
|
+
(widget) => {
|
|
45
|
+
if (widget === null) return;
|
|
46
|
+
registry.addWidget(widget);
|
|
47
|
+
return () => registry.removeWidget(widget);
|
|
48
|
+
},
|
|
49
|
+
[registry],
|
|
50
|
+
);
|
|
51
|
+
const Component: ElementType = component;
|
|
52
|
+
const setWidget = useMergeRefs<Gtk.Widget>(ref, joinGroup);
|
|
53
|
+
return <Component {...rest} ref={setWidget} />;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const createRegistry = (): SizeGroupRegistry => {
|
|
57
|
+
const members = new Set<Gtk.Widget>();
|
|
58
|
+
let group: Gtk.SizeGroup | null = null;
|
|
59
|
+
return {
|
|
60
|
+
addWidget: (widget) => {
|
|
61
|
+
members.add(widget);
|
|
62
|
+
group?.addWidget(widget);
|
|
63
|
+
},
|
|
64
|
+
removeWidget: (widget) => {
|
|
65
|
+
members.delete(widget);
|
|
66
|
+
group?.removeWidget(widget);
|
|
67
|
+
},
|
|
68
|
+
setGroup: (next) => {
|
|
69
|
+
group = next;
|
|
70
|
+
if (next) for (const member of members) next.addWidget(member);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a Gtk.SizeGroup that keeps its member widgets at a common size. Members join the group
|
|
77
|
+
* through {@link SizeGroup.Child}.
|
|
78
|
+
*/
|
|
79
|
+
export const SizeGroup: ((props: SizeGroupProps) => ReactNode) & {
|
|
80
|
+
Child: <C extends ElementType>(props: SizeGroupChildProps<C>) => ReactNode;
|
|
81
|
+
} = Object.assign(
|
|
82
|
+
({ mode, ref, children }: SizeGroupProps): ReactNode => {
|
|
83
|
+
const registryRef = useRef<SizeGroupRegistry | null>(null);
|
|
84
|
+
if (registryRef.current === null) registryRef.current = createRegistry();
|
|
85
|
+
const registry = registryRef.current;
|
|
86
|
+
const setGroup = useCallback<RefCallback<Gtk.SizeGroup>>((group) => registry.setGroup(group), [registry]);
|
|
87
|
+
const mergedRef = useMergeRefs<Gtk.SizeGroup>(ref, setGroup);
|
|
88
|
+
return (
|
|
89
|
+
<>
|
|
90
|
+
<GtkSizeGroup ref={mergedRef} mode={mode} />
|
|
91
|
+
<SizeGroupContext.Provider value={registry}>{children}</SizeGroupContext.Provider>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
{ Child: SizeGroupChild },
|
|
96
|
+
);
|