@react-chess-tools/react-chess-clock 1.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/CHANGELOG.md +7 -0
- package/README.md +697 -0
- package/dist/index.cjs +1014 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +528 -0
- package/dist/index.d.ts +528 -0
- package/dist/index.js +969 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/components/ChessClock/ChessClock.stories.tsx +782 -0
- package/src/components/ChessClock/index.ts +44 -0
- package/src/components/ChessClock/parts/Display.tsx +69 -0
- package/src/components/ChessClock/parts/PlayPause.tsx +190 -0
- package/src/components/ChessClock/parts/Reset.tsx +90 -0
- package/src/components/ChessClock/parts/Root.tsx +37 -0
- package/src/components/ChessClock/parts/Switch.tsx +84 -0
- package/src/components/ChessClock/parts/__tests__/Display.test.tsx +149 -0
- package/src/components/ChessClock/parts/__tests__/PlayPause.test.tsx +411 -0
- package/src/components/ChessClock/parts/__tests__/Reset.test.tsx +160 -0
- package/src/components/ChessClock/parts/__tests__/Root.test.tsx +49 -0
- package/src/components/ChessClock/parts/__tests__/Switch.test.tsx +204 -0
- package/src/hooks/__tests__/clockReducer.test.ts +985 -0
- package/src/hooks/__tests__/useChessClock.test.tsx +1080 -0
- package/src/hooks/clockReducer.ts +379 -0
- package/src/hooks/useChessClock.ts +406 -0
- package/src/hooks/useChessClockContext.ts +35 -0
- package/src/index.ts +65 -0
- package/src/types.ts +217 -0
- package/src/utils/__tests__/calculateSwitchTime.test.ts +150 -0
- package/src/utils/__tests__/formatTime.test.ts +83 -0
- package/src/utils/__tests__/timeControl.test.ts +414 -0
- package/src/utils/__tests__/timingMethods.test.ts +170 -0
- package/src/utils/calculateSwitchTime.ts +37 -0
- package/src/utils/formatTime.ts +59 -0
- package/src/utils/presets.ts +47 -0
- package/src/utils/timeControl.ts +205 -0
- package/src/utils/timingMethods.ts +103 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ChessClock: () => ChessClock,
|
|
34
|
+
ChessClockContext: () => ChessClockContext,
|
|
35
|
+
Display: () => Display,
|
|
36
|
+
PlayPause: () => PlayPause,
|
|
37
|
+
Reset: () => Reset,
|
|
38
|
+
Switch: () => Switch,
|
|
39
|
+
formatClockTime: () => formatClockTime,
|
|
40
|
+
getInitialTimes: () => getInitialTimes,
|
|
41
|
+
normalizeTimeControl: () => normalizeTimeControl,
|
|
42
|
+
parseMultiPeriodTimeControl: () => parseMultiPeriodTimeControl,
|
|
43
|
+
parseTimeControlConfig: () => parseTimeControlConfig,
|
|
44
|
+
parseTimeControlString: () => parseTimeControlString,
|
|
45
|
+
presets: () => presets,
|
|
46
|
+
useChessClock: () => useChessClock,
|
|
47
|
+
useChessClockContext: () => useChessClockContext,
|
|
48
|
+
useOptionalChessClock: () => useOptionalChessClock
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(index_exports);
|
|
51
|
+
|
|
52
|
+
// src/components/ChessClock/parts/Root.tsx
|
|
53
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
54
|
+
|
|
55
|
+
// src/hooks/useChessClockContext.ts
|
|
56
|
+
var import_react = require("react");
|
|
57
|
+
var ChessClockContext = (0, import_react.createContext)(
|
|
58
|
+
null
|
|
59
|
+
);
|
|
60
|
+
function useChessClockContext() {
|
|
61
|
+
const context = (0, import_react.useContext)(ChessClockContext);
|
|
62
|
+
if (!context) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"useChessClockContext must be used within a ChessClock.Root component. Make sure your component is wrapped with <ChessClock.Root>."
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hooks/useChessClock.ts
|
|
71
|
+
var import_react2 = require("react");
|
|
72
|
+
|
|
73
|
+
// src/utils/timeControl.ts
|
|
74
|
+
var TIME_CONTROL_REGEX = /^(\d+(?:\.\d+)?)(?:\+(\d+(?:\.\d+)?))?$/;
|
|
75
|
+
var PERIOD_REGEX = /^(?:(?:(\d+)|sd|g)\/)?(\d+(?:\.\d+)?)(?:\+(\d+(?:\.\d+)?))?$/i;
|
|
76
|
+
function parseTimeControlString(input) {
|
|
77
|
+
const match = input.match(TIME_CONTROL_REGEX);
|
|
78
|
+
if (!match) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid time control string: "${input}". Expected format: "5+3" (minutes + increment) or "10" (minutes only)`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const minutes = parseFloat(match[1]);
|
|
84
|
+
const increment = match[2] ? parseFloat(match[2]) : 0;
|
|
85
|
+
return {
|
|
86
|
+
baseTime: Math.round(minutes * 60),
|
|
87
|
+
increment: Math.round(increment)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function parsePeriod(periodStr) {
|
|
91
|
+
const trimmed = periodStr.trim();
|
|
92
|
+
const match = trimmed.match(PERIOD_REGEX);
|
|
93
|
+
if (!match) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Invalid period format: "${trimmed}". Expected format: "40/90+30" or "sd/30+30"`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const [, movesStr, timeStr, incrementStr] = match;
|
|
99
|
+
const moves = movesStr ? parseInt(movesStr, 10) : void 0;
|
|
100
|
+
const minutes = parseFloat(timeStr);
|
|
101
|
+
const increment = incrementStr ? parseFloat(incrementStr) : 0;
|
|
102
|
+
return {
|
|
103
|
+
baseTime: Math.round(minutes * 60),
|
|
104
|
+
increment: increment > 0 ? Math.round(increment) : void 0,
|
|
105
|
+
moves
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function parseMultiPeriodTimeControl(input) {
|
|
109
|
+
const parts = input.split(/\s*,\s*/);
|
|
110
|
+
return parts.map((part) => parsePeriod(part));
|
|
111
|
+
}
|
|
112
|
+
function normalizeTimeControl(input, timingMethod, clockStart) {
|
|
113
|
+
if (Array.isArray(input)) {
|
|
114
|
+
if (input.length === 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Multi-period time control must have at least one period"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const periods = input.map((period) => ({
|
|
120
|
+
...period,
|
|
121
|
+
baseTime: period.baseTime * 1e3,
|
|
122
|
+
increment: period.increment !== void 0 ? period.increment * 1e3 : void 0,
|
|
123
|
+
delay: period.delay !== void 0 ? period.delay * 1e3 : void 0
|
|
124
|
+
}));
|
|
125
|
+
const lastPeriod = periods[periods.length - 1];
|
|
126
|
+
if (lastPeriod.moves !== void 0) {
|
|
127
|
+
lastPeriod.moves = void 0;
|
|
128
|
+
}
|
|
129
|
+
const firstPeriod = periods[0];
|
|
130
|
+
return {
|
|
131
|
+
baseTime: firstPeriod.baseTime,
|
|
132
|
+
increment: firstPeriod.increment ?? 0,
|
|
133
|
+
delay: firstPeriod.delay ?? 0,
|
|
134
|
+
timingMethod,
|
|
135
|
+
clockStart,
|
|
136
|
+
periods
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let parsed;
|
|
140
|
+
if (typeof input === "string") {
|
|
141
|
+
parsed = parseTimeControlString(input);
|
|
142
|
+
} else {
|
|
143
|
+
parsed = input;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
baseTime: parsed.baseTime * 1e3,
|
|
147
|
+
// Convert to milliseconds
|
|
148
|
+
increment: (parsed.increment ?? 0) * 1e3,
|
|
149
|
+
// Convert to milliseconds
|
|
150
|
+
delay: (parsed.delay ?? 0) * 1e3,
|
|
151
|
+
// Convert to milliseconds
|
|
152
|
+
timingMethod,
|
|
153
|
+
clockStart
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function parseTimeControlConfig(config) {
|
|
157
|
+
const normalized = normalizeTimeControl(
|
|
158
|
+
config.time,
|
|
159
|
+
config.timingMethod ?? "fischer",
|
|
160
|
+
config.clockStart ?? "delayed"
|
|
161
|
+
);
|
|
162
|
+
return {
|
|
163
|
+
...normalized,
|
|
164
|
+
whiteTimeOverride: config.whiteTime ? config.whiteTime * 1e3 : void 0,
|
|
165
|
+
blackTimeOverride: config.blackTime ? config.blackTime * 1e3 : void 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function getInitialTimes(config) {
|
|
169
|
+
return {
|
|
170
|
+
white: config.whiteTimeOverride ?? config.baseTime,
|
|
171
|
+
black: config.blackTimeOverride ?? config.baseTime
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/utils/formatTime.ts
|
|
176
|
+
var AUTO_FORMAT_THRESHOLD = 20;
|
|
177
|
+
function formatClockTime(milliseconds, format = "auto") {
|
|
178
|
+
const clampedMs = Math.max(0, milliseconds);
|
|
179
|
+
if (format === "auto") {
|
|
180
|
+
format = clampedMs < AUTO_FORMAT_THRESHOLD * 1e3 ? "ss.d" : "mm:ss";
|
|
181
|
+
}
|
|
182
|
+
const totalSeconds = Math.ceil(clampedMs / 1e3);
|
|
183
|
+
switch (format) {
|
|
184
|
+
case "ss.d": {
|
|
185
|
+
const secondsWithDecimal = clampedMs / 1e3;
|
|
186
|
+
return secondsWithDecimal.toFixed(1);
|
|
187
|
+
}
|
|
188
|
+
case "mm:ss": {
|
|
189
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
190
|
+
const seconds = totalSeconds % 60;
|
|
191
|
+
if (totalMinutes >= 60) {
|
|
192
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
193
|
+
const minutes = totalMinutes % 60;
|
|
194
|
+
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
195
|
+
}
|
|
196
|
+
return `${totalMinutes}:${seconds.toString().padStart(2, "0")}`;
|
|
197
|
+
}
|
|
198
|
+
case "hh:mm:ss": {
|
|
199
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
200
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
201
|
+
const seconds = totalSeconds % 60;
|
|
202
|
+
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
203
|
+
}
|
|
204
|
+
default:
|
|
205
|
+
return String(totalSeconds);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/utils/timingMethods.ts
|
|
210
|
+
function applyFischerIncrement(currentTime, increment) {
|
|
211
|
+
return currentTime + increment;
|
|
212
|
+
}
|
|
213
|
+
function applyBronsteinDelay(currentTime, timeSpent, delay) {
|
|
214
|
+
const addBack = Math.min(timeSpent, delay);
|
|
215
|
+
return currentTime + addBack;
|
|
216
|
+
}
|
|
217
|
+
function calculateSwitchAdjustment(timingMethod, currentTime, timeSpent, config) {
|
|
218
|
+
switch (timingMethod) {
|
|
219
|
+
case "fischer":
|
|
220
|
+
return applyFischerIncrement(currentTime, config.increment);
|
|
221
|
+
case "delay":
|
|
222
|
+
return currentTime;
|
|
223
|
+
case "bronstein":
|
|
224
|
+
return applyBronsteinDelay(currentTime, timeSpent, config.delay);
|
|
225
|
+
default:
|
|
226
|
+
return currentTime;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function getInitialActivePlayer(clockStart) {
|
|
230
|
+
return clockStart === "immediate" ? "white" : null;
|
|
231
|
+
}
|
|
232
|
+
function getInitialStatus(clockStart) {
|
|
233
|
+
if (clockStart === "immediate") return "running";
|
|
234
|
+
if (clockStart === "delayed") return "delayed";
|
|
235
|
+
return "idle";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/utils/calculateSwitchTime.ts
|
|
239
|
+
function calculateSwitchTime(currentTime, timeSpent, config) {
|
|
240
|
+
let effectiveElapsed = timeSpent;
|
|
241
|
+
if (config.timingMethod === "delay") {
|
|
242
|
+
effectiveElapsed = Math.max(0, timeSpent - config.delay);
|
|
243
|
+
}
|
|
244
|
+
const newTime = Math.max(0, currentTime - effectiveElapsed);
|
|
245
|
+
return calculateSwitchAdjustment(
|
|
246
|
+
config.timingMethod,
|
|
247
|
+
newTime,
|
|
248
|
+
timeSpent,
|
|
249
|
+
config
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/hooks/clockReducer.ts
|
|
254
|
+
function maybeAdvancePeriod(times, periodState) {
|
|
255
|
+
let newTimes = times;
|
|
256
|
+
let newPeriodState = periodState;
|
|
257
|
+
["white", "black"].forEach((player) => {
|
|
258
|
+
const playerPeriodIndex = newPeriodState.periodIndex[player];
|
|
259
|
+
if (playerPeriodIndex < 0 || playerPeriodIndex >= newPeriodState.periods.length) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const currentPeriod = newPeriodState.periods[playerPeriodIndex];
|
|
263
|
+
if (!(currentPeriod == null ? void 0 : currentPeriod.moves)) return;
|
|
264
|
+
const movesRequired = currentPeriod.moves;
|
|
265
|
+
const playerMoves = newPeriodState.periodMoves[player];
|
|
266
|
+
if (playerMoves >= movesRequired) {
|
|
267
|
+
const nextPeriodIndex = playerPeriodIndex + 1;
|
|
268
|
+
if (nextPeriodIndex >= newPeriodState.periods.length) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const nextPeriod = newPeriodState.periods[nextPeriodIndex];
|
|
272
|
+
if (!nextPeriod) return;
|
|
273
|
+
const addedTime = nextPeriod.baseTime;
|
|
274
|
+
newPeriodState = {
|
|
275
|
+
...newPeriodState,
|
|
276
|
+
periodIndex: {
|
|
277
|
+
...newPeriodState.periodIndex,
|
|
278
|
+
[player]: nextPeriodIndex
|
|
279
|
+
},
|
|
280
|
+
periodMoves: {
|
|
281
|
+
...newPeriodState.periodMoves,
|
|
282
|
+
[player]: 0
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
newTimes = {
|
|
286
|
+
...newTimes,
|
|
287
|
+
[player]: newTimes[player] + addedTime
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
return { times: newTimes, periodState: newPeriodState };
|
|
292
|
+
}
|
|
293
|
+
function clockReducer(state, action) {
|
|
294
|
+
var _a, _b, _c;
|
|
295
|
+
switch (action.type) {
|
|
296
|
+
case "START": {
|
|
297
|
+
if (state.status === "finished") return state;
|
|
298
|
+
if (state.status === "delayed") {
|
|
299
|
+
return state;
|
|
300
|
+
}
|
|
301
|
+
const now = ((_a = action.payload) == null ? void 0 : _a.now) ?? Date.now();
|
|
302
|
+
return {
|
|
303
|
+
...state,
|
|
304
|
+
status: "running",
|
|
305
|
+
activePlayer: state.activePlayer ?? "white",
|
|
306
|
+
// Initialize move start time if not already set
|
|
307
|
+
moveStartTime: state.moveStartTime ?? now
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
case "PAUSE": {
|
|
311
|
+
if (state.status !== "running") return state;
|
|
312
|
+
const now = ((_b = action.payload) == null ? void 0 : _b.now) ?? Date.now();
|
|
313
|
+
const elapsedAtPause = state.moveStartTime !== null ? now - state.moveStartTime : 0;
|
|
314
|
+
return {
|
|
315
|
+
...state,
|
|
316
|
+
status: "paused",
|
|
317
|
+
elapsedAtPause,
|
|
318
|
+
moveStartTime: null
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
case "RESUME": {
|
|
322
|
+
if (state.status !== "paused") return state;
|
|
323
|
+
const now = ((_c = action.payload) == null ? void 0 : _c.now) ?? Date.now();
|
|
324
|
+
return {
|
|
325
|
+
...state,
|
|
326
|
+
status: "running",
|
|
327
|
+
moveStartTime: now - state.elapsedAtPause,
|
|
328
|
+
elapsedAtPause: 0
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
case "SWITCH": {
|
|
332
|
+
if (state.status === "finished") return state;
|
|
333
|
+
let newPeriodState = state.periodState;
|
|
334
|
+
const movedPlayer = state.activePlayer;
|
|
335
|
+
const now = action.payload.now ?? Date.now();
|
|
336
|
+
if (state.status === "delayed") {
|
|
337
|
+
const newCount = state.switchCount + 1;
|
|
338
|
+
const newPlayer2 = newCount % 2 === 1 ? "black" : "white";
|
|
339
|
+
const delayedMovePlayer = newCount % 2 === 1 ? "white" : "black";
|
|
340
|
+
if (state.periodState) {
|
|
341
|
+
newPeriodState = {
|
|
342
|
+
...state.periodState,
|
|
343
|
+
periodMoves: {
|
|
344
|
+
...state.periodState.periodMoves,
|
|
345
|
+
[delayedMovePlayer]: state.periodState.periodMoves[delayedMovePlayer] + 1
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (newPeriodState) {
|
|
350
|
+
const advanced = maybeAdvancePeriod(state.times, newPeriodState);
|
|
351
|
+
newPeriodState = advanced.periodState;
|
|
352
|
+
}
|
|
353
|
+
const newStateStatus = newCount >= 2 ? "running" : "delayed";
|
|
354
|
+
return {
|
|
355
|
+
...state,
|
|
356
|
+
activePlayer: newPlayer2,
|
|
357
|
+
switchCount: newCount,
|
|
358
|
+
status: newStateStatus,
|
|
359
|
+
periodState: newPeriodState,
|
|
360
|
+
// Start timing when transitioning to running
|
|
361
|
+
moveStartTime: newStateStatus === "running" ? now : state.moveStartTime
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (movedPlayer === null) return state;
|
|
365
|
+
const { newTimes } = action.payload;
|
|
366
|
+
const newPlayer = movedPlayer === "white" ? "black" : "white";
|
|
367
|
+
if (state.periodState) {
|
|
368
|
+
newPeriodState = {
|
|
369
|
+
...state.periodState,
|
|
370
|
+
periodMoves: {
|
|
371
|
+
...state.periodState.periodMoves,
|
|
372
|
+
[movedPlayer]: state.periodState.periodMoves[movedPlayer] + 1
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
let resolvedTimes = newTimes ?? state.times;
|
|
377
|
+
if (newPeriodState) {
|
|
378
|
+
const advanced = maybeAdvancePeriod(resolvedTimes, newPeriodState);
|
|
379
|
+
resolvedTimes = advanced.times;
|
|
380
|
+
newPeriodState = advanced.periodState;
|
|
381
|
+
}
|
|
382
|
+
const newStatus = state.status === "idle" ? "running" : state.status;
|
|
383
|
+
return {
|
|
384
|
+
...state,
|
|
385
|
+
activePlayer: newPlayer,
|
|
386
|
+
times: resolvedTimes,
|
|
387
|
+
switchCount: state.switchCount + 1,
|
|
388
|
+
periodState: newPeriodState,
|
|
389
|
+
status: newStatus,
|
|
390
|
+
// Reset timing for the new player
|
|
391
|
+
moveStartTime: newStatus === "running" ? now : null,
|
|
392
|
+
elapsedAtPause: 0
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
case "TIMEOUT": {
|
|
396
|
+
const { player } = action.payload;
|
|
397
|
+
return {
|
|
398
|
+
...state,
|
|
399
|
+
status: "finished",
|
|
400
|
+
timeout: player,
|
|
401
|
+
times: { ...state.times, [player]: 0 }
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
case "RESET": {
|
|
405
|
+
const config = parseTimeControlConfig(action.payload);
|
|
406
|
+
const initialTimes = getInitialTimes(config);
|
|
407
|
+
const now = action.payload.now;
|
|
408
|
+
const periodState = config.periods ? {
|
|
409
|
+
periodIndex: { white: 0, black: 0 },
|
|
410
|
+
periodMoves: { white: 0, black: 0 },
|
|
411
|
+
periods: config.periods
|
|
412
|
+
} : void 0;
|
|
413
|
+
return createInitialClockState(
|
|
414
|
+
initialTimes,
|
|
415
|
+
getInitialStatus(config.clockStart),
|
|
416
|
+
getInitialActivePlayer(config.clockStart),
|
|
417
|
+
config,
|
|
418
|
+
periodState,
|
|
419
|
+
now
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
case "ADD_TIME": {
|
|
423
|
+
const { player, milliseconds } = action.payload;
|
|
424
|
+
const now = action.payload.now ?? Date.now();
|
|
425
|
+
const newTimes = {
|
|
426
|
+
...state.times,
|
|
427
|
+
[player]: state.times[player] + milliseconds
|
|
428
|
+
};
|
|
429
|
+
const resetElapsed = state.status === "paused" && player === state.activePlayer;
|
|
430
|
+
return {
|
|
431
|
+
...state,
|
|
432
|
+
times: newTimes,
|
|
433
|
+
moveStartTime: state.status === "running" ? now : null,
|
|
434
|
+
...resetElapsed && { elapsedAtPause: 0 }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
case "SET_TIME": {
|
|
438
|
+
const { player, milliseconds } = action.payload;
|
|
439
|
+
const now = action.payload.now ?? Date.now();
|
|
440
|
+
const newTimes = {
|
|
441
|
+
...state.times,
|
|
442
|
+
[player]: Math.max(0, milliseconds)
|
|
443
|
+
};
|
|
444
|
+
const resetElapsed = state.status === "paused" && player === state.activePlayer;
|
|
445
|
+
return {
|
|
446
|
+
...state,
|
|
447
|
+
times: newTimes,
|
|
448
|
+
moveStartTime: state.status === "running" ? now : null,
|
|
449
|
+
...resetElapsed && { elapsedAtPause: 0 }
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
default:
|
|
453
|
+
return state;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function createInitialClockState(initialTimes, status, activePlayer, config, periodState, now) {
|
|
457
|
+
return {
|
|
458
|
+
times: initialTimes,
|
|
459
|
+
initialTimes,
|
|
460
|
+
status,
|
|
461
|
+
activePlayer,
|
|
462
|
+
timeout: null,
|
|
463
|
+
switchCount: 0,
|
|
464
|
+
periodState,
|
|
465
|
+
config,
|
|
466
|
+
// If starting immediately, initialize the move start time
|
|
467
|
+
moveStartTime: status === "running" ? now ?? Date.now() : null,
|
|
468
|
+
elapsedAtPause: 0
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/hooks/useChessClock.ts
|
|
473
|
+
var DISABLED_CLOCK_CONFIG = {
|
|
474
|
+
time: { baseTime: 0 }
|
|
475
|
+
};
|
|
476
|
+
function serializeTimeRelevantConfig(config) {
|
|
477
|
+
return JSON.stringify({
|
|
478
|
+
time: config.time,
|
|
479
|
+
timingMethod: config.timingMethod,
|
|
480
|
+
clockStart: config.clockStart,
|
|
481
|
+
whiteTime: config.whiteTime,
|
|
482
|
+
blackTime: config.blackTime
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function calculateDisplayTime(baseTime, moveStartTime, elapsedAtPause, timingMethod, delay) {
|
|
486
|
+
if (moveStartTime === null) {
|
|
487
|
+
let effectiveElapsed2 = elapsedAtPause;
|
|
488
|
+
if (timingMethod === "delay") {
|
|
489
|
+
effectiveElapsed2 = Math.max(0, elapsedAtPause - delay);
|
|
490
|
+
}
|
|
491
|
+
return Math.max(0, baseTime - effectiveElapsed2);
|
|
492
|
+
}
|
|
493
|
+
const now = Date.now();
|
|
494
|
+
const rawElapsed = now - moveStartTime;
|
|
495
|
+
let effectiveElapsed = rawElapsed;
|
|
496
|
+
if (timingMethod === "delay") {
|
|
497
|
+
effectiveElapsed = Math.max(0, rawElapsed - delay);
|
|
498
|
+
}
|
|
499
|
+
return Math.max(0, baseTime - effectiveElapsed);
|
|
500
|
+
}
|
|
501
|
+
function useChessClock(options) {
|
|
502
|
+
const [state, dispatch] = (0, import_react2.useReducer)(clockReducer, null, () => {
|
|
503
|
+
const initialConfig = parseTimeControlConfig(options);
|
|
504
|
+
const initialTimesValue = getInitialTimes(initialConfig);
|
|
505
|
+
const initialPeriodState = initialConfig.periods && initialConfig.periods.length > 1 ? {
|
|
506
|
+
periodIndex: { white: 0, black: 0 },
|
|
507
|
+
periodMoves: { white: 0, black: 0 },
|
|
508
|
+
periods: initialConfig.periods
|
|
509
|
+
} : void 0;
|
|
510
|
+
return createInitialClockState(
|
|
511
|
+
initialTimesValue,
|
|
512
|
+
getInitialStatus(initialConfig.clockStart),
|
|
513
|
+
getInitialActivePlayer(initialConfig.clockStart),
|
|
514
|
+
initialConfig,
|
|
515
|
+
initialPeriodState
|
|
516
|
+
);
|
|
517
|
+
});
|
|
518
|
+
const optionsRef = (0, import_react2.useRef)(options);
|
|
519
|
+
optionsRef.current = options;
|
|
520
|
+
const stateRef = (0, import_react2.useRef)(state);
|
|
521
|
+
stateRef.current = state;
|
|
522
|
+
const configKey = serializeTimeRelevantConfig(options);
|
|
523
|
+
const prevConfigRef = (0, import_react2.useRef)(configKey);
|
|
524
|
+
(0, import_react2.useEffect)(() => {
|
|
525
|
+
if (prevConfigRef.current !== configKey) {
|
|
526
|
+
prevConfigRef.current = configKey;
|
|
527
|
+
dispatch({ type: "RESET", payload: { ...options, now: Date.now() } });
|
|
528
|
+
}
|
|
529
|
+
}, [configKey, options]);
|
|
530
|
+
const [tick, forceUpdate] = (0, import_react2.useState)(0);
|
|
531
|
+
const displayTimes = state.activePlayer === null ? state.times : {
|
|
532
|
+
...state.times,
|
|
533
|
+
[state.activePlayer]: calculateDisplayTime(
|
|
534
|
+
state.times[state.activePlayer],
|
|
535
|
+
state.moveStartTime,
|
|
536
|
+
state.elapsedAtPause,
|
|
537
|
+
state.config.timingMethod,
|
|
538
|
+
state.config.delay
|
|
539
|
+
)
|
|
540
|
+
};
|
|
541
|
+
const activePlayerTimedOut = state.status === "running" && state.activePlayer !== null && displayTimes[state.activePlayer] <= 0;
|
|
542
|
+
(0, import_react2.useEffect)(() => {
|
|
543
|
+
if (state.status !== "running" || state.activePlayer === null) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const intervalId = setInterval(() => forceUpdate((c) => c + 1), 100);
|
|
547
|
+
return () => clearInterval(intervalId);
|
|
548
|
+
}, [state.status, state.activePlayer]);
|
|
549
|
+
(0, import_react2.useEffect)(() => {
|
|
550
|
+
var _a, _b;
|
|
551
|
+
if (state.status === "running" && state.activePlayer !== null) {
|
|
552
|
+
(_b = (_a = optionsRef.current).onTimeUpdate) == null ? void 0 : _b.call(_a, displayTimes);
|
|
553
|
+
}
|
|
554
|
+
}, [tick]);
|
|
555
|
+
(0, import_react2.useEffect)(() => {
|
|
556
|
+
var _a, _b;
|
|
557
|
+
if (activePlayerTimedOut && state.activePlayer !== null) {
|
|
558
|
+
dispatch({ type: "TIMEOUT", payload: { player: state.activePlayer } });
|
|
559
|
+
(_b = (_a = optionsRef.current).onTimeout) == null ? void 0 : _b.call(_a, state.activePlayer);
|
|
560
|
+
}
|
|
561
|
+
}, [activePlayerTimedOut, state.activePlayer]);
|
|
562
|
+
const previousActivePlayerRef = (0, import_react2.useRef)(state.activePlayer);
|
|
563
|
+
(0, import_react2.useEffect)(() => {
|
|
564
|
+
var _a, _b;
|
|
565
|
+
if (state.activePlayer !== previousActivePlayerRef.current && state.activePlayer !== null) {
|
|
566
|
+
(_b = (_a = optionsRef.current).onSwitch) == null ? void 0 : _b.call(_a, state.activePlayer);
|
|
567
|
+
}
|
|
568
|
+
previousActivePlayerRef.current = state.activePlayer;
|
|
569
|
+
}, [state.activePlayer]);
|
|
570
|
+
const info = (0, import_react2.useMemo)(
|
|
571
|
+
() => ({
|
|
572
|
+
isRunning: state.status === "running",
|
|
573
|
+
isPaused: state.status === "paused",
|
|
574
|
+
isFinished: state.status === "finished",
|
|
575
|
+
isWhiteActive: state.activePlayer === "white",
|
|
576
|
+
isBlackActive: state.activePlayer === "black",
|
|
577
|
+
hasTimeout: state.timeout !== null,
|
|
578
|
+
// Time odds is based on initial configuration, not current remaining time
|
|
579
|
+
hasTimeOdds: state.initialTimes.white !== state.initialTimes.black
|
|
580
|
+
}),
|
|
581
|
+
[
|
|
582
|
+
state.status,
|
|
583
|
+
state.activePlayer,
|
|
584
|
+
state.timeout,
|
|
585
|
+
state.initialTimes.white,
|
|
586
|
+
state.initialTimes.black
|
|
587
|
+
]
|
|
588
|
+
);
|
|
589
|
+
const start = (0, import_react2.useCallback)(() => {
|
|
590
|
+
dispatch({ type: "START", payload: { now: Date.now() } });
|
|
591
|
+
}, []);
|
|
592
|
+
const pause = (0, import_react2.useCallback)(() => {
|
|
593
|
+
dispatch({ type: "PAUSE", payload: { now: Date.now() } });
|
|
594
|
+
}, []);
|
|
595
|
+
const resume = (0, import_react2.useCallback)(() => {
|
|
596
|
+
dispatch({ type: "RESUME", payload: { now: Date.now() } });
|
|
597
|
+
}, []);
|
|
598
|
+
const switchPlayer = (0, import_react2.useCallback)(() => {
|
|
599
|
+
const currentState = stateRef.current;
|
|
600
|
+
const now = Date.now();
|
|
601
|
+
let newTimes;
|
|
602
|
+
if (currentState.status !== "delayed" && currentState.activePlayer && currentState.moveStartTime !== null) {
|
|
603
|
+
const timeSpent = now - currentState.moveStartTime;
|
|
604
|
+
const currentTime = currentState.times[currentState.activePlayer];
|
|
605
|
+
const newTime = calculateSwitchTime(
|
|
606
|
+
currentTime,
|
|
607
|
+
timeSpent,
|
|
608
|
+
currentState.config
|
|
609
|
+
);
|
|
610
|
+
newTimes = {
|
|
611
|
+
...currentState.times,
|
|
612
|
+
[currentState.activePlayer]: newTime
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
dispatch({ type: "SWITCH", payload: { newTimes, now } });
|
|
616
|
+
}, []);
|
|
617
|
+
const reset = (0, import_react2.useCallback)((newTimeControl) => {
|
|
618
|
+
const currentOptions = optionsRef.current;
|
|
619
|
+
const resetConfig = newTimeControl ? { ...currentOptions, time: newTimeControl } : currentOptions;
|
|
620
|
+
dispatch({ type: "RESET", payload: { ...resetConfig, now: Date.now() } });
|
|
621
|
+
}, []);
|
|
622
|
+
const addTime = (0, import_react2.useCallback)((player, milliseconds) => {
|
|
623
|
+
dispatch({
|
|
624
|
+
type: "ADD_TIME",
|
|
625
|
+
payload: { player, milliseconds, now: Date.now() }
|
|
626
|
+
});
|
|
627
|
+
}, []);
|
|
628
|
+
const setTime = (0, import_react2.useCallback)((player, milliseconds) => {
|
|
629
|
+
dispatch({
|
|
630
|
+
type: "SET_TIME",
|
|
631
|
+
payload: { player, milliseconds, now: Date.now() }
|
|
632
|
+
});
|
|
633
|
+
}, []);
|
|
634
|
+
const methods = (0, import_react2.useMemo)(
|
|
635
|
+
() => ({
|
|
636
|
+
start,
|
|
637
|
+
pause,
|
|
638
|
+
resume,
|
|
639
|
+
switch: switchPlayer,
|
|
640
|
+
reset,
|
|
641
|
+
addTime,
|
|
642
|
+
setTime
|
|
643
|
+
}),
|
|
644
|
+
[start, pause, resume, switchPlayer, reset, addTime, setTime]
|
|
645
|
+
);
|
|
646
|
+
const periodInfo = (0, import_react2.useMemo)(() => {
|
|
647
|
+
if (!state.periodState) {
|
|
648
|
+
return {
|
|
649
|
+
currentPeriodIndex: { white: 0, black: 0 },
|
|
650
|
+
totalPeriods: 1,
|
|
651
|
+
currentPeriod: {
|
|
652
|
+
white: {
|
|
653
|
+
baseTime: state.config.baseTime / 1e3,
|
|
654
|
+
increment: state.config.increment / 1e3,
|
|
655
|
+
delay: state.config.delay / 1e3
|
|
656
|
+
},
|
|
657
|
+
black: {
|
|
658
|
+
baseTime: state.config.baseTime / 1e3,
|
|
659
|
+
increment: state.config.increment / 1e3,
|
|
660
|
+
delay: state.config.delay / 1e3
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
periodMoves: { white: 0, black: 0 }
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const periods = state.periodState.periods;
|
|
667
|
+
const getCurrentPeriod = (periodIndex) => {
|
|
668
|
+
const period = periodIndex >= 0 && periodIndex < periods.length ? periods[periodIndex] : periods[0];
|
|
669
|
+
return {
|
|
670
|
+
...period,
|
|
671
|
+
baseTime: period.baseTime / 1e3,
|
|
672
|
+
increment: period.increment !== void 0 ? period.increment / 1e3 : void 0,
|
|
673
|
+
delay: period.delay !== void 0 ? period.delay / 1e3 : void 0
|
|
674
|
+
};
|
|
675
|
+
};
|
|
676
|
+
return {
|
|
677
|
+
currentPeriodIndex: state.periodState.periodIndex,
|
|
678
|
+
totalPeriods: periods.length,
|
|
679
|
+
currentPeriod: {
|
|
680
|
+
white: getCurrentPeriod(state.periodState.periodIndex.white),
|
|
681
|
+
black: getCurrentPeriod(state.periodState.periodIndex.black)
|
|
682
|
+
},
|
|
683
|
+
periodMoves: state.periodState.periodMoves
|
|
684
|
+
};
|
|
685
|
+
}, [state.periodState, state.config]);
|
|
686
|
+
return {
|
|
687
|
+
times: displayTimes,
|
|
688
|
+
initialTimes: state.initialTimes,
|
|
689
|
+
status: state.status,
|
|
690
|
+
activePlayer: state.activePlayer,
|
|
691
|
+
timeout: state.timeout,
|
|
692
|
+
timingMethod: state.config.timingMethod,
|
|
693
|
+
info,
|
|
694
|
+
...periodInfo,
|
|
695
|
+
methods
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function useOptionalChessClock(options) {
|
|
699
|
+
const result = useChessClock(options ?? DISABLED_CLOCK_CONFIG);
|
|
700
|
+
return options === void 0 ? null : result;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/components/ChessClock/parts/Root.tsx
|
|
704
|
+
var Root = ({
|
|
705
|
+
timeControl,
|
|
706
|
+
children
|
|
707
|
+
}) => {
|
|
708
|
+
const clockState = useChessClock(timeControl);
|
|
709
|
+
return /* @__PURE__ */ import_react3.default.createElement(ChessClockContext.Provider, { value: clockState }, children);
|
|
710
|
+
};
|
|
711
|
+
Root.displayName = "ChessClock.Root";
|
|
712
|
+
|
|
713
|
+
// src/components/ChessClock/parts/Display.tsx
|
|
714
|
+
var import_react4 = __toESM(require("react"), 1);
|
|
715
|
+
var Display = import_react4.default.forwardRef(
|
|
716
|
+
({
|
|
717
|
+
color,
|
|
718
|
+
format = "auto",
|
|
719
|
+
formatTime: customFormatTime,
|
|
720
|
+
className,
|
|
721
|
+
style,
|
|
722
|
+
...rest
|
|
723
|
+
}, ref) => {
|
|
724
|
+
const { times, activePlayer, status, timeout } = useChessClockContext();
|
|
725
|
+
const time = times[color];
|
|
726
|
+
const isActive = activePlayer === color;
|
|
727
|
+
const hasTimeout = timeout === color;
|
|
728
|
+
const isPaused = status === "paused";
|
|
729
|
+
const formattedTime = customFormatTime ? customFormatTime(time) : formatClockTime(time, format);
|
|
730
|
+
return /* @__PURE__ */ import_react4.default.createElement(
|
|
731
|
+
"div",
|
|
732
|
+
{
|
|
733
|
+
ref,
|
|
734
|
+
className,
|
|
735
|
+
style,
|
|
736
|
+
"data-clock-color": color,
|
|
737
|
+
"data-clock-active": isActive ? "true" : "false",
|
|
738
|
+
"data-clock-paused": isPaused ? "true" : "false",
|
|
739
|
+
"data-clock-timeout": hasTimeout ? "true" : "false",
|
|
740
|
+
"data-clock-status": status,
|
|
741
|
+
...rest
|
|
742
|
+
},
|
|
743
|
+
formattedTime
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
Display.displayName = "ChessClock.Display";
|
|
748
|
+
|
|
749
|
+
// src/components/ChessClock/parts/Switch.tsx
|
|
750
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
751
|
+
var import_react_slot = require("@radix-ui/react-slot");
|
|
752
|
+
var Switch = import_react5.default.forwardRef(
|
|
753
|
+
({
|
|
754
|
+
asChild = false,
|
|
755
|
+
children,
|
|
756
|
+
onClick,
|
|
757
|
+
disabled,
|
|
758
|
+
className,
|
|
759
|
+
style,
|
|
760
|
+
type,
|
|
761
|
+
...rest
|
|
762
|
+
}, ref) => {
|
|
763
|
+
const { methods, status } = useChessClockContext();
|
|
764
|
+
const isDisabled = disabled || status === "finished" || status === "idle";
|
|
765
|
+
const handleClick = import_react5.default.useCallback(
|
|
766
|
+
(e) => {
|
|
767
|
+
methods.switch();
|
|
768
|
+
onClick == null ? void 0 : onClick(e);
|
|
769
|
+
},
|
|
770
|
+
[methods, onClick]
|
|
771
|
+
);
|
|
772
|
+
return asChild ? /* @__PURE__ */ import_react5.default.createElement(
|
|
773
|
+
import_react_slot.Slot,
|
|
774
|
+
{
|
|
775
|
+
ref,
|
|
776
|
+
onClick: handleClick,
|
|
777
|
+
className,
|
|
778
|
+
style,
|
|
779
|
+
...{ ...rest, disabled: isDisabled }
|
|
780
|
+
},
|
|
781
|
+
children
|
|
782
|
+
) : /* @__PURE__ */ import_react5.default.createElement(
|
|
783
|
+
"button",
|
|
784
|
+
{
|
|
785
|
+
ref,
|
|
786
|
+
type: type || "button",
|
|
787
|
+
className,
|
|
788
|
+
style,
|
|
789
|
+
onClick: handleClick,
|
|
790
|
+
disabled: isDisabled,
|
|
791
|
+
...rest
|
|
792
|
+
},
|
|
793
|
+
children
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
Switch.displayName = "ChessClock.Switch";
|
|
798
|
+
|
|
799
|
+
// src/components/ChessClock/parts/PlayPause.tsx
|
|
800
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
801
|
+
var import_react_slot2 = require("@radix-ui/react-slot");
|
|
802
|
+
var DEFAULT_CONTENT = {
|
|
803
|
+
start: "Start",
|
|
804
|
+
pause: "Pause",
|
|
805
|
+
resume: "Resume",
|
|
806
|
+
delayed: "Start",
|
|
807
|
+
finished: "Game Over"
|
|
808
|
+
};
|
|
809
|
+
var resolveContent = (isFinished, isDelayed, shouldShowStart, isPaused, isRunning, customContent) => {
|
|
810
|
+
if (isFinished) {
|
|
811
|
+
return customContent.finishedContent ?? DEFAULT_CONTENT.finished;
|
|
812
|
+
}
|
|
813
|
+
if (isDelayed) {
|
|
814
|
+
return customContent.delayedContent ?? DEFAULT_CONTENT.delayed;
|
|
815
|
+
}
|
|
816
|
+
if (shouldShowStart) {
|
|
817
|
+
return customContent.startContent ?? DEFAULT_CONTENT.start;
|
|
818
|
+
}
|
|
819
|
+
if (isPaused) {
|
|
820
|
+
return customContent.resumeContent ?? DEFAULT_CONTENT.resume;
|
|
821
|
+
}
|
|
822
|
+
if (isRunning) {
|
|
823
|
+
return customContent.pauseContent ?? DEFAULT_CONTENT.pause;
|
|
824
|
+
}
|
|
825
|
+
return DEFAULT_CONTENT.start;
|
|
826
|
+
};
|
|
827
|
+
var PlayPause = import_react6.default.forwardRef(
|
|
828
|
+
({
|
|
829
|
+
asChild = false,
|
|
830
|
+
startContent,
|
|
831
|
+
pauseContent,
|
|
832
|
+
resumeContent,
|
|
833
|
+
delayedContent,
|
|
834
|
+
finishedContent,
|
|
835
|
+
children,
|
|
836
|
+
onClick,
|
|
837
|
+
disabled,
|
|
838
|
+
className,
|
|
839
|
+
style,
|
|
840
|
+
type,
|
|
841
|
+
...rest
|
|
842
|
+
}, ref) => {
|
|
843
|
+
const { status, methods } = useChessClockContext();
|
|
844
|
+
const isIdle = status === "idle";
|
|
845
|
+
const isDelayed = status === "delayed";
|
|
846
|
+
const isPaused = status === "paused";
|
|
847
|
+
const isRunning = status === "running";
|
|
848
|
+
const isFinished = status === "finished";
|
|
849
|
+
const shouldShowStart = isIdle || isDelayed;
|
|
850
|
+
const isDisabled = disabled || isFinished || isDelayed;
|
|
851
|
+
const handleClick = import_react6.default.useCallback(
|
|
852
|
+
(e) => {
|
|
853
|
+
if (shouldShowStart) {
|
|
854
|
+
methods.start();
|
|
855
|
+
} else if (isPaused) {
|
|
856
|
+
methods.resume();
|
|
857
|
+
} else if (isRunning) {
|
|
858
|
+
methods.pause();
|
|
859
|
+
}
|
|
860
|
+
onClick == null ? void 0 : onClick(e);
|
|
861
|
+
},
|
|
862
|
+
[shouldShowStart, isPaused, isRunning, methods, onClick]
|
|
863
|
+
);
|
|
864
|
+
const content = children ?? resolveContent(
|
|
865
|
+
isFinished,
|
|
866
|
+
isDelayed,
|
|
867
|
+
shouldShowStart,
|
|
868
|
+
isPaused,
|
|
869
|
+
isRunning,
|
|
870
|
+
{
|
|
871
|
+
startContent,
|
|
872
|
+
pauseContent,
|
|
873
|
+
resumeContent,
|
|
874
|
+
delayedContent,
|
|
875
|
+
finishedContent
|
|
876
|
+
}
|
|
877
|
+
);
|
|
878
|
+
return asChild ? /* @__PURE__ */ import_react6.default.createElement(
|
|
879
|
+
import_react_slot2.Slot,
|
|
880
|
+
{
|
|
881
|
+
ref,
|
|
882
|
+
onClick: handleClick,
|
|
883
|
+
className,
|
|
884
|
+
style,
|
|
885
|
+
...{ ...rest, disabled: isDisabled }
|
|
886
|
+
},
|
|
887
|
+
content
|
|
888
|
+
) : /* @__PURE__ */ import_react6.default.createElement(
|
|
889
|
+
"button",
|
|
890
|
+
{
|
|
891
|
+
ref,
|
|
892
|
+
type: type || "button",
|
|
893
|
+
className,
|
|
894
|
+
style,
|
|
895
|
+
onClick: handleClick,
|
|
896
|
+
disabled: isDisabled,
|
|
897
|
+
...rest
|
|
898
|
+
},
|
|
899
|
+
content
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
PlayPause.displayName = "ChessClock.PlayPause";
|
|
904
|
+
|
|
905
|
+
// src/components/ChessClock/parts/Reset.tsx
|
|
906
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
907
|
+
var import_react_slot3 = require("@radix-ui/react-slot");
|
|
908
|
+
var Reset = import_react7.default.forwardRef(
|
|
909
|
+
({
|
|
910
|
+
asChild = false,
|
|
911
|
+
timeControl,
|
|
912
|
+
children,
|
|
913
|
+
onClick,
|
|
914
|
+
disabled,
|
|
915
|
+
className,
|
|
916
|
+
style,
|
|
917
|
+
type,
|
|
918
|
+
...rest
|
|
919
|
+
}, ref) => {
|
|
920
|
+
const { methods, status } = useChessClockContext();
|
|
921
|
+
const isDisabled = disabled || status === "idle";
|
|
922
|
+
const handleClick = import_react7.default.useCallback(
|
|
923
|
+
(e) => {
|
|
924
|
+
methods.reset(timeControl);
|
|
925
|
+
onClick == null ? void 0 : onClick(e);
|
|
926
|
+
},
|
|
927
|
+
[methods, timeControl, onClick]
|
|
928
|
+
);
|
|
929
|
+
return asChild ? /* @__PURE__ */ import_react7.default.createElement(
|
|
930
|
+
import_react_slot3.Slot,
|
|
931
|
+
{
|
|
932
|
+
ref,
|
|
933
|
+
onClick: handleClick,
|
|
934
|
+
className,
|
|
935
|
+
style,
|
|
936
|
+
...{ ...rest, disabled: isDisabled }
|
|
937
|
+
},
|
|
938
|
+
children
|
|
939
|
+
) : /* @__PURE__ */ import_react7.default.createElement(
|
|
940
|
+
"button",
|
|
941
|
+
{
|
|
942
|
+
ref,
|
|
943
|
+
type: type || "button",
|
|
944
|
+
className,
|
|
945
|
+
style,
|
|
946
|
+
onClick: handleClick,
|
|
947
|
+
disabled: isDisabled,
|
|
948
|
+
...rest
|
|
949
|
+
},
|
|
950
|
+
children
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
Reset.displayName = "ChessClock.Reset";
|
|
955
|
+
|
|
956
|
+
// src/components/ChessClock/index.ts
|
|
957
|
+
var ChessClock = {
|
|
958
|
+
Root,
|
|
959
|
+
Display,
|
|
960
|
+
Switch,
|
|
961
|
+
PlayPause,
|
|
962
|
+
Reset
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/utils/presets.ts
|
|
966
|
+
var presets = {
|
|
967
|
+
// Bullet (< 3 minutes)
|
|
968
|
+
bullet1_0: { baseTime: 60, increment: 0 },
|
|
969
|
+
bullet1_1: { baseTime: 60, increment: 1 },
|
|
970
|
+
bullet2_1: { baseTime: 120, increment: 1 },
|
|
971
|
+
// Blitz (3-10 minutes)
|
|
972
|
+
blitz3_0: { baseTime: 180, increment: 0 },
|
|
973
|
+
blitz3_2: { baseTime: 180, increment: 2 },
|
|
974
|
+
blitz5_0: { baseTime: 300, increment: 0 },
|
|
975
|
+
blitz5_3: { baseTime: 300, increment: 3 },
|
|
976
|
+
// Rapid (10-60 minutes)
|
|
977
|
+
rapid10_0: { baseTime: 600, increment: 0 },
|
|
978
|
+
rapid10_5: { baseTime: 600, increment: 5 },
|
|
979
|
+
rapid15_10: { baseTime: 900, increment: 10 },
|
|
980
|
+
// Classical (≥ 60 minutes)
|
|
981
|
+
classical30_0: { baseTime: 1800, increment: 0 },
|
|
982
|
+
classical90_30: { baseTime: 5400, increment: 30 },
|
|
983
|
+
// Tournament (multi-period)
|
|
984
|
+
fideClassical: [
|
|
985
|
+
{ baseTime: 5400, increment: 30, moves: 40 },
|
|
986
|
+
{ baseTime: 1800, increment: 30, moves: 20 },
|
|
987
|
+
{ baseTime: 900, increment: 30 }
|
|
988
|
+
],
|
|
989
|
+
uscfClassical: [
|
|
990
|
+
{ baseTime: 7200, moves: 40 },
|
|
991
|
+
{ baseTime: 3600, moves: 20 },
|
|
992
|
+
{ baseTime: 1800 }
|
|
993
|
+
]
|
|
994
|
+
};
|
|
995
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
996
|
+
0 && (module.exports = {
|
|
997
|
+
ChessClock,
|
|
998
|
+
ChessClockContext,
|
|
999
|
+
Display,
|
|
1000
|
+
PlayPause,
|
|
1001
|
+
Reset,
|
|
1002
|
+
Switch,
|
|
1003
|
+
formatClockTime,
|
|
1004
|
+
getInitialTimes,
|
|
1005
|
+
normalizeTimeControl,
|
|
1006
|
+
parseMultiPeriodTimeControl,
|
|
1007
|
+
parseTimeControlConfig,
|
|
1008
|
+
parseTimeControlString,
|
|
1009
|
+
presets,
|
|
1010
|
+
useChessClock,
|
|
1011
|
+
useChessClockContext,
|
|
1012
|
+
useOptionalChessClock
|
|
1013
|
+
});
|
|
1014
|
+
//# sourceMappingURL=index.cjs.map
|