@connectorvol/tree 6.4.0 → 6.5.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.
@@ -28,6 +28,7 @@
28
28
  import {
29
29
  isPositionEvaluationNag,
30
30
  nagDescription,
31
+ NAG_CATALOG_SECTIONS,
31
32
  SELECTABLE_NAG_IDS_ORDERED,
32
33
  } from "../(models)/nagCatalog.js";
33
34
  import { setContext } from "svelte";
@@ -93,6 +94,8 @@
93
94
  previewHoverDelayMs?: number | "off";
94
95
  /** Возвращает true, если под деревом показываются комментарий и выбор NAG для текущей ноды. */
95
96
  editMode?: boolean;
97
+ /** Возвращает true, если в режиме правки показывать группу NAG «Задача (развилка)». По умолчанию true. */
98
+ showPuzzleBranchNags?: boolean;
96
99
  /** Возвращает функцию для установки признака несохранённости дерева. */
97
100
  onChangeDirty?: (setIsDirty: (value: boolean) => void) => void;
98
101
  };
@@ -111,6 +114,7 @@
111
114
  setPreviewFen,
112
115
  previewHoverDelayMs = 700,
113
116
  editMode = false,
117
+ showPuzzleBranchNags = false,
114
118
  onChangeDirty,
115
119
  }: TTreeViewerProps = $props();
116
120
 
@@ -179,9 +183,21 @@
179
183
  };
180
184
  });
181
185
 
186
+ $effect(() => {
187
+ void chessTree.currentNode.id;
188
+ scrollToActiveMoveIfNeeded();
189
+ });
190
+
182
191
  /** Представляет true, если доступно редактирование комментария и NAG (есть интерактивный выбор ноды). */
183
192
  const canEditAnnotations = $derived(editMode && selectable);
184
193
 
194
+ /** Представляет видимые секции NAG с учётом настройки показа группы «Задача (развилка)». */
195
+ const visibleNagSections = $derived(
196
+ NAG_CATALOG_SECTIONS.filter((section) =>
197
+ showPuzzleBranchNags ? true : section.key !== "puzzle-branch",
198
+ ),
199
+ );
200
+
185
201
  let cachedPgn: string | null = $state(null);
186
202
  let cachedPgnVersion = $state(-1);
187
203
 
@@ -614,6 +630,7 @@
614
630
 
615
631
  node.data.nags = next.length > 0 ? next : undefined;
616
632
  chessTree.mutationVersion++;
633
+ onSelectNode();
617
634
  }
618
635
 
619
636
  /** Представляет обновление признака вертикального переполнения дерева ходов. */
@@ -860,41 +877,54 @@
860
877
  </label>
861
878
  <div
862
879
  class="relative flex flex-wrap gap-1.5 pb-0.5 pr-0.5"
863
- role="group"
864
- aria-label="Символы NAG"
865
880
  >
866
- {#each SELECTABLE_NAG_IDS_ORDERED as nagId (nagId)}
867
- {@const label = transformNagId(nagId)}
868
- {@const nagOnNode =
869
- chessTree.currentNode.data.nags?.includes(nagId) ??
870
- false}
871
- {@const shadedActive = canEditAnnotations && nagOnNode}
872
- {#if label}
873
- <button
874
- type="button"
875
- disabled={!canEditAnnotations}
876
- title={nagDescription(nagId)}
877
- class={{
878
- "inline-flex min-h-[1.5rem] min-w-[1.5rem] shrink-0 items-center justify-center rounded-full border px-2 py-0.5 text-center font-serif text-[0.625rem] font-semibold leading-none text-white shadow-[0_1px_2px_rgba(0,0,0,0.25)]": true,
879
- "cursor-pointer": canEditAnnotations,
880
- "cursor-not-allowed border-gray-300 bg-gray-300 text-gray-600 shadow-none":
881
- !canEditAnnotations,
882
- "border-white/25": canEditAnnotations,
883
- }}
884
- style:background={canEditAnnotations
885
- ? shadedActive
886
- ? nagBadgeBackground(nagId)
887
- : "rgb(243 244 246)"
888
- : undefined}
889
- style:color={canEditAnnotations && !shadedActive
890
- ? "#374151"
891
- : undefined}
892
- aria-pressed={nagOnNode}
893
- onclick={() => toggleNagAtCurrentNode(nagId)}
894
- >
895
- {label}
896
- </button>
881
+ {#each visibleNagSections as section, index (section.key)}
882
+ {#if index > 0}
883
+ <div
884
+ class="mx-1.5 w-px self-stretch bg-gray-300"
885
+ aria-hidden="true"
886
+ ></div>
897
887
  {/if}
888
+ <div
889
+ class="flex flex-wrap gap-1.5"
890
+ role="group"
891
+ aria-label={section.title}
892
+ >
893
+ {#each section.items as entry (entry.id)}
894
+ {@const nagId = entry.id}
895
+ {@const label = transformNagId(nagId)}
896
+ {@const nagOnNode =
897
+ chessTree.currentNode.data.nags?.includes(nagId) ??
898
+ false}
899
+ {@const shadedActive = canEditAnnotations && nagOnNode}
900
+ {#if label}
901
+ <button
902
+ type="button"
903
+ disabled={!canEditAnnotations}
904
+ title={nagDescription(nagId)}
905
+ class={{
906
+ "inline-flex min-h-[1.5rem] min-w-[1.5rem] shrink-0 items-center justify-center rounded-full border px-2 py-0.5 text-center font-serif text-[0.625rem] font-semibold leading-none text-white shadow-[0_1px_2px_rgba(0,0,0,0.25)]": true,
907
+ "cursor-pointer": canEditAnnotations,
908
+ "cursor-not-allowed border-gray-300 bg-gray-300 text-gray-600 shadow-none":
909
+ !canEditAnnotations,
910
+ "border-white/25": canEditAnnotations,
911
+ }}
912
+ style:background={canEditAnnotations
913
+ ? shadedActive
914
+ ? nagBadgeBackground(nagId)
915
+ : "rgb(243 244 246)"
916
+ : undefined}
917
+ style:color={canEditAnnotations && !shadedActive
918
+ ? "#374151"
919
+ : undefined}
920
+ aria-pressed={nagOnNode}
921
+ onclick={() => toggleNagAtCurrentNode(nagId)}
922
+ >
923
+ {label}
924
+ </button>
925
+ {/if}
926
+ {/each}
927
+ </div>
898
928
  {/each}
899
929
  {#if canEditAnnotations}
900
930
  <span
@@ -35,6 +35,8 @@ type TTreeViewerProps = {
35
35
  previewHoverDelayMs?: number | "off";
36
36
  /** Возвращает true, если под деревом показываются комментарий и выбор NAG для текущей ноды. */
37
37
  editMode?: boolean;
38
+ /** Возвращает true, если в режиме правки показывать группу NAG «Задача (развилка)». По умолчанию true. */
39
+ showPuzzleBranchNags?: boolean;
38
40
  /** Возвращает функцию для установки признака несохранённости дерева. */
39
41
  onChangeDirty?: (setIsDirty: (value: boolean) => void) => void;
40
42
  };
@@ -141,7 +141,7 @@
141
141
  {#if isOpen}
142
142
  <div
143
143
  data-variant-dropdown="1"
144
- class="z-50 min-w-[14rem] rounded-md border bg-white p-1 shadow-md outline-none"
144
+ class="z-50 min-w-[14rem] rounded-md border bg-white p-1 shadow-none outline-none"
145
145
  role="menu"
146
146
  aria-label="Варианты следующего хода"
147
147
  style={position
@@ -39,8 +39,8 @@
39
39
  {#if parentNode.children.length > 1}
40
40
  <div
41
41
  class={{
42
- "relative min-w-0 w-full max-w-full bg-gray-50/95": true,
43
- " rounded-md shadow-none transition-[border-color,box-shadow] duration-150 [&:has(>[data-line-hit]:hover)>.flex>div[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:hover)>.flex>ul>li>span[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:focus-visible)>.flex>div[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:focus-visible)>.flex>ul>li>span[data-line-stroke]]:border-gray-600 ":
42
+ "relative min-w-0 w-full max-w-full bg-gray-50/95 ": true,
43
+ " rounded-md transition-[border-color,box-shadow] duration-150 [&:has(>[data-line-hit]:hover)>.flex>div[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:hover)>.flex>ul>li>span[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:focus-visible)>.flex>div[data-line-stroke]]:border-gray-600 [&:has(>[data-line-hit]:focus-visible)>.flex>ul>li>span[data-line-stroke]]:border-gray-600 ":
44
44
  showVariationDrillExpand,
45
45
  "pr-2": !showVariationDrillExpand,
46
46
  }}
@@ -17,6 +17,8 @@ export type TNagCatalogEntry = {
17
17
  * Представляет группу глифов Lichess (как в меню исследований).
18
18
  */
19
19
  export type TNagCatalogSection = {
20
+ /** Возвращает ключ секции. */
21
+ key: string;
20
22
  /** Возвращает заголовок секции. */
21
23
  title: string;
22
24
  /** Возвращает список глифов в секции. */
@@ -38,6 +38,15 @@ const MOVE_EVALUATION_NAMES = {
38
38
  */
39
39
  export const NAG_CATALOG_SECTIONS = [
40
40
  {
41
+ key: "puzzle-branch",
42
+ title: "Задача (развилка)",
43
+ items: [
44
+ { id: 147, name: "Ветка: верное решение" },
45
+ { id: 148, name: "Ветка: неверное решение" },
46
+ ],
47
+ },
48
+ {
49
+ key: "move-evaluation",
41
50
  title: "Оценка хода",
42
51
  items: MOVE_EVALUATION_NAG_IDS.map((id) => ({
43
52
  id,
@@ -45,6 +54,7 @@ export const NAG_CATALOG_SECTIONS = [
45
54
  })),
46
55
  },
47
56
  {
57
+ key: "position-evaluation",
48
58
  title: "Позиция",
49
59
  items: POSITION_EVALUATION_NAG_IDS.map((id) => ({
50
60
  id,
@@ -52,13 +62,7 @@ export const NAG_CATALOG_SECTIONS = [
52
62
  })),
53
63
  },
54
64
  {
55
- title: "Задача (развилка)",
56
- items: [
57
- { id: 147, name: "Ветка: верное решение" },
58
- { id: 148, name: "Ветка: неверное решение" },
59
- ],
60
- },
61
- {
65
+ key: "observation",
62
66
  title: "Наблюдение",
63
67
  items: [
64
68
  { id: 22, name: "Цугцванг" },
@@ -17,7 +17,7 @@ export function transformPgnToChessNode(pgn) {
17
17
  const san = makeSanAndPlay(pos, move); // Mutating pos!
18
18
  const fen = makeFen(pos.toSetup());
19
19
  const { halfMoves, fullMoves } = calculatePly(fen);
20
- const firstComment = node.comments?.[0];
20
+ const allComments = node.comments?.join(" ");
21
21
  let lastMove;
22
22
  if (isNormal(move)) {
23
23
  lastMove = {
@@ -34,7 +34,7 @@ export function transformPgnToChessNode(pgn) {
34
34
  fen,
35
35
  ply: halfMoves,
36
36
  fullMoves,
37
- parsedFirstComment: firstComment ? parseComment(firstComment) : null,
37
+ parsedFirstComment: allComments ? parseComment(allComments) : null,
38
38
  ...(lastMove !== undefined ? { lastMove } : {}),
39
39
  };
40
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connectorvol/tree",
3
- "version": "6.4.0",
3
+ "version": "6.5.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",