@connectorvol/chess-widgets 1.1.0 → 1.2.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.
- package/dist/position-editor/EditMove.svelte +164 -158
- package/dist/puzzle/puzzleStepPreviewSolver.d.ts +1 -1
- package/dist/puzzle/puzzleStepPreviewSolver.js +4 -1
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte +198 -200
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +66 -8
- package/dist/puzzle-creation/StepPosition.svelte +196 -190
- package/package.json +11 -8
|
@@ -1,210 +1,216 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import {
|
|
3
|
+
Chessboard,
|
|
4
|
+
CHESSBOARD_THEMES,
|
|
5
|
+
createBoardApi,
|
|
6
|
+
type ChessboardTheme,
|
|
7
|
+
} from "@connectorvol/chessboard";
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
import { DEFAULT_EDITABLE_BOARD_SETTINGS } from "../constants/editable-board-settings.js";
|
|
10
|
+
import EditFen from "../position-editor/EditFen.svelte";
|
|
11
|
+
import EditMove from "../position-editor/EditMove.svelte";
|
|
12
|
+
import EditPanel from "../position-editor/EditPanel.svelte";
|
|
13
|
+
import { Fen } from "../position-editor/fen.svelte.js";
|
|
14
|
+
import { Popover } from "bits-ui";
|
|
15
|
+
import { buttonVariants } from "../button-variants.js";
|
|
16
|
+
import { cn } from "../utils.js";
|
|
17
|
+
import { Color } from "@connectorvol/shared";
|
|
18
|
+
import type { TChessboardAppearanceSettings } from "./types.js";
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { buttonVariants } from "../button-variants.js";
|
|
18
|
-
import { cn } from "../utils.js";
|
|
19
|
-
import { Color } from "@connectorvol/shared";
|
|
20
|
-
import type { TChessboardAppearanceSettings } from "./types.js";
|
|
21
|
-
|
|
22
|
-
interface Props {
|
|
23
|
-
initialFen: string;
|
|
24
|
-
onNext: (fen: string) => void;
|
|
25
|
-
boardTheme?: ChessboardTheme;
|
|
26
|
-
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
27
|
-
}
|
|
20
|
+
interface Props {
|
|
21
|
+
initialFen: string;
|
|
22
|
+
onNext: (fen: string) => void;
|
|
23
|
+
boardTheme?: ChessboardTheme;
|
|
24
|
+
boardAppearanceSettings?: TChessboardAppearanceSettings;
|
|
25
|
+
}
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
const props: Props = $props();
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
/** Настройки доски мастера задачи: фиксированный размер, без ручного resize. */
|
|
30
|
+
const puzzleStepBoardSettings = $derived({
|
|
31
|
+
...DEFAULT_EDITABLE_BOARD_SETTINGS,
|
|
32
|
+
...(props.boardAppearanceSettings ?? {}),
|
|
33
|
+
boardSize: 29,
|
|
34
|
+
isResizable: false,
|
|
35
|
+
editSettings: DEFAULT_EDITABLE_BOARD_SETTINGS.editSettings,
|
|
36
|
+
});
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
let chessboard = $derived(
|
|
39
|
+
createBoardApi({
|
|
40
|
+
fen: (() => props.initialFen)(),
|
|
41
|
+
settings: puzzleStepBoardSettings,
|
|
42
|
+
theme: props.boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
/** Один объект Fen на доску: создаём сразу с нужной начальной строкой. */
|
|
47
|
+
const fen = $derived.by(() => {
|
|
48
|
+
const f = new Fen(chessboard);
|
|
49
|
+
try {
|
|
50
|
+
f.fullFen = props.initialFen;
|
|
51
|
+
} catch {
|
|
52
|
+
/* некорректная строка FEN — оставляем текущее состояние Fen */
|
|
53
|
+
}
|
|
54
|
+
return f;
|
|
55
|
+
});
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fen.fullFen = src;
|
|
59
|
-
} catch {
|
|
60
|
-
/* некорректная строка FEN — оставляем текущее состояние Fen */
|
|
61
|
-
}
|
|
57
|
+
$effect(() => {
|
|
58
|
+
const sideToMove = fen.move;
|
|
59
|
+
const targetOrientation =
|
|
60
|
+
sideToMove === "w" ? Color.WHITE : Color.BLACK;
|
|
61
|
+
if (chessboard.orientation !== targetOrientation) {
|
|
62
|
+
chessboard.orientation = targetOrientation;
|
|
63
|
+
}
|
|
62
64
|
});
|
|
63
|
-
});
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const targetOrientation = sideToMove === "w" ? Color.WHITE : Color.BLACK;
|
|
68
|
-
if (chessboard.orientation !== targetOrientation) {
|
|
69
|
-
chessboard.orientation = targetOrientation;
|
|
66
|
+
function handleNext() {
|
|
67
|
+
props.onNext(fen.fullFen);
|
|
70
68
|
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
function handleNext() {
|
|
74
|
-
props.onNext(fen.fullFen);
|
|
75
|
-
}
|
|
76
69
|
</script>
|
|
77
70
|
|
|
78
71
|
<div class="flex flex-col gap-3 pt-2 pb-0">
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
72
|
+
<div class="flex w-full flex-wrap items-center gap-2">
|
|
73
|
+
<h2 class="text-xl font-semibold">Шаг 1: Создание позиции</h2>
|
|
74
|
+
<Popover.Root>
|
|
75
|
+
<Popover.Trigger type="button">
|
|
76
|
+
{#snippet child({ props })}
|
|
77
|
+
<button
|
|
78
|
+
{...props}
|
|
79
|
+
type="button"
|
|
80
|
+
class={cn(
|
|
81
|
+
buttonVariants({
|
|
82
|
+
variant: "outline",
|
|
83
|
+
size: "icon-sm",
|
|
84
|
+
}),
|
|
85
|
+
"size-7 shrink-0 rounded-full text-sm font-semibold",
|
|
86
|
+
)}
|
|
87
|
+
aria-label="Справка: создание стартовой позиции"
|
|
88
|
+
>
|
|
89
|
+
?
|
|
90
|
+
</button>
|
|
91
|
+
{/snippet}
|
|
92
|
+
</Popover.Trigger>
|
|
93
|
+
<Popover.Portal>
|
|
94
|
+
<Popover.Content
|
|
95
|
+
side="bottom"
|
|
96
|
+
align="start"
|
|
97
|
+
sideOffset={8}
|
|
98
|
+
class={cn(
|
|
99
|
+
"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",
|
|
100
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
101
|
+
"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<p class="text-muted-foreground leading-relaxed">
|
|
105
|
+
Установите начальную позицию, с которой ученик будет
|
|
106
|
+
искать решение. В поле «Установить ход» доска
|
|
107
|
+
поворачивается так, что ходящая сторона оказывается у
|
|
108
|
+
нижнего края, а палитры фигур меняются местами.
|
|
109
|
+
</p>
|
|
110
|
+
</Popover.Content>
|
|
111
|
+
</Popover.Portal>
|
|
112
|
+
</Popover.Root>
|
|
113
|
+
</div>
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
115
|
+
<div class="flex flex-col md:flex-row gap-3">
|
|
116
|
+
<div class="flex w-full max-w-full flex-col justify-center gap-2">
|
|
117
|
+
{#snippet boardRow()}
|
|
118
|
+
<div
|
|
119
|
+
class="flex w-full max-w-full flex-col gap-3 md:flex-row md:items-start items-center"
|
|
120
|
+
>
|
|
121
|
+
<!-- Явная ширина: при родителе с w-fit/min-content BoardContainer даёт min(100%, Nrem), и 100% может схлопнуться до нуля. -->
|
|
122
|
+
<div
|
|
123
|
+
class="shrink-0 max-w-full"
|
|
124
|
+
style="width: {puzzleStepBoardSettings.boardSize}rem; max-width: 100%;"
|
|
125
|
+
>
|
|
126
|
+
<Chessboard facade={chessboard} />
|
|
127
|
+
</div>
|
|
128
|
+
<div
|
|
129
|
+
class="hidden min-w-0 flex-1 flex-col gap-3 md:flex md:min-w-[280px]"
|
|
130
|
+
>
|
|
131
|
+
<EditFen
|
|
132
|
+
{fen}
|
|
133
|
+
api={chessboard}
|
|
134
|
+
constrainToBoardWidth={false}
|
|
135
|
+
/>
|
|
136
|
+
<div class="space-y-2">
|
|
137
|
+
<label
|
|
138
|
+
for="puzzle-start-fullmove-md"
|
|
139
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
140
|
+
>
|
|
141
|
+
Номер хода в записи партии
|
|
142
|
+
</label>
|
|
143
|
+
<input
|
|
144
|
+
id="puzzle-start-fullmove-md"
|
|
145
|
+
type="number"
|
|
146
|
+
min="1"
|
|
147
|
+
class={cn(
|
|
148
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
|
149
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
150
|
+
)}
|
|
151
|
+
value={fen.fullmove}
|
|
152
|
+
oninput={(e) => {
|
|
153
|
+
const n = Number.parseInt(
|
|
154
|
+
e.currentTarget.value,
|
|
155
|
+
10,
|
|
156
|
+
);
|
|
157
|
+
if (!Number.isFinite(n) || n < 1) return;
|
|
158
|
+
fen.fullmove = n;
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
<EditMove {fen} api={chessboard} />
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
{/snippet}
|
|
166
|
+
<!-- Без {#if} по fen.move: иначе при смене стороны размонтируется boardRow/EditMove и bind у select ломается до следующего взаимодействия. -->
|
|
167
|
+
<div class={fen.move === "w" ? "order-1" : "order-3"}>
|
|
168
|
+
<EditPanel api={chessboard} color="b" />
|
|
169
|
+
</div>
|
|
170
|
+
<div class="order-2">{@render boardRow()}</div>
|
|
171
|
+
<div class={fen.move === "w" ? "order-3" : "order-1"}>
|
|
172
|
+
<EditPanel api={chessboard} color="w" />
|
|
173
|
+
</div>
|
|
174
|
+
<div class="md:hidden flex w-full flex-col gap-3">
|
|
175
|
+
<EditFen {fen} api={chessboard} constrainToBoardWidth={false} />
|
|
176
|
+
<div class="space-y-2">
|
|
177
|
+
<label
|
|
178
|
+
for="puzzle-start-fullmove-mobile"
|
|
179
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
180
|
+
>
|
|
181
|
+
Номер хода в записи партии
|
|
182
|
+
</label>
|
|
183
|
+
<input
|
|
184
|
+
id="puzzle-start-fullmove-mobile"
|
|
185
|
+
type="number"
|
|
186
|
+
min="1"
|
|
187
|
+
class={cn(
|
|
188
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
|
189
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
190
|
+
)}
|
|
191
|
+
value={fen.fullmove}
|
|
192
|
+
oninput={(e) => {
|
|
193
|
+
const n = Number.parseInt(
|
|
194
|
+
e.currentTarget.value,
|
|
195
|
+
10,
|
|
196
|
+
);
|
|
197
|
+
if (!Number.isFinite(n) || n < 1) return;
|
|
198
|
+
fen.fullmove = n;
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
<EditMove {fen} api={chessboard} />
|
|
158
203
|
</div>
|
|
159
|
-
<EditMove {fen} api={chessboard} />
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
{/snippet}
|
|
163
|
-
<!-- Без {#if} по fen.move: иначе при смене стороны размонтируется boardRow/EditMove и bind у select ломается до следующего взаимодействия. -->
|
|
164
|
-
<div class={fen.move === "w" ? "order-1" : "order-3"}>
|
|
165
|
-
<EditPanel api={chessboard} color="b" />
|
|
166
|
-
</div>
|
|
167
|
-
<div class="order-2">{@render boardRow()}</div>
|
|
168
|
-
<div class={fen.move === "w" ? "order-3" : "order-1"}>
|
|
169
|
-
<EditPanel api={chessboard} color="w" />
|
|
170
|
-
</div>
|
|
171
|
-
<div class="md:hidden flex w-full flex-col gap-3">
|
|
172
|
-
<EditFen {fen} api={chessboard} constrainToBoardWidth={false} />
|
|
173
|
-
<div class="space-y-2">
|
|
174
|
-
<label
|
|
175
|
-
for="puzzle-start-fullmove-mobile"
|
|
176
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
177
|
-
>
|
|
178
|
-
Номер хода в записи партии
|
|
179
|
-
</label>
|
|
180
|
-
<input
|
|
181
|
-
id="puzzle-start-fullmove-mobile"
|
|
182
|
-
type="number"
|
|
183
|
-
min="1"
|
|
184
|
-
class={cn(
|
|
185
|
-
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
|
186
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
187
|
-
)}
|
|
188
|
-
value={fen.fullmove}
|
|
189
|
-
oninput={(e) => {
|
|
190
|
-
const n = Number.parseInt(e.currentTarget.value, 10);
|
|
191
|
-
if (!Number.isFinite(n) || n < 1) return;
|
|
192
|
-
fen.fullmove = n;
|
|
193
|
-
}}
|
|
194
|
-
/>
|
|
195
204
|
</div>
|
|
196
|
-
<EditMove {fen} api={chessboard} />
|
|
197
|
-
</div>
|
|
198
205
|
</div>
|
|
199
|
-
</div>
|
|
200
206
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
<div class="flex justify-end">
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
class={cn(buttonVariants({ variant: "default" }))}
|
|
211
|
+
onclick={handleNext}
|
|
212
|
+
>
|
|
213
|
+
Далее
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
210
216
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectorvol/chess-widgets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,21 +37,25 @@
|
|
|
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": "",
|
|
41
|
+
"test:e2e": "node node_modules/@playwright/test/cli.js test",
|
|
42
|
+
"test:e2e:ui": "node node_modules/@playwright/test/cli.js test --ui"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"@connectorvol/chessboard": "4.
|
|
44
|
-
"@connectorvol/chessops": "3.
|
|
45
|
-
"@connectorvol/shared": "4.
|
|
46
|
-
"@connectorvol/tree": "4.
|
|
45
|
+
"@connectorvol/chessboard": "4.1.0",
|
|
46
|
+
"@connectorvol/chessops": "3.1.0",
|
|
47
|
+
"@connectorvol/shared": "4.1.0",
|
|
48
|
+
"@connectorvol/tree": "4.1.0",
|
|
49
|
+
"bits-ui": "2.16.4",
|
|
47
50
|
"clsx": "^2.1.1",
|
|
51
|
+
"svelte-toolbelt": "^0.10.6",
|
|
48
52
|
"tailwind-merge": "3.3.1",
|
|
49
53
|
"tailwind-variants": "3.1.1"
|
|
50
54
|
},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
|
|
57
|
+
"@playwright/test": "1.54.1",
|
|
53
58
|
"@lucide/svelte": "^0.553.0",
|
|
54
|
-
"bits-ui": "2.16.4",
|
|
55
59
|
"@sveltejs/adapter-static": "3.0.10",
|
|
56
60
|
"@sveltejs/kit": "2.48.0",
|
|
57
61
|
"@sveltejs/package": "2.4.0",
|
|
@@ -71,7 +75,6 @@
|
|
|
71
75
|
},
|
|
72
76
|
"peerDependencies": {
|
|
73
77
|
"@lucide/svelte": "^0.553.0",
|
|
74
|
-
"bits-ui": "2.16.4",
|
|
75
78
|
"svelte": "^5.53.12"
|
|
76
79
|
}
|
|
77
80
|
}
|