@chessviewer-org/chess-viewer 1.0.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/README.md ADDED
@@ -0,0 +1,494 @@
1
+ # @chessviewer-org/chess-viewer
2
+
3
+ Chess diagram generator and FEN utilities. Parse and edit FEN positions, render SVG board diagrams, manipulate boards, work with colors, board themes, presets, images, and history — in Node.js or the browser with **no dependencies and no DOM required**.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@chessviewer-org/chess-viewer)](https://bundlephobia.com/package/@chessviewer-org/chess-viewer)
8
+ [![CI](https://github.com/chessviewer-org/chess-viewer-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/chessviewer-org/chess-viewer-utils/actions/workflows/ci.yml)
9
+ [![types](https://img.shields.io/npm/types/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)
10
+ [![license](https://img.shields.io/npm/l/@chessviewer-org/chess-viewer)](LICENSE)
11
+
12
+ - **Zero dependencies** — nothing pulled into your tree.
13
+ - **Universal** — runs in Node.js and the browser, no DOM, no canvas, no network.
14
+ - **Dual ESM + CJS** with first-class TypeScript types.
15
+ - **Batteries included** — FEN parsing/editing, SVG rendering with an inline piece set, 20 board themes, color & DPI utilities, and more.
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @chessviewer-org/chess-viewer
23
+ # or
24
+ pnpm add @chessviewer-org/chess-viewer
25
+ # or
26
+ yarn add @chessviewer-org/chess-viewer
27
+ ```
28
+
29
+ **Requirements:** Node.js ≥ 18, or any modern browser.
30
+
31
+ ---
32
+
33
+ ## Quick start
34
+
35
+ ```ts
36
+ import { generateDiagram, parseFEN, validateFEN } from '@chessviewer-org/chess-viewer';
37
+
38
+ // Generate a self-contained SVG diagram
39
+ const svg = generateDiagram({
40
+ fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
41
+ size: 400,
42
+ showCoords: true,
43
+ lightSquare: '#f0d9b5',
44
+ darkSquare: '#b58863',
45
+ });
46
+
47
+ // Write to a file (Node.js)
48
+ import { writeFileSync } from 'fs';
49
+ writeFileSync('board.svg', svg);
50
+
51
+ // Or embed directly in HTML
52
+ document.getElementById('board').innerHTML = svg;
53
+ ```
54
+
55
+ ### Example output
56
+
57
+ The diagram below is real SVG produced by `generateDiagram` — no images, no canvas:
58
+
59
+ <p align="center">
60
+ <img src="https://raw.githubusercontent.com/chessviewer-org/chess-viewer-utils/main/assets/preview.svg" width="360" alt="Chess diagram rendered by chess-vision (Italian Game, Wood theme)">
61
+ </p>
62
+
63
+ ---
64
+
65
+ ## Versioning
66
+
67
+ This package follows [Semantic Versioning](https://semver.org/):
68
+ - **Patch** (`1.0.x`) — bug fixes, no API changes
69
+ - **Minor** (`1.x.0`) — new exports added, fully backward-compatible
70
+ - **Major** (`x.0.0`) — breaking API changes
71
+
72
+ ### Install a specific version
73
+
74
+ ```bash
75
+ # Latest stable
76
+ npm install @chessviewer-org/chess-viewer
77
+
78
+ # Specific version
79
+ npm install @chessviewer-org/chess-viewer@1.0.0
80
+
81
+ # Latest minor of a major (e.g. 1.x)
82
+ npm install @chessviewer-org/chess-viewer@^1.0.0
83
+
84
+ # Exact patch
85
+ npm install @chessviewer-org/chess-viewer@~1.0.0
86
+ ```
87
+
88
+ ### Check what version you have
89
+
90
+ ```bash
91
+ npm list @chessviewer-org/chess-viewer
92
+ ```
93
+
94
+ ### Check the latest version on npm
95
+
96
+ ```bash
97
+ npm view @chessviewer-org/chess-viewer version
98
+ # or all published versions:
99
+ npm view @chessviewer-org/chess-viewer versions --json
100
+ ```
101
+
102
+ ### Upgrade to latest
103
+
104
+ ```bash
105
+ npm update @chessviewer-org/chess-viewer
106
+ # or force latest:
107
+ npm install @chessviewer-org/chess-viewer@latest
108
+ ```
109
+
110
+ See [CHANGELOG.md](CHANGELOG.md) for what changed in each release.
111
+
112
+ ---
113
+
114
+ ## API Reference
115
+
116
+ ### `generateDiagram(options)`
117
+
118
+ Generates a self-contained SVG chess diagram. No DOM, no network requests.
119
+
120
+ ```ts
121
+ import { generateDiagram } from '@chessviewer-org/chess-viewer';
122
+
123
+ const svg = generateDiagram({
124
+ fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',
125
+ size: 480, // board pixel size (default: 400)
126
+ showCoords: true, // show a/b/c… and 1/2/3… labels (default: false)
127
+ flipped: false, // show from Black's perspective (default: false)
128
+ showFrame: false, // thin outer frame (default: false)
129
+ lightSquare: '#d4af7a',
130
+ darkSquare: '#8b4513',
131
+ coordColor: 'white', // hex or 'white' | 'black' — keep coords legible on dark boards (default: '#000000')
132
+ label: 'Starting position after 1.e4', // aria-label (default: 'Chess position')
133
+ });
134
+ // → '<svg xmlns="http://www.w3.org/2000/svg" …>…</svg>'
135
+ ```
136
+
137
+ ---
138
+
139
+ ### FEN utilities
140
+
141
+ ```ts
142
+ import {
143
+ parseFEN,
144
+ validateFEN,
145
+ validateFENDetailed,
146
+ getFENValidationError,
147
+ boardToFEN,
148
+ createEmptyBoard,
149
+ isBoardEmpty,
150
+ pieceToName,
151
+ describeBoardPosition,
152
+ FENParseError,
153
+ } from '@chessviewer-org/chess-viewer';
154
+
155
+ // Parse FEN → 8×8 matrix
156
+ const board = parseFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
157
+ // board[0][0] === 'r', board[7][4] === 'K'
158
+
159
+ // Quick validity check
160
+ validateFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'); // → true
161
+
162
+ // Human-readable error for UI
163
+ getFENValidationError('bad/fen'); // → 'Board must have 8 ranks'
164
+
165
+ // Detailed error with user-facing messages
166
+ const result = validateFENDetailed('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
167
+ // → { isValid: true, errorMessage: null }
168
+
169
+ // Matrix → FEN piece placement
170
+ boardToFEN(board); // → 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'
171
+
172
+ // Screen-reader description of position
173
+ describeBoardPosition(board);
174
+ // → 'White: white king e1, white queen d1, … Black: black king e8, …'
175
+
176
+ // FEN parse errors
177
+ try {
178
+ parseFEN('not-valid');
179
+ } catch (e) {
180
+ if (e instanceof FENParseError) console.error(e.message);
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Full FEN record
187
+
188
+ Parse and serialize all six FEN fields — board, side to move, castling rights,
189
+ en passant target, and move clocks. Placement-only strings parse too, filling
190
+ standard defaults.
191
+
192
+ ```ts
193
+ import {
194
+ parseFENRecord,
195
+ buildFENRecord,
196
+ toggleActiveColor,
197
+ fenPlacementField,
198
+ normalizeFEN,
199
+ } from '@chessviewer-org/chess-viewer';
200
+
201
+ const record = parseFENRecord('rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12');
202
+ // → { board, activeColor: 'b', castling: 'KQkq', enPassant: 'e3', halfmove: 5, fullmove: 12 }
203
+
204
+ buildFENRecord(record); // → 'rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12'
205
+ toggleActiveColor(record).activeColor; // → 'w' (pure, no mutation)
206
+
207
+ fenPlacementField('rnbqkbnr/… w KQkq - 0 1'); // → 'rnbqkbnr/…'
208
+ normalizeFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR');
209
+ // → 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1'
210
+ ```
211
+
212
+ ---
213
+
214
+ ### Board manipulation
215
+
216
+ Pure helpers for editing positions — every function returns a new board and
217
+ never mutates its input. Squares accept either algebraic strings (`'e4'`) or
218
+ `[row, col]` matrix indices.
219
+
220
+ ```ts
221
+ import {
222
+ cloneBoard,
223
+ getPieceAt, setPieceAt, removePieceAt, movePiece,
224
+ flipBoard, listPieces, countPieces,
225
+ materialBalance, findKing, hasBothKings,
226
+ } from '@chessviewer-org/chess-viewer';
227
+
228
+ const board = parseFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR');
229
+
230
+ getPieceAt(board, 'e1'); // → 'K'
231
+ const next = movePiece(board, 'b1', 'c3'); // develops the knight, returns a new board
232
+ removePieceAt(next, 'd8'); // → new board with Black's queen removed
233
+
234
+ flipBoard(board); // rotate 180° for Black's perspective
235
+ listPieces(board); // → [{ square: 'a8', piece: 'r' }, …] (a8 → h1)
236
+ countPieces(board); // → { r: 2, n: 2, …, P: 8 }
237
+ materialBalance(board); // → 0 (positive = White ahead)
238
+ findKing(board, 'w'); // → 'e1'
239
+ hasBothKings(board); // → true (exactly one king per side)
240
+ ```
241
+
242
+ ---
243
+
244
+ ### Theme & preset helpers
245
+
246
+ Look up board themes, piece sets, and quality presets, with contrast-aware
247
+ helpers for legible coordinates.
248
+
249
+ ```ts
250
+ import {
251
+ getBoardTheme, listThemeIds,
252
+ getPieceSet, pieceSetsByPopularity,
253
+ getQualityPreset,
254
+ themeContrast, themeCoordinateColor,
255
+ } from '@chessviewer-org/chess-viewer';
256
+
257
+ getBoardTheme('ocean'); // → { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }
258
+ listThemeIds(); // → ['classic', 'brown', 'wood', …]
259
+
260
+ getPieceSet('cburnett'); // → { id: 'cburnett', name: 'Classic (CBurnett)' }
261
+ pieceSetsByPopularity()[0]; // → { id: 'cburnett', … } (most popular first)
262
+
263
+ getQualityPreset(2); // → { value: 2, label: 'Print 2× (600 DPI)', … }
264
+
265
+ const theme = getBoardTheme('classic')!;
266
+ themeContrast(theme); // → WCAG contrast ratio between squares
267
+ themeCoordinateColor(theme); // → 'white' | 'black' (best on dark squares)
268
+ ```
269
+
270
+ ---
271
+
272
+ ### Image utilities
273
+
274
+ Read raster dimensions straight from PNG/JPEG headers (no decoding, no DOM) and
275
+ compute physical print sizes — pairs naturally with `changeDPI`.
276
+
277
+ ```ts
278
+ import { readImageDimensions, physicalSize } from '@chessviewer-org/chess-viewer';
279
+
280
+ const bytes = new Uint8Array(await blob.arrayBuffer());
281
+ readImageDimensions(bytes); // → { width: 1200, height: 1200 } | null
282
+
283
+ physicalSize(1200, 300); // → { inches: 4, mm: 101.6 }
284
+ ```
285
+
286
+ ---
287
+
288
+ ### Board themes & constants
289
+
290
+ ```ts
291
+ import {
292
+ BOARD_THEMES,
293
+ PIECE_SETS,
294
+ PIECE_SET_POPULARITY,
295
+ QUALITY_PRESETS,
296
+ DEFAULT_LIGHT_SQUARE,
297
+ DEFAULT_DARK_SQUARE,
298
+ STARTING_FEN,
299
+ EMPTY_FEN,
300
+ } from '@chessviewer-org/chess-viewer';
301
+
302
+ // 20 built-in board color themes
303
+ BOARD_THEMES.classic // { name: 'Classic', light: '#f0d9b5', dark: '#b58863' }
304
+ BOARD_THEMES.ocean // { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }
305
+
306
+ // 23 Lichess-compatible piece sets
307
+ PIECE_SETS[0] // { id: 'alpha', name: 'Alpha' }
308
+
309
+ // Popularity-ranked piece set ids (most → least)
310
+ PIECE_SET_POPULARITY[0] // 'cburnett'
311
+
312
+ // Print/social quality presets (DPI multipliers)
313
+ QUALITY_PRESETS // [{value:1, label:'Print 1× (300 DPI)', mode:'print', …}, …]
314
+
315
+ // Convenient FEN constants
316
+ STARTING_FEN // 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
317
+ EMPTY_FEN // '8/8/8/8/8/8/8/8 w - - 0 1'
318
+ ```
319
+
320
+ ---
321
+
322
+ ### Color utilities
323
+
324
+ ```ts
325
+ import {
326
+ hexToRgb, rgbToHex,
327
+ rgbToHsv, hsvToRgb,
328
+ hexToHsv, hsvToHex,
329
+ relativeLuminance,
330
+ contrastRatio,
331
+ bestTextColor,
332
+ } from '@chessviewer-org/chess-viewer';
333
+
334
+ hexToRgb('#b58863') // → { r: 181, g: 136, b: 99 }
335
+ rgbToHex(181, 136, 99) // → '#b58863'
336
+
337
+ const { h, s, v } = hexToHsv('#b58863');
338
+ hsvToHex(h, s, v) // → '#b58863'
339
+
340
+ contrastRatio('#000000', '#ffffff') // → 21
341
+ bestTextColor('#b58863') // → 'white' | 'black'
342
+ ```
343
+
344
+ ---
345
+
346
+ ### Coordinate utilities
347
+
348
+ ```ts
349
+ import {
350
+ squareToIndices,
351
+ indicesToSquare,
352
+ getSquareBounds,
353
+ isLightSquare,
354
+ getCoordinateParams,
355
+ } from '@chessviewer-org/chess-viewer';
356
+
357
+ squareToIndices('e4') // → [4, 4] (row 0 = rank 8)
358
+ indicesToSquare(4, 4) // → 'e4'
359
+ squareToIndices('a8') // → [0, 0]
360
+ squareToIndices('h1') // → [7, 7]
361
+
362
+ isLightSquare(0, 0) // → true (a8 is light)
363
+ isLightSquare(7, 0) // → false (a1 is dark)
364
+
365
+ // Pixel bounds of a square (for canvas rendering)
366
+ getSquareBounds(0, 0, 50) // → { x:0, y:0, width:50, height:50, centerX:25, centerY:25 }
367
+ ```
368
+
369
+ ---
370
+
371
+ ### History utilities
372
+
373
+ ```ts
374
+ import {
375
+ createHistoryEntry,
376
+ calculateStatus,
377
+ sortByMostRecent,
378
+ applyFilters,
379
+ mergeById,
380
+ convertToArchivedEntry,
381
+ } from '@chessviewer-org/chess-viewer';
382
+
383
+ const entry = createHistoryEntry(
384
+ 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
385
+ 'manual'
386
+ );
387
+ // → { id: 1234567890, fen: '…', createdAt: …, lastActiveAt: …, source: 'manual', isFavorite: false }
388
+
389
+ calculateStatus(entry.lastActiveAt) // → 'green' | 'yellow' | 'red'
390
+
391
+ // Filter entries
392
+ applyFilters(entries, { fenSearch: 'e4', favoritesOnly: false, source: 'manual' });
393
+
394
+ // Merge two lists (e.g. cloud + local), primary wins on id collision
395
+ mergeById(cloudEntries, localEntries);
396
+ ```
397
+
398
+ ---
399
+
400
+ ### Validation utilities
401
+
402
+ ```ts
403
+ import {
404
+ isValidHexColor,
405
+ sanitizeHexColor,
406
+ sanitizeFileName,
407
+ sanitizeInput,
408
+ safeJSONParse,
409
+ } from '@chessviewer-org/chess-viewer';
410
+
411
+ isValidHexColor('#f0d9b5') // → true
412
+ isValidHexColor('red') // → false
413
+
414
+ sanitizeHexColor('bad', '#fff') // → '#fff'
415
+ sanitizeHexColor('#aabbcc') // → '#aabbcc'
416
+
417
+ sanitizeFileName('my<board>') // → 'my-board-'
418
+ sanitizeInput('<script>xss') // → '&lt;script&gt;xss'
419
+
420
+ safeJSONParse('{"a":1}', {}) // → { a: 1 }
421
+ safeJSONParse('bad json', {}) // → {} (fallback, no throw)
422
+ ```
423
+
424
+ ---
425
+
426
+ ### DPI encoding
427
+
428
+ ```ts
429
+ import { changeDPI } from '@chessviewer-org/chess-viewer';
430
+
431
+ // Rewrite DPI metadata in a PNG or JPEG blob
432
+ const correctedBlob = await changeDPI(originalBlob, 300, 'png');
433
+ const correctedJpeg = await changeDPI(originalBlob, 150, 'jpeg');
434
+ ```
435
+
436
+ ---
437
+
438
+ ### Inline piece SVGs
439
+
440
+ ```ts
441
+ import { getPieceSVG, PIECES } from '@chessviewer-org/chess-viewer';
442
+
443
+ getPieceSVG('K') // → '<svg …>…</svg>' (white king, CBurnett style)
444
+ getPieceSVG('k') // → '<svg …>…</svg>' (black king)
445
+ getPieceSVG('X') // → null
446
+
447
+ // All 12 pieces
448
+ Object.keys(PIECES) // → ['wK','wQ','wR','wB','wN','wP','bK','bQ','bR','bB','bN','bP']
449
+ ```
450
+
451
+ ---
452
+
453
+ ## TypeScript
454
+
455
+ This package ships full TypeScript types. No `@types/…` package needed.
456
+
457
+ ```ts
458
+ import type {
459
+ PieceSymbol,
460
+ BoardMatrix,
461
+ DiagramOptions,
462
+ ValidationResult,
463
+ FENRecord,
464
+ ActiveColor,
465
+ SquareRef,
466
+ PiecePlacement,
467
+ ActiveHistoryEntry,
468
+ ArchivedHistoryEntry,
469
+ HistoryFilters,
470
+ BoardTheme,
471
+ QualityPreset,
472
+ PieceSet,
473
+ CoordinateParams,
474
+ SquareBounds,
475
+ ImageDimensions,
476
+ } from '@chessviewer-org/chess-viewer';
477
+ ```
478
+
479
+ ---
480
+
481
+ ## Documentation & project
482
+
483
+ - [Documentation](docs/README.md) — guides, architecture, and references.
484
+ - [Roadmap](ROADMAP.md) — what's planned and what's out of scope.
485
+ - [Changelog](CHANGELOG.md) — what changed in each release.
486
+ - [Contributing](CONTRIBUTING.md) — how to propose and submit changes.
487
+ - [Security policy](SECURITY.md) — how to report a vulnerability.
488
+ - [Discussions](https://github.com/chessviewer-org/chess-viewer-utils/discussions) — questions and ideas.
489
+
490
+ ---
491
+
492
+ ## License
493
+
494
+ [AGPL-3.0](LICENSE)