@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.
- package/dist/(components)/BoardSettingsTrigger.svelte +10 -0
- package/dist/(components)/BoardSettingsTrigger.svelte.d.ts +18 -0
- package/dist/constants/default-board-appearance-settings.d.ts +9 -0
- package/dist/constants/default-board-appearance-settings.js +30 -0
- package/dist/constants/editable-board-settings.d.ts +3 -21
- package/dist/constants/editable-board-settings.js +24 -19
- package/dist/game-analyzer/GameAnalyzer.svelte +28 -10
- package/dist/game-analyzer/gameAnalyzer.svelte.js +6 -9
- package/dist/game-analyzer/types.d.ts +13 -8
- package/dist/index.d.ts +9 -1
- package/dist/index.js +6 -0
- package/dist/position-editor/EditPanel.svelte +9 -6
- package/dist/puzzle/puzzleCreatedPayload.d.ts +17 -0
- package/dist/puzzle/puzzleData.d.ts +4 -0
- package/dist/puzzle/puzzleData.js +10 -0
- package/dist/puzzle/puzzlePreviewConstants.d.ts +4 -0
- package/dist/puzzle/puzzlePreviewConstants.js +4 -0
- package/dist/puzzle/puzzlePreviewPathNags.d.ts +6 -0
- package/dist/puzzle/puzzlePreviewPathNags.js +35 -0
- package/dist/puzzle/puzzleSolverForkAnnotations.d.ts +2 -4
- package/dist/puzzle/puzzleSolverForkAnnotations.js +13 -22
- package/dist/puzzle/puzzleStepPreviewSolver.d.ts +13 -1
- package/dist/puzzle/puzzleStepPreviewSolver.js +69 -9
- package/dist/puzzle/syncPuzzleBranchNags.d.ts +14 -0
- package/dist/puzzle/syncPuzzleBranchNags.js +125 -0
- package/dist/puzzle-creation/OpeningTagHoverPreview.svelte +81 -0
- package/dist/puzzle-creation/OpeningTagHoverPreview.svelte.d.ts +11 -0
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte +198 -98
- package/dist/puzzle-creation/PuzzleBoardTreeViewerPane.svelte.d.ts +20 -2
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte +100 -106
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte.d.ts +47 -3
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +945 -498
- package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte +36 -0
- package/dist/puzzle-creation/PuzzleWizardTagsStep.svelte.d.ts +12 -0
- package/dist/puzzle-creation/StepMoves.svelte +210 -188
- package/dist/puzzle-creation/StepPosition.svelte +35 -9
- package/dist/puzzle-creation/StepPreview.svelte +24 -11
- package/dist/puzzle-creation/StepPreview.svelte.d.ts +3 -1
- package/dist/puzzle-creation/StepTags.svelte +270 -0
- package/dist/puzzle-creation/StepTags.svelte.d.ts +19 -0
- package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.d.ts +9 -0
- package/dist/puzzle-creation/buildPuzzleWizardBoardSettings.js +19 -0
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.d.ts +10 -3
- package/dist/puzzle-creation/createPuzzleLineEditingBoard.js +14 -9
- package/dist/puzzle-creation/puzzleWizardState.d.ts +35 -0
- package/dist/puzzle-creation/puzzleWizardState.js +37 -0
- package/dist/puzzle-creation/types.d.ts +36 -10
- package/package.json +22 -17
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Popover } from "bits-ui";
|
|
3
|
+
|
|
4
|
+
import { buttonVariants } from "../button-variants.js";
|
|
5
|
+
import { cn } from "../utils.js";
|
|
6
|
+
import type { TPuzzleTags } from "../puzzle/puzzleCreatedPayload.js";
|
|
7
|
+
import type {
|
|
8
|
+
TPuzzleOpeningTagOption,
|
|
9
|
+
TPuzzleTagOption,
|
|
10
|
+
} from "@connectorvol/shared";
|
|
11
|
+
import OpeningTagHoverPreview from "./OpeningTagHoverPreview.svelte";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
/** Возвращает текущие выбранные теги (двухсторонняя привязка). */
|
|
15
|
+
tags: TPuzzleTags;
|
|
16
|
+
/** Возвращает каталог дебютов для выбора одного тега. */
|
|
17
|
+
openingTags: readonly TPuzzleOpeningTagOption[];
|
|
18
|
+
/** Возвращает каталог тактических приёмов для выбора до `tacticTagsMax` тегов. */
|
|
19
|
+
tacticTags: readonly TPuzzleTagOption[];
|
|
20
|
+
/** Возвращает максимальное число выбираемых тактических тегов. */
|
|
21
|
+
tacticTagsMax: number;
|
|
22
|
+
/** Возвращает переход назад на шаг превью. */
|
|
23
|
+
onBack: () => void;
|
|
24
|
+
/** Возвращает завершение мастера с выбранными тегами. */
|
|
25
|
+
onDone: (tags: TPuzzleTags) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type TOpeningPreviewState = {
|
|
29
|
+
tag: TPuzzleOpeningTagOption;
|
|
30
|
+
anchor: DOMRect;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
tags = $bindable(),
|
|
35
|
+
openingTags,
|
|
36
|
+
tacticTags,
|
|
37
|
+
tacticTagsMax,
|
|
38
|
+
onBack,
|
|
39
|
+
onDone,
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
let openingFilter = $state("");
|
|
43
|
+
let tacticFilter = $state("");
|
|
44
|
+
let hoveredOpening = $state<TOpeningPreviewState | null>(null);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Представляет показ превью дебюта при наведении на тег.
|
|
48
|
+
*/
|
|
49
|
+
function handleOpeningPointerEnter(
|
|
50
|
+
event: { currentTarget: EventTarget | null },
|
|
51
|
+
tag: TPuzzleOpeningTagOption,
|
|
52
|
+
) {
|
|
53
|
+
const anchor = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
|
54
|
+
hoveredOpening = { tag, anchor };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Представляет скрытие превью дебюта.
|
|
59
|
+
*/
|
|
60
|
+
function handleOpeningPointerLeave() {
|
|
61
|
+
hoveredOpening = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const filteredOpenings = $derived(
|
|
65
|
+
openingTags.filter((tag) =>
|
|
66
|
+
tag.label.toLowerCase().includes(openingFilter.trim().toLowerCase()),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const filteredTactics = $derived(
|
|
71
|
+
tacticTags.filter((tag) =>
|
|
72
|
+
tag.label.toLowerCase().includes(tacticFilter.trim().toLowerCase()),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const tacticsLimitReached = $derived(
|
|
77
|
+
tags.tactics.length >= tacticTagsMax,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Представляет переключение выбора дебютного тега (только один).
|
|
82
|
+
*/
|
|
83
|
+
function toggleOpening(id: string) {
|
|
84
|
+
tags = {
|
|
85
|
+
...tags,
|
|
86
|
+
opening: tags.opening === id ? undefined : id,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Представляет переключение тактического тега с ограничением по количеству.
|
|
92
|
+
*/
|
|
93
|
+
function toggleTactic(id: string) {
|
|
94
|
+
const selected = tags.tactics.includes(id);
|
|
95
|
+
if (selected) {
|
|
96
|
+
tags = {
|
|
97
|
+
...tags,
|
|
98
|
+
tactics: tags.tactics.filter((t) => t !== id),
|
|
99
|
+
};
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (tags.tactics.length >= tacticTagsMax) return;
|
|
103
|
+
tags = {
|
|
104
|
+
...tags,
|
|
105
|
+
tactics: [...tags.tactics, id],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
{#if hoveredOpening}
|
|
111
|
+
{#key hoveredOpening.tag.id}
|
|
112
|
+
<OpeningTagHoverPreview
|
|
113
|
+
fen={hoveredOpening.tag.fen}
|
|
114
|
+
label={hoveredOpening.tag.label}
|
|
115
|
+
anchor={hoveredOpening.anchor}
|
|
116
|
+
/>
|
|
117
|
+
{/key}
|
|
118
|
+
{/if}
|
|
119
|
+
|
|
120
|
+
<div class="flex flex-col gap-4 pt-2 pb-0">
|
|
121
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
122
|
+
<h2 class="text-xl font-semibold">Шаг 4: Теги задачи</h2>
|
|
123
|
+
<Popover.Root>
|
|
124
|
+
<Popover.Trigger type="button">
|
|
125
|
+
{#snippet child({ props })}
|
|
126
|
+
<button
|
|
127
|
+
{...props}
|
|
128
|
+
type="button"
|
|
129
|
+
class={cn(
|
|
130
|
+
buttonVariants({ variant: "outline", size: "icon-sm" }),
|
|
131
|
+
"size-7 shrink-0 rounded-full text-sm font-semibold",
|
|
132
|
+
)}
|
|
133
|
+
aria-label="Справка: теги задачи"
|
|
134
|
+
>
|
|
135
|
+
?
|
|
136
|
+
</button>
|
|
137
|
+
{/snippet}
|
|
138
|
+
</Popover.Trigger>
|
|
139
|
+
<Popover.Portal>
|
|
140
|
+
<Popover.Content
|
|
141
|
+
side="bottom"
|
|
142
|
+
align="start"
|
|
143
|
+
sideOffset={8}
|
|
144
|
+
class={cn(
|
|
145
|
+
"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",
|
|
146
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
147
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
<div class="space-y-3 text-muted-foreground leading-relaxed">
|
|
151
|
+
<p>
|
|
152
|
+
Теги помогают классифицировать задачу. Шаг опционален: можно
|
|
153
|
+
завершить мастер без выбора тегов.
|
|
154
|
+
</p>
|
|
155
|
+
<p>
|
|
156
|
+
Дебют — один тег из списка популярных дебютов. Приёмы — до трёх
|
|
157
|
+
тактических мотивов из каталога.
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
</Popover.Content>
|
|
161
|
+
</Popover.Portal>
|
|
162
|
+
</Popover.Root>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<section class="space-y-3">
|
|
166
|
+
<div class="flex flex-wrap items-end justify-between gap-2">
|
|
167
|
+
<div>
|
|
168
|
+
<h3 class="text-base font-medium">Дебют</h3>
|
|
169
|
+
<p class="text-muted-foreground text-sm">Можно выбрать не более одного</p>
|
|
170
|
+
</div>
|
|
171
|
+
<input
|
|
172
|
+
type="search"
|
|
173
|
+
class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-9 w-full max-w-xs rounded-md border px-3 text-sm outline-none focus-visible:ring-2"
|
|
174
|
+
placeholder="Поиск дебюта…"
|
|
175
|
+
bind:value={openingFilter}
|
|
176
|
+
aria-label="Поиск дебюта"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<div
|
|
180
|
+
class="border-border flex max-h-48 flex-wrap gap-2 overflow-y-auto rounded-lg border p-3"
|
|
181
|
+
role="radiogroup"
|
|
182
|
+
aria-label="Дебют"
|
|
183
|
+
>
|
|
184
|
+
{#each filteredOpenings as tag (tag.id)}
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
role="radio"
|
|
188
|
+
aria-checked={tags.opening === tag.id}
|
|
189
|
+
class={cn(
|
|
190
|
+
"rounded-full border px-3 py-1.5 text-sm transition-colors",
|
|
191
|
+
tags.opening === tag.id
|
|
192
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
193
|
+
: "border-border bg-background hover:bg-muted",
|
|
194
|
+
)}
|
|
195
|
+
onpointerenter={(event) => handleOpeningPointerEnter(event, tag)}
|
|
196
|
+
onpointerleave={handleOpeningPointerLeave}
|
|
197
|
+
onfocus={(event) => handleOpeningPointerEnter(event, tag)}
|
|
198
|
+
onblur={handleOpeningPointerLeave}
|
|
199
|
+
onclick={() => toggleOpening(tag.id)}
|
|
200
|
+
>
|
|
201
|
+
{tag.label}
|
|
202
|
+
</button>
|
|
203
|
+
{:else}
|
|
204
|
+
<p class="text-muted-foreground text-sm">Ничего не найдено</p>
|
|
205
|
+
{/each}
|
|
206
|
+
</div>
|
|
207
|
+
</section>
|
|
208
|
+
|
|
209
|
+
<section class="space-y-3">
|
|
210
|
+
<div class="flex flex-wrap items-end justify-between gap-2">
|
|
211
|
+
<div>
|
|
212
|
+
<h3 class="text-base font-medium">Приёмы</h3>
|
|
213
|
+
<p class="text-muted-foreground text-sm">
|
|
214
|
+
Выбрано {tags.tactics.length} из {tacticTagsMax}
|
|
215
|
+
</p>
|
|
216
|
+
</div>
|
|
217
|
+
<input
|
|
218
|
+
type="search"
|
|
219
|
+
class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-9 w-full max-w-xs rounded-md border px-3 text-sm outline-none focus-visible:ring-2"
|
|
220
|
+
placeholder="Поиск приёма…"
|
|
221
|
+
bind:value={tacticFilter}
|
|
222
|
+
aria-label="Поиск приёма"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
<div
|
|
226
|
+
class="border-border flex max-h-56 flex-wrap gap-2 overflow-y-auto rounded-lg border p-3"
|
|
227
|
+
role="group"
|
|
228
|
+
aria-label="Тактические приёмы"
|
|
229
|
+
>
|
|
230
|
+
{#each filteredTactics as tag (tag.id)}
|
|
231
|
+
{@const selected = tags.tactics.includes(tag.id)}
|
|
232
|
+
{@const disabled = !selected && tacticsLimitReached}
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
aria-pressed={selected}
|
|
236
|
+
{disabled}
|
|
237
|
+
class={cn(
|
|
238
|
+
"rounded-full border px-3 py-1.5 text-sm transition-colors",
|
|
239
|
+
selected
|
|
240
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
241
|
+
: "border-border bg-background hover:bg-muted",
|
|
242
|
+
disabled && "cursor-not-allowed opacity-50 hover:bg-background",
|
|
243
|
+
)}
|
|
244
|
+
onclick={() => toggleTactic(tag.id)}
|
|
245
|
+
>
|
|
246
|
+
{tag.label}
|
|
247
|
+
</button>
|
|
248
|
+
{:else}
|
|
249
|
+
<p class="text-muted-foreground text-sm">Ничего не найдено</p>
|
|
250
|
+
{/each}
|
|
251
|
+
</div>
|
|
252
|
+
</section>
|
|
253
|
+
|
|
254
|
+
<div class="flex justify-between gap-2">
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
class={cn(buttonVariants({ variant: "outline" }))}
|
|
258
|
+
onclick={onBack}
|
|
259
|
+
>
|
|
260
|
+
Назад
|
|
261
|
+
</button>
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
class={cn(buttonVariants({ variant: "default" }))}
|
|
265
|
+
onclick={() => onDone(tags)}
|
|
266
|
+
>
|
|
267
|
+
Готово
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TPuzzleTags } from "../puzzle/puzzleCreatedPayload.js";
|
|
2
|
+
import type { TPuzzleOpeningTagOption, TPuzzleTagOption } from "@connectorvol/shared";
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Возвращает текущие выбранные теги (двухсторонняя привязка). */
|
|
5
|
+
tags: TPuzzleTags;
|
|
6
|
+
/** Возвращает каталог дебютов для выбора одного тега. */
|
|
7
|
+
openingTags: readonly TPuzzleOpeningTagOption[];
|
|
8
|
+
/** Возвращает каталог тактических приёмов для выбора до `tacticTagsMax` тегов. */
|
|
9
|
+
tacticTags: readonly TPuzzleTagOption[];
|
|
10
|
+
/** Возвращает максимальное число выбираемых тактических тегов. */
|
|
11
|
+
tacticTagsMax: number;
|
|
12
|
+
/** Возвращает переход назад на шаг превью. */
|
|
13
|
+
onBack: () => void;
|
|
14
|
+
/** Возвращает завершение мастера с выбранными тегами. */
|
|
15
|
+
onDone: (tags: TPuzzleTags) => void;
|
|
16
|
+
}
|
|
17
|
+
declare const StepTags: import("svelte").Component<Props, {}, "tags">;
|
|
18
|
+
type StepTags = ReturnType<typeof StepTags>;
|
|
19
|
+
export default StepTags;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TChessBoardDesignSettings, TChessBoardPlaySettings } from "@connectorvol/chessboard";
|
|
2
|
+
import type { TChessboardAppearanceSettings } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Представляет визуальные настройки доски для шагов мастера задачи (построение линии и превью).
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildPuzzleWizardBoardSettings(boardAppearanceSettings?: TChessboardAppearanceSettings): {
|
|
7
|
+
play: TChessBoardPlaySettings;
|
|
8
|
+
design: TChessBoardDesignSettings;
|
|
9
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DEFAULT_BOARD_APPEARANCE_SETTINGS } from "../constants/default-board-appearance-settings.js";
|
|
2
|
+
import { DEFAULT_EDITABLE_BOARD_SETTINGS } from "../constants/editable-board-settings.js";
|
|
3
|
+
/**
|
|
4
|
+
* Представляет визуальные настройки доски для шагов мастера задачи (построение линии и превью).
|
|
5
|
+
*/
|
|
6
|
+
export function buildPuzzleWizardBoardSettings(boardAppearanceSettings) {
|
|
7
|
+
return {
|
|
8
|
+
play: {
|
|
9
|
+
...DEFAULT_BOARD_APPEARANCE_SETTINGS.play,
|
|
10
|
+
...boardAppearanceSettings?.play,
|
|
11
|
+
boardSize: DEFAULT_EDITABLE_BOARD_SETTINGS.play.boardSize,
|
|
12
|
+
isResizable: false,
|
|
13
|
+
},
|
|
14
|
+
design: {
|
|
15
|
+
...DEFAULT_BOARD_APPEARANCE_SETTINGS.design,
|
|
16
|
+
...boardAppearanceSettings?.design,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChessboardTheme } from "@connectorvol/chessboard";
|
|
1
|
+
import type { BoardApi, ChessboardTheme, TChessBoardDesignSettings } from "@connectorvol/chessboard";
|
|
2
2
|
import { type IChessBoardActions } from "@connectorvol/chessboard";
|
|
3
3
|
import { Color } from "@connectorvol/shared";
|
|
4
4
|
import type { TChessboardAppearanceSettings } from "./types.js";
|
|
@@ -6,11 +6,18 @@ import type { TChessboardAppearanceSettings } from "./types.js";
|
|
|
6
6
|
* Представляет определение стороны хода по полному FEN.
|
|
7
7
|
*/
|
|
8
8
|
export declare function sideToMoveFromFullFen(fullFen: string): Color;
|
|
9
|
+
interface ICreatePuzzleLineEditingBoardResult {
|
|
10
|
+
api: BoardApi;
|
|
11
|
+
design: TChessBoardDesignSettings;
|
|
12
|
+
}
|
|
9
13
|
/**
|
|
10
14
|
* Представляет создание API доски для интерактивного построения линии (как в мастере задач):
|
|
11
15
|
* ориентация и перетаскивание по стороне хода, фиксированный размер доски.
|
|
16
|
+
* Возвращает пару `{ api, design }` — `design` следует передать в `<Chessboard design={...} />`,
|
|
17
|
+
* чтобы доска разделяла снимок с превью/настройками через контекст.
|
|
12
18
|
*/
|
|
13
19
|
export declare function createPuzzleLineEditingBoardApi(fullFen: string, actions: IChessBoardActions, opts: {
|
|
14
20
|
boardTheme?: ChessboardTheme;
|
|
15
|
-
boardAppearanceSettings
|
|
16
|
-
}):
|
|
21
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
22
|
+
}): ICreatePuzzleLineEditingBoardResult;
|
|
23
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CHESSBOARD_THEMES, createBoardApi, Draggable, } from "@connectorvol/chessboard";
|
|
1
|
+
import { CHESSBOARD_THEMES, createBoardApi, DEFAULT_DESIGN_SETTINGS, Draggable, } from "@connectorvol/chessboard";
|
|
2
2
|
import { Color } from "@connectorvol/shared";
|
|
3
|
-
import {
|
|
3
|
+
import { buildPuzzleWizardBoardSettings } from "./buildPuzzleWizardBoardSettings.js";
|
|
4
4
|
/**
|
|
5
5
|
* Представляет определение стороны хода по полному FEN.
|
|
6
6
|
*/
|
|
@@ -11,20 +11,25 @@ export function sideToMoveFromFullFen(fullFen) {
|
|
|
11
11
|
/**
|
|
12
12
|
* Представляет создание API доски для интерактивного построения линии (как в мастере задач):
|
|
13
13
|
* ориентация и перетаскивание по стороне хода, фиксированный размер доски.
|
|
14
|
+
* Возвращает пару `{ api, design }` — `design` следует передать в `<Chessboard design={...} />`,
|
|
15
|
+
* чтобы доска разделяла снимок с превью/настройками через контекст.
|
|
14
16
|
*/
|
|
15
17
|
export function createPuzzleLineEditingBoardApi(fullFen, actions, opts) {
|
|
16
18
|
const side = sideToMoveFromFullFen(fullFen);
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
+
const { play, design } = buildPuzzleWizardBoardSettings(opts.boardAppearanceSettings);
|
|
20
|
+
const api = createBoardApi({
|
|
19
21
|
fen: fullFen,
|
|
20
|
-
|
|
21
|
-
...
|
|
22
|
-
boardSize: DEFAULT_EDITABLE_BOARD_SETTINGS.boardSize,
|
|
23
|
-
isResizable: false,
|
|
22
|
+
playSettings: {
|
|
23
|
+
...play,
|
|
24
24
|
orientation: side,
|
|
25
25
|
draggable: side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK,
|
|
26
26
|
},
|
|
27
27
|
actions,
|
|
28
|
-
theme: opts.boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
29
28
|
});
|
|
29
|
+
const finalDesign = {
|
|
30
|
+
...DEFAULT_DESIGN_SETTINGS,
|
|
31
|
+
...design,
|
|
32
|
+
theme: opts.boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
33
|
+
};
|
|
34
|
+
return { api, design: finalDesign };
|
|
30
35
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type PuzzleData } from "../puzzle/puzzleData.js";
|
|
2
|
+
import type { TPuzzleTags } from "../puzzle/puzzleCreatedPayload.js";
|
|
3
|
+
import type { TWizardStep } from "@connectorvol/shared";
|
|
4
|
+
/**
|
|
5
|
+
* Представляет базовый state мастера: поля, которые используют шаги 1–3.
|
|
6
|
+
*/
|
|
7
|
+
export type TPuzzleWizardCoreState = {
|
|
8
|
+
/** Возвращает данные задачи (FEN и главная линия SAN). */
|
|
9
|
+
puzzleData: PuzzleData;
|
|
10
|
+
/** Возвращает PGN дерева решения для превью на шаге 3. */
|
|
11
|
+
solutionPgn: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Представляет расширенный state мастера с полем тегов для шага `PuzzleWizardTagsStep`.
|
|
15
|
+
*/
|
|
16
|
+
export type TPuzzleWizardState = TPuzzleWizardCoreState & {
|
|
17
|
+
/** Возвращает выбранные теги задачи. */
|
|
18
|
+
puzzleTags: TPuzzleTags;
|
|
19
|
+
};
|
|
20
|
+
/** Возвращает базовые шаги мастера: позиция, ходы, превью. */
|
|
21
|
+
export declare const PUZZLE_WIZARD_CORE_STEPS: readonly TWizardStep[];
|
|
22
|
+
/** Возвращает описание опционального шага выбора тегов. */
|
|
23
|
+
export declare const PUZZLE_WIZARD_TAGS_STEP: TWizardStep;
|
|
24
|
+
/**
|
|
25
|
+
* Представляет создание базового state мастера из seed FEN.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createInitialPuzzleWizardCoreState(seedFen: string | undefined): TPuzzleWizardCoreState;
|
|
28
|
+
/**
|
|
29
|
+
* Представляет создание расширенного state мастера с тегами из seed FEN.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createInitialPuzzleWizardState(seedFen: string | undefined): TPuzzleWizardState;
|
|
32
|
+
/**
|
|
33
|
+
* Представляет нормализацию входного FEN (пустая строка трактуется как отсутствие значения).
|
|
34
|
+
*/
|
|
35
|
+
export declare function puzzleWizardSeedFen(prop: string | undefined): string | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createInitialPuzzleData } from "../puzzle/puzzleData.js";
|
|
2
|
+
/** Возвращает базовые шаги мастера: позиция, ходы, превью. */
|
|
3
|
+
export const PUZZLE_WIZARD_CORE_STEPS = [
|
|
4
|
+
{ id: "position", title: "Позиция" },
|
|
5
|
+
{ id: "moves", title: "Ходы" },
|
|
6
|
+
{ id: "preview", title: "Превью" },
|
|
7
|
+
];
|
|
8
|
+
/** Возвращает описание опционального шага выбора тегов. */
|
|
9
|
+
export const PUZZLE_WIZARD_TAGS_STEP = {
|
|
10
|
+
id: "tags",
|
|
11
|
+
title: "Теги",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Представляет создание базового state мастера из seed FEN.
|
|
15
|
+
*/
|
|
16
|
+
export function createInitialPuzzleWizardCoreState(seedFen) {
|
|
17
|
+
return {
|
|
18
|
+
puzzleData: createInitialPuzzleData(seedFen),
|
|
19
|
+
solutionPgn: "",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Представляет создание расширенного state мастера с тегами из seed FEN.
|
|
24
|
+
*/
|
|
25
|
+
export function createInitialPuzzleWizardState(seedFen) {
|
|
26
|
+
return {
|
|
27
|
+
...createInitialPuzzleWizardCoreState(seedFen),
|
|
28
|
+
puzzleTags: { tactics: [] },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Представляет нормализацию входного FEN (пустая строка трактуется как отсутствие значения).
|
|
33
|
+
*/
|
|
34
|
+
export function puzzleWizardSeedFen(prop) {
|
|
35
|
+
const t = prop?.trim();
|
|
36
|
+
return t ? t : undefined;
|
|
37
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
1
2
|
import type { TPuzzleCreatedPayload } from "../puzzle/puzzleCreatedPayload.js";
|
|
2
|
-
import type {
|
|
3
|
+
import type { TWizardStep, TWizardStepContext } from "@connectorvol/shared";
|
|
4
|
+
import type { TPuzzleWizardCoreState } from "./puzzleWizardState.js";
|
|
5
|
+
import type { ChessboardTheme, TChessBoardDesignSettings, TChessBoardPlaySettings } from "@connectorvol/chessboard";
|
|
3
6
|
/**
|
|
4
7
|
* Представляет тип визуальных настроек шахматной доски для мастера создания задачи.
|
|
5
8
|
*/
|
|
6
|
-
export type TChessboardAppearanceSettings =
|
|
9
|
+
export type TChessboardAppearanceSettings = {
|
|
10
|
+
play?: Omit<TChessBoardPlaySettings, "boardSize" | "orientation" | "draggable">;
|
|
11
|
+
design?: Partial<TChessBoardDesignSettings>;
|
|
12
|
+
};
|
|
7
13
|
/**
|
|
8
14
|
* Представляет исход решения или ошибки в превью задачи для колбэка `onOutcome`.
|
|
9
15
|
*/
|
|
@@ -34,13 +40,17 @@ export interface TPuzzlePgnBoardTreeEditorProps {
|
|
|
34
40
|
*/
|
|
35
41
|
solved?: boolean;
|
|
36
42
|
/**
|
|
37
|
-
* Возвращает
|
|
43
|
+
* Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`).
|
|
38
44
|
*/
|
|
39
|
-
|
|
45
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
40
46
|
/**
|
|
41
|
-
* Возвращает
|
|
47
|
+
* Возвращает начальный размер доски в rem (применяется только при создании API доски).
|
|
42
48
|
*/
|
|
43
|
-
|
|
49
|
+
boardSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Возвращает колбэк после завершения изменения размера доски перетаскиванием ручки resize.
|
|
52
|
+
*/
|
|
53
|
+
onResizeAction?: (size: number) => void;
|
|
44
54
|
/**
|
|
45
55
|
* Возвращает дополнительные классы Tailwind для корневого контейнера раскладки.
|
|
46
56
|
*/
|
|
@@ -49,11 +59,19 @@ export interface TPuzzlePgnBoardTreeEditorProps {
|
|
|
49
59
|
/**
|
|
50
60
|
* Представляет свойства компонента мастера создания шахматной задачи.
|
|
51
61
|
*/
|
|
52
|
-
export
|
|
62
|
+
export type TPuzzleCreationWizardProps<S extends TPuzzleWizardCoreState = TPuzzleWizardCoreState> = {
|
|
63
|
+
/**
|
|
64
|
+
* Возвращает общий state визарда (двухсторонняя привязка; инициализация и сброс — на стороне родителя).
|
|
65
|
+
*/
|
|
66
|
+
wizardState: S;
|
|
53
67
|
/**
|
|
54
|
-
* Возвращает функцию, вызываемую при завершении мастера с FEN и строкой
|
|
68
|
+
* Возвращает функцию, вызываемую при завершении мастера с FEN и строкой ходов (шаг 3 без доп. шагов).
|
|
55
69
|
*/
|
|
56
70
|
onPuzzleCreated: (payload: TPuzzleCreatedPayload) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Возвращает колбэк завершения с финальным state (вызывается из `wizard.done()` на доп. шагах).
|
|
73
|
+
*/
|
|
74
|
+
onDone?: (state: S) => void;
|
|
57
75
|
/**
|
|
58
76
|
* Возвращает начальный полный FEN для шага 1; если не задан — пустая доска.
|
|
59
77
|
*/
|
|
@@ -65,5 +83,13 @@ export interface TPuzzleCreationWizardProps {
|
|
|
65
83
|
/**
|
|
66
84
|
* Возвращает дополнительные визуальные настройки шахматной доски (кроме `boardSize`, `orientation`, `draggable`).
|
|
67
85
|
*/
|
|
68
|
-
boardAppearanceSettings
|
|
69
|
-
|
|
86
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
87
|
+
/**
|
|
88
|
+
* Возвращает дополнительные шаги после превью (для индикатора прогресса и навигации).
|
|
89
|
+
*/
|
|
90
|
+
additionalSteps?: readonly TWizardStep[];
|
|
91
|
+
/**
|
|
92
|
+
* Возвращает snippet содержимого дополнительных шагов (вызывается при `currentStep > 3`).
|
|
93
|
+
*/
|
|
94
|
+
additionalStep?: Snippet<[TWizardStepContext<S>]>;
|
|
95
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectorvol/chess-widgets",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"start": "vite",
|
|
30
30
|
"dev": "vite",
|
|
31
31
|
"build": "vite build",
|
|
32
|
-
"preview": "
|
|
32
|
+
"preview": "bun build/index.js",
|
|
33
33
|
"package": "svelte-kit sync && svelte-package && bun pm pack",
|
|
34
34
|
"publish": "bun publish --access public",
|
|
35
35
|
"prepublishOnly": "bun pm pack",
|
|
@@ -37,44 +37,49 @@
|
|
|
37
37
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
38
38
|
"format": "oxfmt --write . && prettier --write \"**/*.svelte\"",
|
|
39
39
|
"prettier": "prettier --write \"**/*.svelte\"",
|
|
40
|
-
"test": "",
|
|
40
|
+
"test": "vitest run",
|
|
41
41
|
"test:e2e": "node node_modules/@playwright/test/cli.js test",
|
|
42
42
|
"test:e2e:ui": "node node_modules/@playwright/test/cli.js test --ui"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@connectorvol/chessboard": "
|
|
46
|
-
"@connectorvol/chessops": "
|
|
47
|
-
"@connectorvol/shared": "
|
|
48
|
-
"@connectorvol/tree": "
|
|
45
|
+
"@connectorvol/chessboard": "9.0.1",
|
|
46
|
+
"@connectorvol/chessops": "9.0.1",
|
|
47
|
+
"@connectorvol/shared": "9.0.1",
|
|
48
|
+
"@connectorvol/tree": "9.0.1",
|
|
49
49
|
"bits-ui": "2.16.4",
|
|
50
|
-
"clsx": "
|
|
51
|
-
"svelte-toolbelt": "
|
|
50
|
+
"clsx": "2.1.1",
|
|
51
|
+
"svelte-toolbelt": "0.10.6",
|
|
52
52
|
"tailwind-merge": "3.3.1",
|
|
53
|
-
"tailwind-variants": "
|
|
53
|
+
"tailwind-variants": "3.2.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
+
"@connectorvol/chess-shadcn": "9.0.1",
|
|
56
57
|
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
|
57
|
-
"@lucide/svelte": "
|
|
58
|
+
"@lucide/svelte": "1.21.0",
|
|
58
59
|
"@playwright/test": "1.54.1",
|
|
59
|
-
"@sveltejs/
|
|
60
|
-
"@sveltejs/kit": "2.48.0",
|
|
60
|
+
"@sveltejs/kit": "2.49.1",
|
|
61
61
|
"@sveltejs/package": "2.4.0",
|
|
62
62
|
"@sveltejs/vite-plugin-svelte": "7.0.0",
|
|
63
63
|
"@tailwindcss/forms": "0.5.10",
|
|
64
64
|
"@tailwindcss/postcss": "4.1.11",
|
|
65
65
|
"@tailwindcss/typography": "0.5.16",
|
|
66
66
|
"@tailwindcss/vite": "4.1.11",
|
|
67
|
+
"@testing-library/svelte": "5.3.1",
|
|
68
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
67
69
|
"autoprefixer": "10.4.21",
|
|
68
|
-
"svelte-
|
|
70
|
+
"svelte-adapter-bun": "^1.0.1",
|
|
71
|
+
"svelte-check": "4.6.0",
|
|
69
72
|
"tailwindcss": "4.1.11",
|
|
70
73
|
"tw-animate-css": "1.4.0",
|
|
71
74
|
"typescript": "6.0.2",
|
|
72
75
|
"vite": "8.0.7",
|
|
73
76
|
"vite-plugin-checker": "0.12.0",
|
|
74
|
-
"vite-plugin-svelte-checker": "0.1.2"
|
|
77
|
+
"vite-plugin-svelte-checker": "0.1.2",
|
|
78
|
+
"vitest": "4.0.17"
|
|
75
79
|
},
|
|
76
80
|
"peerDependencies": {
|
|
77
|
-
"@lucide/svelte": "
|
|
78
|
-
"
|
|
81
|
+
"@lucide/svelte": "1.21.0",
|
|
82
|
+
"mode-watcher": "1.1.0",
|
|
83
|
+
"svelte": "5.56.3"
|
|
79
84
|
}
|
|
80
85
|
}
|