@scrabble-solver/scrabble-solver 2.8.9 → 2.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +10 -10
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/413.js +181 -39
- package/.next/server/chunks/429.js +2 -13
- package/.next/server/chunks/515.js +408 -227
- package/.next/server/chunks/911.js +25 -3
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +52 -19
- package/.next/server/pages/index.html +3 -3
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/A8A_Lmg8cM-Bkf-Jo1CLh/_buildManifest.js +1 -0
- package/.next/static/{yCxjzzYpw5JjJE53PO_s6 → A8A_Lmg8cM-Bkf-Jo1CLh}/_ssgManifest.js +0 -0
- package/.next/static/chunks/317-95ab9051449362fa.js +1 -0
- package/.next/static/chunks/758-eff80059a1365d5d.js +1 -0
- package/.next/static/chunks/pages/_app-0e358b5622cf9e66.js +1 -0
- package/.next/static/chunks/pages/index-0cc5e6eda5adac73.js +1 -0
- package/.next/static/css/9ac903004135f4b1.css +1 -0
- package/.next/trace +42 -42
- package/package.json +12 -12
- package/src/components/Badge/Badge.module.scss +1 -1
- package/src/components/Board/Board.tsx +4 -2
- package/src/components/Board/BoardPure.tsx +25 -5
- package/src/components/Board/hooks/useGrid.ts +212 -91
- package/src/components/Dictionary/Dictionary.tsx +8 -1
- package/src/components/Rack/Rack.tsx +51 -11
- package/src/components/Rack/RackTile.tsx +33 -16
- package/src/components/Results/Results.tsx +19 -3
- package/src/components/SquareButton/Link.tsx +1 -1
- package/src/components/Tile/Tile.module.scss +4 -0
- package/src/components/Tile/Tile.tsx +13 -4
- package/src/components/Tile/TilePure.tsx +3 -4
- package/src/lib/extractCharacters.ts +26 -0
- package/src/lib/extractInputValue.ts +17 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/isCtrl.ts +1 -1
- package/src/lib/memoize.ts +15 -1
- package/src/pages/api/solve.ts +1 -1
- package/src/sdk/fetchJson.ts +36 -0
- package/src/sdk/findWordDefinitions.ts +4 -3
- package/src/sdk/solve.ts +8 -7
- package/src/sdk/verify.ts +5 -6
- package/src/state/sagas.ts +3 -3
- package/src/state/selectors.ts +9 -1
- package/src/state/slices/dictionaryInitialState.ts +10 -2
- package/src/state/slices/dictionarySlice.ts +10 -16
- package/src/state/slices/rackSlice.ts +7 -0
- package/src/state/slices/solveInitialState.ts +14 -2
- package/src/state/slices/solveSlice.ts +7 -4
- package/src/types/index.ts +2 -0
- package/.next/static/chunks/317-a33dd38e9b9a17ed.js +0 -1
- package/.next/static/chunks/758-f333b1dcdb941547.js +0 -1
- package/.next/static/chunks/pages/_app-f8f360878e1c2aff.js +0 -1
- package/.next/static/chunks/pages/index-ecea697d3e5d8a6f.js +0 -1
- package/.next/static/css/64dc2ce1811912f1.css +0 -1
- package/.next/static/yCxjzzYpw5JjJE53PO_s6/_buildManifest.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.10",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@popperjs/core": "^2.11.6",
|
|
32
|
-
"@reduxjs/toolkit": "^1.8.
|
|
33
|
-
"@scrabble-solver/configs": "^2.8.
|
|
34
|
-
"@scrabble-solver/constants": "^2.8.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.8.
|
|
36
|
-
"@scrabble-solver/logger": "^2.8.
|
|
37
|
-
"@scrabble-solver/solver": "^2.8.
|
|
38
|
-
"@scrabble-solver/types": "^2.8.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.8.
|
|
32
|
+
"@reduxjs/toolkit": "^1.8.6",
|
|
33
|
+
"@scrabble-solver/configs": "^2.8.10",
|
|
34
|
+
"@scrabble-solver/constants": "^2.8.10",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.8.10",
|
|
36
|
+
"@scrabble-solver/logger": "^2.8.10",
|
|
37
|
+
"@scrabble-solver/solver": "^2.8.10",
|
|
38
|
+
"@scrabble-solver/types": "^2.8.10",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.8.10",
|
|
40
40
|
"classnames": "^2.3.2",
|
|
41
41
|
"next": "^12.3.1",
|
|
42
42
|
"normalize.css": "^8.0.1",
|
|
43
43
|
"react": "^18.2.0",
|
|
44
44
|
"react-dom": "^18.2.0",
|
|
45
|
-
"react-modal": "^3.
|
|
45
|
+
"react-modal": "^3.16.1",
|
|
46
46
|
"react-popper": "^2.3.0",
|
|
47
47
|
"react-portal": "^4.2.2",
|
|
48
48
|
"react-redux": "^8.0.4",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"uuid": "^9.0.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@svgr/webpack": "^6.
|
|
57
|
+
"@svgr/webpack": "^6.5.0",
|
|
58
58
|
"@types/classnames": "^2.3.0",
|
|
59
59
|
"@types/react": "^18.0.21",
|
|
60
60
|
"@types/react-dom": "^18.0.6",
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"env-cmd": "^10.1.0",
|
|
69
69
|
"sass": "^1.55.0"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "8822c74d0d632ee51bca3bf69ccfc7f517bfadb3"
|
|
72
72
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
justify-content: center;
|
|
5
5
|
padding: var(--spacing--xs) var(--spacing--s);
|
|
6
6
|
border-radius: var(--border--radius);
|
|
7
|
-
background-color: var(--color--
|
|
7
|
+
background-color: var(--color--foreground--secondary);
|
|
8
8
|
box-shadow: va(--box-shadow);
|
|
9
9
|
line-height: var(--line-height);
|
|
10
10
|
font-size: var(--font--size--s);
|
|
@@ -14,20 +14,22 @@ interface Props {
|
|
|
14
14
|
const Board: FunctionComponent<Props> = ({ cellSize, className, innerRef }) => {
|
|
15
15
|
const rows = useTypedSelector(selectRowsWithCandidate);
|
|
16
16
|
const board = useTypedSelector(selectBoard);
|
|
17
|
-
const [{
|
|
17
|
+
const [{ direction, refs }, { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste }] = useGrid(rows);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<BoardPure
|
|
21
21
|
className={className}
|
|
22
22
|
cellSize={cellSize}
|
|
23
23
|
center={board.center}
|
|
24
|
+
direction={direction}
|
|
24
25
|
innerRef={innerRef}
|
|
25
|
-
lastDirection={lastDirection}
|
|
26
26
|
refs={refs}
|
|
27
27
|
rows={rows}
|
|
28
|
+
onChange={onChange}
|
|
28
29
|
onDirectionToggle={onDirectionToggle}
|
|
29
30
|
onFocus={onFocus}
|
|
30
31
|
onKeyDown={onKeyDown}
|
|
32
|
+
onPaste={onPaste}
|
|
31
33
|
/>
|
|
32
34
|
);
|
|
33
35
|
};
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { Cell } from '@scrabble-solver/types';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ChangeEventHandler,
|
|
5
|
+
ClipboardEventHandler,
|
|
6
|
+
FunctionComponent,
|
|
7
|
+
KeyboardEventHandler,
|
|
8
|
+
memo,
|
|
9
|
+
Ref,
|
|
10
|
+
RefObject,
|
|
11
|
+
} from 'react';
|
|
12
|
+
|
|
13
|
+
import { Direction } from 'types';
|
|
4
14
|
|
|
5
15
|
import styles from './Board.module.scss';
|
|
6
16
|
import { Cell as CellComponent } from './components';
|
|
@@ -9,35 +19,45 @@ interface Props {
|
|
|
9
19
|
className?: string;
|
|
10
20
|
cellSize: number;
|
|
11
21
|
center: Cell;
|
|
22
|
+
direction: Direction;
|
|
12
23
|
innerRef?: Ref<HTMLDivElement>;
|
|
13
|
-
lastDirection: 'horizontal' | 'vertical';
|
|
14
24
|
refs: RefObject<HTMLInputElement>[][];
|
|
15
25
|
rows: Cell[][];
|
|
26
|
+
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
16
27
|
onDirectionToggle: () => void;
|
|
17
28
|
onFocus: (x: number, y: number) => void;
|
|
18
29
|
onKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
30
|
+
onPaste: ClipboardEventHandler<HTMLInputElement>;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
33
|
const BoardPure: FunctionComponent<Props> = ({
|
|
22
34
|
className,
|
|
23
35
|
cellSize,
|
|
24
36
|
center,
|
|
37
|
+
direction,
|
|
25
38
|
innerRef,
|
|
26
|
-
lastDirection,
|
|
27
39
|
refs,
|
|
28
40
|
rows,
|
|
41
|
+
onChange,
|
|
29
42
|
onDirectionToggle,
|
|
30
43
|
onFocus,
|
|
31
44
|
onKeyDown,
|
|
45
|
+
onPaste,
|
|
32
46
|
}) => (
|
|
33
|
-
<div
|
|
47
|
+
<div
|
|
48
|
+
className={classNames(styles.board, className)}
|
|
49
|
+
ref={innerRef}
|
|
50
|
+
onChange={onChange}
|
|
51
|
+
onKeyDown={onKeyDown}
|
|
52
|
+
onPaste={onPaste}
|
|
53
|
+
>
|
|
34
54
|
{rows.map((cells, y) => (
|
|
35
55
|
<div className={styles.row} key={y}>
|
|
36
56
|
{cells.map((cell, x) => (
|
|
37
57
|
<CellComponent
|
|
38
58
|
className={styles.cell}
|
|
39
59
|
cell={cell}
|
|
40
|
-
direction={
|
|
60
|
+
direction={direction}
|
|
41
61
|
inputRef={refs[y][x]}
|
|
42
62
|
isCenter={center.x === x && center.y === y}
|
|
43
63
|
key={x}
|
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
/* eslint-disable max-lines, max-statements */
|
|
2
|
-
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
3
|
-
import { Cell } from '@scrabble-solver/types';
|
|
4
|
-
import {
|
|
2
|
+
import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
|
|
3
|
+
import { Board, Cell } from '@scrabble-solver/types';
|
|
4
|
+
import {
|
|
5
|
+
createRef,
|
|
6
|
+
KeyboardEventHandler,
|
|
7
|
+
RefObject,
|
|
8
|
+
useCallback,
|
|
9
|
+
useMemo,
|
|
10
|
+
useState,
|
|
11
|
+
useRef,
|
|
12
|
+
ChangeEventHandler,
|
|
13
|
+
ChangeEvent,
|
|
14
|
+
ClipboardEventHandler,
|
|
15
|
+
} from 'react';
|
|
5
16
|
import { useDispatch } from 'react-redux';
|
|
6
17
|
import { useLatest } from 'react-use';
|
|
7
18
|
|
|
8
|
-
import { createGridOf, createKeyboardNavigation, isCtrl } from 'lib';
|
|
19
|
+
import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
|
|
9
20
|
import { boardSlice, selectConfig, useTypedSelector } from 'state';
|
|
21
|
+
import { Direction } from 'types';
|
|
10
22
|
|
|
11
23
|
import { getPositionInGrid } from '../lib';
|
|
12
24
|
import { Point } from '../types';
|
|
13
25
|
|
|
26
|
+
const toggleDirection = (direction: Direction) => (direction === 'vertical' ? 'horizontal' : 'vertical');
|
|
27
|
+
|
|
14
28
|
interface State {
|
|
15
|
-
|
|
29
|
+
direction: Direction;
|
|
16
30
|
refs: RefObject<HTMLInputElement>[][];
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
interface Actions {
|
|
20
|
-
|
|
34
|
+
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
21
35
|
onDirectionToggle: () => void;
|
|
36
|
+
onFocus: (x: number, y: number) => void;
|
|
22
37
|
onKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
38
|
+
onPaste: ClipboardEventHandler<HTMLInputElement>;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
41
|
const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
@@ -32,8 +48,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
32
48
|
[width, height],
|
|
33
49
|
);
|
|
34
50
|
const activeIndexRef = useRef<Point>({ x: 0, y: 0 });
|
|
35
|
-
const [
|
|
36
|
-
const
|
|
51
|
+
const [direction, setLastDirection] = useState<Direction>('horizontal');
|
|
52
|
+
const directionRef = useLatest(direction);
|
|
37
53
|
|
|
38
54
|
const changeActiveIndex = useCallback(
|
|
39
55
|
(offsetX: number, offsetY: number) => {
|
|
@@ -52,32 +68,156 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
52
68
|
[refs],
|
|
53
69
|
);
|
|
54
70
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
const onFocus = useCallback((x: number, y: number) => {
|
|
62
|
-
activeIndexRef.current = { x, y };
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
const onMoveFocus = useCallback(
|
|
66
|
-
(direction: 'backward' | 'forward') => {
|
|
67
|
-
const offset = direction === 'forward' ? 1 : -1;
|
|
68
|
-
|
|
69
|
-
if (lastDirectionRef.current === 'horizontal') {
|
|
71
|
+
const moveFocus = useCallback(
|
|
72
|
+
(offset: number) => {
|
|
73
|
+
if (directionRef.current === 'horizontal') {
|
|
70
74
|
changeActiveIndex(offset, 0);
|
|
71
75
|
} else {
|
|
72
76
|
changeActiveIndex(0, offset);
|
|
73
77
|
}
|
|
74
78
|
},
|
|
75
|
-
[changeActiveIndex,
|
|
79
|
+
[changeActiveIndex, directionRef],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const insertValue = useCallback(
|
|
83
|
+
(position: Point, value: string) => {
|
|
84
|
+
const characters = value ? extractCharacters(config, value).filter((character) => character !== BLANK) : [BLANK];
|
|
85
|
+
let board = new Board({ rows: rows.map((row) => row.map((cell) => cell.clone())) });
|
|
86
|
+
let { x, y } = position;
|
|
87
|
+
|
|
88
|
+
const scheduleMoveFocus = () => {
|
|
89
|
+
if (directionRef.current === 'horizontal') {
|
|
90
|
+
++x;
|
|
91
|
+
} else {
|
|
92
|
+
++y;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
characters.forEach((character) => {
|
|
97
|
+
if (x >= config.boardWidth || y >= config.boardHeight) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const canCheckUp = y - 1 > 0;
|
|
102
|
+
const canCheckLeft = x > 0;
|
|
103
|
+
const canCheckRight = x + 1 < width;
|
|
104
|
+
const canCheckDown = y + 1 < height;
|
|
105
|
+
|
|
106
|
+
if (canCheckUp) {
|
|
107
|
+
const cellUp = board.rows[y - 1][x];
|
|
108
|
+
const twoCharacterCandidate = cellUp.tile.character + character;
|
|
109
|
+
|
|
110
|
+
if (!cellUp.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
111
|
+
board = boardSlice.reducer(
|
|
112
|
+
board,
|
|
113
|
+
boardSlice.actions.changeCellValue({ x, y: y - 1, value: twoCharacterCandidate }),
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (canCheckDown) {
|
|
120
|
+
const cellDown = board.rows[y + 1][x];
|
|
121
|
+
const twoCharacterCandidate = character + cellDown.tile.character;
|
|
122
|
+
|
|
123
|
+
if (!cellDown.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
124
|
+
board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
|
|
125
|
+
board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y: y + 1, value: EMPTY_CELL }));
|
|
126
|
+
scheduleMoveFocus();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (canCheckLeft) {
|
|
132
|
+
const cellLeft = board.rows[y][x - 1];
|
|
133
|
+
const twoCharacterCandidate = cellLeft.tile.character + character;
|
|
134
|
+
|
|
135
|
+
if (!cellLeft.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
136
|
+
board = boardSlice.reducer(
|
|
137
|
+
board,
|
|
138
|
+
boardSlice.actions.changeCellValue({ x: x - 1, y, value: twoCharacterCandidate }),
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (canCheckRight) {
|
|
145
|
+
const cellRight = board.rows[y][x + 1];
|
|
146
|
+
const twoCharacterCandidate = character + cellRight.tile.character;
|
|
147
|
+
|
|
148
|
+
if (!cellRight.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
149
|
+
board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
|
|
150
|
+
board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x: x + 1, y, value: EMPTY_CELL }));
|
|
151
|
+
scheduleMoveFocus();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!canCheckDown || !canCheckRight) {
|
|
157
|
+
const cell = board.rows[y][x];
|
|
158
|
+
const twoCharacterCandidate = cell.tile.character + character;
|
|
159
|
+
|
|
160
|
+
if (!cell.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
161
|
+
board = boardSlice.reducer(
|
|
162
|
+
board,
|
|
163
|
+
boardSlice.actions.changeCellValue({ x, y, value: twoCharacterCandidate }),
|
|
164
|
+
);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
|
|
170
|
+
scheduleMoveFocus();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
moveFocus(Math.abs(position.x - x) + Math.abs(position.y - y));
|
|
174
|
+
dispatch(boardSlice.actions.change(board));
|
|
175
|
+
},
|
|
176
|
+
[config, directionRef, dispatch, moveFocus, rows],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const onChange = useCallback(
|
|
180
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
181
|
+
const position = getInputRefPosition(event.target);
|
|
182
|
+
|
|
183
|
+
if (!position) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const value = extractInputValue(event.target);
|
|
188
|
+
|
|
189
|
+
if (!value) {
|
|
190
|
+
dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
|
|
191
|
+
moveFocus(-1);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (value === EMPTY_CELL) {
|
|
196
|
+
const { x, y } = position;
|
|
197
|
+
const cell = rows[y][x];
|
|
198
|
+
|
|
199
|
+
if (cell.hasTile()) {
|
|
200
|
+
dispatch(boardSlice.actions.toggleCellIsBlank(position));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
insertValue(position, value);
|
|
206
|
+
},
|
|
207
|
+
[dispatch, insertValue, moveFocus, rows],
|
|
76
208
|
);
|
|
77
209
|
|
|
210
|
+
const onDirectionToggle = useCallback(() => setLastDirection(toggleDirection), []);
|
|
211
|
+
|
|
212
|
+
const onFocus = useCallback((x: number, y: number) => {
|
|
213
|
+
activeIndexRef.current = { x, y };
|
|
214
|
+
}, []);
|
|
215
|
+
|
|
78
216
|
const onKeyDown = useMemo(() => {
|
|
79
217
|
return createKeyboardNavigation({
|
|
80
218
|
onArrowDown: (event) => {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
|
|
81
221
|
if (isCtrl(event)) {
|
|
82
222
|
onDirectionToggle();
|
|
83
223
|
} else {
|
|
@@ -85,6 +225,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
85
225
|
}
|
|
86
226
|
},
|
|
87
227
|
onArrowLeft: (event) => {
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
|
|
88
230
|
if (isCtrl(event)) {
|
|
89
231
|
onDirectionToggle();
|
|
90
232
|
} else {
|
|
@@ -92,6 +234,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
92
234
|
}
|
|
93
235
|
},
|
|
94
236
|
onArrowRight: (event) => {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
|
|
95
239
|
if (isCtrl(event)) {
|
|
96
240
|
onDirectionToggle();
|
|
97
241
|
} else {
|
|
@@ -99,6 +243,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
99
243
|
}
|
|
100
244
|
},
|
|
101
245
|
onArrowUp: (event) => {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
|
|
102
248
|
if (isCtrl(event)) {
|
|
103
249
|
onDirectionToggle();
|
|
104
250
|
} else {
|
|
@@ -112,8 +258,9 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
112
258
|
return;
|
|
113
259
|
}
|
|
114
260
|
|
|
261
|
+
event.preventDefault();
|
|
115
262
|
dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
|
|
116
|
-
|
|
263
|
+
moveFocus(-1);
|
|
117
264
|
},
|
|
118
265
|
onDelete: (event) => {
|
|
119
266
|
const position = getInputRefPosition(event.target as HTMLInputElement);
|
|
@@ -122,8 +269,9 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
122
269
|
return;
|
|
123
270
|
}
|
|
124
271
|
|
|
272
|
+
event.preventDefault();
|
|
125
273
|
dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
|
|
126
|
-
|
|
274
|
+
moveFocus(1);
|
|
127
275
|
},
|
|
128
276
|
onKeyDown: (event) => {
|
|
129
277
|
const position = getInputRefPosition(event.target as HTMLInputElement);
|
|
@@ -138,6 +286,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
138
286
|
const twoCharacterTile = config.getTwoCharacterTileByPrefix(character);
|
|
139
287
|
|
|
140
288
|
if (isTogglingBlank) {
|
|
289
|
+
event.preventDefault();
|
|
141
290
|
dispatch(boardSlice.actions.toggleCellIsBlank(position));
|
|
142
291
|
return;
|
|
143
292
|
}
|
|
@@ -145,75 +294,26 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
145
294
|
if (isCtrl(event) && twoCharacterTile) {
|
|
146
295
|
event.preventDefault();
|
|
147
296
|
dispatch(boardSlice.actions.changeCellValue({ x, y, value: twoCharacterTile }));
|
|
148
|
-
|
|
297
|
+
moveFocus(1);
|
|
149
298
|
return;
|
|
150
299
|
}
|
|
151
300
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const canCheckUp = y - 1 > 0;
|
|
157
|
-
const canCheckLeft = x > 0;
|
|
158
|
-
const canCheckRight = x + 1 < width;
|
|
159
|
-
const canCheckDown = y + 1 < height;
|
|
160
|
-
|
|
161
|
-
if (canCheckUp) {
|
|
162
|
-
const cellUp = rows[y - 1][x];
|
|
163
|
-
const twoCharacterCandidate = cellUp.tile.character + character;
|
|
164
|
-
|
|
165
|
-
if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
166
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, y: y - 1, value: twoCharacterCandidate }));
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (canCheckDown) {
|
|
172
|
-
const cellDown = rows[y + 1][x];
|
|
173
|
-
const twoCharacterCandidate = character + cellDown.tile.character;
|
|
174
|
-
|
|
175
|
-
if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
176
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, value: character }));
|
|
177
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, y: y + 1, value: EMPTY_CELL }));
|
|
178
|
-
onMoveFocus('forward');
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (canCheckLeft) {
|
|
184
|
-
const cellLeft = rows[y][x - 1];
|
|
185
|
-
const twoCharacterCandidate = cellLeft.tile.character + character;
|
|
186
|
-
|
|
187
|
-
if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
188
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, x: x - 1, value: twoCharacterCandidate }));
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (canCheckRight) {
|
|
194
|
-
const cellRight = rows[y][x + 1];
|
|
195
|
-
const twoCharacterCandidate = character + cellRight.tile.character;
|
|
301
|
+
const cell = rows[y][x];
|
|
302
|
+
const twoCharacterCandidate = cell.tile.character + character;
|
|
196
303
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
304
|
+
if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
dispatch(boardSlice.actions.changeCellValue({ ...position, value: twoCharacterCandidate }));
|
|
307
|
+
moveFocus(1);
|
|
308
|
+
return;
|
|
203
309
|
}
|
|
204
310
|
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, value: twoCharacterCandidate }));
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
311
|
+
if (event.target instanceof HTMLInputElement && event.target.value === event.key) {
|
|
312
|
+
// change event did not fire because the same character was typed over the current one
|
|
313
|
+
// but we still want to move the caret
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
moveFocus(1);
|
|
213
316
|
}
|
|
214
|
-
|
|
215
|
-
dispatch(boardSlice.actions.changeCellValue({ ...position, value: character }));
|
|
216
|
-
onMoveFocus('forward');
|
|
217
317
|
},
|
|
218
318
|
onSpace: (event) => {
|
|
219
319
|
const position = getInputRefPosition(event.target as HTMLInputElement);
|
|
@@ -222,14 +322,35 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
|
|
|
222
322
|
return;
|
|
223
323
|
}
|
|
224
324
|
|
|
325
|
+
event.preventDefault();
|
|
225
326
|
dispatch(boardSlice.actions.toggleCellIsBlank(position));
|
|
226
327
|
},
|
|
227
328
|
});
|
|
228
|
-
}, [changeActiveIndex, config, dispatch,
|
|
329
|
+
}, [changeActiveIndex, config, dispatch, onDirectionToggle, rows]);
|
|
330
|
+
|
|
331
|
+
const onPaste = useCallback<ClipboardEventHandler>(
|
|
332
|
+
(event) => {
|
|
333
|
+
if (!(event.target instanceof HTMLInputElement)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const position = getInputRefPosition(event.target);
|
|
338
|
+
|
|
339
|
+
if (!position) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
|
|
345
|
+
const value = event.clipboardData.getData('text/plain').toLocaleLowerCase();
|
|
346
|
+
insertValue(position, value);
|
|
347
|
+
},
|
|
348
|
+
[insertValue],
|
|
349
|
+
);
|
|
229
350
|
|
|
230
351
|
return [
|
|
231
|
-
{
|
|
232
|
-
{ onDirectionToggle, onFocus, onKeyDown },
|
|
352
|
+
{ direction, refs },
|
|
353
|
+
{ onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
|
|
233
354
|
];
|
|
234
355
|
};
|
|
235
356
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { FunctionComponent } from 'react';
|
|
3
3
|
|
|
4
|
-
import { selectDictionary, useTranslate, useTypedSelector } from 'state';
|
|
4
|
+
import { selectDictionary, selectDictionaryError, useTranslate, useTypedSelector } from 'state';
|
|
5
5
|
|
|
6
6
|
import EmptyState from '../EmptyState';
|
|
7
7
|
import Loading from '../Loading';
|
|
@@ -15,6 +15,7 @@ interface Props {
|
|
|
15
15
|
const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
16
16
|
const translate = useTranslate();
|
|
17
17
|
const { results, isLoading } = useTypedSelector(selectDictionary);
|
|
18
|
+
const error = useTypedSelector(selectDictionaryError);
|
|
18
19
|
const isFirstAllowed = results.length > 0 ? results[0].isAllowed : undefined;
|
|
19
20
|
|
|
20
21
|
return (
|
|
@@ -24,6 +25,12 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
|
24
25
|
[styles.isNotAllowed]: isFirstAllowed === false,
|
|
25
26
|
})}
|
|
26
27
|
>
|
|
28
|
+
{typeof error !== 'undefined' && (
|
|
29
|
+
<EmptyState className={styles.emptyState} type="error">
|
|
30
|
+
{error.message}
|
|
31
|
+
</EmptyState>
|
|
32
|
+
)}
|
|
33
|
+
|
|
27
34
|
{results.map(({ definitions, isAllowed, word }) => (
|
|
28
35
|
<div
|
|
29
36
|
className={classNames(styles.result, {
|