@fgv/ts-sudoku-ui 5.1.0-2 → 5.1.0-21

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 (78) hide show
  1. package/eslint.config.js +18 -0
  2. package/lib/tsdoc-metadata.json +1 -1
  3. package/package.json +22 -21
  4. package/.test-failures-catalog.md +0 -470
  5. package/CHANGELOG.md +0 -11
  6. package/CSS_MODULES_MIGRATION_SUMMARY.md +0 -189
  7. package/dist/ts-sudoku-ui.d.ts +0 -668
  8. package/etc/ts-sudoku-ui.api.md +0 -481
  9. package/lib/test/fixtures/killerSudokuPuzzles.d.ts +0 -36
  10. package/lib/test/fixtures/killerSudokuPuzzles.d.ts.map +0 -1
  11. package/lib/test/fixtures/killerSudokuPuzzles.js +0 -57
  12. package/lib/test/fixtures/killerSudokuPuzzles.js.map +0 -1
  13. package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts +0 -22
  14. package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts.map +0 -1
  15. package/lib/test/unit/components/SudokuGridEntry.testHelpers.js +0 -116
  16. package/lib/test/unit/components/SudokuGridEntry.testHelpers.js.map +0 -1
  17. package/lib-commonjs/components/CageOverlay.js +0 -138
  18. package/lib-commonjs/components/CageOverlay.js.map +0 -1
  19. package/lib-commonjs/components/CageSumIndicator.js +0 -71
  20. package/lib-commonjs/components/CageSumIndicator.js.map +0 -1
  21. package/lib-commonjs/components/CombinationCard.js +0 -68
  22. package/lib-commonjs/components/CombinationCard.js.map +0 -1
  23. package/lib-commonjs/components/CombinationGrid.js +0 -56
  24. package/lib-commonjs/components/CombinationGrid.js.map +0 -1
  25. package/lib-commonjs/components/CompactControlRibbon.js +0 -146
  26. package/lib-commonjs/components/CompactControlRibbon.js.map +0 -1
  27. package/lib-commonjs/components/DualKeypad.js +0 -142
  28. package/lib-commonjs/components/DualKeypad.js.map +0 -1
  29. package/lib-commonjs/components/KillerCombinationsExplorer.js +0 -74
  30. package/lib-commonjs/components/KillerCombinationsExplorer.js.map +0 -1
  31. package/lib-commonjs/components/KillerCombinationsModal.js +0 -140
  32. package/lib-commonjs/components/KillerCombinationsModal.js.map +0 -1
  33. package/lib-commonjs/components/KillerCombinationsPanel.js +0 -84
  34. package/lib-commonjs/components/KillerCombinationsPanel.js.map +0 -1
  35. package/lib-commonjs/components/NumberKeypad.js +0 -186
  36. package/lib-commonjs/components/NumberKeypad.js.map +0 -1
  37. package/lib-commonjs/components/SudokuCell.js +0 -245
  38. package/lib-commonjs/components/SudokuCell.js.map +0 -1
  39. package/lib-commonjs/components/SudokuControls.js +0 -145
  40. package/lib-commonjs/components/SudokuControls.js.map +0 -1
  41. package/lib-commonjs/components/SudokuGrid.js +0 -182
  42. package/lib-commonjs/components/SudokuGrid.js.map +0 -1
  43. package/lib-commonjs/components/SudokuGridEntry.js +0 -378
  44. package/lib-commonjs/components/SudokuGridEntry.js.map +0 -1
  45. package/lib-commonjs/components/ValidationDisplay.js +0 -100
  46. package/lib-commonjs/components/ValidationDisplay.js.map +0 -1
  47. package/lib-commonjs/contexts/DiagnosticLoggerContext.js +0 -104
  48. package/lib-commonjs/contexts/DiagnosticLoggerContext.js.map +0 -1
  49. package/lib-commonjs/contexts/index.js +0 -32
  50. package/lib-commonjs/contexts/index.js.map +0 -1
  51. package/lib-commonjs/hooks/useCombinationElimination.js +0 -126
  52. package/lib-commonjs/hooks/useCombinationElimination.js.map +0 -1
  53. package/lib-commonjs/hooks/useKeyboardShortcut.js +0 -58
  54. package/lib-commonjs/hooks/useKeyboardShortcut.js.map +0 -1
  55. package/lib-commonjs/hooks/useKillerCombinations.js +0 -68
  56. package/lib-commonjs/hooks/useKillerCombinations.js.map +0 -1
  57. package/lib-commonjs/hooks/useLongPress.js +0 -72
  58. package/lib-commonjs/hooks/useLongPress.js.map +0 -1
  59. package/lib-commonjs/hooks/usePuzzleSession.js +0 -401
  60. package/lib-commonjs/hooks/usePuzzleSession.js.map +0 -1
  61. package/lib-commonjs/hooks/useResponsiveLayout.js +0 -142
  62. package/lib-commonjs/hooks/useResponsiveLayout.js.map +0 -1
  63. package/lib-commonjs/index.browser.js +0 -29
  64. package/lib-commonjs/index.browser.js.map +0 -1
  65. package/lib-commonjs/index.js +0 -73
  66. package/lib-commonjs/index.js.map +0 -1
  67. package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js +0 -60
  68. package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js.map +0 -1
  69. package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js +0 -125
  70. package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js.map +0 -1
  71. package/lib-commonjs/types/index.js +0 -26
  72. package/lib-commonjs/types/index.js.map +0 -1
  73. package/lib-commonjs/utils/CageLookupManager.js +0 -287
  74. package/lib-commonjs/utils/CageLookupManager.js.map +0 -1
  75. package/lib-commonjs/utils/CagePatternManager.js +0 -143
  76. package/lib-commonjs/utils/CagePatternManager.js.map +0 -1
  77. package/lib-commonjs/utils/puzzleDefinitionHelper.js +0 -103
  78. package/lib-commonjs/utils/puzzleDefinitionHelper.js.map +0 -1
@@ -1,145 +0,0 @@
1
- "use strict";
2
- /*
3
- * MIT License
4
- *
5
- * Copyright (c) 2023 Erik Fortune
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in all
15
- * copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- * SOFTWARE.
24
- */
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.SudokuControls = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importStar(require("react"));
29
- const useResponsiveLayout_1 = require("../hooks/useResponsiveLayout");
30
- /**
31
- * Control panel for grid-wide operations
32
- * @public
33
- */
34
- const SudokuControls = ({ canUndo, canRedo, canReset, isValid, isSolved, onUndo, onRedo, onReset, onExport, className }) => {
35
- const [showResetConfirm, setShowResetConfirm] = (0, react_1.useState)(false);
36
- const responsiveLayout = (0, useResponsiveLayout_1.useResponsiveLayout)();
37
- // Handle reset with confirmation
38
- const handleResetClick = (0, react_1.useCallback)(() => {
39
- if (showResetConfirm) {
40
- onReset();
41
- setShowResetConfirm(false);
42
- }
43
- else {
44
- setShowResetConfirm(true);
45
- // Auto-hide confirmation after 3 seconds
46
- setTimeout(() => setShowResetConfirm(false), 3000);
47
- }
48
- }, [showResetConfirm, onReset]);
49
- // Cancel reset confirmation
50
- const handleCancelReset = (0, react_1.useCallback)(() => {
51
- setShowResetConfirm(false);
52
- }, []);
53
- // Calculate control classes
54
- const controlClasses = (0, react_1.useMemo)(() => {
55
- const classes = ['sudoku-controls'];
56
- if (className)
57
- classes.push(className);
58
- return classes.join(' ');
59
- }, [className]);
60
- // Button base styles
61
- const buttonStyle = {
62
- padding: '8px 16px',
63
- margin: '4px',
64
- border: '1px solid #ddd',
65
- borderRadius: '4px',
66
- backgroundColor: '#fff',
67
- cursor: 'pointer',
68
- fontSize: '14px',
69
- fontWeight: 'bold'
70
- };
71
- const disabledButtonStyle = {
72
- ...buttonStyle,
73
- backgroundColor: '#f5f5f5',
74
- color: '#999',
75
- cursor: 'not-allowed'
76
- };
77
- // Removed unused primaryButtonStyle variable
78
- const dangerButtonStyle = {
79
- ...buttonStyle,
80
- backgroundColor: '#f44336',
81
- color: '#fff',
82
- border: '1px solid #d32f2f'
83
- };
84
- const successButtonStyle = {
85
- ...buttonStyle,
86
- backgroundColor: '#4caf50',
87
- color: '#fff',
88
- border: '1px solid #388e3c'
89
- };
90
- // Calculate if we should use compact layout
91
- const isCompactMode = responsiveLayout.deviceType === 'mobile' &&
92
- (responsiveLayout.keypadLayoutMode === 'side-by-side' || responsiveLayout.keypadLayoutMode === 'stacked');
93
- return (react_1.default.createElement("div", { className: controlClasses, "data-testid": "sudoku-controls" },
94
- react_1.default.createElement("div", { style: {
95
- display: 'flex',
96
- flexDirection: isCompactMode ? 'row' : 'column',
97
- alignItems: 'center',
98
- justifyContent: 'center',
99
- gap: isCompactMode ? '8px' : '16px',
100
- padding: isCompactMode ? '8px' : '16px',
101
- border: '1px solid #ddd',
102
- borderRadius: '8px',
103
- backgroundColor: '#fafafa',
104
- flexWrap: isCompactMode ? 'wrap' : 'nowrap'
105
- } },
106
- react_1.default.createElement("div", { style: { display: 'flex', gap: '8px' } },
107
- react_1.default.createElement("button", { type: "button", onClick: onUndo, disabled: !canUndo, style: canUndo ? buttonStyle : disabledButtonStyle, "data-testid": "undo-button", title: "Undo last move" }, "\u21B6 Undo"),
108
- react_1.default.createElement("button", { type: "button", onClick: onRedo, disabled: !canRedo, style: canRedo ? buttonStyle : disabledButtonStyle, "data-testid": "redo-button", title: "Redo last undone move" }, "\u21B7 Redo")),
109
- !showResetConfirm ? (react_1.default.createElement("button", { type: "button", onClick: handleResetClick, disabled: !canReset, style: canReset ? dangerButtonStyle : disabledButtonStyle, "data-testid": "reset-button", title: "Clear all entries" }, "\uD83D\uDDD1\uFE0F Reset Puzzle")) : (react_1.default.createElement("div", { style: {
110
- display: 'flex',
111
- flexDirection: isCompactMode ? 'row' : 'column',
112
- alignItems: 'center',
113
- gap: '8px'
114
- } },
115
- !isCompactMode && (react_1.default.createElement("div", { style: { fontSize: '12px', color: '#666', textAlign: 'center' } }, "Are you sure? This will clear all entries.")),
116
- react_1.default.createElement("div", { style: { display: 'flex', gap: '8px' } },
117
- react_1.default.createElement("button", { type: "button", onClick: handleResetClick, style: dangerButtonStyle, "data-testid": "confirm-reset-button", title: isCompactMode ? 'Are you sure? This will clear all entries.' : undefined }, "Yes, Reset"),
118
- react_1.default.createElement("button", { type: "button", onClick: handleCancelReset, style: buttonStyle, "data-testid": "cancel-reset-button" }, "Cancel")))),
119
- react_1.default.createElement("button", { type: "button", onClick: onExport, style: successButtonStyle, "data-testid": "export-button", title: "Export puzzle in compatible format" }, "\uD83D\uDCE4 Export Puzzle"),
120
- !(responsiveLayout.deviceType === 'mobile' &&
121
- (responsiveLayout.keypadLayoutMode === 'side-by-side' ||
122
- responsiveLayout.keypadLayoutMode === 'stacked')) && (react_1.default.createElement("div", { style: {
123
- fontSize: '12px',
124
- color: '#666',
125
- textAlign: 'center',
126
- maxWidth: '300px',
127
- lineHeight: '1.4'
128
- } },
129
- react_1.default.createElement("div", { style: { fontWeight: 'bold', marginBottom: '4px' } }, "Instructions:"),
130
- react_1.default.createElement("div", null, "\u2022 Click a cell to select it"),
131
- react_1.default.createElement("div", null, "\u2022 Type 1-9 to enter numbers"),
132
- react_1.default.createElement("div", null, "\u2022 Press Delete/Backspace to clear"),
133
- react_1.default.createElement("div", null, "\u2022 Use arrow keys to navigate"))),
134
- react_1.default.createElement("div", { style: {
135
- padding: '8px 12px',
136
- borderRadius: '4px',
137
- fontSize: '12px',
138
- fontWeight: 'bold',
139
- backgroundColor: isSolved ? '#e8f5e8' : isValid ? '#e3f2fd' : '#ffebee',
140
- color: isSolved ? '#2e7d32' : isValid ? '#1565c0' : '#c62828',
141
- border: `1px solid ${isSolved ? '#4caf50' : isValid ? '#2196f3' : '#f44336'}`
142
- } }, isSolved ? '✅ Solved!' : isValid ? '✓ Valid' : '⚠ Has Errors'))));
143
- };
144
- exports.SudokuControls = SudokuControls;
145
- //# sourceMappingURL=SudokuControls.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SudokuControls.js","sourceRoot":"","sources":["../../src/components/SudokuControls.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * MIT License\n *\n * Copyright (c) 2023 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useMemo, useState } from 'react';\nimport { ISudokuControlsProps } from '../types';\nimport { useResponsiveLayout } from '../hooks/useResponsiveLayout';\n\n/**\n * Control panel for grid-wide operations\n * @public\n */\nexport const SudokuControls: React.FC<ISudokuControlsProps> = ({\n canUndo,\n canRedo,\n canReset,\n isValid,\n isSolved,\n onUndo,\n onRedo,\n onReset,\n onExport,\n className\n}) => {\n const [showResetConfirm, setShowResetConfirm] = useState(false);\n const responsiveLayout = useResponsiveLayout();\n\n // Handle reset with confirmation\n const handleResetClick = useCallback(() => {\n if (showResetConfirm) {\n onReset();\n setShowResetConfirm(false);\n } else {\n setShowResetConfirm(true);\n // Auto-hide confirmation after 3 seconds\n setTimeout(() => setShowResetConfirm(false), 3000);\n }\n }, [showResetConfirm, onReset]);\n\n // Cancel reset confirmation\n const handleCancelReset = useCallback(() => {\n setShowResetConfirm(false);\n }, []);\n\n // Calculate control classes\n const controlClasses = useMemo(() => {\n const classes = ['sudoku-controls'];\n if (className) classes.push(className);\n return classes.join(' ');\n }, [className]);\n\n // Button base styles\n const buttonStyle = {\n padding: '8px 16px',\n margin: '4px',\n border: '1px solid #ddd',\n borderRadius: '4px',\n backgroundColor: '#fff',\n cursor: 'pointer',\n fontSize: '14px',\n fontWeight: 'bold'\n };\n\n const disabledButtonStyle = {\n ...buttonStyle,\n backgroundColor: '#f5f5f5',\n color: '#999',\n cursor: 'not-allowed'\n };\n\n // Removed unused primaryButtonStyle variable\n\n const dangerButtonStyle = {\n ...buttonStyle,\n backgroundColor: '#f44336',\n color: '#fff',\n border: '1px solid #d32f2f'\n };\n\n const successButtonStyle = {\n ...buttonStyle,\n backgroundColor: '#4caf50',\n color: '#fff',\n border: '1px solid #388e3c'\n };\n\n // Calculate if we should use compact layout\n const isCompactMode =\n responsiveLayout.deviceType === 'mobile' &&\n (responsiveLayout.keypadLayoutMode === 'side-by-side' || responsiveLayout.keypadLayoutMode === 'stacked');\n\n return (\n <div className={controlClasses} data-testid=\"sudoku-controls\">\n <div\n style={{\n display: 'flex',\n flexDirection: isCompactMode ? 'row' : 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: isCompactMode ? '8px' : '16px',\n padding: isCompactMode ? '8px' : '16px',\n border: '1px solid #ddd',\n borderRadius: '8px',\n backgroundColor: '#fafafa',\n flexWrap: isCompactMode ? 'wrap' : 'nowrap'\n }}\n >\n {/* Undo/Redo controls */}\n <div style={{ display: 'flex', gap: '8px' }}>\n <button\n type=\"button\"\n onClick={onUndo}\n disabled={!canUndo}\n style={canUndo ? buttonStyle : disabledButtonStyle}\n data-testid=\"undo-button\"\n title=\"Undo last move\"\n >\n ↶ Undo\n </button>\n <button\n type=\"button\"\n onClick={onRedo}\n disabled={!canRedo}\n style={canRedo ? buttonStyle : disabledButtonStyle}\n data-testid=\"redo-button\"\n title=\"Redo last undone move\"\n >\n ↷ Redo\n </button>\n </div>\n\n {/* Reset control with confirmation */}\n {!showResetConfirm ? (\n <button\n type=\"button\"\n onClick={handleResetClick}\n disabled={!canReset}\n style={canReset ? dangerButtonStyle : disabledButtonStyle}\n data-testid=\"reset-button\"\n title=\"Clear all entries\"\n >\n 🗑️ Reset Puzzle\n </button>\n ) : (\n <div\n style={{\n display: 'flex',\n flexDirection: isCompactMode ? 'row' : 'column',\n alignItems: 'center',\n gap: '8px'\n }}\n >\n {!isCompactMode && (\n <div style={{ fontSize: '12px', color: '#666', textAlign: 'center' }}>\n Are you sure? This will clear all entries.\n </div>\n )}\n <div style={{ display: 'flex', gap: '8px' }}>\n <button\n type=\"button\"\n onClick={handleResetClick}\n style={dangerButtonStyle}\n data-testid=\"confirm-reset-button\"\n title={isCompactMode ? 'Are you sure? This will clear all entries.' : undefined}\n >\n Yes, Reset\n </button>\n <button\n type=\"button\"\n onClick={handleCancelReset}\n style={buttonStyle}\n data-testid=\"cancel-reset-button\"\n >\n Cancel\n </button>\n </div>\n </div>\n )}\n\n {/* Export control */}\n <button\n type=\"button\"\n onClick={onExport}\n style={successButtonStyle}\n data-testid=\"export-button\"\n title=\"Export puzzle in compatible format\"\n >\n 📤 Export Puzzle\n </button>\n\n {/* Instructions - hide on mobile with visible keypads */}\n {!(\n responsiveLayout.deviceType === 'mobile' &&\n (responsiveLayout.keypadLayoutMode === 'side-by-side' ||\n responsiveLayout.keypadLayoutMode === 'stacked')\n ) && (\n <div\n style={{\n fontSize: '12px',\n color: '#666',\n textAlign: 'center',\n maxWidth: '300px',\n lineHeight: '1.4'\n }}\n >\n <div style={{ fontWeight: 'bold', marginBottom: '4px' }}>Instructions:</div>\n <div>• Click a cell to select it</div>\n <div>• Type 1-9 to enter numbers</div>\n <div>• Press Delete/Backspace to clear</div>\n <div>• Use arrow keys to navigate</div>\n </div>\n )}\n\n {/* Status indicator */}\n <div\n style={{\n padding: '8px 12px',\n borderRadius: '4px',\n fontSize: '12px',\n fontWeight: 'bold',\n backgroundColor: isSolved ? '#e8f5e8' : isValid ? '#e3f2fd' : '#ffebee',\n color: isSolved ? '#2e7d32' : isValid ? '#1565c0' : '#c62828',\n border: `1px solid ${isSolved ? '#4caf50' : isValid ? '#2196f3' : '#f44336'}`\n }}\n >\n {isSolved ? '✅ Solved!' : isValid ? '✓ Valid' : '⚠ Has Errors'}\n </div>\n </div>\n </div>\n );\n};\n"]}
@@ -1,182 +0,0 @@
1
- "use strict";
2
- /*
3
- * MIT License
4
- *
5
- * Copyright (c) 2023 Erik Fortune
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in all
15
- * copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- * SOFTWARE.
24
- */
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.SudokuGrid = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importStar(require("react"));
29
- const SudokuCell_1 = require("./SudokuCell");
30
- const CageOverlay_1 = require("./CageOverlay");
31
- /**
32
- * Grid component that renders the 9x9 Sudoku grid with proper visual structure
33
- * @public
34
- */
35
- const SudokuGrid = ({ numRows, numColumns, cells, cages, selectedCell, selectedCells, inputMode, puzzleType, puzzleDimensions, onCellSelect, onLongPressToggle, onCellValueChange, onNoteToggle, onClearAllNotes, onNavigate, onDragOver, onDragEnd, isDragging, className }) => {
36
- // Handle keyboard navigation
37
- const handleKeyDown = (0, react_1.useCallback)((event) => {
38
- if (!selectedCell)
39
- return;
40
- const { key } = event;
41
- let direction = null;
42
- switch (key) {
43
- case 'ArrowUp':
44
- direction = 'up';
45
- break;
46
- case 'ArrowDown':
47
- direction = 'down';
48
- break;
49
- case 'ArrowLeft':
50
- direction = 'left';
51
- break;
52
- case 'ArrowRight':
53
- direction = 'right';
54
- break;
55
- default:
56
- return; // Let the cell handle number/delete keys
57
- }
58
- if (direction) {
59
- onNavigate(direction);
60
- event.preventDefault();
61
- }
62
- }, [selectedCell, onNavigate]);
63
- // Create cell select handler
64
- const createCellSelectHandler = (0, react_1.useCallback)((cellId) => {
65
- return (event) => onCellSelect(cellId, event);
66
- }, [onCellSelect]);
67
- // Create cell value change handler
68
- const createCellValueChangeHandler = (0, react_1.useCallback)((cellId) => {
69
- return (value) => onCellValueChange(cellId, value);
70
- }, [onCellValueChange]);
71
- // Create note toggle handler
72
- const createNoteToggleHandler = (0, react_1.useCallback)((cellId) => {
73
- return (note) => onNoteToggle(cellId, note);
74
- }, [onNoteToggle]);
75
- // Create clear all notes handler
76
- const createClearAllNotesHandler = (0, react_1.useCallback)((cellId) => {
77
- return () => onClearAllNotes(cellId);
78
- }, [onClearAllNotes]);
79
- // Create long press toggle handler
80
- const createLongPressToggleHandler = (0, react_1.useCallback)((cellId) => {
81
- return onLongPressToggle
82
- ? (event) => onLongPressToggle(cellId, event)
83
- : undefined;
84
- }, [onLongPressToggle]);
85
- // Create drag over handler
86
- const createDragOverHandler = (0, react_1.useCallback)((cellId) => {
87
- return onDragOver ? () => onDragOver(cellId) : undefined;
88
- }, [onDragOver]);
89
- // Calculate grid classes
90
- const gridClasses = (0, react_1.useMemo)(() => {
91
- const classes = ['sudoku-grid'];
92
- if (className)
93
- classes.push(className);
94
- return classes.join(' ');
95
- }, [className]);
96
- // Calculate grid template styles for dynamic grid sizes
97
- const gridStyles = (0, react_1.useMemo)(() => {
98
- // Base cell size in pixels (for 9x9 grid, this would be 50px, 60px, 70px at different breakpoints)
99
- const baseCellSizePx = 50; // Base size for small screens
100
- // Calculate width based on number of columns
101
- // We use the max of rows/columns to ensure square cells
102
- const maxDimension = Math.max(numRows, numColumns);
103
- const width = maxDimension * baseCellSizePx;
104
- return {
105
- gridTemplateColumns: `repeat(${numColumns}, 1fr)`,
106
- gridTemplateRows: `repeat(${numRows}, 1fr)`,
107
- width: `min(${width}px, 100%)`,
108
- maxWidth: `${width}px`
109
- };
110
- }, [numRows, numColumns]);
111
- // Calculate border classes based on puzzle type
112
- const borderClasses = (0, react_1.useMemo)(() => {
113
- //if (puzzleType === 'killer') {
114
- // Minimal borders for Killer Sudoku to let cage borders stand out
115
- //return 'gap-px border border-2 border-gray-300 bg-gray-300';
116
- //}
117
- // Standard thick borders for other puzzle types
118
- return 'border-2 border-black bg-black';
119
- }, [puzzleType]);
120
- // Sort cells by row and column for proper display order
121
- const sortedCells = (0, react_1.useMemo)(() => {
122
- return [...cells].sort((a, b) => {
123
- if (a.row !== b.row)
124
- return a.row - b.row;
125
- return a.column - b.column;
126
- });
127
- }, [cells]);
128
- // Calculate grid dimensions for overlays
129
- const gridDimensions = (0, react_1.useMemo)(() => {
130
- // Base cell size in grid units - we'll scale this with CSS
131
- const baseCellSize = 1;
132
- return {
133
- cellSize: baseCellSize,
134
- gridSize: {
135
- width: numColumns * baseCellSize,
136
- height: numRows * baseCellSize
137
- }
138
- };
139
- }, [numRows, numColumns]);
140
- // Render diagonal lines for Sudoku X puzzles
141
- const renderDiagonalLines = () => {
142
- if (puzzleType !== 'sudoku-x')
143
- return null;
144
- return (react_1.default.createElement("div", { className: "absolute inset-0 w-full h-full pointer-events-none", style: {
145
- background: `
146
- linear-gradient(45deg, transparent 49%, rgba(156, 163, 175, 0.15) 49.5%, rgba(156, 163, 175, 0.15) 50.5%, transparent 51%),
147
- linear-gradient(-45deg, transparent 49%, rgba(156, 163, 175, 0.15) 49.5%, rgba(156, 163, 175, 0.15) 50.5%, transparent 51%)
148
- `,
149
- zIndex: 1
150
- } }));
151
- };
152
- // Render cage overlay for Killer Sudoku
153
- const renderCageOverlay = () => {
154
- if (puzzleType !== 'killer-sudoku' || !cages || cages.length === 0)
155
- return null;
156
- return (react_1.default.createElement(CageOverlay_1.CageOverlay, { cages: cages, gridSize: gridDimensions.gridSize, cellSize: gridDimensions.cellSize, numRows: numRows, numColumns: numColumns }));
157
- };
158
- return (react_1.default.createElement("div", { className: `
159
- ${gridClasses}
160
- relative
161
- grid ${puzzleType === 'killer-sudoku' ? 'gap-0' : 'gap-0'}
162
- ${borderClasses}
163
- mx-auto outline-none
164
- aspect-square
165
- `, style: gridStyles, onKeyDown: handleKeyDown, onMouseUp: onDragEnd, onTouchEnd: onDragEnd, role: "grid", "aria-label": "Sudoku puzzle entry grid", tabIndex: 0, "data-testid": "sudoku-grid" },
166
- renderDiagonalLines(),
167
- renderCageOverlay(),
168
- sortedCells.map((cellInfo) => {
169
- const isPrimarySelected = selectedCell === cellInfo.id;
170
- const isInMultiSelection = selectedCells.includes(cellInfo.id);
171
- return (react_1.default.createElement(SudokuCell_1.SudokuCell, { key: cellInfo.id, cellInfo: cellInfo, isSelected: isPrimarySelected || isInMultiSelection, inputMode: inputMode, puzzleDimensions: puzzleDimensions
172
- ? {
173
- numRows,
174
- numColumns,
175
- cageHeight: puzzleDimensions.cageHeightInCells,
176
- cageWidth: puzzleDimensions.cageWidthInCells
177
- }
178
- : undefined, onSelect: createCellSelectHandler(cellInfo.id), onLongPressToggle: createLongPressToggleHandler(cellInfo.id), onValueChange: createCellValueChangeHandler(cellInfo.id), onNoteToggle: createNoteToggleHandler(cellInfo.id), onClearAllNotes: createClearAllNotesHandler(cellInfo.id), onDragOver: createDragOverHandler(cellInfo.id), className: isInMultiSelection && !isPrimarySelected ? 'sudoku-cell--multi-selected' : undefined }));
179
- })));
180
- };
181
- exports.SudokuGrid = SudokuGrid;
182
- //# sourceMappingURL=SudokuGrid.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SudokuGrid.js","sourceRoot":"","sources":["../../src/components/SudokuGrid.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * MIT License\n *\n * Copyright (c) 2023 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useMemo } from 'react';\nimport { CellId, NavigationDirection } from '@fgv/ts-sudoku-lib';\nimport { ISudokuGridProps } from '../types';\nimport { SudokuCell } from './SudokuCell';\nimport { CageOverlay } from './CageOverlay';\n\n/**\n * Grid component that renders the 9x9 Sudoku grid with proper visual structure\n * @public\n */\nexport const SudokuGrid: React.FC<ISudokuGridProps> = ({\n numRows,\n numColumns,\n cells,\n cages,\n selectedCell,\n selectedCells,\n inputMode,\n puzzleType,\n puzzleDimensions,\n onCellSelect,\n onLongPressToggle,\n onCellValueChange,\n onNoteToggle,\n onClearAllNotes,\n onNavigate,\n onDragOver,\n onDragEnd,\n isDragging,\n className\n}) => {\n // Handle keyboard navigation\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (!selectedCell) return;\n\n const { key } = event;\n let direction: NavigationDirection | null = null;\n\n switch (key) {\n case 'ArrowUp':\n direction = 'up';\n break;\n case 'ArrowDown':\n direction = 'down';\n break;\n case 'ArrowLeft':\n direction = 'left';\n break;\n case 'ArrowRight':\n direction = 'right';\n break;\n default:\n return; // Let the cell handle number/delete keys\n }\n\n if (direction) {\n onNavigate(direction);\n event.preventDefault();\n }\n },\n [selectedCell, onNavigate]\n );\n\n // Create cell select handler\n const createCellSelectHandler = useCallback(\n (cellId: CellId) => {\n return (event?: React.MouseEvent) => onCellSelect(cellId, event);\n },\n [onCellSelect]\n );\n\n // Create cell value change handler\n const createCellValueChangeHandler = useCallback(\n (cellId: CellId) => {\n return (value: number | undefined) => onCellValueChange(cellId, value);\n },\n [onCellValueChange]\n );\n\n // Create note toggle handler\n const createNoteToggleHandler = useCallback(\n (cellId: CellId) => {\n return (note: number) => onNoteToggle(cellId, note);\n },\n [onNoteToggle]\n );\n\n // Create clear all notes handler\n const createClearAllNotesHandler = useCallback(\n (cellId: CellId) => {\n return () => onClearAllNotes(cellId);\n },\n [onClearAllNotes]\n );\n\n // Create long press toggle handler\n const createLongPressToggleHandler = useCallback(\n (cellId: CellId) => {\n return onLongPressToggle\n ? (event: React.TouchEvent | React.MouseEvent) => onLongPressToggle(cellId, event)\n : undefined;\n },\n [onLongPressToggle]\n );\n\n // Create drag over handler\n const createDragOverHandler = useCallback(\n (cellId: CellId) => {\n return onDragOver ? () => onDragOver(cellId) : undefined;\n },\n [onDragOver]\n );\n\n // Calculate grid classes\n const gridClasses = useMemo(() => {\n const classes = ['sudoku-grid'];\n if (className) classes.push(className);\n return classes.join(' ');\n }, [className]);\n\n // Calculate grid template styles for dynamic grid sizes\n const gridStyles = useMemo(() => {\n // Base cell size in pixels (for 9x9 grid, this would be 50px, 60px, 70px at different breakpoints)\n const baseCellSizePx = 50; // Base size for small screens\n\n // Calculate width based on number of columns\n // We use the max of rows/columns to ensure square cells\n const maxDimension = Math.max(numRows, numColumns);\n const width = maxDimension * baseCellSizePx;\n\n return {\n gridTemplateColumns: `repeat(${numColumns}, 1fr)`,\n gridTemplateRows: `repeat(${numRows}, 1fr)`,\n width: `min(${width}px, 100%)`,\n maxWidth: `${width}px`\n };\n }, [numRows, numColumns]);\n\n // Calculate border classes based on puzzle type\n const borderClasses = useMemo(() => {\n //if (puzzleType === 'killer') {\n // Minimal borders for Killer Sudoku to let cage borders stand out\n //return 'gap-px border border-2 border-gray-300 bg-gray-300';\n //}\n // Standard thick borders for other puzzle types\n return 'border-2 border-black bg-black';\n }, [puzzleType]);\n\n // Sort cells by row and column for proper display order\n const sortedCells = useMemo(() => {\n return [...cells].sort((a, b) => {\n if (a.row !== b.row) return a.row - b.row;\n return a.column - b.column;\n });\n }, [cells]);\n\n // Calculate grid dimensions for overlays\n const gridDimensions = useMemo(() => {\n // Base cell size in grid units - we'll scale this with CSS\n const baseCellSize = 1;\n return {\n cellSize: baseCellSize,\n gridSize: {\n width: numColumns * baseCellSize,\n height: numRows * baseCellSize\n }\n };\n }, [numRows, numColumns]);\n\n // Render diagonal lines for Sudoku X puzzles\n const renderDiagonalLines = (): React.ReactNode => {\n if (puzzleType !== 'sudoku-x') return null;\n\n return (\n <div\n className=\"absolute inset-0 w-full h-full pointer-events-none\"\n style={{\n background: `\n linear-gradient(45deg, transparent 49%, rgba(156, 163, 175, 0.15) 49.5%, rgba(156, 163, 175, 0.15) 50.5%, transparent 51%),\n linear-gradient(-45deg, transparent 49%, rgba(156, 163, 175, 0.15) 49.5%, rgba(156, 163, 175, 0.15) 50.5%, transparent 51%)\n `,\n zIndex: 1\n }}\n />\n );\n };\n\n // Render cage overlay for Killer Sudoku\n const renderCageOverlay = (): React.ReactNode => {\n if (puzzleType !== 'killer-sudoku' || !cages || cages.length === 0) return null;\n\n return (\n <CageOverlay\n cages={cages}\n gridSize={gridDimensions.gridSize}\n cellSize={gridDimensions.cellSize}\n numRows={numRows}\n numColumns={numColumns}\n />\n );\n };\n\n return (\n <div\n className={`\n ${gridClasses}\n relative\n grid ${puzzleType === 'killer-sudoku' ? 'gap-0' : 'gap-0'}\n ${borderClasses}\n mx-auto outline-none\n aspect-square\n `}\n style={gridStyles}\n onKeyDown={handleKeyDown}\n onMouseUp={onDragEnd}\n onTouchEnd={onDragEnd}\n role=\"grid\"\n aria-label=\"Sudoku puzzle entry grid\"\n tabIndex={0}\n data-testid=\"sudoku-grid\"\n >\n {renderDiagonalLines()}\n {renderCageOverlay()}\n {sortedCells.map((cellInfo) => {\n const isPrimarySelected = selectedCell === cellInfo.id;\n const isInMultiSelection = selectedCells.includes(cellInfo.id);\n\n return (\n <SudokuCell\n key={cellInfo.id}\n cellInfo={cellInfo}\n isSelected={isPrimarySelected || isInMultiSelection}\n inputMode={inputMode}\n puzzleDimensions={\n puzzleDimensions\n ? {\n numRows,\n numColumns,\n cageHeight: puzzleDimensions.cageHeightInCells,\n cageWidth: puzzleDimensions.cageWidthInCells\n }\n : undefined\n }\n onSelect={createCellSelectHandler(cellInfo.id)}\n onLongPressToggle={createLongPressToggleHandler(cellInfo.id)}\n onValueChange={createCellValueChangeHandler(cellInfo.id)}\n onNoteToggle={createNoteToggleHandler(cellInfo.id)}\n onClearAllNotes={createClearAllNotesHandler(cellInfo.id)}\n onDragOver={createDragOverHandler(cellInfo.id)}\n className={isInMultiSelection && !isPrimarySelected ? 'sudoku-cell--multi-selected' : undefined}\n />\n );\n })}\n </div>\n );\n};\n"]}