@gtkx/components 1.0.0-rc.1 → 1.0.0-rc.3
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/dist/adw/combo-row.d.ts +11 -0
- package/dist/adw/combo-row.d.ts.map +1 -0
- package/dist/adw/combo-row.js +14 -0
- package/dist/adw/combo-row.js.map +1 -0
- package/dist/adw/index.d.ts +4 -0
- package/dist/adw/index.d.ts.map +1 -0
- package/dist/adw/index.js +3 -0
- package/dist/adw/index.js.map +1 -0
- package/dist/adw/toast.d.ts +10 -0
- package/dist/adw/toast.d.ts.map +1 -0
- package/dist/adw/toast.js +51 -0
- package/dist/adw/toast.js.map +1 -0
- package/dist/adw/types.d.ts +35 -0
- package/dist/adw/types.d.ts.map +1 -0
- package/dist/adw/types.js +2 -0
- package/dist/adw/types.js.map +1 -0
- package/dist/column-view.d.ts +7 -29
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +98 -70
- package/dist/column-view.js.map +1 -1
- package/dist/drop-down.d.ts +7 -28
- package/dist/drop-down.d.ts.map +1 -1
- package/dist/drop-down.js +10 -86
- package/dist/drop-down.js.map +1 -1
- package/dist/grid-view.d.ts +6 -16
- package/dist/grid-view.d.ts.map +1 -1
- package/dist/grid-view.js +25 -33
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +6 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +50 -0
- package/dist/internal/cells.d.ts.map +1 -0
- package/dist/internal/cells.js +185 -0
- package/dist/internal/cells.js.map +1 -0
- package/dist/internal/collection-index.d.ts +21 -0
- package/dist/internal/collection-index.d.ts.map +1 -0
- package/dist/internal/collection-index.js +106 -0
- package/dist/internal/collection-index.js.map +1 -0
- package/dist/internal/collection-model.d.ts +12 -0
- package/dist/internal/collection-model.d.ts.map +1 -0
- package/dist/internal/collection-model.js +164 -0
- package/dist/internal/collection-model.js.map +1 -0
- package/dist/internal/collection.d.ts +18 -0
- package/dist/internal/collection.d.ts.map +1 -0
- package/dist/internal/collection.js +64 -0
- package/dist/internal/collection.js.map +1 -0
- package/dist/internal/drop-down.d.ts +16 -0
- package/dist/internal/drop-down.d.ts.map +1 -0
- package/dist/internal/drop-down.js +110 -0
- package/dist/internal/drop-down.js.map +1 -0
- package/dist/internal/expansion.d.ts +9 -0
- package/dist/internal/expansion.d.ts.map +1 -0
- package/dist/internal/expansion.js +74 -0
- package/dist/internal/expansion.js.map +1 -0
- package/dist/internal/selection.d.ts +13 -0
- package/dist/internal/selection.d.ts.map +1 -0
- package/dist/internal/selection.js +100 -0
- package/dist/internal/selection.js.map +1 -0
- package/dist/internal/use-collection.d.ts +17 -0
- package/dist/internal/use-collection.d.ts.map +1 -0
- package/dist/internal/use-collection.js +34 -0
- package/dist/internal/use-collection.js.map +1 -0
- package/dist/internal/use-widget-ref.d.ts +4 -0
- package/dist/internal/use-widget-ref.d.ts.map +1 -0
- package/dist/internal/use-widget-ref.js +8 -0
- package/dist/internal/use-widget-ref.js.map +1 -0
- package/dist/list-view.d.ts +5 -20
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +32 -53
- package/dist/list-view.js.map +1 -1
- package/dist/size-group.d.ts +8 -16
- package/dist/size-group.d.ts.map +1 -1
- package/dist/size-group.js +35 -51
- package/dist/size-group.js.map +1 -1
- package/dist/types.d.ts +98 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +17 -6
- package/src/adw/combo-row.tsx +20 -0
- package/src/adw/index.ts +9 -0
- package/src/adw/toast.tsx +74 -0
- package/src/adw/types.ts +45 -0
- package/src/column-view.tsx +176 -160
- package/src/drop-down.tsx +12 -185
- package/src/grid-view.tsx +29 -73
- package/src/index.ts +18 -26
- package/src/internal/cells.tsx +345 -0
- package/src/internal/collection-index.ts +168 -0
- package/src/internal/collection-model.ts +246 -0
- package/src/internal/collection.ts +104 -0
- package/src/internal/drop-down.tsx +186 -0
- package/src/internal/expansion.ts +122 -0
- package/src/internal/selection.tsx +162 -0
- package/src/internal/use-collection.tsx +57 -0
- package/src/internal/use-widget-ref.ts +11 -0
- package/src/list-view.tsx +43 -139
- package/src/size-group.tsx +63 -82
- package/src/types.ts +158 -28
- package/dist/cell.d.ts +0 -30
- package/dist/cell.d.ts.map +0 -1
- package/dist/cell.js +0 -45
- package/dist/cell.js.map +0 -1
- package/dist/column-view-column.d.ts +0 -27
- package/dist/column-view-column.d.ts.map +0 -1
- package/dist/column-view-column.js +0 -30
- package/dist/column-view-column.js.map +0 -1
- package/dist/constraint-layout-apply.d.ts +0 -55
- package/dist/constraint-layout-apply.d.ts.map +0 -1
- package/dist/constraint-layout-apply.js +0 -107
- package/dist/constraint-layout-apply.js.map +0 -1
- package/dist/constraint-layout.d.ts +0 -20
- package/dist/constraint-layout.d.ts.map +0 -1
- package/dist/constraint-layout.js +0 -88
- package/dist/constraint-layout.js.map +0 -1
- package/dist/fixed.d.ts +0 -26
- package/dist/fixed.d.ts.map +0 -1
- package/dist/fixed.js +0 -40
- package/dist/fixed.js.map +0 -1
- package/dist/grid.d.ts +0 -27
- package/dist/grid.d.ts.map +0 -1
- package/dist/grid.js +0 -48
- package/dist/grid.js.map +0 -1
- package/dist/hooks/use-cell-containers.d.ts +0 -18
- package/dist/hooks/use-cell-containers.d.ts.map +0 -1
- package/dist/hooks/use-cell-containers.js +0 -67
- package/dist/hooks/use-cell-containers.js.map +0 -1
- package/dist/hooks/use-collection-model.d.ts +0 -26
- package/dist/hooks/use-collection-model.d.ts.map +0 -1
- package/dist/hooks/use-collection-model.js +0 -35
- package/dist/hooks/use-collection-model.js.map +0 -1
- package/dist/hooks/use-collection-widget.d.ts +0 -28
- package/dist/hooks/use-collection-widget.d.ts.map +0 -1
- package/dist/hooks/use-collection-widget.js +0 -21
- package/dist/hooks/use-collection-widget.js.map +0 -1
- package/dist/hooks/use-drop-down-selection.d.ts +0 -16
- package/dist/hooks/use-drop-down-selection.d.ts.map +0 -1
- package/dist/hooks/use-drop-down-selection.js +0 -35
- package/dist/hooks/use-drop-down-selection.js.map +0 -1
- package/dist/hooks/use-expansion-model.d.ts +0 -12
- package/dist/hooks/use-expansion-model.d.ts.map +0 -1
- package/dist/hooks/use-expansion-model.js +0 -71
- package/dist/hooks/use-expansion-model.js.map +0 -1
- package/dist/hooks/use-header-menu.d.ts +0 -4
- package/dist/hooks/use-header-menu.d.ts.map +0 -1
- package/dist/hooks/use-header-menu.js +0 -20
- package/dist/hooks/use-header-menu.js.map +0 -1
- package/dist/hooks/use-installed-model.d.ts +0 -4
- package/dist/hooks/use-installed-model.d.ts.map +0 -1
- package/dist/hooks/use-installed-model.js +0 -15
- package/dist/hooks/use-installed-model.js.map +0 -1
- package/dist/hooks/use-list-model.d.ts +0 -19
- package/dist/hooks/use-list-model.d.ts.map +0 -1
- package/dist/hooks/use-list-model.js +0 -97
- package/dist/hooks/use-list-model.js.map +0 -1
- package/dist/hooks/use-placed-child.d.ts +0 -18
- package/dist/hooks/use-placed-child.d.ts.map +0 -1
- package/dist/hooks/use-placed-child.js +0 -47
- package/dist/hooks/use-placed-child.js.map +0 -1
- package/dist/hooks/use-selection-model.d.ts +0 -13
- package/dist/hooks/use-selection-model.d.ts.map +0 -1
- package/dist/hooks/use-selection-model.js +0 -103
- package/dist/hooks/use-selection-model.js.map +0 -1
- package/dist/hooks/use-sort-handler.d.ts +0 -17
- package/dist/hooks/use-sort-handler.d.ts.map +0 -1
- package/dist/hooks/use-sort-handler.js +0 -87
- package/dist/hooks/use-sort-handler.js.map +0 -1
- package/dist/menu-items.d.ts +0 -5
- package/dist/menu-items.d.ts.map +0 -1
- package/dist/menu-items.js +0 -48
- package/dist/menu-items.js.map +0 -1
- package/dist/menu.d.ts +0 -14
- package/dist/menu.d.ts.map +0 -1
- package/dist/menu.js +0 -24
- package/dist/menu.js.map +0 -1
- package/dist/overlay.d.ts +0 -24
- package/dist/overlay.d.ts.map +0 -1
- package/dist/overlay.js +0 -49
- package/dist/overlay.js.map +0 -1
- package/dist/utils/cell-container-store.d.ts +0 -28
- package/dist/utils/cell-container-store.d.ts.map +0 -1
- package/dist/utils/cell-container-store.js +0 -98
- package/dist/utils/cell-container-store.js.map +0 -1
- package/dist/utils/item-models.d.ts +0 -10
- package/dist/utils/item-models.d.ts.map +0 -1
- package/dist/utils/item-models.js +0 -82
- package/dist/utils/item-models.js.map +0 -1
- package/dist/utils/item-resolver.d.ts +0 -25
- package/dist/utils/item-resolver.d.ts.map +0 -1
- package/dist/utils/item-resolver.js +0 -102
- package/dist/utils/item-resolver.js.map +0 -1
- package/dist/utils/list-item-flatten.d.ts +0 -24
- package/dist/utils/list-item-flatten.d.ts.map +0 -1
- package/dist/utils/list-item-flatten.js +0 -68
- package/dist/utils/list-item-flatten.js.map +0 -1
- package/src/cell.tsx +0 -144
- package/src/column-view-column.tsx +0 -82
- package/src/constraint-layout-apply.ts +0 -173
- package/src/constraint-layout.tsx +0 -135
- package/src/fixed.tsx +0 -86
- package/src/grid.tsx +0 -88
- package/src/hooks/use-cell-containers.ts +0 -93
- package/src/hooks/use-collection-model.ts +0 -60
- package/src/hooks/use-collection-widget.ts +0 -47
- package/src/hooks/use-drop-down-selection.ts +0 -52
- package/src/hooks/use-expansion-model.ts +0 -82
- package/src/hooks/use-header-menu.ts +0 -34
- package/src/hooks/use-installed-model.ts +0 -20
- package/src/hooks/use-list-model.ts +0 -161
- package/src/hooks/use-placed-child.ts +0 -79
- package/src/hooks/use-selection-model.ts +0 -114
- package/src/hooks/use-sort-handler.ts +0 -107
- package/src/menu-items.ts +0 -48
- package/src/menu.tsx +0 -32
- package/src/overlay.tsx +0 -88
- package/src/utils/cell-container-store.ts +0 -114
- package/src/utils/item-models.ts +0 -113
- package/src/utils/item-resolver.ts +0 -138
- package/src/utils/list-item-flatten.ts +0 -90
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type * as Gio from "@gtkx/gi/gio";
|
|
2
|
+
import type { ReactElement } from "react";
|
|
3
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
+
import { GtkMultiSelection, GtkNoSelection, GtkSingleSelection } from "@gtkx/jsx/gtk";
|
|
5
|
+
import { useEffectEvent, useLayoutEffect, useState } from "react";
|
|
6
|
+
import type { Collection } from "./collection.js";
|
|
7
|
+
|
|
8
|
+
type SelectionOptions = {
|
|
9
|
+
collection: Collection;
|
|
10
|
+
selectedIds?: string[] | null | undefined;
|
|
11
|
+
onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
|
|
12
|
+
selectionMode?: Gtk.SelectionMode | null | undefined;
|
|
13
|
+
onItemsChanged: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type LastSelection = {
|
|
17
|
+
selection: Gtk.SelectionModel | null;
|
|
18
|
+
key: string | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type SelectionElementProps = {
|
|
22
|
+
ref: (value: Gtk.SelectionModel | null) => void;
|
|
23
|
+
model: Gio.ListModel;
|
|
24
|
+
onSelectionChanged: () => void;
|
|
25
|
+
onItemsChanged: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function getSelectedIds(selection: Gtk.SelectionModel, collection: Collection): string[] {
|
|
29
|
+
const bitset = selection.getSelection();
|
|
30
|
+
const size = Number(bitset.getSize());
|
|
31
|
+
const ids: string[] = [];
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < size; index++) {
|
|
34
|
+
pushId(collection.idAt(bitset.getNth(index)), ids);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return ids;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function pushId(id: string | null, ids: string[]): void {
|
|
41
|
+
if (id !== null) {
|
|
42
|
+
ids.push(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function applySingleSelection(selection: Gtk.SingleSelection, positions: number[]): void {
|
|
47
|
+
const [first] = positions;
|
|
48
|
+
|
|
49
|
+
if (first === undefined) {
|
|
50
|
+
selection.unselectAll();
|
|
51
|
+
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
selection.selectItem(first, true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function applyMultiSelection(selection: Gtk.SelectionModel, positions: number[]): void {
|
|
59
|
+
const selected = Gtk.Bitset.newEmpty();
|
|
60
|
+
|
|
61
|
+
for (const position of positions) {
|
|
62
|
+
selected.add(position);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
selection.setSelection(selected, Gtk.Bitset.newRange(0, selection.getNItems()));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applySelection(selection: Gtk.SelectionModel, collection: Collection, ids: string[]): void {
|
|
69
|
+
if (selection instanceof Gtk.NoSelection) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const positions = collection.positionsFor(ids);
|
|
74
|
+
|
|
75
|
+
if (ids.length > 0 && positions.length === 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (selection instanceof Gtk.SingleSelection) {
|
|
80
|
+
applySingleSelection(selection, positions);
|
|
81
|
+
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
applyMultiSelection(selection, positions);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function reportSelection(
|
|
89
|
+
selection: Gtk.SelectionModel | null,
|
|
90
|
+
collection: Collection,
|
|
91
|
+
last: LastSelection,
|
|
92
|
+
onSelectionChanged: ((ids: string[]) => void) | null | undefined,
|
|
93
|
+
): void {
|
|
94
|
+
if (selection === null) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ids = getSelectedIds(selection, collection);
|
|
99
|
+
const key = ids.join(" ");
|
|
100
|
+
|
|
101
|
+
if (last.selection === selection && last.key === key) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
last.selection = selection;
|
|
106
|
+
last.key = key;
|
|
107
|
+
onSelectionChanged?.(ids);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function selectionElement(mode: Gtk.SelectionMode | null | undefined, props: SelectionElementProps): ReactElement {
|
|
111
|
+
if (mode === Gtk.SelectionMode.MULTIPLE) {
|
|
112
|
+
return <GtkMultiSelection {...props} />;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (mode === Gtk.SelectionMode.NONE) {
|
|
116
|
+
return <GtkNoSelection {...props} />;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return <GtkSingleSelection {...props} autoselect={false} canUnselect />;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function syncSelection(
|
|
123
|
+
selection: Gtk.SelectionModel | null,
|
|
124
|
+
collection: Collection,
|
|
125
|
+
selectedIds: string[] | null | undefined,
|
|
126
|
+
): void {
|
|
127
|
+
if (selection === null || selectedIds == null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
applySelection(selection, collection, selectedIds);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function newLastSelection(): LastSelection {
|
|
135
|
+
return { selection: null, key: null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function useSelection(options: SelectionOptions): ReactElement {
|
|
139
|
+
const { collection, selectedIds, onSelectionChanged, selectionMode } = options;
|
|
140
|
+
const [selection, setSelection] = useState<Gtk.SelectionModel | null>(null);
|
|
141
|
+
const [last] = useState<LastSelection>(newLastSelection);
|
|
142
|
+
|
|
143
|
+
const report = useEffectEvent((): void => {
|
|
144
|
+
reportSelection(selection, collection, last, onSelectionChanged);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
useLayoutEffect(() => {
|
|
148
|
+
syncSelection(selection, collection, selectedIds);
|
|
149
|
+
report();
|
|
150
|
+
}, [selection, collection, selectedIds]);
|
|
151
|
+
|
|
152
|
+
return selectionElement(selectionMode, {
|
|
153
|
+
ref: setSelection,
|
|
154
|
+
model: collection.model,
|
|
155
|
+
onSelectionChanged: () => {
|
|
156
|
+
reportSelection(selection, collection, last, onSelectionChanged);
|
|
157
|
+
},
|
|
158
|
+
onItemsChanged: options.onItemsChanged,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { useSelection, type SelectionOptions };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { useLayoutEffect, useMemo, useState } from "react";
|
|
3
|
+
import type { ExpansionProps, Item, Section, SelectionProps } from "../types.js";
|
|
4
|
+
import type { Collection } from "./collection.js";
|
|
5
|
+
import { createCollectionIndex } from "./collection-index.js";
|
|
6
|
+
import { createCollectionModel } from "./collection-model.js";
|
|
7
|
+
import { createCollection } from "./collection.js";
|
|
8
|
+
import { useExpansion } from "./expansion.js";
|
|
9
|
+
import { useSelection } from "./selection.js";
|
|
10
|
+
|
|
11
|
+
type CollectionDataOptions = {
|
|
12
|
+
items?: Item[] | undefined;
|
|
13
|
+
sections?: Section[] | undefined;
|
|
14
|
+
flat?: boolean | undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CollectionOptions = CollectionDataOptions & SelectionProps & ExpansionProps;
|
|
18
|
+
|
|
19
|
+
type CollectionResult = {
|
|
20
|
+
collection: Collection;
|
|
21
|
+
selection: ReactElement;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function useCollectionData(options: CollectionDataOptions): Collection {
|
|
25
|
+
const { items, sections, flat } = options;
|
|
26
|
+
const [gtk] = useState(createCollectionModel);
|
|
27
|
+
const index = useMemo(() => createCollectionIndex(items, sections, flat === true), [items, sections, flat]);
|
|
28
|
+
const collection = useMemo(() => createCollection(gtk, index), [gtk, index]);
|
|
29
|
+
|
|
30
|
+
useLayoutEffect(() => {
|
|
31
|
+
gtk.sync(index);
|
|
32
|
+
}, [gtk, index]);
|
|
33
|
+
|
|
34
|
+
return collection;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function useCollection(options: CollectionOptions): CollectionResult {
|
|
38
|
+
const collection = useCollectionData(options);
|
|
39
|
+
|
|
40
|
+
const onItemsChanged = useExpansion({
|
|
41
|
+
collection,
|
|
42
|
+
expandedIds: options.expandedIds,
|
|
43
|
+
onExpandedChange: options.onExpandedChange,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const selection = useSelection({
|
|
47
|
+
collection,
|
|
48
|
+
selectedIds: options.selectedIds,
|
|
49
|
+
onSelectionChanged: options.onSelectionChanged,
|
|
50
|
+
selectionMode: options.selectionMode,
|
|
51
|
+
onItemsChanged,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return { collection, selection };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { useCollection, useCollectionData, type CollectionDataOptions, type CollectionOptions };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Ref, RefCallback } from "react";
|
|
2
|
+
import { useMergedRef } from "@gtkx/react/internal";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
const useWidgetRef = <T>(external: Ref<T | null> | null | undefined): [T | null, RefCallback<T | null>] => {
|
|
6
|
+
const [widget, setWidget] = useState<T | null>(null);
|
|
7
|
+
|
|
8
|
+
return [widget, useMergedRef<T | null>(external, setWidget)];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { useWidgetRef };
|
package/src/list-view.tsx
CHANGED
|
@@ -1,148 +1,52 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { GtkListView,
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "./types.js";
|
|
22
|
-
import type { CellContainerStore } from "./utils/cell-container-store.js";
|
|
23
|
-
import type { ItemResolver } from "./utils/item-resolver.js";
|
|
24
|
-
|
|
25
|
-
const factoryInstaller = makeFactoryInstaller<Gtk.ListView>((widget, factory) => widget.setFactory(factory));
|
|
26
|
-
|
|
27
|
-
const headerFactoryInstaller = makeFactoryInstaller<Gtk.ListView>((widget, factory) =>
|
|
28
|
-
widget.setHeaderFactory(factory),
|
|
29
|
-
);
|
|
30
|
-
|
|
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
|
-
};
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { GtkListView, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
|
|
3
|
+
import { omit } from "@gtkx/utils";
|
|
4
|
+
import type { ListViewProps } from "./types.js";
|
|
5
|
+
import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
|
|
6
|
+
import { useCollection } from "./internal/use-collection.js";
|
|
7
|
+
|
|
8
|
+
const LIST_VIEW_PROPS = [
|
|
9
|
+
"items",
|
|
10
|
+
"sections",
|
|
11
|
+
"renderItem",
|
|
12
|
+
"renderHeader",
|
|
13
|
+
"selectedIds",
|
|
14
|
+
"onSelectionChanged",
|
|
15
|
+
"selectionMode",
|
|
16
|
+
"expandedIds",
|
|
17
|
+
"onExpandedChange",
|
|
18
|
+
"estimatedItemHeight",
|
|
19
|
+
"estimatedItemWidth",
|
|
20
|
+
] as const satisfies (keyof ListViewProps)[];
|
|
39
21
|
|
|
40
22
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* an optional section header renderer, controlled selection and expansion, and
|
|
44
|
-
* estimated item sizing.
|
|
23
|
+
* Renders a Gtk.ListView from declarative items or sections, with per-row rendering,
|
|
24
|
+
* controlled selection, controlled tree expansion, and estimated item sizing.
|
|
45
25
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
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.
|
|
101
|
-
*/
|
|
102
|
-
export const ListView = <T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNode => {
|
|
103
|
-
const {
|
|
104
|
-
ref,
|
|
105
|
-
items,
|
|
106
|
-
sections,
|
|
107
|
-
renderItem,
|
|
108
|
-
renderHeader,
|
|
109
|
-
selectedIds,
|
|
110
|
-
selectionMode,
|
|
111
|
-
onSelectionChanged,
|
|
112
|
-
expandedIds,
|
|
113
|
-
onExpandedChange,
|
|
114
|
-
estimatedItemHeight,
|
|
115
|
-
estimatedItemWidth,
|
|
116
|
-
...intrinsicProps
|
|
117
|
-
} = props as NormalizedListViewProps<T, S>;
|
|
118
|
-
|
|
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>);
|
|
132
|
-
|
|
133
|
-
const treeContext: TreeRenderContext = createTreeRenderContext(expandedIds, wiring.rowId);
|
|
134
|
-
const cellRenderer: CellRenderer<T, S> = itemRenderer<T, S>(renderItem, treeContext);
|
|
26
|
+
function ListView<T = unknown, S = unknown>(props: ListViewProps<T, S>): ReactNode {
|
|
27
|
+
const { renderItem, renderHeader, expandedIds, estimatedItemHeight, estimatedItemWidth } = props;
|
|
28
|
+
const rest = omit(props, LIST_VIEW_PROPS);
|
|
29
|
+
const size = { width: estimatedItemWidth ?? -1, height: estimatedItemHeight ?? -1 };
|
|
30
|
+
const { collection, selection } = useCollection(props);
|
|
31
|
+
const itemCells = useItemCells(size);
|
|
32
|
+
const headerCells = useHeaderCells(size);
|
|
135
33
|
|
|
136
34
|
return (
|
|
137
35
|
<>
|
|
138
|
-
<GtkListView
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
36
|
+
<GtkListView
|
|
37
|
+
model={selection}
|
|
38
|
+
factory={<GtkSignalListItemFactory {...itemCells.handlers} />}
|
|
39
|
+
{...(renderHeader != null && {
|
|
40
|
+
headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} />,
|
|
41
|
+
})}
|
|
42
|
+
{...rest}
|
|
145
43
|
/>
|
|
44
|
+
<ItemPortals store={itemCells} render={renderItem} collection={collection} expandedIds={expandedIds} />
|
|
45
|
+
{renderHeader != null && (
|
|
46
|
+
<HeaderPortals store={headerCells} render={renderHeader} collection={collection} />
|
|
47
|
+
)}
|
|
146
48
|
</>
|
|
147
49
|
);
|
|
148
|
-
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { ListView };
|
package/src/size-group.tsx
CHANGED
|
@@ -1,96 +1,77 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import type { ElementType, ReactNode, Ref, RefCallback } from "react";
|
|
2
3
|
import { GtkSizeGroup } from "@gtkx/jsx/gtk";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
};
|
|
4
|
+
import { useMergedRef } from "@gtkx/react/internal";
|
|
5
|
+
import { createContext, useCallback, useContext, useState } from "react";
|
|
6
|
+
import type { SizeGroupChildProps, SizeGroupProps } from "./types.js";
|
|
7
|
+
|
|
8
|
+
type Register = RefCallback<Gtk.Widget | null>;
|
|
21
9
|
|
|
22
|
-
|
|
10
|
+
type SizeGroupChildRuntimeProps = {
|
|
11
|
+
component: ElementType;
|
|
12
|
+
ref?: Ref<Gtk.Widget | null> | undefined;
|
|
13
|
+
} & Record<string, unknown>;
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!registry) throw new Error("<SizeGroup.Child> must be a child of <SizeGroup>");
|
|
27
|
-
return registry;
|
|
15
|
+
type SizeGroupComponent = ((props: SizeGroupProps) => ReactNode) & {
|
|
16
|
+
Child: <C extends ElementType>(props: SizeGroupChildProps<C>) => ReactNode;
|
|
28
17
|
};
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
const SizeGroupContext = createContext<Register | null>(null);
|
|
20
|
+
const SizeGroupChild = SizeGroupChildImpl as <C extends ElementType>(props: SizeGroupChildProps<C>) => ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a Gtk.SizeGroup that keeps widgets joined through `SizeGroup.Child`
|
|
23
|
+
* at a common size in the given mode, without contributing a widget of its own.
|
|
24
|
+
*/
|
|
25
|
+
const SizeGroup: SizeGroupComponent = Object.assign(SizeGroupRoot, { Child: SizeGroupChild });
|
|
26
|
+
|
|
27
|
+
const useRegister = (): Register => {
|
|
28
|
+
const register = useContext(SizeGroupContext);
|
|
29
|
+
|
|
30
|
+
if (register === null) {
|
|
31
|
+
throw new Error("<SizeGroup.Child> must be a child of <SizeGroup>");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return register;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
function SizeGroupChildImpl(props: SizeGroupChildRuntimeProps): ReactNode {
|
|
38
|
+
const { component: Component, ref, ...rest } = props;
|
|
39
|
+
const refCallback = useMergedRef<Gtk.Widget | null>(ref, useRegister());
|
|
40
|
+
|
|
41
|
+
return <Component {...rest} ref={refCallback} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const addWidget = (widgets: Gtk.Widget[], widget: Gtk.Widget): Gtk.Widget[] =>
|
|
45
|
+
widgets.includes(widget) ? widgets : [...widgets, widget];
|
|
46
|
+
|
|
47
|
+
const removeWidget = (widgets: Gtk.Widget[], widget: Gtk.Widget): Gtk.Widget[] =>
|
|
48
|
+
widgets.filter((entry) => entry !== widget);
|
|
49
|
+
|
|
50
|
+
function SizeGroupRoot(props: SizeGroupProps): ReactNode {
|
|
51
|
+
const { mode, ref, children } = props;
|
|
52
|
+
const [widgets, setWidgets] = useState<Gtk.Widget[]>([]);
|
|
40
53
|
|
|
41
|
-
const
|
|
42
|
-
const registry = useSizeGroupRegistry();
|
|
43
|
-
const joinGroup = useCallback<RefCallback<Gtk.Widget>>(
|
|
54
|
+
const register = useCallback<Register>(
|
|
44
55
|
(widget) => {
|
|
45
|
-
if (widget === null)
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
if (widget === null) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setWidgets((previous) => addWidget(previous, widget));
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
setWidgets((previous) => removeWidget(previous, widget));
|
|
64
|
+
};
|
|
48
65
|
},
|
|
49
|
-
[
|
|
66
|
+
[setWidgets],
|
|
50
67
|
);
|
|
51
|
-
const Component: ElementType = component;
|
|
52
|
-
const setWidget = useMergeRefs<Gtk.Widget>(ref, joinGroup);
|
|
53
|
-
return <Component {...rest} ref={setWidget} />;
|
|
54
|
-
};
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
};
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<GtkSizeGroup ref={ref} mode={mode} widgets={widgets} />
|
|
72
|
+
<SizeGroupContext.Provider value={register}>{children}</SizeGroupContext.Provider>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
74
76
|
|
|
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
|
-
);
|
|
77
|
+
export { SizeGroup };
|