@gtkx/components 0.21.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +173 -0
- package/dist/adw/index.d.ts +3 -0
- package/dist/adw/index.d.ts.map +1 -0
- package/dist/adw/index.js +2 -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 +28 -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 +6 -14
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +98 -100
- package/dist/column-view.js.map +1 -1
- package/dist/drop-down.d.ts +8 -37
- package/dist/drop-down.d.ts.map +1 -1
- package/dist/drop-down.js +109 -118
- package/dist/drop-down.js.map +1 -1
- package/dist/grid-view.d.ts +4 -11
- package/dist/grid-view.d.ts.map +1 -1
- package/dist/grid-view.js +25 -19
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +6 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -14
- 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/expansion.d.ts +9 -0
- package/dist/internal/expansion.d.ts.map +1 -0
- package/dist/internal/expansion.js +75 -0
- package/dist/internal/expansion.js.map +1 -0
- package/dist/internal/selection.d.ts +12 -0
- package/dist/internal/selection.d.ts.map +1 -0
- package/dist/internal/selection.js +99 -0
- package/dist/internal/selection.js.map +1 -0
- package/dist/internal/use-collection.d.ts +18 -0
- package/dist/internal/use-collection.d.ts.map +1 -0
- package/dist/internal/use-collection.js +42 -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 +4 -11
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +32 -28
- package/dist/list-view.js.map +1 -1
- package/dist/size-group.d.ts +12 -0
- package/dist/size-group.d.ts.map +1 -0
- package/dist/size-group.js +41 -0
- package/dist/size-group.js.map +1 -0
- package/dist/types.d.ts +104 -161
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +62 -52
- package/src/adw/index.ts +2 -0
- package/src/adw/toast.tsx +74 -0
- package/src/adw/types.ts +32 -0
- package/src/column-view.tsx +184 -184
- package/src/drop-down.tsx +173 -227
- package/src/grid-view.tsx +29 -65
- package/src/index.ts +16 -25
- 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/expansion.ts +123 -0
- package/src/internal/selection.tsx +159 -0
- package/src/internal/use-collection.tsx +70 -0
- package/src/internal/use-widget-ref.ts +11 -0
- package/src/list-view.tsx +45 -78
- package/src/size-group.tsx +77 -0
- package/src/types.ts +171 -191
- 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/column-view-column.d.ts +0 -17
- package/dist/column-view-column.d.ts.map +0 -1
- package/dist/column-view-column.js +0 -51
- package/dist/column-view-column.js.map +0 -1
- package/dist/constraint-layout-apply.d.ts +0 -6
- 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 -13
- package/dist/constraint-layout.d.ts.map +0 -1
- package/dist/constraint-layout.js +0 -85
- package/dist/constraint-layout.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-drop-down-selection.d.ts +0 -15
- 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-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 -32
- package/dist/hooks/use-header-menu.js.map +0 -1
- package/dist/hooks/use-list-model.d.ts +0 -20
- package/dist/hooks/use-list-model.d.ts.map +0 -1
- package/dist/hooks/use-list-model.js +0 -70
- package/dist/hooks/use-list-model.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 +0 -66
- package/dist/hooks/use-realized-slots.js.map +0 -1
- package/dist/hooks/use-selection-model.d.ts +0 -12
- package/dist/hooks/use-selection-model.d.ts.map +0 -1
- package/dist/hooks/use-selection-model.js +0 -111
- package/dist/hooks/use-selection-model.js.map +0 -1
- package/dist/hooks/use-sort-handler.d.ts +0 -12
- 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/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/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 -18
- package/dist/menu.d.ts.map +0 -1
- package/dist/menu.js +0 -26
- package/dist/menu.js.map +0 -1
- package/dist/utils/item-resolver.d.ts +0 -28
- package/dist/utils/item-resolver.d.ts.map +0 -1
- package/dist/utils/item-resolver.js +0 -111
- package/dist/utils/item-resolver.js.map +0 -1
- package/dist/utils/list-item-flatten.d.ts +0 -32
- package/dist/utils/list-item-flatten.d.ts.map +0 -1
- package/dist/utils/list-item-flatten.js +0 -88
- package/dist/utils/list-item-flatten.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 +0 -83
- package/dist/utils/position-only-model.js.map +0 -1
- package/dist/utils/realized-slot-store.d.ts +0 -30
- package/dist/utils/realized-slot-store.d.ts.map +0 -1
- package/dist/utils/realized-slot-store.js +0 -103
- package/dist/utils/realized-slot-store.js.map +0 -1
- package/src/collection-view.tsx +0 -125
- package/src/column-view-column.tsx +0 -73
- package/src/constraint-layout-apply.ts +0 -121
- package/src/constraint-layout.tsx +0 -130
- package/src/contexts/column-view-context.ts +0 -28
- package/src/hooks/use-drop-down-selection.ts +0 -53
- package/src/hooks/use-header-menu.ts +0 -33
- package/src/hooks/use-list-model.ts +0 -145
- package/src/hooks/use-model-installation.ts +0 -25
- package/src/hooks/use-realized-slots.ts +0 -93
- package/src/hooks/use-selection-model.ts +0 -121
- package/src/hooks/use-sort-handler.ts +0 -102
- package/src/list-portal-host.tsx +0 -27
- package/src/list-slot.tsx +0 -56
- package/src/menu-items.ts +0 -48
- package/src/menu.tsx +0 -34
- package/src/utils/item-resolver.ts +0 -143
- package/src/utils/list-item-flatten.ts +0 -113
- package/src/utils/position-only-model.ts +0 -115
- package/src/utils/realized-slot-store.ts +0 -126
package/package.json
CHANGED
|
@@ -1,55 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
"name": "@gtkx/components",
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
|
+
"description": "High-level GTKX components for declarative collection views, menus, and containers with attach-time placement",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"gtkx",
|
|
7
|
+
"gtk",
|
|
8
|
+
"gtk4",
|
|
9
|
+
"adwaita",
|
|
10
|
+
"react",
|
|
11
|
+
"components",
|
|
12
|
+
"linux",
|
|
13
|
+
"desktop"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://gtkx.dev",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/gtkx-org/gtkx/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/gtkx-org/gtkx.git",
|
|
22
|
+
"directory": "packages/components"
|
|
23
|
+
},
|
|
24
|
+
"license": "MPL-2.0",
|
|
25
|
+
"author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
18
32
|
},
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"directory": "packages/components"
|
|
23
|
-
},
|
|
24
|
-
"license": "MPL-2.0",
|
|
25
|
-
"author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
|
|
26
|
-
"type": "module",
|
|
27
|
-
"exports": {
|
|
28
|
-
"./package.json": "./package.json",
|
|
29
|
-
".": {
|
|
30
|
-
"source": "./src/index.ts",
|
|
31
|
-
"types": "./dist/index.d.ts",
|
|
32
|
-
"default": "./dist/index.js"
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"sideEffects": false,
|
|
36
|
-
"files": [
|
|
37
|
-
"dist",
|
|
38
|
-
"src"
|
|
39
|
-
],
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsc -b"
|
|
42
|
-
},
|
|
43
|
-
"engines": {
|
|
44
|
-
"node": ">=24"
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@gtkx/react": "workspace:*"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@gtkx/vitest": "workspace:*"
|
|
51
|
-
},
|
|
52
|
-
"peerDependencies": {
|
|
53
|
-
"react": "^19"
|
|
33
|
+
"./adw": {
|
|
34
|
+
"types": "./dist/adw/index.d.ts",
|
|
35
|
+
"default": "./dist/adw/index.js"
|
|
54
36
|
}
|
|
55
|
-
}
|
|
37
|
+
},
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"src"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=24"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@gtkx/react": "1.0.0-rc.2",
|
|
48
|
+
"@gtkx/utils": "1.0.0-rc.2"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": "^19.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@codspeed/vitest-plugin": "^5.7.1",
|
|
55
|
+
"vitest": "^4.1.10",
|
|
56
|
+
"@gtkx/config": "1.0.0-rc.2",
|
|
57
|
+
"@gtkx/runtime": "1.0.0-rc.2",
|
|
58
|
+
"@gtkx/testing": "1.0.0-rc.2",
|
|
59
|
+
"@gtkx/vitest": "1.0.0-rc.2"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"bench": "NODE_ENV=production vitest bench --run --config vitest.bench.config.ts",
|
|
63
|
+
"release": "tsx ../../scripts/release-package.ts"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/adw/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from "react";
|
|
2
|
+
import * as Adw from "@gtkx/gi/adw";
|
|
3
|
+
import { createContext, useContext, useMemo } from "react";
|
|
4
|
+
import type { ToastController, ToastOptions, ToastOverlayController, ToastProviderProps } from "./types.js";
|
|
5
|
+
|
|
6
|
+
type OverlayRef = RefObject<Adw.ToastOverlay | null>;
|
|
7
|
+
|
|
8
|
+
const ToastContext = createContext<OverlayRef | null>(null);
|
|
9
|
+
|
|
10
|
+
const useOverlayRef = (): OverlayRef => {
|
|
11
|
+
const overlayRef = useContext(ToastContext);
|
|
12
|
+
|
|
13
|
+
if (overlayRef === null) {
|
|
14
|
+
throw new Error("useToast and useToastOverlay must be used within a ToastProvider");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return overlayRef;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const buildToast = (options: ToastOptions): Adw.Toast => {
|
|
21
|
+
const { onButtonClicked, onDismissed, ...props } = options;
|
|
22
|
+
const toast = new Adw.Toast(props);
|
|
23
|
+
|
|
24
|
+
if (onButtonClicked != null) {
|
|
25
|
+
toast.on("button-clicked", onButtonClicked);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (onDismissed != null) {
|
|
29
|
+
toast.on("dismissed", onDismissed);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return toast;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Shares an Adw.ToastOverlay reference with the toast hooks in every descendant. */
|
|
36
|
+
function ToastProvider(props: ToastProviderProps): ReactNode {
|
|
37
|
+
return <ToastContext.Provider value={props.overlayRef}>{props.children}</ToastContext.Provider>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Returns a {@link ToastController} that shows and dismisses toasts on the nearest provider's overlay. */
|
|
41
|
+
function useToast(): ToastController {
|
|
42
|
+
const overlayRef = useOverlayRef();
|
|
43
|
+
|
|
44
|
+
return useMemo<ToastController>(
|
|
45
|
+
() => ({
|
|
46
|
+
show: (options?: ToastOptions) => {
|
|
47
|
+
const toast = buildToast(options ?? {});
|
|
48
|
+
overlayRef.current?.addToast(toast);
|
|
49
|
+
|
|
50
|
+
return toast;
|
|
51
|
+
},
|
|
52
|
+
dismiss: (toast: Adw.Toast) => {
|
|
53
|
+
toast.dismiss();
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
[overlayRef],
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Returns a {@link ToastOverlayController} for the nearest provider's overlay as a whole. */
|
|
61
|
+
function useToastOverlay(): ToastOverlayController {
|
|
62
|
+
const overlayRef = useOverlayRef();
|
|
63
|
+
|
|
64
|
+
return useMemo<ToastOverlayController>(
|
|
65
|
+
() => ({
|
|
66
|
+
dismissAll: () => {
|
|
67
|
+
overlayRef.current?.dismissAll();
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
[overlayRef],
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { ToastProvider, useToast, useToastOverlay };
|
package/src/adw/types.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type * as Adw from "@gtkx/gi/adw";
|
|
2
|
+
import type { AdwToastProps } from "@gtkx/jsx/adw";
|
|
3
|
+
import type { ReactNode, RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Describes a toast raised through {@link useToast}: the construct-time properties of an
|
|
7
|
+
* `Adw.Toast` plus its `button-clicked` and `dismissed` handlers.
|
|
8
|
+
*/
|
|
9
|
+
type ToastOptions = Adw.ToastConstructorProps & Pick<AdwToastProps, "onButtonClicked" | "onDismissed">;
|
|
10
|
+
|
|
11
|
+
/** Imperative controls for individual toasts, returned by {@link useToast}. */
|
|
12
|
+
type ToastController = {
|
|
13
|
+
/** Builds a toast, shows it through the overlay, and returns it. */
|
|
14
|
+
show: (options?: ToastOptions) => Adw.Toast;
|
|
15
|
+
/** Dismisses a single toast, typically one returned by {@link ToastController.show}. */
|
|
16
|
+
dismiss: (toast: Adw.Toast) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Imperative controls for the overlay as a whole, returned by {@link useToastOverlay}. */
|
|
20
|
+
type ToastOverlayController = {
|
|
21
|
+
/** Dismisses the shown toast and every queued one. */
|
|
22
|
+
dismissAll: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Props for {@link ToastProvider}. */
|
|
26
|
+
type ToastProviderProps = {
|
|
27
|
+
/** Ref also given to the `AdwToastOverlay` the toasts appear over. */
|
|
28
|
+
overlayRef: RefObject<Adw.ToastOverlay | null>;
|
|
29
|
+
children?: ReactNode | undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { type ToastOptions, type ToastController, type ToastOverlayController, type ToastProviderProps };
|
package/src/column-view.tsx
CHANGED
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import
|
|
3
|
-
import { GtkColumnView,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "./
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import type { ColumnViewProps } from "./types.js";
|
|
19
|
-
import type { ItemResolver } from "./utils/item-resolver.js";
|
|
20
|
-
|
|
21
|
-
const headerFactoryBinding: FactoryBinding<Gtk.ColumnView> = {
|
|
22
|
-
install: (widget, factory) => widget.setHeaderFactory(factory),
|
|
23
|
-
uninstall: (widget) => widget.setHeaderFactory(null),
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
+
import { GtkColumnView, GtkColumnViewColumn, GtkCustomSorter, GtkSignalListItemFactory } from "@gtkx/jsx/gtk";
|
|
4
|
+
import { useProperty, useSignal } from "@gtkx/react";
|
|
5
|
+
import { omit } from "@gtkx/utils";
|
|
6
|
+
import { useLayoutEffect, useRef } from "react";
|
|
7
|
+
import type { CellSize } from "./internal/cells.js";
|
|
8
|
+
import type { Collection } from "./internal/collection.js";
|
|
9
|
+
import type { Column, ColumnViewProps } from "./types.js";
|
|
10
|
+
import { HeaderPortals, ItemPortals, useHeaderCells, useItemCells } from "./internal/cells.js";
|
|
11
|
+
import { useCollection } from "./internal/use-collection.js";
|
|
12
|
+
import { useWidgetRef } from "./internal/use-widget-ref.js";
|
|
13
|
+
|
|
14
|
+
type SortProps = {
|
|
15
|
+
sortColumn?: string | null | undefined;
|
|
16
|
+
sortOrder?: Gtk.SortType | null | undefined;
|
|
17
|
+
onSortChanged?: ((column: string | null, order: Gtk.SortType) => void) | null | undefined;
|
|
24
18
|
};
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
columns: ColumnRegistration[];
|
|
28
|
-
register(registration: ColumnRegistration): void;
|
|
29
|
-
unregister(id: string): void;
|
|
30
|
-
}
|
|
20
|
+
type SortTarget = { view: Gtk.ColumnView; column: Gtk.ColumnViewColumn | null };
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
next.set(registration.id, registration);
|
|
38
|
-
return next;
|
|
39
|
-
});
|
|
40
|
-
}, []);
|
|
41
|
-
const unregister = useCallback((id: string): void => {
|
|
42
|
-
setRegistrations((current) => {
|
|
43
|
-
if (!current.has(id)) return current;
|
|
44
|
-
const next = new Map(current);
|
|
45
|
-
next.delete(id);
|
|
46
|
-
return next;
|
|
47
|
-
});
|
|
48
|
-
}, []);
|
|
49
|
-
const columns = useMemo(() => [...registrations.values()], [registrations]);
|
|
50
|
-
return { columns, register, unregister };
|
|
22
|
+
type ColumnCellsProps = {
|
|
23
|
+
column: Column<never>;
|
|
24
|
+
collection: Collection;
|
|
25
|
+
expandedIds: string[] | null | undefined;
|
|
26
|
+
size: CellSize;
|
|
51
27
|
};
|
|
52
28
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* surface (minus its imperative `columns` property) with its model/sort wiring
|
|
56
|
-
* replaced by the declarative {@link ColumnViewProps} API. Columns are declared
|
|
57
|
-
* as {@link ColumnViewColumn} children.
|
|
58
|
-
*/
|
|
59
|
-
export type ColumnViewComponentProps<T = unknown, S = unknown> = Omit<
|
|
60
|
-
GtkColumnViewProps,
|
|
61
|
-
"columns" | keyof ColumnViewProps<T, S>
|
|
62
|
-
> &
|
|
63
|
-
ColumnViewProps<T, S>;
|
|
64
|
-
|
|
65
|
-
type NormalizedColumnViewProps<T, S> = ColumnViewProps<T, S> & {
|
|
66
|
-
ref?: Ref<Gtk.ColumnView | null>;
|
|
67
|
-
renderHeader?: ((value: S) => ReactNode) | null;
|
|
68
|
-
children?: ReactNode;
|
|
69
|
-
[key: string]: unknown;
|
|
29
|
+
type ColumnListProps = Omit<ColumnCellsProps, "column"> & {
|
|
30
|
+
columns: Column<never>[];
|
|
70
31
|
};
|
|
71
32
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
33
|
+
const COLUMN_VIEW_PROPS = [
|
|
34
|
+
"items",
|
|
35
|
+
"sections",
|
|
36
|
+
"renderHeader",
|
|
37
|
+
"columns",
|
|
38
|
+
"selectedIds",
|
|
39
|
+
"onSelectionChanged",
|
|
40
|
+
"selectionMode",
|
|
41
|
+
"expandedIds",
|
|
42
|
+
"onExpandedChange",
|
|
43
|
+
"sortColumn",
|
|
44
|
+
"sortOrder",
|
|
45
|
+
"onSortChanged",
|
|
46
|
+
"estimatedItemHeight",
|
|
47
|
+
"children",
|
|
48
|
+
"ref",
|
|
49
|
+
] as const satisfies (keyof ColumnViewProps)[];
|
|
50
|
+
|
|
51
|
+
const columnById = (view: Gtk.ColumnView, id: string): Gtk.ColumnViewColumn | null => {
|
|
52
|
+
const columns = view.getColumns();
|
|
53
|
+
|
|
54
|
+
for (let index = 0; index < columns.getNItems(); index++) {
|
|
55
|
+
const column = columns.getItem(index);
|
|
56
|
+
|
|
57
|
+
if (column instanceof Gtk.ColumnViewColumn && column.getId() === id) {
|
|
58
|
+
return column;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
};
|
|
85
64
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const widgetRef = useRef<Gtk.ColumnView | null>(null);
|
|
91
|
-
const captureWidget = useCallback((value: Gtk.ColumnView | null) => {
|
|
92
|
-
widgetRef.current = value;
|
|
93
|
-
}, []);
|
|
94
|
-
const [, setRef] = useForwardedRef<Gtk.ColumnView>(props.ref, captureWidget);
|
|
95
|
-
|
|
96
|
-
const externalModel = props.model as Gio.ListModel | undefined;
|
|
97
|
-
const listModel = useListModel<T, S>(
|
|
98
|
-
externalModel === undefined ? { items: props.items } : { model: externalModel },
|
|
99
|
-
);
|
|
65
|
+
const getSortTarget = (view: Gtk.ColumnView | null, sortColumn: string | null | undefined): SortTarget | null => {
|
|
66
|
+
if (view === null || sortColumn === undefined) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
100
69
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
70
|
+
if (sortColumn === null) {
|
|
71
|
+
return { view, column: null };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const column = columnById(view, sortColumn);
|
|
75
|
+
|
|
76
|
+
return column === null ? null : { view, column };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const applySort = (
|
|
80
|
+
sorting: { current: boolean },
|
|
81
|
+
view: Gtk.ColumnView,
|
|
82
|
+
column: Gtk.ColumnViewColumn | null,
|
|
83
|
+
order: Gtk.SortType | null | undefined,
|
|
84
|
+
): void => {
|
|
85
|
+
sorting.current = true;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
view.sortByColumn(column, order ?? Gtk.SortType.ASCENDING);
|
|
89
|
+
} finally {
|
|
90
|
+
sorting.current = false;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const emitSortChanged = (
|
|
95
|
+
sorter: Gtk.ColumnViewSorter | null,
|
|
96
|
+
sorting: { current: boolean },
|
|
97
|
+
sort: SortProps,
|
|
98
|
+
): void => {
|
|
99
|
+
if (sorter === null || sorting.current) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sort.onSortChanged?.(sorter.getPrimarySortColumn()?.getId() ?? null, sorter.getPrimarySortOrder());
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const syncSort = (
|
|
107
|
+
sorting: { current: boolean },
|
|
108
|
+
view: Gtk.ColumnView | null,
|
|
109
|
+
sortColumn: string | null | undefined,
|
|
110
|
+
sortOrder: Gtk.SortType | null | undefined,
|
|
111
|
+
): void => {
|
|
112
|
+
const target = getSortTarget(view, sortColumn);
|
|
113
|
+
|
|
114
|
+
if (target === null) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
applySort(sorting, target.view, target.column, sortOrder);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const useColumnSorting = (view: Gtk.ColumnView | null, sort: SortProps, columns: Column<never>[]): void => {
|
|
122
|
+
const sorting = useRef(false);
|
|
123
|
+
const sorter = useProperty(view, "sorter");
|
|
124
|
+
const columnSorter = sorter instanceof Gtk.ColumnViewSorter ? sorter : null;
|
|
125
|
+
|
|
126
|
+
useSignal(columnSorter, "changed", (): void => {
|
|
127
|
+
emitSortChanged(columnSorter, sorting, sort);
|
|
125
128
|
});
|
|
126
129
|
|
|
127
|
-
const
|
|
128
|
-
() => ({
|
|
129
|
-
columnView: widgetRef,
|
|
130
|
-
resolver: listModel.resolver as ItemResolver<unknown, unknown>,
|
|
131
|
-
register: registry.register,
|
|
132
|
-
unregister: registry.unregister,
|
|
133
|
-
}),
|
|
134
|
-
[listModel.resolver, registry.register, registry.unregister],
|
|
135
|
-
);
|
|
130
|
+
const { sortColumn, sortOrder } = sort;
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
headerResolver: listModel.headerResolver,
|
|
141
|
-
headerStore: headers.store,
|
|
142
|
-
useHeader,
|
|
143
|
-
contextValue,
|
|
144
|
-
};
|
|
132
|
+
useLayoutEffect(() => {
|
|
133
|
+
syncSort(sorting, view, sortColumn, sortOrder);
|
|
134
|
+
}, [view, sortColumn, sortOrder, columns]);
|
|
145
135
|
};
|
|
146
136
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* declared as {@link ColumnViewColumn} children. Supplying an external `model`
|
|
151
|
-
* switches to the uncontrolled form.
|
|
152
|
-
*/
|
|
153
|
-
export const ColumnView = <T = unknown, S = unknown>(props: ColumnViewComponentProps<T, S>): ReactNode => {
|
|
154
|
-
const {
|
|
155
|
-
ref,
|
|
156
|
-
items,
|
|
157
|
-
model,
|
|
158
|
-
selected,
|
|
159
|
-
selectionMode,
|
|
160
|
-
onSelectionChanged,
|
|
161
|
-
sortColumn,
|
|
162
|
-
sortOrder,
|
|
163
|
-
onSortChanged,
|
|
164
|
-
renderHeader,
|
|
165
|
-
estimatedRowHeight,
|
|
166
|
-
children,
|
|
167
|
-
...intrinsicProps
|
|
168
|
-
} = props as NormalizedColumnViewProps<T, S>;
|
|
169
|
-
|
|
170
|
-
const registry = useColumnRegistry();
|
|
171
|
-
const wiring = useColumnViewWiring<T, S>(
|
|
172
|
-
{
|
|
173
|
-
ref,
|
|
174
|
-
items,
|
|
175
|
-
model,
|
|
176
|
-
selected,
|
|
177
|
-
selectionMode,
|
|
178
|
-
onSelectionChanged,
|
|
179
|
-
sortColumn,
|
|
180
|
-
sortOrder,
|
|
181
|
-
onSortChanged,
|
|
182
|
-
renderHeader,
|
|
183
|
-
estimatedRowHeight,
|
|
184
|
-
} as NormalizedColumnViewProps<T, S>,
|
|
185
|
-
registry,
|
|
186
|
-
);
|
|
137
|
+
const ColumnCells = ({ column, collection, expandedIds, size }: ColumnCellsProps): ReactNode => {
|
|
138
|
+
const cells = useItemCells(size);
|
|
139
|
+
const rest = omit(column, ["id", "renderCell", "sortable"]);
|
|
187
140
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
<GtkColumnViewColumn
|
|
144
|
+
id={column.id}
|
|
145
|
+
factory={<GtkSignalListItemFactory {...cells.handlers} />}
|
|
146
|
+
sorter={column.sortable === true ? <GtkCustomSorter /> : null}
|
|
147
|
+
{...rest}
|
|
148
|
+
/>
|
|
149
|
+
<ItemPortals
|
|
150
|
+
store={cells}
|
|
151
|
+
render={column.renderCell}
|
|
152
|
+
collection={collection}
|
|
153
|
+
expandedIds={expandedIds}
|
|
154
|
+
/>
|
|
155
|
+
</>
|
|
192
156
|
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const ColumnList = ({ columns, collection, expandedIds, size }: ColumnListProps): ReactNode =>
|
|
160
|
+
columns.map((column) => (
|
|
161
|
+
<ColumnCells
|
|
162
|
+
key={column.id}
|
|
163
|
+
column={column}
|
|
164
|
+
collection={collection}
|
|
165
|
+
expandedIds={expandedIds}
|
|
166
|
+
size={size}
|
|
167
|
+
/>
|
|
168
|
+
));
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Renders a Gtk.ColumnView from declarative items or sections and a columns array,
|
|
172
|
+
* with controlled selection, expansion, and sorting, per-column header menus, and
|
|
173
|
+
* section header rendering.
|
|
174
|
+
*/
|
|
175
|
+
function ColumnView<T = unknown, S = unknown>(props: ColumnViewProps<T, S>): ReactNode {
|
|
176
|
+
const { renderHeader, columns, expandedIds, sortColumn, sortOrder, onSortChanged, estimatedItemHeight, ref } =
|
|
177
|
+
props;
|
|
178
|
+
|
|
179
|
+
const rest = omit(props, COLUMN_VIEW_PROPS);
|
|
180
|
+
const [view, refCallback] = useWidgetRef<Gtk.ColumnView>(ref);
|
|
181
|
+
const size = { width: -1, height: estimatedItemHeight ?? -1 };
|
|
182
|
+
const { collection, selection } = useCollection(props);
|
|
183
|
+
const headerCells = useHeaderCells(size);
|
|
184
|
+
const columnList = columns as Column<never>[];
|
|
185
|
+
useColumnSorting(view, { sortColumn, sortOrder, onSortChanged }, columnList);
|
|
193
186
|
|
|
194
187
|
return (
|
|
195
188
|
<>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
189
|
+
<GtkColumnView
|
|
190
|
+
ref={refCallback}
|
|
191
|
+
model={selection}
|
|
192
|
+
{...(renderHeader != null && {
|
|
193
|
+
headerFactory: <GtkSignalListItemFactory {...headerCells.handlers} />,
|
|
194
|
+
})}
|
|
195
|
+
{...rest}
|
|
196
|
+
>
|
|
197
|
+
<ColumnList columns={columnList} collection={collection} expandedIds={expandedIds} size={size} />
|
|
198
|
+
</GtkColumnView>
|
|
199
|
+
{renderHeader != null && (
|
|
200
|
+
<HeaderPortals store={headerCells} render={renderHeader} collection={collection} />
|
|
201
|
+
)}
|
|
204
202
|
</>
|
|
205
203
|
);
|
|
206
|
-
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { ColumnView };
|