@connectorvol/chess-widgets 8.0.1 → 9.0.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.
Files changed (48) hide show
  1. package/dist/(components)/BoardSettingsTrigger.svelte +10 -0
  2. package/dist/(components)/BoardSettingsTrigger.svelte.d.ts +18 -0
  3. package/dist/constants/default-board-appearance-settings.d.ts +9 -0
  4. package/dist/constants/default-board-appearance-settings.js +30 -0
  5. package/dist/constants/editable-board-settings.d.ts +3 -21
  6. package/dist/constants/editable-board-settings.js +24 -19
  7. package/dist/game-analyzer/GameAnalyzer.svelte +28 -10
  8. package/dist/game-analyzer/gameAnalyzer.svelte.js +6 -9
  9. package/dist/game-analyzer/types.d.ts +13 -8
  10. package/dist/index.d.ts +9 -1
  11. package/dist/index.js +6 -0
  12. package/dist/position-editor/EditPanel.svelte +9 -6
  13. package/dist/puzzle/puzzleCreatedPayload.d.ts +17 -0
  14. package/dist/puzzle/puzzleData.d.ts +4 -0
  15. package/dist/puzzle/puzzleData.js +10 -0
  16. package/dist/puzzle/puzzlePreviewConstants.d.ts +4 -0
  17. package/dist/puzzle/puzzlePreviewConstants.js +4 -0
  18. package/dist/puzzle/puzzlePreviewPathNags.d.ts +6 -0
  19. package/dist/puzzle/puzzlePreviewPathNags.js +35 -0
  20. package/dist/puzzle/puzzleSolverForkAnnotations.d.ts +2 -4
  21. package/dist/puzzle/puzzleSolverForkAnnotations.js +13 -22
  22. package/dist/puzzle/puzzleStepPreviewSolver.d.ts +13 -1
  23. package/dist/puzzle/puzzleStepPreviewSolver.js +69 -9
  24. package/dist/puzzle/syncPuzzleBranchNags.d.ts +14 -0
  25. package/dist/puzzle/syncPuzzleBranchNags.js +125 -0
  26. package/dist/puzzle-creation/OpeningTagHoverPreview.svelte +81 -0
  27. package/dist/puzzle-creation/OpeningTagHoverPreview.svelte.d.ts +11 -0
  28. package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte +198 -98
  29. package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte.d.ts +20 -2
  30. package/dist/puzzle-creation/PuzzleCreationWizard.svelte +100 -106
  31. package/dist/puzzle-creation/PuzzleCreationWizard.svelte.d.ts +47 -3
  32. package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +945 -498
  33. package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte +36 -0
  34. package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte.d.ts +12 -0
  35. package/dist/puzzle-creation/StepMoves.svelte +210 -188
  36. package/dist/puzzle-creation/StepPosition.svelte +35 -9
  37. package/dist/puzzle-creation/StepPreview.svelte +24 -11
  38. package/dist/puzzle-creation/StepPreview.svelte.d.ts +3 -1
  39. package/dist/puzzle-creation/StepTags.svelte +270 -0
  40. package/dist/puzzle-creation/StepTags.svelte.d.ts +19 -0
  41. package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.d.ts +9 -0
  42. package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.js +19 -0
  43. package/dist/puzzle-creation/createPuzzleLineEditingBoard.d.ts +10 -3
  44. package/dist/puzzle-creation/createPuzzleLineEditingBoard.js +14 -9
  45. package/dist/puzzle-creation/puzzleWizardState.d.ts +35 -0
  46. package/dist/puzzle-creation/puzzleWizardState.js +37 -0
  47. package/dist/puzzle-creation/types.d.ts +36 -10
  48. package/package.json +22 -17
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type {
3
+ TPuzzleOpeningTagOption,
4
+ TPuzzleTagOption,
5
+ } from "@connectorvol/shared";
6
+
7
+ import StepTags from "./StepTags.svelte";
8
+ import { getWizardContext } from "@connectorvol/shared";
9
+ import type { TPuzzleWizardState } from "./puzzleWizardState.js";
10
+
11
+ interface Props {
12
+ /** Возвращает каталог дебютов для выбора одного тега. */
13
+ openingTags: readonly TPuzzleOpeningTagOption[];
14
+ /** Возвращает каталог тактических приёмов. */
15
+ tacticTags: readonly TPuzzleTagOption[];
16
+ /** Возвращает максимальное число выбираемых тактических тегов. */
17
+ tacticTagsMax: number;
18
+ }
19
+
20
+ const { openingTags, tacticTags, tacticTagsMax }: Props = $props();
21
+
22
+ const w = getWizardContext<TPuzzleWizardState>();
23
+ let tags = $state(w.state.puzzleTags);
24
+ </script>
25
+
26
+ <StepTags
27
+ bind:tags
28
+ {openingTags}
29
+ {tacticTags}
30
+ {tacticTagsMax}
31
+ onBack={w.back}
32
+ onDone={(tags) => {
33
+ w.state.puzzleTags = tags;
34
+ w.done();
35
+ }}
36
+ />
@@ -0,0 +1,12 @@
1
+ import type { TPuzzleOpeningTagOption, TPuzzleTagOption } from "@connectorvol/shared";
2
+ interface Props {
3
+ /** Возвращает каталог дебютов для выбора одного тега. */
4
+ openingTags: readonly TPuzzleOpeningTagOption[];
5
+ /** Возвращает каталог тактических приёмов. */
6
+ tacticTags: readonly TPuzzleTagOption[];
7
+ /** Возвращает максимальное число выбираемых тактических тегов. */
8
+ tacticTagsMax: number;
9
+ }
10
+ declare const PuzzleWizardTagsStep: import("svelte").Component<Props, {}, "">;
11
+ type PuzzleWizardTagsStep = ReturnType<typeof PuzzleWizardTagsStep>;
12
+ export default PuzzleWizardTagsStep;
@@ -1,199 +1,221 @@
1
1
  <script lang="ts">
2
- import type { BoardApi } from "@connectorvol/chessboard";
3
-
4
- import { Draggable } from "@connectorvol/chessboard";
5
- import type { ChessTree } from "@connectorvol/tree";
6
-
7
- import { Color } from "@connectorvol/shared";
8
- import { Popover } from "bits-ui";
9
-
10
- import { buttonVariants } from "../button-variants.js";
11
- import PuzzleBoardTreeViewerPane from "./PuzzleBoardTreeViewerPane.svelte";
12
- import { cn } from "../utils.js";
13
-
14
- import type { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
15
- import {
16
- getMainLineFromTree,
17
- type PuzzleData,
18
- } from "../puzzle/puzzleData.js";
19
- import { validatePuzzleSolverForkAnnotations } from "../puzzle/puzzleSolverForkAnnotations.js";
20
-
21
- interface Props {
22
- puzzleData: PuzzleData;
23
- onBack: () => void;
24
- onNext: (moves: string[]) => void;
25
- chess: PgnOps;
26
- tree: ChessTree;
27
- chessboard: BoardApi;
28
- }
29
-
30
- const { puzzleData, onBack, onNext, chess, tree, chessboard }: Props =
31
- $props();
32
-
33
- const initialTurn = $derived(
34
- puzzleData.initialFen.trim().split(/\s+/)[1] === "b"
35
- ? Color.BLACK
36
- : Color.WHITE,
37
- );
38
-
39
- const mainLine = $derived(getMainLineFromTree(tree.rootNode));
40
-
41
- function setChessFen(fen: string) {
42
- chess.setFen(fen);
43
- }
44
-
45
- function setChessboardFen(_animationTime?: number) {
46
- chessboard.fen = chess.fen();
47
- const side = chess.turn();
48
- chessboard.draggable =
49
- side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
50
- }
51
-
52
- function onSelectNode() {
53
- setChessFen(tree.currentNode.data.fen);
54
- setChessboardFen();
55
- }
56
-
57
- function onDeleteVariant() {
58
- setChessFen(tree.currentNode.data.fen);
59
- setChessboardFen();
60
- }
61
-
62
- const canGoNext = $derived(mainLine.length >= 1 && mainLine.length % 2 === 1);
63
-
64
- const solverForkAnnotations = $derived(
65
- validatePuzzleSolverForkAnnotations(tree, initialTurn),
66
- );
67
-
68
- const canProceedToNextStep = $derived(canGoNext && solverForkAnnotations.ok);
69
-
70
- /** Текст предупреждения по шагу для кнопки «!» (если пусто — предупреждений нет). */
71
- const stepIssueMessage = $derived.by(() => {
72
- if (!canGoNext && mainLine.length > 0) {
73
- return `Последний ход должна сделать сторона, которая начинала. Добавьте ещё один ход за ${initialTurn === Color.WHITE ? "белых" : "чёрных"}.`;
2
+ import type { BoardApi } from "@connectorvol/chessboard";
3
+
4
+ import { Draggable } from "@connectorvol/chessboard";
5
+ import type { ChessTree } from "@connectorvol/tree";
6
+
7
+ import { Color } from "@connectorvol/shared";
8
+ import { Popover } from "bits-ui";
9
+
10
+ import { buttonVariants } from "../button-variants.js";
11
+ import PuzzleBoardTreeViewerPane from "./PuzzleBoardTreeViewerPane.svelte";
12
+ import { cn } from "../utils.js";
13
+
14
+ import type { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
15
+ import {
16
+ getMainLineFromTree,
17
+ type PuzzleData,
18
+ } from "../puzzle/puzzleData.js";
19
+ import { validatePuzzleSolverForkAnnotations } from "../puzzle/puzzleSolverForkAnnotations.js";
20
+ import { syncPuzzleBranchNagsOnTree } from "../puzzle/syncPuzzleBranchNags.js";
21
+ import { untrack } from "svelte";
22
+
23
+ interface Props {
24
+ puzzleData: PuzzleData;
25
+ onBack: () => void;
26
+ onNext: (moves: string[]) => void;
27
+ chess: PgnOps;
28
+ tree: ChessTree;
29
+ chessboard: BoardApi;
74
30
  }
75
- if (canGoNext && !solverForkAnnotations.ok) {
76
- return solverForkAnnotations.reason;
31
+
32
+ const { puzzleData, onBack, onNext, chess, tree, chessboard }: Props =
33
+ $props();
34
+
35
+ const initialTurn = $derived(
36
+ puzzleData.initialFen.trim().split(/\s+/)[1] === "b"
37
+ ? Color.BLACK
38
+ : Color.WHITE,
39
+ );
40
+
41
+ const mainLine = $derived(getMainLineFromTree(tree.rootNode));
42
+
43
+ function setChessFen(fen: string) {
44
+ chess.setFen(fen);
45
+ }
46
+
47
+ function setChessboardFen(_animationTime: number | undefined) {
48
+ chessboard.fen = chess.fen();
49
+ const side = chess.turn();
50
+ chessboard.draggable =
51
+ side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
77
52
  }
78
- return null;
79
- });
80
53
 
81
- function handleNext() {
82
- if (canProceedToNextStep) {
83
- onNext(mainLine);
54
+ function onSelectNode() {
55
+ setChessFen(tree.currentNode.data.fen);
56
+ setChessboardFen(0);
57
+ }
58
+
59
+ function onDeleteVariant() {
60
+ setChessFen(tree.currentNode.data.fen);
61
+ setChessboardFen(0);
62
+ }
63
+
64
+ const canGoNext = $derived(
65
+ mainLine.length >= 1 && mainLine.length % 2 === 1,
66
+ );
67
+
68
+ const solverForkAnnotations = $derived(
69
+ validatePuzzleSolverForkAnnotations(tree, initialTurn),
70
+ );
71
+
72
+ const canProceedToNextStep = $derived(
73
+ canGoNext && solverForkAnnotations.ok,
74
+ );
75
+
76
+ /** Представляет автоматическую разметку ✓/✗ на развилках решателя после каждой правки дерева. */
77
+ $effect(() => {
78
+ void tree.mutationVersion;
79
+ untrack(() => {
80
+ syncPuzzleBranchNagsOnTree(tree, initialTurn);
81
+ });
82
+ });
83
+
84
+ /** Текст предупреждения по шагу для кнопки «!» (если пусто — предупреждений нет). */
85
+ const stepIssueMessage = $derived.by(() => {
86
+ if (!canGoNext && mainLine.length > 0) {
87
+ return `Последний ход должна сделать сторона, которая начинала. Добавьте ещё один ход за ${initialTurn === Color.WHITE ? "белых" : "чёрных"}.`;
88
+ }
89
+ if (canGoNext && !solverForkAnnotations.ok) {
90
+ return solverForkAnnotations.reason;
91
+ }
92
+ return null;
93
+ });
94
+
95
+ function handleNext() {
96
+ if (canProceedToNextStep) {
97
+ onNext(mainLine);
98
+ }
84
99
  }
85
- }
86
100
  </script>
87
101
 
88
102
  <div class="flex flex-col gap-3 pt-2 pb-0">
89
- <div class="flex flex-wrap items-center gap-2">
90
- <h2 class="text-xl font-semibold">Шаг 2: Построение линии решения</h2>
91
- <Popover.Root>
92
- <Popover.Trigger type="button">
93
- {#snippet child({ props })}
94
- <button
95
- {...props}
103
+ <div class="flex flex-wrap items-center gap-2">
104
+ <h2 class="text-xl font-semibold">Шаг 2: Построение линии решения</h2>
105
+ <Popover.Root>
106
+ <Popover.Trigger type="button">
107
+ {#snippet child({ props })}
108
+ <button
109
+ {...props}
110
+ type="button"
111
+ class={cn(
112
+ buttonVariants({
113
+ variant: "outline",
114
+ size: "icon-sm",
115
+ }),
116
+ "size-7 shrink-0 rounded-full text-sm font-semibold",
117
+ )}
118
+ aria-label="Справка: построение линии решения"
119
+ >
120
+ ?
121
+ </button>
122
+ {/snippet}
123
+ </Popover.Trigger>
124
+ <Popover.Portal>
125
+ <Popover.Content
126
+ side="bottom"
127
+ align="start"
128
+ sideOffset={8}
129
+ class={cn(
130
+ "bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
131
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
132
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
133
+ )}
134
+ >
135
+ <p class="text-muted-foreground leading-relaxed">
136
+ Делайте ходы за обе стороны. Последний ход должна
137
+ сделать сторона, которая начинала игру (ключевой ход). В
138
+ дереве можно выбирать ходы, добавлять комментарии и
139
+ символы оценки в режиме правки. Ходы главной линии
140
+ автоматически получают ✓ (верный ход), неверные ходы
141
+ решателя в вариациях — ✗; ходы соперника без этих меток.
142
+ Если от позиции решателя есть несколько вариантов хода,
143
+ главный вариант должен быть верным, остальные —
144
+ неверными. После ✓ главный вариант нужно довести до
145
+ позиции, где последним походил решатель. Если несколько
146
+ ответов соперника ведут из одной позиции, каждая такая
147
+ линия должна заканчиваться ходом решателя.
148
+ </p>
149
+ </Popover.Content>
150
+ </Popover.Portal>
151
+ </Popover.Root>
152
+ {#if stepIssueMessage}
153
+ <Popover.Root>
154
+ <Popover.Trigger type="button">
155
+ {#snippet child({ props })}
156
+ <button
157
+ {...props}
158
+ type="button"
159
+ class={cn(
160
+ buttonVariants({
161
+ variant: "outline",
162
+ size: "icon-sm",
163
+ }),
164
+ "size-7 shrink-0 rounded-full border-amber-600/60 text-sm font-semibold text-amber-600 dark:border-amber-400/60 dark:text-amber-400",
165
+ )}
166
+ aria-label="Предупреждение по условиям шага"
167
+ >
168
+ !
169
+ </button>
170
+ {/snippet}
171
+ </Popover.Trigger>
172
+ <Popover.Portal>
173
+ <Popover.Content
174
+ side="bottom"
175
+ align="start"
176
+ sideOffset={8}
177
+ class={cn(
178
+ "bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
179
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
180
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
181
+ )}
182
+ >
183
+ <p
184
+ class="leading-relaxed text-amber-700 dark:text-amber-400"
185
+ >
186
+ {stepIssueMessage}
187
+ </p>
188
+ </Popover.Content>
189
+ </Popover.Portal>
190
+ </Popover.Root>
191
+ {/if}
192
+ </div>
193
+
194
+ <PuzzleBoardTreeViewerPane
195
+ {chessboard}
196
+ chessTree={tree}
197
+ {onSelectNode}
198
+ {onDeleteVariant}
199
+ {setChessFen}
200
+ {setChessboardFen}
201
+ showPuzzleBranchNags={true}
202
+ />
203
+
204
+ <div class="flex justify-between">
205
+ <button
206
+ type="button"
207
+ class={cn(buttonVariants({ variant: "outline" }))}
208
+ onclick={onBack}
209
+ >
210
+ Назад
211
+ </button>
212
+ <button
96
213
  type="button"
97
- class={cn(
98
- buttonVariants({ variant: "outline", size: "icon-sm" }),
99
- "size-7 shrink-0 rounded-full text-sm font-semibold",
100
- )}
101
- aria-label="Справка: построение линии решения"
102
- >
103
- ?
104
- </button>
105
- {/snippet}
106
- </Popover.Trigger>
107
- <Popover.Portal>
108
- <Popover.Content
109
- side="bottom"
110
- align="start"
111
- sideOffset={8}
112
- class={cn(
113
- "bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
114
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
115
- "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
116
- )}
214
+ class={cn(buttonVariants({ variant: "default" }))}
215
+ onclick={handleNext}
216
+ disabled={!canProceedToNextStep}
117
217
  >
118
- <p class="text-muted-foreground leading-relaxed">
119
- Делайте ходы за обе стороны. Последний ход должна сделать сторона,
120
- которая начинала игру (ключевой ход). В дереве можно выбирать ходы,
121
- добавлять комментарии и символы оценки в режиме правки. Если от
122
- позиции решателя есть несколько вариантов хода, у каждого обязательно
123
- отметьте ✓ или ✗ в блоке «Задача (развилка)» под деревом (эти метки
124
- только для ходов из развилки, не для единственного продолжения).
125
- После ✓ главный вариант нужно довести до позиции, где последним
126
- походил решатель. Если несколько ответов соперника ведут из одной
127
- позиции, каждая такая линия должна заканчиваться ходом решателя (это не
128
- требуется внутри продолжения после хода решателя с меткой ✗
129
- «неверное решение»). Ниже по этой линии метки ✓ и ✗ не ставятся.
130
- </p>
131
- </Popover.Content>
132
- </Popover.Portal>
133
- </Popover.Root>
134
- {#if stepIssueMessage}
135
- <Popover.Root>
136
- <Popover.Trigger type="button">
137
- {#snippet child({ props })}
138
- <button
139
- {...props}
140
- type="button"
141
- class={cn(
142
- buttonVariants({ variant: "outline", size: "icon-sm" }),
143
- "size-7 shrink-0 rounded-full border-amber-600/60 text-sm font-semibold text-amber-600 dark:border-amber-400/60 dark:text-amber-400",
144
- )}
145
- aria-label="Предупреждение по условиям шага"
146
- >
147
- !
148
- </button>
149
- {/snippet}
150
- </Popover.Trigger>
151
- <Popover.Portal>
152
- <Popover.Content
153
- side="bottom"
154
- align="start"
155
- sideOffset={8}
156
- class={cn(
157
- "bg-popover text-popover-foreground border-border z-50 max-h-[min(70vh,32rem)] w-[min(calc(100vw-2rem),28rem)] overflow-y-auto rounded-lg border p-4 text-sm shadow-md outline-none",
158
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
159
- "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
160
- )}
161
- >
162
- <p
163
- class="leading-relaxed text-amber-700 dark:text-amber-400"
164
- >
165
- {stepIssueMessage}
166
- </p>
167
- </Popover.Content>
168
- </Popover.Portal>
169
- </Popover.Root>
170
- {/if}
171
- </div>
172
-
173
- <PuzzleBoardTreeViewerPane
174
- {chessboard}
175
- chessTree={tree}
176
- {onSelectNode}
177
- {onDeleteVariant}
178
- {setChessFen}
179
- {setChessboardFen}
180
- />
181
-
182
- <div class="flex justify-between">
183
- <button
184
- type="button"
185
- class={cn(buttonVariants({ variant: "outline" }))}
186
- onclick={onBack}
187
- >
188
- Назад
189
- </button>
190
- <button
191
- type="button"
192
- class={cn(buttonVariants({ variant: "default" }))}
193
- onclick={handleNext}
194
- disabled={!canProceedToNextStep}
195
- >
196
- Далее
197
- </button>
198
- </div>
218
+ Далее
219
+ </button>
220
+ </div>
199
221
  </div>
@@ -3,8 +3,11 @@
3
3
  Chessboard,
4
4
  CHESSBOARD_THEMES,
5
5
  createBoardApi,
6
+ DEFAULT_DESIGN_SETTINGS,
7
+ getBoardDesignSettingsContext,
6
8
  type ChessboardTheme,
7
9
  } from "@connectorvol/chessboard";
10
+ import { untrack } from "svelte";
8
11
 
9
12
  import { DEFAULT_EDITABLE_BOARD_SETTINGS } from "../constants/editable-board-settings.js";
10
13
  import EditFen from "../position-editor/EditFen.svelte";
@@ -28,21 +31,40 @@
28
31
 
29
32
  /** Настройки доски мастера задачи: фиксированный размер, без ручного resize. */
30
33
  const puzzleStepBoardSettings = $derived({
31
- ...DEFAULT_EDITABLE_BOARD_SETTINGS,
32
- ...(props.boardAppearanceSettings ?? {}),
33
- boardSize: 29,
34
- isResizable: false,
35
- editSettings: DEFAULT_EDITABLE_BOARD_SETTINGS.editSettings,
34
+ play: {
35
+ ...DEFAULT_EDITABLE_BOARD_SETTINGS.play,
36
+ ...(props.boardAppearanceSettings?.play ?? {}),
37
+ boardSize: 29,
38
+ isResizable: false,
39
+ editSettings: DEFAULT_EDITABLE_BOARD_SETTINGS.play.editSettings,
40
+ },
41
+ design: {
42
+ ...DEFAULT_EDITABLE_BOARD_SETTINGS.design,
43
+ ...(props.boardAppearanceSettings?.design ?? {}),
44
+ theme: props.boardTheme ?? CHESSBOARD_THEMES.blue,
45
+ },
36
46
  });
37
47
 
38
48
  let chessboard = $derived(
39
49
  createBoardApi({
40
50
  fen: (() => props.initialFen)(),
41
- settings: puzzleStepBoardSettings,
42
- theme: props.boardTheme ?? CHESSBOARD_THEMES.blue,
51
+ playSettings: puzzleStepBoardSettings.play,
43
52
  }),
44
53
  );
45
54
 
55
+ // ponytail: компонент только читает контекст дизайна. Если layout уже
56
+ // поднял его — Chessboard берёт дизайн из контекста сам, иначе
57
+ // используем дизайн из `puzzleStepBoardSettings.design`
58
+ // (`boardTheme` + `boardAppearanceSettings.design`).
59
+ const inheritedDesign = getBoardDesignSettingsContext();
60
+ const localDesign = $derived({
61
+ ...DEFAULT_DESIGN_SETTINGS,
62
+ ...puzzleStepBoardSettings.design,
63
+ });
64
+ const chessboardDesign = $derived(
65
+ inheritedDesign ? undefined : localDesign,
66
+ );
67
+
46
68
  /** Один объект Fen на доску: создаём сразу с нужной начальной строкой. */
47
69
  const fen = $derived.by(() => {
48
70
  const f = new Fen(chessboard);
@@ -121,9 +143,13 @@
121
143
  <!-- Явная ширина: при родителе с w-fit/min-content BoardContainer даёт min(100%, Nrem), и 100% может схлопнуться до нуля. -->
122
144
  <div
123
145
  class="shrink-0 max-w-full"
124
- style="width: {puzzleStepBoardSettings.boardSize}rem; max-width: 100%;"
146
+ style="width: {puzzleStepBoardSettings.play
147
+ .boardSize}rem; max-width: 100%;"
125
148
  >
126
- <Chessboard facade={chessboard} />
149
+ <Chessboard
150
+ facade={chessboard}
151
+ design={chessboardDesign}
152
+ />
127
153
  </div>
128
154
  <div
129
155
  class="hidden min-w-0 flex-1 flex-col gap-3 md:flex md:min-w-[280px]"
@@ -27,10 +27,12 @@
27
27
  * Возвращает колбэк сохранения задачи с FEN и строкой ходов (кнопка «Готово» показывается после решения превью).
28
28
  */
29
29
  onPuzzleCreated?: (payload: TPuzzleCreatedPayload) => void;
30
+ /** Возвращает переход на шаг выбора тегов вместо немедленного завершения. */
31
+ onNext?: () => void;
30
32
  /** Возвращает тему оформления шахматной доски (по умолчанию `CHESSBOARD_THEMES.blue`). */
31
33
  boardTheme?: ChessboardTheme;
32
34
  /** Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`). */
33
- boardAppearanceSettings: TChessboardAppearanceSettings;
35
+ boardAppearanceSettings?: TChessboardAppearanceSettings;
34
36
  }
35
37
 
36
38
  const props: Props = $props();
@@ -150,16 +152,27 @@
150
152
  >
151
153
  Назад
152
154
  </button>
153
- {#if props.onPuzzleCreated && solved}
154
- <button
155
- type="button"
156
- class={cn(buttonVariants({ variant: "default" }))}
157
- onclick={() =>
158
- props.onPuzzleCreated?.(puzzlePartsFromFullPgn(props.solutionPgn))}
159
- disabled={!props.solutionPgn.trim()}
160
- >
161
- Готово
162
- </button>
155
+ {#if solved}
156
+ {#if props.onNext}
157
+ <button
158
+ type="button"
159
+ class={cn(buttonVariants({ variant: "default" }))}
160
+ onclick={props.onNext}
161
+ disabled={!props.solutionPgn.trim()}
162
+ >
163
+ Далее
164
+ </button>
165
+ {:else if props.onPuzzleCreated}
166
+ <button
167
+ type="button"
168
+ class={cn(buttonVariants({ variant: "default" }))}
169
+ onclick={() =>
170
+ props.onPuzzleCreated?.(puzzlePartsFromFullPgn(props.solutionPgn))}
171
+ disabled={!props.solutionPgn.trim()}
172
+ >
173
+ Готово
174
+ </button>
175
+ {/if}
163
176
  {/if}
164
177
  </div>
165
178
  </div>
@@ -13,10 +13,12 @@ interface Props {
13
13
  * Возвращает колбэк сохранения задачи с FEN и строкой ходов (кнопка «Готово» показывается после решения превью).
14
14
  */
15
15
  onPuzzleCreated?: (payload: TPuzzleCreatedPayload) => void;
16
+ /** Возвращает переход на шаг выбора тегов вместо немедленного завершения. */
17
+ onNext?: () => void;
16
18
  /** Возвращает тему оформления шахматной доски (по умолчанию `CHESSBOARD_THEMES.blue`). */
17
19
  boardTheme?: ChessboardTheme;
18
20
  /** Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`). */
19
- boardAppearanceSettings: TChessboardAppearanceSettings;
21
+ boardAppearanceSettings?: TChessboardAppearanceSettings;
20
22
  }
21
23
  declare const StepPreview: import("svelte").Component<Props, {}, "">;
22
24
  type StepPreview = ReturnType<typeof StepPreview>;