@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
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useReducer,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import type {
|
|
10
|
+
ClockColor,
|
|
11
|
+
ClockInfo,
|
|
12
|
+
ClockMethods,
|
|
13
|
+
ClockTimes,
|
|
14
|
+
PeriodState,
|
|
15
|
+
TimeControlConfig,
|
|
16
|
+
TimeControlInput,
|
|
17
|
+
UseChessClockReturn,
|
|
18
|
+
} from "../types";
|
|
19
|
+
import { parseTimeControlConfig, getInitialTimes } from "../utils/timeControl";
|
|
20
|
+
import { formatClockTime } from "../utils/formatTime";
|
|
21
|
+
import { calculateSwitchTime } from "../utils/calculateSwitchTime";
|
|
22
|
+
import {
|
|
23
|
+
getInitialActivePlayer,
|
|
24
|
+
getInitialStatus,
|
|
25
|
+
} from "../utils/timingMethods";
|
|
26
|
+
import { clockReducer, createInitialClockState } from "./clockReducer";
|
|
27
|
+
|
|
28
|
+
/** Default config used internally when clock is disabled */
|
|
29
|
+
const DISABLED_CLOCK_CONFIG: TimeControlConfig = {
|
|
30
|
+
time: { baseTime: 0 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Serializes time-relevant config for change detection.
|
|
35
|
+
* Only includes properties that should trigger a clock reset.
|
|
36
|
+
* Callbacks are intentionally excluded - they use the ref pattern.
|
|
37
|
+
*/
|
|
38
|
+
function serializeTimeRelevantConfig(config: TimeControlConfig): string {
|
|
39
|
+
return JSON.stringify({
|
|
40
|
+
time: config.time,
|
|
41
|
+
timingMethod: config.timingMethod,
|
|
42
|
+
clockStart: config.clockStart,
|
|
43
|
+
whiteTime: config.whiteTime,
|
|
44
|
+
blackTime: config.blackTime,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function calculateDisplayTime(
|
|
49
|
+
baseTime: number,
|
|
50
|
+
moveStartTime: number | null,
|
|
51
|
+
elapsedAtPause: number,
|
|
52
|
+
timingMethod: string,
|
|
53
|
+
delay: number,
|
|
54
|
+
): number {
|
|
55
|
+
// When paused, use the elapsed time stored at pause moment
|
|
56
|
+
if (moveStartTime === null) {
|
|
57
|
+
let effectiveElapsed = elapsedAtPause;
|
|
58
|
+
if (timingMethod === "delay") {
|
|
59
|
+
effectiveElapsed = Math.max(0, elapsedAtPause - delay);
|
|
60
|
+
}
|
|
61
|
+
return Math.max(0, baseTime - effectiveElapsed);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const rawElapsed = now - moveStartTime;
|
|
66
|
+
|
|
67
|
+
// Apply delay method: time doesn't decrement during delay period
|
|
68
|
+
let effectiveElapsed = rawElapsed;
|
|
69
|
+
if (timingMethod === "delay") {
|
|
70
|
+
effectiveElapsed = Math.max(0, rawElapsed - delay);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Math.max(0, baseTime - effectiveElapsed);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Main hook for chess clock state management.
|
|
78
|
+
* Provides timing functionality for chess games with various timing methods.
|
|
79
|
+
*
|
|
80
|
+
* For server-authoritative clocks, use `methods.setTime()` to sync server times.
|
|
81
|
+
* The clock will restart its display interpolation from the new value on each call.
|
|
82
|
+
*
|
|
83
|
+
* **Auto-reset behavior:** The clock automatically resets when time-relevant
|
|
84
|
+
* options change (`time`, `timingMethod`, `clockStart`, `whiteTime`, `blackTime`).
|
|
85
|
+
* Callbacks (`onTimeout`, `onSwitch`, `onTimeUpdate`) do not trigger a reset
|
|
86
|
+
* and can be changed without affecting clock state.
|
|
87
|
+
*
|
|
88
|
+
* @param options - Clock configuration options
|
|
89
|
+
* @returns Clock state, info, and methods
|
|
90
|
+
*/
|
|
91
|
+
export function useChessClock(options: TimeControlConfig): UseChessClockReturn {
|
|
92
|
+
// Initialize reducer with computed initial state.
|
|
93
|
+
// All parsing happens inside the initializer function to ensure it only runs once
|
|
94
|
+
// (on mount), avoiding wasteful re-computation when options is passed inline
|
|
95
|
+
// as a new object reference on every render.
|
|
96
|
+
const [state, dispatch] = useReducer(clockReducer, null, () => {
|
|
97
|
+
const initialConfig = parseTimeControlConfig(options);
|
|
98
|
+
const initialTimesValue = getInitialTimes(initialConfig);
|
|
99
|
+
|
|
100
|
+
// Initialize period state for multi-period time controls
|
|
101
|
+
const initialPeriodState: PeriodState | undefined =
|
|
102
|
+
initialConfig.periods && initialConfig.periods.length > 1
|
|
103
|
+
? {
|
|
104
|
+
periodIndex: { white: 0, black: 0 },
|
|
105
|
+
periodMoves: { white: 0, black: 0 },
|
|
106
|
+
periods: initialConfig.periods,
|
|
107
|
+
}
|
|
108
|
+
: undefined;
|
|
109
|
+
|
|
110
|
+
return createInitialClockState(
|
|
111
|
+
initialTimesValue,
|
|
112
|
+
getInitialStatus(initialConfig.clockStart),
|
|
113
|
+
getInitialActivePlayer(initialConfig.clockStart),
|
|
114
|
+
initialConfig,
|
|
115
|
+
initialPeriodState,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Options ref for callbacks (avoid stale closures)
|
|
120
|
+
const optionsRef = useRef(options);
|
|
121
|
+
optionsRef.current = options;
|
|
122
|
+
|
|
123
|
+
// State ref for callbacks (avoid stale closures)
|
|
124
|
+
const stateRef = useRef(state);
|
|
125
|
+
stateRef.current = state;
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// AUTO-RESET ON OPTIONS CHANGE
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
// Auto-reset when time-relevant options change (not callbacks)
|
|
132
|
+
const configKey = serializeTimeRelevantConfig(options);
|
|
133
|
+
const prevConfigRef = useRef<string>(configKey);
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (prevConfigRef.current !== configKey) {
|
|
137
|
+
prevConfigRef.current = configKey;
|
|
138
|
+
dispatch({ type: "RESET", payload: { ...options, now: Date.now() } });
|
|
139
|
+
}
|
|
140
|
+
}, [configKey, options]);
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// DISPLAY STATE
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
// Tick counter for triggering re-renders when clock is running
|
|
147
|
+
const [tick, forceUpdate] = useState(0);
|
|
148
|
+
|
|
149
|
+
// Display times derived during render (no useMemo — state values change frequently)
|
|
150
|
+
const displayTimes: ClockTimes =
|
|
151
|
+
state.activePlayer === null
|
|
152
|
+
? state.times
|
|
153
|
+
: {
|
|
154
|
+
...state.times,
|
|
155
|
+
[state.activePlayer]: calculateDisplayTime(
|
|
156
|
+
state.times[state.activePlayer],
|
|
157
|
+
state.moveStartTime,
|
|
158
|
+
state.elapsedAtPause,
|
|
159
|
+
state.config.timingMethod,
|
|
160
|
+
state.config.delay,
|
|
161
|
+
),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Derive primitive for timeout detection (prevents effect running on every render)
|
|
165
|
+
const activePlayerTimedOut =
|
|
166
|
+
state.status === "running" &&
|
|
167
|
+
state.activePlayer !== null &&
|
|
168
|
+
displayTimes[state.activePlayer] <= 0;
|
|
169
|
+
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// DISPLAY UPDATE LOOP (100ms interval)
|
|
172
|
+
// ============================================================================
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (state.status !== "running" || state.activePlayer === null) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const intervalId = setInterval(() => forceUpdate((c) => c + 1), 100);
|
|
180
|
+
|
|
181
|
+
return () => clearInterval(intervalId);
|
|
182
|
+
}, [state.status, state.activePlayer]);
|
|
183
|
+
|
|
184
|
+
// Notify consumer of time updates (only on clock ticks, not on every render)
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (state.status === "running" && state.activePlayer !== null) {
|
|
187
|
+
optionsRef.current.onTimeUpdate?.(displayTimes);
|
|
188
|
+
}
|
|
189
|
+
}, [tick]);
|
|
190
|
+
|
|
191
|
+
// Timeout Detection
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (activePlayerTimedOut && state.activePlayer !== null) {
|
|
194
|
+
dispatch({ type: "TIMEOUT", payload: { player: state.activePlayer } });
|
|
195
|
+
optionsRef.current.onTimeout?.(state.activePlayer);
|
|
196
|
+
}
|
|
197
|
+
}, [activePlayerTimedOut, state.activePlayer]);
|
|
198
|
+
|
|
199
|
+
// Active Player Change Callback
|
|
200
|
+
const previousActivePlayerRef = useRef(state.activePlayer);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (
|
|
203
|
+
state.activePlayer !== previousActivePlayerRef.current &&
|
|
204
|
+
state.activePlayer !== null
|
|
205
|
+
) {
|
|
206
|
+
optionsRef.current.onSwitch?.(state.activePlayer);
|
|
207
|
+
}
|
|
208
|
+
previousActivePlayerRef.current = state.activePlayer;
|
|
209
|
+
}, [state.activePlayer]);
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// COMPUTED INFO
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
const info = useMemo<ClockInfo>(
|
|
216
|
+
() => ({
|
|
217
|
+
isRunning: state.status === "running",
|
|
218
|
+
isPaused: state.status === "paused",
|
|
219
|
+
isFinished: state.status === "finished",
|
|
220
|
+
isWhiteActive: state.activePlayer === "white",
|
|
221
|
+
isBlackActive: state.activePlayer === "black",
|
|
222
|
+
hasTimeout: state.timeout !== null,
|
|
223
|
+
// Time odds is based on initial configuration, not current remaining time
|
|
224
|
+
hasTimeOdds: state.initialTimes.white !== state.initialTimes.black,
|
|
225
|
+
}),
|
|
226
|
+
[
|
|
227
|
+
state.status,
|
|
228
|
+
state.activePlayer,
|
|
229
|
+
state.timeout,
|
|
230
|
+
state.initialTimes.white,
|
|
231
|
+
state.initialTimes.black,
|
|
232
|
+
],
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// METHODS
|
|
237
|
+
// ============================================================================
|
|
238
|
+
|
|
239
|
+
const start = useCallback(() => {
|
|
240
|
+
dispatch({ type: "START", payload: { now: Date.now() } });
|
|
241
|
+
}, []);
|
|
242
|
+
|
|
243
|
+
const pause = useCallback(() => {
|
|
244
|
+
dispatch({ type: "PAUSE", payload: { now: Date.now() } });
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
const resume = useCallback(() => {
|
|
248
|
+
dispatch({ type: "RESUME", payload: { now: Date.now() } });
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const switchPlayer = useCallback(() => {
|
|
252
|
+
const currentState = stateRef.current;
|
|
253
|
+
const now = Date.now();
|
|
254
|
+
|
|
255
|
+
// Calculate time adjustment for current player (not in delayed mode)
|
|
256
|
+
let newTimes: ClockTimes | undefined;
|
|
257
|
+
if (
|
|
258
|
+
currentState.status !== "delayed" &&
|
|
259
|
+
currentState.activePlayer &&
|
|
260
|
+
currentState.moveStartTime !== null
|
|
261
|
+
) {
|
|
262
|
+
const timeSpent = now - currentState.moveStartTime;
|
|
263
|
+
const currentTime = currentState.times[currentState.activePlayer];
|
|
264
|
+
|
|
265
|
+
const newTime = calculateSwitchTime(
|
|
266
|
+
currentTime,
|
|
267
|
+
timeSpent,
|
|
268
|
+
currentState.config,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
newTimes = {
|
|
272
|
+
...currentState.times,
|
|
273
|
+
[currentState.activePlayer]: newTime,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
dispatch({ type: "SWITCH", payload: { newTimes, now } });
|
|
278
|
+
}, []);
|
|
279
|
+
|
|
280
|
+
const reset = useCallback((newTimeControl?: TimeControlInput) => {
|
|
281
|
+
// Build the full config for the reducer
|
|
282
|
+
const currentOptions = optionsRef.current;
|
|
283
|
+
const resetConfig: TimeControlConfig = newTimeControl
|
|
284
|
+
? { ...currentOptions, time: newTimeControl }
|
|
285
|
+
: currentOptions;
|
|
286
|
+
|
|
287
|
+
// The reducer will parse the config, update state.config, and reset timing state
|
|
288
|
+
dispatch({ type: "RESET", payload: { ...resetConfig, now: Date.now() } });
|
|
289
|
+
}, []);
|
|
290
|
+
|
|
291
|
+
const addTime = useCallback((player: ClockColor, milliseconds: number) => {
|
|
292
|
+
dispatch({
|
|
293
|
+
type: "ADD_TIME",
|
|
294
|
+
payload: { player, milliseconds, now: Date.now() },
|
|
295
|
+
});
|
|
296
|
+
// Note: when adding time while clock is running, we update the base time
|
|
297
|
+
// but moveStartTime stays the same, so the display interpolates correctly
|
|
298
|
+
}, []);
|
|
299
|
+
|
|
300
|
+
const setTime = useCallback((player: ClockColor, milliseconds: number) => {
|
|
301
|
+
dispatch({
|
|
302
|
+
type: "SET_TIME",
|
|
303
|
+
payload: { player, milliseconds, now: Date.now() },
|
|
304
|
+
});
|
|
305
|
+
// Note: when setting time while clock is running, we update the base time
|
|
306
|
+
// but moveStartTime stays the same, so the display interpolates correctly
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
// Memoize methods
|
|
310
|
+
const methods = useMemo<ClockMethods>(
|
|
311
|
+
() => ({
|
|
312
|
+
start,
|
|
313
|
+
pause,
|
|
314
|
+
resume,
|
|
315
|
+
switch: switchPlayer,
|
|
316
|
+
reset,
|
|
317
|
+
addTime,
|
|
318
|
+
setTime,
|
|
319
|
+
}),
|
|
320
|
+
[start, pause, resume, switchPlayer, reset, addTime, setTime],
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
// Computed period information
|
|
324
|
+
const periodInfo = useMemo(() => {
|
|
325
|
+
if (!state.periodState) {
|
|
326
|
+
// Single period: return defaults
|
|
327
|
+
return {
|
|
328
|
+
currentPeriodIndex: { white: 0, black: 0 },
|
|
329
|
+
totalPeriods: 1,
|
|
330
|
+
currentPeriod: {
|
|
331
|
+
white: {
|
|
332
|
+
baseTime: state.config.baseTime / 1000,
|
|
333
|
+
increment: state.config.increment / 1000,
|
|
334
|
+
delay: state.config.delay / 1000,
|
|
335
|
+
},
|
|
336
|
+
black: {
|
|
337
|
+
baseTime: state.config.baseTime / 1000,
|
|
338
|
+
increment: state.config.increment / 1000,
|
|
339
|
+
delay: state.config.delay / 1000,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
periodMoves: { white: 0, black: 0 },
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Get current period for each player (with bounds checking)
|
|
347
|
+
// Convert from internal milliseconds back to seconds for public API
|
|
348
|
+
const periods = state.periodState.periods;
|
|
349
|
+
const getCurrentPeriod = (periodIndex: number) => {
|
|
350
|
+
const period =
|
|
351
|
+
periodIndex >= 0 && periodIndex < periods.length
|
|
352
|
+
? periods[periodIndex]
|
|
353
|
+
: periods[0];
|
|
354
|
+
return {
|
|
355
|
+
...period,
|
|
356
|
+
baseTime: period.baseTime / 1000,
|
|
357
|
+
increment:
|
|
358
|
+
period.increment !== undefined ? period.increment / 1000 : undefined,
|
|
359
|
+
delay: period.delay !== undefined ? period.delay / 1000 : undefined,
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
currentPeriodIndex: state.periodState.periodIndex,
|
|
365
|
+
totalPeriods: periods.length,
|
|
366
|
+
currentPeriod: {
|
|
367
|
+
white: getCurrentPeriod(state.periodState.periodIndex.white),
|
|
368
|
+
black: getCurrentPeriod(state.periodState.periodIndex.black),
|
|
369
|
+
},
|
|
370
|
+
periodMoves: state.periodState.periodMoves,
|
|
371
|
+
};
|
|
372
|
+
}, [state.periodState, state.config]);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
times: displayTimes,
|
|
376
|
+
initialTimes: state.initialTimes,
|
|
377
|
+
status: state.status,
|
|
378
|
+
activePlayer: state.activePlayer,
|
|
379
|
+
timeout: state.timeout,
|
|
380
|
+
timingMethod: state.config.timingMethod,
|
|
381
|
+
info,
|
|
382
|
+
...periodInfo,
|
|
383
|
+
methods,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Optional chess clock hook for cases where clock may not be needed
|
|
389
|
+
* Maintains hook order by always calling the implementation internally
|
|
390
|
+
*
|
|
391
|
+
* @param options - Clock configuration options, or undefined to disable
|
|
392
|
+
* @returns Clock state, info, and methods, or null if disabled
|
|
393
|
+
*/
|
|
394
|
+
export function useOptionalChessClock(
|
|
395
|
+
options?: TimeControlConfig,
|
|
396
|
+
): ReturnType<typeof useChessClock> | null {
|
|
397
|
+
// Always call useChessClock to maintain hook order
|
|
398
|
+
const result = useChessClock(options ?? DISABLED_CLOCK_CONFIG);
|
|
399
|
+
|
|
400
|
+
return options === undefined ? null : result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Export the format function for convenience
|
|
405
|
+
*/
|
|
406
|
+
export { formatClockTime };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { UseChessClockReturn } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context for chess clock state
|
|
6
|
+
* Provided by ChessClock.Root and consumed by child components
|
|
7
|
+
*/
|
|
8
|
+
export const ChessClockContext = createContext<UseChessClockReturn | null>(
|
|
9
|
+
null,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook to access chess clock context from child components
|
|
14
|
+
* Must be used within a ChessClock.Root provider
|
|
15
|
+
*
|
|
16
|
+
* @throws Error if used outside of ChessClock.Root
|
|
17
|
+
* @returns Chess clock state and methods
|
|
18
|
+
*/
|
|
19
|
+
export function useChessClockContext(): UseChessClockReturn {
|
|
20
|
+
const context = useContext(ChessClockContext);
|
|
21
|
+
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"useChessClockContext must be used within a ChessClock.Root component. " +
|
|
25
|
+
"Make sure your component is wrapped with <ChessClock.Root>.",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Type alias for the context value type
|
|
34
|
+
*/
|
|
35
|
+
export type ChessClockContextType = UseChessClockReturn;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Components
|
|
3
|
+
// ============================================================================
|
|
4
|
+
export { ChessClock } from "./components/ChessClock";
|
|
5
|
+
export { Display } from "./components/ChessClock/parts/Display";
|
|
6
|
+
export { Switch } from "./components/ChessClock/parts/Switch";
|
|
7
|
+
export { PlayPause } from "./components/ChessClock/parts/PlayPause";
|
|
8
|
+
export { Reset } from "./components/ChessClock/parts/Reset";
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Hooks
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export { useChessClock, useOptionalChessClock } from "./hooks/useChessClock";
|
|
14
|
+
export {
|
|
15
|
+
useChessClockContext,
|
|
16
|
+
ChessClockContext,
|
|
17
|
+
} from "./hooks/useChessClockContext";
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Utilities
|
|
21
|
+
// ============================================================================
|
|
22
|
+
export { formatClockTime } from "./utils/formatTime";
|
|
23
|
+
export {
|
|
24
|
+
parseTimeControlString,
|
|
25
|
+
normalizeTimeControl,
|
|
26
|
+
parseTimeControlConfig,
|
|
27
|
+
getInitialTimes,
|
|
28
|
+
parseMultiPeriodTimeControl,
|
|
29
|
+
} from "./utils/timeControl";
|
|
30
|
+
export { presets } from "./utils/presets";
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Types
|
|
34
|
+
// ============================================================================
|
|
35
|
+
export type {
|
|
36
|
+
// Time control types
|
|
37
|
+
TimeControlString,
|
|
38
|
+
TimeControl,
|
|
39
|
+
TimeControlPhase,
|
|
40
|
+
TimeControlInput,
|
|
41
|
+
TimingMethod,
|
|
42
|
+
ClockStartMode,
|
|
43
|
+
TimeControlConfig,
|
|
44
|
+
NormalizedTimeControl,
|
|
45
|
+
PeriodState,
|
|
46
|
+
// State types
|
|
47
|
+
ClockStatus,
|
|
48
|
+
ClockColor,
|
|
49
|
+
ClockTimes,
|
|
50
|
+
ClockInfo,
|
|
51
|
+
ClockMethods,
|
|
52
|
+
UseChessClockReturn,
|
|
53
|
+
// Utility types
|
|
54
|
+
TimeFormat,
|
|
55
|
+
TimingMethodResult,
|
|
56
|
+
} from "./types";
|
|
57
|
+
|
|
58
|
+
// Component prop types are exported via the ChessClock component
|
|
59
|
+
export type {
|
|
60
|
+
ChessClockRootProps,
|
|
61
|
+
ChessClockDisplayProps,
|
|
62
|
+
ChessClockControlProps,
|
|
63
|
+
ChessClockPlayPauseProps,
|
|
64
|
+
ChessClockResetProps,
|
|
65
|
+
} from "./components/ChessClock";
|