@fgv/ts-sudoku-ui 5.0.1-2

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 (190) hide show
  1. package/.rush/temp/chunked-rush-logs/ts-sudoku-ui.build.chunks.jsonl +12 -0
  2. package/.rush/temp/operation/build/log-chunks.jsonl +12 -0
  3. package/.rush/temp/operation/build/state.json +3 -0
  4. package/.rush/temp/shrinkwrap-deps.json +1087 -0
  5. package/.test-failures-catalog.md +470 -0
  6. package/CSS_MODULES_MIGRATION_SUMMARY.md +189 -0
  7. package/dist/ts-sudoku-ui.d.ts +668 -0
  8. package/etc/ts-sudoku-ui.api.md +481 -0
  9. package/lib/components/CageOverlay.d.ts +8 -0
  10. package/lib/components/CageOverlay.d.ts.map +1 -0
  11. package/lib/components/CageOverlay.js +133 -0
  12. package/lib/components/CageOverlay.js.map +1 -0
  13. package/lib/components/CageSumIndicator.d.ts +8 -0
  14. package/lib/components/CageSumIndicator.d.ts.map +1 -0
  15. package/lib/components/CageSumIndicator.js +66 -0
  16. package/lib/components/CageSumIndicator.js.map +1 -0
  17. package/lib/components/CombinationCard.d.ts +8 -0
  18. package/lib/components/CombinationCard.d.ts.map +1 -0
  19. package/lib/components/CombinationCard.js +64 -0
  20. package/lib/components/CombinationCard.js.map +1 -0
  21. package/lib/components/CombinationGrid.d.ts +8 -0
  22. package/lib/components/CombinationGrid.d.ts.map +1 -0
  23. package/lib/components/CombinationGrid.js +51 -0
  24. package/lib/components/CombinationGrid.js.map +1 -0
  25. package/lib/components/CompactControlRibbon.d.ts +45 -0
  26. package/lib/components/CompactControlRibbon.d.ts.map +1 -0
  27. package/lib/components/CompactControlRibbon.js +141 -0
  28. package/lib/components/CompactControlRibbon.js.map +1 -0
  29. package/lib/components/DualKeypad.d.ts +39 -0
  30. package/lib/components/DualKeypad.d.ts.map +1 -0
  31. package/lib/components/DualKeypad.js +137 -0
  32. package/lib/components/DualKeypad.js.map +1 -0
  33. package/lib/components/KillerCombinationsExplorer.d.ts +9 -0
  34. package/lib/components/KillerCombinationsExplorer.d.ts.map +1 -0
  35. package/lib/components/KillerCombinationsExplorer.js +69 -0
  36. package/lib/components/KillerCombinationsExplorer.js.map +1 -0
  37. package/lib/components/KillerCombinationsModal.d.ts +20 -0
  38. package/lib/components/KillerCombinationsModal.d.ts.map +1 -0
  39. package/lib/components/KillerCombinationsModal.js +135 -0
  40. package/lib/components/KillerCombinationsModal.js.map +1 -0
  41. package/lib/components/KillerCombinationsPanel.d.ts +20 -0
  42. package/lib/components/KillerCombinationsPanel.d.ts.map +1 -0
  43. package/lib/components/KillerCombinationsPanel.js +79 -0
  44. package/lib/components/KillerCombinationsPanel.js.map +1 -0
  45. package/lib/components/NumberKeypad.d.ts +30 -0
  46. package/lib/components/NumberKeypad.d.ts.map +1 -0
  47. package/lib/components/NumberKeypad.js +181 -0
  48. package/lib/components/NumberKeypad.js.map +1 -0
  49. package/lib/components/SudokuCell.d.ts +8 -0
  50. package/lib/components/SudokuCell.d.ts.map +1 -0
  51. package/lib/components/SudokuCell.js +240 -0
  52. package/lib/components/SudokuCell.js.map +1 -0
  53. package/lib/components/SudokuControls.d.ts +8 -0
  54. package/lib/components/SudokuControls.d.ts.map +1 -0
  55. package/lib/components/SudokuControls.js +140 -0
  56. package/lib/components/SudokuControls.js.map +1 -0
  57. package/lib/components/SudokuGrid.d.ts +8 -0
  58. package/lib/components/SudokuGrid.d.ts.map +1 -0
  59. package/lib/components/SudokuGrid.js +177 -0
  60. package/lib/components/SudokuGrid.js.map +1 -0
  61. package/lib/components/SudokuGridEntry.d.ts +8 -0
  62. package/lib/components/SudokuGridEntry.d.ts.map +1 -0
  63. package/lib/components/SudokuGridEntry.js +373 -0
  64. package/lib/components/SudokuGridEntry.js.map +1 -0
  65. package/lib/components/ValidationDisplay.d.ts +8 -0
  66. package/lib/components/ValidationDisplay.d.ts.map +1 -0
  67. package/lib/components/ValidationDisplay.js +95 -0
  68. package/lib/components/ValidationDisplay.js.map +1 -0
  69. package/lib/contexts/DiagnosticLoggerContext.d.ts +81 -0
  70. package/lib/contexts/DiagnosticLoggerContext.d.ts.map +1 -0
  71. package/lib/contexts/DiagnosticLoggerContext.js +98 -0
  72. package/lib/contexts/DiagnosticLoggerContext.js.map +1 -0
  73. package/lib/contexts/index.d.ts +3 -0
  74. package/lib/contexts/index.d.ts.map +1 -0
  75. package/lib/contexts/index.js +25 -0
  76. package/lib/contexts/index.js.map +1 -0
  77. package/lib/hooks/useCombinationElimination.d.ts +10 -0
  78. package/lib/hooks/useCombinationElimination.d.ts.map +1 -0
  79. package/lib/hooks/useCombinationElimination.js +123 -0
  80. package/lib/hooks/useCombinationElimination.js.map +1 -0
  81. package/lib/hooks/useKeyboardShortcut.d.ts +19 -0
  82. package/lib/hooks/useKeyboardShortcut.d.ts.map +1 -0
  83. package/lib/hooks/useKeyboardShortcut.js +55 -0
  84. package/lib/hooks/useKeyboardShortcut.js.map +1 -0
  85. package/lib/hooks/useKillerCombinations.d.ts +11 -0
  86. package/lib/hooks/useKillerCombinations.d.ts.map +1 -0
  87. package/lib/hooks/useKillerCombinations.js +65 -0
  88. package/lib/hooks/useKillerCombinations.js.map +1 -0
  89. package/lib/hooks/useLongPress.d.ts +31 -0
  90. package/lib/hooks/useLongPress.d.ts.map +1 -0
  91. package/lib/hooks/useLongPress.js +69 -0
  92. package/lib/hooks/useLongPress.js.map +1 -0
  93. package/lib/hooks/usePuzzleSession.d.ts +32 -0
  94. package/lib/hooks/usePuzzleSession.d.ts.map +1 -0
  95. package/lib/hooks/usePuzzleSession.js +398 -0
  96. package/lib/hooks/usePuzzleSession.js.map +1 -0
  97. package/lib/hooks/useResponsiveLayout.d.ts +44 -0
  98. package/lib/hooks/useResponsiveLayout.d.ts.map +1 -0
  99. package/lib/hooks/useResponsiveLayout.js +139 -0
  100. package/lib/hooks/useResponsiveLayout.js.map +1 -0
  101. package/lib/index.d.ts +28 -0
  102. package/lib/index.d.ts.map +1 -0
  103. package/lib/index.js +49 -0
  104. package/lib/index.js.map +1 -0
  105. package/lib/test/fixtures/killerSudokuPuzzles.d.ts +36 -0
  106. package/lib/test/fixtures/killerSudokuPuzzles.d.ts.map +1 -0
  107. package/lib/test/fixtures/killerSudokuPuzzles.js +57 -0
  108. package/lib/test/fixtures/killerSudokuPuzzles.js.map +1 -0
  109. package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts +22 -0
  110. package/lib/test/unit/components/SudokuGridEntry.testHelpers.d.ts.map +1 -0
  111. package/lib/test/unit/components/SudokuGridEntry.testHelpers.js +116 -0
  112. package/lib/test/unit/components/SudokuGridEntry.testHelpers.js.map +1 -0
  113. package/lib/tsdoc-metadata.json +11 -0
  114. package/lib/types/index.d.ts +234 -0
  115. package/lib/types/index.d.ts.map +1 -0
  116. package/lib/types/index.js +25 -0
  117. package/lib/types/index.js.map +1 -0
  118. package/lib/utils/CageLookupManager.d.ts +74 -0
  119. package/lib/utils/CageLookupManager.d.ts.map +1 -0
  120. package/lib/utils/CageLookupManager.js +283 -0
  121. package/lib/utils/CageLookupManager.js.map +1 -0
  122. package/lib/utils/CagePatternManager.d.ts +82 -0
  123. package/lib/utils/CagePatternManager.d.ts.map +1 -0
  124. package/lib/utils/CagePatternManager.js +139 -0
  125. package/lib/utils/CagePatternManager.js.map +1 -0
  126. package/lib/utils/puzzleDefinitionHelper.d.ts +25 -0
  127. package/lib/utils/puzzleDefinitionHelper.d.ts.map +1 -0
  128. package/lib/utils/puzzleDefinitionHelper.js +99 -0
  129. package/lib/utils/puzzleDefinitionHelper.js.map +1 -0
  130. package/lib-commonjs/components/CageOverlay.js +138 -0
  131. package/lib-commonjs/components/CageOverlay.js.map +1 -0
  132. package/lib-commonjs/components/CageSumIndicator.js +71 -0
  133. package/lib-commonjs/components/CageSumIndicator.js.map +1 -0
  134. package/lib-commonjs/components/CombinationCard.js +68 -0
  135. package/lib-commonjs/components/CombinationCard.js.map +1 -0
  136. package/lib-commonjs/components/CombinationGrid.js +56 -0
  137. package/lib-commonjs/components/CombinationGrid.js.map +1 -0
  138. package/lib-commonjs/components/CompactControlRibbon.js +146 -0
  139. package/lib-commonjs/components/CompactControlRibbon.js.map +1 -0
  140. package/lib-commonjs/components/DualKeypad.js +142 -0
  141. package/lib-commonjs/components/DualKeypad.js.map +1 -0
  142. package/lib-commonjs/components/KillerCombinationsExplorer.js +74 -0
  143. package/lib-commonjs/components/KillerCombinationsExplorer.js.map +1 -0
  144. package/lib-commonjs/components/KillerCombinationsModal.js +140 -0
  145. package/lib-commonjs/components/KillerCombinationsModal.js.map +1 -0
  146. package/lib-commonjs/components/KillerCombinationsPanel.js +84 -0
  147. package/lib-commonjs/components/KillerCombinationsPanel.js.map +1 -0
  148. package/lib-commonjs/components/NumberKeypad.js +186 -0
  149. package/lib-commonjs/components/NumberKeypad.js.map +1 -0
  150. package/lib-commonjs/components/SudokuCell.js +245 -0
  151. package/lib-commonjs/components/SudokuCell.js.map +1 -0
  152. package/lib-commonjs/components/SudokuControls.js +145 -0
  153. package/lib-commonjs/components/SudokuControls.js.map +1 -0
  154. package/lib-commonjs/components/SudokuGrid.js +182 -0
  155. package/lib-commonjs/components/SudokuGrid.js.map +1 -0
  156. package/lib-commonjs/components/SudokuGridEntry.js +378 -0
  157. package/lib-commonjs/components/SudokuGridEntry.js.map +1 -0
  158. package/lib-commonjs/components/ValidationDisplay.js +100 -0
  159. package/lib-commonjs/components/ValidationDisplay.js.map +1 -0
  160. package/lib-commonjs/contexts/DiagnosticLoggerContext.js +104 -0
  161. package/lib-commonjs/contexts/DiagnosticLoggerContext.js.map +1 -0
  162. package/lib-commonjs/contexts/index.js +32 -0
  163. package/lib-commonjs/contexts/index.js.map +1 -0
  164. package/lib-commonjs/hooks/useCombinationElimination.js +126 -0
  165. package/lib-commonjs/hooks/useCombinationElimination.js.map +1 -0
  166. package/lib-commonjs/hooks/useKeyboardShortcut.js +58 -0
  167. package/lib-commonjs/hooks/useKeyboardShortcut.js.map +1 -0
  168. package/lib-commonjs/hooks/useKillerCombinations.js +68 -0
  169. package/lib-commonjs/hooks/useKillerCombinations.js.map +1 -0
  170. package/lib-commonjs/hooks/useLongPress.js +72 -0
  171. package/lib-commonjs/hooks/useLongPress.js.map +1 -0
  172. package/lib-commonjs/hooks/usePuzzleSession.js +401 -0
  173. package/lib-commonjs/hooks/usePuzzleSession.js.map +1 -0
  174. package/lib-commonjs/hooks/useResponsiveLayout.js +142 -0
  175. package/lib-commonjs/hooks/useResponsiveLayout.js.map +1 -0
  176. package/lib-commonjs/index.js +73 -0
  177. package/lib-commonjs/index.js.map +1 -0
  178. package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js +60 -0
  179. package/lib-commonjs/test/fixtures/killerSudokuPuzzles.js.map +1 -0
  180. package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js +125 -0
  181. package/lib-commonjs/test/unit/components/SudokuGridEntry.testHelpers.js.map +1 -0
  182. package/lib-commonjs/types/index.js +26 -0
  183. package/lib-commonjs/types/index.js.map +1 -0
  184. package/lib-commonjs/utils/CageLookupManager.js +287 -0
  185. package/lib-commonjs/utils/CageLookupManager.js.map +1 -0
  186. package/lib-commonjs/utils/CagePatternManager.js +143 -0
  187. package/lib-commonjs/utils/CagePatternManager.js.map +1 -0
  188. package/lib-commonjs/utils/puzzleDefinitionHelper.js +103 -0
  189. package/lib-commonjs/utils/puzzleDefinitionHelper.js.map +1 -0
  190. package/package.json +77 -0
@@ -0,0 +1,69 @@
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 { useRef, useCallback } from 'react';
25
+ /**
26
+ * Hook to detect long press gestures on touch and mouse devices
27
+ * @param options - Configuration options for long press detection
28
+ * @returns Event handlers to spread on target element
29
+ * @public
30
+ */
31
+ export function useLongPress(options) {
32
+ const { onLongPress, delay = 500, shouldPreventDefault = true } = options;
33
+ const timeoutRef = useRef(undefined);
34
+ const isLongPressRef = useRef(false);
35
+ const eventRef = useRef(undefined);
36
+ const start = useCallback((event) => {
37
+ eventRef.current = event;
38
+ isLongPressRef.current = false;
39
+ if (shouldPreventDefault) {
40
+ event.preventDefault();
41
+ }
42
+ timeoutRef.current = setTimeout(() => {
43
+ isLongPressRef.current = true;
44
+ if (eventRef.current) {
45
+ onLongPress(eventRef.current);
46
+ }
47
+ }, delay);
48
+ }, [onLongPress, delay, shouldPreventDefault]);
49
+ const cancel = useCallback(() => {
50
+ if (timeoutRef.current) {
51
+ clearTimeout(timeoutRef.current);
52
+ timeoutRef.current = undefined;
53
+ }
54
+ isLongPressRef.current = false;
55
+ }, []);
56
+ const end = useCallback(() => {
57
+ const wasLongPress = isLongPressRef.current;
58
+ cancel();
59
+ return wasLongPress;
60
+ }, [cancel]);
61
+ return {
62
+ onMouseDown: start,
63
+ onMouseUp: end,
64
+ onMouseLeave: cancel,
65
+ onTouchStart: start,
66
+ onTouchEnd: end
67
+ };
68
+ }
69
+ //# sourceMappingURL=useLongPress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLongPress.js","sourceRoot":"","sources":["../../src/hooks/useLongPress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AA6B5C;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAA6B;IACxD,MAAM,EAAE,WAAW,EAAE,KAAK,GAAG,GAAG,EAAE,oBAAoB,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAE1E,MAAM,UAAU,GAAG,MAAM,CAA6B,SAAS,CAAC,CAAC;IACjE,MAAM,cAAc,GAAG,MAAM,CAAU,KAAK,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,MAAM,CAAkD,SAAS,CAAC,CAAC;IAEpF,MAAM,KAAK,GAAG,WAAW,CACvB,CAAC,KAA0C,EAAE,EAAE;QAC7C,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAC;QACzB,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC;QAE/B,IAAI,oBAAoB,EAAE,CAAC;YACzB,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,CAAC;QAED,UAAU,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACnC,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;YAC9B,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,EACD,CAAC,WAAW,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAC3C,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACjC,UAAU,CAAC,OAAO,GAAG,SAAS,CAAC;QACjC,CAAC;QACD,cAAc,CAAC,OAAO,GAAG,KAAK,CAAC;IACjC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE;QAC3B,MAAM,YAAY,GAAG,cAAc,CAAC,OAAO,CAAC;QAC5C,MAAM,EAAE,CAAC;QACT,OAAO,YAAY,CAAC;IACtB,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO;QACL,WAAW,EAAE,KAAK;QAClB,SAAS,EAAE,GAAG;QACd,YAAY,EAAE,MAAM;QACpB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,GAAG;KAChB,CAAC;AACJ,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 { useRef, useCallback } from 'react';\n\n/**\n * Options for the useLongPress hook\n * @public\n */\nexport interface IUseLongPressOptions {\n /** Callback to execute on long press */\n readonly onLongPress: (event: React.TouchEvent | React.MouseEvent) => void;\n\n /** Delay before long press is triggered (ms) */\n readonly delay?: number;\n\n /** Whether to prevent default behavior during long press */\n readonly shouldPreventDefault?: boolean;\n}\n\n/**\n * Result of the useLongPress hook\n * @public\n */\nexport interface ILongPressHandlers {\n readonly onMouseDown: (event: React.MouseEvent) => void;\n readonly onMouseUp: (event: React.MouseEvent) => boolean;\n readonly onMouseLeave: (event: React.MouseEvent) => void;\n readonly onTouchStart: (event: React.TouchEvent) => void;\n readonly onTouchEnd: (event: React.TouchEvent) => boolean;\n}\n\n/**\n * Hook to detect long press gestures on touch and mouse devices\n * @param options - Configuration options for long press detection\n * @returns Event handlers to spread on target element\n * @public\n */\nexport function useLongPress(options: IUseLongPressOptions): ILongPressHandlers {\n const { onLongPress, delay = 500, shouldPreventDefault = true } = options;\n\n const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);\n const isLongPressRef = useRef<boolean>(false);\n const eventRef = useRef<React.TouchEvent | React.MouseEvent | undefined>(undefined);\n\n const start = useCallback(\n (event: React.TouchEvent | React.MouseEvent) => {\n eventRef.current = event;\n isLongPressRef.current = false;\n\n if (shouldPreventDefault) {\n event.preventDefault();\n }\n\n timeoutRef.current = setTimeout(() => {\n isLongPressRef.current = true;\n if (eventRef.current) {\n onLongPress(eventRef.current);\n }\n }, delay);\n },\n [onLongPress, delay, shouldPreventDefault]\n );\n\n const cancel = useCallback(() => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }\n isLongPressRef.current = false;\n }, []);\n\n const end = useCallback(() => {\n const wasLongPress = isLongPressRef.current;\n cancel();\n return wasLongPress;\n }, [cancel]);\n\n return {\n onMouseDown: start,\n onMouseUp: end,\n onMouseLeave: cancel,\n onTouchStart: start,\n onTouchEnd: end\n };\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { PuzzleSession, CellId, NavigationDirection, NavigationWrap, IPuzzleDefinition } from '@fgv/ts-sudoku-lib';
2
+ import { IValidationError, ICellDisplayInfo, InputMode, ICageDisplayInfo } from '../types';
3
+ /**
4
+ * Hook for managing puzzle session state and operations
5
+ * @public
6
+ */
7
+ export declare function usePuzzleSession(initialPuzzleDescription?: IPuzzleDefinition): {
8
+ session: PuzzleSession | null;
9
+ selectedCell: CellId | null;
10
+ selectedCells: CellId[];
11
+ inputMode: InputMode;
12
+ setSelectedCell: (cell: CellId | null) => void;
13
+ setSelectedCells: (cells: CellId[]) => void;
14
+ setInputMode: (mode: InputMode) => void;
15
+ cellDisplayInfo: ICellDisplayInfo[];
16
+ cageDisplayInfo: ICageDisplayInfo[];
17
+ validationErrors: IValidationError[];
18
+ isValid: boolean;
19
+ isSolved: boolean;
20
+ canUndo: boolean;
21
+ canRedo: boolean;
22
+ error: string | null;
23
+ updateCellValue: (cellId: CellId, value: number | undefined) => void;
24
+ toggleCellNote: (cellId: CellId, note: number) => void;
25
+ clearCellNotes: (cellId: CellId) => void;
26
+ navigateToCell: (direction: NavigationDirection, wrap?: NavigationWrap) => void;
27
+ undo: () => void;
28
+ redo: () => void;
29
+ reset: () => void;
30
+ exportPuzzle: () => IPuzzleDefinition | null;
31
+ };
32
+ //# sourceMappingURL=usePuzzleSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePuzzleSession.d.ts","sourceRoot":"","sources":["../../src/hooks/usePuzzleSession.ts"],"names":[],"mappings":"AAyBA,OAAO,EACL,aAAa,EACb,MAAM,EACN,mBAAmB,EACnB,cAAc,EAEd,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG3F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,wBAAwB,CAAC,EAAE,iBAAiB,GAAG;IAC9E,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC/C,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,gBAAgB,EAAE,CAAC;IACpC,eAAe,EAAE,gBAAgB,EAAE,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrE,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,cAAc,EAAE,CAAC,SAAS,EAAE,mBAAmB,EAAE,IAAI,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IAChF,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,YAAY,EAAE,MAAM,iBAAiB,GAAG,IAAI,CAAC;CAC9C,CAsaA"}
@@ -0,0 +1,398 @@
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 { useCallback, useEffect, useMemo, useState } from 'react';
25
+ import { PuzzleSession, Puzzles } from '@fgv/ts-sudoku-lib';
26
+ import { createEmptyPuzzleDefinition, createPuzzleDefinition } from '../utils/puzzleDefinitionHelper';
27
+ /**
28
+ * Hook for managing puzzle session state and operations
29
+ * @public
30
+ */
31
+ export function usePuzzleSession(initialPuzzleDescription) {
32
+ const [session, setSession] = useState(null);
33
+ const [puzzleDescription, setPuzzleDescription] = useState(null);
34
+ const [selectedCell, setSelectedCell] = useState(null);
35
+ const [selectedCells, setSelectedCells] = useState([]);
36
+ const [inputMode, setInputMode] = useState('notes'); // Notes-first paradigm
37
+ const [error, setError] = useState(null);
38
+ const [updateCounter, setUpdateCounter] = useState(0); // Force re-renders
39
+ // Initialize or update puzzle session
40
+ useEffect(() => {
41
+ // Use the provided description or create a default empty 9x9 puzzle
42
+ const puzzleDescriptor = initialPuzzleDescription || createEmptyPuzzleDefinition();
43
+ const sessionResult = Puzzles.Any.create(puzzleDescriptor).onSuccess((puzzle) => PuzzleSession.create(puzzle));
44
+ if (sessionResult.isSuccess()) {
45
+ setSession(sessionResult.value);
46
+ setPuzzleDescription(puzzleDescriptor);
47
+ setError(null);
48
+ }
49
+ else {
50
+ setError(`Failed to create puzzle session: ${sessionResult.message}`);
51
+ setSession(null);
52
+ setPuzzleDescription(null);
53
+ }
54
+ }, [initialPuzzleDescription]);
55
+ // Get validation errors
56
+ const validationErrors = useMemo(() => {
57
+ if (!session)
58
+ return [];
59
+ const errors = [];
60
+ const invalidCells = session.getInvalidCells();
61
+ invalidCells.forEach((cell) => {
62
+ const contentsResult = session.state.getCellContents(cell.id);
63
+ if (contentsResult.isSuccess() && contentsResult.value.value) {
64
+ const contents = contentsResult.value;
65
+ // Check for duplicates in row, column, and section
66
+ const rowCells = session.rows[cell.row];
67
+ const colCells = session.cols[cell.col];
68
+ // Calculate section index based on puzzle dimensions
69
+ const cageHeight = session.puzzle.dimensions.cageHeightInCells;
70
+ const cageWidth = session.puzzle.dimensions.cageWidthInCells;
71
+ const sectionRow = Math.floor(cell.row / cageHeight);
72
+ const sectionCol = Math.floor(cell.col / cageWidth);
73
+ const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);
74
+ const sectionIndex = sectionRow * sectionsPerRow + sectionCol;
75
+ const sectionCells = session.sections[sectionIndex];
76
+ const checkDuplicates = (cageCellIds, type) => {
77
+ const duplicates = cageCellIds.filter((cellId) => {
78
+ if (cellId === cell.id)
79
+ return false;
80
+ const cellContentsResult = session.state.getCellContents(cellId);
81
+ return cellContentsResult.isSuccess() && cellContentsResult.value.value === contents.value;
82
+ });
83
+ if (duplicates.length > 0) {
84
+ errors.push({
85
+ cellId: cell.id,
86
+ type,
87
+ conflictingCells: duplicates,
88
+ message: `Duplicate ${contents.value} in ${type.replace('duplicate-', '').replace('-', ' ')}`
89
+ });
90
+ }
91
+ };
92
+ checkDuplicates(rowCells.cellIds, 'duplicate-row');
93
+ checkDuplicates(colCells.cellIds, 'duplicate-column');
94
+ checkDuplicates(sectionCells.cellIds, 'duplicate-section');
95
+ // For Sudoku X puzzles, also check diagonal constraints
96
+ if (puzzleDescription?.type === 'sudoku-x') {
97
+ // Find diagonal cages in the session
98
+ const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');
99
+ // Check main diagonal (top-left to bottom-right)
100
+ if (cell.row === cell.col && diagonalCages.length > 0) {
101
+ const mainDiagonal = diagonalCages[0];
102
+ checkDuplicates(mainDiagonal.cellIds, 'duplicate-diagonal');
103
+ }
104
+ // Check anti-diagonal (top-right to bottom-left)
105
+ if (cell.row + cell.col === 8 && diagonalCages.length > 1) {
106
+ const antiDiagonal = diagonalCages[1];
107
+ checkDuplicates(antiDiagonal.cellIds, 'duplicate-diagonal');
108
+ }
109
+ }
110
+ }
111
+ });
112
+ return errors;
113
+ }, [session, puzzleDescription, updateCounter]);
114
+ // Get cell display information
115
+ const cellDisplayInfo = useMemo(() => {
116
+ if (!session)
117
+ return [];
118
+ return session.cells.map((cell) => {
119
+ const contentsResult = session.state.getCellContents(cell.id);
120
+ /* c8 ignore next 1 - defense in depth */
121
+ const contents = contentsResult.isSuccess() ? contentsResult.value : { value: undefined, notes: [] };
122
+ const hasValidationError = validationErrors.some((error) => error.cellId === cell.id);
123
+ return {
124
+ id: cell.id,
125
+ row: cell.row,
126
+ column: cell.col,
127
+ contents,
128
+ isImmutable: cell.immutable,
129
+ hasValidationError
130
+ };
131
+ });
132
+ }, [session, validationErrors, updateCounter]);
133
+ // Get cage display information for Killer Sudoku
134
+ const cageDisplayInfo = useMemo(() => {
135
+ if (!session || puzzleDescription?.type !== 'killer-sudoku')
136
+ return [];
137
+ return session.cages
138
+ .filter((cage) => cage.cageType === 'killer') // Only show killer cages, not diagonal/section cages
139
+ .map((cage) => {
140
+ // Calculate current sum
141
+ let currentSum = 0;
142
+ let isComplete = true;
143
+ let hasValues = false;
144
+ for (const cellId of cage.cellIds) {
145
+ const contentsResult = session.state.getCellContents(cellId);
146
+ if (contentsResult.isSuccess() && contentsResult.value.value) {
147
+ currentSum += contentsResult.value.value;
148
+ hasValues = true;
149
+ }
150
+ else {
151
+ isComplete = false;
152
+ }
153
+ }
154
+ // Check if the current sum is valid (not exceeding target when incomplete, or matching target when complete)
155
+ const isValid = isComplete ? currentSum === cage.total : currentSum <= cage.total;
156
+ // Check if cage should be highlighted (contains selected cell)
157
+ const isHighlighted = selectedCell ? cage.cellIds.includes(selectedCell) : false;
158
+ return {
159
+ cage,
160
+ isHighlighted,
161
+ currentSum: hasValues ? currentSum : undefined,
162
+ isComplete,
163
+ isValid
164
+ };
165
+ });
166
+ }, [session, puzzleDescription, selectedCell, updateCounter]);
167
+ // Helper function to remove notes that conflict with a placed value
168
+ const removeConflictingNotes = useCallback((placedCellId, placedValue) => {
169
+ /* c8 ignore next 1 - defense in depth should not happen */
170
+ if (!session)
171
+ return;
172
+ const placedCell = session.cells.find((cell) => cell.id === placedCellId);
173
+ /* c8 ignore next 1 - defense in depth should not happen */
174
+ if (!placedCell)
175
+ return;
176
+ // Get cells in the same row, column, and section
177
+ const rowCells = session.rows[placedCell.row].cellIds;
178
+ const colCells = session.cols[placedCell.col].cellIds;
179
+ // Calculate section index based on puzzle dimensions
180
+ const cageHeight = session.puzzle.dimensions.cageHeightInCells;
181
+ const cageWidth = session.puzzle.dimensions.cageWidthInCells;
182
+ const sectionRow = Math.floor(placedCell.row / cageHeight);
183
+ const sectionCol = Math.floor(placedCell.col / cageWidth);
184
+ const sectionsPerRow = Math.ceil(session.numColumns / cageWidth);
185
+ const sectionIndex = sectionRow * sectionsPerRow + sectionCol;
186
+ const sectionCells = session.sections[sectionIndex].cellIds;
187
+ // Start with row, column, and section cells
188
+ const affectedCells = new Set([...rowCells, ...colCells, ...sectionCells]);
189
+ // For Sudoku X puzzles, also include diagonal cells
190
+ if (puzzleDescription?.type === 'sudoku-x') {
191
+ // Find diagonal cages in the session
192
+ const diagonalCages = session.cages.filter((cage) => cage.cageType === 'x');
193
+ // Check main diagonal (top-left to bottom-right)
194
+ if (placedCell.row === placedCell.col && diagonalCages.length > 0) {
195
+ const mainDiagonal = diagonalCages[0];
196
+ mainDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));
197
+ }
198
+ // Check anti-diagonal (top-right to bottom-left)
199
+ if (placedCell.row + placedCell.col === 8 && diagonalCages.length > 1) {
200
+ const antiDiagonal = diagonalCages[1];
201
+ antiDiagonal.cellIds.forEach((cellId) => affectedCells.add(cellId));
202
+ }
203
+ }
204
+ // Remove the placed cell itself from the list
205
+ affectedCells.delete(placedCellId);
206
+ // Update notes for each affected cell
207
+ for (const cellId of Array.from(affectedCells)) {
208
+ const contentsResult = session.state.getCellContents(cellId);
209
+ if (contentsResult.isSuccess()) {
210
+ const contents = contentsResult.value;
211
+ // Only update if the cell has notes and contains the placed value
212
+ if (contents.notes.length > 0 && contents.notes.includes(placedValue) && !contents.value) {
213
+ const newNotes = contents.notes.filter((note) => note !== placedValue);
214
+ session.updateCellNotes(cellId, newNotes);
215
+ }
216
+ }
217
+ }
218
+ }, [session, puzzleDescription]);
219
+ // Update cell value with smart note removal
220
+ const updateCellValue = useCallback((cellId, value) => {
221
+ /* c8 ignore next 1 - defense in depth should not happen */
222
+ if (!session)
223
+ return;
224
+ const result = session.updateCellValue(cellId, value);
225
+ if (result.isSuccess()) {
226
+ // If a value was placed (not removed), remove conflicting notes
227
+ if (value !== undefined) {
228
+ removeConflictingNotes(cellId, value);
229
+ }
230
+ // Force re-render by incrementing update counter
231
+ setUpdateCounter((prev) => prev + 1);
232
+ }
233
+ else {
234
+ setError(`Failed to update cell: ${result.message}`);
235
+ }
236
+ }, [session, removeConflictingNotes]);
237
+ // Toggle a note in a cell
238
+ const toggleCellNote = useCallback((cellId, note) => {
239
+ /* c8 ignore next 1 - defense in depth should not happen */
240
+ if (!session)
241
+ return;
242
+ // Get current cell contents
243
+ const contentsResult = session.state.getCellContents(cellId);
244
+ if (contentsResult.isFailure()) {
245
+ setError(`Failed to get cell contents: ${contentsResult.message}`);
246
+ return;
247
+ }
248
+ const currentNotes = contentsResult.value.notes;
249
+ const newNotes = currentNotes.includes(note)
250
+ ? currentNotes.filter((n) => n !== note) // Remove note if it exists
251
+ : [...currentNotes, note].sort(); // Add note if it doesn't exist
252
+ const result = session.updateCellNotes(cellId, newNotes);
253
+ if (result.isSuccess()) {
254
+ setUpdateCounter((prev) => prev + 1);
255
+ /* c8 ignore next 3 - defense in depth */
256
+ }
257
+ else {
258
+ setError(`Failed to toggle note: ${result.message}`);
259
+ }
260
+ }, [session]);
261
+ // Clear all notes from a cell
262
+ const clearCellNotes = useCallback((cellId) => {
263
+ /* c8 ignore next 1 - defense in depth should not happen */
264
+ if (!session)
265
+ return;
266
+ const result = session.updateCellNotes(cellId, []);
267
+ if (result.isSuccess()) {
268
+ setUpdateCounter((prev) => prev + 1);
269
+ }
270
+ else {
271
+ setError(`Failed to clear notes: ${result.message}`);
272
+ }
273
+ }, [session]);
274
+ // Navigate to adjacent cell
275
+ const navigateToCell = useCallback((direction, wrap = 'wrap-around') => {
276
+ if (!session || !selectedCell)
277
+ return;
278
+ const currentCell = session.cells.find((cell) => cell.id === selectedCell);
279
+ /* c8 ignore next 1 - defense in depth should not happen */
280
+ if (!currentCell)
281
+ return;
282
+ let newRow = currentCell.row;
283
+ let newCol = currentCell.col;
284
+ switch (direction) {
285
+ case 'up':
286
+ newRow = newRow > 0 ? newRow - 1 : wrap === 'wrap-around' ? session.numRows - 1 : newRow;
287
+ break;
288
+ case 'down':
289
+ newRow = newRow < session.numRows - 1 ? newRow + 1 : wrap === 'wrap-around' ? 0 : newRow;
290
+ break;
291
+ case 'left':
292
+ newCol = newCol > 0 ? newCol - 1 : wrap === 'wrap-around' ? session.numColumns - 1 : newCol;
293
+ break;
294
+ case 'right':
295
+ newCol = newCol < session.numColumns - 1 ? newCol + 1 : wrap === 'wrap-around' ? 0 : newCol;
296
+ break;
297
+ }
298
+ const targetCell = session.cells.find((cell) => cell.row === newRow && cell.col === newCol);
299
+ if (targetCell) {
300
+ setSelectedCell(targetCell.id);
301
+ }
302
+ }, [session, selectedCell]);
303
+ // Undo last move
304
+ const undo = useCallback(() => {
305
+ /* c8 ignore next 1 - defense in depth should not happen */
306
+ if (!session)
307
+ return;
308
+ const result = session.undo();
309
+ if (result.isSuccess()) {
310
+ setUpdateCounter((prev) => prev + 1);
311
+ }
312
+ else {
313
+ setError(`Failed to undo: ${result.message}`);
314
+ }
315
+ }, [session]);
316
+ // Redo last undone move
317
+ const redo = useCallback(() => {
318
+ /* c8 ignore next 1 - defense in depth should not happen */
319
+ if (!session)
320
+ return;
321
+ const result = session.redo();
322
+ if (result.isSuccess()) {
323
+ setUpdateCounter((prev) => prev + 1);
324
+ }
325
+ else {
326
+ setError(`Failed to redo: ${result.message}`);
327
+ }
328
+ }, [session]);
329
+ // Reset puzzle to initial state
330
+ const reset = useCallback(() => {
331
+ /* c8 ignore next 1 - defense in depth should not happen */
332
+ if (!session)
333
+ return;
334
+ const result = PuzzleSession.create(session.puzzle);
335
+ if (result.isSuccess()) {
336
+ setSession(result.value);
337
+ setSelectedCell(null);
338
+ setUpdateCounter(0);
339
+ /* c8 ignore next 3 - defense in depth */
340
+ }
341
+ else {
342
+ setError(`Failed to reset: ${result.message}`);
343
+ }
344
+ }, [session]);
345
+ // Export puzzle state
346
+ const exportPuzzle = useCallback(() => {
347
+ /* c8 ignore next 1 - defense in depth should not happen */
348
+ if (!session)
349
+ return null;
350
+ const cellsArray = Array(session.numRows * session.numColumns).fill('.');
351
+ session.cells.forEach((cell) => {
352
+ const contentsResult = session.state.getCellContents(cell.id);
353
+ const index = cell.row * session.numColumns + cell.col;
354
+ if (contentsResult.isSuccess() && contentsResult.value.value) {
355
+ cellsArray[index] = contentsResult.value.value.toString();
356
+ }
357
+ });
358
+ /* c8 ignore next 4 - defense in depth */
359
+ const id = session.id && session.id !== '' ? session.id : 'exported-puzzle';
360
+ const description = session.description && session.description !== '' ? session.description : 'Exported Puzzle';
361
+ const type = puzzleDescription?.type ?? 'sudoku';
362
+ return createPuzzleDefinition({
363
+ id,
364
+ description,
365
+ type,
366
+ level: 1,
367
+ totalRows: session.numRows,
368
+ totalColumns: session.numColumns,
369
+ cells: cellsArray.join('')
370
+ });
371
+ }, [session, puzzleDescription, updateCounter]);
372
+ return {
373
+ session,
374
+ selectedCell,
375
+ selectedCells,
376
+ inputMode,
377
+ setSelectedCell,
378
+ setSelectedCells,
379
+ setInputMode,
380
+ cellDisplayInfo,
381
+ cageDisplayInfo,
382
+ validationErrors,
383
+ isValid: session?.checkIsValid() ?? false,
384
+ isSolved: session?.checkIsSolved() ?? false,
385
+ canUndo: session?.canUndo ?? false,
386
+ canRedo: session?.canRedo ?? false,
387
+ error,
388
+ updateCellValue,
389
+ toggleCellNote,
390
+ clearCellNotes,
391
+ navigateToCell,
392
+ undo,
393
+ redo,
394
+ reset,
395
+ exportPuzzle
396
+ };
397
+ }
398
+ //# sourceMappingURL=usePuzzleSession.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Device type detection result
3
+ * @public
4
+ */
5
+ export type DeviceType = 'mobile' | 'tablet' | 'desktop';
6
+ /**
7
+ * Screen orientation
8
+ * @public
9
+ */
10
+ export type ScreenOrientation = 'portrait' | 'landscape';
11
+ /**
12
+ * Layout mode for dual keypad
13
+ * @public
14
+ */
15
+ export type KeypadLayoutMode = 'side-by-side' | 'stacked' | 'overlay' | 'hidden';
16
+ /**
17
+ * Responsive layout information
18
+ * @public
19
+ */
20
+ export interface IResponsiveLayoutInfo {
21
+ /** Device type based on screen size and touch capability */
22
+ readonly deviceType: DeviceType;
23
+ /** Current screen orientation */
24
+ readonly orientation: ScreenOrientation;
25
+ /** Recommended keypad layout mode */
26
+ readonly keypadLayoutMode: KeypadLayoutMode;
27
+ /** Screen width in pixels */
28
+ readonly screenWidth: number;
29
+ /** Screen height in pixels */
30
+ readonly screenHeight: number;
31
+ /** Whether touch is supported */
32
+ readonly isTouchDevice: boolean;
33
+ /** Whether the screen is considered small */
34
+ readonly isSmallScreen: boolean;
35
+ /** Whether the screen has enough space for dual keypads */
36
+ readonly hasSpaceForDualKeypads: boolean;
37
+ }
38
+ /**
39
+ * Hook to detect responsive layout information for optimal keypad display
40
+ * @param forceLayoutMode - Optional layout mode override for testing
41
+ * @public
42
+ */
43
+ export declare function useResponsiveLayout(forceLayoutMode?: KeypadLayoutMode): IResponsiveLayoutInfo;
44
+ //# sourceMappingURL=useResponsiveLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResponsiveLayout.d.ts","sourceRoot":"","sources":["../../src/hooks/useResponsiveLayout.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,WAAW,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjF;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IAExC,qCAAqC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAE5C,6BAA6B;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,8BAA8B;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,iCAAiC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,6CAA6C;IAC7C,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAEhC,2DAA2D;IAC3D,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C;AAsED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,eAAe,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CA2E7F"}