@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.
- package/eslint.config.js +18 -0
- package/lib/tsdoc-metadata.json +1 -1
- package/package.json +22 -21
- package/.test-failures-catalog.md +0 -470
- package/CHANGELOG.md +0 -11
- package/CSS_MODULES_MIGRATION_SUMMARY.md +0 -189
- package/dist/ts-sudoku-ui.d.ts +0 -668
- package/etc/ts-sudoku-ui.api.md +0 -481
- package/lib/test/fixtures/killerSudokuPuzzles.d.ts +0 -36
- package/lib/test/fixtures/killerSudokuPuzzles.d.ts.map +0 -1
- package/lib/test/fixtures/killerSudokuPuzzles.js +0 -57
- package/lib/test/fixtures/killerSudokuPuzzles.js.map +0 -1
- package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts +0 -22
- package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts.map +0 -1
- package/lib/test/unit/components/SudokuGridEntry.testHelpers.js +0 -116
- package/lib/test/unit/components/SudokuGridEntry.testHelpers.js.map +0 -1
- package/lib-commonjs/components/CageOverlay.js +0 -138
- package/lib-commonjs/components/CageOverlay.js.map +0 -1
- package/lib-commonjs/components/CageSumIndicator.js +0 -71
- package/lib-commonjs/components/CageSumIndicator.js.map +0 -1
- package/lib-commonjs/components/CombinationCard.js +0 -68
- package/lib-commonjs/components/CombinationCard.js.map +0 -1
- package/lib-commonjs/components/CombinationGrid.js +0 -56
- package/lib-commonjs/components/CombinationGrid.js.map +0 -1
- package/lib-commonjs/components/CompactControlRibbon.js +0 -146
- package/lib-commonjs/components/CompactControlRibbon.js.map +0 -1
- package/lib-commonjs/components/DualKeypad.js +0 -142
- package/lib-commonjs/components/DualKeypad.js.map +0 -1
- package/lib-commonjs/components/KillerCombinationsExplorer.js +0 -74
- package/lib-commonjs/components/KillerCombinationsExplorer.js.map +0 -1
- package/lib-commonjs/components/KillerCombinationsModal.js +0 -140
- package/lib-commonjs/components/KillerCombinationsModal.js.map +0 -1
- package/lib-commonjs/components/KillerCombinationsPanel.js +0 -84
- package/lib-commonjs/components/KillerCombinationsPanel.js.map +0 -1
- package/lib-commonjs/components/NumberKeypad.js +0 -186
- package/lib-commonjs/components/NumberKeypad.js.map +0 -1
- package/lib-commonjs/components/SudokuCell.js +0 -245
- package/lib-commonjs/components/SudokuCell.js.map +0 -1
- package/lib-commonjs/components/SudokuControls.js +0 -145
- package/lib-commonjs/components/SudokuControls.js.map +0 -1
- package/lib-commonjs/components/SudokuGrid.js +0 -182
- package/lib-commonjs/components/SudokuGrid.js.map +0 -1
- package/lib-commonjs/components/SudokuGridEntry.js +0 -378
- package/lib-commonjs/components/SudokuGridEntry.js.map +0 -1
- package/lib-commonjs/components/ValidationDisplay.js +0 -100
- package/lib-commonjs/components/ValidationDisplay.js.map +0 -1
- package/lib-commonjs/contexts/DiagnosticLoggerContext.js +0 -104
- package/lib-commonjs/contexts/DiagnosticLoggerContext.js.map +0 -1
- package/lib-commonjs/contexts/index.js +0 -32
- package/lib-commonjs/contexts/index.js.map +0 -1
- package/lib-commonjs/hooks/useCombinationElimination.js +0 -126
- package/lib-commonjs/hooks/useCombinationElimination.js.map +0 -1
- package/lib-commonjs/hooks/useKeyboardShortcut.js +0 -58
- package/lib-commonjs/hooks/useKeyboardShortcut.js.map +0 -1
- package/lib-commonjs/hooks/useKillerCombinations.js +0 -68
- package/lib-commonjs/hooks/useKillerCombinations.js.map +0 -1
- package/lib-commonjs/hooks/useLongPress.js +0 -72
- package/lib-commonjs/hooks/useLongPress.js.map +0 -1
- package/lib-commonjs/hooks/usePuzzleSession.js +0 -401
- package/lib-commonjs/hooks/usePuzzleSession.js.map +0 -1
- package/lib-commonjs/hooks/useResponsiveLayout.js +0 -142
- package/lib-commonjs/hooks/useResponsiveLayout.js.map +0 -1
- package/lib-commonjs/index.browser.js +0 -29
- package/lib-commonjs/index.browser.js.map +0 -1
- package/lib-commonjs/index.js +0 -73
- package/lib-commonjs/index.js.map +0 -1
- package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js +0 -60
- package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js.map +0 -1
- package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js +0 -125
- package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js.map +0 -1
- package/lib-commonjs/types/index.js +0 -26
- package/lib-commonjs/types/index.js.map +0 -1
- package/lib-commonjs/utils/CageLookupManager.js +0 -287
- package/lib-commonjs/utils/CageLookupManager.js.map +0 -1
- package/lib-commonjs/utils/CagePatternManager.js +0 -143
- package/lib-commonjs/utils/CagePatternManager.js.map +0 -1
- package/lib-commonjs/utils/puzzleDefinitionHelper.js +0 -103
- 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"]}
|