@gtkx/components 1.0.0-rc.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/column-view.d.ts +2 -2
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +14 -12
- package/dist/column-view.js.map +1 -1
- package/dist/grid-view.js +1 -1
- package/dist/grid-view.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/cells.d.ts +31 -23
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +168 -77
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +8 -11
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +53 -73
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +24 -4
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +247 -96
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +8 -10
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +38 -48
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +11 -0
- package/dist/internal/controlled-sync.d.ts.map +1 -0
- package/dist/internal/controlled-sync.js +19 -0
- package/dist/internal/controlled-sync.js.map +1 -0
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +27 -15
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.d.ts +3 -2
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +73 -48
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/keys.d.ts +5 -0
- package/dist/internal/keys.d.ts.map +1 -0
- package/dist/internal/keys.js +13 -0
- package/dist/internal/keys.js.map +1 -0
- package/dist/internal/selection.d.ts +3 -2
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +68 -35
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/slots.d.ts +10 -0
- package/dist/internal/slots.d.ts.map +1 -0
- package/dist/internal/slots.js +43 -0
- package/dist/internal/slots.js.map +1 -0
- package/dist/internal/tree-expansion.d.ts +19 -0
- package/dist/internal/tree-expansion.d.ts.map +1 -0
- package/dist/internal/tree-expansion.js +72 -0
- package/dist/internal/tree-expansion.js.map +1 -0
- package/dist/internal/tree-order.d.ts +26 -0
- package/dist/internal/tree-order.d.ts.map +1 -0
- package/dist/internal/tree-order.js +109 -0
- package/dist/internal/tree-order.js.map +1 -0
- package/dist/internal/use-collection.d.ts +1 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +4 -4
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +6 -6
- package/dist/list-view.js.map +1 -1
- package/dist/types.d.ts +72 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +17 -9
- package/src/column-view.tsx +28 -25
- package/src/grid-view.tsx +1 -1
- package/src/index.ts +3 -0
- package/src/internal/cells.tsx +339 -117
- package/src/internal/collection-index.ts +68 -92
- package/src/internal/collection-model.ts +327 -112
- package/src/internal/collection.ts +45 -65
- package/src/internal/controlled-sync.ts +34 -0
- package/src/internal/drop-down.tsx +41 -20
- package/src/internal/expansion.ts +95 -70
- package/src/internal/keys.ts +18 -0
- package/src/internal/selection.tsx +100 -51
- package/src/internal/slots.ts +67 -0
- package/src/internal/tree-expansion.ts +115 -0
- package/src/internal/tree-order.ts +208 -0
- package/src/internal/use-collection.tsx +5 -5
- package/src/list-view.tsx +14 -10
- package/src/types.ts +78 -20
package/src/internal/cells.tsx
CHANGED
|
@@ -1,26 +1,39 @@
|
|
|
1
1
|
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
-
import { GtkTreeExpander } from "@gtkx/jsx/gtk";
|
|
4
|
+
import { GtkSignalListItemFactory, GtkTreeExpander } from "@gtkx/jsx/gtk";
|
|
5
5
|
import { createPortal, useProperty } from "@gtkx/react";
|
|
6
|
+
import { setObjectProperty, t } from "@gtkx/runtime";
|
|
6
7
|
import { memo, useLayoutEffect, useState, useSyncExternalStore } from "react";
|
|
7
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
ExpanderDescriptions,
|
|
10
|
+
ListItem,
|
|
11
|
+
ListItemRenderArgs,
|
|
12
|
+
ListItemRenderer,
|
|
13
|
+
ListRowProps,
|
|
14
|
+
ListRowPropsResolver,
|
|
15
|
+
ListSectionRenderer,
|
|
16
|
+
} from "../types.js";
|
|
8
17
|
import type { Collection } from "./collection.js";
|
|
9
|
-
import {
|
|
18
|
+
import { slotRefFor } from "./collection-model.js";
|
|
10
19
|
|
|
11
20
|
type CellSize = {
|
|
12
21
|
width: number;
|
|
13
22
|
height: number;
|
|
14
23
|
};
|
|
15
24
|
|
|
16
|
-
type
|
|
25
|
+
type FactoryHost = GObject.Object & {
|
|
26
|
+
getItem: () => GObject.Object | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type CellHost = FactoryHost & {
|
|
17
30
|
getChild: () => Gtk.Widget | null;
|
|
18
31
|
setChild: (child: Gtk.Widget | null) => void;
|
|
19
32
|
};
|
|
20
33
|
|
|
21
|
-
type CellEntry<
|
|
34
|
+
type CellEntry<H extends FactoryHost> = {
|
|
22
35
|
key: string;
|
|
23
|
-
|
|
36
|
+
host: H;
|
|
24
37
|
item: GObject.Object | null;
|
|
25
38
|
listeners: Set<() => void>;
|
|
26
39
|
subscribe: (onChange: () => void) => () => void;
|
|
@@ -28,51 +41,88 @@ type CellEntry<C extends CellHost> = {
|
|
|
28
41
|
};
|
|
29
42
|
|
|
30
43
|
type FactoryHandlers = {
|
|
31
|
-
onSetup: (
|
|
32
|
-
onBind: (
|
|
33
|
-
onUnbind: (
|
|
34
|
-
onTeardown: (
|
|
44
|
+
onSetup: (host: GObject.Object) => void;
|
|
45
|
+
onBind: (host: GObject.Object) => void;
|
|
46
|
+
onUnbind: (host: GObject.Object) => void;
|
|
47
|
+
onTeardown: (host: GObject.Object) => void;
|
|
35
48
|
};
|
|
36
49
|
|
|
37
|
-
type
|
|
50
|
+
type CellRegistry<H extends FactoryHost> = {
|
|
38
51
|
handlers: FactoryHandlers;
|
|
39
52
|
setSize: (size: CellSize) => void;
|
|
40
53
|
subscribe: (onChange: () => void) => () => void;
|
|
41
|
-
|
|
54
|
+
getVersion: () => number;
|
|
55
|
+
getEntries: () => CellEntry<H>[];
|
|
42
56
|
};
|
|
43
57
|
|
|
44
|
-
type
|
|
58
|
+
type HostGuard<H extends FactoryHost> = (value: GObject.Object) => value is H;
|
|
59
|
+
|
|
60
|
+
type CellRegistryOptions<H extends FactoryHost> = {
|
|
61
|
+
isHost: HostGuard<H>;
|
|
62
|
+
prepare?: ((host: H, size: CellSize) => void) | undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type CellRegistryState<H extends FactoryHost> = CellRegistryOptions<H> & {
|
|
45
66
|
size: CellSize;
|
|
46
|
-
entries: Map<
|
|
47
|
-
snapshot: CellEntry<
|
|
67
|
+
entries: Map<H, CellEntry<H>>;
|
|
68
|
+
snapshot: CellEntry<H>[] | null;
|
|
48
69
|
listeners: Set<() => void>;
|
|
49
70
|
serial: number;
|
|
71
|
+
version: number;
|
|
50
72
|
};
|
|
51
73
|
|
|
52
|
-
type
|
|
74
|
+
type ResolvedRowProps = {
|
|
75
|
+
accessibleLabel: string | null;
|
|
76
|
+
accessibleDescription: string | null;
|
|
77
|
+
isActivatable: boolean;
|
|
78
|
+
isFocusable: boolean;
|
|
79
|
+
isSelectable: boolean;
|
|
80
|
+
};
|
|
53
81
|
|
|
54
|
-
type
|
|
82
|
+
type ItemSlotOptions = {
|
|
55
83
|
item: GObject.Object | null;
|
|
56
84
|
position: number | undefined;
|
|
57
85
|
row: Gtk.TreeListRow | null;
|
|
58
|
-
isRowExpanded: boolean | undefined;
|
|
59
|
-
render: ListItemRenderer<never>;
|
|
60
86
|
collection: Collection;
|
|
61
87
|
expandedIds: string[] | null | undefined;
|
|
62
88
|
};
|
|
63
89
|
|
|
90
|
+
type PositionedHost = Gtk.ListItem | Gtk.ColumnViewRow;
|
|
91
|
+
|
|
92
|
+
type ItemSlot = {
|
|
93
|
+
item: ListItem;
|
|
94
|
+
row: Gtk.TreeListRow | null;
|
|
95
|
+
args: ListItemRenderArgs<unknown>;
|
|
96
|
+
};
|
|
97
|
+
|
|
64
98
|
type ItemCellProps = {
|
|
65
99
|
entry: CellEntry<Gtk.ListItem>;
|
|
66
100
|
render: ListItemRenderer<never>;
|
|
67
101
|
collection: Collection;
|
|
68
102
|
expandedIds: string[] | null | undefined;
|
|
103
|
+
expanderDescriptions: ExpanderDescriptions | null | undefined;
|
|
69
104
|
};
|
|
70
105
|
|
|
71
106
|
type ItemPortalsProps = {
|
|
72
|
-
|
|
107
|
+
registry: CellRegistry<Gtk.ListItem>;
|
|
73
108
|
render: ListItemRenderer<never>;
|
|
74
109
|
collection: Collection;
|
|
75
110
|
expandedIds?: string[] | null | undefined;
|
|
111
|
+
expanderDescriptions?: ExpanderDescriptions | null | undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type ItemRowProps = {
|
|
115
|
+
entry: CellEntry<Gtk.ColumnViewRow>;
|
|
116
|
+
rowProps: ListRowPropsResolver<never>;
|
|
117
|
+
collection: Collection;
|
|
118
|
+
expandedIds: string[] | null | undefined;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type RowPortalsProps = {
|
|
122
|
+
registry: CellRegistry<Gtk.ColumnViewRow>;
|
|
123
|
+
rowProps: ListRowPropsResolver<never>;
|
|
124
|
+
collection: Collection;
|
|
125
|
+
expandedIds: string[] | null | undefined;
|
|
76
126
|
};
|
|
77
127
|
|
|
78
128
|
type HeaderCellProps = {
|
|
@@ -82,40 +132,73 @@ type HeaderCellProps = {
|
|
|
82
132
|
};
|
|
83
133
|
|
|
84
134
|
type HeaderPortalsProps = {
|
|
85
|
-
|
|
135
|
+
registry: CellRegistry<Gtk.ListHeader>;
|
|
86
136
|
render: ListSectionRenderer<never>;
|
|
87
137
|
collection: Collection;
|
|
88
138
|
};
|
|
89
139
|
|
|
140
|
+
type SectionHeaderSlot = {
|
|
141
|
+
factoryProps: { headerFactory?: ReactElement };
|
|
142
|
+
portals: ReactNode;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type RowSlot = {
|
|
146
|
+
factoryProps: { rowFactory?: ReactElement };
|
|
147
|
+
portals: ReactNode;
|
|
148
|
+
};
|
|
149
|
+
|
|
90
150
|
const ItemCell = memo(ItemCellImpl);
|
|
151
|
+
const ItemRow = memo(ItemRowImpl);
|
|
91
152
|
const HeaderCell = memo(HeaderCellImpl);
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
153
|
+
const CELL_OPTIONS: CellRegistryOptions<Gtk.ListItem> = { isHost: isListItem, prepare: prepareCell };
|
|
154
|
+
const HEADER_OPTIONS: CellRegistryOptions<Gtk.ListHeader> = { isHost: isListHeader, prepare: prepareCell };
|
|
155
|
+
const ROW_OPTIONS: CellRegistryOptions<Gtk.ColumnViewRow> = { isHost: isColumnViewRow };
|
|
156
|
+
const NO_SIZE: CellSize = { width: -1, height: -1 };
|
|
157
|
+
const NO_ROW_PROPS: ListRowProps = {};
|
|
158
|
+
const ROW_TEXT_DESCRIPTOR = t.string("borrowed");
|
|
95
159
|
|
|
96
160
|
const placeholder = (size: CellSize): Gtk.Widget =>
|
|
97
161
|
new Gtk.Box({ widthRequest: size.width, heightRequest: size.height });
|
|
98
162
|
|
|
99
|
-
function
|
|
163
|
+
function isListItem(value: GObject.Object): value is Gtk.ListItem {
|
|
164
|
+
return value instanceof Gtk.ListItem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isListHeader(value: GObject.Object): value is Gtk.ListHeader {
|
|
168
|
+
return value instanceof Gtk.ListHeader;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isColumnViewRow(value: GObject.Object): value is Gtk.ColumnViewRow {
|
|
172
|
+
return value instanceof Gtk.ColumnViewRow;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function prepareCell(host: CellHost, size: CellSize): void {
|
|
176
|
+
if (host.getChild() === null) {
|
|
177
|
+
host.setChild(placeholder(size));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function notifyEntry<H extends FactoryHost>(entry: CellEntry<H>): void {
|
|
100
182
|
for (const listener of entry.listeners) {
|
|
101
183
|
listener();
|
|
102
184
|
}
|
|
103
185
|
}
|
|
104
186
|
|
|
105
|
-
function
|
|
187
|
+
function notifyRegistry<H extends FactoryHost>(state: CellRegistryState<H>): void {
|
|
106
188
|
state.snapshot = null;
|
|
189
|
+
state.version += 1;
|
|
107
190
|
|
|
108
191
|
for (const listener of state.listeners) {
|
|
109
192
|
listener();
|
|
110
193
|
}
|
|
111
194
|
}
|
|
112
195
|
|
|
113
|
-
function createEntry<
|
|
196
|
+
function createEntry<H extends FactoryHost>(state: CellRegistryState<H>, host: H): CellEntry<H> {
|
|
114
197
|
state.serial += 1;
|
|
115
198
|
|
|
116
|
-
const entry: CellEntry<
|
|
199
|
+
const entry: CellEntry<H> = {
|
|
117
200
|
key: `gtkx-cell-${String(state.serial)}`,
|
|
118
|
-
|
|
201
|
+
host,
|
|
119
202
|
item: null,
|
|
120
203
|
listeners: new Set(),
|
|
121
204
|
subscribe: (onChange) => {
|
|
@@ -131,20 +214,20 @@ function createEntry<C extends CellHost>(state: StoreState<C>, cell: C): CellEnt
|
|
|
131
214
|
return entry;
|
|
132
215
|
}
|
|
133
216
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
state.entries.set(
|
|
137
|
-
|
|
217
|
+
function setupHost<H extends FactoryHost>(state: CellRegistryState<H>, host: H): void {
|
|
218
|
+
state.prepare?.(host, state.size);
|
|
219
|
+
state.entries.set(host, createEntry(state, host));
|
|
220
|
+
notifyRegistry(state);
|
|
138
221
|
}
|
|
139
222
|
|
|
140
|
-
function
|
|
141
|
-
if (state.entries.delete(
|
|
142
|
-
|
|
223
|
+
function teardownHost<H extends FactoryHost>(state: CellRegistryState<H>, host: H): void {
|
|
224
|
+
if (state.entries.delete(host)) {
|
|
225
|
+
notifyRegistry(state);
|
|
143
226
|
}
|
|
144
227
|
}
|
|
145
228
|
|
|
146
|
-
function writeBinding<
|
|
147
|
-
const entry = state.entries.get(
|
|
229
|
+
function writeBinding<H extends FactoryHost>(state: CellRegistryState<H>, host: H, item: GObject.Object | null): void {
|
|
230
|
+
const entry = state.entries.get(host);
|
|
148
231
|
|
|
149
232
|
if (entry === undefined) {
|
|
150
233
|
return;
|
|
@@ -154,23 +237,12 @@ function writeBinding<C extends CellHost>(state: StoreState<C>, cell: C, item: G
|
|
|
154
237
|
notifyEntry(entry);
|
|
155
238
|
}
|
|
156
239
|
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return cell instanceof Gtk.ListHeader ? cell.getItem() : null;
|
|
240
|
+
function bindHost<H extends FactoryHost>(state: CellRegistryState<H>, host: H): void {
|
|
241
|
+
state.prepare?.(host, state.size);
|
|
242
|
+
writeBinding(state, host, host.getItem());
|
|
163
243
|
}
|
|
164
244
|
|
|
165
|
-
function
|
|
166
|
-
if (cell.getChild() === null) {
|
|
167
|
-
cell.setChild(placeholder(state.size));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
writeBinding(state, cell, boundItem(cell));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function subscribeRoster<C extends CellHost>(state: StoreState<C>, onChange: () => void): () => void {
|
|
245
|
+
function subscribeRegistry<H extends FactoryHost>(state: CellRegistryState<H>, onChange: () => void): () => void {
|
|
174
246
|
state.listeners.add(onChange);
|
|
175
247
|
|
|
176
248
|
return () => {
|
|
@@ -178,81 +250,84 @@ function subscribeRoster<C extends CellHost>(state: StoreState<C>, onChange: ()
|
|
|
178
250
|
};
|
|
179
251
|
}
|
|
180
252
|
|
|
181
|
-
function
|
|
253
|
+
function getRegistryEntries<H extends FactoryHost>(state: CellRegistryState<H>): CellEntry<H>[] {
|
|
182
254
|
state.snapshot ??= state.entries.values().toArray();
|
|
183
255
|
|
|
184
256
|
return state.snapshot;
|
|
185
257
|
}
|
|
186
258
|
|
|
187
|
-
function guarded<
|
|
188
|
-
return (
|
|
189
|
-
if (
|
|
190
|
-
run(
|
|
259
|
+
function guarded<H extends FactoryHost>(isHost: HostGuard<H>, run: (host: H) => void): (host: GObject.Object) => void {
|
|
260
|
+
return (host) => {
|
|
261
|
+
if (isHost(host)) {
|
|
262
|
+
run(host);
|
|
191
263
|
}
|
|
192
264
|
};
|
|
193
265
|
}
|
|
194
266
|
|
|
195
|
-
function createHandlers<
|
|
267
|
+
function createHandlers<H extends FactoryHost>(state: CellRegistryState<H>): FactoryHandlers {
|
|
268
|
+
const { isHost } = state;
|
|
269
|
+
|
|
196
270
|
return {
|
|
197
|
-
onSetup: guarded(
|
|
198
|
-
|
|
271
|
+
onSetup: guarded(isHost, (host) => {
|
|
272
|
+
setupHost(state, host);
|
|
199
273
|
}),
|
|
200
|
-
onBind: guarded(
|
|
201
|
-
|
|
274
|
+
onBind: guarded(isHost, (host) => {
|
|
275
|
+
bindHost(state, host);
|
|
202
276
|
}),
|
|
203
|
-
onUnbind: guarded(
|
|
204
|
-
writeBinding(state,
|
|
277
|
+
onUnbind: guarded(isHost, (host) => {
|
|
278
|
+
writeBinding(state, host, null);
|
|
205
279
|
}),
|
|
206
|
-
onTeardown: guarded(
|
|
207
|
-
|
|
280
|
+
onTeardown: guarded(isHost, (host) => {
|
|
281
|
+
teardownHost(state, host);
|
|
208
282
|
}),
|
|
209
283
|
};
|
|
210
284
|
}
|
|
211
285
|
|
|
212
|
-
function
|
|
213
|
-
const state:
|
|
286
|
+
function createCellRegistry<H extends FactoryHost>(options: CellRegistryOptions<H>, size: CellSize): CellRegistry<H> {
|
|
287
|
+
const state: CellRegistryState<H> = {
|
|
288
|
+
...options,
|
|
214
289
|
size,
|
|
215
290
|
entries: new Map(),
|
|
216
291
|
snapshot: null,
|
|
217
292
|
listeners: new Set(),
|
|
218
293
|
serial: 0,
|
|
294
|
+
version: 0,
|
|
219
295
|
};
|
|
220
296
|
|
|
221
297
|
return {
|
|
222
|
-
handlers: createHandlers(state
|
|
298
|
+
handlers: createHandlers(state),
|
|
223
299
|
setSize: (next) => {
|
|
224
300
|
state.size = next;
|
|
225
301
|
},
|
|
226
|
-
subscribe: (onChange) =>
|
|
227
|
-
|
|
302
|
+
subscribe: (onChange) => subscribeRegistry(state, onChange),
|
|
303
|
+
getVersion: () => state.version,
|
|
304
|
+
getEntries: () => getRegistryEntries(state),
|
|
228
305
|
};
|
|
229
306
|
}
|
|
230
307
|
|
|
231
|
-
function
|
|
232
|
-
const [
|
|
308
|
+
function useCellRegistry<H extends FactoryHost>(options: CellRegistryOptions<H>, size: CellSize): CellRegistry<H> {
|
|
309
|
+
const [registry] = useState(() => createCellRegistry(options, size));
|
|
233
310
|
|
|
234
311
|
useLayoutEffect(() => {
|
|
235
|
-
|
|
312
|
+
registry.setSize(size);
|
|
236
313
|
});
|
|
237
314
|
|
|
238
|
-
return
|
|
315
|
+
return registry;
|
|
239
316
|
}
|
|
240
317
|
|
|
241
|
-
function useItemCells(size: CellSize):
|
|
242
|
-
return
|
|
318
|
+
function useItemCells(size: CellSize): CellRegistry<Gtk.ListItem> {
|
|
319
|
+
return useCellRegistry(CELL_OPTIONS, size);
|
|
243
320
|
}
|
|
244
321
|
|
|
245
|
-
function useHeaderCells(size: CellSize):
|
|
246
|
-
return
|
|
322
|
+
function useHeaderCells(size: CellSize): CellRegistry<Gtk.ListHeader> {
|
|
323
|
+
return useCellRegistry(HEADER_OPTIONS, size);
|
|
247
324
|
}
|
|
248
325
|
|
|
249
|
-
function isRowWanted(options:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return expandedIds == null ? options.isRowExpanded === true : expandedIds.includes(item.id);
|
|
326
|
+
function isRowWanted(options: ItemSlotOptions, item: ListItem): boolean {
|
|
327
|
+
return options.expandedIds?.includes(item.id) ?? false;
|
|
253
328
|
}
|
|
254
329
|
|
|
255
|
-
function itemArgs(item: ListItem, options:
|
|
330
|
+
function itemArgs(item: ListItem, options: ItemSlotOptions): ListItemRenderArgs<unknown> {
|
|
256
331
|
const args: ListItemRenderArgs<unknown> = { item: item.value, index: options.position ?? 0 };
|
|
257
332
|
const row = options.row;
|
|
258
333
|
|
|
@@ -269,7 +344,49 @@ function itemArgs(item: ListItem, options: ItemBodyOptions): ListItemRenderArgs<
|
|
|
269
344
|
return args;
|
|
270
345
|
}
|
|
271
346
|
|
|
272
|
-
function
|
|
347
|
+
function slotFor(options: ItemSlotOptions): ItemSlot | null {
|
|
348
|
+
const ref = slotRefFor(options.item);
|
|
349
|
+
const item = ref === null ? undefined : options.collection.itemAt(ref);
|
|
350
|
+
|
|
351
|
+
if (item === undefined) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return { item, row: options.row, args: itemArgs(item, options) };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function useItemSlot(
|
|
359
|
+
entry: CellEntry<PositionedHost>,
|
|
360
|
+
collection: Collection,
|
|
361
|
+
expandedIds: string[] | null | undefined,
|
|
362
|
+
): ItemSlot | null {
|
|
363
|
+
const position = useProperty(entry.host, "position");
|
|
364
|
+
const item = useSyncExternalStore(entry.subscribe, entry.getItem);
|
|
365
|
+
const row = item instanceof Gtk.TreeListRow ? item : null;
|
|
366
|
+
|
|
367
|
+
return slotFor({ item, position, row, collection, expandedIds });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function expanderDescriptionFor(
|
|
371
|
+
slot: ItemSlot,
|
|
372
|
+
descriptions: ExpanderDescriptions | null | undefined,
|
|
373
|
+
): string | undefined {
|
|
374
|
+
const { isExpanded } = slot.args;
|
|
375
|
+
|
|
376
|
+
if (isExpanded === undefined || descriptions == null) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return isExpanded ? descriptions.collapse : descriptions.expand;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function wrapExpander(
|
|
384
|
+
slot: ItemSlot,
|
|
385
|
+
content: ReactNode,
|
|
386
|
+
descriptions: ExpanderDescriptions | null | undefined,
|
|
387
|
+
): ReactNode {
|
|
388
|
+
const { item, row } = slot;
|
|
389
|
+
|
|
273
390
|
const expander: ReactNode =
|
|
274
391
|
row === null
|
|
275
392
|
? (
|
|
@@ -278,9 +395,10 @@ function wrapExpander(item: ListItem, row: Gtk.TreeListRow | null, content: Reac
|
|
|
278
395
|
: (
|
|
279
396
|
<GtkTreeExpander
|
|
280
397
|
listRow={row}
|
|
281
|
-
hideExpander={item.
|
|
282
|
-
indentForDepth={item.
|
|
283
|
-
indentForIcon={item.
|
|
398
|
+
hideExpander={item.shouldHideExpander ?? false}
|
|
399
|
+
indentForDepth={item.shouldIndentForDepth ?? true}
|
|
400
|
+
indentForIcon={item.shouldIndentForIcon ?? true}
|
|
401
|
+
accessibleDescription={expanderDescriptionFor(slot, descriptions)}
|
|
284
402
|
>
|
|
285
403
|
{content}
|
|
286
404
|
</GtkTreeExpander>
|
|
@@ -289,57 +407,161 @@ function wrapExpander(item: ListItem, row: Gtk.TreeListRow | null, content: Reac
|
|
|
289
407
|
return expander;
|
|
290
408
|
}
|
|
291
409
|
|
|
292
|
-
function itemBody(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
410
|
+
function itemBody(
|
|
411
|
+
slot: ItemSlot | null,
|
|
412
|
+
render: ListItemRenderer<never>,
|
|
413
|
+
descriptions: ExpanderDescriptions | null | undefined,
|
|
414
|
+
): ReactNode {
|
|
415
|
+
if (slot === null) {
|
|
297
416
|
return null;
|
|
298
417
|
}
|
|
299
418
|
|
|
300
|
-
const
|
|
419
|
+
const renderItem = render as ListItemRenderer<unknown>;
|
|
301
420
|
|
|
302
|
-
return wrapExpander(
|
|
421
|
+
return wrapExpander(slot, renderItem(slot.args), descriptions);
|
|
303
422
|
}
|
|
304
423
|
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
424
|
+
function rowText(value: string | undefined): string | null {
|
|
425
|
+
return value ?? null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* eslint-disable-next-line unicorn/consistent-boolean-name -- reads the flag off a row, paired with rowText */
|
|
429
|
+
function rowFlag(value: boolean | undefined): boolean {
|
|
430
|
+
return value ?? true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function rowPropsFor(slot: ItemSlot | null, rowProps: ListRowPropsResolver<never>): ResolvedRowProps {
|
|
434
|
+
const resolve = rowProps as ListRowPropsResolver<unknown>;
|
|
435
|
+
const props = slot === null ? NO_ROW_PROPS : resolve(slot.args);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
accessibleLabel: rowText(props.accessibleLabel),
|
|
439
|
+
accessibleDescription: rowText(props.accessibleDescription),
|
|
440
|
+
isActivatable: rowFlag(props.isActivatable),
|
|
441
|
+
isFocusable: rowFlag(props.isFocusable),
|
|
442
|
+
isSelectable: rowFlag(props.isSelectable),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function setRowText(host: Gtk.ColumnViewRow, name: string, value: string | null): void {
|
|
447
|
+
setObjectProperty(host, name, ROW_TEXT_DESCRIPTOR, value);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function applyRowProps(host: Gtk.ColumnViewRow, props: ResolvedRowProps): void {
|
|
451
|
+
setRowText(host, "accessible-label", props.accessibleLabel);
|
|
452
|
+
setRowText(host, "accessible-description", props.accessibleDescription);
|
|
453
|
+
host.setActivatable(props.isActivatable);
|
|
454
|
+
host.setFocusable(props.isFocusable);
|
|
455
|
+
host.setSelectable(props.isSelectable);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function ItemCellImpl({ entry, render, collection, expandedIds, expanderDescriptions }: ItemCellProps): ReactNode {
|
|
459
|
+
const slot = useItemSlot(entry, collection, expandedIds);
|
|
460
|
+
const body = itemBody(slot, render, expanderDescriptions);
|
|
311
461
|
|
|
312
|
-
return createPortal(body, entry.
|
|
462
|
+
return createPortal(body, entry.host, entry.key);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function ItemRowImpl({ entry, rowProps, collection, expandedIds }: ItemRowProps): ReactNode {
|
|
466
|
+
const slot = useItemSlot(entry, collection, expandedIds);
|
|
467
|
+
const props = rowPropsFor(slot, rowProps);
|
|
468
|
+
|
|
469
|
+
useLayoutEffect(() => {
|
|
470
|
+
applyRowProps(entry.host, props);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return null;
|
|
313
474
|
}
|
|
314
475
|
|
|
315
476
|
function HeaderCellImpl({ entry, render, collection }: HeaderCellProps): ReactNode {
|
|
316
477
|
const item = useSyncExternalStore(entry.subscribe, entry.getItem);
|
|
317
|
-
const
|
|
478
|
+
const ref = slotRefFor(item);
|
|
318
479
|
const renderHeader = render as ListSectionRenderer<unknown>;
|
|
319
|
-
const body =
|
|
480
|
+
const body = ref === null ? null : renderHeader({ section: collection.sectionFor(ref.store.level.path) });
|
|
320
481
|
|
|
321
|
-
return createPortal(body, entry.
|
|
482
|
+
return createPortal(body, entry.host, entry.key);
|
|
322
483
|
}
|
|
323
484
|
|
|
324
|
-
function usePortalEntries<
|
|
325
|
-
|
|
485
|
+
function usePortalEntries<H extends FactoryHost>(registry: CellRegistry<H>): CellEntry<H>[] {
|
|
486
|
+
useSyncExternalStore(registry.subscribe, registry.getVersion);
|
|
487
|
+
|
|
488
|
+
return registry.getEntries();
|
|
326
489
|
}
|
|
327
490
|
|
|
328
|
-
const ItemPortals = ({
|
|
329
|
-
|
|
330
|
-
|
|
491
|
+
const ItemPortals = ({
|
|
492
|
+
registry,
|
|
493
|
+
render,
|
|
494
|
+
collection,
|
|
495
|
+
expandedIds,
|
|
496
|
+
expanderDescriptions,
|
|
497
|
+
}: ItemPortalsProps): ReactNode =>
|
|
498
|
+
usePortalEntries(registry).map((entry) => (
|
|
499
|
+
<ItemCell
|
|
500
|
+
key={entry.key}
|
|
501
|
+
entry={entry}
|
|
502
|
+
render={render}
|
|
503
|
+
collection={collection}
|
|
504
|
+
expandedIds={expandedIds}
|
|
505
|
+
expanderDescriptions={expanderDescriptions}
|
|
506
|
+
/>
|
|
507
|
+
));
|
|
508
|
+
|
|
509
|
+
const RowPortals = ({ registry, rowProps, collection, expandedIds }: RowPortalsProps): ReactNode =>
|
|
510
|
+
usePortalEntries(registry).map((entry) => (
|
|
511
|
+
<ItemRow
|
|
512
|
+
key={entry.key}
|
|
513
|
+
entry={entry}
|
|
514
|
+
rowProps={rowProps}
|
|
515
|
+
collection={collection}
|
|
516
|
+
expandedIds={expandedIds}
|
|
517
|
+
/>
|
|
331
518
|
));
|
|
332
519
|
|
|
333
|
-
const HeaderPortals = ({
|
|
334
|
-
usePortalEntries(
|
|
520
|
+
const HeaderPortals = ({ registry, render, collection }: HeaderPortalsProps): ReactNode =>
|
|
521
|
+
usePortalEntries(registry).map((entry) => (
|
|
335
522
|
<HeaderCell key={entry.key} entry={entry} render={render} collection={collection} />
|
|
336
523
|
));
|
|
337
524
|
|
|
525
|
+
const useSectionHeader = (
|
|
526
|
+
render: ListSectionRenderer<never> | null | undefined,
|
|
527
|
+
collection: Collection,
|
|
528
|
+
size: CellSize,
|
|
529
|
+
): SectionHeaderSlot => {
|
|
530
|
+
const registry = useHeaderCells(size);
|
|
531
|
+
|
|
532
|
+
if (render == null) {
|
|
533
|
+
return { factoryProps: {}, portals: null };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
factoryProps: { headerFactory: <GtkSignalListItemFactory {...registry.handlers} /> },
|
|
538
|
+
portals: <HeaderPortals registry={registry} render={render} collection={collection} />,
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const useRowProps = (
|
|
543
|
+
rowProps: ListRowPropsResolver<never> | null | undefined,
|
|
544
|
+
collection: Collection,
|
|
545
|
+
expandedIds: string[] | null | undefined,
|
|
546
|
+
): RowSlot => {
|
|
547
|
+
const registry = useCellRegistry(ROW_OPTIONS, NO_SIZE);
|
|
548
|
+
|
|
549
|
+
if (rowProps == null) {
|
|
550
|
+
return { factoryProps: {}, portals: null };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
factoryProps: { rowFactory: <GtkSignalListItemFactory {...registry.handlers} /> },
|
|
555
|
+
portals: (
|
|
556
|
+
<RowPortals registry={registry} rowProps={rowProps} collection={collection} expandedIds={expandedIds} />
|
|
557
|
+
),
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
|
|
338
561
|
export {
|
|
339
562
|
useItemCells,
|
|
340
|
-
|
|
563
|
+
useRowProps,
|
|
564
|
+
useSectionHeader,
|
|
341
565
|
ItemPortals,
|
|
342
|
-
HeaderPortals,
|
|
343
566
|
type CellSize,
|
|
344
|
-
type CellStore,
|
|
345
567
|
};
|