@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,116 +0,0 @@
1
- /*
2
- * MIT License
3
- *
4
- * Copyright (c) 2023 Erik Fortune
5
- *
6
- * Permission is hereby granted, free of charge, to any person obtaining a copy
7
- * of this software and associated documentation files (the "Software"), to deal
8
- * in the Software without restriction, including without limitation the rights
9
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- * copies of the Software, and to permit persons to whom the Software is
11
- * furnished to do so, subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be included in all
14
- * copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- * SOFTWARE.
23
- */
24
- import { render, act } from '@testing-library/react';
25
- import { createPuzzleDefinition } from '../../../utils/puzzleDefinitionHelper';
26
- export { createPuzzleDefinition };
27
- // Custom render function - just use standard render
28
- export function customRender(ui, options) {
29
- return render(ui, options);
30
- }
31
- // Helper function to simulate long press delay using fake timers
32
- export function advanceByLongPress(ms = 600) {
33
- act(() => {
34
- jest.advanceTimersByTime(ms);
35
- });
36
- }
37
- // Helper to set window dimensions for different device types
38
- export function setWindowDimensions(width, height) {
39
- Object.defineProperty(window, 'innerWidth', {
40
- writable: true,
41
- configurable: true,
42
- value: width
43
- });
44
- Object.defineProperty(window, 'innerHeight', {
45
- writable: true,
46
- configurable: true,
47
- value: height
48
- });
49
- }
50
- // Helper to render with mobile portrait layout (side-by-side keypads)
51
- export function renderWithMobilePortrait(component, options) {
52
- setWindowDimensions(375, 667);
53
- return customRender(component, options);
54
- }
55
- // Helper to render with desktop layout (hidden/overlay keypads)
56
- export function renderWithDesktop(component, options) {
57
- setWindowDimensions(1024, 768);
58
- return customRender(component, options);
59
- }
60
- // Mock window.matchMedia for responsive layout tests
61
- export const mockMatchMedia = (query) => {
62
- return {
63
- matches: false,
64
- media: query,
65
- onchange: null,
66
- addListener: jest.fn(),
67
- removeListener: jest.fn(),
68
- addEventListener: jest.fn(),
69
- removeEventListener: jest.fn(),
70
- dispatchEvent: jest.fn()
71
- };
72
- };
73
- // Test puzzle definitions
74
- export const validPuzzleDescription = createPuzzleDefinition({
75
- id: 'test-puzzle',
76
- description: 'Test Puzzle',
77
- type: 'sudoku',
78
- level: 1,
79
- totalRows: 9,
80
- totalColumns: 9,
81
- cells: '123456789456789123789123456234567891567891234891234567345678912678912345912345678'
82
- });
83
- export const simplePuzzleDescription = createPuzzleDefinition({
84
- id: 'simple-puzzle',
85
- description: 'Simple Test Puzzle',
86
- type: 'sudoku',
87
- level: 1,
88
- totalRows: 9,
89
- totalColumns: 9,
90
- cells: '1' + '.'.repeat(80) // 81 total characters
91
- });
92
- export const sudokuXPuzzleDescription = createPuzzleDefinition({
93
- id: 'sudoku-x-puzzle',
94
- description: 'Sudoku-X Test Puzzle',
95
- type: 'sudoku-x',
96
- level: 1,
97
- totalRows: 9,
98
- totalColumns: 9,
99
- cells: '.'.repeat(81)
100
- });
101
- export const invalidPuzzleDescription = createPuzzleDefinition({
102
- id: 'invalid-puzzle',
103
- description: 'Invalid Puzzle',
104
- type: 'sudoku',
105
- level: 1,
106
- totalRows: 9,
107
- totalColumns: 9,
108
- cells: 'invalid' // Too short
109
- });
110
- export const defaultProps = {
111
- initialPuzzleDescription: undefined,
112
- onStateChange: undefined,
113
- onValidationErrors: undefined,
114
- className: undefined
115
- };
116
- //# sourceMappingURL=SudokuGridEntry.testHelpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SudokuGridEntry.testHelpers.js","sourceRoot":"","sources":["../../../../src/test/unit/components/SudokuGridEntry.testHelpers.ts"],"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 { render, RenderResult, RenderOptions, act } from '@testing-library/react';\nimport { IPuzzleDefinition } from '@fgv/ts-sudoku-lib';\nimport { createPuzzleDefinition } from '../../../utils/puzzleDefinitionHelper';\n\n// Re-export for use in test files\nexport type { IPuzzleDefinition };\nexport { createPuzzleDefinition };\n\n// Custom render function - just use standard render\nexport function customRender(ui: React.ReactElement, options?: RenderOptions): RenderResult {\n return render(ui, options);\n}\n\n// Helper function to simulate long press delay using fake timers\nexport function advanceByLongPress(ms: number = 600): void {\n act(() => {\n jest.advanceTimersByTime(ms);\n });\n}\n\n// Helper to set window dimensions for different device types\nexport function setWindowDimensions(width: number, height: number): void {\n Object.defineProperty(window, 'innerWidth', {\n writable: true,\n configurable: true,\n value: width\n });\n Object.defineProperty(window, 'innerHeight', {\n writable: true,\n configurable: true,\n value: height\n });\n}\n\n// Helper to render with mobile portrait layout (side-by-side keypads)\nexport function renderWithMobilePortrait(\n component: React.ReactElement,\n options?: RenderOptions\n): RenderResult {\n setWindowDimensions(375, 667);\n return customRender(component, options);\n}\n\n// Helper to render with desktop layout (hidden/overlay keypads)\nexport function renderWithDesktop(component: React.ReactElement, options?: RenderOptions): RenderResult {\n setWindowDimensions(1024, 768);\n return customRender(component, options);\n}\n\n// Mock window.matchMedia for responsive layout tests\nexport const mockMatchMedia = (query: string): MediaQueryList => {\n return {\n matches: false,\n media: query,\n onchange: null,\n addListener: jest.fn(),\n removeListener: jest.fn(),\n addEventListener: jest.fn(),\n removeEventListener: jest.fn(),\n dispatchEvent: jest.fn()\n } as unknown as MediaQueryList;\n};\n\n// Test puzzle definitions\nexport const validPuzzleDescription: IPuzzleDefinition = createPuzzleDefinition({\n id: 'test-puzzle',\n description: 'Test Puzzle',\n type: 'sudoku',\n level: 1,\n totalRows: 9,\n totalColumns: 9,\n cells: '123456789456789123789123456234567891567891234891234567345678912678912345912345678'\n});\n\nexport const simplePuzzleDescription: IPuzzleDefinition = createPuzzleDefinition({\n id: 'simple-puzzle',\n description: 'Simple Test Puzzle',\n type: 'sudoku',\n level: 1,\n totalRows: 9,\n totalColumns: 9,\n cells: '1' + '.'.repeat(80) // 81 total characters\n});\n\nexport const sudokuXPuzzleDescription: IPuzzleDefinition = createPuzzleDefinition({\n id: 'sudoku-x-puzzle',\n description: 'Sudoku-X Test Puzzle',\n type: 'sudoku-x',\n level: 1,\n totalRows: 9,\n totalColumns: 9,\n cells: '.'.repeat(81)\n});\n\nexport const invalidPuzzleDescription: IPuzzleDefinition = createPuzzleDefinition({\n id: 'invalid-puzzle',\n description: 'Invalid Puzzle',\n type: 'sudoku',\n level: 1,\n totalRows: 9,\n totalColumns: 9,\n cells: 'invalid' // Too short\n});\n\nexport const defaultProps: {\n initialPuzzleDescription: undefined;\n onStateChange: undefined;\n onValidationErrors: undefined;\n className: undefined;\n} = {\n initialPuzzleDescription: undefined,\n onStateChange: undefined,\n onValidationErrors: undefined,\n className: undefined\n};\n"]}
@@ -1,138 +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.CageOverlay = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importStar(require("react"));
29
- const ts_sudoku_lib_1 = require("@fgv/ts-sudoku-lib");
30
- const CageSumIndicator_1 = require("./CageSumIndicator");
31
- const CagePatternManager_1 = require("../utils/CagePatternManager");
32
- const CageLookupManager_1 = require("../utils/CageLookupManager");
33
- const DiagnosticLoggerContext_1 = require("../contexts/DiagnosticLoggerContext");
34
- /**
35
- * Helper to get cell position from CellId
36
- */
37
- function getTopLeftPosition(cellIds, log, numRows, numColumns) {
38
- if (cellIds.length === 0)
39
- return { top: 0, left: 0 };
40
- const positions = cellIds.map((cellId) => {
41
- const parsed = (0, ts_sudoku_lib_1.parseCellId)(cellId);
42
- if (parsed && parsed.row >= 0 && parsed.row < numRows && parsed.col >= 0 && parsed.col < numColumns) {
43
- return parsed;
44
- }
45
- // Fallback - should not happen with valid CellIds
46
- log.warn(`Invalid CellId format: ${cellId}`);
47
- return { row: 0, col: 0 };
48
- });
49
- // Find the top-left most cell
50
- let minRow = positions[0].row;
51
- let minCol = positions[0].col;
52
- positions.forEach(({ row, col }) => {
53
- if (row < minRow || (row === minRow && col < minCol)) {
54
- minRow = row;
55
- minCol = col;
56
- }
57
- });
58
- // Convert grid coordinates to percentage-based positioning
59
- const cellWidthPercent = 100 / numColumns;
60
- const cellHeightPercent = 100 / numRows;
61
- return {
62
- top: minRow * cellHeightPercent + 0.5, // Small offset from corner
63
- left: minCol * cellWidthPercent + 0.5 // Small offset from corner
64
- };
65
- }
66
- /**
67
- * Component for rendering cage boundaries and sum indicators in Killer Sudoku
68
- * @public
69
- */
70
- const CageOverlay = ({ cages, gridSize, cellSize, numRows = 9, numColumns = 9, className }) => {
71
- const log = (0, DiagnosticLoggerContext_1.useDiagnosticLogger)();
72
- // Initialize managers once
73
- const patternManager = (0, react_1.useMemo)(() => new CagePatternManager_1.CagePatternManager(), []);
74
- const lookupManager = (0, react_1.useMemo)(() => CageLookupManager_1.CageLookupManager.getInstance(), []);
75
- // Debug logging
76
- (0, react_1.useEffect)(() => {
77
- log.info('CageOverlay rendered', {
78
- cageCount: cages.length,
79
- gridSize,
80
- cellSize,
81
- cages: cages.map((c) => ({ id: c.cage.id, cellIds: c.cage.cellIds }))
82
- });
83
- }, [cages, gridSize, cellSize, log]);
84
- // Calculate SVG viewBox and class names
85
- const viewBox = `0 0 ${gridSize.width} ${gridSize.height}`;
86
- const overlayClasses = (0, react_1.useMemo)(() => {
87
- const classes = ['cage-overlay'];
88
- if (className)
89
- classes.push(className);
90
- return classes.join(' ');
91
- }, [className]);
92
- return (react_1.default.createElement("div", { className: `
93
- ${overlayClasses}
94
- absolute inset-0 w-full h-full pointer-events-none
95
- `, style: { zIndex: 1000 }, "data-testid": "cage-overlay" },
96
- react_1.default.createElement("svg", { width: "100%", height: "100%", viewBox: viewBox, className: "absolute inset-0", preserveAspectRatio: "none" }, cages.map((cageInfo, index) => {
97
- const { cage, isHighlighted, isValid } = cageInfo;
98
- // Build occupied cells set for this cage
99
- const occupiedCells = patternManager.buildOccupiedCellsSet(cage.cellIds);
100
- // Render each cell with connected borders
101
- return cage.cellIds.map((cellId, cellIndex) => {
102
- // Parse cell position using centralized function
103
- const parsed = (0, ts_sudoku_lib_1.parseCellId)(cellId);
104
- let row = 0, col = 0;
105
- if (parsed &&
106
- parsed.row >= 0 &&
107
- parsed.row < numRows &&
108
- parsed.col >= 0 &&
109
- parsed.col < numColumns) {
110
- row = parsed.row;
111
- col = parsed.col;
112
- }
113
- else {
114
- log.warn(`Invalid CellId format: ${cellId}`);
115
- }
116
- const x = col * cellSize;
117
- const y = row * cellSize;
118
- // Analyze neighbors and get pattern
119
- const neighbors = patternManager.analyzeNeighbors(cellId, occupiedCells);
120
- const neighborPattern = lookupManager.neighborsToPattern(neighbors);
121
- const pathData = lookupManager.getPatternSVG(neighborPattern);
122
- // Skip if no path data
123
- if (!pathData) {
124
- return null;
125
- }
126
- // Render connected border pattern
127
- return (react_1.default.createElement("g", { key: /* c8 ignore next 1 - defense in depth */ `${cage.id ?? index}-${cellIndex}`, transform: `translate(${x}, ${y}) scale(${cellSize})` },
128
- react_1.default.createElement("path", { d: pathData, stroke: isHighlighted ? '#1e40af' : isValid ? '#2563eb' : '#dc2626', strokeWidth: 0.8 / cellSize, strokeDasharray: "6 4", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "1.0", vectorEffect: "non-scaling-stroke" })));
129
- });
130
- })),
131
- cages.map((cageInfo, index) => {
132
- const { cage, currentSum, isComplete, isValid } = cageInfo;
133
- const position = getTopLeftPosition(cage.cellIds, log, numRows, numColumns);
134
- return (react_1.default.createElement(CageSumIndicator_1.CageSumIndicator, { key: /* c8 ignore next 1 - defense in depth */ cage.id ?? index, cage: cage, currentSum: currentSum, isComplete: isComplete, isValid: isValid, position: position }));
135
- })));
136
- };
137
- exports.CageOverlay = CageOverlay;
138
- //# sourceMappingURL=CageOverlay.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CageOverlay.js","sourceRoot":"","sources":["../../src/components/CageOverlay.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, { useMemo, useEffect } from 'react';\nimport { CellId, parseCellId } from '@fgv/ts-sudoku-lib';\nimport { ICageOverlayProps } from '../types';\nimport { CageSumIndicator } from './CageSumIndicator';\nimport { CagePatternManager } from '../utils/CagePatternManager';\nimport { CageLookupManager } from '../utils/CageLookupManager';\nimport { useDiagnosticLogger } from '../contexts/DiagnosticLoggerContext';\nimport { Logging } from '@fgv/ts-utils';\n\n/**\n * Helper to get cell position from CellId\n */\nfunction getTopLeftPosition(\n cellIds: CellId[],\n log: Logging.LogReporter<unknown, unknown>,\n numRows: number,\n numColumns: number\n): { top: number; left: number } {\n if (cellIds.length === 0) return { top: 0, left: 0 };\n\n const positions = cellIds.map((cellId) => {\n const parsed = parseCellId(cellId);\n if (parsed && parsed.row >= 0 && parsed.row < numRows && parsed.col >= 0 && parsed.col < numColumns) {\n return parsed;\n }\n // Fallback - should not happen with valid CellIds\n log.warn(`Invalid CellId format: ${cellId}`);\n return { row: 0, col: 0 };\n });\n\n // Find the top-left most cell\n let minRow = positions[0].row;\n let minCol = positions[0].col;\n\n positions.forEach(({ row, col }) => {\n if (row < minRow || (row === minRow && col < minCol)) {\n minRow = row;\n minCol = col;\n }\n });\n\n // Convert grid coordinates to percentage-based positioning\n const cellWidthPercent = 100 / numColumns;\n const cellHeightPercent = 100 / numRows;\n\n return {\n top: minRow * cellHeightPercent + 0.5, // Small offset from corner\n left: minCol * cellWidthPercent + 0.5 // Small offset from corner\n };\n}\n\n/**\n * Component for rendering cage boundaries and sum indicators in Killer Sudoku\n * @public\n */\nexport const CageOverlay: React.FC<ICageOverlayProps> = ({\n cages,\n gridSize,\n cellSize,\n numRows = 9,\n numColumns = 9,\n className\n}) => {\n const log = useDiagnosticLogger();\n\n // Initialize managers once\n const patternManager = useMemo(() => new CagePatternManager(), []);\n const lookupManager = useMemo(() => CageLookupManager.getInstance(), []);\n\n // Debug logging\n useEffect(() => {\n log.info('CageOverlay rendered', {\n cageCount: cages.length,\n gridSize,\n cellSize,\n cages: cages.map((c) => ({ id: c.cage.id, cellIds: c.cage.cellIds }))\n });\n }, [cages, gridSize, cellSize, log]);\n\n // Calculate SVG viewBox and class names\n const viewBox = `0 0 ${gridSize.width} ${gridSize.height}`;\n\n const overlayClasses = useMemo(() => {\n const classes = ['cage-overlay'];\n if (className) classes.push(className);\n return classes.join(' ');\n }, [className]);\n\n return (\n <div\n className={`\n ${overlayClasses}\n absolute inset-0 w-full h-full pointer-events-none\n `}\n style={{ zIndex: 1000 }}\n data-testid=\"cage-overlay\"\n >\n {/* SVG for cage boundaries using connected border system */}\n <svg\n width=\"100%\"\n height=\"100%\"\n viewBox={viewBox}\n className=\"absolute inset-0\"\n preserveAspectRatio=\"none\"\n >\n {/* Render connected cage borders using two-stage lookup */}\n {cages.map((cageInfo, index) => {\n const { cage, isHighlighted, isValid } = cageInfo;\n\n // Build occupied cells set for this cage\n const occupiedCells = patternManager.buildOccupiedCellsSet(cage.cellIds);\n\n // Render each cell with connected borders\n return cage.cellIds.map((cellId, cellIndex) => {\n // Parse cell position using centralized function\n const parsed = parseCellId(cellId);\n let row = 0,\n col = 0;\n\n if (\n parsed &&\n parsed.row >= 0 &&\n parsed.row < numRows &&\n parsed.col >= 0 &&\n parsed.col < numColumns\n ) {\n row = parsed.row;\n col = parsed.col;\n } else {\n log.warn(`Invalid CellId format: ${cellId}`);\n }\n\n const x = col * cellSize;\n const y = row * cellSize;\n\n // Analyze neighbors and get pattern\n const neighbors = patternManager.analyzeNeighbors(cellId, occupiedCells);\n const neighborPattern = lookupManager.neighborsToPattern(neighbors);\n const pathData = lookupManager.getPatternSVG(neighborPattern);\n\n // Skip if no path data\n if (!pathData) {\n return null;\n }\n\n // Render connected border pattern\n return (\n <g\n key={/* c8 ignore next 1 - defense in depth */ `${cage.id ?? index}-${cellIndex}`}\n transform={`translate(${x}, ${y}) scale(${cellSize})`}\n >\n <path\n d={pathData}\n stroke={isHighlighted ? '#1e40af' : isValid ? '#2563eb' : '#dc2626'}\n strokeWidth={0.8 / cellSize}\n strokeDasharray=\"6 4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n opacity=\"1.0\"\n vectorEffect=\"non-scaling-stroke\"\n />\n </g>\n );\n });\n })}\n </svg>\n\n {/* Sum indicators */}\n {cages.map((cageInfo, index) => {\n const { cage, currentSum, isComplete, isValid } = cageInfo;\n const position = getTopLeftPosition(cage.cellIds, log, numRows, numColumns);\n\n return (\n <CageSumIndicator\n key={/* c8 ignore next 1 - defense in depth */ cage.id ?? index}\n cage={cage}\n currentSum={currentSum}\n isComplete={isComplete}\n isValid={isValid}\n position={position}\n />\n );\n })}\n </div>\n );\n};\n"]}
@@ -1,71 +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.CageSumIndicator = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importStar(require("react"));
29
- /**
30
- * Component for displaying the target sum of a cage in Killer Sudoku
31
- * @public
32
- */
33
- const CageSumIndicator = ({ cage, currentSum, isComplete, isValid, position, className }) => {
34
- // Calculate display classes based on state
35
- const displayClasses = (0, react_1.useMemo)(() => {
36
- const classes = ['cage-sum-indicator'];
37
- /* c8 ignore next 1 - defense in depth */
38
- if (className)
39
- classes.push(className);
40
- if (!isValid) {
41
- classes.push('cage-sum-indicator--invalid');
42
- }
43
- else if (isComplete) {
44
- classes.push('cage-sum-indicator--complete');
45
- }
46
- else if (currentSum !== undefined) {
47
- classes.push('cage-sum-indicator--partial');
48
- }
49
- return classes.join(' ');
50
- }, [className, isValid, isComplete, currentSum]);
51
- return (react_1.default.createElement("div", { className: `
52
- ${displayClasses}
53
- absolute z-20 pointer-events-none
54
- text-xs font-bold
55
- bg-white border border-gray-400 rounded px-1
56
- min-w-[20px] text-center
57
- shadow-sm
58
- `, style: {
59
- top: `${position.top}%`,
60
- left: `${position.left}%`,
61
- fontSize: '10px',
62
- lineHeight: '16px'
63
- }, "data-testid": `cage-sum-${cage.id}` },
64
- react_1.default.createElement("div", { className: "text-gray-700" }, cage.total),
65
- currentSum !== undefined && (react_1.default.createElement("div", { className: `
66
- text-xs
67
- ${isValid ? 'text-blue-600' : 'text-red-600'}
68
- `, style: { fontSize: '8px', lineHeight: '10px' } }, currentSum))));
69
- };
70
- exports.CageSumIndicator = CageSumIndicator;
71
- //# sourceMappingURL=CageSumIndicator.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CageSumIndicator.js","sourceRoot":"","sources":["../../src/components/CageSumIndicator.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;AAEH,uDAAuC;AAGvC;;;GAGG;AACI,MAAM,gBAAgB,GAAqC,CAAC,EACjE,IAAI,EACJ,UAAU,EACV,UAAU,EACV,OAAO,EACP,QAAQ,EACR,SAAS,EACV,EAAE,EAAE;IACH,2CAA2C;IAC3C,MAAM,cAAc,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE;QAClC,MAAM,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAEvC,yCAAyC;QACzC,IAAI,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEvC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,UAAU,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC/C,CAAC;aAAM,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC9C,CAAC;QAED,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;IAEjD,OAAO,CACL,uCACE,SAAS,EAAE;UACP,cAAc;;;;;;OAMjB,EACD,KAAK,EAAE;YACL,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG;YACvB,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,GAAG;YACzB,QAAQ,EAAE,MAAM;YAChB,UAAU,EAAE,MAAM;SACnB,iBACY,YAAY,IAAI,CAAC,EAAE,EAAE;QAElC,uCAAK,SAAS,EAAC,eAAe,IAAE,IAAI,CAAC,KAAK,CAAO;QAChD,UAAU,KAAK,SAAS,IAAI,CAC3B,uCACE,SAAS,EAAE;;cAEP,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc;WAC7C,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAE7C,UAAU,CACP,CACP,CACG,CACP,CAAC;AACJ,CAAC,CAAC;AA1DW,QAAA,gBAAgB,oBA0D3B","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, { useMemo } from 'react';\nimport { ICageSumIndicatorProps } from '../types';\n\n/**\n * Component for displaying the target sum of a cage in Killer Sudoku\n * @public\n */\nexport const CageSumIndicator: React.FC<ICageSumIndicatorProps> = ({\n cage,\n currentSum,\n isComplete,\n isValid,\n position,\n className\n}) => {\n // Calculate display classes based on state\n const displayClasses = useMemo(() => {\n const classes = ['cage-sum-indicator'];\n\n /* c8 ignore next 1 - defense in depth */\n if (className) classes.push(className);\n\n if (!isValid) {\n classes.push('cage-sum-indicator--invalid');\n } else if (isComplete) {\n classes.push('cage-sum-indicator--complete');\n } else if (currentSum !== undefined) {\n classes.push('cage-sum-indicator--partial');\n }\n\n return classes.join(' ');\n }, [className, isValid, isComplete, currentSum]);\n\n return (\n <div\n className={`\n ${displayClasses}\n absolute z-20 pointer-events-none\n text-xs font-bold\n bg-white border border-gray-400 rounded px-1\n min-w-[20px] text-center\n shadow-sm\n `}\n style={{\n top: `${position.top}%`,\n left: `${position.left}%`,\n fontSize: '10px',\n lineHeight: '16px'\n }}\n data-testid={`cage-sum-${cage.id}`}\n >\n <div className=\"text-gray-700\">{cage.total}</div>\n {currentSum !== undefined && (\n <div\n className={`\n text-xs\n ${isValid ? 'text-blue-600' : 'text-red-600'}\n `}\n style={{ fontSize: '8px', lineHeight: '10px' }}\n >\n {currentSum}\n </div>\n )}\n </div>\n );\n};\n"]}
@@ -1,68 +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.CombinationCard = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importStar(require("react"));
29
- /**
30
- * Individual combination card component with toggle functionality
31
- * @public
32
- */
33
- exports.CombinationCard = react_1.default.memo(({ combination, onToggle, className }) => {
34
- const { combination: numbers, signature, isEliminated } = combination;
35
- const handleClick = (0, react_1.useCallback)(() => {
36
- onToggle(signature);
37
- }, [onToggle, signature]);
38
- const handleKeyDown = (0, react_1.useCallback)((event) => {
39
- if (event.key === ' ' || event.key === 'Enter') {
40
- event.preventDefault();
41
- onToggle(signature);
42
- }
43
- }, [onToggle, signature]);
44
- const cardClasses = [
45
- 'px-3 py-1',
46
- 'rounded border',
47
- 'transition-all duration-200',
48
- 'cursor-pointer select-none',
49
- 'hover:shadow-sm',
50
- 'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1',
51
- 'inline-flex items-center justify-center',
52
- isEliminated
53
- ? 'border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 opacity-60'
54
- : 'border-blue-500 dark:border-blue-400 bg-white dark:bg-gray-800 hover:bg-blue-50 dark:hover:bg-gray-700',
55
- className
56
- ]
57
- .filter(Boolean)
58
- .join(' ');
59
- const numberDisplay = numbers.join('');
60
- const ariaLabel = numbers.join(', ');
61
- return (react_1.default.createElement("button", { type: "button", className: cardClasses, onClick: handleClick, onKeyDown: handleKeyDown, "aria-pressed": isEliminated, "aria-label": `Combination ${ariaLabel}${isEliminated ? ' (eliminated)' : ''}`, role: "button" },
62
- react_1.default.createElement("span", { className: `text-sm font-mono font-bold ${isEliminated ? 'text-gray-500 dark:text-gray-400 line-through' : 'text-gray-900 dark:text-white'}` }, numberDisplay)));
63
- }, (prev, next) => {
64
- return (prev.combination.signature === next.combination.signature &&
65
- prev.combination.isEliminated === next.combination.isEliminated);
66
- });
67
- exports.CombinationCard.displayName = 'CombinationCard';
68
- //# sourceMappingURL=CombinationCard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CombinationCard.js","sourceRoot":"","sources":["../../src/components/CombinationCard.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;AAEH,uDAA2C;AAG3C;;;GAGG;AACU,QAAA,eAAe,GAC1B,eAAK,CAAC,IAAI,CACR,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;IACvC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC;IAEtE,MAAM,WAAW,GAAG,IAAA,mBAAW,EAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IAE1B,MAAM,aAAa,GAAG,IAAA,mBAAW,EAC/B,CAAC,KAA0B,EAAE,EAAE;QAC7B,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC;YAC/C,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,QAAQ,CAAC,SAAS,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,EACD,CAAC,QAAQ,EAAE,SAAS,CAAC,CACtB,CAAC;IAEF,MAAM,WAAW,GAAW;QAC1B,WAAW;QACX,gBAAgB;QAChB,6BAA6B;QAC7B,4BAA4B;QAC5B,iBAAiB;QACjB,yEAAyE;QACzE,yCAAyC;QACzC,YAAY;YACV,CAAC,CAAC,6EAA6E;YAC/E,CAAC,CAAC,wGAAwG;QAC5G,SAAS;KACV;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAErC,OAAO,CACL,0CACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,kBACV,YAAY,gBACd,eAAe,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,EAC5E,IAAI,EAAC,QAAQ;QAEb,wCACE,SAAS,EAAE,+BACT,YAAY,CAAC,CAAC,CAAC,+CAA+C,CAAC,CAAC,CAAC,+BACnE,EAAE,IAED,aAAa,CACT,CACA,CACV,CAAC;AACJ,CAAC,EACD,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;IACb,OAAO,CACL,IAAI,CAAC,WAAW,CAAC,SAAS,KAAK,IAAI,CAAC,WAAW,CAAC,SAAS;QACzD,IAAI,CAAC,WAAW,CAAC,YAAY,KAAK,IAAI,CAAC,WAAW,CAAC,YAAY,CAChE,CAAC;AACJ,CAAC,CACF,CAAC;AAEJ,uBAAe,CAAC,WAAW,GAAG,iBAAiB,CAAC","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 } from 'react';\nimport { ICombinationCardProps } from '../types';\n\n/**\n * Individual combination card component with toggle functionality\n * @public\n */\nexport const CombinationCard: React.NamedExoticComponent<ICombinationCardProps> =\n React.memo<ICombinationCardProps>(\n ({ combination, onToggle, className }) => {\n const { combination: numbers, signature, isEliminated } = combination;\n\n const handleClick = useCallback(() => {\n onToggle(signature);\n }, [onToggle, signature]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault();\n onToggle(signature);\n }\n },\n [onToggle, signature]\n );\n\n const cardClasses: string = [\n 'px-3 py-1',\n 'rounded border',\n 'transition-all duration-200',\n 'cursor-pointer select-none',\n 'hover:shadow-sm',\n 'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1',\n 'inline-flex items-center justify-center',\n isEliminated\n ? 'border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 opacity-60'\n : 'border-blue-500 dark:border-blue-400 bg-white dark:bg-gray-800 hover:bg-blue-50 dark:hover:bg-gray-700',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const numberDisplay = numbers.join('');\n const ariaLabel = numbers.join(', ');\n\n return (\n <button\n type=\"button\"\n className={cardClasses}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n aria-pressed={isEliminated}\n aria-label={`Combination ${ariaLabel}${isEliminated ? ' (eliminated)' : ''}`}\n role=\"button\"\n >\n <span\n className={`text-sm font-mono font-bold ${\n isEliminated ? 'text-gray-500 dark:text-gray-400 line-through' : 'text-gray-900 dark:text-white'\n }`}\n >\n {numberDisplay}\n </span>\n </button>\n );\n },\n (prev, next) => {\n return (\n prev.combination.signature === next.combination.signature &&\n prev.combination.isEliminated === next.combination.isEliminated\n );\n }\n );\n\nCombinationCard.displayName = 'CombinationCard';\n"]}
@@ -1,56 +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.CombinationGrid = void 0;
27
- const tslib_1 = require("tslib");
28
- const react_1 = tslib_1.__importDefault(require("react"));
29
- const CombinationCard_1 = require("./CombinationCard");
30
- /**
31
- * Grid layout component for displaying combination cards
32
- * @public
33
- */
34
- const CombinationGrid = ({ combinations, onToggle, mode, className }) => {
35
- const gridClasses = ['flex flex-wrap gap-2 p-4', 'overflow-y-auto', 'justify-start', className]
36
- .filter(Boolean)
37
- .join(' ');
38
- if (combinations.length === 0) {
39
- return (react_1.default.createElement("div", { className: "flex items-center justify-center p-8 text-gray-500 dark:text-gray-400" },
40
- react_1.default.createElement("p", null, "No combinations available for this cage")));
41
- }
42
- // Sort combinations: valid ones first, then by numeric value
43
- const sortedCombinations = [...combinations].sort((a, b) => {
44
- // First sort by elimination status (valid ones first)
45
- if (a.isEliminated !== b.isEliminated) {
46
- return a.isEliminated ? 1 : -1;
47
- }
48
- // Then sort by the numeric value of the combination
49
- const aValue = parseInt(a.combination.join(''), 10);
50
- const bValue = parseInt(b.combination.join(''), 10);
51
- return aValue - bValue;
52
- });
53
- return (react_1.default.createElement("div", { className: gridClasses }, sortedCombinations.map((combo) => (react_1.default.createElement(CombinationCard_1.CombinationCard, { key: combo.signature, combination: combo, onToggle: onToggle })))));
54
- };
55
- exports.CombinationGrid = CombinationGrid;
56
- //# sourceMappingURL=CombinationGrid.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CombinationGrid.js","sourceRoot":"","sources":["../../src/components/CombinationGrid.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;;AAEH,0DAA0B;AAE1B,uDAAoD;AAEpD;;;GAGG;AACI,MAAM,eAAe,GAAoC,CAAC,EAC/D,YAAY,EACZ,QAAQ,EACR,IAAI,EACJ,SAAS,EACV,EAAE,EAAE;IACH,MAAM,WAAW,GAAG,CAAC,0BAA0B,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,CAAC;SAC5F,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CACL,uCAAK,SAAS,EAAC,uEAAuE;YACpF,mFAA8C,CAC1C,CACP,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,MAAM,kBAAkB,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACzD,sDAAsD;QACtD,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC;YACtC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,oDAAoD;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACpD,OAAO,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,OAAO,CACL,uCAAK,SAAS,EAAE,WAAW,IACxB,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACjC,8BAAC,iCAAe,IAAC,GAAG,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAClF,CAAC,CACE,CACP,CAAC;AACJ,CAAC,CAAC;AArCW,QAAA,eAAe,mBAqC1B","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 from 'react';\nimport { ICombinationGridProps } from '../types';\nimport { CombinationCard } from './CombinationCard';\n\n/**\n * Grid layout component for displaying combination cards\n * @public\n */\nexport const CombinationGrid: React.FC<ICombinationGridProps> = ({\n combinations,\n onToggle,\n mode,\n className\n}) => {\n const gridClasses = ['flex flex-wrap gap-2 p-4', 'overflow-y-auto', 'justify-start', className]\n .filter(Boolean)\n .join(' ');\n\n if (combinations.length === 0) {\n return (\n <div className=\"flex items-center justify-center p-8 text-gray-500 dark:text-gray-400\">\n <p>No combinations available for this cage</p>\n </div>\n );\n }\n\n // Sort combinations: valid ones first, then by numeric value\n const sortedCombinations = [...combinations].sort((a, b) => {\n // First sort by elimination status (valid ones first)\n if (a.isEliminated !== b.isEliminated) {\n return a.isEliminated ? 1 : -1;\n }\n // Then sort by the numeric value of the combination\n const aValue = parseInt(a.combination.join(''), 10);\n const bValue = parseInt(b.combination.join(''), 10);\n return aValue - bValue;\n });\n\n return (\n <div className={gridClasses}>\n {sortedCombinations.map((combo) => (\n <CombinationCard key={combo.signature} combination={combo} onToggle={onToggle} />\n ))}\n </div>\n );\n};\n"]}