@sudobility/sudojo_lib 0.0.103 → 0.0.104

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.
@@ -1 +1 @@
1
- {"version":3,"file":"useBoardEntry.d.ts","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,YAAY,EAAE,OAAO,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gDAAgD;IAChD,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,6BAA6B;IAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAsDD,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,OAAO,EACP,KAAK,GACN,EAAE,oBAAoB,GAAG,mBAAmB,CAoK5C"}
1
+ {"version":3,"file":"useBoardEntry.d.ts","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,YAAY,EAAE,OAAO,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gDAAgD;IAChD,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,6BAA6B;IAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAoED,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,OAAO,EACP,KAAK,GACN,EAAE,oBAAoB,GAAG,mBAAmB,CAiL5C"}
@@ -50,6 +50,19 @@ function puzzleStringToCells(puzzle) {
50
50
  };
51
51
  });
52
52
  }
53
+ /**
54
+ * Classify a validation error message into a translation key
55
+ */
56
+ function classifyValidationError(errorMsg) {
57
+ const lower = errorMsg.toLowerCase();
58
+ if (lower.includes('multiple') || lower.includes('not unique')) {
59
+ return 'enter.errors.multipleSolutions';
60
+ }
61
+ if (lower.includes('cannot solve') || lower.includes('no solution')) {
62
+ return 'enter.errors.noSolution';
63
+ }
64
+ return 'enter.errors.validationFailed';
65
+ }
53
66
  export function useBoardEntry({ networkClient, baseUrl, token, }) {
54
67
  const [cells, setCells] = useState(createEmptyBoard);
55
68
  const [selectedIndex, setSelectedIndex] = useState(null);
@@ -60,12 +73,13 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
60
73
  const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);
61
74
  const clueCount = useMemo(() => countClues(cells), [cells]);
62
75
  // Use the solver validate hook
63
- const { data: validateData, isLoading: isValidating } = useSolverValidate(networkClient, baseUrl, token, { original: puzzleString }, {
76
+ const { data: validateData, error: validateError, isLoading: isValidating, } = useSolverValidate(networkClient, baseUrl, token, { original: puzzleString }, {
64
77
  enabled: shouldValidate && clueCount >= 17,
65
78
  retry: false,
66
79
  });
67
80
  // Track whether we've already processed this validation result
68
81
  const processedDataRef = useRef(null);
82
+ const processedErrorRef = useRef(null);
69
83
  // Handle validation result
70
84
  useEffect(() => {
71
85
  if (!shouldValidate || isValidating)
@@ -87,17 +101,7 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
87
101
  }
88
102
  else if (validateData.error) {
89
103
  // Validation failed with specific error
90
- const errorMsg = validateData.error.toLowerCase();
91
- if (errorMsg.includes('multiple') || errorMsg.includes('not unique')) {
92
- setValidationError('enter.errors.multipleSolutions');
93
- }
94
- else if (errorMsg.includes('cannot solve') ||
95
- errorMsg.includes('no solution')) {
96
- setValidationError('enter.errors.noSolution');
97
- }
98
- else {
99
- setValidationError('enter.errors.validationFailed');
100
- }
104
+ setValidationError(classifyValidationError(validateData.error));
101
105
  setValidatedPuzzle(null);
102
106
  }
103
107
  else {
@@ -108,6 +112,22 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
108
112
  }
109
113
  setShouldValidate(false);
110
114
  }, [validateData, shouldValidate, isValidating, puzzleString]);
115
+ // Handle network/query errors (e.g. 400 responses throw NetworkError)
116
+ useEffect(() => {
117
+ if (!shouldValidate || isValidating)
118
+ return;
119
+ if (!validateError)
120
+ return;
121
+ if (processedErrorRef.current === validateError)
122
+ return;
123
+ processedErrorRef.current = validateError;
124
+ // Extract error message from NetworkError response body or message
125
+ const networkErr = validateError;
126
+ const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';
127
+ setValidationError(classifyValidationError(errorMsg));
128
+ setValidatedPuzzle(null);
129
+ setShouldValidate(false);
130
+ }, [validateError, shouldValidate, isValidating]);
111
131
  // Select a cell
112
132
  const selectCell = useCallback((index) => {
113
133
  if (index >= 0 && index < 81) {
@@ -1 +1 @@
1
- {"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n}\n\nexport interface UseBoardEntryOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string */\n setCellsFromPuzzle: (puzzle: string) => void;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyBoard(): SudokuCell[] {\n return Array.from({ length: 81 }, (_, index) => ({\n index,\n solution: null,\n given: null,\n input: null,\n pencilmarks: null,\n }));\n}\n\n/**\n * Convert cells to 81-character puzzle string\n * '0' for empty, '1'-'9' for given values\n */\nfunction cellsToPuzzleString(cells: SudokuCell[]): string {\n return cells.map(cell => cell.given?.toString() ?? '0').join('');\n}\n\n/**\n * Count the number of non-empty cells (clues)\n */\nfunction countClues(cells: SudokuCell[]): number {\n return cells.filter(cell => cell.given !== null).length;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyBoard();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\nexport function useBoardEntry({\n networkClient,\n baseUrl,\n token,\n}: UseBoardEntryOptions): UseBoardEntryReturn {\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyBoard);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const { data: validateData, isLoading: isValidating } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= 17,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const solution = validateData.data?.board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n const errorMsg = validateData.error.toLowerCase();\n if (errorMsg.includes('multiple') || errorMsg.includes('not unique')) {\n setValidationError('enter.errors.multipleSolutions');\n } else if (\n errorMsg.includes('cannot solve') ||\n errorMsg.includes('no solution')\n ) {\n setValidationError('enter.errors.noSolution');\n } else {\n setValidationError('enter.errors.validationFailed');\n }\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < 17) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyBoard());\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, []);\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n };\n}\n"]}
1
+ {"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n}\n\nexport interface UseBoardEntryOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string */\n setCellsFromPuzzle: (puzzle: string) => void;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyBoard(): SudokuCell[] {\n return Array.from({ length: 81 }, (_, index) => ({\n index,\n solution: null,\n given: null,\n input: null,\n pencilmarks: null,\n }));\n}\n\n/**\n * Convert cells to 81-character puzzle string\n * '0' for empty, '1'-'9' for given values\n */\nfunction cellsToPuzzleString(cells: SudokuCell[]): string {\n return cells.map(cell => cell.given?.toString() ?? '0').join('');\n}\n\n/**\n * Count the number of non-empty cells (clues)\n */\nfunction countClues(cells: SudokuCell[]): number {\n return cells.filter(cell => cell.given !== null).length;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyBoard();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\n/**\n * Classify a validation error message into a translation key\n */\nfunction classifyValidationError(errorMsg: string): string {\n const lower = errorMsg.toLowerCase();\n if (lower.includes('multiple') || lower.includes('not unique')) {\n return 'enter.errors.multipleSolutions';\n }\n if (lower.includes('cannot solve') || lower.includes('no solution')) {\n return 'enter.errors.noSolution';\n }\n return 'enter.errors.validationFailed';\n}\n\nexport function useBoardEntry({\n networkClient,\n baseUrl,\n token,\n}: UseBoardEntryOptions): UseBoardEntryReturn {\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyBoard);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const {\n data: validateData,\n error: validateError,\n isLoading: isValidating,\n } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= 17,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n const processedErrorRef = useRef<typeof validateError | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const solution = validateData.data?.board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n setValidationError(classifyValidationError(validateData.error));\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Handle network/query errors (e.g. 400 responses throw NetworkError)\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateError) return;\n if (processedErrorRef.current === validateError) return;\n processedErrorRef.current = validateError;\n\n // Extract error message from NetworkError response body or message\n const networkErr = validateError as {\n response?: { error?: string };\n message?: string;\n };\n const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';\n setValidationError(classifyValidationError(errorMsg));\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, [validateError, shouldValidate, isValidating]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < 17) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyBoard());\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, []);\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/sudojo_lib",
3
- "version": "0.0.103",
3
+ "version": "0.0.104",
4
4
  "description": "Business logic library for Sudojo app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",