@connectorvol/tree 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/(classes)/chessTree.svelte.d.ts +0 -5
- package/dist/(classes)/chessTree.svelte.js +0 -7
- package/dist/(components)/Move.svelte +1 -5
- package/dist/(components)/Move.svelte.d.ts +0 -2
- package/dist/(components)/TreeViewer.svelte +17 -107
- package/dist/(utils)/scrollToActiveMove.js +0 -13
- package/dist/(utils)/scrollToStart.js +0 -6
- package/package.json +10 -13
- package/dist/(utils)/treeViewerVirtualScroll.d.ts +0 -14
- package/dist/(utils)/treeViewerVirtualScroll.js +0 -9
- package/dist/(utils)/treeVirtualRows.d.ts +0 -14
- package/dist/(utils)/treeVirtualRows.js +0 -43
|
@@ -31,11 +31,6 @@ export declare class ChessTree {
|
|
|
31
31
|
* Возвращает родительский узел или null, если текущий узел — корень.
|
|
32
32
|
*/
|
|
33
33
|
getCurrentNodeParent(): ChessTreeNode | null;
|
|
34
|
-
/**
|
|
35
|
-
* Представляет поиск родительского узла для произвольного узла дерева.
|
|
36
|
-
* Возвращает родителя или null, если родитель не найден в индексе.
|
|
37
|
-
*/
|
|
38
|
-
getParentNode(node: ChessTreeNode): ChessTreeNode | null;
|
|
39
34
|
/**
|
|
40
35
|
* Представляет функцию удаления варианта из дерева.
|
|
41
36
|
* Удаляет указанный узел и все его дочерние узлы.
|
|
@@ -110,13 +110,6 @@ export class ChessTree {
|
|
|
110
110
|
getCurrentNodeParent() {
|
|
111
111
|
return this.findParentNode(this.currentNode);
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Представляет поиск родительского узла для произвольного узла дерева.
|
|
115
|
-
* Возвращает родителя или null, если родитель не найден в индексе.
|
|
116
|
-
*/
|
|
117
|
-
getParentNode(node) {
|
|
118
|
-
return this.findParentNode(node);
|
|
119
|
-
}
|
|
120
113
|
/**
|
|
121
114
|
* Представляет функцию удаления варианта из дерева.
|
|
122
115
|
* Удаляет указанный узел и все его дочерние узлы.
|
|
@@ -24,8 +24,6 @@
|
|
|
24
24
|
needSpace: boolean;
|
|
25
25
|
needRenderParentChildrens?: boolean;
|
|
26
26
|
pieceSet: PieceSet;
|
|
27
|
-
/** Представляет режим виртуализации: не рекурсировать в первого ребёнка (следующая строка списка). */
|
|
28
|
-
virtualStopMainRecursion?: boolean;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
const {
|
|
@@ -35,7 +33,6 @@
|
|
|
35
33
|
needSpace,
|
|
36
34
|
needRenderParentChildrens = true,
|
|
37
35
|
pieceSet,
|
|
38
|
-
virtualStopMainRecursion = false,
|
|
39
36
|
}: Props = $props();
|
|
40
37
|
|
|
41
38
|
const { chessTree, onSelectNode, onDeleteVariant, selectable } = getGameContext();
|
|
@@ -67,7 +64,6 @@
|
|
|
67
64
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
68
65
|
<ContextMenu.Root>
|
|
69
66
|
<ContextMenu.Trigger
|
|
70
|
-
data-node-id={chessNode.id}
|
|
71
67
|
class={{
|
|
72
68
|
"cursor-pointer ": selectable,
|
|
73
69
|
"cursor-default": !selectable,
|
|
@@ -277,7 +273,7 @@
|
|
|
277
273
|
{@render emptyMove()}
|
|
278
274
|
{/if}
|
|
279
275
|
{#if childMoves?.[0]}
|
|
280
|
-
{#if !isForcedNodeId
|
|
276
|
+
{#if !isForcedNodeId}
|
|
281
277
|
<Move
|
|
282
278
|
{pieceSet}
|
|
283
279
|
chessNode={chessNode.children[0]}
|
|
@@ -8,8 +8,6 @@ interface Props {
|
|
|
8
8
|
needSpace: boolean;
|
|
9
9
|
needRenderParentChildrens?: boolean;
|
|
10
10
|
pieceSet: PieceSet;
|
|
11
|
-
/** Представляет режим виртуализации: не рекурсировать в первого ребёнка (следующая строка списка). */
|
|
12
|
-
virtualStopMainRecursion?: boolean;
|
|
13
11
|
}
|
|
14
12
|
declare const Move: import("svelte").Component<Props, {}, "">;
|
|
15
13
|
type Move = ReturnType<typeof Move>;
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ClassValue } from "svelte/elements";
|
|
3
|
-
import { onMount } from "svelte";
|
|
4
|
-
import { get } from "svelte/store";
|
|
5
|
-
import { createVirtualizer } from "@tanstack/svelte-virtual";
|
|
6
|
-
|
|
7
3
|
import { ChessTree } from "../(classes)/chessTree.svelte.js";
|
|
8
4
|
|
|
9
5
|
import Move from "./Move.svelte";
|
|
10
6
|
import { initGameContext } from "../(utils)/context.js";
|
|
11
|
-
import {
|
|
12
|
-
buildMainLineNodes,
|
|
13
|
-
getVirtualRowIndexForNode,
|
|
14
|
-
needSpaceForMainLineNode,
|
|
15
|
-
} from "../(utils)/treeVirtualRows.js";
|
|
16
|
-
import { setTreeViewerVirtualScrollApi } from "../(utils)/treeViewerVirtualScroll.js";
|
|
17
7
|
import type { PieceSet } from "@connectorvol/shared";
|
|
18
8
|
|
|
19
9
|
type Props = {
|
|
@@ -45,66 +35,8 @@
|
|
|
45
35
|
onDeleteVariant,
|
|
46
36
|
setChessFen,
|
|
47
37
|
setChessboardFen,
|
|
48
|
-
selectable
|
|
38
|
+
selectable
|
|
49
39
|
);
|
|
50
|
-
|
|
51
|
-
let scrollEl = $state<HTMLDivElement | null>(null);
|
|
52
|
-
|
|
53
|
-
const mainLineNodes = $derived(buildMainLineNodes(chessTree.rootNode.moves));
|
|
54
|
-
|
|
55
|
-
const virtualizerStore = createVirtualizer({
|
|
56
|
-
count: 0,
|
|
57
|
-
getScrollElement: () => null,
|
|
58
|
-
estimateSize: () => 96,
|
|
59
|
-
overscan: 8,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
$effect(() => {
|
|
63
|
-
const v = get(virtualizerStore);
|
|
64
|
-
v.setOptions({
|
|
65
|
-
count: mainLineNodes.length,
|
|
66
|
-
getScrollElement: scrollEl ? () => scrollEl : () => null,
|
|
67
|
-
estimateSize: () => 96,
|
|
68
|
-
overscan: 8,
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
function measureRow(node: HTMLDivElement) {
|
|
73
|
-
queueMicrotask(() => {
|
|
74
|
-
get(virtualizerStore).measureElement(node);
|
|
75
|
-
});
|
|
76
|
-
return {};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
onMount(() => {
|
|
80
|
-
setTreeViewerVirtualScrollApi({
|
|
81
|
-
scrollToCurrentNode: (behavior: ScrollBehavior = "instant") => {
|
|
82
|
-
const v = get(virtualizerStore);
|
|
83
|
-
const line = buildMainLineNodes(chessTree.rootNode.moves);
|
|
84
|
-
if (line.length === 0) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const idx = getVirtualRowIndexForNode(
|
|
88
|
-
chessTree.currentNode,
|
|
89
|
-
line,
|
|
90
|
-
(n) => chessTree.getParentNode(n),
|
|
91
|
-
);
|
|
92
|
-
v.scrollToIndex(Math.min(idx, line.length - 1), {
|
|
93
|
-
align: "center",
|
|
94
|
-
behavior,
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
scrollToStart: (behavior: ScrollBehavior = "instant") => {
|
|
98
|
-
const v = get(virtualizerStore);
|
|
99
|
-
const line = buildMainLineNodes(chessTree.rootNode.moves);
|
|
100
|
-
if (line.length === 0) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
v.scrollToIndex(0, { align: "start", behavior });
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
return () => setTreeViewerVirtualScrollApi(null);
|
|
107
|
-
});
|
|
108
40
|
</script>
|
|
109
41
|
|
|
110
42
|
{#snippet comment(text: string)}
|
|
@@ -120,53 +52,31 @@
|
|
|
120
52
|
{/snippet}
|
|
121
53
|
|
|
122
54
|
<div
|
|
123
|
-
bind:this={scrollEl}
|
|
124
55
|
id="tree-viewer-container"
|
|
125
56
|
class={[
|
|
126
|
-
"flex-1 relative
|
|
57
|
+
"flex-1 relative overflow-auto border border-gray-300 rounded-l-sm shadow-sm ",
|
|
127
58
|
className,
|
|
128
59
|
]}
|
|
129
60
|
role="region"
|
|
130
61
|
aria-label="Chess move tree"
|
|
131
62
|
oncontextmenu={(e) => e.preventDefault()}
|
|
132
63
|
>
|
|
133
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
class={{
|
|
66
|
+
"grid flex-1 grid-cols-[60px_1fr_1fr] divide-x divide-y divide-gray-300 select-none ": true,
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
134
69
|
{#if chessTree.rootNode.comments}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{#each chessTree.rootNode.comments as firstComment}
|
|
139
|
-
{@render comment(firstComment)}
|
|
140
|
-
{/each}
|
|
141
|
-
</div>
|
|
70
|
+
{#each chessTree.rootNode.comments as firstComment}
|
|
71
|
+
{@render comment(firstComment)}
|
|
72
|
+
{/each}
|
|
142
73
|
{/if}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{@const rowNode = mainLineNodes[row.index]}
|
|
151
|
-
<div
|
|
152
|
-
class="absolute left-0 top-0 w-full grid grid-cols-[60px_1fr_1fr] divide-x divide-y divide-gray-300 select-none"
|
|
153
|
-
style="transform: translateY({row.start}px);"
|
|
154
|
-
data-index={row.index}
|
|
155
|
-
use:measureRow
|
|
156
|
-
>
|
|
157
|
-
<Move
|
|
158
|
-
chessNode={rowNode}
|
|
159
|
-
depth={1}
|
|
160
|
-
needSpace={needSpaceForMainLineNode(rowNode, (n) =>
|
|
161
|
-
chessTree.getParentNode(n),
|
|
162
|
-
)}
|
|
163
|
-
parentNode={chessTree.getParentNode(rowNode)}
|
|
164
|
-
// virtualStopMainRecursion={true}
|
|
165
|
-
{pieceSet}
|
|
166
|
-
/>
|
|
167
|
-
</div>
|
|
168
|
-
{/each}
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
74
|
+
<Move
|
|
75
|
+
chessNode={chessTree.rootNode.moves}
|
|
76
|
+
depth={1}
|
|
77
|
+
needSpace={false}
|
|
78
|
+
parentNode={null}
|
|
79
|
+
{pieceSet}
|
|
80
|
+
/>
|
|
171
81
|
</div>
|
|
172
82
|
</div>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getTreeViewerVirtualScrollApi } from "./treeViewerVirtualScroll.js";
|
|
2
1
|
export const ACTIVE_MOVE_CLASS = "active-move-in-tree-viewer";
|
|
3
2
|
/**
|
|
4
3
|
* Представляет функцию для плавного скролла до активного хода в дереве ходов
|
|
@@ -6,11 +5,6 @@ export const ACTIVE_MOVE_CLASS = "active-move-in-tree-viewer";
|
|
|
6
5
|
* @param behavior - Поведение скролла ('smooth' | 'instant' | 'auto')
|
|
7
6
|
*/
|
|
8
7
|
function scrollToActiveMove(containerId = "tree-viewer-container", behavior = "instant") {
|
|
9
|
-
const virtual = getTreeViewerVirtualScrollApi();
|
|
10
|
-
if (virtual) {
|
|
11
|
-
virtual.scrollToCurrentNode(behavior);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
8
|
const container = document.getElementById(containerId);
|
|
15
9
|
if (!container) {
|
|
16
10
|
console.warn(`Контейнер с ID "${containerId}" не найден`);
|
|
@@ -72,13 +66,6 @@ function isActiveMoveVisible(containerId = "tree-viewer-container") {
|
|
|
72
66
|
* @param behavior - Поведение скролла ('smooth' | 'instant' | 'auto')
|
|
73
67
|
*/
|
|
74
68
|
export function scrollToActiveMoveIfNeeded(containerId = "tree-viewer-container", behavior = "instant") {
|
|
75
|
-
const virtual = getTreeViewerVirtualScrollApi();
|
|
76
|
-
if (virtual) {
|
|
77
|
-
if (!isActiveMoveVisible(containerId)) {
|
|
78
|
-
virtual.scrollToCurrentNode(behavior);
|
|
79
|
-
}
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
69
|
if (!isActiveMoveVisible(containerId)) {
|
|
83
70
|
scrollToActiveMove(containerId, behavior);
|
|
84
71
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { getTreeViewerVirtualScrollApi } from "./treeViewerVirtualScroll.js";
|
|
2
1
|
/**
|
|
3
2
|
* Представляет функцию для скролла к началу дерева ходов (первый видимый ход)
|
|
4
3
|
* @param containerId - ID контейнера с деревом ходов
|
|
5
4
|
* @param behavior - Поведение скролла ('smooth' | 'instant' | 'auto')
|
|
6
5
|
*/
|
|
7
6
|
export function scrollToStart(containerId = "tree-viewer-container", behavior = "instant") {
|
|
8
|
-
const virtual = getTreeViewerVirtualScrollApi();
|
|
9
|
-
if (virtual) {
|
|
10
|
-
virtual.scrollToStart(behavior);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
7
|
const container = document.getElementById(containerId);
|
|
14
8
|
if (!container) {
|
|
15
9
|
console.warn(`Контейнер с ID "${containerId}" не найден`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectorvol/tree",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"!dist/**/*.test.*",
|
|
@@ -39,37 +39,34 @@
|
|
|
39
39
|
"test:ui": "npx playwright test --ui"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@connectorvol/chessops": "
|
|
42
|
+
"@connectorvol/chessops": "2.0.0",
|
|
43
43
|
"@connectorvol/shared": "2.0.0",
|
|
44
44
|
"clsx": "^2.1.1",
|
|
45
|
-
"tailwind-merge": "
|
|
46
|
-
"tailwind-variants": "
|
|
45
|
+
"tailwind-merge": "3.3.1",
|
|
46
|
+
"tailwind-variants": "3.1.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@connectorvol/chessboard": "2.
|
|
49
|
+
"@connectorvol/chessboard": "2.1.0",
|
|
50
50
|
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
|
51
51
|
"@internationalized/date": "3.9.0",
|
|
52
|
-
"@lucide/svelte": "^0.
|
|
52
|
+
"@lucide/svelte": "^0.553.0",
|
|
53
53
|
"@playwright/test": "1.54.1",
|
|
54
54
|
"@sveltejs/adapter-static": "3.0.10",
|
|
55
55
|
"@sveltejs/kit": "2.48.0",
|
|
56
56
|
"@sveltejs/package": "2.4.0",
|
|
57
|
-
"@sveltejs/vite-plugin-svelte": "
|
|
57
|
+
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
58
58
|
"@tailwindcss/forms": "0.5.10",
|
|
59
59
|
"@tailwindcss/postcss": "4.1.11",
|
|
60
60
|
"@tailwindcss/typography": "0.5.16",
|
|
61
61
|
"@tailwindcss/vite": "4.1.11",
|
|
62
62
|
"autoprefixer": "10.4.21",
|
|
63
63
|
"mode-watcher": "^1.1.0",
|
|
64
|
-
"prettier": "3.6.2",
|
|
65
|
-
"prettier-plugin-svelte": "3.4.0",
|
|
66
|
-
"prettier-plugin-tailwindcss": "0.6.14",
|
|
67
64
|
"svelte-check": "4.3.2",
|
|
68
65
|
"svelte-sonner": "^1.0.5",
|
|
69
66
|
"tailwindcss": "4.1.11",
|
|
70
|
-
"tw-animate-css": "
|
|
71
|
-
"typescript": "
|
|
72
|
-
"vite": "
|
|
67
|
+
"tw-animate-css": "1.4.0",
|
|
68
|
+
"typescript": "6.0.2",
|
|
69
|
+
"vite": "8.0.7",
|
|
73
70
|
"vite-plugin-checker": "0.12.0",
|
|
74
71
|
"vite-plugin-svelte-checker": "0.1.2"
|
|
75
72
|
},
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Представляет API скролла виртуализованного TreeViewer для соседних компонентов
|
|
3
|
-
* (панель навигации не является потомком TreeViewer в DOM).
|
|
4
|
-
*/
|
|
5
|
-
export type TreeViewerVirtualScrollApi = {
|
|
6
|
-
/** Возвращает void после прокрутки к строке с текущим узлом. */
|
|
7
|
-
scrollToCurrentNode: (behavior?: ScrollBehavior) => void;
|
|
8
|
-
/** Возвращает void после прокрутки к началу списка ходов. */
|
|
9
|
-
scrollToStart: (behavior?: ScrollBehavior) => void;
|
|
10
|
-
};
|
|
11
|
-
/** Представляет регистрацию API на время жизни TreeViewer. */
|
|
12
|
-
export declare function setTreeViewerVirtualScrollApi(next: TreeViewerVirtualScrollApi | null): void;
|
|
13
|
-
/** Представляет получение API скролла, если смонтирован TreeViewer. */
|
|
14
|
-
export declare function getTreeViewerVirtualScrollApi(): TreeViewerVirtualScrollApi | null;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
let api = null;
|
|
2
|
-
/** Представляет регистрацию API на время жизни TreeViewer. */
|
|
3
|
-
export function setTreeViewerVirtualScrollApi(next) {
|
|
4
|
-
api = next;
|
|
5
|
-
}
|
|
6
|
-
/** Представляет получение API скролла, если смонтирован TreeViewer. */
|
|
7
|
-
export function getTreeViewerVirtualScrollApi() {
|
|
8
|
-
return api;
|
|
9
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ChessTreeNode } from "../(models)/chessTreeNode.js";
|
|
2
|
-
/**
|
|
3
|
-
* Представляет цепочку главной линии: от корневого узла ходов по первым дочерним узлам.
|
|
4
|
-
*/
|
|
5
|
-
export declare function buildMainLineNodes(rootMoves: ChessTreeNode): ChessTreeNode[];
|
|
6
|
-
/**
|
|
7
|
-
* Представляет индекс строки виртуального списка для узла: главная линия или вариант,
|
|
8
|
-
* отображаемый внутри строки первого ребёнка родителя.
|
|
9
|
-
*/
|
|
10
|
-
export declare function getVirtualRowIndexForNode(node: ChessTreeNode, mainLine: ChessTreeNode[], getParent: (n: ChessTreeNode) => ChessTreeNode | null): number;
|
|
11
|
-
/**
|
|
12
|
-
* Представляет значение needSpace для узла главной линии (как у рекурсивного Move).
|
|
13
|
-
*/
|
|
14
|
-
export declare function needSpaceForMainLineNode(node: ChessTreeNode, getParent: (n: ChessTreeNode) => ChessTreeNode | null): boolean;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Представляет цепочку главной линии: от корневого узла ходов по первым дочерним узлам.
|
|
3
|
-
*/
|
|
4
|
-
export function buildMainLineNodes(rootMoves) {
|
|
5
|
-
const out = [];
|
|
6
|
-
let cur = rootMoves;
|
|
7
|
-
while (cur) {
|
|
8
|
-
out.push(cur);
|
|
9
|
-
cur = cur.children[0];
|
|
10
|
-
}
|
|
11
|
-
return out;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Представляет индекс строки виртуального списка для узла: главная линия или вариант,
|
|
15
|
-
* отображаемый внутри строки первого ребёнка родителя.
|
|
16
|
-
*/
|
|
17
|
-
export function getVirtualRowIndexForNode(node, mainLine, getParent) {
|
|
18
|
-
const mainLineIndex = new Map(mainLine.map((n, i) => [n.id, i]));
|
|
19
|
-
let cur = node;
|
|
20
|
-
while (cur) {
|
|
21
|
-
const idx = mainLineIndex.get(cur.id);
|
|
22
|
-
if (idx !== undefined) {
|
|
23
|
-
return idx;
|
|
24
|
-
}
|
|
25
|
-
const parent = getParent(cur);
|
|
26
|
-
if (!parent) {
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
if (parent.children[0]?.id !== cur.id) {
|
|
30
|
-
const anchor = parent.children[0];
|
|
31
|
-
return anchor ? (mainLineIndex.get(anchor.id) ?? 0) : 0;
|
|
32
|
-
}
|
|
33
|
-
cur = parent;
|
|
34
|
-
}
|
|
35
|
-
return 0;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Представляет значение needSpace для узла главной линии (как у рекурсивного Move).
|
|
39
|
-
*/
|
|
40
|
-
export function needSpaceForMainLineNode(node, getParent) {
|
|
41
|
-
const parent = getParent(node);
|
|
42
|
-
return parent ? parent.children.length > 1 : false;
|
|
43
|
-
}
|