@gtkx/components 1.0.0-rc.2 → 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 +2 -1
- package/dist/adw/index.d.ts.map +1 -1
- package/dist/adw/index.js +1 -0
- package/dist/adw/index.js.map +1 -1
- package/dist/adw/types.d.ts +9 -2
- package/dist/adw/types.d.ts.map +1 -1
- package/dist/adw/types.js.map +1 -1
- package/dist/column-view.js +2 -2
- package/dist/column-view.js.map +1 -1
- package/dist/drop-down.d.ts +3 -5
- package/dist/drop-down.d.ts.map +1 -1
- package/dist/drop-down.js +5 -112
- package/dist/drop-down.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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 +1 -1
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +3 -4
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/selection.d.ts +1 -0
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +1 -0
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/use-collection.d.ts +1 -2
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +9 -17
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/types.d.ts +7 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/adw/combo-row.tsx +20 -0
- package/src/adw/index.ts +8 -1
- package/src/adw/types.ts +15 -2
- package/src/column-view.tsx +2 -2
- package/src/drop-down.tsx +9 -195
- package/src/index.ts +0 -1
- package/src/internal/drop-down.tsx +186 -0
- package/src/internal/expansion.ts +5 -6
- package/src/internal/selection.tsx +3 -0
- package/src/internal/use-collection.tsx +11 -24
- package/src/types.ts +13 -14
package/src/drop-down.tsx
CHANGED
|
@@ -1,205 +1,19 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
7
|
-
import type { Collection } from "./internal/collection.js";
|
|
8
|
-
import type { DropDownProps, ItemRenderer, RenderItemArgs } from "./types.js";
|
|
9
|
-
import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
|
|
10
|
-
import { useCollectionData } from "./internal/use-collection.js";
|
|
11
|
-
import { useWidgetRef } from "./internal/use-widget-ref.js";
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { GtkDropDown } from "@gtkx/jsx/gtk";
|
|
3
|
+
import type { DropDownBaseProps } from "./internal/drop-down.js";
|
|
4
|
+
import type { DropDownProps } from "./types.js";
|
|
5
|
+
import { DropDownBase } from "./internal/drop-down.js";
|
|
12
6
|
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
type DropDownRuntimeProps = DropDownProps & {
|
|
16
|
-
component?: ElementType | undefined;
|
|
17
|
-
ref?: Ref<SelectableWidget | null> | undefined;
|
|
18
|
-
} & Record<string, unknown>;
|
|
19
|
-
|
|
20
|
-
type SelectionOptions = {
|
|
21
|
-
widget: SelectableWidget | null;
|
|
22
|
-
collection: Collection;
|
|
23
|
-
applying: RefObject<boolean>;
|
|
24
|
-
props: DropDownRuntimeProps;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type KnownSelection = {
|
|
28
|
-
known: { current: string | null };
|
|
29
|
-
onSelectionChanged: ((id: string) => void) | null | undefined;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type DropDownComponent = <T = unknown, S = unknown, C extends ElementType = typeof GtkDropDown>(
|
|
33
|
-
props: DropDownProps<T, S, C>,
|
|
34
|
-
) => ReactNode;
|
|
35
|
-
|
|
36
|
-
const DROP_DOWN_PROPS: string[] = [
|
|
37
|
-
"component",
|
|
38
|
-
"items",
|
|
39
|
-
"sections",
|
|
40
|
-
"selectedId",
|
|
41
|
-
"onSelectionChanged",
|
|
42
|
-
"renderItem",
|
|
43
|
-
"renderListItem",
|
|
44
|
-
"renderHeader",
|
|
45
|
-
"ref",
|
|
46
|
-
];
|
|
7
|
+
type DropDownComponent = <T = unknown, S = unknown>(props: DropDownProps<T, S>) => ReactNode;
|
|
47
8
|
|
|
48
9
|
/**
|
|
49
|
-
* Renders a
|
|
10
|
+
* Renders a `Gtk.DropDown` backed by a collection model, with customizable renderers for the
|
|
50
11
|
* collapsed display, popup rows, and popup section headers, and controlled selection.
|
|
51
|
-
* The backing widget defaults to GtkDropDown and is swappable through `component`.
|
|
52
12
|
*/
|
|
53
13
|
const DropDown: DropDownComponent = DropDownImpl as DropDownComponent;
|
|
54
14
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
return JSON.stringify(value);
|
|
62
|
-
} catch {
|
|
63
|
-
return String(value);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const defaultItemContent = (value: unknown): ReactNode =>
|
|
68
|
-
value == null ? null : <GtkLabel>{describeValue(value)}</GtkLabel>;
|
|
69
|
-
|
|
70
|
-
const defaultRenderItem = ({ item }: RenderItemArgs<unknown>): ReactNode => defaultItemContent(item);
|
|
71
|
-
|
|
72
|
-
const faceRenderer = (props: DropDownRuntimeProps): ItemRenderer<never> =>
|
|
73
|
-
props.renderItem ?? defaultRenderItem;
|
|
74
|
-
|
|
75
|
-
const listRenderer = (props: DropDownRuntimeProps): ItemRenderer<never> =>
|
|
76
|
-
props.renderListItem ?? props.renderItem ?? defaultRenderItem;
|
|
77
|
-
|
|
78
|
-
const resolvePosition = (
|
|
79
|
-
widget: SelectableWidget,
|
|
80
|
-
collection: Collection,
|
|
81
|
-
selectedId: string | null | undefined,
|
|
82
|
-
): number => {
|
|
83
|
-
const requested = selectedId == null ? -1 : collection.positionFor(selectedId);
|
|
84
|
-
|
|
85
|
-
return requested >= 0 ? requested : widget.getSelected();
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const updateKnownSelection = (
|
|
89
|
-
tracker: KnownSelection,
|
|
90
|
-
effectiveId: string,
|
|
91
|
-
selectedId: string | null | undefined,
|
|
92
|
-
): void => {
|
|
93
|
-
const { known, onSelectionChanged } = tracker;
|
|
94
|
-
const expectedId = selectedId ?? known.current;
|
|
95
|
-
const isNew = effectiveId !== known.current;
|
|
96
|
-
known.current = effectiveId;
|
|
97
|
-
|
|
98
|
-
if (expectedId !== null && effectiveId !== expectedId && isNew) {
|
|
99
|
-
onSelectionChanged?.(effectiveId);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const reportKnownSelection = (tracker: KnownSelection, id: string | null): void => {
|
|
104
|
-
const { known, onSelectionChanged } = tracker;
|
|
105
|
-
|
|
106
|
-
if (id === null || id === known.current) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
known.current = id;
|
|
111
|
-
onSelectionChanged?.(id);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const reportSelectedNotify = (
|
|
115
|
-
options: SelectionOptions,
|
|
116
|
-
tracker: KnownSelection,
|
|
117
|
-
applying: RefObject<boolean>,
|
|
118
|
-
): void => {
|
|
119
|
-
const { widget, collection } = options;
|
|
120
|
-
|
|
121
|
-
if (widget === null || applying.current) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
reportKnownSelection(tracker, collection.idAt(widget.getSelected()));
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const applySelectedPosition = (widget: SelectableWidget, position: number, applying: RefObject<boolean>): void => {
|
|
129
|
-
applying.current = true;
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
widget.selected = position;
|
|
133
|
-
} finally {
|
|
134
|
-
applying.current = false;
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const useDropDownSelection = (options: SelectionOptions): void => {
|
|
139
|
-
const { widget, collection, applying } = options;
|
|
140
|
-
const { selectedId } = options.props;
|
|
141
|
-
const known = useRef<string | null>(null);
|
|
142
|
-
|
|
143
|
-
const syncKnownSelection = useEffectEvent((position: number): void => {
|
|
144
|
-
const effectiveId = collection.idAt(position);
|
|
145
|
-
|
|
146
|
-
if (effectiveId === null) {
|
|
147
|
-
known.current = null;
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const { onSelectionChanged } = options.props;
|
|
153
|
-
updateKnownSelection({ known, onSelectionChanged }, effectiveId, selectedId);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
useLayoutEffect(() => {
|
|
157
|
-
if (widget === null) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const position = resolvePosition(widget, collection, selectedId);
|
|
162
|
-
applySelectedPosition(widget, position, applying);
|
|
163
|
-
syncKnownSelection(position);
|
|
164
|
-
}, [widget, collection, selectedId, applying]);
|
|
165
|
-
|
|
166
|
-
useSignal(widget, "notify::selected", (): void => {
|
|
167
|
-
reportSelectedNotify(options, { known, onSelectionChanged: options.props.onSelectionChanged }, applying);
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
function DropDownImpl(props: DropDownRuntimeProps): ReactNode {
|
|
172
|
-
const { component, items, sections, renderListItem, renderHeader, ref } = props;
|
|
173
|
-
const rest = omit(props, DROP_DOWN_PROPS);
|
|
174
|
-
const [widget, refCallback] = useWidgetRef<SelectableWidget>(ref);
|
|
175
|
-
const applying = useRef(false);
|
|
176
|
-
const collection = useCollectionData({ items, sections, applying });
|
|
177
|
-
const faceCells = useItemCells({ width: -1, height: -1 });
|
|
178
|
-
const listCells = useItemCells({ width: -1, height: -1 });
|
|
179
|
-
const headerCells = useHeaderCells({ width: -1, height: -1 });
|
|
180
|
-
useDropDownSelection({ widget, collection, applying, props });
|
|
181
|
-
const Component = component ?? GtkDropDown;
|
|
182
|
-
const hasHeader = typeof renderHeader === "function";
|
|
183
|
-
|
|
184
|
-
return (
|
|
185
|
-
<>
|
|
186
|
-
<Component
|
|
187
|
-
ref={refCallback}
|
|
188
|
-
model={collection.model}
|
|
189
|
-
factory={<GtkSignalListItemFactory {...faceCells.handlers} />}
|
|
190
|
-
{...(renderListItem != null && {
|
|
191
|
-
listFactory: <GtkSignalListItemFactory {...listCells.handlers} />,
|
|
192
|
-
})}
|
|
193
|
-
{...(hasHeader && { headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} /> })}
|
|
194
|
-
{...rest}
|
|
195
|
-
/>
|
|
196
|
-
<ItemPortals store={faceCells} render={faceRenderer(props)} collection={collection} />
|
|
197
|
-
{renderListItem != null && (
|
|
198
|
-
<ItemPortals store={listCells} render={listRenderer(props)} collection={collection} />
|
|
199
|
-
)}
|
|
200
|
-
{hasHeader && <HeaderPortals store={headerCells} render={renderHeader} collection={collection} />}
|
|
201
|
-
</>
|
|
202
|
-
);
|
|
15
|
+
function DropDownImpl(props: DropDownBaseProps): ReactNode {
|
|
16
|
+
return <DropDownBase {...props} component={GtkDropDown} />;
|
|
203
17
|
}
|
|
204
18
|
|
|
205
19
|
export { DropDown };
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
|
+
import type { ElementType, ReactNode, Ref } from "react";
|
|
3
|
+
import { GtkLabel, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
|
|
4
|
+
import { applyWrite } from "@gtkx/react/internal";
|
|
5
|
+
import { omit } from "@gtkx/utils";
|
|
6
|
+
import { useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
7
|
+
import type { DropDownOwnProps, ItemRenderer, RenderItemArgs } from "../types.js";
|
|
8
|
+
import type { Collection } from "./collection.js";
|
|
9
|
+
import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./cells.js";
|
|
10
|
+
import { useCollectionData } from "./use-collection.js";
|
|
11
|
+
import { useWidgetRef } from "./use-widget-ref.js";
|
|
12
|
+
|
|
13
|
+
type SelectableWidget = GObject.Object & { getSelected: () => number; selected: number };
|
|
14
|
+
|
|
15
|
+
type DropDownBaseProps = DropDownOwnProps<unknown, unknown> & {
|
|
16
|
+
onNotifySelected?: ((value: number | null, self: SelectableWidget) => void) | null | undefined;
|
|
17
|
+
ref?: Ref<SelectableWidget | null> | undefined;
|
|
18
|
+
} & Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
type SelectionOptions = {
|
|
21
|
+
widget: SelectableWidget | null;
|
|
22
|
+
collection: Collection;
|
|
23
|
+
props: DropDownBaseProps;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type NotifySelectedHandler = NonNullable<DropDownBaseProps["onNotifySelected"]>;
|
|
27
|
+
|
|
28
|
+
type KnownSelection = {
|
|
29
|
+
known: { current: string | null };
|
|
30
|
+
onSelectionChanged: ((id: string) => void) | null | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const DROP_DOWN_PROPS: string[] = [
|
|
34
|
+
"component",
|
|
35
|
+
"items",
|
|
36
|
+
"sections",
|
|
37
|
+
"selectedId",
|
|
38
|
+
"onSelectionChanged",
|
|
39
|
+
"onNotifySelected",
|
|
40
|
+
"renderItem",
|
|
41
|
+
"renderListItem",
|
|
42
|
+
"renderHeader",
|
|
43
|
+
"ref",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const describeValue = (value: unknown): string => {
|
|
47
|
+
if (typeof value === "string") {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
return JSON.stringify(value);
|
|
53
|
+
} catch {
|
|
54
|
+
return String(value);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const defaultItemContent = (value: unknown): ReactNode =>
|
|
59
|
+
value == null ? null : <GtkLabel>{describeValue(value)}</GtkLabel>;
|
|
60
|
+
|
|
61
|
+
const defaultRenderItem = ({ item }: RenderItemArgs<unknown>): ReactNode => defaultItemContent(item);
|
|
62
|
+
const faceRenderer = (props: DropDownBaseProps): ItemRenderer<never> => props.renderItem ?? defaultRenderItem;
|
|
63
|
+
|
|
64
|
+
const listRenderer = (props: DropDownBaseProps): ItemRenderer<never> =>
|
|
65
|
+
props.renderListItem ?? props.renderItem ?? defaultRenderItem;
|
|
66
|
+
|
|
67
|
+
const resolvePosition = (
|
|
68
|
+
widget: SelectableWidget,
|
|
69
|
+
collection: Collection,
|
|
70
|
+
selectedId: string | null | undefined,
|
|
71
|
+
): number => {
|
|
72
|
+
const requested = selectedId == null ? -1 : collection.positionFor(selectedId);
|
|
73
|
+
|
|
74
|
+
return requested >= 0 ? requested : widget.getSelected();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const updateKnownSelection = (
|
|
78
|
+
tracker: KnownSelection,
|
|
79
|
+
effectiveId: string,
|
|
80
|
+
selectedId: string | null | undefined,
|
|
81
|
+
): void => {
|
|
82
|
+
const { known, onSelectionChanged } = tracker;
|
|
83
|
+
const expectedId = selectedId ?? known.current;
|
|
84
|
+
const isNew = effectiveId !== known.current;
|
|
85
|
+
known.current = effectiveId;
|
|
86
|
+
|
|
87
|
+
if (expectedId !== null && effectiveId !== expectedId && isNew) {
|
|
88
|
+
onSelectionChanged?.(effectiveId);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const reportKnownSelection = (tracker: KnownSelection, id: string | null): void => {
|
|
93
|
+
const { known, onSelectionChanged } = tracker;
|
|
94
|
+
|
|
95
|
+
if (id === null || id === known.current) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
known.current = id;
|
|
100
|
+
onSelectionChanged?.(id);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const reportSelectedNotify = (options: SelectionOptions, tracker: KnownSelection): void => {
|
|
104
|
+
const { widget, collection } = options;
|
|
105
|
+
|
|
106
|
+
if (widget === null) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
reportKnownSelection(tracker, collection.idAt(widget.getSelected()));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const applySelectedPosition = (widget: SelectableWidget, position: number): void => {
|
|
114
|
+
applyWrite(() => {
|
|
115
|
+
widget.selected = position;
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const useDropDownSelection = (options: SelectionOptions): NotifySelectedHandler => {
|
|
120
|
+
const { widget, collection } = options;
|
|
121
|
+
const { selectedId } = options.props;
|
|
122
|
+
const known = useRef<string | null>(null);
|
|
123
|
+
|
|
124
|
+
const syncKnownSelection = useEffectEvent((position: number): void => {
|
|
125
|
+
const effectiveId = collection.idAt(position);
|
|
126
|
+
|
|
127
|
+
if (effectiveId === null) {
|
|
128
|
+
known.current = null;
|
|
129
|
+
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { onSelectionChanged } = options.props;
|
|
134
|
+
updateKnownSelection({ known, onSelectionChanged }, effectiveId, selectedId);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
useLayoutEffect(() => {
|
|
138
|
+
if (widget === null) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const position = resolvePosition(widget, collection, selectedId);
|
|
143
|
+
applySelectedPosition(widget, position);
|
|
144
|
+
syncKnownSelection(position);
|
|
145
|
+
}, [widget, collection, selectedId]);
|
|
146
|
+
|
|
147
|
+
return (value, self) => {
|
|
148
|
+
reportSelectedNotify(options, { known, onSelectionChanged: options.props.onSelectionChanged });
|
|
149
|
+
options.props.onNotifySelected?.(value, self);
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
function DropDownBase(props: DropDownBaseProps & { component: ElementType }): ReactNode {
|
|
154
|
+
const { component: Component, items, sections, renderListItem, renderHeader, ref } = props;
|
|
155
|
+
const rest = omit(props, DROP_DOWN_PROPS);
|
|
156
|
+
const [widget, refCallback] = useWidgetRef<SelectableWidget>(ref);
|
|
157
|
+
const collection = useCollectionData({ items, sections });
|
|
158
|
+
const faceCells = useItemCells({ width: -1, height: -1 });
|
|
159
|
+
const listCells = useItemCells({ width: -1, height: -1 });
|
|
160
|
+
const headerCells = useHeaderCells({ width: -1, height: -1 });
|
|
161
|
+
const handleNotifySelected = useDropDownSelection({ widget, collection, props });
|
|
162
|
+
const hasHeader = typeof renderHeader === "function";
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<>
|
|
166
|
+
<Component
|
|
167
|
+
ref={refCallback}
|
|
168
|
+
model={collection.model}
|
|
169
|
+
factory={<GtkSignalListItemFactory {...faceCells.handlers} />}
|
|
170
|
+
{...(renderListItem != null && {
|
|
171
|
+
listFactory: <GtkSignalListItemFactory {...listCells.handlers} />,
|
|
172
|
+
})}
|
|
173
|
+
{...(hasHeader && { headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} /> })}
|
|
174
|
+
{...rest}
|
|
175
|
+
onNotifySelected={handleNotifySelected}
|
|
176
|
+
/>
|
|
177
|
+
<ItemPortals store={faceCells} render={faceRenderer(props)} collection={collection} />
|
|
178
|
+
{renderListItem != null && (
|
|
179
|
+
<ItemPortals store={listCells} render={listRenderer(props)} collection={collection} />
|
|
180
|
+
)}
|
|
181
|
+
{hasHeader && <HeaderPortals store={headerCells} render={renderHeader} collection={collection} />}
|
|
182
|
+
</>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export { DropDownBase, type DropDownBaseProps };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import type { RefObject } from "react";
|
|
3
|
-
import { useSignal } from "@gtkx/react";
|
|
4
3
|
import { useEffectEvent, useLayoutEffect, useRef, useState } from "react";
|
|
5
4
|
import type { Collection } from "./collection.js";
|
|
6
5
|
import { eachRow } from "./collection.js";
|
|
@@ -102,7 +101,7 @@ function runControlledExpansion(
|
|
|
102
101
|
report();
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
function useExpansion(options: ExpansionOptions): void {
|
|
104
|
+
function useExpansion(options: ExpansionOptions): () => void {
|
|
106
105
|
const { collection, expandedIds, onExpandedChange } = options;
|
|
107
106
|
const [last] = useState<LastExpansion>(newLastExpansion);
|
|
108
107
|
const expanding = useRef(false);
|
|
@@ -111,13 +110,13 @@ function useExpansion(options: ExpansionOptions): void {
|
|
|
111
110
|
reportWhenIdle(collection, last, expanding, onExpandedChange);
|
|
112
111
|
});
|
|
113
112
|
|
|
114
|
-
useSignal(collection.isTree ? collection.model : null, "items-changed", () => {
|
|
115
|
-
reportWhenIdle(collection, last, expanding, onExpandedChange);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
113
|
useLayoutEffect(() => {
|
|
119
114
|
runControlledExpansion(collection, expandedIds, expanding, report);
|
|
120
115
|
}, [collection, expandedIds]);
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
reportWhenIdle(collection, last, expanding, onExpandedChange);
|
|
119
|
+
};
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
export { useExpansion, type ExpansionOptions };
|
|
@@ -10,6 +10,7 @@ type SelectionOptions = {
|
|
|
10
10
|
selectedIds?: string[] | null | undefined;
|
|
11
11
|
onSelectionChanged?: ((ids: string[]) => void) | null | undefined;
|
|
12
12
|
selectionMode?: Gtk.SelectionMode | null | undefined;
|
|
13
|
+
onItemsChanged: () => void;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
type LastSelection = {
|
|
@@ -21,6 +22,7 @@ type SelectionElementProps = {
|
|
|
21
22
|
ref: (value: Gtk.SelectionModel | null) => void;
|
|
22
23
|
model: Gio.ListModel;
|
|
23
24
|
onSelectionChanged: () => void;
|
|
25
|
+
onItemsChanged: () => void;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
function getSelectedIds(selection: Gtk.SelectionModel, collection: Collection): string[] {
|
|
@@ -153,6 +155,7 @@ function useSelection(options: SelectionOptions): ReactElement {
|
|
|
153
155
|
onSelectionChanged: () => {
|
|
154
156
|
reportSelection(selection, collection, last, onSelectionChanged);
|
|
155
157
|
},
|
|
158
|
+
onItemsChanged: options.onItemsChanged,
|
|
156
159
|
});
|
|
157
160
|
}
|
|
158
161
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type { ReactElement
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
2
|
import { useLayoutEffect, useMemo, useState } from "react";
|
|
3
3
|
import type { ExpansionProps, Item, Section, SelectionProps } from "../types.js";
|
|
4
|
-
import type { CollectionIndex } from "./collection-index.js";
|
|
5
|
-
import type { CollectionModel } from "./collection-model.js";
|
|
6
4
|
import type { Collection } from "./collection.js";
|
|
7
5
|
import { createCollectionIndex } from "./collection-index.js";
|
|
8
6
|
import { createCollectionModel } from "./collection-model.js";
|
|
@@ -14,7 +12,6 @@ type CollectionDataOptions = {
|
|
|
14
12
|
items?: Item[] | undefined;
|
|
15
13
|
sections?: Section[] | undefined;
|
|
16
14
|
flat?: boolean | undefined;
|
|
17
|
-
applying?: RefObject<boolean> | undefined;
|
|
18
15
|
};
|
|
19
16
|
|
|
20
17
|
type CollectionOptions = CollectionDataOptions & SelectionProps & ExpansionProps;
|
|
@@ -24,44 +21,34 @@ type CollectionResult = {
|
|
|
24
21
|
selection: ReactElement;
|
|
25
22
|
};
|
|
26
23
|
|
|
27
|
-
function runSync(gtk: CollectionModel, index: CollectionIndex, applying: RefObject<boolean> | undefined): void {
|
|
28
|
-
if (applying === undefined) {
|
|
29
|
-
gtk.sync(index);
|
|
30
|
-
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
applying.current = true;
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
gtk.sync(index);
|
|
38
|
-
} finally {
|
|
39
|
-
applying.current = false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
24
|
function useCollectionData(options: CollectionDataOptions): Collection {
|
|
44
|
-
const { items, sections, flat
|
|
25
|
+
const { items, sections, flat } = options;
|
|
45
26
|
const [gtk] = useState(createCollectionModel);
|
|
46
27
|
const index = useMemo(() => createCollectionIndex(items, sections, flat === true), [items, sections, flat]);
|
|
47
28
|
const collection = useMemo(() => createCollection(gtk, index), [gtk, index]);
|
|
48
29
|
|
|
49
30
|
useLayoutEffect(() => {
|
|
50
|
-
|
|
51
|
-
}, [gtk, index
|
|
31
|
+
gtk.sync(index);
|
|
32
|
+
}, [gtk, index]);
|
|
52
33
|
|
|
53
34
|
return collection;
|
|
54
35
|
}
|
|
55
36
|
|
|
56
37
|
function useCollection(options: CollectionOptions): CollectionResult {
|
|
57
38
|
const collection = useCollectionData(options);
|
|
58
|
-
|
|
39
|
+
|
|
40
|
+
const onItemsChanged = useExpansion({
|
|
41
|
+
collection,
|
|
42
|
+
expandedIds: options.expandedIds,
|
|
43
|
+
onExpandedChange: options.onExpandedChange,
|
|
44
|
+
});
|
|
59
45
|
|
|
60
46
|
const selection = useSelection({
|
|
61
47
|
collection,
|
|
62
48
|
selectedIds: options.selectedIds,
|
|
63
49
|
onSelectionChanged: options.onSelectionChanged,
|
|
64
50
|
selectionMode: options.selectionMode,
|
|
51
|
+
onItemsChanged,
|
|
65
52
|
});
|
|
66
53
|
|
|
67
54
|
return { collection, selection };
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type * as Gtk from "@gtkx/gi/gtk";
|
|
|
2
2
|
import type {
|
|
3
3
|
GtkColumnViewColumnProps,
|
|
4
4
|
GtkColumnViewProps,
|
|
5
|
-
|
|
5
|
+
GtkDropDownProps,
|
|
6
6
|
GtkGridViewProps,
|
|
7
7
|
GtkListViewProps,
|
|
8
8
|
} from "@gtkx/jsx/gtk";
|
|
@@ -13,11 +13,6 @@ type ChildProps<C extends ElementType> = {
|
|
|
13
13
|
component: C;
|
|
14
14
|
} & ComponentPropsWithRef<C>;
|
|
15
15
|
|
|
16
|
-
/** Props of a component whose backing widget defaults to one type but can be swapped through `component`. */
|
|
17
|
-
type WidgetProps<C extends ElementType, Own = unknown, ExtraOmit extends string = never> = Own & {
|
|
18
|
-
component?: C;
|
|
19
|
-
} & Omit<ComponentPropsWithRef<C>, ExtraOmit | keyof Own>;
|
|
20
|
-
|
|
21
16
|
/**
|
|
22
17
|
* A single item in a collection model, identified by a stable id and holding an
|
|
23
18
|
* arbitrary value. Nested items form a tree.
|
|
@@ -131,15 +126,18 @@ type DropDownOwnProps<T, S> = SourceProps<T, S> & {
|
|
|
131
126
|
renderHeader?: HeaderRenderer<S> | null | undefined;
|
|
132
127
|
};
|
|
133
128
|
|
|
129
|
+
type DropDownWidgetProps<Widget, T, S> = Omit<
|
|
130
|
+
Widget,
|
|
131
|
+
"model" | "factory" | "listFactory" | "headerFactory" | keyof DropDownOwnProps<T, S>
|
|
132
|
+
> &
|
|
133
|
+
DropDownOwnProps<T, S>;
|
|
134
|
+
|
|
134
135
|
/**
|
|
135
|
-
* Props for {@link DropDown}.
|
|
136
|
-
*
|
|
136
|
+
* Props for {@link DropDown}. Combines the underlying Gtk.DropDown props with the declarative
|
|
137
|
+
* collection props: flat items or grouped sections, controlled single selection, and renderers
|
|
138
|
+
* for the collapsed display, popup rows, and popup section headers.
|
|
137
139
|
*/
|
|
138
|
-
type DropDownProps<T = unknown, S = unknown
|
|
139
|
-
C,
|
|
140
|
-
DropDownOwnProps<T, S>,
|
|
141
|
-
"model" | "factory" | "listFactory" | "headerFactory"
|
|
142
|
-
>;
|
|
140
|
+
type DropDownProps<T = unknown, S = unknown> = DropDownWidgetProps<GtkDropDownProps, T, S>;
|
|
143
141
|
|
|
144
142
|
type GridViewOwnProps<T> = ItemSizeProps &
|
|
145
143
|
SelectionProps & {
|
|
@@ -190,7 +188,8 @@ export {
|
|
|
190
188
|
type SelectionProps,
|
|
191
189
|
type ExpansionProps,
|
|
192
190
|
type ChildProps,
|
|
193
|
-
type
|
|
191
|
+
type DropDownOwnProps,
|
|
192
|
+
type DropDownWidgetProps,
|
|
194
193
|
type Item,
|
|
195
194
|
type Section,
|
|
196
195
|
type RenderItemArgs,
|