@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,401 +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.usePuzzleSession = usePuzzleSession;
27
- const react_1 = require("react");
28
- const ts_sudoku_lib_1 = require("@fgv/ts-sudoku-lib");
29
- const puzzleDefinitionHelper_1 = require("../utils/puzzleDefinitionHelper");
30
- /**
31
- * Hook for managing puzzle session state and operations
32
- * @public
33
- */
34
- function usePuzzleSession(initialPuzzleDescription) {
35
- const [session, setSession] = (0, react_1.useState)(null);
36
- const [puzzleDescription, setPuzzleDescription] = (0, react_1.useState)(null);
37
- const [selectedCell, setSelectedCell] = (0, react_1.useState)(null);
38
- const [selectedCells, setSelectedCells] = (0, react_1.useState)([]);
39
- const [inputMode, setInputMode] = (0, react_1.useState)('notes'); // Notes-first paradigm
40
- const [error, setError] = (0, react_1.useState)(null);
41
- const [updateCounter, setUpdateCounter] = (0, react_1.useState)(0); // Force re-renders
42
- // Initialize or update puzzle session
43
- (0, react_1.useEffect)(() => {
44
- // Use the provided description or create a default empty 9x9 puzzle
45
- const puzzleDescriptor = initialPuzzleDescription || (0, puzzleDefinitionHelper_1.createEmptyPuzzleDefinition)();
46
- const sessionResult = ts_sudoku_lib_1.Puzzles.Any.create(puzzleDescriptor).onSuccess((puzzle) => ts_sudoku_lib_1.PuzzleSession.create(puzzle));
47
- if (sessionResult.isSuccess()) {
48
- setSession(sessionResult.value);
49
- setPuzzleDescription(puzzleDescriptor);
50
- setError(null);
51
- }
52
- else {
53
- setError(`Failed to create puzzle session: ${sessionResult.message}`);
54
- setSession(null);
55
- setPuzzleDescription(null);
56
- }
57
- }, [initialPuzzleDescription]);
58
- // Get validation errors
59
- const validationErrors = (0, react_1.useMemo)(() => {
60
- if (!session)
61
- return [];
62
- const errors = [];
63
- const invalidCells = session.getInvalidCells();
64
- invalidCells.forEach((cell) => {
65
- const contentsResult = session.state.getCellContents(cell.id);
66
- if (contentsResult.isSuccess() && contentsResult.value.value) {
67
- const contents = contentsResult.value;
68
- // Check for duplicates in row, column, and section
69
- const rowCells = session.rows[cell.row];
70
- const colCells = session.cols[cell.col];
71
- // Calculate section index based on puzzle dimensions
72
- const cageHeight = session.puzzle.dimensions.cageHeightInCells;
73
- const cageWidth = session.puzzle.dimensions.cageWidthInCells;
74
- const sectionRow = Math.floor(cell.row / cageHeight);
75
- const sectionCol = Math.floor(cell.col / cageWidth);
76
- const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);
77
- const sectionIndex = sectionRow * sectionsPerRow + sectionCol;
78
- const sectionCells = session.sections[sectionIndex];
79
- const checkDuplicates = (cageCellIds, type) => {
80
- const duplicates = cageCellIds.filter((cellId) => {
81
- if (cellId === cell.id)
82
- return false;
83
- const cellContentsResult = session.state.getCellContents(cellId);
84
- return cellContentsResult.isSuccess() && cellContentsResult.value.value === contents.value;
85
- });
86
- if (duplicates.length > 0) {
87
- errors.push({
88
- cellId: cell.id,
89
- type,
90
- conflictingCells: duplicates,
91
- message: `Duplicate ${contents.value} in ${type.replace('duplicate-', '').replace('-', ' ')}`
92
- });
93
- }
94
- };
95
- checkDuplicates(rowCells.cellIds, 'duplicate-row');
96
- checkDuplicates(colCells.cellIds, 'duplicate-column');
97
- checkDuplicates(sectionCells.cellIds, 'duplicate-section');
98
- // For Sudoku X puzzles, also check diagonal constraints
99
- if (puzzleDescription?.type === 'sudoku-x') {
100
- // Find diagonal cages in the session
101
- const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');
102
- // Check main diagonal (top-left to bottom-right)
103
- if (cell.row === cell.col && diagonalCages.length > 0) {
104
- const mainDiagonal = diagonalCages[0];
105
- checkDuplicates(mainDiagonal.cellIds, 'duplicate-diagonal');
106
- }
107
- // Check anti-diagonal (top-right to bottom-left)
108
- if (cell.row + cell.col === 8 && diagonalCages.length > 1) {
109
- const antiDiagonal = diagonalCages[1];
110
- checkDuplicates(antiDiagonal.cellIds, 'duplicate-diagonal');
111
- }
112
- }
113
- }
114
- });
115
- return errors;
116
- }, [session, puzzleDescription, updateCounter]);
117
- // Get cell display information
118
- const cellDisplayInfo = (0, react_1.useMemo)(() => {
119
- if (!session)
120
- return [];
121
- return session.cells.map((cell) => {
122
- const contentsResult = session.state.getCellContents(cell.id);
123
- /* c8 ignore next 1 - defense in depth */
124
- const contents = contentsResult.isSuccess() ? contentsResult.value : { value: undefined, notes: [] };
125
- const hasValidationError = validationErrors.some((error) => error.cellId === cell.id);
126
- return {
127
- id: cell.id,
128
- row: cell.row,
129
- column: cell.col,
130
- contents,
131
- isImmutable: cell.immutable,
132
- hasValidationError
133
- };
134
- });
135
- }, [session, validationErrors, updateCounter]);
136
- // Get cage display information for Killer Sudoku
137
- const cageDisplayInfo = (0, react_1.useMemo)(() => {
138
- if (!session || puzzleDescription?.type !== 'killer-sudoku')
139
- return [];
140
- return session.cages
141
- .filter((cage) => cage.cageType === 'killer') // Only show killer cages, not diagonal/section cages
142
- .map((cage) => {
143
- // Calculate current sum
144
- let currentSum = 0;
145
- let isComplete = true;
146
- let hasValues = false;
147
- for (const cellId of cage.cellIds) {
148
- const contentsResult = session.state.getCellContents(cellId);
149
- if (contentsResult.isSuccess() && contentsResult.value.value) {
150
- currentSum += contentsResult.value.value;
151
- hasValues = true;
152
- }
153
- else {
154
- isComplete = false;
155
- }
156
- }
157
- // Check if the current sum is valid (not exceeding target when incomplete, or matching target when complete)
158
- const isValid = isComplete ? currentSum === cage.total : currentSum <= cage.total;
159
- // Check if cage should be highlighted (contains selected cell)
160
- const isHighlighted = selectedCell ? cage.cellIds.includes(selectedCell) : false;
161
- return {
162
- cage,
163
- isHighlighted,
164
- currentSum: hasValues ? currentSum : undefined,
165
- isComplete,
166
- isValid
167
- };
168
- });
169
- }, [session, puzzleDescription, selectedCell, updateCounter]);
170
- // Helper function to remove notes that conflict with a placed value
171
- const removeConflictingNotes = (0, react_1.useCallback)((placedCellId, placedValue) => {
172
- /* c8 ignore next 1 - defense in depth should not happen */
173
- if (!session)
174
- return;
175
- const placedCell = session.cells.find((cell) => cell.id === placedCellId);
176
- /* c8 ignore next 1 - defense in depth should not happen */
177
- if (!placedCell)
178
- return;
179
- // Get cells in the same row, column, and section
180
- const rowCells = session.rows[placedCell.row].cellIds;
181
- const colCells = session.cols[placedCell.col].cellIds;
182
- // Calculate section index based on puzzle dimensions
183
- const cageHeight = session.puzzle.dimensions.cageHeightInCells;
184
- const cageWidth = session.puzzle.dimensions.cageWidthInCells;
185
- const sectionRow = Math.floor(placedCell.row / cageHeight);
186
- const sectionCol = Math.floor(placedCell.col / cageWidth);
187
- const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);
188
- const sectionIndex = sectionRow * sectionsPerRow + sectionCol;
189
- const sectionCells = session.sections[sectionIndex].cellIds;
190
- // Start with row, column, and section cells
191
- const affectedCells = new Set([...rowCells, ...colCells, ...sectionCells]);
192
- // For Sudoku X puzzles, also include diagonal cells
193
- if (puzzleDescription?.type === 'sudoku-x') {
194
- // Find diagonal cages in the session
195
- const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');
196
- // Check main diagonal (top-left to bottom-right)
197
- if (placedCell.row === placedCell.col && diagonalCages.length > 0) {
198
- const mainDiagonal = diagonalCages[0];
199
- mainDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));
200
- }
201
- // Check anti-diagonal (top-right to bottom-left)
202
- if (placedCell.row + placedCell.col === 8 && diagonalCages.length > 1) {
203
- const antiDiagonal = diagonalCages[1];
204
- antiDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));
205
- }
206
- }
207
- // Remove the placed cell itself from the list
208
- affectedCells.delete(placedCellId);
209
- // Update notes for each affected cell
210
- for (const cellId of Array.from(affectedCells)) {
211
- const contentsResult = session.state.getCellContents(cellId);
212
- if (contentsResult.isSuccess()) {
213
- const contents = contentsResult.value;
214
- // Only update if the cell has notes and contains the placed value
215
- if (contents.notes.length > 0 && contents.notes.includes(placedValue) && !contents.value) {
216
- const newNotes = contents.notes.filter((note) => note !== placedValue);
217
- session.updateCellNotes(cellId, newNotes);
218
- }
219
- }
220
- }
221
- }, [session, puzzleDescription]);
222
- // Update cell value with smart note removal
223
- const updateCellValue = (0, react_1.useCallback)((cellId, value) => {
224
- /* c8 ignore next 1 - defense in depth should not happen */
225
- if (!session)
226
- return;
227
- const result = session.updateCellValue(cellId, value);
228
- if (result.isSuccess()) {
229
- // If a value was placed (not removed), remove conflicting notes
230
- if (value !== undefined) {
231
- removeConflictingNotes(cellId, value);
232
- }
233
- // Force re-render by incrementing update counter
234
- setUpdateCounter((prev) => prev + 1);
235
- }
236
- else {
237
- setError(`Failed to update cell: ${result.message}`);
238
- }
239
- }, [session, removeConflictingNotes]);
240
- // Toggle a note in a cell
241
- const toggleCellNote = (0, react_1.useCallback)((cellId, note) => {
242
- /* c8 ignore next 1 - defense in depth should not happen */
243
- if (!session)
244
- return;
245
- // Get current cell contents
246
- const contentsResult = session.state.getCellContents(cellId);
247
- if (contentsResult.isFailure()) {
248
- setError(`Failed to get cell contents: ${contentsResult.message}`);
249
- return;
250
- }
251
- const currentNotes = contentsResult.value.notes;
252
- const newNotes = currentNotes.includes(note)
253
- ? currentNotes.filter((n) => n !== note) // Remove note if it exists
254
- : [...currentNotes, note].sort(); // Add note if it doesn't exist
255
- const result = session.updateCellNotes(cellId, newNotes);
256
- if (result.isSuccess()) {
257
- setUpdateCounter((prev) => prev + 1);
258
- /* c8 ignore next 3 - defense in depth */
259
- }
260
- else {
261
- setError(`Failed to toggle note: ${result.message}`);
262
- }
263
- }, [session]);
264
- // Clear all notes from a cell
265
- const clearCellNotes = (0, react_1.useCallback)((cellId) => {
266
- /* c8 ignore next 1 - defense in depth should not happen */
267
- if (!session)
268
- return;
269
- const result = session.updateCellNotes(cellId, []);
270
- if (result.isSuccess()) {
271
- setUpdateCounter((prev) => prev + 1);
272
- }
273
- else {
274
- setError(`Failed to clear notes: ${result.message}`);
275
- }
276
- }, [session]);
277
- // Navigate to adjacent cell
278
- const navigateToCell = (0, react_1.useCallback)((direction, wrap = 'wrap-around') => {
279
- if (!session || !selectedCell)
280
- return;
281
- const currentCell = session.cells.find((cell) => cell.id === selectedCell);
282
- /* c8 ignore next 1 - defense in depth should not happen */
283
- if (!currentCell)
284
- return;
285
- let newRow = currentCell.row;
286
- let newCol = currentCell.col;
287
- switch (direction) {
288
- case 'up':
289
- newRow = newRow > 0 ? newRow - 1 : wrap === 'wrap-around' ? session.numRows - 1 : newRow;
290
- break;
291
- case 'down':
292
- newRow = newRow < session.numRows - 1 ? newRow + 1 : wrap === 'wrap-around' ? 0 : newRow;
293
- break;
294
- case 'left':
295
- newCol = newCol > 0 ? newCol - 1 : wrap === 'wrap-around' ? session.numColumns - 1 : newCol;
296
- break;
297
- case 'right':
298
- newCol = newCol < session.numColumns - 1 ? newCol + 1 : wrap === 'wrap-around' ? 0 : newCol;
299
- break;
300
- }
301
- const targetCell = session.cells.find((cell) => cell.row === newRow && cell.col === newCol);
302
- if (targetCell) {
303
- setSelectedCell(targetCell.id);
304
- }
305
- }, [session, selectedCell]);
306
- // Undo last move
307
- const undo = (0, react_1.useCallback)(() => {
308
- /* c8 ignore next 1 - defense in depth should not happen */
309
- if (!session)
310
- return;
311
- const result = session.undo();
312
- if (result.isSuccess()) {
313
- setUpdateCounter((prev) => prev + 1);
314
- }
315
- else {
316
- setError(`Failed to undo: ${result.message}`);
317
- }
318
- }, [session]);
319
- // Redo last undone move
320
- const redo = (0, react_1.useCallback)(() => {
321
- /* c8 ignore next 1 - defense in depth should not happen */
322
- if (!session)
323
- return;
324
- const result = session.redo();
325
- if (result.isSuccess()) {
326
- setUpdateCounter((prev) => prev + 1);
327
- }
328
- else {
329
- setError(`Failed to redo: ${result.message}`);
330
- }
331
- }, [session]);
332
- // Reset puzzle to initial state
333
- const reset = (0, react_1.useCallback)(() => {
334
- /* c8 ignore next 1 - defense in depth should not happen */
335
- if (!session)
336
- return;
337
- const result = ts_sudoku_lib_1.PuzzleSession.create(session.puzzle);
338
- if (result.isSuccess()) {
339
- setSession(result.value);
340
- setSelectedCell(null);
341
- setUpdateCounter(0);
342
- /* c8 ignore next 3 - defense in depth */
343
- }
344
- else {
345
- setError(`Failed to reset: ${result.message}`);
346
- }
347
- }, [session]);
348
- // Export puzzle state
349
- const exportPuzzle = (0, react_1.useCallback)(() => {
350
- /* c8 ignore next 1 - defense in depth should not happen */
351
- if (!session)
352
- return null;
353
- const cellsArray = Array(session.numRows * session.numColumns).fill('.');
354
- session.cells.forEach((cell) => {
355
- const contentsResult = session.state.getCellContents(cell.id);
356
- const index = cell.row * session.numColumns + cell.col;
357
- if (contentsResult.isSuccess() && contentsResult.value.value) {
358
- cellsArray[index] = contentsResult.value.value.toString();
359
- }
360
- });
361
- /* c8 ignore next 4 - defense in depth */
362
- const id = session.id && session.id !== '' ? session.id : 'exported-puzzle';
363
- const description = session.description && session.description !== '' ? session.description : 'Exported Puzzle';
364
- const type = puzzleDescription?.type ?? 'sudoku';
365
- return (0, puzzleDefinitionHelper_1.createPuzzleDefinition)({
366
- id,
367
- description,
368
- type,
369
- level: 1,
370
- totalRows: session.numRows,
371
- totalColumns: session.numColumns,
372
- cells: cellsArray.join('')
373
- });
374
- }, [session, puzzleDescription, updateCounter]);
375
- return {
376
- session,
377
- selectedCell,
378
- selectedCells,
379
- inputMode,
380
- setSelectedCell,
381
- setSelectedCells,
382
- setInputMode,
383
- cellDisplayInfo,
384
- cageDisplayInfo,
385
- validationErrors,
386
- isValid: session?.checkIsValid() ?? false,
387
- isSolved: session?.checkIsSolved() ?? false,
388
- canUndo: session?.canUndo ?? false,
389
- canRedo: session?.canRedo ?? false,
390
- error,
391
- updateCellValue,
392
- toggleCellNote,
393
- clearCellNotes,
394
- navigateToCell,
395
- undo,
396
- redo,
397
- reset,
398
- exportPuzzle
399
- };
400
- }
401
- //# sourceMappingURL=usePuzzleSession.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"usePuzzleSession.js","sourceRoot":"","sources":["../../src/hooks/usePuzzleSession.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 { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n PuzzleSession,\n CellId,\n NavigationDirection,\n NavigationWrap,\n Puzzles,\n IPuzzleDefinition\n} from '@fgv/ts-sudoku-lib';\nimport { IValidationError, ICellDisplayInfo, InputMode, ICageDisplayInfo } from '../types';\nimport { createEmptyPuzzleDefinition, createPuzzleDefinition } from '../utils/puzzleDefinitionHelper';\n\n/**\n * Hook for managing puzzle session state and operations\n * @public\n */\nexport function usePuzzleSession(initialPuzzleDescription?: IPuzzleDefinition): {\n session: PuzzleSession | null;\n selectedCell: CellId | null;\n selectedCells: CellId[];\n inputMode: InputMode;\n setSelectedCell: (cell: CellId | null) => void;\n setSelectedCells: (cells: CellId[]) => void;\n setInputMode: (mode: InputMode) => void;\n cellDisplayInfo: ICellDisplayInfo[];\n cageDisplayInfo: ICageDisplayInfo[];\n validationErrors: IValidationError[];\n isValid: boolean;\n isSolved: boolean;\n canUndo: boolean;\n canRedo: boolean;\n error: string | null;\n updateCellValue: (cellId: CellId, value: number | undefined) => void;\n toggleCellNote: (cellId: CellId, note: number) => void;\n clearCellNotes: (cellId: CellId) => void;\n navigateToCell: (direction: NavigationDirection, wrap?: NavigationWrap) => void;\n undo: () => void;\n redo: () => void;\n reset: () => void;\n exportPuzzle: () => IPuzzleDefinition | null;\n} {\n const [session, setSession] = useState<PuzzleSession | null>(null);\n const [puzzleDescription, setPuzzleDescription] = useState<IPuzzleDefinition | null>(null);\n const [selectedCell, setSelectedCell] = useState<CellId | null>(null);\n const [selectedCells, setSelectedCells] = useState<CellId[]>([]);\n const [inputMode, setInputMode] = useState<InputMode>('notes'); // Notes-first paradigm\n const [error, setError] = useState<string | null>(null);\n const [updateCounter, setUpdateCounter] = useState(0); // Force re-renders\n\n // Initialize or update puzzle session\n useEffect(() => {\n // Use the provided description or create a default empty 9x9 puzzle\n const puzzleDescriptor: IPuzzleDefinition = initialPuzzleDescription || createEmptyPuzzleDefinition();\n\n const sessionResult = Puzzles.Any.create(puzzleDescriptor).onSuccess((puzzle) =>\n PuzzleSession.create(puzzle)\n );\n\n if (sessionResult.isSuccess()) {\n setSession(sessionResult.value);\n setPuzzleDescription(puzzleDescriptor);\n setError(null);\n } else {\n setError(`Failed to create puzzle session: ${sessionResult.message}`);\n setSession(null);\n setPuzzleDescription(null);\n }\n }, [initialPuzzleDescription]);\n\n // Get validation errors\n const validationErrors = useMemo((): IValidationError[] => {\n if (!session) return [];\n\n const errors: IValidationError[] = [];\n const invalidCells = session.getInvalidCells();\n\n invalidCells.forEach((cell) => {\n const contentsResult = session.state.getCellContents(cell.id);\n if (contentsResult.isSuccess() && contentsResult.value.value) {\n const contents = contentsResult.value;\n\n // Check for duplicates in row, column, and section\n const rowCells = session.rows[cell.row];\n const colCells = session.cols[cell.col];\n\n // Calculate section index based on puzzle dimensions\n const cageHeight = session.puzzle.dimensions.cageHeightInCells;\n const cageWidth = session.puzzle.dimensions.cageWidthInCells;\n const sectionRow = Math.floor(cell.row / cageHeight);\n const sectionCol = Math.floor(cell.col / cageWidth);\n const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);\n const sectionIndex = sectionRow * sectionsPerRow + sectionCol;\n const sectionCells = session.sections[sectionIndex];\n\n const checkDuplicates = (cageCellIds: CellId[], type: IValidationError['type']): void => {\n const duplicates = cageCellIds.filter((cellId) => {\n if (cellId === cell.id) return false;\n const cellContentsResult = session.state.getCellContents(cellId);\n return cellContentsResult.isSuccess() && cellContentsResult.value.value === contents.value;\n });\n\n if (duplicates.length > 0) {\n errors.push({\n cellId: cell.id,\n type,\n conflictingCells: duplicates,\n message: `Duplicate ${contents.value} in ${type.replace('duplicate-', '').replace('-', ' ')}`\n });\n }\n };\n\n checkDuplicates(rowCells.cellIds, 'duplicate-row');\n checkDuplicates(colCells.cellIds, 'duplicate-column');\n checkDuplicates(sectionCells.cellIds, 'duplicate-section');\n\n // For Sudoku X puzzles, also check diagonal constraints\n if (puzzleDescription?.type === 'sudoku-x') {\n // Find diagonal cages in the session\n const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');\n\n // Check main diagonal (top-left to bottom-right)\n if (cell.row === cell.col && diagonalCages.length > 0) {\n const mainDiagonal = diagonalCages[0];\n checkDuplicates(mainDiagonal.cellIds, 'duplicate-diagonal');\n }\n\n // Check anti-diagonal (top-right to bottom-left)\n if (cell.row + cell.col === 8 && diagonalCages.length > 1) {\n const antiDiagonal = diagonalCages[1];\n checkDuplicates(antiDiagonal.cellIds, 'duplicate-diagonal');\n }\n }\n }\n });\n\n return errors;\n }, [session, puzzleDescription, updateCounter]);\n\n // Get cell display information\n const cellDisplayInfo = useMemo((): ICellDisplayInfo[] => {\n if (!session) return [];\n\n return session.cells.map((cell) => {\n const contentsResult = session.state.getCellContents(cell.id);\n /* c8 ignore next 1 - defense in depth */\n const contents = contentsResult.isSuccess() ? contentsResult.value : { value: undefined, notes: [] };\n const hasValidationError = validationErrors.some((error) => error.cellId === cell.id);\n\n return {\n id: cell.id,\n row: cell.row,\n column: cell.col,\n contents,\n isImmutable: cell.immutable,\n hasValidationError\n };\n });\n }, [session, validationErrors, updateCounter]);\n\n // Get cage display information for Killer Sudoku\n const cageDisplayInfo = useMemo((): ICageDisplayInfo[] => {\n if (!session || puzzleDescription?.type !== 'killer-sudoku') return [];\n\n return session.cages\n .filter((cage) => cage.cageType === 'killer') // Only show killer cages, not diagonal/section cages\n .map((cage) => {\n // Calculate current sum\n let currentSum = 0;\n let isComplete = true;\n let hasValues = false;\n\n for (const cellId of cage.cellIds) {\n const contentsResult = session.state.getCellContents(cellId);\n if (contentsResult.isSuccess() && contentsResult.value.value) {\n currentSum += contentsResult.value.value;\n hasValues = true;\n } else {\n isComplete = false;\n }\n }\n\n // Check if the current sum is valid (not exceeding target when incomplete, or matching target when complete)\n const isValid = isComplete ? currentSum === cage.total : currentSum <= cage.total;\n\n // Check if cage should be highlighted (contains selected cell)\n const isHighlighted = selectedCell ? cage.cellIds.includes(selectedCell) : false;\n\n return {\n cage,\n isHighlighted,\n currentSum: hasValues ? currentSum : undefined,\n isComplete,\n isValid\n };\n });\n }, [session, puzzleDescription, selectedCell, updateCounter]);\n\n // Helper function to remove notes that conflict with a placed value\n const removeConflictingNotes = useCallback(\n (placedCellId: CellId, placedValue: number) => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const placedCell = session.cells.find((cell) => cell.id === placedCellId);\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!placedCell) return;\n\n // Get cells in the same row, column, and section\n const rowCells = session.rows[placedCell.row].cellIds;\n const colCells = session.cols[placedCell.col].cellIds;\n\n // Calculate section index based on puzzle dimensions\n const cageHeight = session.puzzle.dimensions.cageHeightInCells;\n const cageWidth = session.puzzle.dimensions.cageWidthInCells;\n const sectionRow = Math.floor(placedCell.row / cageHeight);\n const sectionCol = Math.floor(placedCell.col / cageWidth);\n const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);\n const sectionIndex = sectionRow * sectionsPerRow + sectionCol;\n const sectionCells = session.sections[sectionIndex].cellIds;\n\n // Start with row, column, and section cells\n const affectedCells = new Set([...rowCells, ...colCells, ...sectionCells]);\n\n // For Sudoku X puzzles, also include diagonal cells\n if (puzzleDescription?.type === 'sudoku-x') {\n // Find diagonal cages in the session\n const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');\n\n // Check main diagonal (top-left to bottom-right)\n if (placedCell.row === placedCell.col && diagonalCages.length > 0) {\n const mainDiagonal = diagonalCages[0];\n mainDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));\n }\n\n // Check anti-diagonal (top-right to bottom-left)\n if (placedCell.row + placedCell.col === 8 && diagonalCages.length > 1) {\n const antiDiagonal = diagonalCages[1];\n antiDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));\n }\n }\n\n // Remove the placed cell itself from the list\n affectedCells.delete(placedCellId);\n\n // Update notes for each affected cell\n for (const cellId of Array.from(affectedCells)) {\n const contentsResult = session.state.getCellContents(cellId);\n if (contentsResult.isSuccess()) {\n const contents = contentsResult.value;\n\n // Only update if the cell has notes and contains the placed value\n if (contents.notes.length > 0 && contents.notes.includes(placedValue) && !contents.value) {\n const newNotes = contents.notes.filter((note) => note !== placedValue);\n session.updateCellNotes(cellId, newNotes);\n }\n }\n }\n },\n [session, puzzleDescription]\n );\n\n // Update cell value with smart note removal\n const updateCellValue = useCallback(\n (cellId: CellId, value: number | undefined) => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const result = session.updateCellValue(cellId, value);\n if (result.isSuccess()) {\n // If a value was placed (not removed), remove conflicting notes\n if (value !== undefined) {\n removeConflictingNotes(cellId, value);\n }\n\n // Force re-render by incrementing update counter\n setUpdateCounter((prev) => prev + 1);\n } else {\n setError(`Failed to update cell: ${result.message}`);\n }\n },\n [session, removeConflictingNotes]\n );\n\n // Toggle a note in a cell\n const toggleCellNote = useCallback(\n (cellId: CellId, note: number) => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n // Get current cell contents\n const contentsResult = session.state.getCellContents(cellId);\n if (contentsResult.isFailure()) {\n setError(`Failed to get cell contents: ${contentsResult.message}`);\n return;\n }\n\n const currentNotes = contentsResult.value.notes;\n const newNotes = currentNotes.includes(note)\n ? currentNotes.filter((n) => n !== note) // Remove note if it exists\n : [...currentNotes, note].sort(); // Add note if it doesn't exist\n\n const result = session.updateCellNotes(cellId, newNotes);\n if (result.isSuccess()) {\n setUpdateCounter((prev) => prev + 1);\n /* c8 ignore next 3 - defense in depth */\n } else {\n setError(`Failed to toggle note: ${result.message}`);\n }\n },\n [session]\n );\n\n // Clear all notes from a cell\n const clearCellNotes = useCallback(\n (cellId: CellId) => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const result = session.updateCellNotes(cellId, []);\n if (result.isSuccess()) {\n setUpdateCounter((prev) => prev + 1);\n } else {\n setError(`Failed to clear notes: ${result.message}`);\n }\n },\n [session]\n );\n\n // Navigate to adjacent cell\n const navigateToCell = useCallback(\n (direction: NavigationDirection, wrap: NavigationWrap = 'wrap-around') => {\n if (!session || !selectedCell) return;\n\n const currentCell = session.cells.find((cell) => cell.id === selectedCell);\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!currentCell) return;\n\n let newRow = currentCell.row;\n let newCol = currentCell.col;\n\n switch (direction) {\n case 'up':\n newRow = newRow > 0 ? newRow - 1 : wrap === 'wrap-around' ? session.numRows - 1 : newRow;\n break;\n case 'down':\n newRow = newRow < session.numRows - 1 ? newRow + 1 : wrap === 'wrap-around' ? 0 : newRow;\n break;\n case 'left':\n newCol = newCol > 0 ? newCol - 1 : wrap === 'wrap-around' ? session.numColumns - 1 : newCol;\n break;\n case 'right':\n newCol = newCol < session.numColumns - 1 ? newCol + 1 : wrap === 'wrap-around' ? 0 : newCol;\n break;\n }\n\n const targetCell = session.cells.find((cell) => cell.row === newRow && cell.col === newCol);\n if (targetCell) {\n setSelectedCell(targetCell.id);\n }\n },\n [session, selectedCell]\n );\n\n // Undo last move\n const undo = useCallback(() => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const result = session.undo();\n if (result.isSuccess()) {\n setUpdateCounter((prev) => prev + 1);\n } else {\n setError(`Failed to undo: ${result.message}`);\n }\n }, [session]);\n\n // Redo last undone move\n const redo = useCallback(() => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const result = session.redo();\n if (result.isSuccess()) {\n setUpdateCounter((prev) => prev + 1);\n } else {\n setError(`Failed to redo: ${result.message}`);\n }\n }, [session]);\n\n // Reset puzzle to initial state\n const reset = useCallback(() => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return;\n\n const result = PuzzleSession.create(session.puzzle);\n if (result.isSuccess()) {\n setSession(result.value);\n setSelectedCell(null);\n setUpdateCounter(0);\n /* c8 ignore next 3 - defense in depth */\n } else {\n setError(`Failed to reset: ${result.message}`);\n }\n }, [session]);\n\n // Export puzzle state\n const exportPuzzle = useCallback((): IPuzzleDefinition | null => {\n /* c8 ignore next 1 - defense in depth should not happen */\n if (!session) return null;\n\n const cellsArray = Array(session.numRows * session.numColumns).fill('.');\n\n session.cells.forEach((cell) => {\n const contentsResult = session.state.getCellContents(cell.id);\n const index = cell.row * session.numColumns + cell.col;\n if (contentsResult.isSuccess() && contentsResult.value.value) {\n cellsArray[index] = contentsResult.value.value.toString();\n }\n });\n\n /* c8 ignore next 4 - defense in depth */\n const id = session.id && session.id !== '' ? session.id : 'exported-puzzle';\n const description =\n session.description && session.description !== '' ? session.description : 'Exported Puzzle';\n const type = puzzleDescription?.type ?? 'sudoku';\n\n return createPuzzleDefinition({\n id,\n description,\n type,\n level: 1,\n totalRows: session.numRows,\n totalColumns: session.numColumns,\n cells: cellsArray.join('')\n });\n }, [session, puzzleDescription, updateCounter]);\n\n return {\n session,\n selectedCell,\n selectedCells,\n inputMode,\n setSelectedCell,\n setSelectedCells,\n setInputMode,\n cellDisplayInfo,\n cageDisplayInfo,\n validationErrors,\n isValid: session?.checkIsValid() ?? false,\n isSolved: session?.checkIsSolved() ?? false,\n canUndo: session?.canUndo ?? false,\n canRedo: session?.canRedo ?? false,\n error,\n updateCellValue,\n toggleCellNote,\n clearCellNotes,\n navigateToCell,\n undo,\n redo,\n reset,\n exportPuzzle\n };\n}\n"]}
@@ -1,142 +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.useResponsiveLayout = useResponsiveLayout;
27
- const react_1 = require("react");
28
- /**
29
- * Breakpoints for responsive design
30
- */
31
- const BREAKPOINTS = {
32
- mobile: 768,
33
- tablet: 1024,
34
- smallHeight: 600
35
- };
36
- /**
37
- * Detect if device supports touch
38
- */
39
- function detectTouchSupport() {
40
- return ('ontouchstart' in window ||
41
- navigator.maxTouchPoints > 0 ||
42
- (navigator.msMaxTouchPoints ?? 0) > 0);
43
- }
44
- /**
45
- * Determine device type based on screen size and touch capability
46
- */
47
- function determineDeviceType(width, height, isTouch) {
48
- if (width <= BREAKPOINTS.mobile) {
49
- return 'mobile';
50
- }
51
- if (width <= BREAKPOINTS.tablet && isTouch) {
52
- return 'tablet';
53
- }
54
- return 'desktop';
55
- }
56
- /**
57
- * Determine optimal keypad layout mode
58
- */
59
- function determineKeypadLayoutMode(deviceType, orientation, width, height, isTouch) {
60
- // Desktop: show overlay on demand or hidden by default
61
- if (deviceType === 'desktop') {
62
- return isTouch ? 'overlay' : 'hidden';
63
- }
64
- // Mobile portrait: side-by-side if width allows
65
- if (deviceType === 'mobile' && orientation === 'portrait') {
66
- return width >= 360 ? 'side-by-side' : 'stacked';
67
- }
68
- // Mobile landscape: stacked beside grid if height allows
69
- if (deviceType === 'mobile' && orientation === 'landscape') {
70
- return height >= BREAKPOINTS.smallHeight ? 'stacked' : 'overlay';
71
- }
72
- // Tablet: side-by-side in portrait, stacked in landscape
73
- if (deviceType === 'tablet') {
74
- return orientation === 'portrait' ? 'side-by-side' : 'stacked';
75
- }
76
- /* c8 ignore next 2 - defense in depth: all device types are explicitly handled above */
77
- return 'overlay';
78
- }
79
- /**
80
- * Hook to detect responsive layout information for optimal keypad display
81
- * @param forceLayoutMode - Optional layout mode override for testing
82
- * @public
83
- */
84
- function useResponsiveLayout(forceLayoutMode) {
85
- const [layoutInfo, setLayoutInfo] = (0, react_1.useState)(() => {
86
- const width = window.innerWidth;
87
- const height = window.innerHeight;
88
- const isTouchDevice = detectTouchSupport();
89
- const orientation = width > height ? 'landscape' : 'portrait';
90
- const deviceType = determineDeviceType(width, height, isTouchDevice);
91
- const keypadLayoutMode = forceLayoutMode ?? determineKeypadLayoutMode(deviceType, orientation, width, height, isTouchDevice);
92
- return {
93
- deviceType,
94
- orientation,
95
- keypadLayoutMode,
96
- screenWidth: width,
97
- screenHeight: height,
98
- isTouchDevice,
99
- isSmallScreen: width <= BREAKPOINTS.mobile,
100
- hasSpaceForDualKeypads: width >= 480 || (orientation === 'landscape' && height >= BREAKPOINTS.smallHeight)
101
- };
102
- });
103
- (0, react_1.useEffect)(() => {
104
- // Don't set up listeners if layout mode is forced
105
- if (forceLayoutMode) {
106
- return;
107
- }
108
- function updateLayoutInfo() {
109
- const width = window.innerWidth;
110
- const height = window.innerHeight;
111
- const isTouchDevice = detectTouchSupport();
112
- const orientation = width > height ? 'landscape' : 'portrait';
113
- const deviceType = determineDeviceType(width, height, isTouchDevice);
114
- const keypadLayoutMode = determineKeypadLayoutMode(deviceType, orientation, width, height, isTouchDevice);
115
- setLayoutInfo({
116
- deviceType,
117
- orientation,
118
- keypadLayoutMode,
119
- screenWidth: width,
120
- screenHeight: height,
121
- isTouchDevice,
122
- isSmallScreen: width <= BREAKPOINTS.mobile,
123
- hasSpaceForDualKeypads: width >= 480 || (orientation === 'landscape' && height >= BREAKPOINTS.smallHeight)
124
- });
125
- }
126
- // Update on resize
127
- window.addEventListener('resize', updateLayoutInfo);
128
- // Update on orientation change
129
- window.addEventListener('orientationchange', () => {
130
- // Small delay to ensure screen dimensions are updated
131
- setTimeout(updateLayoutInfo, 100);
132
- });
133
- // Initial update
134
- updateLayoutInfo();
135
- return () => {
136
- window.removeEventListener('resize', updateLayoutInfo);
137
- window.removeEventListener('orientationchange', updateLayoutInfo);
138
- };
139
- }, [forceLayoutMode]);
140
- return layoutInfo;
141
- }
142
- //# sourceMappingURL=useResponsiveLayout.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useResponsiveLayout.js","sourceRoot":"","sources":["../../src/hooks/useResponsiveLayout.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 { useState, useEffect } from 'react';\n\n/**\n * Device type detection result\n * @public\n */\nexport type DeviceType = 'mobile' | 'tablet' | 'desktop';\n\n/**\n * Screen orientation\n * @public\n */\nexport type ScreenOrientation = 'portrait' | 'landscape';\n\n/**\n * Layout mode for dual keypad\n * @public\n */\nexport type KeypadLayoutMode = 'side-by-side' | 'stacked' | 'overlay' | 'hidden';\n\n/**\n * Responsive layout information\n * @public\n */\nexport interface IResponsiveLayoutInfo {\n /** Device type based on screen size and touch capability */\n readonly deviceType: DeviceType;\n\n /** Current screen orientation */\n readonly orientation: ScreenOrientation;\n\n /** Recommended keypad layout mode */\n readonly keypadLayoutMode: KeypadLayoutMode;\n\n /** Screen width in pixels */\n readonly screenWidth: number;\n\n /** Screen height in pixels */\n readonly screenHeight: number;\n\n /** Whether touch is supported */\n readonly isTouchDevice: boolean;\n\n /** Whether the screen is considered small */\n readonly isSmallScreen: boolean;\n\n /** Whether the screen has enough space for dual keypads */\n readonly hasSpaceForDualKeypads: boolean;\n}\n\n/**\n * Breakpoints for responsive design\n */\nconst BREAKPOINTS = {\n mobile: 768,\n tablet: 1024,\n smallHeight: 600\n} as const;\n\n/**\n * Detect if device supports touch\n */\nfunction detectTouchSupport(): boolean {\n return (\n 'ontouchstart' in window ||\n navigator.maxTouchPoints > 0 ||\n ((navigator as unknown as { msMaxTouchPoints?: number }).msMaxTouchPoints ?? 0) > 0\n );\n}\n\n/**\n * Determine device type based on screen size and touch capability\n */\nfunction determineDeviceType(width: number, height: number, isTouch: boolean): DeviceType {\n if (width <= BREAKPOINTS.mobile) {\n return 'mobile';\n }\n\n if (width <= BREAKPOINTS.tablet && isTouch) {\n return 'tablet';\n }\n\n return 'desktop';\n}\n\n/**\n * Determine optimal keypad layout mode\n */\nfunction determineKeypadLayoutMode(\n deviceType: DeviceType,\n orientation: ScreenOrientation,\n width: number,\n height: number,\n isTouch: boolean\n): KeypadLayoutMode {\n // Desktop: show overlay on demand or hidden by default\n if (deviceType === 'desktop') {\n return isTouch ? 'overlay' : 'hidden';\n }\n\n // Mobile portrait: side-by-side if width allows\n if (deviceType === 'mobile' && orientation === 'portrait') {\n return width >= 360 ? 'side-by-side' : 'stacked';\n }\n\n // Mobile landscape: stacked beside grid if height allows\n if (deviceType === 'mobile' && orientation === 'landscape') {\n return height >= BREAKPOINTS.smallHeight ? 'stacked' : 'overlay';\n }\n\n // Tablet: side-by-side in portrait, stacked in landscape\n if (deviceType === 'tablet') {\n return orientation === 'portrait' ? 'side-by-side' : 'stacked';\n }\n /* c8 ignore next 2 - defense in depth: all device types are explicitly handled above */\n return 'overlay';\n}\n\n/**\n * Hook to detect responsive layout information for optimal keypad display\n * @param forceLayoutMode - Optional layout mode override for testing\n * @public\n */\nexport function useResponsiveLayout(forceLayoutMode?: KeypadLayoutMode): IResponsiveLayoutInfo {\n const [layoutInfo, setLayoutInfo] = useState<IResponsiveLayoutInfo>(() => {\n const width = window.innerWidth;\n const height = window.innerHeight;\n const isTouchDevice = detectTouchSupport();\n const orientation: ScreenOrientation = width > height ? 'landscape' : 'portrait';\n const deviceType = determineDeviceType(width, height, isTouchDevice);\n const keypadLayoutMode =\n forceLayoutMode ?? determineKeypadLayoutMode(deviceType, orientation, width, height, isTouchDevice);\n\n return {\n deviceType,\n orientation,\n keypadLayoutMode,\n screenWidth: width,\n screenHeight: height,\n isTouchDevice,\n isSmallScreen: width <= BREAKPOINTS.mobile,\n hasSpaceForDualKeypads:\n width >= 480 || (orientation === 'landscape' && height >= BREAKPOINTS.smallHeight)\n };\n });\n\n useEffect(() => {\n // Don't set up listeners if layout mode is forced\n if (forceLayoutMode) {\n return;\n }\n\n function updateLayoutInfo(): void {\n const width = window.innerWidth;\n const height = window.innerHeight;\n const isTouchDevice = detectTouchSupport();\n const orientation: ScreenOrientation = width > height ? 'landscape' : 'portrait';\n const deviceType = determineDeviceType(width, height, isTouchDevice);\n const keypadLayoutMode = determineKeypadLayoutMode(\n deviceType,\n orientation,\n width,\n height,\n isTouchDevice\n );\n\n setLayoutInfo({\n deviceType,\n orientation,\n keypadLayoutMode,\n screenWidth: width,\n screenHeight: height,\n isTouchDevice,\n isSmallScreen: width <= BREAKPOINTS.mobile,\n hasSpaceForDualKeypads:\n width >= 480 || (orientation === 'landscape' && height >= BREAKPOINTS.smallHeight)\n });\n }\n\n // Update on resize\n window.addEventListener('resize', updateLayoutInfo);\n\n // Update on orientation change\n window.addEventListener('orientationchange', () => {\n // Small delay to ensure screen dimensions are updated\n setTimeout(updateLayoutInfo, 100);\n });\n\n // Initial update\n updateLayoutInfo();\n\n return () => {\n window.removeEventListener('resize', updateLayoutInfo);\n window.removeEventListener('orientationchange', updateLayoutInfo);\n };\n }, [forceLayoutMode]);\n\n return layoutInfo;\n}\n"]}