@hackersheet/next-document-content-kifu 0.1.0-alpha.15 → 0.1.0-alpha.17

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.
Files changed (46) hide show
  1. package/dist/cjs/components/kifu/kifu.js +2 -2
  2. package/dist/cjs/components/shogi-player/adapters/kifu-adapter.d.ts +11 -0
  3. package/dist/cjs/components/shogi-player/adapters/kifu-adapter.js +104 -0
  4. package/dist/cjs/components/shogi-player/board-renderer.d.ts +60 -0
  5. package/dist/cjs/components/shogi-player/board-renderer.js +137 -0
  6. package/dist/cjs/components/shogi-player/button.d.ts +22 -1
  7. package/dist/cjs/components/shogi-player/button.js +2 -1
  8. package/dist/cjs/components/shogi-player/canvas-utils.d.ts +29 -0
  9. package/dist/cjs/components/shogi-player/{index.js → canvas-utils.js} +26 -16
  10. package/dist/cjs/components/shogi-player/hands-renderer.d.ts +42 -0
  11. package/dist/cjs/components/shogi-player/hands-renderer.js +86 -0
  12. package/dist/cjs/components/shogi-player/moves-area.d.ts +24 -3
  13. package/dist/cjs/components/shogi-player/moves-area.js +15 -20
  14. package/dist/cjs/components/shogi-player/shogi-board-canvas.d.ts +20 -13
  15. package/dist/cjs/components/shogi-player/shogi-board-canvas.js +9 -119
  16. package/dist/cjs/components/shogi-player/shogi-hands-canvas.d.ts +20 -12
  17. package/dist/cjs/components/shogi-player/shogi-hands-canvas.js +7 -79
  18. package/dist/cjs/components/shogi-player/shogi-player.d.ts +22 -0
  19. package/dist/cjs/components/shogi-player/shogi-player.js +51 -41
  20. package/dist/cjs/components/shogi-player/types.d.ts +169 -0
  21. package/dist/cjs/components/shogi-player/types.js +16 -0
  22. package/dist/esm/components/kifu/kifu.mjs +1 -1
  23. package/dist/esm/components/shogi-player/adapters/kifu-adapter.d.mts +11 -0
  24. package/dist/esm/components/shogi-player/adapters/kifu-adapter.mjs +80 -0
  25. package/dist/esm/components/shogi-player/board-renderer.d.mts +60 -0
  26. package/dist/esm/components/shogi-player/board-renderer.mjs +109 -0
  27. package/dist/esm/components/shogi-player/button.d.mts +22 -1
  28. package/dist/esm/components/shogi-player/button.mjs +2 -1
  29. package/dist/esm/components/shogi-player/canvas-utils.d.mts +29 -0
  30. package/dist/esm/components/shogi-player/canvas-utils.mjs +22 -0
  31. package/dist/esm/components/shogi-player/hands-renderer.d.mts +42 -0
  32. package/dist/esm/components/shogi-player/hands-renderer.mjs +60 -0
  33. package/dist/esm/components/shogi-player/moves-area.d.mts +24 -3
  34. package/dist/esm/components/shogi-player/moves-area.mjs +16 -17
  35. package/dist/esm/components/shogi-player/shogi-board-canvas.d.mts +20 -13
  36. package/dist/esm/components/shogi-player/shogi-board-canvas.mjs +12 -116
  37. package/dist/esm/components/shogi-player/shogi-hands-canvas.d.mts +20 -12
  38. package/dist/esm/components/shogi-player/shogi-hands-canvas.mjs +4 -76
  39. package/dist/esm/components/shogi-player/shogi-player.d.mts +22 -0
  40. package/dist/esm/components/shogi-player/shogi-player.mjs +52 -42
  41. package/dist/esm/components/shogi-player/types.d.mts +169 -0
  42. package/dist/esm/components/shogi-player/types.mjs +0 -0
  43. package/package.json +3 -3
  44. package/dist/cjs/components/shogi-player/index.d.ts +0 -2
  45. package/dist/esm/components/shogi-player/index.d.mts +0 -2
  46. package/dist/esm/components/shogi-player/index.mjs +0 -4
@@ -29,13 +29,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
  var moves_area_exports = {};
31
31
  __export(moves_area_exports, {
32
- MovesArea: () => MovesArea
32
+ default: () => MovesArea
33
33
  });
34
34
  module.exports = __toCommonJS(moves_area_exports);
35
- var import_json_kifu_format = require("json-kifu-format");
36
35
  var import_react = __toESM(require("react"));
37
36
  function MovesArea(props) {
38
- const moves = props.moves;
39
37
  const scrollRef = (0, import_react.useRef)(null);
40
38
  const containerRef = (0, import_react.useRef)(null);
41
39
  (0, import_react.useEffect)(() => {
@@ -49,30 +47,27 @@ function MovesArea(props) {
49
47
  });
50
48
  }
51
49
  }, [props.tesuu]);
52
- const current = 0 === props.tesuu ? " bg-amber-600" : "";
50
+ const initialCurrent = 0 === props.tesuu ? " bg-amber-600" : "";
53
51
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "absolute overflow-y-auto h-full w-full border-2 border-black text-black", ref: containerRef }, /* @__PURE__ */ import_react.default.createElement("div", { className: "grid gap-0 text-xs" }, /* @__PURE__ */ import_react.default.createElement(
54
52
  "div",
55
53
  {
56
54
  onClick: () => props.onTesuuChange && props.onTesuuChange(0),
57
- className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + current
55
+ className: "col-span-500 grid grid-cols-subgrid gap-2 py-1 px-2 cursor-pointer hover:bg-amber-100" + initialCurrent
58
56
  },
59
- /* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef })),
60
- /* @__PURE__ */ import_react.default.createElement("div", null, "\u958B\u59CB\u5C40\u9762")
61
- ), moves.map((move, index) => {
62
- if (index === 0) return;
63
- const current2 = index === props.tesuu ? " bg-amber-600" : "";
64
- return /* @__PURE__ */ import_react.default.createElement(import_react.Fragment, { key: index }, /* @__PURE__ */ import_react.default.createElement(
57
+ /* @__PURE__ */ import_react.default.createElement("div", null, 0 === props.tesuu && /* @__PURE__ */ import_react.default.createElement("span", { ref: scrollRef, className: "sr-only", "aria-hidden": "true" })),
58
+ /* @__PURE__ */ import_react.default.createElement("div", null, props.readableMoves[0])
59
+ ), props.readableMoves.slice(1).map((moveText, index) => {
60
+ const moveIndex = index + 1;
61
+ const moveCurrent = moveIndex === props.tesuu ? " bg-amber-600" : "";
62
+ return /* @__PURE__ */ import_react.default.createElement(
65
63
  "div",
66
64
  {
67
- className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + current2,
68
- onClick: () => props.onTesuuChange && props.onTesuuChange(index)
65
+ key: moveIndex,
66
+ className: "col-span-500 grid grid-cols-subgrid border-black gap-2 border-t py-1 px-2 cursor-pointer hover:bg-amber-100" + moveCurrent,
67
+ onClick: () => props.onTesuuChange && props.onTesuuChange(moveIndex)
69
68
  },
70
- /* @__PURE__ */ import_react.default.createElement("div", { className: "flex" }, index === props.tesuu && /* @__PURE__ */ import_react.default.createElement("div", { ref: scrollRef }), /* @__PURE__ */ import_react.default.createElement("div", { className: "tabular-nums text-right flex-auto" }, index)),
71
- /* @__PURE__ */ import_react.default.createElement("div", null, import_json_kifu_format.JKFPlayer.moveToReadableKifu(move))
72
- ));
69
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "flex" }, moveIndex === props.tesuu && /* @__PURE__ */ import_react.default.createElement("span", { ref: scrollRef, className: "sr-only", "aria-hidden": "true" }), /* @__PURE__ */ import_react.default.createElement("div", { className: "tabular-nums text-right flex-auto" }, moveIndex)),
70
+ /* @__PURE__ */ import_react.default.createElement("div", null, moveText)
71
+ );
73
72
  })));
74
73
  }
75
- // Annotate the CommonJS export names for ESM import in node:
76
- 0 && (module.exports = {
77
- MovesArea
78
- });
@@ -1,17 +1,24 @@
1
- import { IMoveMoveFormat } from 'json-kifu-format/dist/src/Formats';
2
1
  import React from 'react';
3
- import { Piece } from 'shogi.js';
2
+ import { ShogiBoardCanvasProps } from './types.js';
3
+ import 'shogi.js';
4
4
 
5
- type Props = {
6
- size?: number;
7
- boardColor?: string;
8
- lineColor?: string;
9
- fontFamily?: string;
10
- fontSizeRatio?: number;
11
- pieces: Piece[][];
12
- isSente?: boolean;
13
- currentMove?: IMoveMoveFormat;
14
- };
15
- declare const ShogiBoardCanvas: React.FC<Props>;
5
+ /**
6
+ * Canvas component for rendering the shogi board with pieces
7
+ *
8
+ * @component
9
+ * @param props - Component props
10
+ * @returns Canvas element displaying the shogi board
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <ShogiBoardCanvas
15
+ * size={360}
16
+ * pieces={boardState}
17
+ * isSente={true}
18
+ * currentMove={lastMove}
19
+ * />
20
+ * ```
21
+ */
22
+ declare const ShogiBoardCanvas: React.FC<ShogiBoardCanvasProps>;
16
23
 
17
24
  export { ShogiBoardCanvas as default };
@@ -32,119 +32,9 @@ __export(shogi_board_canvas_exports, {
32
32
  default: () => shogi_board_canvas_default
33
33
  });
34
34
  module.exports = __toCommonJS(shogi_board_canvas_exports);
35
- var import_json_kifu_format = require("json-kifu-format");
36
35
  var import_react = __toESM(require("react"));
37
- var import_shogi = require("shogi.js");
38
- const getCanvasDimensions = (size) => {
39
- const dpr = window.devicePixelRatio || 1;
40
- return { width: size * dpr, height: size * dpr, dpr };
41
- };
42
- const getBoardLayout = (size) => {
43
- const margin = size * 0.06;
44
- const boardSize = size - margin * 2;
45
- const cell = boardSize / 9;
46
- return { margin, boardSize, cell };
47
- };
48
- const drawBackground = (ctx, size, boardColor) => {
49
- ctx.fillStyle = boardColor;
50
- ctx.fillRect(0, 0, size, size);
51
- };
52
- const drawBoard = (ctx, margin, boardSize, lineColor) => {
53
- ctx.strokeStyle = lineColor;
54
- ctx.lineWidth = 2;
55
- ctx.strokeRect(margin, margin, boardSize, boardSize);
56
- ctx.lineWidth = 1;
57
- Array.from({ length: 8 }).forEach((_, i) => {
58
- const offset = (i + 1) * (boardSize / 9);
59
- ctx.beginPath();
60
- ctx.moveTo(margin + offset, margin);
61
- ctx.lineTo(margin + offset, margin + boardSize);
62
- ctx.stroke();
63
- ctx.beginPath();
64
- ctx.moveTo(margin, margin + offset);
65
- ctx.lineTo(margin + boardSize, margin + offset);
66
- ctx.stroke();
67
- });
68
- };
69
- const drawPieces = (ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente) => {
70
- ctx.textAlign = "center";
71
- ctx.textBaseline = "middle";
72
- pieces.forEach((row, rowIndex) => {
73
- row.forEach((piece, colIndex) => {
74
- if (!piece) return;
75
- const x = isSente ? 8 - rowIndex : rowIndex;
76
- const y = isSente ? colIndex : 8 - colIndex;
77
- const px = margin + x * cell + cell / 2;
78
- const py = margin + y * cell + cell / 2;
79
- ctx.save();
80
- ctx.translate(px, py);
81
- if (isSente && piece.color === import_shogi.Color.White) {
82
- ctx.rotate(Math.PI);
83
- } else if (!isSente && piece.color === import_shogi.Color.Black) {
84
- ctx.rotate(Math.PI);
85
- }
86
- const kan = import_json_kifu_format.JKFPlayer.kindToKan(piece.kind);
87
- ctx.fillStyle = "#000";
88
- if (kan.length === 2) {
89
- const baseFontSize = cell * fontSizeRatio * 0.5;
90
- ctx.font = `${baseFontSize}px ${fontFamily}`;
91
- const scaleX = 2;
92
- const scaleY = 1;
93
- const offsetY = cell * 0.18;
94
- ctx.save();
95
- ctx.scale(scaleX, scaleY);
96
- ctx.fillText(kan[0], 0 / scaleX, -offsetY / scaleY);
97
- ctx.restore();
98
- ctx.save();
99
- ctx.scale(scaleX, scaleY);
100
- ctx.fillText(kan[1], 0 / scaleX, offsetY / scaleY);
101
- ctx.restore();
102
- } else {
103
- const fontSize = cell * fontSizeRatio;
104
- ctx.font = `${fontSize}px ${fontFamily}`;
105
- ctx.fillText(kan, 0, 0);
106
- }
107
- ctx.restore();
108
- });
109
- });
110
- };
111
- const drawCoordinates = (ctx, margin, boardSize, cell, fontFamily, isSente) => {
112
- ctx.fillStyle = "#000";
113
- ctx.font = `${cell * 0.35}px ${fontFamily}`;
114
- ctx.textAlign = "center";
115
- ctx.textBaseline = "middle";
116
- Array.from({ length: 9 }).forEach((_, i) => {
117
- const x = margin + i * cell + cell / 2;
118
- const y = margin / 2;
119
- const label = isSente ? import_json_kifu_format.JKFPlayer.numToZen(9 - i) : import_json_kifu_format.JKFPlayer.numToZen(i + 1);
120
- ctx.fillText(label, x, y);
121
- });
122
- Array.from({ length: 9 }).forEach((_, i) => {
123
- const x = margin + boardSize + margin / 2;
124
- const y = margin + i * cell + cell / 2;
125
- const label = isSente ? import_json_kifu_format.JKFPlayer.numToKan(i + 1) : import_json_kifu_format.JKFPlayer.numToKan(9 - i);
126
- ctx.fillText(label, x, y);
127
- });
128
- };
129
- const drawHighlightedCell = (ctx, margin, cell, isSente, currentMove) => {
130
- if (!currentMove) return;
131
- if (currentMove.to) {
132
- const toRow = currentMove.to.x - 1;
133
- const toCol = currentMove.to.y - 1;
134
- const toX = isSente ? 8 - toRow : toRow;
135
- const toY = isSente ? toCol : 8 - toCol;
136
- ctx.fillStyle = "rgba(255,0,0,0.1)";
137
- ctx.fillRect(margin + toX * cell, margin + toY * cell, cell, cell);
138
- }
139
- if (currentMove.from) {
140
- const fromRow = currentMove.from.x - 1;
141
- const fromCol = currentMove.from.y - 1;
142
- const fromX = isSente ? 8 - fromRow : fromRow;
143
- const fromY = isSente ? fromCol : 8 - fromCol;
144
- ctx.fillStyle = "rgba(255,0,0,0.1)";
145
- ctx.fillRect(margin + fromX * cell, margin + fromY * cell, cell, cell);
146
- }
147
- };
36
+ var import_board_renderer = require("./board-renderer");
37
+ var import_canvas_utils = require("./canvas-utils");
148
38
  const ShogiBoardCanvas = ({
149
39
  size = 360,
150
40
  boardColor = "#f9d27a",
@@ -161,19 +51,19 @@ const ShogiBoardCanvas = ({
161
51
  if (!canvas) return;
162
52
  const ctx = canvas.getContext("2d");
163
53
  if (!ctx) return;
164
- const { width, height, dpr } = getCanvasDimensions(size);
54
+ const { width, height, dpr } = (0, import_canvas_utils.getCanvasDimensions)(size);
165
55
  canvas.width = width;
166
56
  canvas.height = height;
167
57
  canvas.style.width = `${size}px`;
168
58
  canvas.style.height = `${size}px`;
169
59
  ctx.scale(dpr, dpr);
170
60
  ctx.clearRect(0, 0, size, size);
171
- const { margin, boardSize, cell } = getBoardLayout(size);
172
- drawBackground(ctx, size, boardColor);
173
- drawBoard(ctx, margin, boardSize, lineColor);
174
- drawHighlightedCell(ctx, margin, cell, isSente, currentMove);
175
- drawPieces(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
176
- drawCoordinates(ctx, margin, boardSize, cell, fontFamily, isSente);
61
+ const { margin, boardSize, cell } = (0, import_canvas_utils.getBoardLayout)(size);
62
+ (0, import_board_renderer.drawBoardBackground)(ctx, size, boardColor);
63
+ (0, import_board_renderer.drawBoardGrid)(ctx, margin, boardSize, lineColor);
64
+ (0, import_board_renderer.drawHighlightedCell)(ctx, margin, cell, isSente, currentMove);
65
+ (0, import_board_renderer.drawBoardPieces)(ctx, pieces, margin, cell, fontFamily, fontSizeRatio, isSente);
66
+ (0, import_board_renderer.drawBoardCoordinates)(ctx, margin, boardSize, cell, fontFamily, isSente);
177
67
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, pieces, isSente, currentMove]);
178
68
  return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef, width: size, height: size });
179
69
  };
@@ -1,16 +1,24 @@
1
1
  import React from 'react';
2
- import { Piece } from 'shogi.js';
2
+ import { ShogiHandsCanvasProps } from './types.js';
3
+ import 'shogi.js';
3
4
 
4
- type Props = {
5
- size?: number;
6
- boardColor?: string;
7
- lineColor?: string;
8
- fontFamily?: string;
9
- fontSizeRatio?: number;
10
- hands: Piece[][];
11
- isSente?: boolean;
12
- isTop?: boolean;
13
- };
14
- declare const ShogiHandsCanvas: React.FC<Props>;
5
+ /**
6
+ * Canvas component for rendering captured pieces (hands)
7
+ *
8
+ * @component
9
+ * @param props - Component props
10
+ * @returns Canvas element displaying the hands (captured pieces area)
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <ShogiHandsCanvas
15
+ * size={360}
16
+ * hands={capturedPieces}
17
+ * isSente={true}
18
+ * isTop={true}
19
+ * />
20
+ * ```
21
+ */
22
+ declare const ShogiHandsCanvas: React.FC<ShogiHandsCanvasProps>;
15
23
 
16
24
  export { ShogiHandsCanvas as default };
@@ -32,80 +32,9 @@ __export(shogi_hands_canvas_exports, {
32
32
  default: () => shogi_hands_canvas_default
33
33
  });
34
34
  module.exports = __toCommonJS(shogi_hands_canvas_exports);
35
- var import_json_kifu_format = require("json-kifu-format");
36
35
  var import_react = __toESM(require("react"));
37
- var import_shogi = require("shogi.js");
38
- const getCanvasDimensions = (size) => {
39
- const dpr = window.devicePixelRatio || 1;
40
- return { width: size * dpr, height: size * dpr, dpr };
41
- };
42
- const getHandsLayout = (size) => {
43
- const margin = size * 0.06;
44
- const boardSize = size - margin * 2;
45
- const cellSize = boardSize / 9;
46
- const handsHeight = cellSize + margin + 2;
47
- return { margin, handsHeight, boardSize, cellSize };
48
- };
49
- const drawBackground = (ctx, width, height, color) => {
50
- ctx.fillStyle = color;
51
- ctx.fillRect(0, 0, width, height);
52
- };
53
- const drawHandsFrame = (ctx, x, y, width, height, lineColor, isTop) => {
54
- ctx.strokeStyle = lineColor;
55
- ctx.lineWidth = 2;
56
- if (isTop) {
57
- ctx.strokeRect(x, y, width, height);
58
- } else {
59
- ctx.strokeRect(x, 2, width, height);
60
- }
61
- };
62
- const drawCoordinates = (ctx, fontSize, fontFamily) => {
63
- ctx.fillStyle = "#000";
64
- ctx.font = `${fontSize}px ${fontFamily}`;
65
- ctx.textAlign = "center";
66
- ctx.textBaseline = "middle";
67
- };
68
- const drawPieces = (ctx, hands, margin, boardSize, cell, fontFamily, fontSizeRatio, isSente, isTop) => {
69
- ctx.textAlign = "center";
70
- ctx.textBaseline = "middle";
71
- const pieces = isSente && isTop || !isSente && !isTop ? hands[import_shogi.Color.White] : hands[import_shogi.Color.Black];
72
- if (!pieces || pieces.length === 0) return;
73
- const grouped = pieces.reduce(
74
- (acc, piece) => {
75
- if (!piece) return acc;
76
- const key = piece.kind;
77
- if (!acc[key]) acc[key] = { count: 0, color: piece.color };
78
- acc[key].count += 1;
79
- return acc;
80
- },
81
- {}
82
- );
83
- const order = ["OU", "HI", "KA", "KI", "GI", "KE", "KY", "FU"];
84
- const kinds = order.filter((kind) => grouped[kind]);
85
- kinds.forEach((kind, index) => {
86
- const { count, color } = grouped[kind];
87
- const px = isTop ? margin + boardSize - (index * cell + cell / 2) : margin + index * cell + cell / 2;
88
- const py = isTop ? margin + cell / 2 : cell / 2 + 2;
89
- ctx.save();
90
- ctx.translate(px, py);
91
- if (isSente && color === import_shogi.Color.White) {
92
- ctx.rotate(Math.PI);
93
- } else if (!isSente && color === import_shogi.Color.Black) {
94
- ctx.rotate(Math.PI);
95
- }
96
- const fontSize = cell * fontSizeRatio;
97
- const kan = import_json_kifu_format.JKFPlayer.kindToKan(kind);
98
- ctx.font = `${fontSize}px ${fontFamily}`;
99
- ctx.fillText(kan, 0, 0);
100
- if (count > 1) {
101
- ctx.font = `${fontSize * 0.5}px ${fontFamily}`;
102
- const countOffsetX = cell * 0;
103
- const countOffsetY = cell * 0.8;
104
- ctx.fillText(String(count), countOffsetX, countOffsetY);
105
- }
106
- ctx.restore();
107
- });
108
- };
36
+ var import_canvas_utils = require("./canvas-utils");
37
+ var import_hands_renderer = require("./hands-renderer");
109
38
  const ShogiHandsCanvas = ({
110
39
  size = 360,
111
40
  boardColor = "#f9d27a",
@@ -122,18 +51,17 @@ const ShogiHandsCanvas = ({
122
51
  if (!canvas) return;
123
52
  const ctx = canvas.getContext("2d");
124
53
  if (!ctx) return;
125
- const { margin, handsHeight, boardSize, cellSize } = getHandsLayout(size);
126
- const { width: canvasWidth, dpr } = getCanvasDimensions(size);
54
+ const { margin, handsHeight, boardSize, cellSize } = (0, import_canvas_utils.getHandsLayout)(size);
55
+ const { width: canvasWidth, dpr } = (0, import_canvas_utils.getCanvasDimensions)(size);
127
56
  canvas.width = canvasWidth;
128
57
  canvas.height = handsHeight * dpr;
129
58
  canvas.style.width = `${size}px`;
130
59
  canvas.style.height = `${handsHeight}px`;
131
60
  ctx.scale(dpr, dpr);
132
61
  ctx.clearRect(0, 0, size, handsHeight);
133
- drawBackground(ctx, size, handsHeight, boardColor);
134
- drawHandsFrame(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
135
- drawCoordinates(ctx, cellSize * 0.35, fontFamily);
136
- drawPieces(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
62
+ (0, import_hands_renderer.drawHandsBackground)(ctx, size, handsHeight, boardColor);
63
+ (0, import_hands_renderer.drawHandsFrame)(ctx, margin, margin, boardSize, cellSize, lineColor, isTop);
64
+ (0, import_hands_renderer.drawHandsPieces)(ctx, hands, margin, boardSize, cellSize, fontFamily, fontSizeRatio, isSente, isTop);
137
65
  }, [size, boardColor, lineColor, fontFamily, fontSizeRatio, isSente, hands, isTop]);
138
66
  return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: canvasRef });
139
67
  };
@@ -1,10 +1,32 @@
1
1
  import React from 'react';
2
+ import { KifuAdapterFactory } from './types.js';
3
+ import 'shogi.js';
2
4
 
5
+ /**
6
+ * Props for the ShogiPlayer component
7
+ * @property kifuText - KIF format kifu (game record) text
8
+ * @property size - Canvas size in CSS pixels (default: 360)
9
+ * @property tesuu - Initial move number to display
10
+ * @property adapterFactory - Factory function for creating KifuAdapter (for DI/testing)
11
+ */
3
12
  type ShogiPlayerProps = {
4
13
  kifuText: string;
5
14
  size?: number;
6
15
  tesuu?: number;
16
+ adapterFactory?: KifuAdapterFactory;
7
17
  };
18
+ /**
19
+ * Shogi game viewer component with board, hands, and move navigation
20
+ *
21
+ * @component
22
+ * @param props - Component props
23
+ * @returns Interactive shogi player with keyboard and button navigation
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <ShogiPlayer kifuText={kifString} tesuu={10} />
28
+ * ```
29
+ */
8
30
  declare function ShogiPlayer(props: ShogiPlayerProps): React.JSX.Element;
9
31
 
10
32
  export { type ShogiPlayerProps, ShogiPlayer as default };
@@ -32,48 +32,33 @@ __export(shogi_player_exports, {
32
32
  default: () => ShogiPlayer
33
33
  });
34
34
  module.exports = __toCommonJS(shogi_player_exports);
35
- var import_json_kifu_format = require("json-kifu-format");
36
35
  var import_react = __toESM(require("react"));
36
+ var import_kifu_adapter = require("./adapters/kifu-adapter");
37
37
  var import_button = __toESM(require("./button"));
38
- var import_moves_area = require("./moves-area");
38
+ var import_moves_area = __toESM(require("./moves-area"));
39
39
  var import_shogi_board_canvas = __toESM(require("./shogi-board-canvas"));
40
40
  var import_shogi_hands_canvas = __toESM(require("./shogi-hands-canvas"));
41
41
  function ShogiPlayer(props) {
42
- const [player] = (0, import_react.useState)(import_json_kifu_format.JKFPlayer.parse(props.kifuText.trim()));
43
- const [pieces, setPieces] = (0, import_react.useState)(player.shogi.board);
44
- const [hands, setHands] = (0, import_react.useState)(player.shogi.hands);
45
- const [moves, setMoves] = (0, import_react.useState)(player.kifu.moves);
46
- const [tesuu, setTesuu] = (0, import_react.useState)(player.tesuu);
47
- const [currentMove, setCurrentMove] = (0, import_react.useState)(player.getMove());
42
+ const factory = props.adapterFactory ?? import_kifu_adapter.createKifuAdapter;
43
+ const adapterRef = (0, import_react.useRef)(factory(props.kifuText));
44
+ const [gameState, setGameState] = (0, import_react.useState)(() => adapterRef.current.getState());
48
45
  const [isSente, setIsSente] = (0, import_react.useState)(true);
49
- const [maxTesuu, setMaxTesuu] = (0, import_react.useState)(player.getMaxTesuu());
50
- const [comments, setComments] = (0, import_react.useState)(player.getComments());
51
- const size = props.size ? props.size : 360;
52
- const updateState = () => {
53
- setPieces([...player.shogi.board]);
54
- setHands([...player.shogi.hands]);
55
- setMoves([...player.kifu.moves]);
56
- setCurrentMove(player.getMove());
57
- setMaxTesuu(player.getMaxTesuu());
58
- setComments(player.getComments());
59
- setTesuu(player.tesuu);
60
- };
61
- const handleForward = () => {
62
- player.forward();
63
- updateState();
64
- };
65
- const handleBackward = () => {
66
- player.backward();
67
- updateState();
68
- };
69
- const handleGoto = (tesuu2) => {
70
- player.goto(tesuu2);
71
- updateState();
72
- };
73
- const handeleToggle = () => {
74
- setIsSente(!isSente);
75
- updateState();
76
- };
46
+ const size = props.size ?? 360;
47
+ const handleForward = (0, import_react.useCallback)(() => {
48
+ const newState = adapterRef.current.forward();
49
+ setGameState(newState);
50
+ }, []);
51
+ const handleBackward = (0, import_react.useCallback)(() => {
52
+ const newState = adapterRef.current.backward();
53
+ setGameState(newState);
54
+ }, []);
55
+ const handleGoto = (0, import_react.useCallback)((tesuu) => {
56
+ const newState = adapterRef.current.goto(tesuu);
57
+ setGameState(newState);
58
+ }, []);
59
+ const handleToggle = (0, import_react.useCallback)(() => {
60
+ setIsSente((prev) => !prev);
61
+ }, []);
77
62
  const handleKeydown = (0, import_react.useCallback)(
78
63
  (event) => {
79
64
  event.preventDefault();
@@ -85,7 +70,7 @@ function ShogiPlayer(props) {
85
70
  break;
86
71
  case "Down":
87
72
  case "ArrowDown":
88
- handleGoto(maxTesuu);
73
+ handleGoto(gameState.maxMoveIndex);
89
74
  break;
90
75
  case "Left":
91
76
  case "ArrowLeft":
@@ -97,16 +82,41 @@ function ShogiPlayer(props) {
97
82
  handleForward();
98
83
  break;
99
84
  case "r":
100
- handeleToggle();
85
+ handleToggle();
101
86
  break;
102
87
  }
103
88
  },
104
- [maxTesuu, isSente]
89
+ [gameState.maxMoveIndex, handleGoto, handleBackward, handleForward, handleToggle]
105
90
  );
106
91
  (0, import_react.useEffect)(() => {
107
92
  if (props.tesuu !== void 0) {
108
93
  handleGoto(props.tesuu);
109
94
  }
110
- }, [props.tesuu]);
111
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col sm:flex-row w-fit", tabIndex: 1, onKeyDown: handleKeydown }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs text-right p-1" }, isSente ? "\u2616 " + player.kifu.header["\u5F8C\u624B"] : "\u2617 " + player.kifu.header["\u5148\u624B"]), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: true }), /* @__PURE__ */ import_react.default.createElement(import_shogi_board_canvas.default, { size, pieces, isSente, currentMove }), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands, isSente, isTop: false }), /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs p-1" }, isSente ? "\u2617 " + player.kifu.header["\u5148\u624B"] : "\u2616 " + player.kifu.header["\u5F8C\u624B"])), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col sm:w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block flex-1 relative w-full" }, /* @__PURE__ */ import_react.default.createElement(import_moves_area.MovesArea, { moves, tesuu, onTesuuChange: handleGoto })), /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block text-black border-black border-2 p-1 text-xs max-h-40 w-0 min-w-full overflow-auto" }, comments.map((comment, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index }, comment)), comments.length === 0 && tesuu !== 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0"), tesuu === 0 && Object.entries(player.kifu.header).map(([key, value], i) => /* @__PURE__ */ import_react.default.createElement("div", { key: i, className: "whitespace-nowrap" }, key, ": ", value))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(maxTesuu) }, "\u6700\u5F8C"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handeleToggle }, "\u53CD\u8EE2"))));
95
+ }, [props.tesuu, handleGoto]);
96
+ (0, import_react.useEffect)(() => {
97
+ return () => {
98
+ adapterRef.current.dispose();
99
+ };
100
+ }, []);
101
+ const topPlayerName = isSente ? `\u2616 ${gameState.header.goteName}` : `\u2617 ${gameState.header.senteName}`;
102
+ const bottomPlayerName = isSente ? `\u2617 ${gameState.header.senteName}` : `\u2616 ${gameState.header.goteName}`;
103
+ return /* @__PURE__ */ import_react.default.createElement(
104
+ "div",
105
+ {
106
+ className: "flex flex-col sm:flex-row w-fit",
107
+ tabIndex: 0,
108
+ role: "application",
109
+ "aria-label": "Shogi player - use arrow keys to navigate, space or right arrow to move forward, left arrow to move backward, 'r' to flip board",
110
+ onKeyDown: handleKeydown
111
+ },
112
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs text-right p-1" }, topPlayerName), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands: gameState.hands, isSente, isTop: true }), /* @__PURE__ */ import_react.default.createElement(import_shogi_board_canvas.default, { size, pieces: gameState.board, isSente, currentMove: gameState.currentMove }), /* @__PURE__ */ import_react.default.createElement(import_shogi_hands_canvas.default, { size, hands: gameState.hands, isSente, isTop: false }), /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-[#f9d27a] text-black text-xs p-1" }, bottomPlayerName)),
113
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "flex flex-col sm:w-fit bg-[#f9d27a] p-4 gap-4" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block flex-1 relative w-full" }, /* @__PURE__ */ import_react.default.createElement(
114
+ import_moves_area.default,
115
+ {
116
+ readableMoves: gameState.readableMoves,
117
+ tesuu: gameState.currentMoveIndex,
118
+ onTesuuChange: handleGoto
119
+ }
120
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "hidden sm:block text-black border-black border-2 p-1 text-xs max-h-40 w-0 min-w-full overflow-auto" }, gameState.comments.map((comment, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index }, comment)), gameState.comments.length === 0 && gameState.currentMoveIndex !== 0 && /* @__PURE__ */ import_react.default.createElement("div", null, "\xA0"), gameState.currentMoveIndex === 0 && Object.entries(gameState.header).map(([key, value], i) => /* @__PURE__ */ import_react.default.createElement("div", { key: i, className: "whitespace-nowrap" }, key, ": ", value))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(0) }, "\u6700\u521D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleBackward }, "\u524D"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleForward }, "\u6B21"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: () => handleGoto(gameState.maxMoveIndex) }, "\u6700\u5F8C"), /* @__PURE__ */ import_react.default.createElement(import_button.default, { onClick: handleToggle }, "\u53CD\u8EE2")))
121
+ );
112
122
  }